diff --git a/docs/docs/03-light-clients/04-wasm/03-integration.md b/docs/docs/03-light-clients/04-wasm/03-integration.md index 82e70e1ebc6..7e2d63eb474 100644 --- a/docs/docs/03-light-clients/04-wasm/03-integration.md +++ b/docs/docs/03-light-clients/04-wasm/03-integration.md @@ -18,6 +18,8 @@ The sample code below shows the relevant integration points in `app.go` required import ( ... "github.com/cosmos/cosmos-sdk/runtime" + + cmtos "github.com/cometbft/cometbft/libs/os" wasm "github.com/cosmos/ibc-go/modules/light-clients/08-wasm" wasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" @@ -54,13 +56,14 @@ func NewSimApp( ... wasmtypes.StoreKey, ) - // Instantiate 08-wasm's keeper - // This sample code uses a constructor function that - // accepts a pointer to an existing instance of Wasm VM. - // This is the recommended approach when the chain - // also uses `x/wasm`, and then the Wasm VM instance - // can be shared. - // See the section below for more information. + + // Instantiate 08-wasm's keeper + // This sample code uses a constructor function that + // accepts a pointer to an existing instance of Wasm VM. + // This is the recommended approach when the chain + // also uses `x/wasm`, and then the Wasm VM instance + // can be shared. + // See the section below for more information. app.WasmClientKeeper = wasmkeeper.NewKeeperWithVM( appCodec, runtime.NewKVStoreService(keys[wasmtypes.StoreKey]), @@ -106,6 +109,17 @@ func NewSimApp( } } ... + + if loadLatest { + ... + + ctx := app.BaseApp.NewUncachedContext(true, cmtproto.Header{}) + + // Initialize pinned codes in wasmvm as they are not persisted there + if err := wasmkeeper.InitializePinnedCodes(ctx); err != nil { + cmtos.Exit(fmt.Sprintf("failed initialize pinned codes %s", err)) + } + } } ``` @@ -245,4 +259,8 @@ Or alternatively the parameter can be updated via a governance proposal (see at ## Adding snapshot support -In order to use the `08-wasm` module chains are required to register the `WasmSnapshotter` extension in the snapshot manager. This snapshotter takes care of persisting the external state, in the form of contract code, of the Wasm VM instance to disk when the chain is snapshotted. +In order to use the `08-wasm` module chains are required to register the `WasmSnapshotter` extension in the snapshot manager. This snapshotter takes care of persisting the external state, in the form of contract code, of the Wasm VM instance to disk when the chain is snapshotted. [This code](https://github.com/cosmos/ibc-go/blob/2bd29c08fd1fe50b461fc33a25735aa792dc896e/modules/light-clients/08-wasm/testing/simapp/app.go#L768-L776) should be placed in `NewSimApp` function in `app.go`: + +## Pin byte codes at start + +Wasm byte codes should be pinned to the WasmVM cache on every application start, therefore [this code](https://github.com/cosmos/ibc-go/blob/0ed221f687ffce75984bc57402fd678e07aa6cc5/modules/light-clients/08-wasm/testing/simapp/app.go#L821-L826) should be placed in `NewSimApp` function in `app.go`. diff --git a/modules/light-clients/08-wasm/keeper/keeper.go b/modules/light-clients/08-wasm/keeper/keeper.go index 3fb6bec81a6..15cbfcb4a59 100644 --- a/modules/light-clients/08-wasm/keeper/keeper.go +++ b/modules/light-clients/08-wasm/keeper/keeper.go @@ -192,3 +192,18 @@ func (k Keeper) GetWasmClientState(ctx sdk.Context, clientID string) (*types.Cli return wasmClientState, nil } + +// InitializePinnedCodes updates wasmvm to pin to cache all contracts marked as pinned +func InitializePinnedCodes(ctx sdk.Context) error { + checksums, err := types.GetAllChecksums(ctx) + if err != nil { + return err + } + + for _, checksum := range checksums { + if err := ibcwasm.GetVM().Pin(checksum); err != nil { + return err + } + } + return nil +} diff --git a/modules/light-clients/08-wasm/keeper/keeper_test.go b/modules/light-clients/08-wasm/keeper/keeper_test.go index 7beea146c96..a9396c190d1 100644 --- a/modules/light-clients/08-wasm/keeper/keeper_test.go +++ b/modules/light-clients/08-wasm/keeper/keeper_test.go @@ -231,3 +231,71 @@ func (suite *KeeperTestSuite) TestNewKeeper() { }) } } + +func (suite *KeeperTestSuite) TestInitializedPinnedCodes() { + var capturedChecksums []wasmvm.Checksum + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + suite.mockVM.PinFn = func(checksum wasmvm.Checksum) error { + capturedChecksums = append(capturedChecksums, checksum) + return nil + } + }, + nil, + }, + { + "failure: pin error", + func() { + suite.mockVM.PinFn = func(checksum wasmvm.Checksum) error { + return wasmtesting.ErrMockVM + } + }, + wasmtesting.ErrMockVM, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + ctx := suite.chainA.GetContext() + wasmClientKeeper := GetSimApp(suite.chainA).WasmClientKeeper + + contracts := [][]byte{wasmtesting.Code, wasmtesting.CreateMockContract([]byte("gzipped-contract"))} + checksumIDs := make([]types.Checksum, len(contracts)) + signer := authtypes.NewModuleAddress(govtypes.ModuleName).String() + + // store contract on chain + for i, contract := range contracts { + msg := types.NewMsgStoreCode(signer, contract) + + res, err := wasmClientKeeper.StoreCode(ctx, msg) + suite.Require().NoError(err) + + checksumIDs[i] = res.Checksum + } + + // malleate after storing contracts + tc.malleate() + + err := keeper.InitializePinnedCodes(ctx) + + expPass := tc.expError == nil + if expPass { + suite.Require().NoError(err) + suite.ElementsMatch(checksumIDs, capturedChecksums) + } else { + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} diff --git a/modules/light-clients/08-wasm/testing/simapp/app.go b/modules/light-clients/08-wasm/testing/simapp/app.go index dc6b30d7e5e..f343cea2ef8 100644 --- a/modules/light-clients/08-wasm/testing/simapp/app.go +++ b/modules/light-clients/08-wasm/testing/simapp/app.go @@ -102,6 +102,8 @@ import ( stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" abci "github.com/cometbft/cometbft/abci/types" + cmtos "github.com/cometbft/cometbft/libs/os" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" "github.com/cosmos/ibc-go/modules/capability" capabilitykeeper "github.com/cosmos/ibc-go/modules/capability/keeper" @@ -815,6 +817,13 @@ func NewSimApp( if err := app.LoadLatestVersion(); err != nil { panic(fmt.Errorf("error loading last version: %w", err)) } + + ctx := app.BaseApp.NewUncachedContext(true, cmtproto.Header{}) + + // Initialize pinned codes in wasmvm as they are not persisted there + if err := wasmkeeper.InitializePinnedCodes(ctx); err != nil { + cmtos.Exit(fmt.Sprintf("failed initialize pinned codes %s", err)) + } } app.ScopedIBCKeeper = scopedIBCKeeper