@agoric/cosmos 0.34.2-dev-eb7e9eb.0 → 0.35.0-u11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,51 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [0.35.0-u11.0](https://github.com/Agoric/agoric-sdk/compare/@agoric/cosmos@0.34.1...@agoric/cosmos@0.35.0-u11.0) (2023-08-24)
7
+
8
+
9
+ ### ⚠ BREAKING CHANGES
10
+
11
+ * **cosmos:** add required export-dir export cmd option
12
+ * remove deprecated `ag-cosmos-helper`
13
+
14
+ ### Features
15
+
16
+ * **agd:** try harder to find cosmic-swingset ([a1d1666](https://github.com/Agoric/agoric-sdk/commit/a1d1666997c2f5b9c7bf14748f6d9603c0b3c5f9))
17
+ * **cosmic-swingset:** add repair-metadata snapshot restore option ([ebbb982](https://github.com/Agoric/agoric-sdk/commit/ebbb9829f1f845c0932ae92b23d0d43be9a0e196))
18
+ * **cosmic-swingset:** replace import/export options ([393b91b](https://github.com/Agoric/agoric-sdk/commit/393b91baaa25c61364955102e8cfcdcaec90870d))
19
+ * **cosmic-swingset:** use x/swingset for swing-store export data ([3336b62](https://github.com/Agoric/agoric-sdk/commit/3336b62fbb10bd2293a832f8c30e590530d14213))
20
+ * **cosmos:** add required export-dir export cmd option ([8d2571c](https://github.com/Agoric/agoric-sdk/commit/8d2571c51c2fe08d630dd2897d7e5e1b45ab45c9))
21
+ * **cosmos:** fix and migrate swing-store ([6ba1957](https://github.com/Agoric/agoric-sdk/commit/6ba19571688518dcfdc4553a0c822695a61908b1))
22
+ * **cosmos:** KVEntry implements json Marshaler and Unmarshaller ([6d2fe11](https://github.com/Agoric/agoric-sdk/commit/6d2fe11d144c5bbdc1611b59c84b6842e8084cb9))
23
+ * **cosmos:** spawn JS on export command ([fe4eb56](https://github.com/Agoric/agoric-sdk/commit/fe4eb56facf83569aa343f098e97c6229556afa9))
24
+ * **cosmos:** wire new swingset port handler ([3361b25](https://github.com/Agoric/agoric-sdk/commit/3361b25ddaa00116476d3de1107e800499ab5c21))
25
+ * **x/swingset:** add store data to genesis ([df72903](https://github.com/Agoric/agoric-sdk/commit/df729030643a097262ad1393503d380e243107eb))
26
+ * **x/swingset:** add WaitUntilSwingStoreExportDone ([f8acd22](https://github.com/Agoric/agoric-sdk/commit/f8acd22381ff3da4682bcb0cdcf71665095506a4))
27
+ * **x/swingset:** allow taking snapshot latest height ([0c0e742](https://github.com/Agoric/agoric-sdk/commit/0c0e74227d34d49ac7ce76ce8e92715816d5ea6a))
28
+ * **x/swingset:** export swing store in genesis ([e5f9425](https://github.com/Agoric/agoric-sdk/commit/e5f9425e74c7235323cd6b1b88540b73b57a69a6))
29
+ * **x/swingset:** import swing store from genesis state ([2446cf4](https://github.com/Agoric/agoric-sdk/commit/2446cf43bb13aad7de0805cd7e33c966d2e31016))
30
+ * Cosmos upgrade handler calls swingset ([66f7bcc](https://github.com/Agoric/agoric-sdk/commit/66f7bccce7ce30cf5b9e1e5321710567c05723cb))
31
+
32
+
33
+ ### Bug Fixes
34
+
35
+ * **cosmos:** don't init controller before upgrade ([e567c21](https://github.com/Agoric/agoric-sdk/commit/e567c21a224d239c467f740bb937f84b18db9dd7))
36
+ * **cosmos:** module order independent init and bootstrap ([3ce4012](https://github.com/Agoric/agoric-sdk/commit/3ce4012ea99b39b2d6bfd422b1d7ea7a7e904568))
37
+ * **cosmos:** prevent Golang error wrapping stack frame divergence ([1d8acf6](https://github.com/Agoric/agoric-sdk/commit/1d8acf6270cadfbcdafb1081360155260d031ac1))
38
+ * **cosmos:** Support building on Linux aarch64 ([475708e](https://github.com/Agoric/agoric-sdk/commit/475708e63bb95d75184072547ca92586a978c5a0))
39
+ * **x/swingset:** enforce snapshot restore before init ([c946d58](https://github.com/Agoric/agoric-sdk/commit/c946d5866ef956c198d7ea14936eb9904aa272ae))
40
+ * **x/swingset:** guard snapshot restore for concurrency ([5320a30](https://github.com/Agoric/agoric-sdk/commit/5320a30a873455764104e13d89131e30a93a238c))
41
+ * **x/swingset:** switch export/import to replay artifact level ([c037ea3](https://github.com/Agoric/agoric-sdk/commit/c037ea3931877fe4d56df5b82cc7c3eb77a84a53))
42
+ * **x/vstorage:** value can be empty in genesis data ([9a51df5](https://github.com/Agoric/agoric-sdk/commit/9a51df515b87638b869564ab08445a0ce0d55707))
43
+
44
+
45
+ ### Build System
46
+
47
+ * remove deprecated `ag-cosmos-helper` ([ee43112](https://github.com/Agoric/agoric-sdk/commit/ee431121e3f93406896f8a9e7d949fbf1427c44e))
48
+
49
+
50
+
6
51
  ### [0.34.1](https://github.com/Agoric/agoric-sdk/compare/@agoric/cosmos@0.34.0...@agoric/cosmos@0.34.1) (2023-06-09)
7
52
 
8
53
  **Note:** Version bump only for package @agoric/cosmos
package/app/app.go CHANGED
@@ -126,6 +126,14 @@ import (
126
126
 
127
127
  const appName = "agoric"
128
128
 
129
+ // FlagSwingStoreExportDir defines the config flag used to specify where a
130
+ // genesis swing-store export is expected. For start from genesis, the default
131
+ // value is config/swing-store in the home directory. For genesis export, the
132
+ // value is always a "swing-store" directory sibling to the exported
133
+ // genesis.json file.
134
+ // TODO: document this flag in config, likely alongside the genesis path
135
+ const FlagSwingStoreExportDir = "swing-store-export-dir"
136
+
129
137
  var (
130
138
  // DefaultNodeHome default home directories for the application daemon
131
139
  DefaultNodeHome string
@@ -588,6 +596,7 @@ func NewAgoricApp(
588
596
  app.EvidenceKeeper = *evidenceKeeper
589
597
 
590
598
  skipGenesisInvariants := cast.ToBool(appOpts.Get(crisis.FlagSkipGenesisInvariants))
599
+ swingStoreExportDir := cast.ToString(appOpts.Get(FlagSwingStoreExportDir))
591
600
 
592
601
  // NOTE: Any module instantiated in the module manager that is later modified
593
602
  // must be passed by reference here.
@@ -617,7 +626,7 @@ func NewAgoricApp(
617
626
  transferModule,
618
627
  icaModule,
619
628
  vstorage.NewAppModule(app.VstorageKeeper),
620
- swingset.NewAppModule(app.SwingSetKeeper, setBootstrapNeeded, app.ensureControllerInited),
629
+ swingset.NewAppModule(app.SwingSetKeeper, &app.SwingStoreExportsHandler, setBootstrapNeeded, app.ensureControllerInited, swingStoreExportDir),
621
630
  vibcModule,
622
631
  vbankModule,
623
632
  lienModule,
package/cmd/agd/main.go CHANGED
@@ -13,7 +13,7 @@ import (
13
13
 
14
14
  func main() {
15
15
  // We need to delegate to our default app for running the actual chain.
16
- daemoncmd.OnStartHook = func(logger log.Logger) {
16
+ launchVM := func(logger log.Logger) {
17
17
  args := []string{"ag-chain-cosmos", "--home", gaia.DefaultNodeHome}
18
18
  args = append(args, os.Args[1:]...)
19
19
 
@@ -22,12 +22,15 @@ func main() {
22
22
  panic(lookErr)
23
23
  }
24
24
 
25
- logger.Info("Start chain delegating to JS executable", "binary", binary, "args", args)
25
+ logger.Info("agd delegating to JS executable", "binary", binary, "args", args)
26
26
  execErr := syscall.Exec(binary, args, os.Environ())
27
27
  if execErr != nil {
28
28
  panic(execErr)
29
29
  }
30
30
  }
31
31
 
32
+ daemoncmd.OnStartHook = launchVM
33
+ daemoncmd.OnExportHook = launchVM
34
+
32
35
  daemon.RunWithController(nil)
33
36
  }
@@ -28,6 +28,7 @@ import (
28
28
  genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli"
29
29
  "github.com/spf13/cast"
30
30
  "github.com/spf13/cobra"
31
+ "github.com/spf13/viper"
31
32
  tmcli "github.com/tendermint/tendermint/libs/cli"
32
33
  "github.com/tendermint/tendermint/libs/log"
33
34
  dbm "github.com/tendermint/tm-db"
@@ -40,7 +41,8 @@ import (
40
41
  type Sender func(needReply bool, str string) (string, error)
41
42
 
42
43
  var AppName = "agd"
43
- var OnStartHook func(log.Logger)
44
+ var OnStartHook func(logger log.Logger)
45
+ var OnExportHook func(logger log.Logger)
44
46
 
45
47
  // NewRootCmd creates a new root command for simd. It is called once in the
46
48
  // main function.
@@ -133,6 +135,14 @@ func initRootCmd(sender Sender, rootCmd *cobra.Command, encodingConfig params.En
133
135
  }
134
136
  server.AddCommands(rootCmd, gaia.DefaultNodeHome, ac.newApp, ac.appExport, addModuleInitFlags)
135
137
 
138
+ hasVMController := sender != nil
139
+ for _, command := range rootCmd.Commands() {
140
+ if command.Name() == "export" {
141
+ extendCosmosExportCommand(command, hasVMController)
142
+ break
143
+ }
144
+ }
145
+
136
146
  // add keybase, auxiliary RPC, query, and tx child commands
137
147
  rootCmd.AddCommand(
138
148
  rpc.StatusCommand(),
@@ -232,7 +242,16 @@ func (ac appCreator) newApp(
232
242
  panic(err)
233
243
  }
234
244
 
235
- snapshotDir := filepath.Join(cast.ToString(appOpts.Get(flags.FlagHome)), "data", "snapshots")
245
+ homePath := cast.ToString(appOpts.Get(flags.FlagHome))
246
+
247
+ // Set a default value for FlagSwingStoreExportDir based on the homePath
248
+ // in case we need to InitGenesis with swing-store data
249
+ viper, ok := appOpts.(*viper.Viper)
250
+ if ok && cast.ToString(appOpts.Get(gaia.FlagSwingStoreExportDir)) == "" {
251
+ viper.Set(gaia.FlagSwingStoreExportDir, filepath.Join(homePath, "config", ExportedSwingStoreDirectoryName))
252
+ }
253
+
254
+ snapshotDir := filepath.Join(homePath, "data", "snapshots")
236
255
  snapshotDB, err := sdk.NewLevelDB("metadata", snapshotDir)
237
256
  if err != nil {
238
257
  panic(err)
@@ -245,7 +264,7 @@ func (ac appCreator) newApp(
245
264
  return gaia.NewAgoricApp(
246
265
  ac.sender,
247
266
  logger, db, traceStore, true, skipUpgradeHeights,
248
- cast.ToString(appOpts.Get(flags.FlagHome)),
267
+ homePath,
249
268
  cast.ToUint(appOpts.Get(server.FlagInvCheckPeriod)),
250
269
  ac.encCfg,
251
270
  appOpts,
@@ -263,6 +282,74 @@ func (ac appCreator) newApp(
263
282
  )
264
283
  }
265
284
 
285
+ const (
286
+ // FlagExportDir is the command-line flag for the "export" command specifying
287
+ // where the output of the export should be placed. It contains both the
288
+ // items names below: the genesis file, and a directory containing the
289
+ // exported swing-store artifacts
290
+ FlagExportDir = "export-dir"
291
+ // ExportedGenesisFileName is the file name used to save the genesis in the export-dir
292
+ ExportedGenesisFileName = "genesis.json"
293
+ // ExportedSwingStoreDirectoryName is the directory name used to save the swing-store
294
+ // export (artifacts only) in the export-dir
295
+ ExportedSwingStoreDirectoryName = "swing-store"
296
+ )
297
+
298
+ // extendCosmosExportCommand monkey-patches the "export" command added by
299
+ // cosmos-sdk to add a required "export-dir" command-line flag, and create the
300
+ // genesis export in the specified directory.
301
+ func extendCosmosExportCommand(cmd *cobra.Command, hasVMController bool) {
302
+ cmd.Flags().String(FlagExportDir, "", "The directory where to create the genesis export")
303
+ err := cmd.MarkFlagRequired(FlagExportDir)
304
+ if err != nil {
305
+ panic(err)
306
+ }
307
+
308
+ originalRunE := cmd.RunE
309
+
310
+ extendedRunE := func(cmd *cobra.Command, args []string) error {
311
+ serverCtx := server.GetServerContextFromCmd(cmd)
312
+
313
+ exportDir, _ := cmd.Flags().GetString(FlagExportDir)
314
+ err := os.MkdirAll(exportDir, os.ModePerm)
315
+ if err != nil {
316
+ return err
317
+ }
318
+
319
+ genesisPath := filepath.Join(exportDir, ExportedGenesisFileName)
320
+ swingStoreExportPath := filepath.Join(exportDir, ExportedSwingStoreDirectoryName)
321
+
322
+ err = os.MkdirAll(swingStoreExportPath, os.ModePerm)
323
+ if err != nil {
324
+ return err
325
+ }
326
+ // We unconditionally set FlagSwingStoreExportDir as for export, it makes
327
+ // little sense for users to control this location separately, and we don't
328
+ // want to override any swing-store artifacts that may be associated to the
329
+ // current genesis.
330
+ serverCtx.Viper.Set(gaia.FlagSwingStoreExportDir, swingStoreExportPath)
331
+
332
+ // This will fail is a genesis.json already exists in the export-dir
333
+ genesisFile, err := os.OpenFile(genesisPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, os.ModePerm)
334
+ if err != nil {
335
+ return err
336
+ }
337
+ defer genesisFile.Close()
338
+
339
+ cmd.SetOut(genesisFile)
340
+
341
+ return originalRunE(cmd, args)
342
+ }
343
+
344
+ // Only modify the command handler when we have a VM controller to handle
345
+ // the full export logic. Otherwise, appExport will just exec the VM program
346
+ // (OnExportHook), which will result in re-entering this flow with the VM
347
+ // controller set.
348
+ if hasVMController {
349
+ cmd.RunE = extendedRunE
350
+ }
351
+ }
352
+
266
353
  func (ac appCreator) appExport(
267
354
  logger log.Logger,
268
355
  db dbm.DB,
@@ -272,6 +359,9 @@ func (ac appCreator) appExport(
272
359
  jailAllowedAddrs []string,
273
360
  appOpts servertypes.AppOptions,
274
361
  ) (servertypes.ExportedApp, error) {
362
+ if OnExportHook != nil {
363
+ OnExportHook(logger)
364
+ }
275
365
 
276
366
  homePath, ok := appOpts.Get(flags.FlagHome).(string)
277
367
  if !ok || homePath == "" {
package/git-revision.txt CHANGED
@@ -1 +1 @@
1
- eb7e9eb
1
+ 92b6cd724
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agoric/cosmos",
3
- "version": "0.34.2-dev-eb7e9eb.0+eb7e9eb",
3
+ "version": "0.35.0-u11.0",
4
4
  "description": "Connect JS to the Cosmos blockchain SDK",
5
5
  "parsers": {
6
6
  "js": "mjs"
@@ -19,7 +19,7 @@
19
19
  "postpack": "git clean -f git-revision.txt",
20
20
  "build": "exit 0",
21
21
  "lint-fix": "yarn lint:eslint --fix",
22
- "lint": "exit 0"
22
+ "lint": "eslint '**/*.{cjs,js}'"
23
23
  },
24
24
  "dependencies": {
25
25
  "bindings": "^1.2.1",
@@ -35,5 +35,5 @@
35
35
  "publishConfig": {
36
36
  "access": "public"
37
37
  },
38
- "gitHead": "eb7e9ebe52e78052e5ded601b6658896d257cab4"
38
+ "gitHead": "92b6cd72484079b0349d8ccfa4510aeb820e8d67"
39
39
  }
@@ -9,18 +9,11 @@ option go_package = "github.com/Agoric/agoric-sdk/golang/cosmos/x/vstorage/types
9
9
 
10
10
  // Query defines the gRPC querier service
11
11
  service Query {
12
- // Return the raw string value of an arbitrary vstorage datum.
12
+ // Return an arbitrary vstorage datum.
13
13
  rpc Data(QueryDataRequest) returns (QueryDataResponse) {
14
14
  option (google.api.http).get = "/agoric/vstorage/data/{path}";
15
15
  }
16
16
 
17
- // Return a formatted representation of a vstorage datum that must be
18
- // a valid StreamCell with CapData values, or standalone CapData.
19
- rpc CapData(QueryCapDataRequest)
20
- returns (QueryCapDataResponse) {
21
- option (google.api.http).get = "/agoric/vstorage/capdata/{path}";
22
- }
23
-
24
17
  // Return the children of a given vstorage path.
25
18
  rpc Children(QueryChildrenRequest)
26
19
  returns (QueryChildrenResponse) {
@@ -44,51 +37,6 @@ message QueryDataResponse {
44
37
  ];
45
38
  }
46
39
 
47
- // QueryCapDataRequest contains a path and formatting configuration.
48
- message QueryCapDataRequest {
49
- string path = 1 [
50
- (gogoproto.jsontag) = "path",
51
- (gogoproto.moretags) = "yaml:\"path\""
52
- ];
53
- // mediaType must be an actual media type in the registry at
54
- // https://www.iana.org/assignments/media-types/media-types.xhtml
55
- // or a special value that does not conflict with the media type syntax.
56
- // The only valid value is "JSON Lines", which is also the default.
57
- string media_type = 2 [
58
- (gogoproto.jsontag) = "mediaType",
59
- (gogoproto.moretags) = "yaml:\"mediaType\""
60
- ];
61
- // itemFormat, if present, must be the special value "flat" to indicate that
62
- // the deep structure of each item should be flattened into a single level
63
- // with kebab-case keys (e.g., `{ "metrics": { "min": 0, "max": 88 } }` as
64
- // `{ "metrics-min": 0, "metrics-max": 88 }`).
65
- string item_format = 3 [
66
- (gogoproto.jsontag) = "itemFormat",
67
- (gogoproto.moretags) = "yaml:\"itemFormat\""
68
- ];
69
- // remotableValueFormat indicates how to transform references to opaque but
70
- // distinguishable Remotables into readable embedded representations.
71
- // * "object" represents each Remotable as an `{ id, allegedName }` object, e.g. `{ "id": "board007", "allegedName": "IST brand" }`.
72
- // * "string" represents each Remotable as a string with bracket-wrapped contents including its alleged name and id, e.g. "[Alleged: IST brand <board007>]".
73
- string remotable_value_format = 10 [
74
- (gogoproto.jsontag) = "remotableValueFormat",
75
- (gogoproto.moretags) = "yaml:\"remotableValueFormat\""
76
- ];
77
- }
78
-
79
- // QueryCapDataResponse represents the result with the requested formatting,
80
- // reserving space for future metadata such as media type.
81
- message QueryCapDataResponse {
82
- string block_height = 1 [
83
- (gogoproto.jsontag) = "blockHeight",
84
- (gogoproto.moretags) = "yaml:\"blockHeight\""
85
- ];
86
- string value = 10 [
87
- (gogoproto.jsontag) = "value",
88
- (gogoproto.moretags) = "yaml:\"value\""
89
- ];
90
- }
91
-
92
40
  // QueryChildrenRequest is the vstorage path children query.
93
41
  message QueryChildrenRequest {
94
42
  string path = 1 [
@@ -4,6 +4,8 @@ import (
4
4
  // "os"
5
5
  "fmt"
6
6
 
7
+ agoric "github.com/Agoric/agoric-sdk/golang/cosmos/types"
8
+ "github.com/Agoric/agoric-sdk/golang/cosmos/x/swingset/keeper"
7
9
  "github.com/Agoric/agoric-sdk/golang/cosmos/x/swingset/types"
8
10
  sdk "github.com/cosmos/cosmos-sdk/types"
9
11
  )
@@ -28,33 +30,58 @@ func DefaultGenesisState() *types.GenesisState {
28
30
 
29
31
  // InitGenesis initializes the (Cosmos-side) SwingSet state from the GenesisState.
30
32
  // Returns whether the app should send a bootstrap action to the controller.
31
- func InitGenesis(ctx sdk.Context, keeper Keeper, data *types.GenesisState) bool {
32
- keeper.SetParams(ctx, data.GetParams())
33
- keeper.SetState(ctx, data.GetState())
33
+ func InitGenesis(ctx sdk.Context, k Keeper, swingStoreExportsHandler *SwingStoreExportsHandler, swingStoreExportDir string, data *types.GenesisState) bool {
34
+ k.SetParams(ctx, data.GetParams())
35
+ k.SetState(ctx, data.GetState())
34
36
 
35
37
  swingStoreExportData := data.GetSwingStoreExportData()
36
- if len(swingStoreExportData) > 0 {
37
- // See https://github.com/Agoric/agoric-sdk/issues/6527
38
- panic("genesis with swing-store state not implemented")
38
+ if len(swingStoreExportData) == 0 {
39
+ return true
39
40
  }
40
41
 
41
- // TODO: bootstrap only if not restoring swing-store from genesis state
42
- return true
42
+ artifactProvider, err := keeper.OpenSwingStoreExportDirectory(swingStoreExportDir)
43
+ if err != nil {
44
+ panic(err)
45
+ }
46
+
47
+ swingStore := k.GetSwingStore(ctx)
48
+
49
+ for _, entry := range swingStoreExportData {
50
+ swingStore.Set([]byte(entry.Key), []byte(entry.Value))
51
+ }
52
+
53
+ snapshotHeight := uint64(ctx.BlockHeight())
54
+
55
+ getExportDataReader := func() (agoric.KVEntryReader, error) {
56
+ exportDataIterator := swingStore.Iterator(nil, nil)
57
+ return agoric.NewKVIteratorReader(exportDataIterator), nil
58
+ }
59
+
60
+ err = swingStoreExportsHandler.RestoreExport(
61
+ keeper.SwingStoreExportProvider{
62
+ BlockHeight: snapshotHeight,
63
+ GetExportDataReader: getExportDataReader,
64
+ ReadNextArtifact: artifactProvider.ReadNextArtifact,
65
+ },
66
+ keeper.SwingStoreRestoreOptions{
67
+ ArtifactMode: keeper.SwingStoreArtifactModeReplay,
68
+ ExportDataMode: keeper.SwingStoreExportDataModeAll,
69
+ },
70
+ )
71
+ if err != nil {
72
+ panic(err)
73
+ }
74
+
75
+ return false
43
76
  }
44
77
 
45
- func ExportGenesis(ctx sdk.Context, k Keeper) *types.GenesisState {
78
+ func ExportGenesis(ctx sdk.Context, k Keeper, swingStoreExportsHandler *SwingStoreExportsHandler, swingStoreExportDir string) *types.GenesisState {
46
79
  gs := &types.GenesisState{
47
80
  Params: k.GetParams(ctx),
48
81
  State: k.GetState(ctx),
49
82
  SwingStoreExportData: []*types.SwingStoreExportDataEntry{},
50
83
  }
51
84
 
52
- // Only export the swing-store shadow copy for now
53
- // TODO:
54
- // - perform state-sync export with check blockHeight (figure out how to
55
- // handle export of historical height),
56
- // - include swing-store artifacts in genesis state
57
- // See https://github.com/Agoric/agoric-sdk/issues/6527
58
85
  exportDataIterator := k.GetSwingStore(ctx).Iterator(nil, nil)
59
86
  defer exportDataIterator.Close()
60
87
  for ; exportDataIterator.Valid(); exportDataIterator.Next() {
@@ -64,5 +91,51 @@ func ExportGenesis(ctx sdk.Context, k Keeper) *types.GenesisState {
64
91
  }
65
92
  gs.SwingStoreExportData = append(gs.SwingStoreExportData, &entry)
66
93
  }
94
+
95
+ snapshotHeight := uint64(ctx.BlockHeight())
96
+
97
+ err := swingStoreExportsHandler.InitiateExport(
98
+ // The export will fail if the export of a historical height was requested
99
+ snapshotHeight,
100
+ swingStoreGenesisEventHandler{exportDir: swingStoreExportDir, snapshotHeight: snapshotHeight},
101
+ // The export will fail if the swing-store does not contain all replay artifacts
102
+ keeper.SwingStoreExportOptions{
103
+ ArtifactMode: keeper.SwingStoreArtifactModeReplay,
104
+ ExportDataMode: keeper.SwingStoreExportDataModeSkip,
105
+ },
106
+ )
107
+ if err != nil {
108
+ panic(err)
109
+ }
110
+
111
+ err = keeper.WaitUntilSwingStoreExportDone()
112
+ if err != nil {
113
+ panic(err)
114
+ }
115
+
67
116
  return gs
68
117
  }
118
+
119
+ type swingStoreGenesisEventHandler struct {
120
+ exportDir string
121
+ snapshotHeight uint64
122
+ }
123
+
124
+ func (eventHandler swingStoreGenesisEventHandler) OnExportStarted(height uint64, retrieveSwingStoreExport func() error) error {
125
+ return retrieveSwingStoreExport()
126
+ }
127
+
128
+ func (eventHandler swingStoreGenesisEventHandler) OnExportRetrieved(provider keeper.SwingStoreExportProvider) error {
129
+ if eventHandler.snapshotHeight != provider.BlockHeight {
130
+ return fmt.Errorf("snapshot block height (%d) doesn't match requested height (%d)", provider.BlockHeight, eventHandler.snapshotHeight)
131
+ }
132
+
133
+ artifactsProvider := keeper.SwingStoreExportProvider{
134
+ GetExportDataReader: func() (agoric.KVEntryReader, error) {
135
+ return nil, nil
136
+ },
137
+ ReadNextArtifact: provider.ReadNextArtifact,
138
+ }
139
+
140
+ return keeper.WriteSwingStoreExportToDirectory(artifactsProvider, eventHandler.exportDir)
141
+ }
@@ -638,19 +638,42 @@ func (exportsHandler SwingStoreExportsHandler) retrieveExport(onExportRetrieved
638
638
 
639
639
  defer os.RemoveAll(exportDir)
640
640
 
641
- rawManifest, err := os.ReadFile(filepath.Join(exportDir, ExportManifestFilename))
641
+ provider, err := OpenSwingStoreExportDirectory(exportDir)
642
642
  if err != nil {
643
643
  return err
644
644
  }
645
645
 
646
- var manifest exportManifest
647
- err = json.Unmarshal(rawManifest, &manifest)
646
+ if blockHeight != 0 && provider.BlockHeight != blockHeight {
647
+ return fmt.Errorf("export manifest blockHeight (%d) doesn't match (%d)", provider.BlockHeight, blockHeight)
648
+ }
649
+
650
+ err = onExportRetrieved(provider)
648
651
  if err != nil {
649
652
  return err
650
653
  }
651
654
 
652
- if blockHeight != 0 && manifest.BlockHeight != blockHeight {
653
- return fmt.Errorf("export manifest blockHeight (%d) doesn't match (%d)", manifest.BlockHeight, blockHeight)
655
+ operationDetails.logger.Info("retrieved swing-store export", "exportDir", exportDir)
656
+
657
+ return nil
658
+ }
659
+
660
+ // OpenSwingStoreExportDirectory creates an export provider from a swing-store
661
+ // export saved on disk in the provided directory. It expects the export manifest
662
+ // to be present in that directory. The provider's function will read the
663
+ // export's data and artifacts from disk on demand. Each artifact is using a
664
+ // dedicated file, and the export data is read from a jsonl-like file, if any.
665
+ // The export manifest filename and overall export format is common with the JS
666
+ // swing-store import/export logic.
667
+ func OpenSwingStoreExportDirectory(exportDir string) (SwingStoreExportProvider, error) {
668
+ rawManifest, err := os.ReadFile(filepath.Join(exportDir, ExportManifestFilename))
669
+ if err != nil {
670
+ return SwingStoreExportProvider{}, err
671
+ }
672
+
673
+ var manifest exportManifest
674
+ err = json.Unmarshal(rawManifest, &manifest)
675
+ if err != nil {
676
+ return SwingStoreExportProvider{}, err
654
677
  }
655
678
 
656
679
  getExportDataReader := func() (agoric.KVEntryReader, error) {
@@ -689,18 +712,7 @@ func (exportsHandler SwingStoreExportsHandler) retrieveExport(onExportRetrieved
689
712
  return artifact, err
690
713
  }
691
714
 
692
- err = onExportRetrieved(SwingStoreExportProvider{BlockHeight: manifest.BlockHeight, GetExportDataReader: getExportDataReader, ReadNextArtifact: readNextArtifact})
693
- if err != nil {
694
- return err
695
- }
696
-
697
- // if nextArtifact != len(manifest.Artifacts) {
698
- // return errors.New("not all export artifacts were retrieved")
699
- // }
700
-
701
- operationDetails.logger.Info("retrieved swing-store export", "exportDir", exportDir)
702
-
703
- return nil
715
+ return SwingStoreExportProvider{BlockHeight: manifest.BlockHeight, GetExportDataReader: getExportDataReader, ReadNextArtifact: readNextArtifact}, nil
704
716
  }
705
717
 
706
718
  // RestoreExport restores the JS swing-store using previously exported data and artifacts.
@@ -739,8 +751,41 @@ func (exportsHandler SwingStoreExportsHandler) RestoreExport(provider SwingStore
739
751
  }
740
752
  defer os.RemoveAll(exportDir)
741
753
 
742
- manifest := exportManifest{
754
+ err = WriteSwingStoreExportToDirectory(provider, exportDir)
755
+ if err != nil {
756
+ return err
757
+ }
758
+
759
+ action := &swingStoreRestoreExportAction{
760
+ Type: swingStoreExportActionType,
743
761
  BlockHeight: blockHeight,
762
+ Request: restoreRequest,
763
+ Args: [1]swingStoreImportOptions{{
764
+ ExportDir: exportDir,
765
+ ArtifactMode: restoreOptions.ArtifactMode,
766
+ ExportDataMode: restoreOptions.ExportDataMode,
767
+ }},
768
+ }
769
+
770
+ _, err = exportsHandler.blockingSend(action, true)
771
+ if err != nil {
772
+ return err
773
+ }
774
+
775
+ exportsHandler.logger.Info("restored swing-store export", "exportDir", exportDir, "height", blockHeight)
776
+
777
+ return nil
778
+ }
779
+
780
+ // WriteSwingStoreExportToDirectory consumes a provider and saves a swing-store
781
+ // export to disk in the provided directory. It creates files for each artifact
782
+ // deriving a filename from the artifact name, and stores any "export data" in
783
+ // a jsonl-like file, before saving the export manifest linking these together.
784
+ // The export manifest filename and overall export format is common with the JS
785
+ // swing-store import/export logic.
786
+ func WriteSwingStoreExportToDirectory(provider SwingStoreExportProvider, exportDir string) error {
787
+ manifest := exportManifest{
788
+ BlockHeight: provider.BlockHeight,
744
789
  }
745
790
 
746
791
  exportDataReader, err := provider.GetExportDataReader()
@@ -808,28 +853,5 @@ func (exportsHandler SwingStoreExportsHandler) RestoreExport(provider SwingStore
808
853
  if err != nil {
809
854
  return err
810
855
  }
811
- err = writeExportFile(ExportManifestFilename, manifestBytes)
812
- if err != nil {
813
- return err
814
- }
815
-
816
- action := &swingStoreRestoreExportAction{
817
- Type: swingStoreExportActionType,
818
- BlockHeight: blockHeight,
819
- Request: restoreRequest,
820
- Args: [1]swingStoreImportOptions{{
821
- ExportDir: exportDir,
822
- ArtifactMode: restoreOptions.ArtifactMode,
823
- ExportDataMode: restoreOptions.ExportDataMode,
824
- }},
825
- }
826
-
827
- _, err = exportsHandler.blockingSend(action, true)
828
- if err != nil {
829
- return err
830
- }
831
-
832
- exportsHandler.logger.Info("restored swing-store export", "exportDir", exportDir, "height", blockHeight)
833
-
834
- return nil
856
+ return writeExportFile(ExportManifestFilename, manifestBytes)
835
857
  }