diff --git a/x/wasm/IBC.md b/x/wasm/IBC.md index d5f1b8144b..c3cd0a0023 100644 --- a/x/wasm/IBC.md +++ b/x/wasm/IBC.md @@ -27,9 +27,9 @@ as how contracts can properly identify their counterparty. (We considered on port for the `x/wasm` module and multiplexing on it, but [dismissed that idea](#rejected-ideas)) * Upon `Instantiate`, if a contract is *IBC Enabled*, we dynamically - bind a port for this contract. The port name is `wasm-`, - eg. `wasm-cosmos1hmdudppzceg27qsuq707tjg8rkgj7g5hnvnw29` -* If a *Channel* is being established with a registered `wasm-xyz` port, + bind a port for this contract. The port name is `wasm.`, + eg. `wasm.cosmos1hmdudppzceg27qsuq707tjg8rkgj7g5hnvnw29` +* If a *Channel* is being established with a registered `wasm.xyz` port, the `x/wasm.Keeper` will handle this and call into the appropriate contract to determine supported protocol versions during the [`ChanOpenTry` and `ChanOpenAck` phases](https://docs.cosmos.network/master/ibc/overview.html#channels). @@ -42,7 +42,7 @@ as how contracts can properly identify their counterparty. * When sending a packet, the CosmWasm contract must specify the local *ChannelID*. As there is a unique *PortID* per contract, that is filled in by `x/wasm` to produce the globally unique `(PortID, ChannelID)` -* When receiving a Packet (or Ack or Timeout), the contracts receives the +* When receiving a Packet (or Ack or Timeout), the contracts receives the local *ChannelID* it came from, as well as the packet that was sent by the counterparty. * When receiving an Ack or Timeout packet, the contract also receives the original packet that it sent earlier. @@ -54,320 +54,17 @@ as how contracts can properly identify their counterparty. Establishing *Clients* and *Connections* is out of the scope of this module and must be created by the same means as for `ibc-transfer` -(via the cli or otherwise). `x/wasm` will bind a unique *Port* for each -"IBC Enabled" contract. +(via the [go cli](https://github.com/cosmos/relayer) or better [ts-relayer](https://github.com/confio/ts-relayer)). +`x/wasm` will bind a unique *Port* for each "IBC Enabled" contract. For mocks, all the Packet Handling and Channel Lifecycle Hooks are routed to some Golang stub handler, but containing the contract address, so we -can perform contract-specific actions for each packet. +can perform contract-specific actions for each packet. In a real setting, +we route to the contract that owns the port/channel and call one of it's various +entry points. -### Messages - -An "IBC Enabled" contract may dispatch the following messages not available -to other contracts: - -* `IBCSendMsg` - this sends an IBC packet over an established channel. -* `IBCCloseChannel` - given an existing channelID bound to this contract's Port, - initiate the closing sequence and reject all pending packets. - -They are returned from `handle` just like any other `CosmosMsg` -(For mocks, we will trigger this externally, later only valid contract addresses -should be able to do so). - -### Packet Handling - -An "IBC Enabled" contract must support the following callbacks from the runtime -(we will likely multiplex many over one wasm export, as we do with handle, but these -are the different calls we must support): - -* `IBCRecvPacket` - when another chain sends a packet destined to - an IBCEnabled contract, this will be routed to the proper contract - and call a function exposed for this purpose. -* `IBCPacketAck` - The original sender of `IBCSendMsg` will - get this callback eventually if the message was - processed on the other chain (this may be either a success or an error, - but comes from the app-level protocol, not the IBC protocol). -* `IBCPacketTimeout` - The original sender of `IBCSendMsg` will - get this callback eventually if the message failed to be - processed on the other chain (for timeout, closed channel, or - other IBC-level failure) - -Note: We may add some helpers inside the contract to map `IBCPacketAck` / `IBCPacketTimeout` -to `IBCPacketSucceeded` / `IBCPacketFailed` assuming they use the standard envelope. However, -we decided not to enforce this on the Go-level, to allow contracts to communicate using protocols -that do not use this envelope. - -### Channel Lifecycle Hooks - -If you look at the [4 step process](https://docs.cosmos.network/master/ibc/overview.html#channels) for -channel handshakes, we simplify this from the view of the contract: - -1. The main path to initiate a channel from a contract to an external port is via an external - client executing `simd tx ibc channel open-init` or such. This allows the initiating party - to select which version/protocol they want to connect with. There must be a callback for - the initiating contract to verify if the version and/or ordering are valid. - **Question**: can we reuse the same check-logic as for step 2 -2. The counterparty has a chance for version negotiation in `OnChanOpenTry`, where the contract - can apply custom logic. It provides the protocol versions that the initiating party expects, and - the contract can reject the connection or accept it and return the protocol version it will communicate with. - Implementing this (contract selects a version, not just the relayer) requires that we support - [ADR 025](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-025-ibc-passive-channels.md). - TODO: check with Agoric and cwgoes to upstream any Cosmos SDK changes so this can work out of the box. -3. `OnChanOpened` is called on the contract for both `OnChanOpenAck` and `OnChanOpenConfirm` containing - the final version string (counterparty version). This gives a chance to abort the process - if we realize this doesn't work. Or save the info (we may need to define this channel uses an older version - of the protocol for example). -4. `OnChanClosed` is called on both sides if the channel is closed for any reason, allowing them to - perform any cleanup. This will be followed by `IBCPacketTimeout` callbacks for all the in-progress - packets that were not processed before it closed, as well as all pending acks (pending the relayer - to provide that). - -We require the following callbacks on the contract - -* `OnChanNegotiate` - called on the initiating side on `OnChanOpenInit` and on receiving end for `OnChanOpenTry`. - @alpe: does it make sense to use the same shape for both of these? Rather than `CounterPartyVersion`, they would - have `ProposedVersion`, but otherwise the same logic. -* `OnChanOpened` - called on both sides after the initial 2 steps have passed to confirm the version used -* `OnChanClosed` - this is called when an existing channel is closed for any reason - -### Queries - -We may want to expose some basic queries of IBC State to the contract. -We should check if this is useful to the contract and if it opens up -any possible DoS: - -* `GetPortID` - return PortID given a contract address -* `ListChannels` - return a list of all (portID, channelID) pairs - that are bound to a given port. -* `ListPendingPackets` - given a (portID, channelID) identifier, return all packets - in that channel, that have been sent by this chain, but for which no acknowledgement - or timeout has yet been received -* `ListPendingAcknowledgements` - given a (portID, channelID) identifier, return all packets - in that channel, that have been received and processed by this chain, but for which - we have no proof the acknowledgement has been relayed -* `GetCounterparty` - given a local (portID, channelID) identifier, it will look up - what (portID, channelID) are bound to the remote end of the channel. - -## Contract Details - -Here we map out the workflow with the exact arguments passed with those calls -from the Go side (which we will use with our mock), and then a -proposal for multiplexing this over fewer wasm exports (define some rust types) - -Messages: - -```go -package messages - -type IBCSendMsg struct { - // This is our contract-local ID - ChannelID string - Msg []byte - // optional fields (or do we need exactly/at least one of these?) - TimeoutHeight uint64 - TimeoutTimestamp uint64 -} - -type IBCCloseChannel struct { - ChannelID string -} - -// "Versions must be strings but and implement any versioning structure..." -// https://docs.cosmos.network/master/ibc/custom.html#channel-handshake-version-negotiation -// Use the same approach here -type Version string -``` - -Packet callbacks: - -```go -package packets - -// for reference: this is more like what we pass to wasmvm -// func (c *mockContract) OnReceive(params cosmwasm2.Env, msg []byte, store prefix.Store, api cosmwasm.GoAPI, -// querier keeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasm2.OnReceiveIBCResponse, uint64, error) {} -// below is how we want to expose it in x/wasm: - -// IBCRecvPacket is called when we receive a packet sent from the other end of a channel -// The response bytes listed here will be returned to the caller as "result" in IBCPacketAck -// What do we do with error? -// -// If we were to assume/enforce an envelope, then we could wrap response/error into the acknowledge packet, -// but we delegated that encoding to the contract -func IBCRecvPacket(ctx sdk.Context, k *wasm.Keeper, contractAddress sdk.AccAddress, env IBCPacketInfo, msg []byte) - (response []byte, err error) {} - -// how to handle error here? if we push it up the ibc stack and fail the transaction (normal handling), -// the packet may be posted again and again. just log and ignore failures here? what does a failure even mean? -// only realistic one I can imagine is OutOfGas (panic) to retry with higher gas limit. -// -// if there any point in returning a response, what does it mean? -func IBCPacketAck(ctx sdk.Context, k *wasm.Keeper, contractAddress sdk.AccAddress, env IBCPacketInfo, - originalMsg []byte, result []byte) error {} - -// same question as for IBCPacketAck -func IBCPacketDropped(ctx sdk.Context, k *wasm.Keeper, contractAddress sdk.AccAddress, env IBCPacketInfo, - originalMsg []byte, errorMsg string) error {} - -// do we need/want all this info? -type IBCPacketInfo struct { - // local id for the Channel packet was sent/received on - ChannelID string - - // sequence for the packet (will already be enforced in order if ORDERED channel) - Sequence uint64 - - // Note: Timeout if guaranteed ONLY to be exceeded iff we are processing IBCPacketDropped - // otherwise, this is just interesting metadata - - // block height after which the packet times out - TimeoutHeight uint64 - // block timestamp (in nanoseconds) after which the packet times out - TimeoutTimestamp uint64 -} -``` - -Channel Lifecycle: - -```go -package lifecycle - -// if this returns error, we reject the channel opening -// otherwise response has the versions we accept -// -// It is provided the full ChannelInfo being proposed to do any needed checks -func OnChanNegotiate(ctx sdk.Context, k *wasm.Keeper, contractAddress sdk.AccAddress, request ChannelInfo) - (version Version, err error) {} - -// This is called with the full ChannelInfo once the other side has agreed. -// An error here will still abort the handshake process. (so you can double check the order/version here) -// -// The main purpose is to allow the contract to set up any internal state knowing the channel was established, -// and keep a registry with that ChannelID -func OnChanOpened(ctx sdk.Context, k *wasm.Keeper, contractAddress sdk.AccAddress, request ChannelInfo) error {} - -// This is called when the channel is closed for any reason -// TODO: any meaning to return an error here? we cannot abort closing a channel -func OnChanClosed(ctx sdk.Context, k *wasm.Keeper, contractAddress sdk.AccAddress, request ChannelClosedInfo) error {} - -type ChannelInfo struct { - // key info to enforce (error if not what is expected) - Order channeltypes.Order - // The proposed version. This may come from the relayer, from the initiating contract, or the responding contract. - // In any case, this is the currently agreed upon version to use and if there is disagreement, the contract should - // propose another one (if possible in return value), or return an error - ProposedVersion Version - // local id for the Channel that is being initiated - ChannelID string - // these two are taken from channeltypes.Counterparty - RemotePortID string - RemoteChannelID string -} - -type ChannelClosedInfo struct { - // local id for the Channel that is being shut down - ChannelID string -} -``` - -Queries: - -These are callbacks that the contract can make, calling into a QueryPlugin. -The general type definition for a `QueryPlugin` is -`func(ctx sdk.Context, request *wasmTypes.IBCQuery) ([]byte, error)`. -All other info (like the contract address we are querying for) must be passed in -from the contract (which knows it's own address). - -Here we just defined the request and response types (which will be serialized into `[]byte`) - -```go -package queries - -type QueryPort struct { - ContractAddress string -} - -type QueryPortResponse struct { - PortID string -} - -type QueryChannels struct { - // exactly one of these must be set. ContractAddress is a shortcut to save the Contract->PortID mapping - PortID string - ContractAddress string -} - -type QueryChannelsResponse struct { - ChannelIDs []ChannelMetadata -} - -type ChannelMetadata struct { - // Local portID, channelID is our unique identifier - PortID string - ChannelID string - RemotePortID string - Order channeltypes.Order - Verson Version -} - -type QueryPendingPackets struct { - // Always required - ChannelID string - - // exactly one of these must be set. ContractAddress is a shortcut to save the Contract->PortID mapping - PortID string - ContractAddress string -} - -type QueryPendingPacketsResponse struct { - Packets []PacketMetadata -} - -type PacketMetadata struct { - // The original (serialized) message we sent - Msg []byte - - Sequence uint64 - // block height after which the packet times out - TimeoutHeight uint64 - // block timestamp (in nanoseconds) after which the packet times out - TimeoutTimestamp uint64 -} - -type QueryPendingAcknowlegdements struct { - // Always required - ChannelID string - - // exactly one of these must be set. ContractAddress is a shortcut to save the Contract->PortID mapping - PortID string - ContractAddress string -} - -type QueryPendingPacketsResponse struct { - // Do we need another struct for the metadata? - Packets []PacketMetadata -} - - -type QueryCounterparty struct { - // Always required - ChannelID string - - // exactly one of these must be set. ContractAddress is a shortcut to save the Contract->PortID mapping - PortID string - ContractAddress string -} - -// lists (port, channel) on the counterparty for our local channel -type QueryCounterpartyResponse struct { - PortID string - ChannelID string -} -``` - -### Contract (Wasm) entrypoints - -**TODO** +Please refer to the CosmWasm repo for all +[details on the IBC API from the point of view of a CosmWasm contract](https://github.com/CosmWasm/cosmwasm/blob/main/IBC.md). ## Future Ideas