Skip to content

Commit

Permalink
chore: use context.Context and appmodule.Environment in 08-wasm (#7880)
Browse files Browse the repository at this point in the history
* core modules

* fmt

* fix broken tests

* lint fix

* actually, we can do wasm for now. this will work fine.

* appmodulev2 -> appmodule

* environment patch

* ok most migrated now

* use branch service isntead of query contexts

* wip gas

* migration complete

* linter, error checks, etc etc

* import ordering

* linter

* update queryHandler commentg

* comment

* fix gas check

* make event emission functions methods on keeper

* remove context argument from logger method

* remove unwraps where possible

* back to queyr router

* fix

* remove sdk.Context and height args from WasmSnapshotter functionality

* restore height arg because thats an iface impl

---------

Co-authored-by: Gjermund Garaba <[email protected]>
  • Loading branch information
technicallyty and gjermundgaraba authored Jan 30, 2025
1 parent 70e7b6a commit 98d7e75
Show file tree
Hide file tree
Showing 16 changed files with 209 additions and 196 deletions.
77 changes: 44 additions & 33 deletions modules/light-clients/08-wasm/keeper/contract_keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@ package keeper

import (
"bytes"
"context"
"encoding/hex"
"encoding/json"
"errors"
"fmt"

wasmvm "github.com/CosmWasm/wasmvm/v2"
wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types"

"cosmossdk.io/core/header"
errorsmod "cosmossdk.io/errors"
storetypes "cosmossdk.io/store/types"

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

internaltypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/types"
"github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types"
Expand All @@ -34,70 +36,79 @@ var (
)

// instantiateContract calls vm.Instantiate with appropriate arguments.
func (k Keeper) instantiateContract(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) {
sdkGasMeter := ctx.GasMeter()
func (k Keeper) instantiateContract(ctx context.Context, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) {
sdkGasMeter := k.GasService.GasMeter(ctx)
multipliedGasMeter := types.NewMultipliedGasMeter(sdkGasMeter, types.VMGasRegister)
gasLimit := VMGasRegister.RuntimeGasForContract(ctx)
gasLimit := VMGasRegister.RuntimeGasForContract(sdkGasMeter)

env := getEnv(ctx, clientID)
env := getEnv(k.HeaderService.HeaderInfo(ctx), clientID)

msgInfo := wasmvmtypes.MessageInfo{
Sender: "",
Funds: nil,
}

ctx.GasMeter().ConsumeGas(types.VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: instantiate")
if err := sdkGasMeter.Consume(types.VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: instantiate"); err != nil {
return nil, fmt.Errorf("failed to consume gas for Loading Cosmwasm module instantiate: %w", err)
}

resp, gasUsed, err := k.GetVM().Instantiate(checksum, env, msgInfo, msg, internaltypes.NewStoreAdapter(clientStore), wasmvmAPI, k.newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization)
types.VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed)
types.VMGasRegister.ConsumeRuntimeGas(sdkGasMeter, gasUsed)
return resp, err
}

// callContract calls vm.Sudo with internally constructed gas meter and environment.
func (k Keeper) callContract(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) {
sdkGasMeter := ctx.GasMeter()
func (k Keeper) callContract(ctx context.Context, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) {
sdkGasMeter := k.GasService.GasMeter(ctx)
multipliedGasMeter := types.NewMultipliedGasMeter(sdkGasMeter, VMGasRegister)
gasLimit := VMGasRegister.RuntimeGasForContract(ctx)
gasLimit := VMGasRegister.RuntimeGasForContract(sdkGasMeter)

env := getEnv(ctx, clientID)
env := getEnv(k.HeaderService.HeaderInfo(ctx), clientID)

ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: sudo")
if err := sdkGasMeter.Consume(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: sudo"); err != nil {
return nil, fmt.Errorf("failed to consume gas for Loading Cosmwasm module sudo: %w", err)
}
resp, gasUsed, err := k.GetVM().Sudo(checksum, env, msg, internaltypes.NewStoreAdapter(clientStore), wasmvmAPI, k.newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization)
VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed)
VMGasRegister.ConsumeRuntimeGas(sdkGasMeter, gasUsed)
return resp, err
}

// queryContract calls vm.Query.
func (k Keeper) queryContract(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.QueryResult, error) {
sdkGasMeter := ctx.GasMeter()
func (k Keeper) queryContract(ctx context.Context, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.QueryResult, error) {
sdkGasMeter := k.GasService.GasMeter(ctx)
multipliedGasMeter := types.NewMultipliedGasMeter(sdkGasMeter, VMGasRegister)
gasLimit := VMGasRegister.RuntimeGasForContract(ctx)
gasLimit := VMGasRegister.RuntimeGasForContract(sdkGasMeter)

env := getEnv(ctx, clientID)
env := getEnv(k.HeaderService.HeaderInfo(ctx), clientID)

ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: query")
if err := sdkGasMeter.Consume(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: query"); err != nil {
return nil, fmt.Errorf("failed to consume gas for Loading Cosmwasm module query: %w", err)
}
resp, gasUsed, err := k.GetVM().Query(checksum, env, msg, internaltypes.NewStoreAdapter(clientStore), wasmvmAPI, k.newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization)
VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed)
VMGasRegister.ConsumeRuntimeGas(sdkGasMeter, gasUsed)

return resp, err
}

// migrateContract calls vm.Migrate with internally constructed gas meter and environment.
func (k Keeper) migrateContract(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) {
sdkGasMeter := ctx.GasMeter()
func (k Keeper) migrateContract(ctx context.Context, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) {
sdkGasMeter := k.GasService.GasMeter(ctx)
multipliedGasMeter := types.NewMultipliedGasMeter(sdkGasMeter, VMGasRegister)
gasLimit := VMGasRegister.RuntimeGasForContract(ctx)
gasLimit := VMGasRegister.RuntimeGasForContract(sdkGasMeter)

env := getEnv(ctx, clientID)
env := getEnv(k.HeaderService.HeaderInfo(ctx), clientID)

ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: migrate")
if err := sdkGasMeter.Consume(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: migrate"); err != nil {
return nil, fmt.Errorf("failed to consume gas for Loading Cosmwasm module migrate: %w", err)
}
resp, gasUsed, err := k.GetVM().Migrate(checksum, env, msg, internaltypes.NewStoreAdapter(clientStore), wasmvmAPI, k.newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization)
VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed)
VMGasRegister.ConsumeRuntimeGas(sdkGasMeter, gasUsed)

return resp, err
}

// WasmInstantiate accepts a message to instantiate a wasm contract, JSON encodes it and calls instantiateContract.
func (k Keeper) WasmInstantiate(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, cs *types.ClientState, payload types.InstantiateMessage) error {
func (k Keeper) WasmInstantiate(ctx context.Context, clientID string, clientStore storetypes.KVStore, cs *types.ClientState, payload types.InstantiateMessage) error {
encodedData, err := json.Marshal(payload)
if err != nil {
return errorsmod.Wrap(err, "failed to marshal payload for wasm contract instantiation")
Expand Down Expand Up @@ -136,7 +147,7 @@ func (k Keeper) WasmInstantiate(ctx sdk.Context, clientID string, clientStore st
// - the response of the contract call contains non-empty events
// - the response of the contract call contains non-empty attributes
// - the data bytes of the response cannot be unmarshaled into the result type
func (k Keeper) WasmSudo(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, cs *types.ClientState, payload types.SudoMsg) ([]byte, error) {
func (k Keeper) WasmSudo(ctx context.Context, clientID string, clientStore storetypes.KVStore, cs *types.ClientState, payload types.SudoMsg) ([]byte, error) {
encodedData, err := json.Marshal(payload)
if err != nil {
return nil, errorsmod.Wrap(err, "failed to marshal payload for wasm execution")
Expand Down Expand Up @@ -171,7 +182,7 @@ func (k Keeper) WasmSudo(ctx sdk.Context, clientID string, clientStore storetype
// WasmMigrate migrate calls the migrate entry point of the contract with the given payload and returns the result.
// WasmMigrate returns an error if:
// - the contract migration returns an error
func (k Keeper) WasmMigrate(ctx sdk.Context, clientStore storetypes.KVStore, cs *types.ClientState, clientID string, payload []byte) error {
func (k Keeper) WasmMigrate(ctx context.Context, clientStore storetypes.KVStore, cs *types.ClientState, clientID string, payload []byte) error {
res, err := k.migrateContract(ctx, clientID, clientStore, cs.Checksum, payload)
if err != nil {
return errorsmod.Wrap(types.ErrVMError, err.Error())
Expand All @@ -192,7 +203,7 @@ func (k Keeper) WasmMigrate(ctx sdk.Context, clientStore storetypes.KVStore, cs
// WasmQuery returns an error if:
// - the contract query returns an error
// - the data bytes of the response cannot be unmarshal into the result type
func (k Keeper) WasmQuery(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, cs *types.ClientState, payload types.QueryMsg) ([]byte, error) {
func (k Keeper) WasmQuery(ctx context.Context, clientID string, clientStore storetypes.KVStore, cs *types.ClientState, payload types.QueryMsg) ([]byte, error) {
encodedData, err := json.Marshal(payload)
if err != nil {
return nil, errorsmod.Wrap(err, "failed to marshal payload for wasm query")
Expand Down Expand Up @@ -250,15 +261,15 @@ func unmarshalClientState(cdc codec.BinaryCodec, bz []byte) (exported.ClientStat
}

// getEnv returns the state of the blockchain environment the contract is running on
func getEnv(ctx sdk.Context, contractAddr string) wasmvmtypes.Env {
chainID := ctx.BlockHeader().ChainID
height := ctx.BlockHeader().Height
func getEnv(headerInfo header.Info, contractAddr string) wasmvmtypes.Env {
chainID := headerInfo.ChainID
height := headerInfo.Height

// safety checks before casting below
if height < 0 {
panic(errors.New("block height must never be negative"))
}
nsec := ctx.BlockTime().UnixNano()
nsec := headerInfo.Time.UnixNano()
if nsec < 0 {
panic(errors.New("block (unix) time must never be negative "))
}
Expand Down
38 changes: 22 additions & 16 deletions modules/light-clients/08-wasm/keeper/events.go
Original file line number Diff line number Diff line change
@@ -1,39 +1,45 @@
package keeper

import (
"context"
"encoding/hex"
"errors"

"cosmossdk.io/core/event"

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

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

// emitStoreWasmCodeEvent emits a store wasm code event
func emitStoreWasmCodeEvent(ctx sdk.Context, checksum types.Checksum) {
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
func (k Keeper) emitStoreWasmCodeEvent(ctx context.Context, checksum types.Checksum) error {
em := k.EventService.EventManager(ctx)
return errors.Join(
em.EmitKV(
types.EventTypeStoreWasmCode,
sdk.NewAttribute(types.AttributeKeyWasmChecksum, hex.EncodeToString(checksum)),
event.NewAttribute(types.AttributeKeyWasmChecksum, hex.EncodeToString(checksum)),
),
sdk.NewEvent(
em.EmitKV(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
event.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
),
})
)
}

// emitMigrateContractEvent emits a migrate contract event
func emitMigrateContractEvent(ctx sdk.Context, clientID string, checksum, newChecksum types.Checksum) {
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
func (k Keeper) emitMigrateContractEvent(ctx context.Context, clientID string, checksum, newChecksum types.Checksum) error {
em := k.EventService.EventManager(ctx)
return errors.Join(
em.EmitKV(
types.EventTypeMigrateContract,
sdk.NewAttribute(types.AttributeKeyClientID, clientID),
sdk.NewAttribute(types.AttributeKeyWasmChecksum, hex.EncodeToString(checksum)),
sdk.NewAttribute(types.AttributeKeyNewChecksum, hex.EncodeToString(newChecksum)),
event.NewAttribute(types.AttributeKeyClientID, clientID),
event.NewAttribute(types.AttributeKeyWasmChecksum, hex.EncodeToString(checksum)),
event.NewAttribute(types.AttributeKeyNewChecksum, hex.EncodeToString(newChecksum)),
),
sdk.NewEvent(
em.EmitKV(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
event.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
),
})
)
}
5 changes: 2 additions & 3 deletions modules/light-clients/08-wasm/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"cosmossdk.io/collections"
errorsmod "cosmossdk.io/errors"

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

"github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types"
Expand All @@ -19,7 +18,7 @@ import (
var _ types.QueryServer = (*Keeper)(nil)

// Code implements the Query/Code gRPC method
func (k Keeper) Code(goCtx context.Context, req *types.QueryCodeRequest) (*types.QueryCodeResponse, error) {
func (k Keeper) Code(ctx context.Context, req *types.QueryCodeRequest) (*types.QueryCodeResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "empty request")
}
Expand All @@ -30,7 +29,7 @@ func (k Keeper) Code(goCtx context.Context, req *types.QueryCodeRequest) (*types
}

// Only return checksums we previously stored, not arbitrary checksums that might be stored via e.g Wasmd.
if !k.HasChecksum(sdk.UnwrapSDKContext(goCtx), checksum) {
if !k.HasChecksum(ctx, checksum) {
return nil, status.Error(codes.NotFound, errorsmod.Wrap(types.ErrWasmChecksumNotFound, req.Checksum).Error())
}

Expand Down
43 changes: 21 additions & 22 deletions modules/light-clients/08-wasm/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,24 @@ import (
"bytes"
"context"
"encoding/hex"
"fmt"

wasmvm "github.com/CosmWasm/wasmvm/v2"

"cosmossdk.io/collections"
"cosmossdk.io/core/store"
"cosmossdk.io/core/appmodule"
"cosmossdk.io/core/log"
errorsmod "cosmossdk.io/errors"
"cosmossdk.io/log"

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

"github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types"
clienttypes "github.com/cosmos/ibc-go/v9/modules/core/02-client/types"
"github.com/cosmos/ibc-go/v9/modules/core/exported"
)

// Keeper defines the 08-wasm keeper
type Keeper struct {
appmodule.Environment
// implements gRPC QueryServer interface
types.QueryServer

Expand All @@ -30,8 +30,7 @@ type Keeper struct {

vm types.WasmEngine

checksums collections.KeySet[[]byte]
storeService store.KVStoreService
checksums collections.KeySet[[]byte]

queryPlugins QueryPlugins

Expand All @@ -49,12 +48,8 @@ func (k Keeper) GetAuthority() string {
}

// 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)
func (k Keeper) Logger() log.Logger {
return k.Environment.Logger
}

// GetVM returns the keeper's vm engine.
Expand All @@ -77,8 +72,8 @@ 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)
func (k Keeper) newQueryHandler(ctx context.Context, callerID string) *queryHandler {
return newQueryHandler(ctx, k.Environment, k.getQueryPlugins(), callerID)
}

// storeWasmCode stores the contract to the VM, pins the checksum in the VM's in memory cache and stores the checksum
Expand All @@ -87,10 +82,12 @@ func (k Keeper) newQueryHandler(ctx sdk.Context, callerID string) *queryHandler
// - 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 context.Context, code []byte, storeFn func(code wasmvm.WasmCode, gasLimit uint64) (wasmvm.Checksum, uint64, error)) ([]byte, error) {
sdkCtx := sdk.UnwrapSDKContext(ctx) // TODO: https://github.com/cosmos/ibc-go/issues/5917
meter := k.GasService.GasMeter(ctx)
var err error
if types.IsGzip(code) {
sdkCtx.GasMeter().ConsumeGas(types.VMGasRegister.UncompressCosts(len(code)), "Uncompress gzip bytecode")
if err := meter.Consume(types.VMGasRegister.UncompressCosts(len(code)), "Uncompress gzip bytecode"); err != nil {
return nil, fmt.Errorf("failed to consume gas for Uncompress gzip bytecode: %w", err)
}
code, err = types.Uncompress(code, types.MaxWasmSize)
if err != nil {
return nil, errorsmod.Wrap(err, "failed to store contract")
Expand All @@ -113,9 +110,9 @@ func (k Keeper) storeWasmCode(ctx context.Context, code []byte, storeFn func(cod
}

// create the code in the vm
gasLeft := types.VMGasRegister.RuntimeGasForContract(sdkCtx)
gasLeft := types.VMGasRegister.RuntimeGasForContract(meter)
vmChecksum, gasUsed, err := storeFn(code, gasLeft)
types.VMGasRegister.ConsumeRuntimeGas(sdkCtx, gasUsed)
types.VMGasRegister.ConsumeRuntimeGas(meter, gasUsed)
if err != nil {
return nil, errorsmod.Wrap(err, "failed to store contract")
}
Expand All @@ -141,7 +138,7 @@ func (k Keeper) storeWasmCode(ctx context.Context, code []byte, storeFn func(cod

// 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 {
func (k Keeper) migrateContractCode(ctx context.Context, clientID string, newChecksum, migrateMsg []byte) error {
clientStore := k.clientKeeper.ClientStore(ctx, clientID)
wasmClientState, found := types.GetClientState(clientStore, k.cdc)
if !found {
Expand Down Expand Up @@ -180,13 +177,15 @@ func (k Keeper) migrateContractCode(ctx sdk.Context, clientID string, newChecksu

k.clientKeeper.SetClientState(ctx, clientID, wasmClientState)

emitMigrateContractEvent(ctx, clientID, oldChecksum, newChecksum)
if err = k.emitMigrateContractEvent(ctx, clientID, oldChecksum, newChecksum); err != nil {
return fmt.Errorf("failed to emit migrate contract events: %w", err)
}

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) {
func (k Keeper) GetWasmClientState(ctx context.Context, clientID string) (*types.ClientState, error) {
clientState, found := k.clientKeeper.GetClientState(ctx, clientID)
if !found {
return nil, errorsmod.Wrapf(clienttypes.ErrClientTypeNotFound, "clientID %s", clientID)
Expand Down Expand Up @@ -233,7 +232,7 @@ func (k Keeper) HasChecksum(ctx context.Context, checksum types.Checksum) bool {
}

// InitializePinnedCodes updates wasmvm to pin to cache all contracts marked as pinned
func (k Keeper) InitializePinnedCodes(ctx sdk.Context) error {
func (k Keeper) InitializePinnedCodes(ctx context.Context) error {
checksums, err := k.GetAllChecksums(ctx)
if err != nil {
return err
Expand Down
Loading

0 comments on commit 98d7e75

Please sign in to comment.