Some checks failed
CodeQL / Analyze (push) Waiting to run
golangci-lint / lint (push) Waiting to run
Tests / Code Coverage / build (amd64) (push) Waiting to run
Tests / Code Coverage / build (arm64) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[additional-args:-tags="test_e2e" name:e2e path:./e2e]) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[name:08-wasm path:./modules/light-clients/08-wasm]) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[name:ibc-go path:.]) (push) Waiting to run
Docker Build & Push Simapp (main) / docker-build (push) Has been cancelled
246 lines
7.7 KiB
Go
246 lines
7.7 KiB
Go
package keeper
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/hex"
|
|
|
|
wasmvm "github.com/CosmWasm/wasmvm/v2"
|
|
|
|
"cosmossdk.io/collections"
|
|
"cosmossdk.io/core/store"
|
|
errorsmod "cosmossdk.io/errors"
|
|
"cosmossdk.io/log"
|
|
|
|
"git.cw.tr/mukan-network/mukan-sdk/codec"
|
|
sdk "git.cw.tr/mukan-network/mukan-sdk/types"
|
|
|
|
"github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types"
|
|
clienttypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/02-client/types"
|
|
"git.cw.tr/mukan-network/mukan-ibc/modules/core/exported"
|
|
)
|
|
|
|
// Keeper defines the 08-wasm keeper
|
|
type Keeper struct {
|
|
// implements gRPC QueryServer interface
|
|
types.QueryServer
|
|
|
|
cdc codec.BinaryCodec
|
|
clientKeeper types.ClientKeeper
|
|
|
|
vm types.WasmEngine
|
|
|
|
checksums collections.KeySet[[]byte]
|
|
storeService store.KVStoreService
|
|
|
|
queryPlugins QueryPlugins
|
|
|
|
authority string
|
|
}
|
|
|
|
// Codec returns the 08-wasm module's codec.
|
|
func (k Keeper) Codec() codec.BinaryCodec {
|
|
return k.cdc
|
|
}
|
|
|
|
// GetAuthority returns the 08-wasm module's authority.
|
|
func (k Keeper) GetAuthority() string {
|
|
return k.authority
|
|
}
|
|
|
|
// Logger returns a module-specific logger.
|
|
func (Keeper) Logger(ctx sdk.Context) log.Logger {
|
|
return moduleLogger(ctx)
|
|
}
|
|
|
|
func moduleLogger(ctx sdk.Context) log.Logger {
|
|
return ctx.Logger().With("module", "x/"+exported.ModuleName+"-"+types.ModuleName)
|
|
}
|
|
|
|
// GetVM returns the keeper's vm engine.
|
|
func (k Keeper) GetVM() types.WasmEngine {
|
|
return k.vm
|
|
}
|
|
|
|
// GetChecksums returns the stored checksums.
|
|
func (k Keeper) GetChecksums() collections.KeySet[[]byte] {
|
|
return k.checksums
|
|
}
|
|
|
|
// getQueryPlugins returns the set query plugins.
|
|
func (k Keeper) getQueryPlugins() QueryPlugins {
|
|
return k.queryPlugins
|
|
}
|
|
|
|
// setQueryPlugins sets the plugins.
|
|
func (k *Keeper) setQueryPlugins(plugins QueryPlugins) {
|
|
k.queryPlugins = plugins
|
|
}
|
|
|
|
func (k Keeper) newQueryHandler(ctx sdk.Context, callerID string) *queryHandler {
|
|
return newQueryHandler(ctx, k.getQueryPlugins(), callerID)
|
|
}
|
|
|
|
// storeWasmCode stores the contract to the VM, pins the checksum in the VM's in memory cache and stores the checksum
|
|
// in the 08-wasm store. The checksum identifying it is returned if successful. The following checks are made to the
|
|
// contract code before storing:
|
|
// - Size bounds are checked. Contract length must not be 0 or exceed a specific size (maxWasmSize).
|
|
// - The contract must not have already been stored in store.
|
|
func (k Keeper) storeWasmCode(ctx sdk.Context, code []byte, storeFn func(code wasmvm.WasmCode, gasLimit uint64) (wasmvm.Checksum, uint64, error)) ([]byte, error) {
|
|
var err error
|
|
if types.IsGzip(code) {
|
|
ctx.GasMeter().ConsumeGas(types.VMGasRegister.UncompressCosts(len(code)), "Uncompress gzip bytecode")
|
|
code, err = types.Uncompress(code, types.MaxWasmSize)
|
|
if err != nil {
|
|
return nil, errorsmod.Wrap(err, "failed to store contract")
|
|
}
|
|
}
|
|
|
|
// run the code through the wasm light client validation process
|
|
if err := types.ValidateWasmCode(code); err != nil {
|
|
return nil, errorsmod.Wrap(err, "wasm bytecode validation failed")
|
|
}
|
|
|
|
// Check to see if store already has checksum.
|
|
checksum, err := types.CreateChecksum(code)
|
|
if err != nil {
|
|
return nil, errorsmod.Wrap(err, "wasm bytecode checksum failed")
|
|
}
|
|
|
|
if k.HasChecksum(ctx, checksum) {
|
|
return nil, types.ErrWasmCodeExists
|
|
}
|
|
|
|
// create the code in the vm
|
|
gasLeft := types.VMGasRegister.RuntimeGasForContract(ctx)
|
|
vmChecksum, gasUsed, err := storeFn(code, gasLeft)
|
|
types.VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed)
|
|
if err != nil {
|
|
return nil, errorsmod.Wrap(err, "failed to store contract")
|
|
}
|
|
|
|
// SANITY: We've checked our store, additional safety check to assert that the checksum returned by WasmVM equals checksum generated by us.
|
|
if !bytes.Equal(vmChecksum, checksum) {
|
|
return nil, errorsmod.Wrapf(types.ErrInvalidChecksum, "expected %s, got %s", hex.EncodeToString(checksum), hex.EncodeToString(vmChecksum))
|
|
}
|
|
|
|
// pin the code to the vm in-memory cache
|
|
if err := k.GetVM().Pin(vmChecksum); err != nil {
|
|
return nil, errorsmod.Wrapf(err, "failed to pin contract with checksum (%s) to vm cache", hex.EncodeToString(vmChecksum))
|
|
}
|
|
|
|
// store the checksum
|
|
err = k.GetChecksums().Set(ctx, checksum)
|
|
if err != nil {
|
|
return nil, errorsmod.Wrap(err, "failed to store checksum")
|
|
}
|
|
|
|
return checksum, nil
|
|
}
|
|
|
|
// migrateContractCode migrates the contract for a given light client to one denoted by the given new checksum. The checksum we
|
|
// are migrating to must first be stored using storeWasmCode and must not match the checksum currently stored for this light client.
|
|
func (k Keeper) migrateContractCode(ctx sdk.Context, clientID string, newChecksum, migrateMsg []byte) error {
|
|
clientStore := k.clientKeeper.ClientStore(ctx, clientID)
|
|
wasmClientState, found := types.GetClientState(clientStore, k.cdc)
|
|
if !found {
|
|
return errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID)
|
|
}
|
|
oldChecksum := wasmClientState.Checksum
|
|
|
|
if !k.HasChecksum(ctx, newChecksum) {
|
|
return types.ErrWasmChecksumNotFound
|
|
}
|
|
|
|
if bytes.Equal(wasmClientState.Checksum, newChecksum) {
|
|
return errorsmod.Wrapf(types.ErrWasmCodeExists, "new checksum (%s) is the same as current checksum (%s)", hex.EncodeToString(newChecksum), hex.EncodeToString(wasmClientState.Checksum))
|
|
}
|
|
|
|
// update the checksum, this needs to be done before the contract migration
|
|
// so that wasmMigrate can call the right code. Note that this is not
|
|
// persisted to the client store.
|
|
wasmClientState.Checksum = newChecksum
|
|
|
|
err := k.WasmMigrate(ctx, clientStore, wasmClientState, clientID, migrateMsg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// client state may be updated by the contract migration
|
|
wasmClientState, err = k.GetWasmClientState(ctx, clientID)
|
|
if err != nil {
|
|
// note that this also ensures that the updated client state is
|
|
// still a wasm client state
|
|
return errorsmod.Wrap(err, "failed to retrieve the updated wasm client state")
|
|
}
|
|
|
|
// update the client state checksum before persisting it
|
|
wasmClientState.Checksum = newChecksum
|
|
|
|
k.clientKeeper.SetClientState(ctx, clientID, wasmClientState)
|
|
|
|
emitMigrateContractEvent(ctx, clientID, oldChecksum, newChecksum)
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetWasmClientState returns the 08-wasm client state for the given client identifier.
|
|
func (k Keeper) GetWasmClientState(ctx sdk.Context, clientID string) (*types.ClientState, error) {
|
|
clientState, found := k.clientKeeper.GetClientState(ctx, clientID)
|
|
if !found {
|
|
return nil, errorsmod.Wrapf(clienttypes.ErrClientTypeNotFound, "clientID %s", clientID)
|
|
}
|
|
|
|
wasmClientState, ok := clientState.(*types.ClientState)
|
|
if !ok {
|
|
return nil, errorsmod.Wrapf(clienttypes.ErrInvalidClient, "expected type %T, got %T", (*types.ClientState)(nil), wasmClientState)
|
|
}
|
|
|
|
return wasmClientState, nil
|
|
}
|
|
|
|
// GetAllChecksums is a helper to get all checksums from the store.
|
|
// It returns an empty slice if no checksums are found
|
|
func (k Keeper) GetAllChecksums(ctx sdk.Context) ([]types.Checksum, error) {
|
|
iterator, err := k.GetChecksums().Iterate(ctx, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
keys, err := iterator.Keys()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
checksums := make([]types.Checksum, 0, len(keys))
|
|
for _, key := range keys {
|
|
checksums = append(checksums, key)
|
|
}
|
|
|
|
return checksums, nil
|
|
}
|
|
|
|
// HasChecksum returns true if the given checksum exists in the store and
|
|
// false otherwise.
|
|
func (k Keeper) HasChecksum(ctx sdk.Context, checksum types.Checksum) bool {
|
|
found, err := k.GetChecksums().Has(ctx, checksum)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
return found
|
|
}
|
|
|
|
// InitializePinnedCodes updates wasmvm to pin to cache all contracts marked as pinned
|
|
func (k Keeper) InitializePinnedCodes(ctx sdk.Context) error {
|
|
checksums, err := k.GetAllChecksums(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, checksum := range checksums {
|
|
if err := k.GetVM().Pin(checksum); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|