diff --git a/docs/apps/interchain-accounts/legacy/auth-modules.md b/docs/apps/interchain-accounts/legacy/auth-modules.md new file mode 100644 index 00000000000..adb3119ad94 --- /dev/null +++ b/docs/apps/interchain-accounts/legacy/auth-modules.md @@ -0,0 +1,268 @@ + + +# Building an authentication module + +## Deprecation Notice + +**This document is deprecated and will be removed in future releases**. + +Authentication modules play the role of the `Base Application` as described in [ICS-30 IBC Middleware](https://github.com/cosmos/ibc/tree/master/spec/app/ics-030-middleware), and enable application developers to perform custom logic when working with the Interchain Accounts controller API. {synopsis} + +The controller submodule is used for account registration and packet sending. It executes only logic required of all controllers of interchain accounts. The type of authentication used to manage the interchain accounts remains unspecified. There may exist many different types of authentication which are desirable for different use cases. Thus the purpose of the authentication module is to wrap the controller submodule with custom authentication logic. + +In ibc-go, authentication modules are connected to the controller chain via a middleware stack. The controller submodule is implemented as [middleware](https://github.com/cosmos/ibc/tree/master/spec/app/ics-030-middleware) and the authentication module is connected to the controller submodule as the base application of the middleware stack. To implement an authentication module, the `IBCModule` interface must be fulfilled. By implementing the controller submodule as middleware, any amount of authentication modules can be created and connected to the controller submodule without writing redundant code. + +The authentication module must: + +- Authenticate interchain account owners. +- Track the associated interchain account address for an owner. +- Send packets on behalf of an owner (after authentication). + +> Please note that since ibc-go v6 the channel capability is claimed by the controller submodule and therefore it is not required for authentication modules to claim the capability in the `OnChanOpenInit` callback. When the authentication module sends packets on the channel created for the associated interchain account it can pass a `nil` capability to the legacy function `SendTx` of the controller keeper (see [section `SendTx`](#sendtx) below for mode information). + +## `IBCModule` implementation + +The following `IBCModule` callbacks must be implemented with appropriate custom logic: + +```go +// OnChanOpenInit implements the IBCModule interface +func (im IBCModule) OnChanOpenInit( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + chanCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + // since ibc-go v6 the authentication module *must not* claim the channel capability on OnChanOpenInit + + // perform custom logic + + return version, nil +} + +// OnChanOpenAck implements the IBCModule interface +func (im IBCModule) OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyVersion string, +) error { + // perform custom logic + + return nil +} + +// OnChanCloseConfirm implements the IBCModule interface +func (im IBCModule) OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // perform custom logic + + return nil +} + +// OnAcknowledgementPacket implements the IBCModule interface +func (im IBCModule) OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, +) error { + // perform custom logic + + return nil +} + +// OnTimeoutPacket implements the IBCModule interface. +func (im IBCModule) OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) error { + // perform custom logic + + return nil +} +``` + +The following functions must be defined to fulfill the `IBCModule` interface, but they will never be called by the controller submodule so they may error or panic. That is because in Interchain Accounts, the channel handshake is always initiated on the controller chain and packets are always sent to the host chain and never to the controller chain. + +```go +// OnChanOpenTry implements the IBCModule interface +func (im IBCModule) OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + chanCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + panic("UNIMPLEMENTED") +} + +// OnChanOpenConfirm implements the IBCModule interface +func (im IBCModule) OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + panic("UNIMPLEMENTED") +} + +// OnChanCloseInit implements the IBCModule interface +func (im IBCModule) OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + panic("UNIMPLEMENTED") +} + +// OnRecvPacket implements the IBCModule interface. A successful acknowledgement +// is returned if the packet data is succesfully decoded and the receive application +// logic returns without error. +func (im IBCModule) OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) ibcexported.Acknowledgement { + panic("UNIMPLEMENTED") +} +``` + +## `OnAcknowledgementPacket` + +Controller chains will be able to access the acknowledgement written into the host chain state once a relayer relays the acknowledgement. +The acknowledgement bytes contain either the response of the execution of the message(s) on the host chain or an error. They will be passed to the auth module via the `OnAcknowledgementPacket` callback. Auth modules are expected to know how to decode the acknowledgement. + +If the controller chain is connected to a host chain using the host module on ibc-go, it may interpret the acknowledgement bytes as follows: + +Begin by unmarshaling the acknowledgement into `sdk.TxMsgData`: + +```go +var ack channeltypes.Acknowledgement +if err := channeltypes.SubModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil { + return err +} + +txMsgData := &sdk.TxMsgData{} +if err := proto.Unmarshal(ack.GetResult(), txMsgData); err != nil { + return err +} +``` + +If the `txMsgData.Data` field is non nil, the host chain is using SDK version <= v0.45. +The auth module should interpret the `txMsgData.Data` as follows: + +```go +switch len(txMsgData.Data) { +case 0: + // see documentation below for SDK 0.46.x or greater +default: + for _, msgData := range txMsgData.Data { + if err := handler(msgData); err != nil { + return err + } + } +... +} +``` + +A handler will be needed to interpret what actions to perform based on the message type sent. +A router could be used, or more simply a switch statement. + +```go +func handler(msgData sdk.MsgData) error { +switch msgData.MsgType { +case sdk.MsgTypeURL(&banktypes.MsgSend{}): + msgResponse := &banktypes.MsgSendResponse{} + if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil { + return err + } + + handleBankSendMsg(msgResponse) + +case sdk.MsgTypeURL(&stakingtypes.MsgDelegate{}): + msgResponse := &stakingtypes.MsgDelegateResponse{} + if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil { + return err + } + + handleStakingDelegateMsg(msgResponse) + +case sdk.MsgTypeURL(&transfertypes.MsgTransfer{}): + msgResponse := &transfertypes.MsgTransferResponse{} + if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil { + return err + } + + handleIBCTransferMsg(msgResponse) + +default: + return +} +``` + +If the `txMsgData.Data` is empty, the host chain is using SDK version > v0.45. +The auth module should interpret the `txMsgData.Responses` as follows: + +```go +... +// switch statement from above +case 0: + for _, any := range txMsgData.MsgResponses { + if err := handleAny(any); err != nil { + return err + } + } +} +``` + +A handler will be needed to interpret what actions to perform based on the type URL of the Any. +A router could be used, or more simply a switch statement. +It may be possible to deduplicate logic between `handler` and `handleAny`. + +```go +func handleAny(any *codectypes.Any) error { +switch any.TypeURL { +case banktypes.MsgSend: + msgResponse, err := unpackBankMsgSendResponse(any) + if err != nil { + return err + } + + handleBankSendMsg(msgResponse) + +case stakingtypes.MsgDelegate: + msgResponse, err := unpackStakingDelegateResponse(any) + if err != nil { + return err + } + + handleStakingDelegateMsg(msgResponse) + + case transfertypes.MsgTransfer: + msgResponse, err := unpackIBCTransferMsgResponse(any) + if err != nil { + return err + } + + handleIBCTransferMsg(msgResponse) + +default: + return +} +``` + +## Integration into `app.go` file + +To integrate the authentication module into your chain, please follow the steps outlined in [`app.go` integration](./integration.md#example-integration). diff --git a/docs/apps/interchain-accounts/legacy/integration.md b/docs/apps/interchain-accounts/legacy/integration.md new file mode 100644 index 00000000000..4a2f99fd692 --- /dev/null +++ b/docs/apps/interchain-accounts/legacy/integration.md @@ -0,0 +1,195 @@ + + +# Integration + +## Deprecation Notice + +**This document is deprecated and will be removed in future releases**. + +Learn how to integrate Interchain Accounts host and controller functionality to your chain. The following document only applies for Cosmos SDK chains. {synopsis} + +The Interchain Accounts module contains two submodules. Each submodule has its own IBC application. The Interchain Accounts module should be registered as an `AppModule` in the same way all SDK modules are registered on a chain, but each submodule should create its own `IBCModule` as necessary. A route should be added to the IBC router for each submodule which will be used. + +Chains who wish to support ICS-27 may elect to act as a host chain, a controller chain or both. Disabling host or controller functionality may be done statically by excluding the host or controller module entirely from the `app.go` file or it may be done dynamically by taking advantage of the on-chain parameters which enable or disable the host or controller submodules. + +Interchain Account authentication modules are the base application of a middleware stack. The controller submodule is the middleware in this stack. + +![ICApreV6](../../../assets/ica/ica-pre-v6.png) + +> Please note that since ibc-go v6 the channel capability is claimed by the controller submodule and therefore it is not required for authentication modules to claim the capability in the `OnChanOpenInit` callback. Therefore the custom authentication module does not need a scoped keeper anymore. + +## Example integration + +```go +// app.go + +// Register the AppModule for the Interchain Accounts module and the authentication module +// Note: No `icaauth` exists, this must be substituted with an actual Interchain Accounts authentication module +ModuleBasics = module.NewBasicManager( + ... + ica.AppModuleBasic{}, + icaauth.AppModuleBasic{}, + ... +) + +... + +// Add module account permissions for the Interchain Accounts module +// Only necessary for host chain functionality +// Each Interchain Account created on the host chain is derived from the module account created +maccPerms = map[string][]string{ + ... + icatypes.ModuleName: nil, +} + +... + +// Add Interchain Accounts Keepers for each submodule used and the authentication module +// If a submodule is being statically disabled, the associated Keeper does not need to be added. +type App struct { + ... + + ICAControllerKeeper icacontrollerkeeper.Keeper + ICAHostKeeper icahostkeeper.Keeper + ICAAuthKeeper icaauthkeeper.Keeper + + ... +} + +... + +// Create store keys for each submodule Keeper and the authentication module +keys := sdk.NewKVStoreKeys( + ... + icacontrollertypes.StoreKey, + icahosttypes.StoreKey, + icaauthtypes.StoreKey, + ... +) + +... + +// Create the scoped keepers for each submodule keeper and authentication keeper +scopedICAControllerKeeper := app.CapabilityKeeper.ScopeToModule(icacontrollertypes.SubModuleName) +scopedICAHostKeeper := app.CapabilityKeeper.ScopeToModule(icahosttypes.SubModuleName) + +... + +// Create the Keeper for each submodule +app.ICAControllerKeeper = icacontrollerkeeper.NewKeeper( + appCodec, keys[icacontrollertypes.StoreKey], app.GetSubspace(icacontrollertypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, // may be replaced with middleware such as ics29 fee + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + scopedICAControllerKeeper, app.MsgServiceRouter(), +) +app.ICAHostKeeper = icahostkeeper.NewKeeper( + appCodec, keys[icahosttypes.StoreKey], app.GetSubspace(icahosttypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, // may be replaced with middleware such as ics29 fee + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + app.AccountKeeper, scopedICAHostKeeper, app.MsgServiceRouter(), +) + +// Create Interchain Accounts AppModule +icaModule := ica.NewAppModule(&app.ICAControllerKeeper, &app.ICAHostKeeper) + +// Create your Interchain Accounts authentication module +app.ICAAuthKeeper = icaauthkeeper.NewKeeper(appCodec, keys[icaauthtypes.StoreKey], app.ICAControllerKeeper) + +// ICA auth AppModule +icaAuthModule := icaauth.NewAppModule(appCodec, app.ICAAuthKeeper) + +// ICA auth IBC Module +icaAuthIBCModule := icaauth.NewIBCModule(app.ICAAuthKeeper) + +// Create controller IBC application stack and host IBC module as desired +icaControllerStack := icacontroller.NewIBCMiddleware(icaAuthIBCModule, app.ICAControllerKeeper) +icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) + +// Register host and authentication routes +ibcRouter. + AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). + AddRoute(icahosttypes.SubModuleName, icaHostIBCModule). + AddRoute(icaauthtypes.ModuleName, icaControllerStack) // Note, the authentication module is routed to the top level of the middleware stack + +... + +// Register Interchain Accounts and authentication module AppModule's +app.moduleManager = module.NewManager( + ... + icaModule, + icaAuthModule, +) + +... + +// Add fee middleware to begin blocker logic +app.moduleManager.SetOrderBeginBlockers( + ... + icatypes.ModuleName, + ... +) + +// Add fee middleware to end blocker logic +app.moduleManager.SetOrderEndBlockers( + ... + icatypes.ModuleName, + ... +) + +// Add Interchain Accounts module InitGenesis logic +app.moduleManager.SetOrderInitGenesis( + ... + icatypes.ModuleName, + ... +) + +// initParamsKeeper init params keeper and its subspaces +func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino, key, tkey sdk.StoreKey) paramskeeper.Keeper { + ... + paramsKeeper.Subspace(icahosttypes.SubModuleName) + paramsKeeper.Subspace(icacontrollertypes.SubModuleName) + ... +``` + +## Using submodules exclusively + +As described above, the Interchain Accounts application module is structured to support the ability of exclusively enabling controller or host functionality. +This can be achieved by simply omitting either controller or host `Keeper` from the Interchain Accounts `NewAppModule` constructor function, and mounting only the desired submodule via the `IBCRouter`. +Alternatively, submodules can be enabled and disabled dynamically using [on-chain parameters](./parameters.md). + +The following snippets show basic examples of statically disabling submodules using `app.go`. + +### Disabling controller chain functionality + +```go +// Create Interchain Accounts AppModule omitting the controller keeper +icaModule := ica.NewAppModule(nil, &app.ICAHostKeeper) + +// Create host IBC Module +icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) + +// Register host route +ibcRouter.AddRoute(icahosttypes.SubModuleName, icaHostIBCModule) +``` + +### Disabling host chain functionality + +```go +// Create Interchain Accounts AppModule omitting the host keeper +icaModule := ica.NewAppModule(&app.ICAControllerKeeper, nil) + +// Create your Interchain Accounts authentication module, setting up the Keeper, AppModule and IBCModule appropriately +app.ICAAuthKeeper = icaauthkeeper.NewKeeper(appCodec, keys[icaauthtypes.StoreKey], app.ICAControllerKeeper) +icaAuthModule := icaauth.NewAppModule(appCodec, app.ICAAuthKeeper) +icaAuthIBCModule := icaauth.NewIBCModule(app.ICAAuthKeeper) + +// Create controller IBC application stack +icaControllerStack := icacontroller.NewIBCMiddleware(icaAuthIBCModule, app.ICAControllerKeeper) + +// Register controller and authentication routes +ibcRouter. + AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). + AddRoute(icaauthtypes.ModuleName, icaControllerStack) // Note, the authentication module is routed to the top level of the middleware stack +``` \ No newline at end of file diff --git a/docs/apps/interchain-accounts/legacy/keeper-api.md b/docs/apps/interchain-accounts/legacy/keeper-api.md new file mode 100644 index 00000000000..d4e5d2cd9c2 --- /dev/null +++ b/docs/apps/interchain-accounts/legacy/keeper-api.md @@ -0,0 +1,121 @@ + + +# Keeper API + +## Deprecation Notice + +**This document is deprecated and will be removed in future releases**. + +The controller submodule keeper exposes two legacy functions that allow respectively for custom authentication modules to register interchain accounts and send packets to the interchain account. + +## `RegisterInterchainAccount` + +The authentication module can begin registering interchain accounts by calling `RegisterInterchainAccount`: + +```go +if err := keeper.icaControllerKeeper.RegisterInterchainAccount(ctx, connectionID, owner.String(), version); err != nil { + return err +} + +return nil +``` + +The `version` argument is used to support ICS-29 fee middleware for relayer incentivization of ICS-27 packets. Consumers of the `RegisterInterchainAccount` are expected to build the appropriate JSON encoded version string themselves and pass it accordingly. If an empty string is passed in the `version` argument, then the version will be initialized to a default value in the `OnChanOpenInit` callback of the controller's handler, so that channel handshake can proceed. + +The following code snippet illustrates how to construct an appropriate interchain accounts `Metadata` and encode it as a JSON bytestring: + +```go +icaMetadata := icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, +} + +appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) +if err != nil { + return err +} + +if err := keeper.icaControllerKeeper.RegisterInterchainAccount(ctx, controllerConnectionID, owner.String(), string(appVersion)); err != nil { + return err +} +``` + +Similarly, if the application stack is configured to route through ICS-29 fee middleware and a fee enabled channel is desired, construct the appropriate ICS-29 `Metadata` type: + +```go +icaMetadata := icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, +} + +appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) +if err != nil { + return err +} + +feeMetadata := feetypes.Metadata{ + AppVersion: string(appVersion), + FeeVersion: feetypes.Version, +} + +feeEnabledVersion, err := feetypes.ModuleCdc.MarshalJSON(&feeMetadata) +if err != nil { + return err +} + +if err := keeper.icaControllerKeeper.RegisterInterchainAccount(ctx, controllerConnectionID, owner.String(), string(feeEnabledVersion)); err != nil { + return err +} +``` + +## `SendTx` + +The authentication module can attempt to send a packet by calling `SendTx`: + +```go +// Authenticate owner +// perform custom logic + +// Construct controller portID based on interchain account owner address +portID, err := icatypes.NewControllerPortID(owner.String()) +if err != nil { + return err +} + +// Obtain data to be sent to the host chain. +// In this example, the owner of the interchain account would like to send a bank MsgSend to the host chain. +// The appropriate serialization function should be called. The host chain must be able to deserialize the transaction. +// If the host chain is using the ibc-go host module, `SerializeCosmosTx` should be used. +msg := &banktypes.MsgSend{FromAddress: fromAddr, ToAddress: toAddr, Amount: amt} +data, err := icatypes.SerializeCosmosTx(keeper.cdc, []proto.Message{msg}) +if err != nil { + return err +} + +// Construct packet data +packetData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, +} + +// Obtain timeout timestamp +// An appropriate timeout timestamp must be determined based on the usage of the interchain account. +// If the packet times out, the channel will be closed requiring a new channel to be created. +timeoutTimestamp := obtainTimeoutTimestamp() + +// Send the interchain accounts packet, returning the packet sequence +// A nil channel capability can be passed, since the controller submodule (and not the authentication module) +// claims the channel capability since ibc-go v6. +seq, err = keeper.icaControllerKeeper.SendTx(ctx, nil, portID, packetData, timeoutTimestamp) +``` + +The data within an `InterchainAccountPacketData` must be serialized using a format supported by the host chain. +If the host chain is using the ibc-go host chain submodule, `SerializeCosmosTx` should be used. If the `InterchainAccountPacketData.Data` is serialized using a format not supported by the host chain, the packet will not be successfully received. \ No newline at end of file diff --git a/docs/assets/ica/ica-pre-v6.png b/docs/assets/ica/ica-pre-v6.png new file mode 100644 index 00000000000..acd00d1294b Binary files /dev/null and b/docs/assets/ica/ica-pre-v6.png differ