Clean up docs, point to CosmWasm repo for contract details
This commit is contained in:
325
x/wasm/IBC.md
325
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))
|
(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
|
* Upon `Instantiate`, if a contract is *IBC Enabled*, we dynamically
|
||||||
bind a port for this contract. The port name is `wasm-<contract address>`,
|
bind a port for this contract. The port name is `wasm.<contract address>`,
|
||||||
eg. `wasm-cosmos1hmdudppzceg27qsuq707tjg8rkgj7g5hnvnw29`
|
eg. `wasm.cosmos1hmdudppzceg27qsuq707tjg8rkgj7g5hnvnw29`
|
||||||
* If a *Channel* is being established with a registered `wasm-xyz` port,
|
* If a *Channel* is being established with a registered `wasm.xyz` port,
|
||||||
the `x/wasm.Keeper` will handle this and call into the appropriate
|
the `x/wasm.Keeper` will handle this and call into the appropriate
|
||||||
contract to determine supported protocol versions during the
|
contract to determine supported protocol versions during the
|
||||||
[`ChanOpenTry` and `ChanOpenAck` phases](https://docs.cosmos.network/master/ibc/overview.html#channels).
|
[`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*.
|
* 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`
|
As there is a unique *PortID* per contract, that is filled in by `x/wasm`
|
||||||
to produce the globally unique `(PortID, ChannelID)`
|
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.
|
*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
|
* When receiving an Ack or Timeout packet, the contract also receives the
|
||||||
original packet that it sent earlier.
|
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
|
Establishing *Clients* and *Connections* is out of the scope of this
|
||||||
module and must be created by the same means as for `ibc-transfer`
|
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
|
(via the [go cli](https://github.com/cosmos/relayer) or better [ts-relayer](https://github.com/confio/ts-relayer)).
|
||||||
"IBC Enabled" contract.
|
`x/wasm` will bind a unique *Port* for each "IBC Enabled" contract.
|
||||||
|
|
||||||
For mocks, all the Packet Handling and Channel Lifecycle Hooks are routed
|
For mocks, all the Packet Handling and Channel Lifecycle Hooks are routed
|
||||||
to some Golang stub handler, but containing the contract address, so we
|
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
|
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).
|
||||||
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**
|
|
||||||
|
|
||||||
## Future Ideas
|
## Future Ideas
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user