@agoric/cosmos 0.34.2-dev-af62279.0 → 0.34.2-dev-22cbeb1.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 +88 -28
- package/git-revision.txt +1 -1
- package/package.json +2 -2
- package/vm/controller.go +1 -2
- package/x/swingset/abci.go +0 -3
- package/x/swingset/genesis.go +5 -34
- package/x/swingset/keeper/msg_server.go +0 -2
- package/x/swingset/keeper/snapshotter.go +80 -43
- package/x/swingset/keeper/snapshotter_test.go +14 -4
- package/x/swingset/module.go +11 -5
- package/x/vstorage/genesis.go +0 -3
- package/x/vstorage/keeper/keeper.go +0 -24
- package/x/vstorage/keeper/keeper_test.go +0 -41
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
|
-
|
|
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
|
-
|
|
825
|
-
|
|
826
|
-
|
|
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
|
-
|
|
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
|
-
|
|
853
|
-
os.Exit(1)
|
|
879
|
+
panic(errors.Wrapf(err, "cannot unmarshal Controller init response: %s", out))
|
|
854
880
|
}
|
|
855
881
|
if !res {
|
|
856
|
-
fmt.
|
|
857
|
-
|
|
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
|
-
|
|
1
|
+
22cbeb1
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agoric/cosmos",
|
|
3
|
-
"version": "0.34.2-dev-
|
|
3
|
+
"version": "0.34.2-dev-22cbeb1.0+22cbeb1",
|
|
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": "
|
|
38
|
+
"gitHead": "22cbeb145d1b74d9d5ceecce7cd4253ba23d534a"
|
|
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("
|
|
76
|
+
return "", fmt.Errorf("unregistered port %d", portNum)
|
|
78
77
|
}
|
|
79
78
|
return handler.Receive(&controllerContext, msg)
|
|
80
79
|
}
|
package/x/swingset/abci.go
CHANGED
|
@@ -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(),
|
package/x/swingset/genesis.go
CHANGED
|
@@ -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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
58
|
-
|
|
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
|
|
71
|
-
takeSnapshot
|
|
72
|
-
newRestoreContext
|
|
73
|
-
logger
|
|
74
|
-
|
|
75
|
-
blockingSend
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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:
|
|
111
|
-
|
|
112
|
-
blockingSend:
|
|
113
|
-
activeSnapshot:
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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.
|
|
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
|
}
|
package/x/swingset/module.go
CHANGED
|
@@ -80,14 +80,16 @@ func (AppModuleBasic) GetTxCmd() *cobra.Command {
|
|
|
80
80
|
|
|
81
81
|
type AppModule struct {
|
|
82
82
|
AppModuleBasic
|
|
83
|
-
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:
|
|
90
|
-
keeper:
|
|
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
|
-
|
|
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 {
|
package/x/vstorage/genesis.go
CHANGED
|
@@ -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
|
-
}
|