README
ics: 4 title: Channel & Packet Semantics stage: draft category: IBC/TAO kind: instantiation requires: 2, 3, 5, 24 version compatibility: ibc-go v7.0.0 author: Christopher Goes [email protected] created: 2019-03-07 modified: 2019-08-25
Synopsis
The "channel" abstraction provides message delivery semantics to the interblockchain communication protocol, in three categories: ordering, exactly-once delivery, and module permissioning. A channel serves as a conduit for packets passing between a module on one chain and a module on another, ensuring that packets are executed only once, delivered in the order in which they were sent (if necessary), and delivered only to the corresponding module owning the other end of the channel on the destination chain. Each channel is associated with a particular connection, and a connection may have any number of associated channels, allowing the use of common identifiers and amortising the cost of header verification across all the channels utilising a connection & light client.
Channels are payload-agnostic. The modules which send and receive IBC packets decide how to construct packet data and how to act upon the incoming packet data, and must utilise their own application logic to determine which state transactions to apply according to what data the packet contains.
Motivation
The interblockchain communication protocol uses a cross-chain message passing model. IBC packets are relayed from one blockchain to the other by external relayer processes. Chain A and chain B confirm new blocks independently, and packets from one chain to the other may be delayed, censored, or re-ordered arbitrarily. Packets are visible to relayers and can be read from a blockchain by any relayer process and submitted to any other blockchain.
The IBC protocol must provide ordering (for ordered channels) and exactly-once delivery guarantees to allow applications to reason about the combined state of connected modules on two chains.
Example: An application may wish to allow a single tokenized asset to be transferred between and held on multiple blockchains while preserving fungibility and conservation of supply. The application can mint asset vouchers on chain
Bwhen a particular IBC packet is committed to chainB, and require outgoing sends of that packet on chainAto escrow an equal amount of the asset on chainAuntil the vouchers are later redeemed back to chainAwith an IBC packet in the reverse direction. This ordering guarantee along with correct application logic can ensure that total supply is preserved across both chains and that any vouchers minted on chainBcan later be redeemed back to chainA.
In order to provide the desired ordering, exactly-once delivery, and module permissioning semantics to the application layer, the interblockchain communication protocol must implement an abstraction to enforce these semantics — channels are this abstraction.
Definitions
ConsensusState is as defined in ICS 2.
Connection is as defined in ICS 3.
Port and authenticateCapability are as defined in ICS 5.
hash is a generic collision-resistant hash function, the specifics of which must be agreed on by the modules utilising the channel. hash can be defined differently by different chains.
Identifier, get, set, delete, getCurrentHeight, and module-system related primitives are as defined in ICS 24.
See upgrades spec for definition of pendingInflightPackets and restoreChannel.
A channel is a pipeline for exactly-once packet delivery between specific modules on separate blockchains, which has at least one end capable of sending packets and one end capable of receiving packets.
A bidirectional channel is a channel where packets can flow in both directions: from A to B and from B to A.
A unidirectional channel is a channel where packets can only flow in one direction: from A to B (or from B to A, the order of naming is arbitrary).
An ordered channel is a channel where packets are delivered exactly in the order which they were sent. This channel type offers a very strict guarantee of ordering. Either, the packets are received in the order they were sent, or if a packet in the sequence times out; then all future packets are also not receivable and the channel closes.
An ordered_allow_timeout channel is a less strict version of the ordered channel. Here, the channel logic will take a best effort approach to delivering the packets in order. In a stream of packets, the channel will relay all packets in order and if a packet in the stream times out, the timeout logic for that packet will execute and the rest of the later packets will continue processing in order. Thus, we do not close the channel on a timeout with this channel type.
An unordered channel is a channel where packets can be delivered in any order, which may differ from the order in which they were sent.
enum ChannelOrder {
ORDERED,
UNORDERED,
ORDERED_ALLOW_TIMEOUT,
}
Directionality and ordering are independent, so one can speak of a bidirectional unordered channel, a unidirectional ordered channel, etc.
All channels provide exactly-once packet delivery, meaning that a packet sent on one end of a channel is delivered no more and no less than once, eventually, to the other end.
This specification only concerns itself with bidirectional channels. Unidirectional channels can use almost exactly the same protocol and will be outlined in a future ICS.
An end of a channel is a data structure on one chain storing channel metadata:
interface ChannelEnd {
state: ChannelState
ordering: ChannelOrder
counterpartyPortIdentifier: Identifier
counterpartyChannelIdentifier: Identifier
connectionHops: [Identifier]
version: string
upgradeSequence: uint64
}
- The
stateis the current state of the channel end. - The
orderingfield indicates whether the channel isunordered,ordered, orordered_allow_timeout. - The
counterpartyPortIdentifieridentifies the port on the counterparty chain which owns the other end of the channel. - The
counterpartyChannelIdentifieridentifies the channel end on the counterparty chain. - The
nextSequenceSend, stored separately, tracks the sequence number for the next packet to be sent. - The
nextSequenceRecv, stored separately, tracks the sequence number for the next packet to be received. - The
nextSequenceAck, stored separately, tracks the sequence number for the next packet to be acknowledged. - The
connectionHopsstores the list of connection identifiers ordered starting from the receiving end towards the sender.connectionHops[0]is the connection end on the receiving chain. More than one connection hop indicates a multi-hop channel. - The
versionstring stores an opaque channel version, which is agreed upon during the handshake. This can determine module-level configuration such as which packet encoding is used for the channel. This version is not used by the core IBC protocol. If the version string contains structured metadata for the application to parse and interpret, then it is considered best practice to encode all metadata in a JSON struct and include the marshalled string in the version field.
See the upgrade spec for details on upgradeSequence.
Channel ends have a state:
enum ChannelState {
INIT,
TRYOPEN,
OPEN,
CLOSED,
FLUSHING,
FLUSHINGCOMPLETE,
}
- A channel end in
INITstate has just started the opening handshake. - A channel end in
TRYOPENstate has acknowledged the handshake step on the counterparty chain. - A channel end in
OPENstate has completed the handshake and is ready to send and receive packets. - A channel end in
CLOSEDstate has been closed and can no longer be used to send or receive packets.
See the upgrade spec for details on FLUSHING and FLUSHCOMPLETE.
A Packet, in the interblockchain communication protocol, is a particular interface defined as follows:
interface Packet {
sequence: uint64
timeoutHeight: Height
timeoutTimestamp: uint64
sourcePort: Identifier
sourceChannel: Identifier
destPort: Identifier
destChannel: Identifier
data: bytes
}
- The
sequencenumber corresponds to the order of sends and receives, where a packet with an earlier sequence number must be sent and received before a packet with a later sequence number. - The
timeoutHeightindicates a consensus height on the destination chain after which the packet will no longer be processed, and will instead count as having timed-out. - The
timeoutTimestampindicates a timestamp on the destination chain after which the packet will no longer be processed, and will instead count as having timed-out. - The
sourcePortidentifies the port on the sending chain. - The
sourceChannelidentifies the channel end on the sending chain. - The
destPortidentifies the port on the receiving chain. - The
destChannelidentifies the channel end on the receiving chain. - The
datais an opaque value which can be defined by the application logic of the associated modules.
Note that a Packet is never directly serialised. Rather it is an intermediary structure used in certain function calls that may need to be created or processed by modules calling the IBC handler.
An OpaquePacket is a packet, but cloaked in an obscuring data type by the host state machine, such that a module cannot act upon it other than to pass it to the IBC handler. The IBC handler can cast a Packet to an OpaquePacket and vice versa.
type OpaquePacket = object
In order to enable new channel types (e.g. ORDERED_ALLOW_TIMEOUT), the protocol introduces standardized packet receipts that will serve as sentinel values for the receiving chain to explicitly write to its store the outcome of a recvPacket.
enum PacketReceipt {
SUCCESSFUL_RECEIPT,
TIMEOUT_RECEIPT,
}
Desired Properties
Efficiency
- The speed of packet transmission and confirmation should be limited only by the speed of the underlying chains. Proofs should be batchable where possible.
Exactly-once delivery
- IBC packets sent on one end of a channel should be delivered exactly once to the other end.
- No network synchrony assumptions should be required for exactly-once safety. If one or both of the chains halt, packets may be delivered no more than once, and once the chains resume packets should be able to flow again.
Ordering
- On ordered channels, packets should be sent and received in the same order: if packet x is sent before packet y by a channel end on chain
A, packet x must be received before packet y by the corresponding channel end on chainB. If packet x is sent before packet y by a channel and packet x is timed out; then packet y and any packet sent after x cannot be received. - On ordered_allow_timeout channels, packets should be sent and received in the same order: if packet x is sent before packet y by a channel end on chain
A, packet x must be received or timed out before packet y by the corresponding channel end on chainB. - On unordered channels, packets may be sent and received in any order. Unordered packets, like ordered packets, have individual timeouts specified in terms of the destination chain's height.
Permissioning
- Channels should be permissioned to one module on each end, determined during the handshake and immutable afterwards (higher-level logic could tokenize channel ownership by tokenising ownership of the port). Only the module associated with a channel end should be able to send or receive on it.
Technical Specification
Dataflow visualisation
The architecture of clients, connections, channels and packets:
Preliminaries
Store paths
Channel structures are stored under a store path prefix unique to a combination of a port identifier and channel identifier:
function channelPath(portIdentifier: Identifier, channelIdentifier: Identifier): Path {
return "channelEnds/ports/{portIdentifier}/channels/{channelIdentifier}"
}
The capability key associated with a channel is stored under the channelCapabilityPath:
function channelCapabilityPath(portIdentifier: Identifier, channelIdentifier: Identifier): Path {
return "{channelPath(portIdentifier, channelIdentifier)}/key"
}
The nextSequenceSend, nextSequenceRecv, and nextSequenceAck unsigned integer counters are stored separately so they can be proved individually:
function nextSequenceSendPath(portIdentifier: Identifier, channelIdentifier: Identifier): Path {
return "nextSequenceSend/ports/{portIdentifier}/channels/{channelIdentifier}"
}
function nextSequenceRecvPath(portIdentifier: Identifier, channelIdentifier: Identifier): Path {
return "nextSequenceRecv/ports/{portIdentifier}/channels/{channelIdentifier}"
}
function nextSequenceAckPath(portIdentifier: Identifier, channelIdentifier: Identifier): Path {
return "nextSequenceAck/ports/{portIdentifier}/channels/{channelIdentifier}"
}
Constant-size commitments to packet data fields are stored under the packet sequence number:
function packetCommitmentPath(portIdentifier: Identifier, channelIdentifier: Identifier, sequence: uint64): Path {
return "commitments/ports/{portIdentifier}/channels/{channelIdentifier}/sequences/{sequence}"
}
Absence of the path in the store is equivalent to a zero-bit.
Packet receipt data are stored under the packetReceiptPath. In the case of a successful receive, the destination chain writes a sentinel success value of SUCCESSFUL_RECEIPT.
Some channel types MAY write a sentinel timeout value TIMEOUT_RECEIPT if the packet is received after the specified timeout.
function packetReceiptPath(portIdentifier: Identifier, channelIdentifier: Identifier, sequence: uint64): Path {
return "receipts/ports/{portIdentifier}/channels/{channelIdentifier}/sequences/{sequence}"
}
Packet acknowledgement data are stored under the packetAcknowledgementPath:
function packetAcknowledgementPath(portIdentifier: Identifier, channelIdentifier: Identifier, sequence: uint64): Path {
return "acks/ports/{portIdentifier}/channels/{channelIdentifier}/sequences/{sequence}"
}
Versioning
During the handshake process, two ends of a channel come to agreement on a version bytestring associated with that channel. The contents of this version bytestring are and will remain opaque to the IBC core protocol. Host state machines MAY utilise the version data to indicate supported IBC/APP protocols, agree on packet encoding formats, or negotiate other channel-related metadata related to custom logic on top of IBC.
Host state machines MAY also safely ignore the version data or specify an empty string.
Sub-protocols
Note: If the host state machine is utilising object capability authentication (see ICS 005), all functions utilising ports take an additional capability parameter.
Identifier validation
Channels are stored under a unique (portIdentifier, channelIdentifier) prefix.
The validation function validatePortIdentifier MAY be provided.
type validateChannelIdentifier = (portIdentifier: Identifier, channelIdentifier: Identifier) => boolean
If not provided, the default validateChannelIdentifier function will always return true.
Channel lifecycle management
| Initiator | Datagram | Chain acted upon | Prior state (A, B) | Posterior state (A, B) |
|---|---|---|---|---|
| Actor | ChanOpenInit | A | (none, none) | (INIT, none) |
| Relayer | ChanOpenTry | B | (INIT, none) | (INIT, TRYOPEN) |
| Relayer | ChanOpenAck | A | (INIT, TRYOPEN) | (OPEN, TRYOPEN) |
| Relayer | ChanOpenConfirm | B | (OPEN, TRYOPEN) | (OPEN, OPEN) |
| Initiator | Datagram | Chain acted upon | Prior state (A, B) | Posterior state (A, B) |
|---|---|---|---|---|
| Actor | ChanCloseInit | A | (OPEN, OPEN) | (CLOSED, OPEN) |
| Relayer | ChanCloseConfirm | B | (CLOSED, OPEN) | (CLOSED, CLOSED) |
| Actor | ChanCloseFrozen | A or B | (OPEN, OPEN) | (CLOSED, CLOSED) |
Opening handshake
The chanOpenInit function is called by a module to initiate a channel opening handshake with a module on another chain. Functions chanOpenInit and chanOpenTry do no set the new channel end in state because the channel version might be modified by the application callback. A function writeChannel should be used to write the channel end in state after executing the application callback:
function writeChannel(
portIdentifier: Identifier,
channelIdentifier: Identifier,
state: ChannelState,
order: ChannelOrder,
counterpartyPortIdentifier: Identifier,
counterpartyChannelIdentifier: Identifier,
connectionHops: [Identifier],
version: string) {
channel = ChannelEnd{
state, order,
counterpartyPortIdentifier, counterpartyChannelIdentifier,
connectionHops, version
}
provableStore.set(channelPath(portIdentifier, channelIdentifier), channel)
}
See handler functions handleChanOpenInit and handleChanOpenTry in Channel lifecycle management for more details.
The opening channel must provide the identifiers of the local channel identifier, local port, remote port, and remote channel identifier.
When the opening handshake is complete, the module which initiates the handshake will own the end of the created channel on the host ledger, and the counterparty module which it specifies will own the other end of the created channel on the counterparty chain. Once a channel is created, ownership cannot be changed (although higher-level abstractions could be implemented to provide this).
Chains MUST implement a function generateIdentifier which chooses an identifier, e.g. by incrementing a counter:
type generateIdentifier = () -> Identifier
function chanOpenInit(
order: ChannelOrder,
connectionHops: [Identifier],
portIdentifier: Identifier,
counterpartyPortIdentifier: Identifier): (channelIdentifier: Identifier, channelCapability: CapabilityKey) {
channelIdentifier = generateIdentifier()
abortTransactionUnless(validateChannelIdentifier(portIdentifier, channelIdentifier))
abortTransactionUnless(provableStore.get(channelPath(portIdentifier, channelIdentifier)) === null)
connection = provableStore.get(connectionPath(connectionHops[0]))
// optimistic channel handshakes are allowed
abortTransactionUnless(connection !== null)
abortTransactionUnless(authenticateCapability(portPath(portIdentifier), portCapability))
channelCapability = newCapability(channelCapabilityPath(portIdentifier, channelIdentifier))
provableStore.set(nextSequenceSendPath(portIdentifier, channelIdentifier), 1)
provableStore.set(nextSequenceRecvPath(portIdentifier, channelIdentifier), 1)
provableStore.set(nextSequenceAckPath(portIdentifier, channelIdentifier), 1)
return channelIdentifier, channelCapability
}
The chanOpenTry function is called by a module to accept the first step of a channel opening handshake initiated by a module on another
Excerpt (19998 of 73073 characters). Read the whole page on cosmos/ibc (ICS specifications) ↗