Integration
Learn how to integrate the 08-wasm
module in a chain binary and about the recommended approaches depending on whether the x/wasm
module is already used in the chain. The following document only applies for Cosmos SDK chains.
Importing the 08-wasm
module
08-wasm
has no stable releases yet. To use it, you need to import the git commit that contains the module with the compatible versions of ibc-go
and wasmvm
. To do so, run the following command with the desired git commit in your project:
go get github.com/cosmos/ibc-go/modules/light-clients/08-wasm@7ee2a2452b79d0bc8316dc622a1243afa058e8cb
The following table shows the compatibility matrix between the 08-wasm
module, ibc-go
, and wasmvm
.
Version | Git commit to import |
---|---|
v0.4.1+ibc-go-v8.4-wasmvm-v2.0 | ccd4dc278e720be87418028026ebd93a80fa5ac0 |
v0.3.1+ibc-go-v7.4-wasmvm-v1.5 | 13c071f0b34d67342f0b7a8874d84d2e68b887e1 |
app.go
setup
The sample code below shows the relevant integration points in app.go
required to set up the 08-wasm
module in a chain binary. Since 08-wasm
is a light client module itself, please check out as well the section Integrating light clients for more information:
// app.go
import (
...
"github.com/cosmos/cosmos-sdk/runtime"
cmtos "github.com/cometbft/cometbft/libs/os"
ibcwasm "github.com/cosmos/ibc-go/modules/light-clients/08-wasm"
ibcwasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper"
ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types"
...
)
...
// Register the AppModule for the 08-wasm module
ModuleBasics = module.NewBasicManager(
...
ibcwasm.AppModuleBasic{},
...
)
// Add 08-wasm Keeper
type SimApp struct {
...
WasmClientKeeper ibcwasmkeeper.Keeper
...
}
func NewSimApp(
logger log.Logger,
db dbm.DB,
traceStore io.Writer,
loadLatest bool,
appOpts servertypes.AppOptions,
baseAppOptions ...func(*baseapp.BaseApp),
) *SimApp {
...
keys := sdk.NewKVStoreKeys(
...
ibcwasmtypes.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.
app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithVM(
appCodec,
runtime.NewKVStoreService(keys[ibcwasmtypes.StoreKey]),
app.IBCKeeper.ClientKeeper,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
wasmVM,
app.GRPCQueryRouter(),
)
wasmLightClientModule := wasm.NewLightClientModule(app.WasmClientKeeper)
app.IBCKeeper.ClientKeeper.AddRoute(ibcwasmtypes.ModuleName, &wasmLightClientModule)
app.ModuleManager = module.NewManager(
// SDK app modules
...
ibcwasm.NewAppModule(app.WasmClientKeeper),
)
app.ModuleManager.SetOrderBeginBlockers(
...
ibcwasmtypes.ModuleName,
...
)
app.ModuleManager.SetOrderEndBlockers(
...
ibcwasmtypes.ModuleName,
...
)
genesisModuleOrder := []string{
...
ibcwasmtypes.ModuleName,
...
}
app.ModuleManager.SetOrderInitGenesis(genesisModuleOrder...)
app.ModuleManager.SetOrderExportGenesis(genesisModuleOrder...)
...
// initialize BaseApp
app.SetInitChainer(app.InitChainer)
...
// must be before Loading version
if manager := app.SnapshotManager(); manager != nil {
err := manager.RegisterExtensions(
ibcwasmkeeper.NewWasmSnapshotter(app.CommitMultiStore(), &app.WasmClientKeeper),
)
if err != nil {
panic(fmt.Errorf("failed to register snapshot extension: %s", err))
}
}
...
if loadLatest {
...
ctx := app.BaseApp.NewUncachedContext(true, cmtproto.Header{})
// Initialize pinned codes in wasmvm as they are not persisted there
if err := app.WasmClientKeeper.InitializePinnedCodes(ctx); err != nil {
cmtos.Exit(fmt.Sprintf("failed initialize pinned codes %s", err))
}
}
}
Keeper instantiation
When it comes to instantiating 08-wasm
's keeper, there are two recommended ways of doing it. Choosing one or the other will depend on whether the chain already integrates x/wasm
or not.
If x/wasm
is present
If the chain where the module is integrated uses x/wasm
then we recommend that both 08-wasm
and x/wasm
share the same Wasm VM instance. Having two separate Wasm VM instances is still possible, but care should be taken to make sure that both instances do not share the directory when the VM stores blobs and various caches, otherwise unexpected behaviour is likely to happen (from x/wasm
v0.51 and 08-wasm
v0.2.0+ibc-go-v8.3-wasmvm-v2.0 this will be forbidden anyway, since wasmvm v2.0.0 and above will not allow two different Wasm VM instances to shared the same data folder).
In order to share the Wasm VM instance, please follow the guideline below. Please note that this requires x/wasm
v0.41 or above.
- Instantiate the Wasm VM in
app.go
with the parameters of your choice. - Create an
Option
with this Wasm VM instance. - Add the option created in the previous step to a slice and pass it to the
x/wasm NewKeeper
constructor function. - Pass the pointer to the Wasm VM instance to
08-wasm
NewKeeperWithVM
constructor function.
The code to set this up would look something like this:
// app.go
import (
...
"github.com/cosmos/cosmos-sdk/runtime"
wasmvm "github.com/CosmWasm/wasmvm/v2"
wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
ibcwasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper"
ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types"
...
)
...
// instantiate the Wasm VM with the chosen parameters
wasmer, err := wasmvm.NewVM(
dataDir,
availableCapabilities,
contractMemoryLimit, // default of 32
contractDebugMode,
memoryCacheSize,
)
if err != nil {
panic(err)
}
// create an Option slice (or append to an existing one)
// with the option to use a custom Wasm VM instance
wasmOpts = []wasmkeeper.Option{
wasmkeeper.WithWasmEngine(wasmer),
}
// the keeper will use the provided Wasm VM instance,
// instead of instantiating a new one
app.WasmKeeper = wasmkeeper.NewKeeper(
appCodec,
keys[wasmtypes.StoreKey],
app.AccountKeeper,
app.BankKeeper,
app.StakingKeeper,
distrkeeper.NewQuerier(app.DistrKeeper),
app.IBCFeeKeeper, // ISC4 Wrapper: fee IBC middleware
app.IBCKeeper.ChannelKeeper,
&app.IBCKeeper.PortKeeper,
scopedWasmKeeper,
app.TransferKeeper,
app.MsgServiceRouter(),
app.GRPCQueryRouter(),
wasmDir,
wasmConfig,
availableCapabilities,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
wasmOpts...,
)
app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithVM(
appCodec,
runtime.NewKVStoreService(keys[ibcwasmtypes.StoreKey]),
app.IBCKeeper.ClientKeeper,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
wasmer, // pass the Wasm VM instance to `08-wasm` keeper constructor
app.GRPCQueryRouter(),
)
...
If x/wasm
is not present
If the chain does not use x/wasm
, even though it is still possible to use the method above from the previous section
(e.g. instantiating a Wasm VM in app.go an pass it to 08-wasm's NewKeeperWithVM
constructor function, since there would be no need in this case to share the Wasm VM instance with another module, you can use the NewKeeperWithConfig
constructor function and provide the Wasm VM configuration parameters of your choice instead. A Wasm VM instance will be created in NewKeeperWithConfig
. The parameters that can set are:
DataDir
is the directory for Wasm blobs and various caches. As an example, inwasmd
this is set to thewasm
folder under the home directory. In the code snippet below we set this field to theibc_08-wasm_client_data
folder under the home directory.SupportedCapabilities
is a list of capabilities supported by the chain.wasmd
sets this to all the available capabilities, but 08-wasm only requiresiterator
.MemoryCacheSize
sets the size in MiB of an in-memory cache for e.g. module caching. It is not consensus-critical and should be defined on a per-node basis, often in the range 100 to 1000 MB.wasmd
reads this value of. Default value is 256.ContractDebugMode
is a flag to enable/disable printing debug logs from the contract to STDOUT. This should be false in production environments. Default value is false.
Another configuration parameter of the Wasm VM is the contract memory limit (in MiB), which is set to 32, following the example of wasmd
. This parameter is not configurable by users of 08-wasm
.
The following sample code shows how the keeper would be constructed using this method:
// app.go
import (
...
"github.com/cosmos/cosmos-sdk/runtime"
ibcwasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper"
ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types"
...
)
...
// homePath is the path to the directory where the data
// directory for Wasm blobs and caches will be created
wasmConfig := ibcwasmtypes.WasmConfig{
DataDir: filepath.Join(homePath, "ibc_08-wasm_client_data"),
SupportedCapabilities: []string{"iterator"},
ContractDebugMode: false,
}
app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithConfig(
appCodec,
runtime.NewKVStoreService(keys[ibcwasmtypes.StoreKey]),
app.IBCKeeper.ClientKeeper,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
wasmConfig,
app.GRPCQueryRouter(),
)
Check out also the WasmConfig
type definition for more information on each of the configurable parameters. Some parameters allow node-level configurations. There is additionally the function DefaultWasmConfig
available that returns a configuration with the default values.
Options
The 08-wasm
module comes with an options API inspired by the one in x/wasm
.
Currently the only option available is the WithQueryPlugins
option, which allows registration of custom query plugins for the 08-wasm
module. The use of this API is optional and it is only required if the chain wants to register custom query plugins for the 08-wasm
module.
WithQueryPlugins
By default, the 08-wasm
module does not configure any querier options for light client contracts. However, it is possible to register custom query plugins for QueryRequest::Custom
and QueryRequest::Stargate
.
Assuming that the keeper is not yet instantiated, the following sample code shows how to register query plugins for the 08-wasm
module.
We first construct a QueryPlugins
object with the desired query plugins:
queryPlugins := ibcwasmtypes.QueryPlugins {
Custom: MyCustomQueryPlugin(),
// `myAcceptList` is a `[]string` containing the list of gRPC query paths that the chain wants to allow for the `08-wasm` module to query.
// These queries must be registered in the chain's gRPC query router, be deterministic, and track their gas usage.
// The `AcceptListStargateQuerier` function will return a query plugin that will only allow queries for the paths in the `myAcceptList`.
// The query responses are encoded in protobuf unlike the implementation in `x/wasm`.
Stargate: ibcwasmtypes.AcceptListStargateQuerier(myAcceptList),
}
Note that the Stargate
querier appends the user defined accept list of query routes to a default list defined by the 08-wasm
module.
The defaultAcceptList
defines a single query route: "/ibc.core.client.v1.Query/VerifyMembership"
. This allows for light client smart contracts to delegate parts of their workflow to other light clients for auxiliary proof verification. For example, proof of inclusion of block and tx data by a data availability provider.
// defaultAcceptList defines a set of default allowed queries made available to the Querier.
var defaultAcceptList = []string{
"/ibc.core.client.v1.Query/VerifyMembership",
}
You may leave any of the fields in the QueryPlugins
object as nil
if you do not want to register a query plugin for that query type.
Then, we pass the QueryPlugins
object to the WithQueryPlugins
option:
querierOption := ibcwasmkeeper.WithQueryPlugins(&queryPlugins)
Finally, we pass the option to the NewKeeperWithConfig
or NewKeeperWithVM
constructor function during Keeper instantiation:
app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithConfig(
appCodec,
runtime.NewKVStoreService(keys[ibcwasmtypes.StoreKey]),
app.IBCKeeper.ClientKeeper,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
wasmConfig,
app.GRPCQueryRouter(),
+ querierOption,
)
app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithVM(
appCodec,
runtime.NewKVStoreService(keys[ibcwasmtypes.StoreKey]),
app.IBCKeeper.ClientKeeper,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
wasmer, // pass the Wasm VM instance to `08-wasm` keeper constructor
app.GRPCQueryRouter(),
+ querierOption,
)
Updating AllowedClients
If the chain's 02-client submodule parameter AllowedClients
contains the single wildcard "*"
element, then it is not necessary to do anything in order to allow the creation of 08-wasm
clients. However, if the parameter contains a list of client types (e.g. ["06-solomachine", "07-tendermint"]
), then in order to use the 08-wasm
module chains must update the AllowedClients
parameter of core IBC. This can be configured directly in the application upgrade handler with the sample code below:
import (
...
ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types"
...
)
...
func CreateWasmUpgradeHandler(
mm *module.Manager,
configurator module.Configurator,
clientKeeper clientkeeper.Keeper,
) upgradetypes.UpgradeHandler {
return func(ctx context.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) {
sdkCtx := sdk.UnwrapSDKContext(ctx)
// explicitly update the IBC 02-client params, adding the wasm client type
params := clientKeeper.GetParams(ctx)
params.AllowedClients = append(params.AllowedClients, ibcwasmtypes.Wasm)
clientKeeper.SetParams(ctx, params)
return mm.RunMigrations(ctx, configurator, vm)
}
}
Or alternatively the parameter can be updated via a governance proposal (see at the bottom of section Creating clients
for an example of how to do this).
Adding the module to the store
As part of the upgrade migration you must also add the module to the upgrades store.
func (app SimApp) RegisterUpgradeHandlers() {
...
if upgradeInfo.Name == UpgradeName && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) {
storeUpgrades := storetypes.StoreUpgrades{
Added: []string{
ibcwasmtypes.ModuleName,
},
}
// configure store loader that checks if version == upgradeHeight and applies store upgrades
app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades))
}
}
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. This code 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 should be placed in NewSimApp
function in app.go
.