@agoric/cosmos 0.34.2-dev-afe8a6e.0 → 0.34.2-dev-857e650.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/app/app.go CHANGED
@@ -8,6 +8,7 @@ import (
8
8
  "net/http"
9
9
  "os"
10
10
  "path/filepath"
11
+ "runtime/debug"
11
12
  "time"
12
13
 
13
14
  "github.com/cosmos/cosmos-sdk/baseapp"
@@ -21,6 +22,7 @@ import (
21
22
  servertypes "github.com/cosmos/cosmos-sdk/server/types"
22
23
  "github.com/cosmos/cosmos-sdk/simapp"
23
24
  sdk "github.com/cosmos/cosmos-sdk/types"
25
+ "github.com/cosmos/cosmos-sdk/types/errors"
24
26
  "github.com/cosmos/cosmos-sdk/types/module"
25
27
  "github.com/cosmos/cosmos-sdk/version"
26
28
  "github.com/cosmos/cosmos-sdk/x/auth"
@@ -196,6 +198,7 @@ type GaiaApp struct { // nolint: golint
196
198
  interfaceRegistry types.InterfaceRegistry
197
199
 
198
200
  controllerInited bool
201
+ bootstrapNeeded bool
199
202
  lienPort int
200
203
  vbankPort int
201
204
  vibcPort int
@@ -431,17 +434,20 @@ func NewAgoricApp(
431
434
 
432
435
  // This function is tricky to get right, so we build it ourselves.
433
436
  callToController := func(ctx sdk.Context, str string) (string, error) {
437
+ app.CheckControllerInited(true)
434
438
  // We use SwingSet-level metering to charge the user for the call.
435
- app.MustInitController(ctx)
436
439
  defer vm.SetControllerContext(ctx)()
437
440
  return sendToController(true, str)
438
441
  }
439
442
 
443
+ setBootstrapNeeded := func() {
444
+ app.bootstrapNeeded = true
445
+ }
446
+
440
447
  app.VstorageKeeper = vstorage.NewKeeper(
441
448
  keys[vstorage.StoreKey],
442
449
  )
443
- vm.RegisterPortHandler("vstorage", vstorage.NewStorageHandler(app.VstorageKeeper))
444
- app.vstoragePort = vm.GetPort("vstorage")
450
+ app.vstoragePort = vm.RegisterPortHandler("vstorage", vstorage.NewStorageHandler(app.VstorageKeeper))
445
451
 
446
452
  // The SwingSetKeeper is the Keeper from the SwingSet module
447
453
  app.SwingSetKeeper = swingset.NewKeeper(
@@ -453,8 +459,18 @@ func NewAgoricApp(
453
459
 
454
460
  app.SwingSetSnapshotter = swingsetkeeper.NewSwingsetSnapshotter(
455
461
  bApp,
456
- app.SwingSetKeeper,
457
- sendToController,
462
+ app.SwingSetKeeper.ExportSwingStore,
463
+ func(action vm.Jsonable, mustNotBeInited bool) (string, error) {
464
+ if mustNotBeInited {
465
+ app.CheckControllerInited(false)
466
+ }
467
+
468
+ bz, err := json.Marshal(action)
469
+ if err != nil {
470
+ return "", err
471
+ }
472
+ return sendToController(true, string(bz))
473
+ },
458
474
  )
459
475
 
460
476
  app.VibcKeeper = vibc.NewKeeper(
@@ -582,7 +598,7 @@ func NewAgoricApp(
582
598
  transferModule,
583
599
  icaModule,
584
600
  vstorage.NewAppModule(app.VstorageKeeper),
585
- swingset.NewAppModule(app.SwingSetKeeper),
601
+ swingset.NewAppModule(app.SwingSetKeeper, setBootstrapNeeded),
586
602
  vibcModule,
587
603
  vbankModule,
588
604
  lienModule,
@@ -809,57 +825,91 @@ func normalizeModuleAccount(ctx sdk.Context, ak authkeeper.AccountKeeper, name s
809
825
  type cosmosInitAction struct {
810
826
  Type string `json:"type"`
811
827
  ChainID string `json:"chainID"`
828
+ IsBootstrap bool `json:"isBootstrap"`
812
829
  Params swingset.Params `json:"params"`
813
- StoragePort int `json:"storagePort"`
814
830
  SupplyCoins sdk.Coins `json:"supplyCoins"`
815
- VibcPort int `json:"vibcPort"`
816
- VbankPort int `json:"vbankPort"`
817
- LienPort int `json:"lienPort"`
818
831
  UpgradePlan *upgradetypes.Plan `json:"upgradePlan,omitempty"`
832
+ LienPort int `json:"lienPort"`
833
+ StoragePort int `json:"storagePort"`
834
+ VbankPort int `json:"vbankPort"`
835
+ VibcPort int `json:"vibcPort"`
819
836
  }
820
837
 
821
838
  // Name returns the name of the App
822
839
  func (app *GaiaApp) Name() string { return app.BaseApp.Name() }
823
840
 
824
- func (app *GaiaApp) MustInitController(ctx sdk.Context) {
825
- if app.controllerInited {
826
- return
841
+ // CheckControllerInited exits if the controller initialization state does not match `expected`.
842
+ func (app *GaiaApp) CheckControllerInited(expected bool) {
843
+ if app.controllerInited != expected {
844
+ fmt.Fprintf(os.Stderr, "controllerInited != %t\n", expected)
845
+ debug.PrintStack()
846
+ os.Exit(1)
827
847
  }
848
+ }
849
+
850
+ // initController sends the initialization message to the VM.
851
+ // Exits if the controller has already been initialized.
852
+ func (app *GaiaApp) initController(ctx sdk.Context, bootstrap bool) {
853
+ app.CheckControllerInited(false)
828
854
  app.controllerInited = true
829
855
  // Begin initializing the controller here.
830
856
  action := &cosmosInitAction{
831
857
  Type: "AG_COSMOS_INIT",
832
858
  ChainID: ctx.ChainID(),
859
+ IsBootstrap: bootstrap,
833
860
  Params: app.SwingSetKeeper.GetParams(ctx),
834
- StoragePort: app.vstoragePort,
835
861
  SupplyCoins: sdk.NewCoins(app.BankKeeper.GetSupply(ctx, "uist")),
836
- VibcPort: app.vibcPort,
837
- VbankPort: app.vbankPort,
838
- LienPort: app.lienPort,
839
862
  UpgradePlan: app.upgradePlan,
863
+ LienPort: app.lienPort,
864
+ StoragePort: app.vstoragePort,
865
+ VbankPort: app.vbankPort,
866
+ VibcPort: app.vibcPort,
840
867
  }
868
+ // This really abuses `BlockingSend` to get back at `sendToController`
841
869
  out, err := app.SwingSetKeeper.BlockingSend(ctx, action)
842
870
 
843
871
  // fmt.Fprintf(os.Stderr, "AG_COSMOS_INIT Returned from SwingSet: %s, %v\n", out, err)
844
872
 
845
873
  if err != nil {
846
- fmt.Fprintln(os.Stderr, "Cannot initialize Controller", err)
847
- os.Exit(1)
874
+ panic(errors.Wrap(err, "cannot initialize Controller"))
848
875
  }
849
876
  var res bool
850
877
  err = json.Unmarshal([]byte(out), &res)
851
878
  if err != nil {
852
- fmt.Fprintln(os.Stderr, "Cannot unmarshal Controller init response", out, err)
853
- os.Exit(1)
879
+ panic(errors.Wrapf(err, "cannot unmarshal Controller init response: %s", out))
854
880
  }
855
881
  if !res {
856
- fmt.Fprintln(os.Stderr, "Controller negative init response")
857
- os.Exit(1)
882
+ panic(fmt.Errorf("controller negative init response"))
883
+ }
884
+ }
885
+
886
+ type bootstrapBlockAction struct {
887
+ Type string `json:"type"`
888
+ BlockTime int64 `json:"blockTime"`
889
+ }
890
+
891
+ // BootstrapController initializes the controller (with the bootstrap flag) and sends a bootstrap action.
892
+ func (app *GaiaApp) BootstrapController(ctx sdk.Context) error {
893
+ app.initController(ctx, true)
894
+
895
+ stdlog.Println("Running SwingSet until bootstrap is ready")
896
+ // Just run the SwingSet kernel to finish bootstrap and get ready to open for
897
+ // business.
898
+ action := &bootstrapBlockAction{
899
+ Type: "BOOTSTRAP_BLOCK",
900
+ BlockTime: ctx.BlockTime().Unix(),
858
901
  }
902
+
903
+ _, err := app.SwingSetKeeper.BlockingSend(ctx, action)
904
+ return err
859
905
  }
860
906
 
861
907
  // BeginBlocker application updates every begin block
862
908
  func (app *GaiaApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock {
909
+ if !app.controllerInited {
910
+ app.initController(ctx, false)
911
+ }
912
+
863
913
  return app.mm.BeginBlock(ctx, req)
864
914
  }
865
915
 
@@ -878,6 +928,21 @@ func (app *GaiaApp) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci
878
928
  app.UpgradeKeeper.SetModuleVersionMap(ctx, app.mm.GetVersionMap())
879
929
  res := app.mm.InitGenesis(ctx, app.appCodec, genesisState)
880
930
 
931
+ // initialize the provision and reserve module accounts, to avoid their implicit creation
932
+ // as a default account upon receiving a transfer. See BlockedAddrs().
933
+ normalizeModuleAccount(ctx, app.AccountKeeper, vbanktypes.ProvisionPoolName)
934
+ normalizeModuleAccount(ctx, app.AccountKeeper, vbanktypes.ReservePoolName)
935
+
936
+ if app.bootstrapNeeded {
937
+ err := app.BootstrapController(ctx)
938
+ // fmt.Fprintf(os.Stderr, "BOOTSTRAP_BLOCK Returned from swingset: %s, %v\n", out, err)
939
+ if err != nil {
940
+ // NOTE: A failed BOOTSTRAP_BLOCK means that the SwingSet state is inconsistent.
941
+ // Panic here, in the hopes that a replay from scratch will fix the problem.
942
+ panic(err)
943
+ }
944
+ }
945
+
881
946
  // Agoric: report the genesis time explicitly.
882
947
  genTime := req.GetTime()
883
948
  if genTime.After(time.Now()) {
@@ -885,11 +950,6 @@ func (app *GaiaApp) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci
885
950
  stdlog.Printf("Genesis time %s is in %s\n", genTime, d)
886
951
  }
887
952
 
888
- // initialize the provision and reserve module accounts, to avoid their implicit creation
889
- // as a default account upon receiving a transfer. See BockedAddrs().
890
- normalizeModuleAccount(ctx, app.AccountKeeper, vbanktypes.ProvisionPoolName)
891
- normalizeModuleAccount(ctx, app.AccountKeeper, vbanktypes.ReservePoolName)
892
-
893
953
  return res
894
954
  }
895
955
 
package/git-revision.txt CHANGED
@@ -1 +1 @@
1
- afe8a6e
1
+ 857e650
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agoric/cosmos",
3
- "version": "0.34.2-dev-afe8a6e.0+afe8a6e",
3
+ "version": "0.34.2-dev-857e650.0+857e650",
4
4
  "description": "Connect JS to the Cosmos blockchain SDK",
5
5
  "parsers": {
6
6
  "js": "mjs"
@@ -35,5 +35,5 @@
35
35
  "publishConfig": {
36
36
  "access": "public"
37
37
  },
38
- "gitHead": "afe8a6e7881372935ed998ec096ae0c31cfe5f21"
38
+ "gitHead": "857e6508f673ffb20e5efc409a3397616e31093d"
39
39
  }
package/vm/controller.go CHANGED
@@ -8,7 +8,6 @@ import (
8
8
 
9
9
  type ControllerContext struct {
10
10
  Context sdk.Context
11
- StoragePort int
12
11
  IBCChannelHandlerPort int
13
12
  }
14
13
 
@@ -74,7 +73,7 @@ func UnregisterPortHandler(portNum int) error {
74
73
  func ReceiveFromController(portNum int, msg string) (string, error) {
75
74
  handler := portToHandler[portNum]
76
75
  if handler == nil {
77
- return "", fmt.Errorf("Unregistered port %d", portNum)
76
+ return "", fmt.Errorf("unregistered port %d", portNum)
78
77
  }
79
78
  return handler.Receive(&controllerContext, msg)
80
79
  }
@@ -9,13 +9,11 @@ import (
9
9
  sdk "github.com/cosmos/cosmos-sdk/types"
10
10
  abci "github.com/tendermint/tendermint/abci/types"
11
11
 
12
- "github.com/Agoric/agoric-sdk/golang/cosmos/vm"
13
12
  "github.com/Agoric/agoric-sdk/golang/cosmos/x/swingset/types"
14
13
  )
15
14
 
16
15
  type beginBlockAction struct {
17
16
  Type string `json:"type"`
18
- StoragePort int `json:"storagePort"`
19
17
  BlockHeight int64 `json:"blockHeight"`
20
18
  BlockTime int64 `json:"blockTime"`
21
19
  ChainID string `json:"chainID"`
@@ -39,7 +37,6 @@ func BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock, keeper Keeper) erro
39
37
 
40
38
  action := &beginBlockAction{
41
39
  Type: "BEGIN_BLOCK",
42
- StoragePort: vm.GetPort("vstorage"),
43
40
  BlockHeight: ctx.BlockHeight(),
44
41
  BlockTime: ctx.BlockTime().Unix(),
45
42
  ChainID: ctx.ChainID(),
@@ -3,12 +3,9 @@ package swingset
3
3
  import (
4
4
  // "os"
5
5
  "fmt"
6
- stdlog "log"
7
6
 
8
- "github.com/Agoric/agoric-sdk/golang/cosmos/vm"
9
7
  "github.com/Agoric/agoric-sdk/golang/cosmos/x/swingset/types"
10
8
  sdk "github.com/cosmos/cosmos-sdk/types"
11
- abci "github.com/tendermint/tendermint/abci/types"
12
9
  )
13
10
 
14
11
  func NewGenesisState() *types.GenesisState {
@@ -31,40 +28,14 @@ func DefaultGenesisState() *types.GenesisState {
31
28
  }
32
29
  }
33
30
 
34
- type bootstrapBlockAction struct {
35
- Type string `json:"type"`
36
- BlockTime int64 `json:"blockTime"`
37
- StoragePort int `json:"storagePort"`
38
- }
39
-
40
- func BootSwingset(ctx sdk.Context, keeper Keeper) error {
41
- // Just run the SwingSet kernel to finish bootstrap and get ready to open for
42
- // business.
43
- action := &bootstrapBlockAction{
44
- Type: "BOOTSTRAP_BLOCK",
45
- BlockTime: ctx.BlockTime().Unix(),
46
- StoragePort: vm.GetPort("vstorage"),
47
- }
48
-
49
- _, err := keeper.BlockingSend(ctx, action)
50
- return err
51
- }
52
-
53
- func InitGenesis(ctx sdk.Context, keeper Keeper, data *types.GenesisState) []abci.ValidatorUpdate {
31
+ // InitGenesis initializes the (Cosmos-side) SwingSet state from the GenesisState.
32
+ // Returns whether the app should send a bootstrap action to the controller.
33
+ func InitGenesis(ctx sdk.Context, keeper Keeper, data *types.GenesisState) bool {
54
34
  keeper.SetParams(ctx, data.GetParams())
55
35
  keeper.SetState(ctx, data.GetState())
56
36
 
57
- stdlog.Println("Running SwingSet until bootstrap is ready")
58
- err := BootSwingset(ctx, keeper)
59
-
60
- // fmt.Fprintf(os.Stderr, "BOOTSTRAP_BLOCK Returned from swingset: %s, %v\n", out, err)
61
- if err != nil {
62
- // NOTE: A failed BOOTSTRAP_BLOCK means that the SwingSet state is inconsistent.
63
- // Panic here, in the hopes that a replay from scratch will fix the problem.
64
- panic(err)
65
- }
66
-
67
- return []abci.ValidatorUpdate{}
37
+ // TODO: bootstrap only if not restoring swing-store from genesis state
38
+ return true
68
39
  }
69
40
 
70
41
  func ExportGenesis(ctx sdk.Context, k Keeper) *types.GenesisState {
@@ -25,7 +25,6 @@ type deliverInboundAction struct {
25
25
  Peer string `json:"peer"`
26
26
  Messages [][]interface{} `json:"messages"`
27
27
  Ack uint64 `json:"ack"`
28
- StoragePort int `json:"storagePort"`
29
28
  BlockHeight int64 `json:"blockHeight"`
30
29
  BlockTime int64 `json:"blockTime"`
31
30
  }
@@ -58,7 +57,6 @@ func (keeper msgServer) DeliverInbound(goCtx context.Context, msg *types.MsgDeli
58
57
  Peer: msg.Submitter.String(),
59
58
  Messages: messages,
60
59
  Ack: msg.Ack,
61
- StoragePort: vm.GetPort("vstorage"),
62
60
  BlockHeight: ctx.BlockHeight(),
63
61
  BlockTime: ctx.BlockTime().Unix(),
64
62
  }
@@ -41,6 +41,8 @@ func sanitizeArtifactName(name string) string {
41
41
  }
42
42
 
43
43
  type activeSnapshot struct {
44
+ // Whether the operation in progress is a restore
45
+ isRestore bool
44
46
  // The block height of the snapshot in progress
45
47
  height int64
46
48
  // The logger for this snapshot
@@ -62,17 +64,13 @@ type exportManifest struct {
62
64
  Artifacts [][2]string `json:"artifacts"`
63
65
  }
64
66
 
65
- type SwingStoreExporter interface {
66
- ExportSwingStore(ctx sdk.Context) []*vstoragetypes.DataEntry
67
- }
68
-
69
67
  type SwingsetSnapshotter struct {
70
- isConfigured func() bool
71
- takeSnapshot func(height int64)
72
- newRestoreContext func(height int64) sdk.Context
73
- logger log.Logger
74
- exporter SwingStoreExporter
75
- blockingSend func(action vm.Jsonable) (string, error)
68
+ isConfigured func() bool
69
+ takeSnapshot func(height int64)
70
+ newRestoreContext func(height int64) sdk.Context
71
+ logger log.Logger
72
+ getSwingStoreExportData func(ctx sdk.Context) []*vstoragetypes.DataEntry
73
+ blockingSend func(action vm.Jsonable, mustNotBeInited bool) (string, error)
76
74
  // Only modified by the main goroutine.
77
75
  activeSnapshot *activeSnapshot
78
76
  }
@@ -84,49 +82,64 @@ type snapshotAction struct {
84
82
  Args []json.RawMessage `json:"args,omitempty"`
85
83
  }
86
84
 
87
- func NewSwingsetSnapshotter(app *baseapp.BaseApp, exporter SwingStoreExporter, sendToController func(bool, string) (string, error)) SwingsetSnapshotter {
88
- // The sendToController performed by this submodule are non-deterministic.
89
- // This submodule will send messages to JS from goroutines at unpredictable
90
- // times, but this is safe because when handling the messages, the JS side
91
- // does not perform operations affecting consensus and ignores state changes
92
- // since committing the previous block.
93
- // Since this submodule implements block level commit synchronization, the
94
- // processing and results are both insensitive to sub-block timing of messages.
95
-
96
- blockingSend := func(action vm.Jsonable) (string, error) {
97
- bz, err := json.Marshal(action)
98
- if err != nil {
99
- return "", err
100
- }
101
- return sendToController(true, string(bz))
102
- }
103
-
85
+ // NewSwingsetSnapshotter creates a SwingsetSnapshotter which exclusively
86
+ // manages communication with the JS side for Swingset snapshots, ensuring
87
+ // insensitivity to sub-block timing, and enforcing concurrency requirements.
88
+ // The caller of this submodule must arrange block level commit synchronization,
89
+ // to ensure the results are deterministic.
90
+ //
91
+ // Some `blockingSend` calls performed by this submodule are non-deterministic.
92
+ // This submodule will send messages to JS from goroutines at unpredictable
93
+ // times, but this is safe because when handling the messages, the JS side
94
+ // does not perform operations affecting consensus and ignores state changes
95
+ // since committing the previous block.
96
+ // Some other `blockingSend` calls however do change the JS swing-store and
97
+ // must happen before the Swingset controller on the JS side was inited.
98
+ func NewSwingsetSnapshotter(
99
+ app *baseapp.BaseApp,
100
+ getSwingStoreExportData func(ctx sdk.Context) []*vstoragetypes.DataEntry,
101
+ blockingSend func(action vm.Jsonable, mustNotBeInited bool) (string, error),
102
+ ) SwingsetSnapshotter {
104
103
  return SwingsetSnapshotter{
105
104
  isConfigured: func() bool { return app.SnapshotManager() != nil },
106
105
  takeSnapshot: app.Snapshot,
107
106
  newRestoreContext: func(height int64) sdk.Context {
108
107
  return app.NewUncachedContext(false, tmproto.Header{Height: height})
109
108
  },
110
- logger: app.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName), "submodule", "snapshotter"),
111
- exporter: exporter,
112
- blockingSend: blockingSend,
113
- activeSnapshot: nil,
109
+ logger: app.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName), "submodule", "snapshotter"),
110
+ getSwingStoreExportData: getSwingStoreExportData,
111
+ blockingSend: blockingSend,
112
+ activeSnapshot: nil,
114
113
  }
115
114
  }
116
115
 
116
+ // checkNotActive returns an error if there is an active snapshot.
117
+ func (snapshotter *SwingsetSnapshotter) checkNotActive() error {
118
+ active := snapshotter.activeSnapshot
119
+ if active != nil {
120
+ select {
121
+ case <-active.done:
122
+ snapshotter.activeSnapshot = nil
123
+ default:
124
+ if active.isRestore {
125
+ return fmt.Errorf("snapshot restore already in progress for height %d", active.height)
126
+ } else {
127
+ return fmt.Errorf("snapshot already in progress for height %d", active.height)
128
+ }
129
+ }
130
+ }
131
+ return nil
132
+ }
133
+
117
134
  // InitiateSnapshot synchronously initiates a snapshot for the given height.
118
135
  // If a snapshot is already in progress, or if no snapshot manager is configured,
119
136
  // this will fail.
120
137
  // The snapshot operation is performed in a goroutine, and synchronized with the
121
138
  // main thread through the `WaitUntilSnapshotStarted` method.
122
139
  func (snapshotter *SwingsetSnapshotter) InitiateSnapshot(height int64) error {
123
- if snapshotter.activeSnapshot != nil {
124
- select {
125
- case <-snapshotter.activeSnapshot.done:
126
- snapshotter.activeSnapshot = nil
127
- default:
128
- return fmt.Errorf("snapshot already in progress for height %d", snapshotter.activeSnapshot.height)
129
- }
140
+ err := snapshotter.checkNotActive()
141
+ if err != nil {
142
+ return err
130
143
  }
131
144
 
132
145
  if !snapshotter.isConfigured() {
@@ -157,7 +170,7 @@ func (snapshotter *SwingsetSnapshotter) InitiateSnapshot(height int64) error {
157
170
  }
158
171
 
159
172
  // blockingSend for COSMOS_SNAPSHOT action is safe to call from a goroutine
160
- _, err := snapshotter.blockingSend(action)
173
+ _, err := snapshotter.blockingSend(action, false)
161
174
 
162
175
  if err != nil {
163
176
  // First indicate a snapshot is no longer in progress if the call to
@@ -188,7 +201,7 @@ func (snapshotter *SwingsetSnapshotter) InitiateSnapshot(height int64) error {
188
201
  BlockHeight: height,
189
202
  Request: "discard",
190
203
  }
191
- _, err = snapshotter.blockingSend(action)
204
+ _, err = snapshotter.blockingSend(action, false)
192
205
 
193
206
  if err != nil {
194
207
  logger.Error("failed to discard swingset snapshot", "err", err)
@@ -286,7 +299,7 @@ func (snapshotter *SwingsetSnapshotter) SnapshotExtension(height uint64, payload
286
299
  BlockHeight: activeSnapshot.height,
287
300
  Request: "retrieve",
288
301
  }
289
- out, err := snapshotter.blockingSend(action)
302
+ out, err := snapshotter.blockingSend(action, false)
290
303
 
291
304
  if err != nil {
292
305
  return err
@@ -369,6 +382,30 @@ func (snapshotter *SwingsetSnapshotter) RestoreExtension(height uint64, format u
369
382
  return snapshots.ErrUnknownFormat
370
383
  }
371
384
 
385
+ err := snapshotter.checkNotActive()
386
+ if err != nil {
387
+ return err
388
+ }
389
+
390
+ // We technically don't need to create an active snapshot here since both
391
+ // `InitiateSnapshot` and `RestoreExtension` should only be called from the
392
+ // main thread, but it doesn't cost much to add in case things go wrong.
393
+ active := &activeSnapshot{
394
+ isRestore: true,
395
+ height: int64(height),
396
+ logger: snapshotter.logger,
397
+ // goroutine synchronization is unnecessary since anything checking should
398
+ // be called from the same thread.
399
+ // Effectively `WaitUntilSnapshotStarted` would block infinitely and
400
+ // and `InitiateSnapshot` will error when calling `checkNotActive`.
401
+ startedResult: nil,
402
+ done: nil,
403
+ }
404
+ snapshotter.activeSnapshot = active
405
+ defer func() {
406
+ snapshotter.activeSnapshot = nil
407
+ }()
408
+
372
409
  ctx := snapshotter.newRestoreContext(int64(height))
373
410
 
374
411
  exportDir, err := os.MkdirTemp("", fmt.Sprintf("agd-state-sync-restore-%d-*", height))
@@ -392,7 +429,7 @@ func (snapshotter *SwingsetSnapshotter) RestoreExtension(height uint64, format u
392
429
  // At this point the content of the cosmos DB has been verified against the
393
430
  // AppHash, which means the SwingStore data it contains can be used as the
394
431
  // trusted root against which to validate the artifacts.
395
- swingStoreEntries := snapshotter.exporter.ExportSwingStore(ctx)
432
+ swingStoreEntries := snapshotter.getSwingStoreExportData(ctx)
396
433
 
397
434
  if len(swingStoreEntries) > 0 {
398
435
  encoder := json.NewEncoder(exportDataFile)
@@ -480,7 +517,7 @@ func (snapshotter *SwingsetSnapshotter) RestoreExtension(height uint64, format u
480
517
  Args: []json.RawMessage{encodedExportDir},
481
518
  }
482
519
 
483
- _, err = snapshotter.blockingSend(action)
520
+ _, err = snapshotter.blockingSend(action, true)
484
521
  if err != nil {
485
522
  return err
486
523
  }
@@ -2,6 +2,7 @@ package keeper
2
2
 
3
3
  import (
4
4
  "errors"
5
+ "io"
5
6
  "testing"
6
7
 
7
8
  "github.com/Agoric/agoric-sdk/golang/cosmos/vm"
@@ -16,7 +17,7 @@ func newTestSnapshotter() SwingsetSnapshotter {
16
17
  takeSnapshot: func(height int64) {},
17
18
  newRestoreContext: func(height int64) sdk.Context { return sdk.Context{} },
18
19
  logger: logger,
19
- blockingSend: func(action vm.Jsonable) (string, error) { return "", nil },
20
+ blockingSend: func(action vm.Jsonable, mustNotBeInited bool) (string, error) { return "", nil },
20
21
  }
21
22
  }
22
23
 
@@ -40,6 +41,15 @@ func TestSnapshotInProgress(t *testing.T) {
40
41
  t.Error("wanted error for snapshot in progress")
41
42
  }
42
43
 
44
+ err = swingsetSnapshotter.RestoreExtension(
45
+ 456, SnapshotFormat,
46
+ func() ([]byte, error) {
47
+ return nil, io.EOF
48
+ })
49
+ if err == nil {
50
+ t.Error("wanted error for snapshot in progress")
51
+ }
52
+
43
53
  close(ch)
44
54
  <-swingsetSnapshotter.activeSnapshot.done
45
55
  err = swingsetSnapshotter.InitiateSnapshot(456)
@@ -89,7 +99,7 @@ func TestSecondCommit(t *testing.T) {
89
99
 
90
100
  func TestInitiateFails(t *testing.T) {
91
101
  swingsetSnapshotter := newTestSnapshotter()
92
- swingsetSnapshotter.blockingSend = func(action vm.Jsonable) (string, error) {
102
+ swingsetSnapshotter.blockingSend = func(action vm.Jsonable, mustNotBeInited bool) (string, error) {
93
103
  if action.(*snapshotAction).Request == "initiate" {
94
104
  return "", errors.New("initiate failed")
95
105
  }
@@ -116,7 +126,7 @@ func TestInitiateFails(t *testing.T) {
116
126
 
117
127
  func TestRetrievalFails(t *testing.T) {
118
128
  swingsetSnapshotter := newTestSnapshotter()
119
- swingsetSnapshotter.blockingSend = func(action vm.Jsonable) (string, error) {
129
+ swingsetSnapshotter.blockingSend = func(action vm.Jsonable, mustNotBeInited bool) (string, error) {
120
130
  if action.(*snapshotAction).Request == "retrieve" {
121
131
  return "", errors.New("retrieve failed")
122
132
  }
@@ -152,7 +162,7 @@ func TestRetrievalFails(t *testing.T) {
152
162
  func TestDiscard(t *testing.T) {
153
163
  discardCalled := false
154
164
  swingsetSnapshotter := newTestSnapshotter()
155
- swingsetSnapshotter.blockingSend = func(action vm.Jsonable) (string, error) {
165
+ swingsetSnapshotter.blockingSend = func(action vm.Jsonable, mustNotBeInited bool) (string, error) {
156
166
  if action.(*snapshotAction).Request == "discard" {
157
167
  discardCalled = true
158
168
  }
@@ -80,14 +80,16 @@ func (AppModuleBasic) GetTxCmd() *cobra.Command {
80
80
 
81
81
  type AppModule struct {
82
82
  AppModuleBasic
83
- keeper Keeper
83
+ keeper Keeper
84
+ setBootstrapNeeded func()
84
85
  }
85
86
 
86
87
  // NewAppModule creates a new AppModule Object
87
- func NewAppModule(k Keeper) AppModule {
88
+ func NewAppModule(k Keeper, setBootstrapNeeded func()) AppModule {
88
89
  am := AppModule{
89
- AppModuleBasic: AppModuleBasic{},
90
- keeper: k,
90
+ AppModuleBasic: AppModuleBasic{},
91
+ keeper: k,
92
+ setBootstrapNeeded: setBootstrapNeeded,
91
93
  }
92
94
  return am
93
95
  }
@@ -147,7 +149,11 @@ func (am AppModule) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) []abci.V
147
149
  func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json.RawMessage) []abci.ValidatorUpdate {
148
150
  var genesisState types.GenesisState
149
151
  cdc.MustUnmarshalJSON(data, &genesisState)
150
- return InitGenesis(ctx, am.keeper, &genesisState)
152
+ bootstrapNeeded := InitGenesis(ctx, am.keeper, &genesisState)
153
+ if bootstrapNeeded {
154
+ am.setBootstrapNeeded()
155
+ }
156
+ return []abci.ValidatorUpdate{}
151
157
  }
152
158
 
153
159
  func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage {
@@ -22,9 +22,6 @@ func ValidateGenesis(data *types.GenesisState) error {
22
22
  if err := types.ValidatePath(entry.Path); err != nil {
23
23
  return fmt.Errorf("genesis vstorage.data entry %q has invalid path format: %s", entry.Path, err)
24
24
  }
25
- if entry.Value == "" {
26
- return fmt.Errorf("genesis vstorage.data entry %q has no data", entry.Path)
27
- }
28
25
  }
29
26
  return nil
30
27
  }
@@ -181,30 +181,6 @@ func (k Keeper) ImportStorage(ctx sdk.Context, entries []*types.DataEntry) {
181
181
  }
182
182
  }
183
183
 
184
- func (k Keeper) MigrateNoDataPlaceholders(ctx sdk.Context) {
185
- store := ctx.KVStore(k.storeKey)
186
-
187
- iterator := sdk.KVStorePrefixIterator(store, nil)
188
-
189
- // Copy empty keys first since cosmos stores do not support writing keys
190
- // while an iterator is open over the domain
191
- emptyKeys := [][]byte{}
192
- for ; iterator.Valid(); iterator.Next() {
193
- rawValue := iterator.Value()
194
- if bytes.Equal(rawValue, types.EncodedDataPrefix) {
195
- key := iterator.Key()
196
- clonedKey := make([]byte, len(key))
197
- copy(clonedKey, key)
198
- emptyKeys = append(emptyKeys, clonedKey)
199
- }
200
- }
201
- iterator.Close()
202
-
203
- for _, key := range emptyKeys {
204
- store.Set(key, types.EncodedNoDataValue)
205
- }
206
- }
207
-
208
184
  func (k Keeper) EmitChange(ctx sdk.Context, change *ProposedChange) {
209
185
  if change.NewValue == change.ValueFromLastBlock {
210
186
  // No change.
@@ -273,44 +273,3 @@ func TestStorageNotify(t *testing.T) {
273
273
  t.Errorf("got after second flush events %#v, want %#v", got, expectedAfterFlushEvents)
274
274
  }
275
275
  }
276
-
277
- func TestStorageMigrate(t *testing.T) {
278
- testKit := makeTestKit()
279
- ctx, keeper := testKit.ctx, testKit.vstorageKeeper
280
-
281
- // Simulate a pre-migration storage with empty string as placeholders
282
- keeper.SetStorage(ctx, types.NewStorageEntry("key1", "value1"))
283
- keeper.SetStorage(ctx, types.NewStorageEntry("key1.child1.grandchild1", "value1grandchild"))
284
- keeper.SetStorage(ctx, types.NewStorageEntry("key1.child1", ""))
285
-
286
- // Do a deep set.
287
- keeper.SetStorage(ctx, types.NewStorageEntry("key2.child2.grandchild2", "value2grandchild"))
288
- keeper.SetStorage(ctx, types.NewStorageEntry("key2.child2.grandchild2a", "value2grandchilda"))
289
- keeper.SetStorage(ctx, types.NewStorageEntry("key2.child2", ""))
290
- keeper.SetStorage(ctx, types.NewStorageEntry("key2", ""))
291
-
292
- keeper.MigrateNoDataPlaceholders(ctx)
293
-
294
- if keeper.HasStorage(ctx, "key1.child1") {
295
- t.Errorf("has key1.child1, want no value")
296
- }
297
- if keeper.HasStorage(ctx, "key2.child2") {
298
- t.Errorf("has key2.child2, want no value")
299
- }
300
- if keeper.HasStorage(ctx, "key2") {
301
- t.Errorf("has key2, want no value")
302
- }
303
-
304
- // Check the export.
305
- expectedExport := []*types.DataEntry{
306
- {Path: "key1", Value: "value1"},
307
- {Path: "key1.child1.grandchild1", Value: "value1grandchild"},
308
- {Path: "key2.child2.grandchild2", Value: "value2grandchild"},
309
- {Path: "key2.child2.grandchild2a", Value: "value2grandchilda"},
310
- }
311
- got := keeper.ExportStorage(ctx)
312
- if !reflect.DeepEqual(got, expectedExport) {
313
- t.Errorf("got export %q, want %q", got, expectedExport)
314
- }
315
- keeper.ImportStorage(ctx, got)
316
- }