diff --git a/app/app.go b/app/app.go index 644a7281be..fdd1089506 100644 --- a/app/app.go +++ b/app/app.go @@ -98,6 +98,7 @@ import ( wasmappparams "github.com/CosmWasm/wasmd/app/params" "github.com/CosmWasm/wasmd/x/wasm" wasmclient "github.com/CosmWasm/wasmd/x/wasm/client" + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" // unnamed import of statik for swagger UI support _ "github.com/cosmos/cosmos-sdk/client/docs/statik" @@ -667,6 +668,22 @@ func NewWasmApp( app.SetBeginBlocker(app.BeginBlocker) app.SetEndBlocker(app.EndBlocker) + // must be before Loading version + // requires the snapshot store to be created and registered as a BaseAppOption + // see cmd/wasmd/root.go: 206 - 214 approx + if manager := app.SnapshotManager(); manager != nil { + err := manager.RegisterExtensions( + wasmkeeper.NewWasmSnapshotter(app.CommitMultiStore(), &app.wasmKeeper), + ) + if err != nil { + panic(fmt.Errorf("failed to register snapshot extension: %s", err)) + } + } + + app.scopedIBCKeeper = scopedIBCKeeper + app.scopedTransferKeeper = scopedTransferKeeper + app.scopedWasmKeeper = scopedWasmKeeper + if loadLatest { if err := app.LoadLatestVersion(); err != nil { tmos.Exit(fmt.Sprintf("failed to load latest version: %s", err)) @@ -679,9 +696,6 @@ func NewWasmApp( } } - app.scopedIBCKeeper = scopedIBCKeeper - app.scopedTransferKeeper = scopedTransferKeeper - app.scopedWasmKeeper = scopedWasmKeeper return app } diff --git a/app/test_helpers.go b/app/test_helpers.go index 0639df3eb3..512381471f 100644 --- a/app/test_helpers.go +++ b/app/test_helpers.go @@ -5,21 +5,11 @@ import ( "encoding/hex" "encoding/json" "fmt" + "path/filepath" "strconv" "testing" "time" - bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" - minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" - "github.com/stretchr/testify/require" - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/libs/log" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" - tmtypes "github.com/tendermint/tendermint/types" - dbm "github.com/tendermint/tm-db" - - "github.com/CosmWasm/wasmd/x/wasm" - bam "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" codectypes "github.com/cosmos/cosmos-sdk/codec/types" @@ -27,11 +17,22 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" "github.com/cosmos/cosmos-sdk/simapp/helpers" + "github.com/cosmos/cosmos-sdk/snapshots" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/errors" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/log" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + tmtypes "github.com/tendermint/tendermint/types" + dbm "github.com/tendermint/tm-db" + + "github.com/CosmWasm/wasmd/x/wasm" ) // DefaultConsensusParams defines the default Tendermint consensus params used in @@ -53,44 +54,28 @@ var DefaultConsensusParams = &abci.ConsensusParams{ }, } -func setup(withGenesis bool, invCheckPeriod uint, opts ...wasm.Option) (*WasmApp, GenesisState) { +func setup(t testing.TB, withGenesis bool, invCheckPeriod uint, opts ...wasm.Option) (*WasmApp, GenesisState) { + nodeHome := t.TempDir() + snapshotDir := filepath.Join(nodeHome, "data", "snapshots") + snapshotDB, err := sdk.NewLevelDB("metadata", snapshotDir) + require.NoError(t, err) + snapshotStore, err := snapshots.NewStore(snapshotDB, snapshotDir) + require.NoError(t, err) + baseAppOpts := []func(*bam.BaseApp){bam.SetSnapshotStore(snapshotStore), bam.SetSnapshotKeepRecent(2)} db := dbm.NewMemDB() - app := NewWasmApp(log.NewNopLogger(), db, nil, true, map[int64]bool{}, DefaultNodeHome, invCheckPeriod, MakeEncodingConfig(), wasm.EnableAllProposals, EmptyBaseAppOptions{}, opts) + app := NewWasmApp(log.NewNopLogger(), db, nil, true, map[int64]bool{}, nodeHome, invCheckPeriod, MakeEncodingConfig(), wasm.EnableAllProposals, EmptyBaseAppOptions{}, opts, baseAppOpts...) if withGenesis { return app, NewDefaultGenesisState() } return app, GenesisState{} } -// Setup initializes a new WasmApp. A Nop logger is set in WasmApp. -func Setup(isCheckTx bool) *WasmApp { - app, genesisState := setup(!isCheckTx, 5) - if !isCheckTx { - // init chain must be called to stop deliverState from being nil - stateBytes, err := json.MarshalIndent(genesisState, "", " ") - if err != nil { - panic(err) - } - - // Initialize the chain - app.InitChain( - abci.RequestInitChain{ - Validators: []abci.ValidatorUpdate{}, - ConsensusParams: DefaultConsensusParams, - AppStateBytes: stateBytes, - }, - ) - } - - return app -} - // SetupWithGenesisValSet initializes a new WasmApp with a validator set and genesis accounts // that also act as delegators. For simplicity, each validator is bonded with a delegation // of one consensus engine unit (10^6) in the default token of the WasmApp from first genesis // account. A Nop logger is set in WasmApp. func SetupWithGenesisValSet(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs []authtypes.GenesisAccount, opts []wasm.Option, balances ...banktypes.Balance) *WasmApp { - app, genesisState := setup(true, 5, opts...) + app, genesisState := setup(t, true, 5, opts...) // set genesis accounts authGenesis := authtypes.NewGenesisState(authtypes.DefaultParams(), genAccs) genesisState[authtypes.ModuleName] = app.appCodec.MustMarshalJSON(authGenesis) @@ -167,37 +152,9 @@ func SetupWithGenesisValSet(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs return app } -// SetupWithGenesisAccounts initializes a new WasmApp with the provided genesis -// accounts and possible balances. -func SetupWithGenesisAccounts(genAccs []authtypes.GenesisAccount, balances ...banktypes.Balance) *WasmApp { - app, genesisState := setup(true, 0) - authGenesis := authtypes.NewGenesisState(authtypes.DefaultParams(), genAccs) - genesisState[authtypes.ModuleName] = app.appCodec.MustMarshalJSON(authGenesis) - - totalSupply := sdk.NewCoins() - for _, b := range balances { - totalSupply = totalSupply.Add(b.Coins...) - } - - bankGenesis := banktypes.NewGenesisState(banktypes.DefaultGenesisState().Params, balances, totalSupply, []banktypes.Metadata{}) - genesisState[banktypes.ModuleName] = app.appCodec.MustMarshalJSON(bankGenesis) - - stateBytes, err := json.MarshalIndent(genesisState, "", " ") - if err != nil { - panic(err) - } - - app.InitChain( - abci.RequestInitChain{ - Validators: []abci.ValidatorUpdate{}, - ConsensusParams: DefaultConsensusParams, - AppStateBytes: stateBytes, - }, - ) - - app.Commit() - app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: app.LastBlockHeight() + 1}}) - +// SetupWithEmptyStore setup a wasmd app instance with empty DB +func SetupWithEmptyStore(t testing.TB) *WasmApp { + app, _ := setup(t, false, 0) return app } diff --git a/x/wasm/client/cli/tx.go b/x/wasm/client/cli/tx.go index afca1a246a..8f3203ea68 100644 --- a/x/wasm/client/cli/tx.go +++ b/x/wasm/client/cli/tx.go @@ -14,7 +14,7 @@ import ( "github.com/spf13/cobra" flag "github.com/spf13/pflag" - wasmUtils "github.com/CosmWasm/wasmd/x/wasm/client/utils" + "github.com/CosmWasm/wasmd/x/wasm/ioutils" "github.com/CosmWasm/wasmd/x/wasm/types" ) @@ -87,13 +87,13 @@ func parseStoreCodeArgs(file string, sender sdk.AccAddress, flags *flag.FlagSet) } // gzip the wasm file - if wasmUtils.IsWasm(wasm) { - wasm, err = wasmUtils.GzipIt(wasm) + if ioutils.IsWasm(wasm) { + wasm, err = ioutils.GzipIt(wasm) if err != nil { return types.MsgStoreCode{}, err } - } else if !wasmUtils.IsGzip(wasm) { + } else if !ioutils.IsGzip(wasm) { return types.MsgStoreCode{}, fmt.Errorf("invalid input file. Use wasm binary or gzip") } diff --git a/x/wasm/client/rest/tx.go b/x/wasm/client/rest/tx.go index 847875e6b4..17407c4b6a 100644 --- a/x/wasm/client/rest/tx.go +++ b/x/wasm/client/rest/tx.go @@ -10,7 +10,7 @@ import ( "github.com/cosmos/cosmos-sdk/types/rest" "github.com/gorilla/mux" - wasmUtils "github.com/CosmWasm/wasmd/x/wasm/client/utils" + "github.com/CosmWasm/wasmd/x/wasm/ioutils" "github.com/CosmWasm/wasmd/x/wasm/types" ) @@ -55,13 +55,13 @@ func storeCodeHandlerFn(cliCtx client.Context) http.HandlerFunc { wasm := req.WasmBytes // gzip the wasm file - if wasmUtils.IsWasm(wasm) { - wasm, err = wasmUtils.GzipIt(wasm) + if ioutils.IsWasm(wasm) { + wasm, err = ioutils.GzipIt(wasm) if err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - } else if !wasmUtils.IsGzip(wasm) { + } else if !ioutils.IsGzip(wasm) { rest.WriteErrorResponse(w, http.StatusBadRequest, "Invalid input file, use wasm binary or zip") return } diff --git a/x/wasm/keeper/ioutil.go b/x/wasm/ioutils/ioutil.go similarity index 72% rename from x/wasm/keeper/ioutil.go rename to x/wasm/ioutils/ioutil.go index 5bb7bbbf92..9c7bd81f04 100644 --- a/x/wasm/keeper/ioutil.go +++ b/x/wasm/ioutils/ioutil.go @@ -1,4 +1,4 @@ -package keeper +package ioutils import ( "bytes" @@ -9,13 +9,8 @@ import ( "github.com/CosmWasm/wasmd/x/wasm/types" ) -// magic bytes to identify gzip. -// See https://www.ietf.org/rfc/rfc1952.txt -// and https://github.com/golang/go/blob/master/src/net/http/sniff.go#L186 -var gzipIdent = []byte("\x1F\x8B\x08") - -// uncompress returns gzip uncompressed content or given src when not gzip. -func uncompress(src []byte, limit uint64) ([]byte, error) { +// Uncompress returns gzip uncompressed content if input was gzip, or original src otherwise +func Uncompress(src []byte, limit uint64) ([]byte, error) { switch n := uint64(len(src)); { case n < 3: return src, nil diff --git a/x/wasm/keeper/ioutil_test.go b/x/wasm/ioutils/ioutil_test.go similarity index 91% rename from x/wasm/keeper/ioutil_test.go rename to x/wasm/ioutils/ioutil_test.go index 287e59cb9e..13bbbb110e 100644 --- a/x/wasm/keeper/ioutil_test.go +++ b/x/wasm/ioutils/ioutil_test.go @@ -1,4 +1,4 @@ -package keeper +package ioutils import ( "bytes" @@ -16,10 +16,10 @@ import ( ) func TestUncompress(t *testing.T) { - wasmRaw, err := ioutil.ReadFile("./testdata/hackatom.wasm") + wasmRaw, err := ioutil.ReadFile("../keeper/testdata/hackatom.wasm") require.NoError(t, err) - wasmGzipped, err := ioutil.ReadFile("./testdata/hackatom.wasm.gzip") + wasmGzipped, err := ioutil.ReadFile("../keeper/testdata/hackatom.wasm.gzip") require.NoError(t, err) const maxSize = 400_000 @@ -80,7 +80,7 @@ func TestUncompress(t *testing.T) { } for msg, spec := range specs { t.Run(msg, func(t *testing.T) { - r, err := uncompress(spec.src, maxSize) + r, err := Uncompress(spec.src, maxSize) require.True(t, errors.Is(spec.expError, err), "exp %v got %+v", spec.expError, err) if spec.expError != nil { return diff --git a/x/wasm/client/utils/utils.go b/x/wasm/ioutils/utils.go similarity index 76% rename from x/wasm/client/utils/utils.go rename to x/wasm/ioutils/utils.go index bbe9adc7f9..d4b8abf349 100644 --- a/x/wasm/client/utils/utils.go +++ b/x/wasm/ioutils/utils.go @@ -1,12 +1,17 @@ -package utils +package ioutils import ( "bytes" "compress/gzip" ) +// Note: []byte can never be const as they are inherently mutable var ( + // magic bytes to identify gzip. + // See https://www.ietf.org/rfc/rfc1952.txt + // and https://github.com/golang/go/blob/master/src/net/http/sniff.go#L186 gzipIdent = []byte("\x1F\x8B\x08") + wasmIdent = []byte("\x00\x61\x73\x6D") ) diff --git a/x/wasm/client/utils/utils_test.go b/x/wasm/ioutils/utils_test.go similarity index 94% rename from x/wasm/client/utils/utils_test.go rename to x/wasm/ioutils/utils_test.go index 5dd212ca4a..176e1a1f47 100644 --- a/x/wasm/client/utils/utils_test.go +++ b/x/wasm/ioutils/utils_test.go @@ -1,4 +1,4 @@ -package utils +package ioutils import ( "io/ioutil" @@ -8,7 +8,7 @@ import ( ) func GetTestData() ([]byte, []byte, []byte, error) { - wasmCode, err := ioutil.ReadFile("../../keeper/testdata/hackatom.wasm") + wasmCode, err := ioutil.ReadFile("../keeper/testdata/hackatom.wasm") if err != nil { return nil, nil, nil, err diff --git a/x/wasm/keeper/keeper.go b/x/wasm/keeper/keeper.go index 92db816ccb..a42d16aa2d 100644 --- a/x/wasm/keeper/keeper.go +++ b/x/wasm/keeper/keeper.go @@ -22,6 +22,7 @@ import ( paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" "github.com/tendermint/tendermint/libs/log" + "github.com/CosmWasm/wasmd/x/wasm/ioutils" "github.com/CosmWasm/wasmd/x/wasm/types" ) @@ -158,7 +159,7 @@ func (k Keeper) create(ctx sdk.Context, creator sdk.AccAddress, wasmCode []byte, if !authZ.CanCreateCode(k.getUploadAccessConfig(ctx), creator) { return 0, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "can not create code") } - wasmCode, err = uncompress(wasmCode, uint64(types.MaxWasmSize)) + wasmCode, err = ioutils.Uncompress(wasmCode, uint64(types.MaxWasmSize)) if err != nil { return 0, sdkerrors.Wrap(types.ErrCreateFailed, err.Error()) } @@ -200,7 +201,7 @@ func (k Keeper) storeCodeInfo(ctx sdk.Context, codeID uint64, codeInfo types.Cod } func (k Keeper) importCode(ctx sdk.Context, codeID uint64, codeInfo types.CodeInfo, wasmCode []byte) error { - wasmCode, err := uncompress(wasmCode, uint64(types.MaxWasmSize)) + wasmCode, err := ioutils.Uncompress(wasmCode, uint64(types.MaxWasmSize)) if err != nil { return sdkerrors.Wrap(types.ErrCreateFailed, err.Error()) } diff --git a/x/wasm/keeper/snapshotter.go b/x/wasm/keeper/snapshotter.go new file mode 100644 index 0000000000..a781cb07a3 --- /dev/null +++ b/x/wasm/keeper/snapshotter.go @@ -0,0 +1,153 @@ +package keeper + +import ( + "encoding/hex" + "io" + + snapshot "github.com/cosmos/cosmos-sdk/snapshots/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + protoio "github.com/gogo/protobuf/io" + "github.com/tendermint/tendermint/libs/log" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + + "github.com/CosmWasm/wasmd/x/wasm/ioutils" + "github.com/CosmWasm/wasmd/x/wasm/types" +) + +var _ snapshot.ExtensionSnapshotter = &WasmSnapshotter{} + +// SnapshotFormat format 1 is just gzipped wasm byte code for each item payload. No protobuf envelope, no metadata. +const SnapshotFormat = 1 + +type WasmSnapshotter struct { + wasm *Keeper + cms sdk.MultiStore +} + +func NewWasmSnapshotter(cms sdk.MultiStore, wasm *Keeper) *WasmSnapshotter { + return &WasmSnapshotter{ + wasm: wasm, + cms: cms, + } +} + +func (ws *WasmSnapshotter) SnapshotName() string { + return types.ModuleName +} + +func (ws *WasmSnapshotter) SnapshotFormat() uint32 { + return SnapshotFormat +} + +func (ws *WasmSnapshotter) SupportedFormats() []uint32 { + // If we support older formats, add them here and handle them in Restore + return []uint32{SnapshotFormat} +} + +func (ws *WasmSnapshotter) Snapshot(height uint64, protoWriter protoio.Writer) error { + cacheMS, err := ws.cms.CacheMultiStoreWithVersion(int64(height)) + if err != nil { + return err + } + + ctx := sdk.NewContext(cacheMS, tmproto.Header{}, false, log.NewNopLogger()) + seenBefore := make(map[string]bool) + var rerr error + + ws.wasm.IterateCodeInfos(ctx, func(id uint64, info types.CodeInfo) bool { + // Many code ids may point to the same code hash... only sync it once + hexHash := hex.EncodeToString(info.CodeHash) + // if seenBefore, just skip this one and move to the next + if seenBefore[hexHash] { + return false + } + seenBefore[hexHash] = true + + // load code and abort on error + wasmBytes, err := ws.wasm.GetByteCode(ctx, id) + if err != nil { + rerr = err + return true + } + + compressedWasm, err := ioutils.GzipIt(wasmBytes) + if err != nil { + rerr = err + return true + } + + err = snapshot.WriteExtensionItem(protoWriter, compressedWasm) + if err != nil { + rerr = err + return true + } + + return false + }) + + return rerr +} + +func (ws *WasmSnapshotter) Restore( + height uint64, format uint32, protoReader protoio.Reader, +) (snapshot.SnapshotItem, error) { + if format == SnapshotFormat { + return ws.processAllItems(height, protoReader, restoreV1, finalizeV1) + } + return snapshot.SnapshotItem{}, snapshot.ErrUnknownFormat +} + +func restoreV1(ctx sdk.Context, k *Keeper, compressedCode []byte) error { + wasmCode, err := ioutils.Uncompress(compressedCode, uint64(types.MaxWasmSize)) + if err != nil { + return sdkerrors.Wrap(types.ErrCreateFailed, err.Error()) + } + + // FIXME: check which codeIDs the checksum matches?? + _, err = k.wasmVM.Create(wasmCode) + if err != nil { + return sdkerrors.Wrap(types.ErrCreateFailed, err.Error()) + } + return nil +} + +func finalizeV1(ctx sdk.Context, k *Keeper) error { + // FIXME: ensure all codes have been uploaded? + return k.InitializePinnedCodes(ctx) +} + +func (ws *WasmSnapshotter) processAllItems( + height uint64, + protoReader protoio.Reader, + cb func(sdk.Context, *Keeper, []byte) error, + finalize func(sdk.Context, *Keeper) error, +) (snapshot.SnapshotItem, error) { + ctx := sdk.NewContext(ws.cms, tmproto.Header{Height: int64(height)}, false, log.NewNopLogger()) + + // keep the last item here... if we break, it will either be empty (if we hit io.EOF) + // or contain the last item (if we hit payload == nil) + var item snapshot.SnapshotItem + for { + item = snapshot.SnapshotItem{} + err := protoReader.ReadMsg(&item) + if err == io.EOF { + break + } else if err != nil { + return snapshot.SnapshotItem{}, sdkerrors.Wrap(err, "invalid protobuf message") + } + + // if it is not another ExtensionPayload message, then it is not for us. + // we should return it an let the manager handle this one + payload := item.GetExtensionPayload() + if payload == nil { + break + } + + if err := cb(ctx, ws.wasm, payload.Payload); err != nil { + return snapshot.SnapshotItem{}, sdkerrors.Wrap(err, "processing snapshot item") + } + } + + return item, finalize(ctx, ws.wasm) +} diff --git a/x/wasm/keeper/snapshotter_integration_test.go b/x/wasm/keeper/snapshotter_integration_test.go new file mode 100644 index 0000000000..a6530a296c --- /dev/null +++ b/x/wasm/keeper/snapshotter_integration_test.go @@ -0,0 +1,125 @@ +package keeper_test + +import ( + "crypto/sha256" + "io/ioutil" + "testing" + "time" + + "github.com/CosmWasm/wasmd/x/wasm/types" + + "github.com/stretchr/testify/assert" + + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/stretchr/testify/require" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + tmtypes "github.com/tendermint/tendermint/types" + + "github.com/CosmWasm/wasmd/app" + "github.com/CosmWasm/wasmd/x/wasm/keeper" +) + +func TestSnapshotter(t *testing.T) { + specs := map[string]struct { + wasmFiles []string + }{ + "single contract": { + wasmFiles: []string{"./testdata/reflect.wasm"}, + }, + "multiple contract": { + wasmFiles: []string{"./testdata/reflect.wasm", "./testdata/burner.wasm", "./testdata/reflect.wasm"}, + }, + "duplicate contracts": { + wasmFiles: []string{"./testdata/reflect.wasm", "./testdata/reflect.wasm"}, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + // setup source app + srcWasmApp, genesisAddr := newWasmExampleApp(t) + + // store wasm codes on chain + ctx := srcWasmApp.NewUncachedContext(false, tmproto.Header{ + ChainID: "foo", + Height: srcWasmApp.LastBlockHeight() + 1, + Time: time.Now(), + }) + wasmKeeper := app.NewTestSupport(t, srcWasmApp).WasmKeeper() + contractKeeper := keeper.NewDefaultPermissionKeeper(&wasmKeeper) + + srcCodeIDToChecksum := make(map[uint64][]byte, len(spec.wasmFiles)) + for i, v := range spec.wasmFiles { + wasmCode, err := ioutil.ReadFile(v) + require.NoError(t, err) + codeID, err := contractKeeper.Create(ctx, genesisAddr, wasmCode, nil) + require.NoError(t, err) + require.Equal(t, uint64(i+1), codeID) + hash := sha256.Sum256(wasmCode) + srcCodeIDToChecksum[codeID] = hash[:] + } + // create snapshot + srcWasmApp.Commit() + snapshotHeight := uint64(srcWasmApp.LastBlockHeight()) + snapshot, err := srcWasmApp.SnapshotManager().Create(snapshotHeight) + require.NoError(t, err) + assert.NotNil(t, snapshot) + + // when snapshot imported into dest app instance + destWasmApp := app.SetupWithEmptyStore(t) + require.NoError(t, destWasmApp.SnapshotManager().Restore(*snapshot)) + for i := uint32(0); i < snapshot.Chunks; i++ { + chunkBz, err := srcWasmApp.SnapshotManager().LoadChunk(snapshot.Height, snapshot.Format, i) + require.NoError(t, err) + end, err := destWasmApp.SnapshotManager().RestoreChunk(chunkBz) + require.NoError(t, err) + if end { + break + } + } + + // then all wasm contracts are imported + wasmKeeper = app.NewTestSupport(t, destWasmApp).WasmKeeper() + ctx = destWasmApp.NewUncachedContext(false, tmproto.Header{ + ChainID: "foo", + Height: destWasmApp.LastBlockHeight() + 1, + Time: time.Now(), + }) + + destCodeIDToChecksum := make(map[uint64][]byte, len(spec.wasmFiles)) + wasmKeeper.IterateCodeInfos(ctx, func(id uint64, info types.CodeInfo) bool { + bz, err := wasmKeeper.GetByteCode(ctx, id) + require.NoError(t, err) + hash := sha256.Sum256(bz) + destCodeIDToChecksum[id] = hash[:] + assert.Equal(t, hash[:], info.CodeHash) + return false + }) + assert.Equal(t, srcCodeIDToChecksum, destCodeIDToChecksum) + }) + } +} + +func newWasmExampleApp(t *testing.T) (*app.WasmApp, sdk.AccAddress) { + var senderPrivKey = ed25519.GenPrivKey() + pubKey, err := cryptocodec.ToTmPubKeyInterface(senderPrivKey.PubKey()) + require.NoError(t, err) + + senderAddr := senderPrivKey.PubKey().Address().Bytes() + acc := authtypes.NewBaseAccount(senderAddr, senderPrivKey.PubKey(), 0, 0) + amount, ok := sdk.NewIntFromString("10000000000000000000") + require.True(t, ok) + + balance := banktypes.Balance{ + Address: acc.GetAddress().String(), + Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, amount)), + } + validator := tmtypes.NewValidator(pubKey, 1) + valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{validator}) + wasmApp := app.SetupWithGenesisValSet(t, valSet, []authtypes.GenesisAccount{acc}, nil, balance) + + return wasmApp, senderAddr +} diff --git a/x/wasm/keeper/test_common.go b/x/wasm/keeper/test_common.go index d609771c77..55767c1236 100644 --- a/x/wasm/keeper/test_common.go +++ b/x/wasm/keeper/test_common.go @@ -178,6 +178,7 @@ type TestKeepers struct { Router *baseapp.Router EncodingConfig wasmappparams.EncodingConfig Faucet *TestFaucet + MultiStore sdk.CommitMultiStore } // CreateDefaultTestInput common settings for CreateTestInput @@ -431,6 +432,7 @@ func createTestInput( Router: router, EncodingConfig: encodingConfig, Faucet: faucet, + MultiStore: ms, } return ctx, keepers }