Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add snapshotter extension #4723

Merged
merged 32 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3722182
add snapshotter
trinitys7 Sep 10, 2023
ac49db0
defind AbsoluteCodePositionLen and testing
trinitys7 Sep 10, 2023
a1e523e
fix IterateCode
trinitys7 Sep 10, 2023
bf55119
init TestIterateCode
trinitys7 Sep 10, 2023
15d56ae
nits
trinitys7 Sep 10, 2023
6072ed0
add snapshotter manager to simapp
trinitys7 Sep 11, 2023
7030272
Merge remote-tracking branch 'origin/feat/wasm-clients' into snapshotter
trinitys7 Sep 14, 2023
bf6a7ae
Merge branch 'feat/wasm-clients' into snapshotter
crodriguezvega Sep 22, 2023
b5babb3
Fix minor issues, lint.
DimitrisJim Sep 22, 2023
3c43175
Add export_test.go file for exporting functions/methods for use in te…
DimitrisJim Sep 22, 2023
a0ad090
spec -> tc.
DimitrisJim Sep 22, 2023
3a129a0
use ElementsMatch in TestIterateCode
trinitys7 Sep 24, 2023
572d8a1
linting package import
trinitys7 Sep 24, 2023
369d309
Merge branch 'feat/wasm-clients' into trinitys-snapshotter
DimitrisJim Sep 26, 2023
658250b
Refactor to use 08-wasm simapp.
DimitrisJim Sep 26, 2023
38ded82
Merge branch 'feat/wasm-clients' into trinitys-snapshotter
DimitrisJim Oct 19, 2023
86ee5d3
Fix sdk 0.50 issues.
DimitrisJim Oct 19, 2023
80bfaf8
Merge branch 'feat/wasm-clients' into snapshotter
damiannolan Oct 30, 2023
dde9563
Merge branch 'feat/wasm-clients' into snapshotter
damiannolan Oct 31, 2023
47f4745
chore: adapt snapshotter code to obtain registered codeHashes and que…
damiannolan Oct 31, 2023
43b71db
chore: adding godocs to all exported methods of WasmSnapshotter
damiannolan Oct 31, 2023
d639e2d
chore: adding reference link to cosmwasm's x/wasm snapshotter impleme…
damiannolan Oct 31, 2023
0765a17
chore: do not discard codeHash checksum and pin code to in-memory cac…
damiannolan Oct 31, 2023
e818769
Merge branch 'feat/wasm-clients' into snapshotter
damiannolan Oct 31, 2023
cb877a5
make lint-fix
colin-axner Oct 31, 2023
82a813f
Merge branch 'feat/wasm-clients' into snapshotter
DimitrisJim Oct 31, 2023
67eb3e9
Use mockVM with snapshotter.
DimitrisJim Oct 31, 2023
3f492a1
Move default setup for callbacks into constructor.
DimitrisJim Nov 1, 2023
c34461c
Merge branch 'feat/wasm-clients' into snapshotter
damiannolan Nov 1, 2023
bc0b35d
chore: rm finalize func and cleanup error returns
damiannolan Nov 1, 2023
ea57365
chore: update casing on func name and add additional assertions to sn…
damiannolan Nov 1, 2023
7d08468
Merge branch 'feat/wasm-clients' into snapshotter
damiannolan Nov 1, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions modules/light-clients/08-wasm/keeper/export_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package keeper

/*
This file is to allow for unexported functions to be accessible to the testing package.
*/

func GenerateWasmCodeHash(code []byte) []byte {
return generateWasmCodeHash(code)
}
9 changes: 9 additions & 0 deletions modules/light-clients/08-wasm/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (

"github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm"
"github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper"
wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing"
"github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing/simapp"
"github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types"
ibctesting "github.com/cosmos/ibc-go/v8/testing"
Expand All @@ -26,6 +27,8 @@ type KeeperTestSuite struct {

coordinator *ibctesting.Coordinator

// mockVM is a mock wasm VM that implements the WasmEngine interface
mockVM *wasmtesting.MockWasmEngine
chainA *ibctesting.TestChain
}

Expand Down Expand Up @@ -58,6 +61,12 @@ func (suite *KeeperTestSuite) SetupTest() {
types.RegisterQueryServer(queryHelper, GetSimApp(suite.chainA).WasmClientKeeper)
}

func (suite *KeeperTestSuite) SetupSnapshotterWithMockVM() *simapp.SimApp {
suite.mockVM = wasmtesting.NewMockWasmEngine()

return simapp.SetupWithSnapShotter(suite.T(), suite.mockVM)
}

func TestKeeperTestSuite(t *testing.T) {
testifysuite.Run(t, new(KeeperTestSuite))
}
Expand Down
146 changes: 146 additions & 0 deletions modules/light-clients/08-wasm/keeper/snapshotter.go
colin-axner marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package keeper
DimitrisJim marked this conversation as resolved.
Show resolved Hide resolved

import (
"encoding/hex"
"io"

errorsmod "cosmossdk.io/errors"
snapshot "cosmossdk.io/store/snapshots/types"
storetypes "cosmossdk.io/store/types"

sdk "github.com/cosmos/cosmos-sdk/types"

tmproto "github.com/cometbft/cometbft/proto/tendermint/types"

"github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types"
)

var _ snapshot.ExtensionSnapshotter = &WasmSnapshotter{}

// SnapshotFormat defines the default snapshot extension encoding format.
// SnapshotFormat 1 is gzipped wasm byte code for each item payload. No protobuf envelope, no metadata.
const SnapshotFormat = 1

// WasmSnapshotter implements the snapshot.ExtensionSnapshotter interface and is used to
// import and export state maintained within the wasmvm cache.
// NOTE: The following ExtensionSnapshotter has been adapted from CosmWasm's x/wasm:
// https://github.com/CosmWasm/wasmd/blob/v0.43.0/x/wasm/keeper/snapshotter.go
type WasmSnapshotter struct {
cms storetypes.MultiStore
keeper *Keeper
}

// NewWasmSnapshotter creates and returns a new snapshot.ExtensionSnapshotter implementation for the 08-wasm module.
func NewWasmSnapshotter(cms storetypes.MultiStore, keeper *Keeper) snapshot.ExtensionSnapshotter {
return &WasmSnapshotter{
cms: cms,
keeper: keeper,
}
}

// SnapshotName implements the snapshot.ExtensionSnapshotter interface.
// A unique name should be provided such that the implementation can be identified by the manager.
func (*WasmSnapshotter) SnapshotName() string {
return types.ModuleName
}

// SnapshotFormat implements the snapshot.ExtensionSnapshotter interface.
// This is the default format used for encoding payloads when taking a snapshot.
func (*WasmSnapshotter) SnapshotFormat() uint32 {
return SnapshotFormat
}

// SupportedFormats implements the snapshot.ExtensionSnapshotter interface.
// This defines a list of supported formats the snapshotter extension can restore from.
func (*WasmSnapshotter) SupportedFormats() []uint32 {
return []uint32{SnapshotFormat}
}

// SnapshotExtension implements the snapshot.ExntensionSnapshotter interface.
// SnapshotExtension is used to write data payloads into the underlying protobuf stream from the 08-wasm module.
func (ws *WasmSnapshotter) SnapshotExtension(height uint64, payloadWriter snapshot.ExtensionPayloadWriter) error {
cacheMS, err := ws.cms.CacheMultiStoreWithVersion(int64(height))
if err != nil {
return err
}

ctx := sdk.NewContext(cacheMS, tmproto.Header{}, false, nil)

codeHashes, err := types.GetAllCodeHashes(ctx)
if err != nil {
return err
}

for _, codeHash := range codeHashes {
wasmCode, err := ws.keeper.wasmVM.GetCode(codeHash)
if err != nil {
return err
}

compressedWasm, err := types.GzipIt(wasmCode)
if err != nil {
return err
}

if err = payloadWriter(compressedWasm); err != nil {
return err
}
}

return nil
}

// RestoreExtension implements the snapshot.ExtensionSnapshotter interface.
// RestoreExtension is used to read data from an existing extension state snapshot into the 08-wasm module.
// The payload reader returns io.EOF when it has reached the end of the extension state snapshot.
func (ws *WasmSnapshotter) RestoreExtension(height uint64, format uint32, payloadReader snapshot.ExtensionPayloadReader) error {
if format == ws.SnapshotFormat() {
return ws.processAllItems(height, payloadReader, restoreV1)
}

return errorsmod.Wrapf(snapshot.ErrUnknownFormat, "expected %d, got %d", ws.SnapshotFormat(), format)
}

func restoreV1(ctx sdk.Context, k *Keeper, compressedCode []byte) error {
if !types.IsGzip(compressedCode) {
return errorsmod.Wrap(types.ErrInvalidData, "expected wasm code is not gzip format")
}

wasmCode, err := types.Uncompress(compressedCode, types.MaxWasmByteSize())
if err != nil {
return errorsmod.Wrap(err, "failed to uncompress wasm code")
}

codeHash, err := k.wasmVM.StoreCode(wasmCode)
if err != nil {
return errorsmod.Wrap(err, "failed to store wasm code")
}

if err := k.wasmVM.Pin(codeHash); err != nil {
return errorsmod.Wrapf(err, "failed to pin code hash: %s to in-memory cache", hex.EncodeToString(codeHash))
}

return nil
}

func (ws *WasmSnapshotter) processAllItems(
height uint64,
payloadReader snapshot.ExtensionPayloadReader,
cb func(sdk.Context, *Keeper, []byte) error,
) error {
ctx := sdk.NewContext(ws.cms, tmproto.Header{Height: int64(height)}, false, nil)
for {
payload, err := payloadReader()
if err == io.EOF {
break
} else if err != nil {
return err
}

if err := cb(ctx, ws.keeper, payload); err != nil {
return errorsmod.Wrap(err, "failure processing snapshot item")
}
}

return nil
}
109 changes: 109 additions & 0 deletions modules/light-clients/08-wasm/keeper/snapshotter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package keeper_test

import (
"encoding/hex"
"time"

authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"

tmproto "github.com/cometbft/cometbft/proto/tendermint/types"

"github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper"
wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing"
"github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing/simapp"
"github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types"
)

func (suite *KeeperTestSuite) TestSnapshotter() {
gzippedContract, err := types.GzipIt([]byte("gzipped-contract"))
suite.Require().NoError(err)

testCases := []struct {
name string
contracts [][]byte
}{
{
name: "single contract",
contracts: [][]byte{wasmtesting.Code},
},
{
name: "multiple contracts",
contracts: [][]byte{wasmtesting.Code, gzippedContract},
},
}

for _, tc := range testCases {
tc := tc

suite.Run(tc.name, func() {
t := suite.T()
wasmClientApp := suite.SetupSnapshotterWithMockVM()

ctx := wasmClientApp.NewUncachedContext(false, tmproto.Header{
ChainID: "foo",
Height: wasmClientApp.LastBlockHeight() + 1,
Time: time.Now(),
})

var srcChecksumCodes []byte
var codeHashes [][]byte
// store contract on chain
for _, contract := range tc.contracts {
signer := authtypes.NewModuleAddress(govtypes.ModuleName).String()
msg := types.NewMsgStoreCode(signer, contract)

res, err := wasmClientApp.WasmClientKeeper.StoreCode(ctx, msg)
suite.Require().NoError(err)

codeHashes = append(codeHashes, res.Checksum)
srcChecksumCodes = append(srcChecksumCodes, res.Checksum...)

suite.Require().NoError(err)
}

// create snapshot
res, err := wasmClientApp.Commit()
suite.Require().NoError(err)
suite.Require().NotNil(res)

snapshotHeight := uint64(wasmClientApp.LastBlockHeight())
snapshot, err := wasmClientApp.SnapshotManager().Create(snapshotHeight)
suite.Require().NoError(err)
suite.Require().NotNil(snapshot)

// setup dest app with snapshot imported
destWasmClientApp := simapp.SetupWithEmptyStore(t, suite.mockVM)
colin-axner marked this conversation as resolved.
Show resolved Hide resolved
suite.Require().NoError(destWasmClientApp.SnapshotManager().Restore(*snapshot))

for i := uint32(0); i < snapshot.Chunks; i++ {
chunkBz, err := wasmClientApp.SnapshotManager().LoadChunk(snapshot.Height, snapshot.Format, i)
suite.Require().NoError(err)

end, err := destWasmClientApp.SnapshotManager().RestoreChunk(chunkBz)
suite.Require().NoError(err)

if end {
break
}
}

var allDestAppCodeHashInWasmVMStore []byte
// check wasm contracts are imported
ctx = destWasmClientApp.NewUncachedContext(false, tmproto.Header{
ChainID: "foo",
Height: destWasmClientApp.LastBlockHeight() + 1,
Time: time.Now(),
})

for _, codeHash := range codeHashes {
resp, err := destWasmClientApp.WasmClientKeeper.Code(ctx, &types.QueryCodeRequest{CodeHash: hex.EncodeToString(codeHash)})
suite.Require().NoError(err)

allDestAppCodeHashInWasmVMStore = append(allDestAppCodeHashInWasmVMStore, keeper.GenerateWasmCodeHash(resp.Data)...)
}

suite.Require().Equal(srcChecksumCodes, allDestAppCodeHashInWasmVMStore)
})
}
}
32 changes: 30 additions & 2 deletions modules/light-clients/08-wasm/testing/mock_engine.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package testing

import (
"crypto/sha256"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"reflect"

Expand Down Expand Up @@ -34,8 +37,9 @@ type (

func NewMockWasmEngine() *MockWasmEngine {
m := &MockWasmEngine{
queryCallbacks: map[string]queryFn{},
sudoCallbacks: map[string]sudoFn{},
queryCallbacks: map[string]queryFn{},
sudoCallbacks: map[string]sudoFn{},
storedContracts: map[uint32][]byte{},
}

for _, msgType := range queryTypes {
Expand All @@ -52,6 +56,27 @@ func NewMockWasmEngine() *MockWasmEngine {
}
}

// Set up default behavior for Store/Pin/Get
m.StoreCodeFn = func(code wasmvm.WasmCode) (wasmvm.Checksum, error) {
hash := sha256.Sum256(code)
checkSum := wasmvm.Checksum(hash[:])

m.storedContracts[binary.LittleEndian.Uint32(checkSum)] = code
return checkSum, nil
}

m.PinFn = func(codeID wasmvm.Checksum) error {
return nil
}

m.GetCodeFn = func(codeID wasmvm.Checksum) (wasmvm.WasmCode, error) {
code, ok := m.storedContracts[binary.LittleEndian.Uint32(codeID)]
if !ok {
return nil, errors.New("code not found")
}
return code, nil
}

return m
}

Expand Down Expand Up @@ -85,6 +110,9 @@ type MockWasmEngine struct {
// queryCallbacks contains a mapping of queryMsg field type name to callback function.
queryCallbacks map[string]queryFn
sudoCallbacks map[string]sudoFn

// contracts contains a mapping of code hash to code.
storedContracts map[uint32][]byte
}

// StoreCode implements the WasmEngine interface.
Expand Down
10 changes: 10 additions & 0 deletions modules/light-clients/08-wasm/testing/simapp/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,16 @@ func NewSimApp(
app.SetEndBlocker(app.EndBlocker)
app.setAnteHandler(txConfig)

// must be before Loading version
if manager := app.SnapshotManager(); manager != nil {
err := manager.RegisterExtensions(
wasmkeeper.NewWasmSnapshotter(app.CommitMultiStore(), &app.WasmClientKeeper),
)
if err != nil {
panic(fmt.Errorf("failed to register snapshot extension: %s", err))
}
}

// In v0.46, the SDK introduces _postHandlers_. PostHandlers are like
// antehandlers, but are run _after_ the `runMsgs` execution. They are also
// defined as a chain, and have the same signature as antehandlers.
Expand Down
Loading
Loading