Skip to content

Commit

Permalink
Clean up docs, point to CosmWasm repo for contract details
Browse files Browse the repository at this point in the history
  • Loading branch information
ethanfrey committed Apr 20, 2021
1 parent c76c07c commit 602881f
Showing 1 changed file with 11 additions and 314 deletions.
325 changes: 11 additions & 314 deletions x/wasm/IBC.md
Original file line number Diff line number Diff line change
Expand Up @@ -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-<contract address>`,
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.<contract address>`,
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).
Expand All @@ -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.
Expand All @@ -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

Expand Down

0 comments on commit 602881f

Please sign in to comment.