From 3007cae679c451be11d6f00642ab7ac2d6bd4bbb Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Tue, 10 Sep 2024 12:04:34 +0200 Subject: [PATCH 01/71] start removing channel refs --- .../v2/ics-004-packet-semantics/README.md | 484 +----------------- 1 file changed, 7 insertions(+), 477 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 843161551..91e90759b 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -1,6 +1,6 @@ --- ics: 4 -title: Channel & Packet Semantics +title: Packet Semantics stage: draft category: IBC/TAO kind: instantiation @@ -13,105 +13,34 @@ 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. +IBC version 2 will simply provide packet delivery between two chains communicating and identifying each other by on-chain light clients as specified in ICS-02 with application packet data being routed to their specific IBC applications with packet-flow semantics as defined in this ICS-04. The clientIDs will tell the IBC router which chain to send the packets to and which chain a received packet came from, while the portID specifies which application on the router the packet should be sent to. ### 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 `B` when a particular IBC packet is committed to chain `B`, and require outgoing sends of that packet on chain `A` to escrow an equal amount of the asset on chain `A` until the vouchers are later redeemed back to chain `A` with 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 chain `B` can later be redeemed back to chain `A`. -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](../ics-002-client-semantics). -`Connection` is as defined in [ICS 3](../../ics-003-connection-semantics). - `Port` and `authenticateCapability` are as defined in [ICS 5](../ics-005-port-allocation). `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](../ics-024-host-requirements). -See [upgrades spec](../../ics-004-channel-and-packet-semantics/UPGRADES.md) 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. - -```typescript -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: - -```typescript -interface ChannelEnd { - state: ChannelState - ordering: ChannelOrder - counterpartyPortIdentifier: Identifier - counterpartyChannelIdentifier: Identifier - connectionHops: [Identifier] - version: string - upgradeSequence: uint64 -} -``` - - The `state` is the current state of the channel end. -- The `ordering` field indicates whether the channel is `unordered`, `ordered`, or `ordered_allow_timeout`. +- NOTE - do we need to keep this as only `unordered`? - The `ordering` field indicates whether the channel is `unordered`, `ordered`, or `ordered_allow_timeout`. - The `counterpartyPortIdentifier` identifies the port on the counterparty chain which owns the other end of the channel. -- The `counterpartyChannelIdentifier` identifies the channel end on the counterparty chain. +- The `counterpartyClientIdentifier` identifies the client 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 `connectionHops` stores 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 `version` string 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](../../ics-004-channel-and-packet-semantics/UPGRADES.md) for details on `upgradeSequence`. - -Channel ends have a *state*: - -```typescript -enum ChannelState { - INIT, - TRYOPEN, - OPEN, - CLOSED, - FLUSHING, - FLUSHINGCOMPLETE, -} -``` - -- A channel end in `INIT` state has just started the opening handshake. -- A channel end in `TRYOPEN` state has acknowledged the handshake step on the counterparty chain. -- A channel end in `OPEN` state has completed the handshake and is ready to send and receive packets. -- A channel end in `CLOSED` state has been closed and can no longer be used to send or receive packets. - -See the [upgrade spec](../../ics-004-channel-and-packet-semantics/UPGRADES.md) for details on `FLUSHING` and `FLUSHCOMPLETE`. +NOTE - Do we want to keep an upgrade spec to specify how to change the client and so on? See the [upgrade spec](../../ics-004-channel-and-packet-semantics/UPGRADES.md) for details on `upgradeSequence`. A `Packet`, in the interblockchain communication protocol, is a particular interface defined as follows: @@ -273,50 +202,6 @@ type validateChannelIdentifier = (portIdentifier: Identifier, channelIdentifier: If not provided, the default `validateChannelIdentifier` function will always return `true`. -#### Channel lifecycle management - -![Channel State Machine](../../ics-004-channel-and-packet-semantics/channel-state-machine.png) - -| 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: - -```typescript -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](../../ics-026-routing-module/README.md#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). @@ -327,365 +212,10 @@ Chains MUST implement a function `generateIdentifier` which chooses an identifie type generateIdentifier = () -> Identifier ``` -```typescript -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 chain. - -```typescript -function chanOpenTry( - order: ChannelOrder, - connectionHops: [Identifier], - portIdentifier: Identifier, - counterpartyPortIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - counterpartyVersion: string, - proofInit: CommitmentProof | MultihopProof, - proofHeight: Height): (channelIdentifier: Identifier, channelCapability: CapabilityKey) { - channelIdentifier = generateIdentifier() - - abortTransactionUnless(validateChannelIdentifier(portIdentifier, channelIdentifier)) - abortTransactionUnless(authenticateCapability(portPath(portIdentifier), portCapability)) - - connection = provableStore.get(connectionPath(connectionHops[0])) - abortTransactionUnless(connection !== null) - abortTransactionUnless(connection.state === OPEN) - - // return hops from counterparty's view - counterpartyHops = getCounterPartyHops(proofInit, connection) - - expected = ChannelEnd{ - INIT, order, portIdentifier, - "", counterpartyHops, - counterpartyVersion - } - - if (connectionHops.length > 1) { - key = channelPath(counterparty.PortId, counterparty.ChannelId) - abortTransactionUnless(connection.verifyMultihopMembership( - connection, - proofHeight, - proofInit, - connectionHops, - key - expected)) - } else { - abortTransactionUnless(connection.verifyChannelState( - proofHeight, - proofInit, - counterpartyPortIdentifier, - counterpartyChannelIdentifier, - expected - )) - } - - channelCapability = newCapability(channelCapabilityPath(portIdentifier, channelIdentifier)) - - // initialize channel sequences - provableStore.set(nextSequenceSendPath(portIdentifier, channelIdentifier), 1) - provableStore.set(nextSequenceRecvPath(portIdentifier, channelIdentifier), 1) - provableStore.set(nextSequenceAckPath(portIdentifier, channelIdentifier), 1) - - return channelIdentifier, channelCapability -} -``` - -The `chanOpenAck` is called by the handshake-originating module to acknowledge the acceptance of the initial request by the -counterparty module on the other chain. - -```typescript -function chanOpenAck( - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - counterpartyVersion: string, - proofTry: CommitmentProof | MultihopProof, - proofHeight: Height) { - channel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) - abortTransactionUnless(channel.state === INIT) - abortTransactionUnless(authenticateCapability(channelCapabilityPath(portIdentifier, channelIdentifier), capability)) - - connection = provableStore.get(connectionPath(channel.connectionHops[0])) - abortTransactionUnless(connection !== null) - abortTransactionUnless(connection.state === OPEN) - - // return hops from counterparty's view - counterpartyHops = getCounterPartyHops(proofTry, connection) - - expected = ChannelEnd{TRYOPEN, channel.order, portIdentifier, - channelIdentifier, counterpartyHops, counterpartyVersion} - - if (channel.connectionHops.length > 1) { - key = channelPath(counterparty.PortId, counterparty.ChannelId) - abortTransactionUnless(connection.verifyMultihopMembership( - connection, - proofHeight, - proofTry, - channel.connectionHops, - key, - expected)) - } else { - abortTransactionUnless(connection.verifyChannelState( - proofHeight, - proofTry, - channel.counterpartyPortIdentifier, - counterpartyChannelIdentifier, - expected - )) - } - // write will happen in the handler defined in the ICS26 spec -} -``` - -The `chanOpenConfirm` function is called by the handshake-accepting module to acknowledge the acknowledgement -of the handshake-originating module on the other chain and finish the channel opening handshake. - -```typescript -function chanOpenConfirm( - portIdentifier: Identifier, - channelIdentifier: Identifier, - proofAck: CommitmentProof | MultihopProof, - proofHeight: Height) { - channel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) - abortTransactionUnless(channel !== null) - abortTransactionUnless(channel.state === TRYOPEN) - abortTransactionUnless(authenticateCapability(channelCapabilityPath(portIdentifier, channelIdentifier), capability)) - - connection = provableStore.get(connectionPath(channel.connectionHops[0])) - abortTransactionUnless(connection !== null) - abortTransactionUnless(connection.state === OPEN) - - // return hops from counterparty's view - counterpartyHops = getCounterPartyHops(proofAck, connection) - - expected = ChannelEnd{OPEN, channel.order, portIdentifier, - channelIdentifier, counterpartyHops, channel.version} - - if (connectionHops.length > 1) { - key = channelPath(counterparty.PortId, counterparty.ChannelId) - abortTransactionUnless(connection.verifyMultihopMembership( - connection, - proofHeight, - proofAck, - channel.connectionHops, - key - expected)) - } else { - abortTransactionUnless(connection.verifyChannelState( - proofHeight, - proofAck, - channel.counterpartyPortIdentifier, - channel.counterpartyChannelIdentifier, - expected - )) - } - - // write will happen in the handler defined in the ICS26 spec -} -``` - -##### Closing handshake - -The `chanCloseInit` function is called by either module to close their end of the channel. Once closed, channels cannot be reopened. - -Calling modules MAY atomically execute appropriate application logic in conjunction with calling `chanCloseInit`. - -Any in-flight packets can be timed-out as soon as a channel is closed. - -```typescript -function chanCloseInit( - portIdentifier: Identifier, - channelIdentifier: Identifier) { - abortTransactionUnless(authenticateCapability(channelCapabilityPath(portIdentifier, channelIdentifier), capability)) - channel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) - abortTransactionUnless(channel !== null) - abortTransactionUnless(channel.state !== CLOSED) - connection = provableStore.get(connectionPath(channel.connectionHops[0])) - abortTransactionUnless(connection !== null) - abortTransactionUnless(connection.state === OPEN) - channel.state = CLOSED - provableStore.set(channelPath(portIdentifier, channelIdentifier), channel) -} -``` - -The `chanCloseConfirm` function is called by the counterparty module to close their end of the channel, -since the other end has been closed. - -Calling modules MAY atomically execute appropriate application logic in conjunction with calling `chanCloseConfirm`. - -Once closed, channels cannot be reopened and identifiers cannot be reused. Identifier reuse is prevented because -we want to prevent potential replay of previously sent packets. The replay problem is analogous to using sequence -numbers with signed messages, except where the light client algorithm "signs" the messages (IBC packets), and the replay -prevention sequence is the combination of port identifier, channel identifier, and packet sequence - hence we cannot -allow the same port identifier & channel identifier to be reused again with a sequence reset to zero, since this -might allow packets to be replayed. It would be possible to safely reuse identifiers if timeouts of a particular -maximum height/time were mandated & tracked, and future specification versions may incorporate this feature. - -```typescript -function chanCloseConfirm( - portIdentifier: Identifier, - channelIdentifier: Identifier, - proofInit: CommitmentProof | MultihopProof, - proofHeight: Height) { - abortTransactionUnless(authenticateCapability(channelCapabilityPath(portIdentifier, channelIdentifier), capability)) - channel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) - abortTransactionUnless(channel !== null) - abortTransactionUnless(channel.state !== CLOSED) - connection = provableStore.get(connectionPath(channel.connectionHops[0])) - abortTransactionUnless(connection !== null) - abortTransactionUnless(connection.state === OPEN) - - // return hops from counterparty's view - counterpartyHops = getCounterPartyHops(proofInit, connection) - - expected = ChannelEnd{CLOSED, channel.order, portIdentifier, - channelIdentifier, counterpartyHops, channel.version} - - if (connectionHops.length > 1) { - key = channelPath(counterparty.PortId, counterparty.ChannelId) - abortTransactionUnless(connection.verifyMultihopMembership( - connection, - proofHeight, - proofInit, - channel.connectionHops, - key - expected)) - } else { - abortTransactionUnless(connection.verifyChannelState( - proofHeight, - proofInit, - channel.counterpartyPortIdentifier, - channel.counterpartyChannelIdentifier, - expected - )) - } - - // write may happen asynchronously in the handler defined in the ICS26 spec - // if the channel is closing during an upgrade, - // then we can delete all auxiliary upgrade information - provableStore.delete(channelUpgradePath(portIdentifier, channelIdentifier)) - privateStore.delete(counterpartyUpgradePath(portIdentifier, channelIdentifier)) - - channel.state = CLOSED - provableStore.set(channelPath(portIdentifier, channelIdentifier), channel) -} -``` - -The `chanCloseFrozen` function is called by a relayer to force close a multi-hop channel if any client state in the -channel path is frozen. A relayer should send proof of the frozen client state to each end of the channel with a -proof of the frozen client state in the channel path starting from each channel end up until the first frozen client. -The multi-hop proof for each channel end will be different and consist of a proof formed starting from each channel -end up to the frozen client. - -The multi-hop proof starts with a chain with a frozen client for the misbehaving chain. However, the frozen client exists -on the next blockchain in the channel path so the key/value proof is indexed to evaluate on the consensus state holding -that client state. The client state path requires knowledge of the client id which can be determined from the -connectionEnd on the misbehaving chain prior to the misbehavior submission. - -Once frozen, it is possible for a channel to be unfrozen (reactivated) via governance processes once the misbehavior in -the channel path has been resolved. However, this process is out-of-protocol. - -Example: - -Given a multi-hop channel path over connections from chain `A` to chain `E` and misbehaving chain `C` - -`A <--> B <--x C x--> D <--> E` - -Assume any relayer submits evidence of misbehavior to chain `B` and chain `D` to freeze their respective clients for chain `C`. - -A relayer may then provide a multi-hop proof of the frozen client on chain `B` to chain `A` to close the channel on chain `A`, and another relayer (or the same one) may also relay a multi-hop proof of the frozen client on chain `D` to chain `E` to close the channel end on chain `E`. - -However, it must also be proven that the frozen client state corresponds to a specific hop in the channel path. - -Therefore, a proof of the connection end on chain `B` with counterparty connection end on chain `C` must also be provided along with the client state proof to prove that the `clientID` for the client state matches the `clientID` in the connection end. Furthermore, the `connectionID` for the connection end MUST match the expected ID from the channel's `connectionHops` field. - -```typescript -function chanCloseFrozen( - portIdentifier: Identifier, - channelIdentifier: Identifier, - proofConnection: MultihopProof, - proofClientState: MultihopProof, - proofHeight: Height) { - abortTransactionUnless(authenticateCapability(channelCapabilityPath(portIdentifier, channelIdentifier), capability)) - channel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) - abortTransactionUnless(channel !== null) - hopsLength = channel.connectionHops.length - abortTransactionUnless(hopsLength === 1) - abortTransactionUnless(channel.state !== CLOSED) - connection = provableStore.get(connectionPath(channel.connectionHops[0])) - abortTransactionUnless(connection !== null) - abortTransactionUnless(connection.state === OPEN) - - // lookup connectionID for connectionEnd corresponding to misbehaving chain - let connectionIdx = proofConnection.ConnectionProofs.length + 1 - abortTransactionUnless(connectionIdx < hopsLength) - let connectionID = channel.ConnectionHops[connectionIdx] - let connectionProofKey = connectionPath(connectionID) - let connectionProofValue = proofConnection.KeyProof.Value - let frozenConnectionEnd = abortTransactionUnless(Unmarshal(connectionProofValue)) - - // the clientID in the connection end must match the clientID for the frozen client state - let clientID = frozenConnectionEnd.ClientId - - // truncated connectionHops. e.g. client D on chain C is frozen: A, B, C, D, E -> A, B, C - let connectionHops = channel.ConnectionHops[:len(mProof.ConnectionProofs)+1] - - // verify the connection proof - abortTransactionUnless(connection.verifyMultihopMembership( - connection, - proofHeight, - proofConnection, - connectionHops, - connectionProofKey, - connectionProofValue)) - - - // key and value for the frozen client state - let clientStateKey = clientStatePath(clientID) - let clientStateValue = proofClientState.KeyProof.Value - let frozenClientState = abortTransactionUnless(Unmarshal(clientStateValue)) - - // ensure client state is frozen by checking FrozenHeight - abortTransactionUnless(frozenClientState.FrozenHeight.RevisionHeight !== 0) - - // verify the frozen client state proof - abortTransactionUnless(connection.verifyMultihopMembership( - connection, - proofHeight, - proofConnection, - connectionHops, - clientStateKey, - clientStateValue)) - - channel.state = FROZEN - provableStore.set(channelPath(portIdentifier, channelIdentifier), channel) -} -``` - ##### Multihop utility functions +MMM, + ```typescript // Return the counterparty connectionHops function getCounterPartyHops(proof: CommitmentProof | MultihopProof, lastConnection: ConnectionEnd) string[] { From 7a9a664a72c8d82b456f93425b56f947edfb48c4 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Tue, 10 Sep 2024 16:49:28 +0200 Subject: [PATCH 02/71] add counterparty refs --- .../v2/ics-004-packet-semantics/README.md | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 91e90759b..6ac799587 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -33,6 +33,7 @@ The interblockchain communication protocol uses a cross-chain message passing mo - The `state` is the current state of the channel end. - NOTE - do we need to keep this as only `unordered`? - The `ordering` field indicates whether the channel is `unordered`, `ordered`, or `ordered_allow_timeout`. +NOTE - do we need to keep these two? - The `counterpartyPortIdentifier` identifies the port on the counterparty chain which owns the other end of the channel. - The `counterpartyClientIdentifier` identifies the client end on the counterparty chain. - The `nextSequenceSend`, stored separately, tracks the sequence number for the next packet to be sent. @@ -40,6 +41,15 @@ The interblockchain communication protocol uses a cross-chain message passing mo - The `nextSequenceAck`, stored separately, tracks the sequence number for the next packet to be acknowledged. - The `version` string 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. +`Counterparty` is the data structure responsible for maintaining the counterparty information. + +```typescript +interface Counterparty { + channelId: Identifier + keyPrefix: CommitmentPrefix +} +``` + NOTE - Do we want to keep an upgrade spec to specify how to change the client and so on? See the [upgrade spec](../../ics-004-channel-and-packet-semantics/UPGRADES.md) for details on `upgradeSequence`. A `Packet`, in the interblockchain communication protocol, is a particular interface defined as follows: @@ -191,6 +201,41 @@ Host state machines MAY also safely ignore the version data or specify an empty > Note: If the host state machine is utilising object capability authentication (see [ICS 005](../ics-005-port-allocation)), all functions utilising ports take an additional capability parameter. +#### Counterparty Idenfitifcation and Registration + +A client MUST have the ability to idenfity its counterparty. With a client, we can prove any key/value path on the counterparty. However, without knowing which identifier the counterparty uses when it sends messages to us, we cannot differentiate between messages sent from the counterparty to our chain vs messages sent from the counterparty with other chains. Most implementations will not be able to store the ICS-24 paths directly as a key in the global namespace, but will instead write to a reserved, prefixed keyspace so as not to conflict with other application state writes. Thus the counteparty information we must have includes both its identifier for our chain as well as the key prefix under which it will write the provable ICS-24 paths. + +Thus, IBC version 2 introduces a new message `RegisterCounterparty` that will associate the counterparty client of our chain with our client of the counterparty. Thus, if the `RegisterCounterparty` message is submitted to both sides correctly. Then both sides have mirrored pairs that can be treated as channel identifiers. Assuming they are correct, the client on each side is unique and provides an authenticated stream of packet data between the two chains. If the `RegisterCounterparty` message submits the wrong clientID, this can lead to invalid behaviour; but this is equivalent to a relayer submitting an invalid client in place of a correct client for the desired chain. In the simplest case, we can rely on out-of-band social consensus to only send on valid pairs that represent a connection between the desired chains of the user; just as we currently rely on out-of-band social consensus that a given clientID and channel built on top of it is the valid, canonical identifier of our desired chain. + +```typescript +function RegisterCounterparty( + channelIdentifier: Identifier, // this will be our own client identifier representing our channel to desired chain + counterpartyChannelIdentifier: Identifier, // this is the counterparty's identifier of our chain + counterpartyKeyPrefix: CommitmentPrefix, + authentication: data, // implementation-specific authentication data +) { + assert(verify(authentication)) + + counterparty = Counterparty{ + channelId: counterpartyChannelIdentifier, + keyPrefix: counterpartyKeyPrefix + } + + privateStore.set(counterpartyPath(channelIdentifier), counterparty) +} +``` + +The `RegisterCounterparty` method allows for authentication data that implementations may verify before storing the provided counterparty identifier. The strongest authentication possible is to have a valid clientState and consensus state of our chain in the authentication along with a proof it was stored at the claimed counterparty identifier. +A simpler but weaker authentication would simply be to check that the `RegisterCounterparty` message is sent by the same relayer that initialized the client. This would make the client parameters completely initialized by the relayer. Thus, users must verify that the client is pointing to the correct chain and that the counterparty identifier is correct as well before using the lite channel identified by the provided client-client pair. + +```typescript +// getCounterparty retrieves the stored counterparty identifier +// given the channelIdentifier on our chain once it is provided +function getCounterparty(channelIdentifier: Identifier): Counterparty { + return privateStore.get(counterpartyPath(channelIdentifier)) +} +``` + #### Identifier validation Channels are stored under a unique `(portIdentifier, channelIdentifier)` prefix. From 411fc3358ef59a5a0a021c76b66a13b3db1e55ce Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Wed, 11 Sep 2024 13:22:20 +0200 Subject: [PATCH 03/71] introducing multi-packet data structure --- .../v2/ics-004-packet-semantics/README.md | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 6ac799587..1f9776b7b 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -13,7 +13,7 @@ modified: 2019-08-25 ## Synopsis -IBC version 2 will simply provide packet delivery between two chains communicating and identifying each other by on-chain light clients as specified in ICS-02 with application packet data being routed to their specific IBC applications with packet-flow semantics as defined in this ICS-04. The clientIDs will tell the IBC router which chain to send the packets to and which chain a received packet came from, while the portID specifies which application on the router the packet should be sent to. +The ICS-04 defines the packet data strcuture, the packet-flow semantics and the mechanisms to route packets to their specific IBC applications. ### Motivation @@ -32,7 +32,7 @@ The interblockchain communication protocol uses a cross-chain message passing mo `Identifier`, `get`, `set`, `delete`, `getCurrentHeight`, and module-system related primitives are as defined in [ICS 24](../ics-024-host-requirements). - The `state` is the current state of the channel end. -- NOTE - do we need to keep this as only `unordered`? - The `ordering` field indicates whether the channel is `unordered`, `ordered`, or `ordered_allow_timeout`. +- NOTE - do we need to keep this as only `unordered` for some compatibility reasons? Otherwise being only underored we can delete this, no? - The `ordering` field indicates whether the channel is `unordered`, `ordered`, or `ordered_allow_timeout`. NOTE - do we need to keep these two? - The `counterpartyPortIdentifier` identifies the port on the counterparty chain which owns the other end of the channel. - The `counterpartyClientIdentifier` identifies the client end on the counterparty chain. @@ -59,14 +59,16 @@ interface Packet { sequence: uint64 timeoutHeight: Height timeoutTimestamp: uint64 - sourcePort: Identifier - sourceChannel: Identifier - destPort: Identifier - destChannel: Identifier - data: bytes + sourcePort: Identifier // identifier of the application on sender + sourceChannel: Identifier // identifier of the client of destination on sender chain + destPort: Identifier // identifier of the application on destination + destChannel: Identifier // identifier of the client of sender on the destination chain + data: [] bytes } ``` +IBC version 2 will provide packet delivery between two chains communicating and identifying each other by on-chain light clients as specified in ICS-02. The channelID derived from the clientIDs will tell the IBC router which chain to send the packets to and which chain a received packet came from, while the portID specifies which application on the router the packet should be sent to. + - The `sequence` number 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 `timeoutHeight` indicates 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 `timeoutTimestamp` indicates a timestamp on the destination chain after which the packet will no longer be processed, and will instead count as having timed-out. @@ -74,7 +76,7 @@ interface Packet { - The `sourceChannel` identifies the channel end on the sending chain. - The `destPort` identifies the port on the receiving chain. - The `destChannel` identifies the channel end on the receiving chain. -- The `data` is an opaque value which can be defined by the application logic of the associated modules. +- The `data` is an opaque array of data values which can be defined by the application logic of the associated modules. When multiple values are passed-in the system will handle the packet as a multi-data packet. 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. @@ -84,7 +86,7 @@ An `OpaquePacket` is a packet, but cloaked in an obscuring data type by the host 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`. +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`. ```typescript enum PacketReceipt { @@ -108,9 +110,7 @@ enum PacketReceipt { #### 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 chain `B`. 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 chain `B`. -- 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. +- IBC version 2 supports only *unordered* communications, thus, packets may be sent and received in any order. Unordered packets, have individual timeouts specified in terms of the destination chain's height. #### Permissioning From 9820a3e78388f0a373187f7bbcaf3489446b5de8 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Wed, 11 Sep 2024 17:09:19 +0200 Subject: [PATCH 04/71] add multi-data packet --- .../v2/ics-004-packet-semantics/README.md | 176 +++++++++++------- 1 file changed, 105 insertions(+), 71 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 1f9776b7b..708eb5299 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -13,35 +13,27 @@ modified: 2019-08-25 ## Synopsis -The ICS-04 defines the packet data strcuture, the packet-flow semantics and the mechanisms to route packets to their specific IBC applications. +The ICS-04 defines the mechanism to bidirectionally set up clients for each pair of communicating chains with specific Identifiers to establish the ground truth for the secure packet delivery, the packet data strcuture including the multi-data packet, the packet-flow semantics, the mechanisms to route the verification to the underlying clients, and how to route packets to their specific IBC applications. ### 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 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. > **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 `B` when a particular IBC packet is committed to chain `B`, and require outgoing sends of that packet on chain `A` to escrow an equal amount of the asset on chain `A` until the vouchers are later redeemed back to chain `A` with 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 chain `B` can later be redeemed back to chain `A`. +The IBC version 2 will provide packet delivery between two chains communicating and identifying each other by on-chain light clients as specified in ICS-02. + ### Definitions `ConsensusState` is as defined in [ICS 2](../ics-002-client-semantics). - +// NOTE what about Port and Capabilities? `Port` and `authenticateCapability` are as defined in [ICS 5](../ics-005-port-allocation). `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](../ics-024-host-requirements). -- The `state` is the current state of the channel end. -- NOTE - do we need to keep this as only `unordered` for some compatibility reasons? Otherwise being only underored we can delete this, no? - The `ordering` field indicates whether the channel is `unordered`, `ordered`, or `ordered_allow_timeout`. -NOTE - do we need to keep these two? -- The `counterpartyPortIdentifier` identifies the port on the counterparty chain which owns the other end of the channel. -- The `counterpartyClientIdentifier` identifies the client 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 `version` string 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. - -`Counterparty` is the data structure responsible for maintaining the counterparty information. +`Counterparty` is the data structure responsible for maintaining the counterparty information necessary to establish the ground truth for securing the interchain communication. ```typescript interface Counterparty { @@ -50,7 +42,15 @@ interface Counterparty { } ``` -NOTE - Do we want to keep an upgrade spec to specify how to change the client and so on? See the [upgrade spec](../../ics-004-channel-and-packet-semantics/UPGRADES.md) for details on `upgradeSequence`. +- 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. +// Note do we need to keep the version in the end? Should this be just the protocol version? +- The `version` string 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. + +// NOTE - Do we want to keep an upgrade spec to specify how to change the client and so on? Probably unnecessary, just showing here how to do that would be enough. + +See the [upgrade spec](../../ics-004-channel-and-packet-semantics/UPGRADES.md) for details on `upgradeSequence`. A `Packet`, in the interblockchain communication protocol, is a particular interface defined as follows: @@ -59,24 +59,36 @@ interface Packet { sequence: uint64 timeoutHeight: Height timeoutTimestamp: uint64 - sourcePort: Identifier // identifier of the application on sender - sourceChannel: Identifier // identifier of the client of destination on sender chain - destPort: Identifier // identifier of the application on destination - destChannel: Identifier // identifier of the client of sender on the destination chain - data: [] bytes + sourceID: Identifier // identifier of the source chain clientID + destID: Identifier // identifier of the sender chain clientID on the destination chain + packetData: [] PacketData } ``` -IBC version 2 will provide packet delivery between two chains communicating and identifying each other by on-chain light clients as specified in ICS-02. The channelID derived from the clientIDs will tell the IBC router which chain to send the packets to and which chain a received packet came from, while the portID specifies which application on the router the packet should be sent to. - - The `sequence` number 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 `timeoutHeight` indicates 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 `timeoutTimestamp` indicates a timestamp on the destination chain after which the packet will no longer be processed, and will instead count as having timed-out. -- The `sourcePort` identifies the port on the sending chain. -- The `sourceChannel` identifies the channel end on the sending chain. -- The `destPort` identifies the port on the receiving chain. -- The `destChannel` identifies the channel end on the receiving chain. -- The `data` is an opaque array of data values which can be defined by the application logic of the associated modules. When multiple values are passed-in the system will handle the packet as a multi-data packet. +- The `sourceID` derived from the `clientIDs` will tell the IBC router which chain a received packet came from. +- The `destID` derived from the `clientIDs` will tell the IBC router which chain to send the packets to. + +The `PacketData` is a particular interface defined as follows: + +```typescript +interface PacketData { + sourcePort: Identifier // identifier of the source chain application + destPort: Identifier // identifier of the destination chain application + version: string // Maybe not necessary + encoding: string + payload: bytes +} +``` + +- The `sourcePort` identify the source application +- The `destPort` derived from the `clientIDs` will tell the IBC router which chain to send the packets to. +- The `encoding` to allow the specification of custom data encoding +- The `payload` that can be defined by the application logic of the associated modules. + +When the array of packetData, passed-in the packet, is populated with multiple values, the system will handle the packet as a multi-data packet. 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. @@ -100,7 +112,8 @@ enum PacketReceipt { #### 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. +- Proofs should be batchable where possible. +- The system must be able to process the multiple packetData contained in a single IBC packet, to reduce the amount of packet flows. #### Exactly-once delivery @@ -114,6 +127,8 @@ enum PacketReceipt { #### Permissioning +// NOTE - here what about capabilities and permissions? + - 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. @@ -131,6 +146,8 @@ The architecture of clients, connections, channels and packets: Channel structures are stored under a store path prefix unique to a combination of a port identifier and channel identifier: +// NOTE Channel paths and capabilities should be maintained? + ```typescript function channelPath(portIdentifier: Identifier, channelIdentifier: Identifier): Path { return "channelEnds/ports/{portIdentifier}/channels/{channelIdentifier}" @@ -188,15 +205,6 @@ function packetAcknowledgementPath(portIdentifier: Identifier, channelIdentifier } ``` -### 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](../ics-005-port-allocation)), all functions utilising ports take an additional capability parameter. @@ -236,47 +244,24 @@ function getCounterparty(channelIdentifier: Identifier): Counterparty { } ``` -#### Identifier validation - -Channels are stored under a unique `(portIdentifier, channelIdentifier)` prefix. -The validation function `validatePortIdentifier` MAY be provided. +Thus, once two chains have set up clients for each other with specific Identifiers, they can send IBC packets using the packet interface defined before. -```typescript -type validateChannelIdentifier = (portIdentifier: Identifier, channelIdentifier: Identifier) => boolean -``` +Since the packets are addressed **directly** with the underlying light clients, there are **no** more handshakes necessary. Instead the packet sender must be capable of providing the correct pair. -If not provided, the default `validateChannelIdentifier` function will always return `true`. +Sending a packet with the wrong source client is equivalent to sending a packet with the wrong source channel. Sending a packet on a channel with the wrong provided counterparty is a new source of errors, however this is added to the burden of out-of-band social consensus. -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). +If the client and counterparty identifiers are setup correctly, then the correctness and soundness properties of IBC holds. IBC packet flow is guaranteed to succeed. If a user sends a packet with the wrong destination channel, then as we will see it will be impossible for the intended destination to correctly verify the packet thus, the packet will simply time out. -Chains MUST implement a function `generateIdentifier` which chooses an identifier, e.g. by incrementing a counter: +#### Registering IBC applications on the router -```typescript -type generateIdentifier = () -> Identifier -``` +The IBC router contains a mapping from a reserved application port and the supported versions of that application as well as a mapping from channelIdentifiers to channels. -##### Multihop utility functions - -MMM, - ```typescript -// Return the counterparty connectionHops -function getCounterPartyHops(proof: CommitmentProof | MultihopProof, lastConnection: ConnectionEnd) string[] { - - let counterpartyHops: string[] = [lastConnection.counterpartyConnectionIdentifier] - if typeof(proof) === 'MultihopProof' { - for connData in proofs.ConnectionProofs { - connectionEnd = abortTransactionUnless(Unmarshal(connData.Value)) - counterpartyHops.push(connectionEnd.GetCounterparty().GetConnectionID()) - } - - // reverse the hops so they are ordered from sender --> receiver - counterpartyHops = counterpartyHops.reverse() - } - - return counterpartyHops +type IBCRouter struct { + versions: portID -> [Version] + callbacks: portID -> [Callback] + clients: clientId -> Client + ports: portID -> counterpartyPortID } ``` @@ -1117,3 +1102,52 @@ Mar 28, 2023 - Add `writeChannel` function to write channel end after executing ## Copyright All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0). + + +/* NOTE What to do with this? + +#### Identifier validation + +Channels are stored under a unique `(portIdentifier, channelIdentifier)` prefix. +The validation function `validatePortIdentifier` MAY be provided. + +```typescript +type validateChannelIdentifier = (portIdentifier: Identifier, channelIdentifier: Identifier) => boolean +``` + +If not provided, the default `validateChannelIdentifier` function will always return `true`. + +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: + +```typescript +type generateIdentifier = () -> Identifier +``` + +##### Multihop utility functions + +MMM, + +```typescript +// Return the counterparty connectionHops +function getCounterPartyHops(proof: CommitmentProof | MultihopProof, lastConnection: ConnectionEnd) string[] { + + let counterpartyHops: string[] = [lastConnection.counterpartyConnectionIdentifier] + if typeof(proof) === 'MultihopProof' { + for connData in proofs.ConnectionProofs { + connectionEnd = abortTransactionUnless(Unmarshal(connData.Value)) + counterpartyHops.push(connectionEnd.GetCounterparty().GetConnectionID()) + } + + // reverse the hops so they are ordered from sender --> receiver + counterpartyHops = counterpartyHops.reverse() + } + + return counterpartyHops +} +``` + +*/ From 103095d687d54dfd35bc65cf56acc007b4dad1e1 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Wed, 11 Sep 2024 17:25:36 +0200 Subject: [PATCH 05/71] introduce packet handlers --- .../v2/ics-004-packet-semantics/README.md | 677 ++++-------------- 1 file changed, 131 insertions(+), 546 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 708eb5299..18aec0c4a 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -265,7 +265,9 @@ type IBCRouter struct { } ``` -#### Packet flow & handling +#### Packet Flow through the Router & handling + +TODO : Adapat to new flow ![Packet State Machine](../../ics-004-channel-and-packet-semantics/packet-state-machine.png) @@ -296,6 +298,8 @@ Represented spatially, packet transit between two machines can be rendered as fo ##### Sending packets +TODO Adapt description + The `sendPacket` function is called by a module in order to send *data* (in the form of an IBC packet) on a channel end owned by the calling module. Calling modules MUST execute application logic atomically in conjunction with calling `sendPacket`. @@ -313,40 +317,36 @@ Note that the full packet is not stored in the state of the chain - merely a sho ```typescript function sendPacket( - capability: CapabilityKey, - sourcePort: Identifier, - sourceChannel: Identifier, - timeoutHeight: Height, - timeoutTimestamp: uint64, - data: bytes): uint64 { - channel = provableStore.get(channelPath(sourcePort, sourceChannel)) - - // check that the channel must be OPEN to send packets; - abortTransactionUnless(channel !== null) - abortTransactionUnless(channel.state === OPEN) - connection = provableStore.get(connectionPath(channel.connectionHops[0])) - abortTransactionUnless(connection !== null) - - // check if the calling module owns the sending port - abortTransactionUnless(authenticateCapability(channelCapabilityPath(sourcePort, sourceChannel), capability)) + sourcePort: Identifier, + sourceChannel: Identifier, + timeoutHeight: Height, + timeoutTimestamp: uint64, + packetData: []byte +): uint64 { + // in this specification, the source channel is the clientId + client = router.clients[packet.sourceChannel] + assert(client !== null) // disallow packets with a zero timeoutHeight and timeoutTimestamp - abortTransactionUnless(timeoutHeight !== 0 || timeoutTimestamp !== 0) + assert(timeoutHeight !== 0 || timeoutTimestamp !== 0) // check that the timeout height hasn't already passed in the local client tracking the receiving chain - latestClientHeight = provableStore.get(clientPath(connection.clientIdentifier)).latestClientHeight() - abortTransactionUnless(timeoutHeight === 0 || latestClientHeight < timeoutHeight) - - // increment the send sequence counter - sequence = provableStore.get(nextSequenceSendPath(sourcePort, sourceChannel)) - provableStore.set(nextSequenceSendPath(sourcePort, sourceChannel), sequence+1) + latestClientHeight = client.latestClientHeight() + assert(timeoutHeight === 0 || latestClientHeight < timeoutHeight) + // if the sequence doesn't already exist, this call initializes the sequence to 0 + sequence = channelStore.get(nextSequenceSendPath(commitPort, sourceChannel)) + // store commitment to the packet data & packet timeout - provableStore.set( - packetCommitmentPath(sourcePort, sourceChannel, sequence), + channelStore.set( + packetCommitmentPath(commitPort, sourceChannel, sequence), hash(hash(data), timeoutHeight, timeoutTimestamp) ) + // increment the sequence. Thus there are monotonically increasing sequences for packet flow + // from sourcePort, sourceChannel pair + channelStore.set(nextSequenceSendPath(commitPort, sourceChannel), sequence+1) + // log that a packet can be safely sent emitLogEntry("sendPacket", { sequence: sequence, @@ -355,12 +355,12 @@ function sendPacket( timeoutTimestamp: timeoutTimestamp }) - return sequence } ``` #### Receiving packets +TODO Adapt description The `recvPacket` function is called by a module in order to receive an IBC packet sent on the corresponding channel end on the counterparty chain. Atomically in conjunction with calling `recvPacket`, calling modules MUST either execute application logic or queue the packet for future execution. @@ -382,137 +382,47 @@ We pass the address of the `relayer` that signed and submitted the packet to ena ```typescript function recvPacket( packet: OpaquePacket, - proof: CommitmentProof | MultihopProof, + proof: CommitmentProof, proofHeight: Height, relayer: string): Packet { - - channel = provableStore.get(channelPath(packet.destPort, packet.destChannel)) - abortTransactionUnless(channel !== null) - abortTransactionUnless(channel.state === OPEN || (channel.state === FLUSHING) || (channel.state === FLUSHCOMPLETE)) - counterpartyUpgrade = privateStore.get(counterpartyUpgradePath(packet.destPort, packet.destChannel)) - // defensive check that ensures chain does not process a packet higher than the last packet sent before - // counterparty went into FLUSHING mode. If the counterparty is implemented correctly, this should never abort - abortTransactionUnless(counterpartyUpgrade.nextSequenceSend == 0 || packet.sequence < counterpartyUpgrade.nextSequenceSend) - abortTransactionUnless(authenticateCapability(channelCapabilityPath(packet.destPort, packet.destChannel), capability)) - abortTransactionUnless(packet.sourcePort === channel.counterpartyPortIdentifier) - abortTransactionUnless(packet.sourceChannel === channel.counterpartyChannelIdentifier) - - connection = provableStore.get(connectionPath(channel.connectionHops[0])) - abortTransactionUnless(connection !== null) - abortTransactionUnless(connection.state === OPEN) - - if (len(channel.connectionHops) > 1) { - key = packetCommitmentPath(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) - abortTransactionUnless(connection.verifyMultihopMembership( - connection, + // in this specification, the destination channel is the clientId + client = router.clients[packet.destChannel] + assert(client !== null) + + // assert source channel is destChannel's counterparty channel identifier + counterparty = getCounterparty(packet.destChannel) + assert(packet.sourceChannel == counterparty.channelId) + + // assert source port is destPort's counterparty port identifier + assert(packet.sourcePort == ports[packet.destPort]) + + packetPath = packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence) + merklePath = applyPrefix(counterparty.keyPrefix, packetPath) + // DISCUSSION NEEDED: Should we have an in-protocol notion of Prefixing the path + // or should we make this a concern of the client's VerifyMembership + // proofPath = applyPrefix(client.counterpartyChannelStoreIdentifier, packetPath) + assert(client.verifyMembership( proofHeight, + 0, 0, // zeroed out delay period proof, - channel.ConnectionHops, - key, + merklePath, hash(packet.data, packet.timeoutHeight, packet.timeoutTimestamp) - )) - } else { - abortTransactionUnless(connection.verifyPacketData( - proofHeight, - proof, - packet.sourcePort, - packet.sourceChannel, - packet.sequence, - hash(packet.data, packet.timeoutHeight, packet.timeoutTimestamp) - )) - } + )) - // do sequence check before any state changes - if channel.order == ORDERED || channel.order == ORDERED_ALLOW_TIMEOUT { - nextSequenceRecv = provableStore.get(nextSequenceRecvPath(packet.destPort, packet.destChannel)) - if (packet.sequence < nextSequenceRecv) { - // event is emitted even if transaction is aborted - emitLogEntry("recvPacket", { - data: packet.data - timeoutHeight: packet.timeoutHeight, - timeoutTimestamp: packet.timeoutTimestamp, - sequence: packet.sequence, - sourcePort: packet.sourcePort, - sourceChannel: packet.sourceChannel, - destPort: packet.destPort, - destChannel: packet.destChannel, - order: channel.order, - connection: channel.connectionHops[0] - }) - } - - abortTransactionUnless(packet.sequence === nextSequenceRecv) - } + assert(packet.timeoutHeight === 0 || getConsensusHeight() < packet.timeoutHeight) + assert(packet.timeoutTimestamp === 0 || currentTimestamp() < packet.timeoutTimestamp) - switch channel.order { - case ORDERED: - case UNORDERED: - abortTransactionUnless(packet.timeoutHeight === 0 || getConsensusHeight() < packet.timeoutHeight) - abortTransactionUnless(packet.timeoutTimestamp === 0 || currentTimestamp() < packet.timeoutTimestamp) - break; - - case ORDERED_ALLOW_TIMEOUT: - // for ORDERED_ALLOW_TIMEOUT, we do not abort on timeout - // instead increment next sequence recv and write the sentinel timeout value in packet receipt - // then return - if (getConsensusHeight() >= packet.timeoutHeight && packet.timeoutHeight != 0) || (currentTimestamp() >= packet.timeoutTimestamp && packet.timeoutTimestamp != 0) { - nextSequenceRecv = nextSequenceRecv + 1 - provableStore.set(nextSequenceRecvPath(packet.destPort, packet.destChannel), nextSequenceRecv) - provableStore.set( - packetReceiptPath(packet.destPort, packet.destChannel, packet.sequence), - TIMEOUT_RECEIPT - ) - } - return; - - default: - // unsupported channel type - abortTransactionUnless(false) - } + + // we must set the receipt so it can be verified on the other side + // this receipt does not contain any data, since the packet has not yet been processed + // it's the sentinel success receipt: []byte{0x01} + packetReceipt = channelStore.get(packetReceiptPath(packet.destPort, packet.destChannel, packet.sequence)) + assert(packetReceipt === null) - // REPLAY PROTECTION: in order to free storage, implementations may choose to - // delete acknowledgements and packet receipts when a channel upgrade is successfully - // completed. In that case, implementations must also make sure that any packet with - // a sequence already used before the channel upgrade is rejected. This is needed to - // prevent replay attacks (see this PR in ibc-go for an example of how this is achieved: - // https://github.com/cosmos/ibc-go/pull/5651). - - // all assertions passed (except sequence check), we can alter state - - switch channel.order { - case ORDERED: - case ORDERED_ALLOW_TIMEOUT: - nextSequenceRecv = nextSequenceRecv + 1 - provableStore.set(nextSequenceRecvPath(packet.destPort, packet.destChannel), nextSequenceRecv) - break; - - case UNORDERED: - // for unordered channels we must set the receipt so it can be verified on the other side - // this receipt does not contain any data, since the packet has not yet been processed - // it's the sentinel success receipt: []byte{0x01} - packetReceipt = provableStore.get(packetReceiptPath(packet.destPort, packet.destChannel, packet.sequence)) - if (packetReceipt != null) { - emitLogEntry("recvPacket", { - data: packet.data - timeoutHeight: packet.timeoutHeight, - timeoutTimestamp: packet.timeoutTimestamp, - sequence: packet.sequence, - sourcePort: packet.sourcePort, - sourceChannel: packet.sourceChannel, - destPort: packet.destPort, - destChannel: packet.destChannel, - order: channel.order, - connection: channel.connectionHops[0] - }) - } - - abortTransactionUnless(packetReceipt === null) - provableStore.set( - packetReceiptPath(packet.destPort, packet.destChannel, packet.sequence), - SUCCESSFUL_RECEIPT - ) - break; - } + channelStore.set( + packetReceiptPath(packet.destPort, packet.destChannel, packet.sequence), + SUCCESSFUL_RECEIPT + ) // log that a packet has been received emitLogEntry("recvPacket", { @@ -528,13 +438,20 @@ function recvPacket( connection: channel.connectionHops[0] }) - // return transparent packet - return packet + cbs = router.callbacks[packet.destPort] + // IMPORTANT: if the ack is error, then the callback reverts its internal state changes, but the entire tx continues + ack = cbs.OnRecvPacket(packet, relayer) + + if ack != nil { + channelStore.set(packetAcknowledgementPath(packet.destPort, packet.destChannel, packet.sequence), ack) + } } ``` #### Writing acknowledgements +TODO: Define multidata ack, adpat description + The `writeAcknowledgement` function is called by a module in order to write data which resulted from processing an IBC packet that the sending chain can then verify, a sort of "execution receipt" or "RPC call response". Calling modules MUST execute application logic atomically in conjunction with calling `writeAcknowledgement`. @@ -551,35 +468,6 @@ The IBC handler performs the following steps in order: - Checks that an acknowledgement for this packet has not yet been written - Sets the opaque acknowledgement value at a store path unique to the packet -```typescript -function writeAcknowledgement( - packet: Packet, - acknowledgement: bytes) { - // acknowledgement must not be empty - abortTransactionUnless(len(acknowledgement) !== 0) - - // cannot already have written the acknowledgement - abortTransactionUnless(provableStore.get(packetAcknowledgementPath(packet.destPort, packet.destChannel, packet.sequence) === null)) - - // write the acknowledgement - provableStore.set( - packetAcknowledgementPath(packet.destPort, packet.destChannel, packet.sequence), - hash(acknowledgement) - ) - - // log that a packet has been acknowledged - emitLogEntry("writeAcknowledgement", { - sequence: packet.sequence, - timeoutHeight: packet.timeoutHeight, - port: packet.destPort, - channel: packet.destChannel, - timeoutTimestamp: packet.timeoutTimestamp, - data: packet.data, - acknowledgement - }) -} -``` - #### Processing acknowledgements The `acknowledgePacket` function is called by a module to process the acknowledgement of a packet previously sent by @@ -590,83 +478,44 @@ Calling modules MAY atomically execute appropriate application acknowledgement-h We pass the `relayer` address just as in [Receiving packets](#receiving-packets) to allow for possible incentivization here as well. + ```typescript function acknowledgePacket( - packet: OpaquePacket, - acknowledgement: bytes, - proof: CommitmentProof | MultihopProof, - proofHeight: Height, - relayer: string): Packet { - - // abort transaction unless that channel is open, calling module owns the associated port, and the packet fields match - channel = provableStore.get(channelPath(packet.sourcePort, packet.sourceChannel)) - abortTransactionUnless(channel !== null) - abortTransactionUnless(channel.state === OPEN || channel.state === FLUSHING) - abortTransactionUnless(authenticateCapability(channelCapabilityPath(packet.sourcePort, packet.sourceChannel), capability)) - abortTransactionUnless(packet.destPort === channel.counterpartyPortIdentifier) - abortTransactionUnless(packet.destChannel === channel.counterpartyChannelIdentifier) + packet: OpaquePacket, + acknowledgement: bytes, + proof: CommitmentProof, + proofHeight: Height, + relayer: string +) { + // in this specification, the source channel is the clientId + client = router.clients[packet.sourceChannel] + assert(client !== null) - connection = provableStore.get(connectionPath(channel.connectionHops[0])) - abortTransactionUnless(connection !== null) - abortTransactionUnless(connection.state === OPEN) + // assert dest channel is sourceChannel's counterparty channel identifier + counterparty = getCounterparty(packet.destChannel) + assert(packet.sourceChannel == counterparty.channelId) + + // assert dest port is sourcePort's counterparty port identifier + assert(packet.destPort == ports[packet.sourcePort]) // verify we sent the packet and haven't cleared it out yet - abortTransactionUnless(provableStore.get(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) - === hash(packet.data, packet.timeoutHeight, packet.timeoutTimestamp)) - - // abort transaction unless correct acknowledgement on counterparty chain - if (len(channel.connectionHops) > 1) { - key = packetAcknowledgementPath(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) - abortTransactionUnless(connection.verifyMultihopMembership( - connection, - proofHeight, - proof, - channel.ConnectionHops, - key, - acknowledgement - )) - } else { - abortTransactionUnless(connection.verifyPacketAcknowledgement( + assert(provableStore.get(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) + === hash(hash(packet.data), packet.timeoutHeight, packet.timeoutTimestamp)) + + ackPath = packetAcknowledgementPath(packet.destPort, packet.destChannel) + merklePath = applyPrefix(counterparty.keyPrefix, ackPath) + assert(client.verifyMembership( proofHeight, + 0, 0, proof, - packet.destPort, - packet.destChannel, - packet.sequence, - acknowledgement - )) - } + merklePath, + hash(acknowledgement) + )) - // abort transaction unless acknowledgement is processed in order - if (channel.order === ORDERED || channel.order == ORDERED_ALLOW_TIMEOUT) { - nextSequenceAck = provableStore.get(nextSequenceAckPath(packet.sourcePort, packet.sourceChannel)) - abortTransactionUnless(packet.sequence === nextSequenceAck) - nextSequenceAck = nextSequenceAck + 1 - provableStore.set(nextSequenceAckPath(packet.sourcePort, packet.sourceChannel), nextSequenceAck) - } + cbs = router.callbacks[packet.sourcePort] + cbs.OnAcknowledgePacket(packet, acknowledgement, relayer) - // all assertions passed, we can alter state - - // delete our commitment so we can't "acknowledge" again - provableStore.delete(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) - - if channel.state == FLUSHING { - upgradeTimeout = privateStore.get(counterpartyUpgradeTimeout(portIdentifier, channelIdentifier)) - if upgradeTimeout != nil { - // counterparty-specified timeout must not have exceeded - // if it has, then restore the channel and abort upgrade handshake - if (upgradeTimeout.timeoutHeight != 0 && currentHeight() >= upgradeTimeout.timeoutHeight) || - (upgradeTimeout.timeoutTimestamp != 0 && currentTimestamp() >= upgradeTimeout.timeoutTimestamp ) { - restoreChannel(portIdentifier, channelIdentifier) - } else if pendingInflightPackets(portIdentifier, channelIdentifier) == nil { - // if this was the last in-flight packet, then move channel state to FLUSHCOMPLETE - channel.state = FLUSHCOMPLETE - publicStore.set(channelPath(portIdentifier, channelIdentifier), channel) - } - } - } - - // return transparent packet - return packet + channelStore.delete(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) } ``` @@ -720,309 +569,49 @@ We pass the `relayer` address just as in [Receiving packets](#receiving-packets) ```typescript function timeoutPacket( - packet: OpaquePacket, - proof: CommitmentProof | MultihopProof, - proofHeight: Height, - nextSequenceRecv: Maybe, - relayer: string): Packet { - - channel = provableStore.get(channelPath(packet.sourcePort, packet.sourceChannel)) - abortTransactionUnless(channel !== null) - - abortTransactionUnless(authenticateCapability(channelCapabilityPath(packet.sourcePort, packet.sourceChannel), capability)) - abortTransactionUnless(packet.destChannel === channel.counterpartyChannelIdentifier) + packet: OpaquePacket, + proof: CommitmentProof, + proofHeight: Height, + relayer: string +) { + // in this specification, the source channel is the clientId + client = router.clients[packet.sourceChannel] + assert(client !== null) - connection = provableStore.get(connectionPath(channel.connectionHops[0])) - abortTransactionUnless(connection !== null) + // assert dest channel is sourceChannel's counterparty channel identifier + counterparty = getCounterparty(packet.destChannel) + assert(packet.sourceChannel == counterparty.channelId) + + // assert dest port is sourcePort's counterparty port identifier + assert(packet.destPort == ports[packet.sourcePort]) - // note: the connection may have been closed - abortTransactionUnless(packet.destPort === channel.counterpartyPortIdentifier) + // verify we sent the packet and haven't cleared it out yet + assert(provableStore.get(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) + === hash(hash(packet.data), packet.timeoutHeight, packet.timeoutTimestamp)) // get the timestamp from the final consensus state in the channel path var proofTimestamp - if (channel.connectionHops.length > 1) { - consensusState = abortTransactionUnless(Unmarshal(proof.ConsensusProofs[proof.ConsensusProofs.length-1].Value)) - proofTimestamp = consensusState.GetTimestamp() - } else { - proofTimestamp, err = connection.getTimestampAtHeight(connection, proofHeight) - } + proofTimestamp = client.getTimestampAtHeight(proofHeight) + assert(err != nil) // check that timeout height or timeout timestamp has passed on the other end - abortTransactionUnless( + asert( (packet.timeoutHeight > 0 && proofHeight >= packet.timeoutHeight) || (packet.timeoutTimestamp > 0 && proofTimestamp >= packet.timeoutTimestamp)) - // verify we actually sent this packet, check the store - abortTransactionUnless(provableStore.get(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) - === hash(packet.data, packet.timeoutHeight, packet.timeoutTimestamp)) - - switch channel.order { - case ORDERED: - // ordered channel: check that packet has not been received - // only allow timeout on next sequence so all sequences before the timed out packet are processed (received/timed out) - // before this packet times out - abortTransactionUnless(packet.sequence == nextSequenceRecv) - // ordered channel: check that the recv sequence is as claimed - if (channel.connectionHops.length > 1) { - key = nextSequenceRecvPath(packet.srcPort, packet.srcChannel) - abortTransactionUnless(connection.verifyMultihopMembership( - connection, - proofHeight, - proof, - channel.ConnectionHops, - key, - nextSequenceRecv - )) - } else { - abortTransactionUnless(connection.verifyNextSequenceRecv( - proofHeight, - proof, - packet.destPort, - packet.destChannel, - nextSequenceRecv - )) - } - break; - - case UNORDERED: - if (channel.connectionHops.length > 1) { - key = packetReceiptPath(packet.srcPort, packet.srcChannel, packet.sequence) - abortTransactionUnless(connection.verifyMultihopNonMembership( - connection, - proofHeight, - proof, - channel.ConnectionHops, - key - )) - } else { - // unordered channel: verify absence of receipt at packet index - abortTransactionUnless(connection.verifyPacketReceiptAbsence( - proofHeight, - proof, - packet.destPort, - packet.destChannel, - packet.sequence - )) - } - break; - - // NOTE: For ORDERED_ALLOW_TIMEOUT, the relayer must first attempt the receive on the destination chain - // before the timeout receipt can be written and subsequently proven on the sender chain in timeoutPacket - case ORDERED_ALLOW_TIMEOUT: - abortTransactionUnless(packet.sequence == nextSequenceRecv - 1) - - if (channel.connectionHops.length > 1) { - abortTransactionUnless(connection.verifyMultihopMembership( - connection, - proofHeight, - proof, - channel.ConnectionHops, - packetReceiptPath(packet.destPort, packet.destChannel, packet.sequence), - TIMEOUT_RECEIPT - )) - } else { - abortTransactionUnless(connection.verifyPacketReceipt( - proofHeight, - proof, - packet.destPort, - packet.destChannel, - packet.sequence - TIMEOUT_RECEIPT, - )) - } - break; - - default: - // unsupported channel type - abortTransactionUnless(true) - } - - // all assertions passed, we can alter state - - // delete our commitment - provableStore.delete(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) - - if channel.state == FLUSHING { - upgradeTimeout = privateStore.get(counterpartyUpgradeTimeout(portIdentifier, channelIdentifier)) - if upgradeTimeout != nil { - // counterparty-specified timeout must not have exceeded - // if it has, then restore the channel and abort upgrade handshake - if (upgradeTimeout.timeoutHeight != 0 && currentHeight() >= upgradeTimeout.timeoutHeight) || - (upgradeTimeout.timeoutTimestamp != 0 && currentTimestamp() >= upgradeTimeout.timeoutTimestamp ) { - restoreChannel(portIdentifier, channelIdentifier) - } else if pendingInflightPackets(portIdentifier, channelIdentifier) == nil { - // if this was the last in-flight packet, then move channel state to FLUSHCOMPLETE - channel.state = FLUSHCOMPLETE - publicStore.set(channelPath(portIdentifier, channelIdentifier), channel) - } - } - } - - // only close on strictly ORDERED channels - if channel.order === ORDERED { - // if the channel is ORDERED and a packet is timed out in FLUSHING state then - // all upgrade information is deleted and the channel is set to CLOSED. - - if channel.State == FLUSHING { - // delete auxiliary upgrade state - provableStore.delete(channelUpgradePath(portIdentifier, channelIdentifier)) - privateStore.delete(counterpartyUpgradePath(portIdentifier, channelIdentifier)) - } - - // ordered channel: close the channel - channel.state = CLOSED - provableStore.set(channelPath(packet.sourcePort, packet.sourceChannel), channel) - } - // on ORDERED_ALLOW_TIMEOUT, increment NextSequenceAck so that next packet can be acknowledged after this packet timed out. - if channel.order === ORDERED_ALLOW_TIMEOUT { - nextSequenceAck = nextSequenceAck + 1 - provableStore.set(nextSequenceAckPath(packet.srcPort, packet.srcChannel), nextSequenceAck) - } - - // return transparent packet - return packet -} -``` - -##### Timing-out on close - -The `timeoutOnClose` function is called by a module in order to prove that the channel -to which an unreceived packet was addressed has been closed, so the packet will never be received -(even if the `timeoutHeight` or `timeoutTimestamp` has not yet been reached). - -Calling modules MAY atomically execute appropriate application timeout-handling logic in conjunction with calling `timeoutOnClose`. - -We pass the `relayer` address just as in [Receiving packets](#receiving-packets) to allow for possible incentivization here as well. - -```typescript -function timeoutOnClose( - packet: Packet, - proof: CommitmentProof | MultihopProof, - proofClosed: CommitmentProof | MultihopProof, - proofHeight: Height, - nextSequenceRecv: Maybe, - relayer: string): Packet { - - channel = provableStore.get(channelPath(packet.sourcePort, packet.sourceChannel)) - // note: the channel may have been closed - abortTransactionUnless(authenticateCapability(channelCapabilityPath(packet.sourcePort, packet.sourceChannel), capability)) - abortTransactionUnless(packet.destChannel === channel.counterpartyChannelIdentifier) - - connection = provableStore.get(connectionPath(channel.connectionHops[0])) - // note: the connection may have been closed - abortTransactionUnless(packet.destPort === channel.counterpartyPortIdentifier) - - // verify we actually sent this packet, check the store - abortTransactionUnless(provableStore.get(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) - === hash(packet.data, packet.timeoutHeight, packet.timeoutTimestamp)) - - // return hops from counterparty's view - counterpartyHops = getCounterpartyHops(proof, connection) - - // check that the opposing channel end has closed - expected = ChannelEnd{CLOSED, channel.order, channel.portIdentifier, - channel.channelIdentifier, counterpartyHops, channel.version} - - // verify channel is closed - if (channel.connectionHops.length > 1) { - key = channelPath(counterparty.PortId, counterparty.ChannelId) - abortTransactionUnless(connection.VerifyMultihopMembership( - connection, - proofHeight, - proofClosed, - channel.ConnectionHops, - key, - expected - )) - } else { - abortTransactionUnless(connection.verifyChannelState( - proofHeight, - proofClosed, - channel.counterpartyPortIdentifier, - channel.counterpartyChannelIdentifier, - expected - )) - } - - switch channel.order { - case ORDERED: - // ordered channel: check that packet has not been received - abortTransactionUnless(packet.sequence >= nextSequenceRecv) - - // ordered channel: check that the recv sequence is as claimed - if (channel.connectionHops.length > 1) { - key = nextSequenceRecvPath(packet.destPort, packet.destChannel) - abortTransactionUnless(connection.verifyMultihopMembership( - connection, - proofHeight, - proof, - channel.ConnectionHops, - key, - nextSequenceRecv - )) - } else { - abortTransactionUnless(connection.verifyNextSequenceRecv( - proofHeight, - proof, - packet.destPort, - packet.destChannel, - nextSequenceRecv - )) - } - break; - - case UNORDERED: - // unordered channel: verify absence of receipt at packet index - if (channel.connectionHops.length > 1) { - abortTransactionUnless(connection.verifyMultihopNonMembership( - connection, - proofHeight, - proof, - channel.ConnectionHops, - key - )) - } else { - abortTransactionUnless(connection.verifyPacketReceiptAbsence( - proofHeight, - proof, - packet.destPort, - packet.destChannel, - packet.sequence - )) - } - break; - - case ORDERED_ALLOW_TIMEOUT: - // if packet.sequence >= nextSequenceRecv, then the relayer has not attempted - // to receive the packet on the destination chain (e.g. because the channel is already closed). - // In this situation it is not needed to verify the presence of a timeout receipt. - // Otherwise, if packet.sequence < nextSequenceRecv, then the relayer has attempted - // to receive the packet on the destination chain, and nextSequenceRecv has been incremented. - // In this situation, verify the presence of timeout receipt. - if packet.sequence < nextSequenceRecv { - abortTransactionUnless(connection.verifyPacketReceipt( - proofHeight, - proof, - packet.destPort, - packet.destChannel, - packet.sequence - TIMEOUT_RECEIPT, - )) - } - break; - - default: - // unsupported channel type - abortTransactionUnless(true) - } - - // all assertions passed, we can alter state + receiptPath = packetReceiptPath(packet.destPort, packet.destChannel, packet.sequence) + merklePath = applyPrefix(counterparty.keyPrefix, receiptPath) + assert(client.verifyNonMembership( + proofHeight + 0, 0, + proof, + merklePath + )) - // delete our commitment - provableStore.delete(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) + cbs = router.callbacks[packet.sourcePort] + cbs.OnTimeoutPacket(packet, relayer) - // return transparent packet - return packet + channelStore.delete(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) } ``` @@ -1032,10 +621,6 @@ Packets must be acknowledged in order to be cleaned-up. #### Reasoning about race conditions -##### Simultaneous handshake attempts - -If two machines simultaneously initiate channel opening handshakes with each other, attempting to use the same identifiers, both will fail and new identifiers must be used. - ##### Identifier allocation There is an unavoidable race condition on identifier allocation on the destination chain. Modules would be well-advised to utilise pseudo-random, non-valuable identifiers. Managing to claim the identifier that another module wishes to use, however, while annoying, cannot man-in-the-middle a handshake since the receiving module must already own the port to which the handshake was targeted. @@ -1048,9 +633,9 @@ There is no race condition between a packet timeout and packet confirmation, as Verification of cross-chain state prevents man-in-the-middle attacks for both connection handshakes & channel handshakes since all information (source, destination client, channel, etc.) is known by the module which starts the handshake and confirmed prior to handshake completion. -##### Connection / channel closure with in-flight packets +##### Clients unreachability with in-flight packets -If a connection or channel is closed while packets are in-flight, the packets can no longer be received on the destination chain and can be timed-out on the source chain. +If a client has been frozen while packets are in-flight, the packets can no longer be received on the destination chain and can be timed-out on the source chain. #### Querying channels From c53f581be2ca1877633d7d3dbf4db6d17c8b6671 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Wed, 11 Sep 2024 17:37:57 +0200 Subject: [PATCH 06/71] send packet handl --- .../v2/ics-004-packet-semantics/README.md | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 18aec0c4a..45644b1c9 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -298,33 +298,30 @@ Represented spatially, packet transit between two machines can be rendered as fo ##### Sending packets -TODO Adapt description - -The `sendPacket` function is called by a module in order to send *data* (in the form of an IBC packet) on a channel end owned by the calling module. +The `sendPacket` function is called by a module in order to send *data* in the form of an IBC packet. Calling modules MUST execute application logic atomically in conjunction with calling `sendPacket`. The IBC handler performs the following steps in order: -- Checks that the channel is not closed to send packets -- Checks that the calling module owns the sending port (see [ICS 5](../ics-005-port-allocation)) +- Checks that the underlying clients is properly registered in the IBC router. - Checks that the timeout height specified has not already passed on the destination chain -- Increments the send sequence counter associated with the channel - Stores a constant-size commitment to the packet data & packet timeout +- Increments the send sequence counter associated with the channel - Returns the sequence number of the sent packet Note that the full packet is not stored in the state of the chain - merely a short hash-commitment to the data & timeout value. The packet data can be calculated from the transaction execution and possibly returned as log output which relayers can index. ```typescript function sendPacket( - sourcePort: Identifier, - sourceChannel: Identifier, + sourceID: Identifier, + destID: Identifier, timeoutHeight: Height, timeoutTimestamp: uint64, packetData: []byte ): uint64 { // in this specification, the source channel is the clientId - client = router.clients[packet.sourceChannel] + client = router.clients[packet.sourceID] assert(client !== null) // disallow packets with a zero timeoutHeight and timeoutTimestamp @@ -335,17 +332,17 @@ function sendPacket( assert(timeoutHeight === 0 || latestClientHeight < timeoutHeight) // if the sequence doesn't already exist, this call initializes the sequence to 0 - sequence = channelStore.get(nextSequenceSendPath(commitPort, sourceChannel)) + sequence = channelStore.get(nextSequenceSendPath(commitPort, sourceID)) // store commitment to the packet data & packet timeout channelStore.set( - packetCommitmentPath(commitPort, sourceChannel, sequence), + packetCommitmentPath(commitPort, sourceID, sequence), hash(hash(data), timeoutHeight, timeoutTimestamp) ) // increment the sequence. Thus there are monotonically increasing sequences for packet flow // from sourcePort, sourceChannel pair - channelStore.set(nextSequenceSendPath(commitPort, sourceChannel), sequence+1) + channelStore.set(nextSequenceSendPath(commitPort, sourceID), sequence+1) // log that a packet can be safely sent emitLogEntry("sendPacket", { From b484b4e4eff18e5c02e3fcdcf4a189e34e080ef4 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Thu, 12 Sep 2024 10:54:50 +0200 Subject: [PATCH 07/71] change packet commitment paths --- .../v2/ics-004-packet-semantics/README.md | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 45644b1c9..858c160d5 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -180,9 +180,13 @@ function nextSequenceAckPath(portIdentifier: Identifier, channelIdentifier: Iden Constant-size commitments to packet data fields are stored under the packet sequence number: +// Note what about this: https://github.com/cosmos/ibc/issues/1129 + +// Note - Am I breaking something here? If that's ok, we could change nextSequence* paths too. + ```typescript -function packetCommitmentPath(portIdentifier: Identifier, channelIdentifier: Identifier, sequence: uint64): Path { - return "commitments/ports/{portIdentifier}/channels/{channelIdentifier}/sequences/{sequence}" +function packetCommitmentPath(sourceID: Identifier, sequence: uint64): Path { + return "commitments/clients/{sourceID}/sequences/{sequence}" } ``` @@ -192,16 +196,16 @@ Packet receipt data are stored under the `packetReceiptPath`. In the case of a s Some channel types MAY write a sentinel timeout value `TIMEOUT_RECEIPT` if the packet is received after the specified timeout. ```typescript -function packetReceiptPath(portIdentifier: Identifier, channelIdentifier: Identifier, sequence: uint64): Path { - return "receipts/ports/{portIdentifier}/channels/{channelIdentifier}/sequences/{sequence}" +function packetReceiptPath(destID: Identifier, Identifier, sequence: uint64): Path { + return "receipts/clients/{destID}/sequences/{sequence}" } ``` Packet acknowledgement data are stored under the `packetAcknowledgementPath`: ```typescript -function packetAcknowledgementPath(portIdentifier: Identifier, channelIdentifier: Identifier, sequence: uint64): Path { - return "acks/ports/{portIdentifier}/channels/{channelIdentifier}/sequences/{sequence}" +function packetAcknowledgementPath(sourceID: Identifier, sequence: uint64): Path { + return "acks/clients/{sourceID}/sequences/{sequence}" } ``` @@ -330,7 +334,7 @@ function sendPacket( // check that the timeout height hasn't already passed in the local client tracking the receiving chain latestClientHeight = client.latestClientHeight() assert(timeoutHeight === 0 || latestClientHeight < timeoutHeight) - + // NOTE - What is the commit port? Should be the sourcePort? If yes, in the packet we should put destPort and destID? // if the sequence doesn't already exist, this call initializes the sequence to 0 sequence = channelStore.get(nextSequenceSendPath(commitPort, sourceID)) @@ -358,7 +362,7 @@ function sendPacket( #### Receiving packets TODO Adapt description -The `recvPacket` function is called by a module in order to receive an IBC packet sent on the corresponding channel end on the counterparty chain. +The `recvPacket` function is called by a module in order to receive an IBC packet sent on the corresponding client on the counterparty chain. Atomically in conjunction with calling `recvPacket`, calling modules MUST either execute application logic or queue the packet for future execution. From 29834262b854945703cdd8599fc921c441f39c40 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Thu, 12 Sep 2024 11:12:21 +0200 Subject: [PATCH 08/71] fix paths --- .../v2/ics-004-packet-semantics/README.md | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 858c160d5..1168f5830 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -146,7 +146,7 @@ The architecture of clients, connections, channels and packets: Channel structures are stored under a store path prefix unique to a combination of a port identifier and channel identifier: -// NOTE Channel paths and capabilities should be maintained? +/* NOTE Channel paths and capabilities should be maintained for backward compatibility? ```typescript function channelPath(portIdentifier: Identifier, channelIdentifier: Identifier): Path { @@ -162,31 +162,33 @@ function channelCapabilityPath(portIdentifier: Identifier, channelIdentifier: Id } ``` +*/ + +// Note what about this: https://github.com/cosmos/ibc/issues/1129 + +// Note - Am I breaking something here with the approach taken? Is that ok? + The `nextSequenceSend`, `nextSequenceRecv`, and `nextSequenceAck` unsigned integer counters are stored separately so they can be proved individually: ```typescript -function nextSequenceSendPath(portIdentifier: Identifier, channelIdentifier: Identifier): Path { - return "nextSequenceSend/ports/{portIdentifier}/channels/{channelIdentifier}" +function nextSequenceSendPath(sourceID: Identifier, destID: Identifier): Path { + return "nextSequenceSend/clients/{sourceID}/clients/{destID}" } -function nextSequenceRecvPath(portIdentifier: Identifier, channelIdentifier: Identifier): Path { - return "nextSequenceRecv/ports/{portIdentifier}/channels/{channelIdentifier}" +function nextSequenceRecvPath(sourceID: Identifier,destID: Identifier): Path { + return "nextSequenceRecv/clients/{sourceID}/clients/{destID}" } - -function nextSequenceAckPath(portIdentifier: Identifier, channelIdentifier: Identifier): Path { - return "nextSequenceAck/ports/{portIdentifier}/channels/{channelIdentifier}" + +function nextSequenceAckPath(sourceID: Identifier, destID: Identifier): Path { + return "nextSequenceAck/clients/{sourceID}/clients/{destID}" } ``` Constant-size commitments to packet data fields are stored under the packet sequence number: -// Note what about this: https://github.com/cosmos/ibc/issues/1129 - -// Note - Am I breaking something here? If that's ok, we could change nextSequence* paths too. - ```typescript -function packetCommitmentPath(sourceID: Identifier, sequence: uint64): Path { - return "commitments/clients/{sourceID}/sequences/{sequence}" +function packetCommitmentPath(sourceID: Identifier, destID: Identifier, sequence: uint64): Path { + return "commitments/clients/{sourceID}/clients/{destID}/sequences/{sequence}" } ``` @@ -196,16 +198,16 @@ Packet receipt data are stored under the `packetReceiptPath`. In the case of a s Some channel types MAY write a sentinel timeout value `TIMEOUT_RECEIPT` if the packet is received after the specified timeout. ```typescript -function packetReceiptPath(destID: Identifier, Identifier, sequence: uint64): Path { - return "receipts/clients/{destID}/sequences/{sequence}" +function packetReceiptPath(sourceID: Identifier, destID: Identifier, sequence: uint64): Path { + return "receipts/clients/{sourceID}/clients/{destID}/sequences/{sequence}" } ``` Packet acknowledgement data are stored under the `packetAcknowledgementPath`: ```typescript -function packetAcknowledgementPath(sourceID: Identifier, sequence: uint64): Path { - return "acks/clients/{sourceID}/sequences/{sequence}" +function packetAcknowledgementPath(sourceID: Identifier, destID: Identifier, sequence: uint64): Path { + return "acks/clients/{sourceID}/clients/{destID}/sequences/{sequence}" } ``` @@ -336,11 +338,12 @@ function sendPacket( assert(timeoutHeight === 0 || latestClientHeight < timeoutHeight) // NOTE - What is the commit port? Should be the sourcePort? If yes, in the packet we should put destPort and destID? // if the sequence doesn't already exist, this call initializes the sequence to 0 + //sequence = channelStore.get(nextSequenceSendPath(commitPort, sourceID)) sequence = channelStore.get(nextSequenceSendPath(commitPort, sourceID)) // store commitment to the packet data & packet timeout channelStore.set( - packetCommitmentPath(commitPort, sourceID, sequence), + packetCommitmentPath(sourceID, sequence), hash(hash(data), timeoutHeight, timeoutTimestamp) ) From 9ef0322e1360134c4dd7e54442400a551abea6dd Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Thu, 12 Sep 2024 11:15:43 +0200 Subject: [PATCH 09/71] mod sendPacket --- spec/core/v2/ics-004-packet-semantics/README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 1168f5830..c4c7c4feb 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -339,20 +339,24 @@ function sendPacket( // NOTE - What is the commit port? Should be the sourcePort? If yes, in the packet we should put destPort and destID? // if the sequence doesn't already exist, this call initializes the sequence to 0 //sequence = channelStore.get(nextSequenceSendPath(commitPort, sourceID)) - sequence = channelStore.get(nextSequenceSendPath(commitPort, sourceID)) + sequence = channelStore.get(nextSequenceSendPath(sourceID, destID)) // store commitment to the packet data & packet timeout channelStore.set( - packetCommitmentPath(sourceID, sequence), + packetCommitmentPath(sourceID, destID, sequence), hash(hash(data), timeoutHeight, timeoutTimestamp) ) // increment the sequence. Thus there are monotonically increasing sequences for packet flow // from sourcePort, sourceChannel pair - channelStore.set(nextSequenceSendPath(commitPort, sourceID), sequence+1) + channelStore.set(nextSequenceSendPath(sourceID, destID), sequence+1) // log that a packet can be safely sent + // introducing sourceID and destID can be useful for monitoring - e.g. if one wants to monitor all packets between sourceID and destID emitting this in the event would simplify his life. + emitLogEntry("sendPacket", { + sourceID: Identifier, + destID: Identifier, sequence: sequence, data: data, timeoutHeight: timeoutHeight, @@ -364,7 +368,6 @@ function sendPacket( #### Receiving packets -TODO Adapt description The `recvPacket` function is called by a module in order to receive an IBC packet sent on the corresponding client on the counterparty chain. Atomically in conjunction with calling `recvPacket`, calling modules MUST either execute application logic or queue the packet for future execution. From 0408dfe6eec3b28a44ac3d7a6f03dce24c259315 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Thu, 12 Sep 2024 14:01:41 +0200 Subject: [PATCH 10/71] mod receive --- .../v2/ics-004-packet-semantics/README.md | 65 ++++++++++--------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index c4c7c4feb..1b1a80c4b 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -144,9 +144,16 @@ The architecture of clients, connections, channels and packets: #### Store paths -Channel structures are stored under a store path prefix unique to a combination of a port identifier and channel identifier: +// Unnecessary? + +```typescript +function counterpartyPath(sourceClientID: Identifier, destClientID: Identifier, keyPrefix: CommitmentPrefix): Path { + return "counterparty/client/{sourceClientID}/client/{destClientID}/CommitmentPrefix/{keyPrefix}" +} +``` /* NOTE Channel paths and capabilities should be maintained for backward compatibility? +Channel structures are stored under a store path prefix unique to a combination of a port identifier and channel identifier: ```typescript function channelPath(portIdentifier: Identifier, channelIdentifier: Identifier): Path { @@ -223,19 +230,19 @@ Thus, IBC version 2 introduces a new message `RegisterCounterparty` that will as ```typescript function RegisterCounterparty( - channelIdentifier: Identifier, // this will be our own client identifier representing our channel to desired chain - counterpartyChannelIdentifier: Identifier, // this is the counterparty's identifier of our chain + clientID: Identifier, // this will be our own client identifier representing our channel to desired chain + counterpartyClientID: Identifier, // this is the counterparty's identifier of our chain counterpartyKeyPrefix: CommitmentPrefix, authentication: data, // implementation-specific authentication data ) { assert(verify(authentication)) counterparty = Counterparty{ - channelId: counterpartyChannelIdentifier, + channelId: counterpartyClientID, keyPrefix: counterpartyKeyPrefix } - privateStore.set(counterpartyPath(channelIdentifier), counterparty) + privateStore.set(counterpartyPath(clientID), counterparty) } ``` @@ -245,8 +252,8 @@ A simpler but weaker authentication would simply be to check that the `RegisterC ```typescript // getCounterparty retrieves the stored counterparty identifier // given the channelIdentifier on our chain once it is provided -function getCounterparty(channelIdentifier: Identifier): Counterparty { - return privateStore.get(counterpartyPath(channelIdentifier)) +function getCounterparty(clientID: Identifier): Counterparty { + return privateStore.get(counterpartyPath(clientID)) } ``` @@ -320,14 +327,14 @@ Note that the full packet is not stored in the state of the chain - merely a sho ```typescript function sendPacket( - sourceID: Identifier, - destID: Identifier, + sourceClientID: Identifier, + destClientID: Identifier, timeoutHeight: Height, timeoutTimestamp: uint64, packetData: []byte ): uint64 { // in this specification, the source channel is the clientId - client = router.clients[packet.sourceID] + client = router.clients[packet.sourceClientID] assert(client !== null) // disallow packets with a zero timeoutHeight and timeoutTimestamp @@ -339,17 +346,18 @@ function sendPacket( // NOTE - What is the commit port? Should be the sourcePort? If yes, in the packet we should put destPort and destID? // if the sequence doesn't already exist, this call initializes the sequence to 0 //sequence = channelStore.get(nextSequenceSendPath(commitPort, sourceID)) - sequence = channelStore.get(nextSequenceSendPath(sourceID, destID)) + sequence = channelStore.get(nextSequenceSendPath(sourceClientID, destClientID)) // store commitment to the packet data & packet timeout + // Note do we need to keep the channelStore? Should this be instead the counterParty store or something similar? Do we keep it for backward compatibility? channelStore.set( - packetCommitmentPath(sourceID, destID, sequence), + packetCommitmentPath(sourceClientID, destClientID, sequence), hash(hash(data), timeoutHeight, timeoutTimestamp) ) // increment the sequence. Thus there are monotonically increasing sequences for packet flow // from sourcePort, sourceChannel pair - channelStore.set(nextSequenceSendPath(sourceID, destID), sequence+1) + channelStore.set(nextSequenceSendPath(sourceClientID, destClientID), sequence+1) // log that a packet can be safely sent // introducing sourceID and destID can be useful for monitoring - e.g. if one wants to monitor all packets between sourceID and destID emitting this in the event would simplify his life. @@ -374,13 +382,11 @@ Atomically in conjunction with calling `recvPacket`, calling modules MUST either The IBC handler performs the following steps in order: -- Checks that the channel & connection are open to receive packets -- Checks that the calling module owns the receiving port +- Checks that the clients is properly set in IBC router - Checks that the packet metadata matches the channel & connection information - Checks that the packet sequence is the next sequence the channel end expects to receive (for ordered and ordered_allow_timeout channels) - Checks that the timeout height and timestamp have not yet passed - Checks the inclusion proof of packet data commitment in the outgoing chain's state -- Optionally (in case channel upgrades and deletion of acknowledgements and packet receipts are implemented): reject any packet with a sequence already used before a successful channel upgrade - Sets a store path to indicate that the packet has been received (unordered channels only) - Increments the packet receive sequence associated with the channel end (ordered and ordered_allow_timeout channels only) @@ -393,17 +399,17 @@ function recvPacket( proofHeight: Height, relayer: string): Packet { // in this specification, the destination channel is the clientId - client = router.clients[packet.destChannel] + client = router.clients[packet.destClientID] assert(client !== null) // assert source channel is destChannel's counterparty channel identifier - counterparty = getCounterparty(packet.destChannel) - assert(packet.sourceChannel == counterparty.channelId) + counterparty = getCounterparty(packet.sourceClientID) + assert(packet.sourceClientID == counterparty.clientId) // assert source port is destPort's counterparty port identifier - assert(packet.sourcePort == ports[packet.destPort]) + assert(packet.sourcePort == ports[packet.destPort]) // Needed? - packetPath = packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence) + packetPath = packetCommitmentPath(packet.sourceClientID, packet.destClientID, packet.sequence) merklePath = applyPrefix(counterparty.keyPrefix, packetPath) // DISCUSSION NEEDED: Should we have an in-protocol notion of Prefixing the path // or should we make this a concern of the client's VerifyMembership @@ -423,11 +429,11 @@ function recvPacket( // we must set the receipt so it can be verified on the other side // this receipt does not contain any data, since the packet has not yet been processed // it's the sentinel success receipt: []byte{0x01} - packetReceipt = channelStore.get(packetReceiptPath(packet.destPort, packet.destChannel, packet.sequence)) + packetReceipt = channelStore.get(packetReceiptPath(packet.sourceChannelID, packet.destChannelID, packet.sequence)) assert(packetReceipt === null) channelStore.set( - packetReceiptPath(packet.destPort, packet.destChannel, packet.sequence), + packetReceiptPath(packet.sourceChannelID, packet.destChannelID, packet.sequence), SUCCESSFUL_RECEIPT ) @@ -437,20 +443,17 @@ function recvPacket( timeoutHeight: packet.timeoutHeight, timeoutTimestamp: packet.timeoutTimestamp, sequence: packet.sequence, - sourcePort: packet.sourcePort, - sourceChannel: packet.sourceChannel, - destPort: packet.destPort, - destChannel: packet.destChannel, - order: channel.order, - connection: channel.connectionHops[0] + sourceClientID: packet.sourceClientID, + destClientID: packet.destClientID, + // MMM app= packet.PacketData.destPort?? I mean shall we use the app in somehow here? }) - cbs = router.callbacks[packet.destPort] + cbs = router.callbacks[packet.destClientID] // IMPORTANT: if the ack is error, then the callback reverts its internal state changes, but the entire tx continues ack = cbs.OnRecvPacket(packet, relayer) if ack != nil { - channelStore.set(packetAcknowledgementPath(packet.destPort, packet.destChannel, packet.sequence), ack) + channelStore.set(packetAcknowledgementPath(packet.sourceClientID, packet.destClientID, packet.sequence), ack) } } ``` From 9ff280cf903b422cd277040e524a9bb47ca8d871 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Tue, 24 Sep 2024 12:15:31 +0200 Subject: [PATCH 11/71] allign with new packet structure --- .../v2/ics-004-packet-semantics/README.md | 53 +++++++++++-------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 1b1a80c4b..6c38b9c0e 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -56,39 +56,46 @@ A `Packet`, in the interblockchain communication protocol, is a particular inter ```typescript interface Packet { - sequence: uint64 - timeoutHeight: Height - timeoutTimestamp: uint64 - sourceID: Identifier // identifier of the source chain clientID - destID: Identifier // identifier of the sender chain clientID on the destination chain - packetData: [] PacketData + sourceIdentifier: bytes, + destIdentifier: bytes, + sequence: uint64 + timeout: uint64, + data: [Payload] } ``` +- The `sourceIdentifier` derived from the `clientIdentifier` will tell the IBC router which chain a received packet came from. +- The `destIdentifier` derived from the `clientIdentifier` will tell the IBC router which chain to send the packets to. - The `sequence` number 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 `timeoutHeight` indicates 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 `timeoutTimestamp` indicates a timestamp on the destination chain after which the packet will no longer be processed, and will instead count as having timed-out. -- The `sourceID` derived from the `clientIDs` will tell the IBC router which chain a received packet came from. -- The `destID` derived from the `clientIDs` will tell the IBC router which chain to send the packets to. +- The `timeout` indicates the UNIX timestamp in seconds and is encoded in LittleEndian. It must be passed on the destination chain and once elapsed, will no longer allow the packet processing, and will instead generate a time-out. -The `PacketData` is a particular interface defined as follows: +The `Payload` is a particular interface defined as follows: ```typescript -interface PacketData { - sourcePort: Identifier // identifier of the source chain application - destPort: Identifier // identifier of the destination chain application - version: string // Maybe not necessary - encoding: string - payload: bytes +interface Payload { + sourcePort: bytes, + destPort: bytes, + version: string, + encoding: Encoding, + appData: bytes, +} + +enum Encoding { + NO_ENCODING_SPECIFIED, + PROTO_3, + JSON, + RLP, + BCS, } ``` -- The `sourcePort` identify the source application -- The `destPort` derived from the `clientIDs` will tell the IBC router which chain to send the packets to. -- The `encoding` to allow the specification of custom data encoding -- The `payload` that can be defined by the application logic of the associated modules. +- The `sourcePort` identifies the source application. +- The `destPort` identifies the destination application. +- The `version` to specify the application version to be used. +- The `encoding` to allow the specification of custom data encoding among those agreed in the `Encoding` enum. +- The `appData` that can be defined by the application logic of the associated modules. -When the array of packetData, passed-in the packet, is populated with multiple values, the system will handle the packet as a multi-data packet. +When the array of payloads, passed-in the packet, is populated with multiple values, the system will handle the packet as a multi-data packet. 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. @@ -468,7 +475,7 @@ Calling modules MUST execute application logic atomically in conjunction with ca This is an asynchronous acknowledgement, the contents of which do not need to be determined when the packet is received, only when processing is complete. In the synchronous case, `writeAcknowledgement` can be called in the same transaction (atomically) with `recvPacket`. -Acknowledging packets is not required; however, if an ordered channel uses acknowledgements, either all or no packets must be acknowledged (since the acknowledgements are processed in order). Note that if packets are not acknowledged, packet commitments cannot be deleted on the source chain. Future versions of IBC may include ways for modules to specify whether or not they will be acknowledging packets in order to allow for cleanup. +Acknowledging packets is not required; however, if packets are not acknowledged, packet commitments cannot be deleted on the source chain. Future versions of IBC may include ways for modules to specify whether or not they will be acknowledging packets in order to allow for cleanup. `writeAcknowledgement` *does not* check if the packet being acknowledged was actually received, because this would result in proofs being verified twice for acknowledged packets. This aspect of correctness is the responsibility of the calling module. The calling module MUST only call `writeAcknowledgement` with a packet previously received from `recvPacket`. From e3c6620ad1dc5bea0b5bdca1dad01ed85596689c Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Tue, 24 Sep 2024 12:28:02 +0200 Subject: [PATCH 12/71] mod ack and paths --- spec/core/v2/ics-004-packet-semantics/README.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 6c38b9c0e..19c6e7d44 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -114,6 +114,16 @@ enum PacketReceipt { } ``` +The `Acknowledgement` is a particular interface defined as follows: + +```typescript +interface Acknowledgement { + appAcknowledgement: [bytes] +} +``` + +An application may not need to return an acknowledgment. In this case, it may return a sentinel acknowledgement value `SENTINEL_ACKNOWLEDGMENT` which will be the single byte in the byte array: `bytes(0x01)`. In this case, the IBC `acknowledgePacket` handler will still do the core IBC acknowledgment logic but it will not call the application's acknowledgePacket callback. + ### Desired Properties #### Efficiency @@ -202,7 +212,7 @@ Constant-size commitments to packet data fields are stored under the packet sequ ```typescript function packetCommitmentPath(sourceID: Identifier, destID: Identifier, sequence: uint64): Path { - return "commitments/clients/{sourceID}/clients/{destID}/sequences/{sequence}" + return "commitments/channels/{identifier}/sequences/{bigEndianUint64Sequence}" } ``` @@ -213,7 +223,7 @@ Some channel types MAY write a sentinel timeout value `TIMEOUT_RECEIPT` if the p ```typescript function packetReceiptPath(sourceID: Identifier, destID: Identifier, sequence: uint64): Path { - return "receipts/clients/{sourceID}/clients/{destID}/sequences/{sequence}" + return "receipts/channels/{identifier}/sequences/{bigEndianUint64Sequence}" } ``` @@ -221,7 +231,7 @@ Packet acknowledgement data are stored under the `packetAcknowledgementPath`: ```typescript function packetAcknowledgementPath(sourceID: Identifier, destID: Identifier, sequence: uint64): Path { - return "acks/clients/{sourceID}/clients/{destID}/sequences/{sequence}" + return "acks/channels/{identifier}/sequences/{bigEndianUint64Sequence}" } ``` From e77a2f4dbf5a84057e823dcb25646ac1ce4c209a Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Wed, 25 Sep 2024 13:05:06 +0200 Subject: [PATCH 13/71] change counterparty def --- .../v2/ics-004-packet-semantics/README.md | 32 ++++--------------- 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 19c6e7d44..b90cfef3f 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -37,21 +37,24 @@ The IBC version 2 will provide packet delivery between two chains communicating ```typescript interface Counterparty { - channelId: Identifier + clientId: bytes + channelId: bytes keyPrefix: CommitmentPrefix } ``` +NOTE: Do we need and want to keep nextSequence* info? To my understanding this should eventually go in the privateStore. That means we don't need to specify the paths later on, correct? + - 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. -// Note do we need to keep the version in the end? Should this be just the protocol version? -- The `version` string 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. // NOTE - Do we want to keep an upgrade spec to specify how to change the client and so on? Probably unnecessary, just showing here how to do that would be enough. See the [upgrade spec](../../ics-004-channel-and-packet-semantics/UPGRADES.md) for details on `upgradeSequence`. +The `Packet` interface is defined in [add ref once merged](ref) + A `Packet`, in the interblockchain communication protocol, is a particular interface defined as follows: ```typescript @@ -739,27 +742,4 @@ Chains MUST implement a function `generateIdentifier` which chooses an identifie type generateIdentifier = () -> Identifier ``` -##### Multihop utility functions - -MMM, - -```typescript -// Return the counterparty connectionHops -function getCounterPartyHops(proof: CommitmentProof | MultihopProof, lastConnection: ConnectionEnd) string[] { - - let counterpartyHops: string[] = [lastConnection.counterpartyConnectionIdentifier] - if typeof(proof) === 'MultihopProof' { - for connData in proofs.ConnectionProofs { - connectionEnd = abortTransactionUnless(Unmarshal(connData.Value)) - counterpartyHops.push(connectionEnd.GetCounterparty().GetConnectionID()) - } - - // reverse the hops so they are ordered from sender --> receiver - counterpartyHops = counterpartyHops.reverse() - } - - return counterpartyHops -} -``` - */ From d9582efb83a28551023c2d6e10f46ac5a791edaf Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Wed, 25 Sep 2024 17:38:42 +0200 Subject: [PATCH 14/71] discussions-related-fixes --- .../v2/ics-004-packet-semantics/README.md | 151 ++++++++---------- 1 file changed, 67 insertions(+), 84 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index b90cfef3f..04557144b 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -43,17 +43,15 @@ interface Counterparty { } ``` -NOTE: Do we need and want to keep nextSequence* info? To my understanding this should eventually go in the privateStore. That means we don't need to specify the paths later on, correct? +NOTE: Do we need and want to keep nextSequence* info? To my understanding ack and recv are relative to ordered channel and can be safely removed. Then nextSequenceSend should eventually go in the privateStore. That means we don't need to specify the paths later on, correct? - 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. // NOTE - Do we want to keep an upgrade spec to specify how to change the client and so on? Probably unnecessary, just showing here how to do that would be enough. See the [upgrade spec](../../ics-004-channel-and-packet-semantics/UPGRADES.md) for details on `upgradeSequence`. -The `Packet` interface is defined in [add ref once merged](ref) +The `Packet`, `Payload`, `Encoding` and the `Acknowledgement` interfaces are as defined in [add ref once merged](ref). For convenience, following we recall their structures. A `Packet`, in the interblockchain communication protocol, is a particular interface defined as follows: @@ -69,7 +67,7 @@ interface Packet { - The `sourceIdentifier` derived from the `clientIdentifier` will tell the IBC router which chain a received packet came from. - The `destIdentifier` derived from the `clientIdentifier` will tell the IBC router which chain to send the packets to. -- The `sequence` number 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 `sequence` number corresponds to the order of sends and receives, where a packet with an earlier sequence number MUST be sent and received (NOTE: not sure about received) before a packet with a later sequence number. - The `timeout` indicates the UNIX timestamp in seconds and is encoded in LittleEndian. It must be passed on the destination chain and once elapsed, will no longer allow the packet processing, and will instead generate a time-out. The `Payload` is a particular interface defined as follows: @@ -94,9 +92,9 @@ enum Encoding { - The `sourcePort` identifies the source application. - The `destPort` identifies the destination application. -- The `version` to specify the application version to be used. -- The `encoding` to allow the specification of custom data encoding among those agreed in the `Encoding` enum. -- The `appData` that can be defined by the application logic of the associated modules. +- The `version` specifies the application version to be used. +- The `encoding` allows the specification of custom data encoding among those agreed in the `Encoding` enum. +- The `appData` is defined by the application of the associated modules. When the array of payloads, passed-in the packet, is populated with multiple values, the system will handle the packet as a multi-data packet. @@ -125,15 +123,19 @@ interface Acknowledgement { } ``` +- The `appAcknowledgement` is an array of bytes. Each element of the array identifies the source application acknowledgement. + An application may not need to return an acknowledgment. In this case, it may return a sentinel acknowledgement value `SENTINEL_ACKNOWLEDGMENT` which will be the single byte in the byte array: `bytes(0x01)`. In this case, the IBC `acknowledgePacket` handler will still do the core IBC acknowledgment logic but it will not call the application's acknowledgePacket callback. +E.g. If a packet within 3 payloads intended for 3 different application is sent out, the expectation is that each of the payload is acted upon in the same order as it has been placed in the packet. Likewise, the array of appAcknowledgement is expected to be populated within the same order. + ### 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. -- The system must be able to process the multiple packetData contained in a single IBC packet, to reduce the amount of packet flows. +- The system MUST be able to process the multiple payloads contained in a single IBC packet, to reduce the amount of packet flows. #### Exactly-once delivery @@ -143,7 +145,7 @@ An application may not need to return an acknowledgment. In this case, it may re #### Ordering -- IBC version 2 supports only *unordered* communications, thus, packets may be sent and received in any order. Unordered packets, have individual timeouts specified in terms of the destination chain's height. +- IBC version 2 supports only *unordered* communications, thus, packets may be sent and received in any order. Unordered packets, have individual timeouts specified in seconds UNIX timestamp. #### Permissioning @@ -156,6 +158,7 @@ An application may not need to return an acknowledgment. In this case, it may re ### Dataflow visualisation +TODO The architecture of clients, connections, channels and packets: ![Dataflow Visualisation](../../ics-004-channel-and-packet-semantics/dataflow.png) @@ -164,19 +167,23 @@ The architecture of clients, connections, channels and packets: #### Store paths -// Unnecessary? +NOTE do we stil need this, or we can retrieve the sequence from the commitment path associated with the clientID? + +- The `nextSequenceSend`, stored separately, tracks the sequence number for the next packet to be sent. ```typescript -function counterpartyPath(sourceClientID: Identifier, destClientID: Identifier, keyPrefix: CommitmentPrefix): Path { - return "counterparty/client/{sourceClientID}/client/{destClientID}/CommitmentPrefix/{keyPrefix}" +function nextSequenceSendPath(sourceID: bytes, destID: bytes): Path { + return "nextSequenceSend/clients/{sourceID}/clients/{destID}" } ``` -/* NOTE Channel paths and capabilities should be maintained for backward compatibility? +/* Is all of this Unnecessary? + +NOTE Channel paths and capabilities should be maintained for backward compatibility? Channel structures are stored under a store path prefix unique to a combination of a port identifier and channel identifier: ```typescript -function channelPath(portIdentifier: Identifier, channelIdentifier: Identifier): Path { +function channelPath(portIdentifier: bytes, channelIdentifier: bytes): Path { return "channelEnds/ports/{portIdentifier}/channels/{channelIdentifier}" } ``` @@ -191,50 +198,29 @@ function channelCapabilityPath(portIdentifier: Identifier, channelIdentifier: Id */ -// Note what about this: https://github.com/cosmos/ibc/issues/1129 - -// Note - Am I breaking something here with the approach taken? Is that ok? - -The `nextSequenceSend`, `nextSequenceRecv`, and `nextSequenceAck` unsigned integer counters are stored separately so they can be proved individually: - -```typescript -function nextSequenceSendPath(sourceID: Identifier, destID: Identifier): Path { - return "nextSequenceSend/clients/{sourceID}/clients/{destID}" -} - -function nextSequenceRecvPath(sourceID: Identifier,destID: Identifier): Path { - return "nextSequenceRecv/clients/{sourceID}/clients/{destID}" -} - -function nextSequenceAckPath(sourceID: Identifier, destID: Identifier): Path { - return "nextSequenceAck/clients/{sourceID}/clients/{destID}" -} -``` - Constant-size commitments to packet data fields are stored under the packet sequence number: ```typescript -function packetCommitmentPath(sourceID: Identifier, destID: Identifier, sequence: uint64): Path { - return "commitments/channels/{identifier}/sequences/{bigEndianUint64Sequence}" +function packetCommitmentPath(sourceId: bytes, sequence: uint64): Path { + return "commitments/channels/{sourceId}/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. +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`. While in the case of a timeout, the destination chain MAY (May?Must?Should?Mmm) write a sentinel timeout value `TIMEOUT_RECEIPT` if the packet is received after the specified timeout. ```typescript -function packetReceiptPath(sourceID: Identifier, destID: Identifier, sequence: uint64): Path { - return "receipts/channels/{identifier}/sequences/{bigEndianUint64Sequence}" +function packetReceiptPath(sourceId: bytes, sequence: uint64): Path { + return "receipts/channels/{sourceId}/sequences/{bigEndianUint64Sequence}" } ``` Packet acknowledgement data are stored under the `packetAcknowledgementPath`: ```typescript -function packetAcknowledgementPath(sourceID: Identifier, destID: Identifier, sequence: uint64): Path { - return "acks/channels/{identifier}/sequences/{bigEndianUint64Sequence}" +function packetAcknowledgementPath(sourceId: bytes, sequence: uint64): Path { + return "acks/channels/{sourceId}/sequences/{sequence}" } ``` @@ -250,19 +236,20 @@ Thus, IBC version 2 introduces a new message `RegisterCounterparty` that will as ```typescript function RegisterCounterparty( - clientID: Identifier, // this will be our own client identifier representing our channel to desired chain - counterpartyClientID: Identifier, // this is the counterparty's identifier of our chain + clientID: bytes, // this will be our own client identifier representing our channel to desired chain + counterpartyClientId: bytes, // this is the counterparty's identifier of our chain counterpartyKeyPrefix: CommitmentPrefix, authentication: data, // implementation-specific authentication data ) { assert(verify(authentication)) counterparty = Counterparty{ - channelId: counterpartyClientID, + clientId: clientId, + channelId: counterpartyClientId, keyPrefix: counterpartyKeyPrefix } - privateStore.set(counterpartyPath(clientID), counterparty) + privateStore.set(Map) } ``` @@ -272,8 +259,8 @@ A simpler but weaker authentication would simply be to check that the `RegisterC ```typescript // getCounterparty retrieves the stored counterparty identifier // given the channelIdentifier on our chain once it is provided -function getCounterparty(clientID: Identifier): Counterparty { - return privateStore.get(counterpartyPath(clientID)) +function getCounterparty(clientId: bytes): Counterparty { + return privateStore.get(clientId) // retrieves from map the counterparty } ``` @@ -291,21 +278,23 @@ The IBC router contains a mapping from a reserved application port and the suppo ```typescript type IBCRouter struct { - versions: portID -> [Version] - callbacks: portID -> [Callback] + versions: portId -> [Version] + callbacks: portId -> [Callback] clients: clientId -> Client - ports: portID -> counterpartyPortID + ports: portId -> counterpartyPortId } ``` #### Packet Flow through the Router & handling -TODO : Adapat to new flow +TODO : Adapt to new flow ![Packet State Machine](../../ics-004-channel-and-packet-semantics/packet-state-machine.png) ##### A day in the life of a packet +TODO + The following sequence of steps must occur for a packet to be sent from module *1* on machine *A* to module *2* on machine *B*, starting from scratch. The module can interface with the IBC handler through [ICS 25]( ../../ics-025-handler-interface) or [ICS 26]( ../../ics-026-routing-module). @@ -338,7 +327,7 @@ Calling modules MUST execute application logic atomically in conjunction with ca The IBC handler performs the following steps in order: - Checks that the underlying clients is properly registered in the IBC router. -- Checks that the timeout height specified has not already passed on the destination chain +- Checks that the timeout specified has not already passed on the destination chain - Stores a constant-size commitment to the packet data & packet timeout - Increments the send sequence counter associated with the channel - Returns the sequence number of the sent packet @@ -347,22 +336,19 @@ Note that the full packet is not stored in the state of the chain - merely a sho ```typescript function sendPacket( - sourceClientID: Identifier, - destClientID: Identifier, - timeoutHeight: Height, + sourceClientId: bytes, + destClientId: bytes, timeoutTimestamp: uint64, packetData: []byte ): uint64 { // in this specification, the source channel is the clientId - client = router.clients[packet.sourceClientID] + client = router.clients[packet.sourceClientId] assert(client !== null) // disallow packets with a zero timeoutHeight and timeoutTimestamp - assert(timeoutHeight !== 0 || timeoutTimestamp !== 0) - - // check that the timeout height hasn't already passed in the local client tracking the receiving chain - latestClientHeight = client.latestClientHeight() - assert(timeoutHeight === 0 || latestClientHeight < timeoutHeight) + assert(timeoutTimestamp !== 0) // Maybe this can be enforced even for unreal timeouts value and not only for 0 + assert(currentTimestamp()> timeoutTimestamp) // Mmm + // NOTE - What is the commit port? Should be the sourcePort? If yes, in the packet we should put destPort and destID? // if the sequence doesn't already exist, this call initializes the sequence to 0 //sequence = channelStore.get(nextSequenceSendPath(commitPort, sourceID)) @@ -371,8 +357,8 @@ function sendPacket( // store commitment to the packet data & packet timeout // Note do we need to keep the channelStore? Should this be instead the counterParty store or something similar? Do we keep it for backward compatibility? channelStore.set( - packetCommitmentPath(sourceClientID, destClientID, sequence), - hash(hash(data), timeoutHeight, timeoutTimestamp) + packetCommitmentPath(sourceClientId sequence), + hash(hash(data), timeoutTimestamp) ) // increment the sequence. Thus there are monotonically increasing sequences for packet flow @@ -383,11 +369,10 @@ function sendPacket( // introducing sourceID and destID can be useful for monitoring - e.g. if one wants to monitor all packets between sourceID and destID emitting this in the event would simplify his life. emitLogEntry("sendPacket", { - sourceID: Identifier, - destID: Identifier, + sourceID: sourceClientId, + destID: destClientId, sequence: sequence, data: data, - timeoutHeight: timeoutHeight, timeoutTimestamp: timeoutTimestamp }) @@ -419,68 +404,65 @@ function recvPacket( proofHeight: Height, relayer: string): Packet { // in this specification, the destination channel is the clientId - client = router.clients[packet.destClientID] + client = router.clients[packet.destClientId] assert(client !== null) // assert source channel is destChannel's counterparty channel identifier - counterparty = getCounterparty(packet.sourceClientID) - assert(packet.sourceClientID == counterparty.clientId) + counterparty = getCounterparty(packet.sourceClientId) + assert(packet.sourceClientId == counterparty.clientId) // assert source port is destPort's counterparty port identifier assert(packet.sourcePort == ports[packet.destPort]) // Needed? - packetPath = packetCommitmentPath(packet.sourceClientID, packet.destClientID, packet.sequence) + packetPath = packetCommitmentPath(packet.sourceClientId, packet.sequence) merklePath = applyPrefix(counterparty.keyPrefix, packetPath) // DISCUSSION NEEDED: Should we have an in-protocol notion of Prefixing the path // or should we make this a concern of the client's VerifyMembership // proofPath = applyPrefix(client.counterpartyChannelStoreIdentifier, packetPath) assert(client.verifyMembership( proofHeight, - 0, 0, // zeroed out delay period proof, merklePath, - hash(packet.data, packet.timeoutHeight, packet.timeoutTimestamp) + hash(packet.data, packet.timeoutTimestamp) )) - assert(packet.timeoutHeight === 0 || getConsensusHeight() < packet.timeoutHeight) assert(packet.timeoutTimestamp === 0 || currentTimestamp() < packet.timeoutTimestamp) // we must set the receipt so it can be verified on the other side // this receipt does not contain any data, since the packet has not yet been processed // it's the sentinel success receipt: []byte{0x01} - packetReceipt = channelStore.get(packetReceiptPath(packet.sourceChannelID, packet.destChannelID, packet.sequence)) + packetReceipt = channelStore.get(packetReceiptPath(packet.sourceChannelId, packet.sequence)) assert(packetReceipt === null) channelStore.set( - packetReceiptPath(packet.sourceChannelID, packet.destChannelID, packet.sequence), + packetReceiptPath(packet.sourceChannelId, packet.sequence), SUCCESSFUL_RECEIPT ) // log that a packet has been received emitLogEntry("recvPacket", { data: packet.data - timeoutHeight: packet.timeoutHeight, timeoutTimestamp: packet.timeoutTimestamp, sequence: packet.sequence, - sourceClientID: packet.sourceClientID, - destClientID: packet.destClientID, + sourceClientId: packet.sourceClientId, + destClientId: packet.destClientId, // MMM app= packet.PacketData.destPort?? I mean shall we use the app in somehow here? }) - cbs = router.callbacks[packet.destClientID] + cbs = router.callbacks[packet.destClientId] // IMPORTANT: if the ack is error, then the callback reverts its internal state changes, but the entire tx continues ack = cbs.OnRecvPacket(packet, relayer) if ack != nil { - channelStore.set(packetAcknowledgementPath(packet.sourceClientID, packet.destClientID, packet.sequence), ack) + channelStore.set(packetAcknowledgementPath(packet.sourceClientID, packet.sequence), ack) } } ``` #### Writing acknowledgements -TODO: Define multidata ack, adpat description +TODO: All ack related stuff... Define multidata ack, adpat description.... The `writeAcknowledgement` function is called by a module in order to write data which resulted from processing an IBC packet that the sending chain can then verify, a sort of "execution receipt" or "RPC call response". @@ -508,7 +490,6 @@ Calling modules MAY atomically execute appropriate application acknowledgement-h We pass the `relayer` address just as in [Receiving packets](#receiving-packets) to allow for possible incentivization here as well. - ```typescript function acknowledgePacket( packet: OpaquePacket, @@ -575,6 +556,8 @@ or `0xb2` (error). #### Timeouts +TODO + Application semantics may require some timeout: an upper limit to how long the chain will wait for a transaction to be processed before considering it an error. Since the two chains have different local clocks, this is an obvious attack vector for a double spend - an attacker may delay the relay of the receipt or wait to send the packet until right after the timeout - so applications cannot safely implement naive timeout logic themselves. Note that in order to avoid any possible "double-spend" attacks, the timeout algorithm requires that the destination chain is running and reachable. One can prove nothing in a complete network partition, and must wait to connect; the timeout must be proven on the recipient chain, not simply the absence of a response on the sending chain. From 3860445a8662090282dad83440bf4c26ff13b2b5 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Thu, 26 Sep 2024 12:50:47 +0200 Subject: [PATCH 15/71] add sketch packet flow --- .../core/v2/ics-004-packet-semantics/README.md | 4 ++++ .../Sketch_1P_Happy_Path.png | Bin 0 -> 132782 bytes 2 files changed, 4 insertions(+) create mode 100644 spec/core/v2/ics-004-packet-semantics/Sketch_1P_Happy_Path.png diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 04557144b..3fac388b7 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -297,6 +297,10 @@ TODO The following sequence of steps must occur for a packet to be sent from module *1* on machine *A* to module *2* on machine *B*, starting from scratch. +![V2 Happy Path Single Payload Sketch](Sketch_1P_Happy_Path.png) + +V1 OLD THINGS.. + The module can interface with the IBC handler through [ICS 25]( ../../ics-025-handler-interface) or [ICS 26]( ../../ics-026-routing-module). 1. Initial client & port setup, in any order diff --git a/spec/core/v2/ics-004-packet-semantics/Sketch_1P_Happy_Path.png b/spec/core/v2/ics-004-packet-semantics/Sketch_1P_Happy_Path.png new file mode 100644 index 0000000000000000000000000000000000000000..bab1c334e7c18ec830a723363f316a096ae2f93e GIT binary patch literal 132782 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%0AP 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 M*USy8u6V@%57z!ug#Z8m literal 0 HcmV?d00001 From 6150e6800d22a4b3cd8d736065897d0125afdaec Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Fri, 27 Sep 2024 16:47:01 +0200 Subject: [PATCH 16/71] rework + ack logic --- .../v2/ics-004-packet-semantics/README.md | 244 ++++++++---------- 1 file changed, 114 insertions(+), 130 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 3fac388b7..27af31fd3 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -4,7 +4,7 @@ title: Packet Semantics stage: draft category: IBC/TAO kind: instantiation -requires: 2, 3, 5, 24 +requires: 2, 24 version compatibility: ibc-go v7.0.0 author: Christopher Goes created: 2019-03-07 @@ -43,15 +43,7 @@ interface Counterparty { } ``` -NOTE: Do we need and want to keep nextSequence* info? To my understanding ack and recv are relative to ordered channel and can be safely removed. Then nextSequenceSend should eventually go in the privateStore. That means we don't need to specify the paths later on, correct? - -- The `nextSequenceSend`, stored separately, tracks the sequence number for the next packet to be sent. - -// NOTE - Do we want to keep an upgrade spec to specify how to change the client and so on? Probably unnecessary, just showing here how to do that would be enough. - -See the [upgrade spec](../../ics-004-channel-and-packet-semantics/UPGRADES.md) for details on `upgradeSequence`. - -The `Packet`, `Payload`, `Encoding` and the `Acknowledgement` interfaces are as defined in [add ref once merged](ref). For convenience, following we recall their structures. +The `Packet`, `Payload`, `Encoding` and the `Acknowledgement` interfaces are as defined in [packet specification](https://github.com/cosmos/ibc/blob/c7b2e6d5184b5310843719b428923e0c5ee5a026/spec/core/v2/ics-004-packet-semantics/PACKET.md). For convenience, following we recall their structures. A `Packet`, in the interblockchain communication protocol, is a particular interface defined as follows: @@ -158,8 +150,7 @@ E.g. If a packet within 3 payloads intended for 3 different application is sent ### Dataflow visualisation -TODO -The architecture of clients, connections, channels and packets: +TODO The architecture of clients, connections, channels and packets: ![Dataflow Visualisation](../../ics-004-channel-and-packet-semantics/dataflow.png) @@ -167,38 +158,9 @@ The architecture of clients, connections, channels and packets: #### Store paths -NOTE do we stil need this, or we can retrieve the sequence from the commitment path associated with the clientID? +The protocol defines the paths `packetCommitmentPath`, `packetRecepitPath` and `packetAcknowledgementPath` that MUST be used as the referece locations in the provableStore to prove respectilvey the packet commitment, the receipt and the acknowledgment to the counterparty chain. -- The `nextSequenceSend`, stored separately, tracks the sequence number for the next packet to be sent. - -```typescript -function nextSequenceSendPath(sourceID: bytes, destID: bytes): Path { - return "nextSequenceSend/clients/{sourceID}/clients/{destID}" -} -``` - -/* Is all of this Unnecessary? - -NOTE Channel paths and capabilities should be maintained for backward compatibility? -Channel structures are stored under a store path prefix unique to a combination of a port identifier and channel identifier: - -```typescript -function channelPath(portIdentifier: bytes, channelIdentifier: bytes): Path { - return "channelEnds/ports/{portIdentifier}/channels/{channelIdentifier}" -} -``` - -The capability key associated with a channel is stored under the `channelCapabilityPath`: - -```typescript -function channelCapabilityPath(portIdentifier: Identifier, channelIdentifier: Identifier): Path { - return "{channelPath(portIdentifier, channelIdentifier)}/key" -} -``` - -*/ - -Constant-size commitments to packet data fields are stored under the packet sequence number: +Thus Constant-size commitments to packet data fields are stored under the packet sequence number: ```typescript function packetCommitmentPath(sourceId: bytes, sequence: uint64): Path { @@ -212,7 +174,7 @@ Packet receipt data are stored under the `packetReceiptPath`. In the case of a s ```typescript function packetReceiptPath(sourceId: bytes, sequence: uint64): Path { - return "receipts/channels/{sourceId}/sequences/{bigEndianUint64Sequence}" + return "receipts/channels/{sourceId}/sequences/{sequence}" } ``` @@ -224,6 +186,14 @@ function packetAcknowledgementPath(sourceId: bytes, sequence: uint64): Path { } ``` +- The `nextSequenceSend` is stored separately in the privateStore and tracks the sequence number for the next packet to be sent for a given source clientId. + +```typescript +function nextSequenceSendPath(sourceID: bytes): Path { + return "nextSequenceSend/clients/{sourceID} +} +``` + ### Sub-protocols > Note: If the host state machine is utilising object capability authentication (see [ICS 005](../ics-005-port-allocation)), all functions utilising ports take an additional capability parameter. @@ -293,45 +263,25 @@ TODO : Adapt to new flow ##### A day in the life of a packet -TODO +TODO Write Setup. The following sequence of steps must occur for a packet to be sent from module *1* on machine *A* to module *2* on machine *B*, starting from scratch. -![V2 Happy Path Single Payload Sketch](Sketch_1P_Happy_Path.png) - -V1 OLD THINGS.. +TODO Modify Sketch -The module can interface with the IBC handler through [ICS 25]( ../../ics-025-handler-interface) or [ICS 26]( ../../ics-026-routing-module). - -1. Initial client & port setup, in any order - 1. Client created on *A* for *B* (see [ICS 2](../ics-002-client-semantics)) - 1. Client created on *B* for *A* (see [ICS 2](../ics-002-client-semantics)) - 1. Module *1* binds to a port (see [ICS 5](../ics-005-port-allocation)) - 1. Module *2* binds to a port (see [ICS 5](../ics-005-port-allocation)), which is communicated out-of-band to module *1* -1. Establishment of a connection & channel, optimistic send, in order - 1. Connection opening handshake started from *A* to *B* by module *1* (see [ICS 3](../../ics-003-connection-semantics)) - 1. Channel opening handshake started from *1* to *2* using the newly created connection (this ICS) - 1. Packet sent over the newly created channel from *1* to *2* (this ICS) -1. Successful completion of handshakes (if either handshake fails, the connection/channel can be closed & the packet timed-out) - 1. Connection opening handshake completes successfully (see [ICS 3](../../ics-003-connection-semantics)) (this will require participation of a relayer process) - 1. Channel opening handshake completes successfully (this ICS) (this will require participation of a relayer process) -1. Packet confirmation on machine *B*, module *2* (or packet timeout if the timeout height has passed) (this will require participation of a relayer process) -1. Acknowledgement (possibly) relayed back from module *2* on machine *B* to module *1* on machine *A* - -Represented spatially, packet transit between two machines can be rendered as follows: - -![Packet Transit](../../ics-004-channel-and-packet-semantics/packet-transit.png) +![V2 Happy Path Single Payload Sketch](Sketch_1P_Happy_Path.png) ##### Sending packets -The `sendPacket` function is called by a module in order to send *data* in the form of an IBC packet. +The `sendPacket` function is called by the IBC handler when an IBC packet is submitted to the newtwork in order to send *data* in the form of an IBC packet. ∀ `Payload` included in the `packet.data`, which may refer to a different application, the application specific callbacks are retrieved from the the IBC router and the `onSendPacket` is the then triggered on the specified application. The `onSendPacket` executes the application logic. Once all payloads contained in the `packet.data` have been acted upon, the packet commitment is generated and the sequence number specific to the sourceClientId is incremented. -Calling modules MUST execute application logic atomically in conjunction with calling `sendPacket`. +The `sendPacket` core function MUST execute the applications logic atomically triggering the `onSendPacket` callback ∀ application contained in the `packet.data` payload. The IBC handler performs the following steps in order: - Checks that the underlying clients is properly registered in the IBC router. - Checks that the timeout specified has not already passed on the destination chain +- Executes the `onSendPacket` ∀ Payload included in the packet. - Stores a constant-size commitment to the packet data & packet timeout - Increments the send sequence counter associated with the channel - Returns the sequence number of the sent packet @@ -343,7 +293,7 @@ function sendPacket( sourceClientId: bytes, destClientId: bytes, timeoutTimestamp: uint64, - packetData: []byte + packet: []byte ): uint64 { // in this specification, the source channel is the clientId client = router.clients[packet.sourceClientId] @@ -353,24 +303,27 @@ function sendPacket( assert(timeoutTimestamp !== 0) // Maybe this can be enforced even for unreal timeouts value and not only for 0 assert(currentTimestamp()> timeoutTimestamp) // Mmm - // NOTE - What is the commit port? Should be the sourcePort? If yes, in the packet we should put destPort and destID? // if the sequence doesn't already exist, this call initializes the sequence to 0 - //sequence = channelStore.get(nextSequenceSendPath(commitPort, sourceID)) - sequence = channelStore.get(nextSequenceSendPath(sourceClientID, destClientID)) + sequence = privateStore.get(nextSequenceSendPath(sourceClientId)) + // Executes Application logic ∀ Payload + for payload in packet.data + cbs = router.callbacks[payload.sourcePort] + success = cbs.onSendPacket(version, encoding, appData) + // IMPORTANT: if the one of the onSendPacket fails, the transaction is aborted and the potential state changes that previosly onSendPacket should apply are automatically reverted. + abortUnless(success) + // store commitment to the packet data & packet timeout - // Note do we need to keep the channelStore? Should this be instead the counterParty store or something similar? Do we keep it for backward compatibility? - channelStore.set( + provableStore.set( packetCommitmentPath(sourceClientId sequence), hash(hash(data), timeoutTimestamp) ) - // increment the sequence. Thus there are monotonically increasing sequences for packet flow - // from sourcePort, sourceChannel pair - channelStore.set(nextSequenceSendPath(sourceClientID, destClientID), sequence+1) + // increment the sequence. Thus there are monotonically increasing sequences for packet flow for a given clientId + privateStore.set(nextSequenceSendPath(sourceClientID), sequence+1) // log that a packet can be safely sent - // introducing sourceID and destID can be useful for monitoring - e.g. if one wants to monitor all packets between sourceID and destID emitting this in the event would simplify his life. + // NOTE introducing sourceID and destID can be useful for monitoring - e.g. if one wants to monitor all packets between sourceID and destID emitting this in the event would simplify his life. emitLogEntry("sendPacket", { sourceID: sourceClientId, @@ -385,19 +338,17 @@ function sendPacket( #### Receiving packets -The `recvPacket` function is called by a module in order to receive an IBC packet sent on the corresponding client on the counterparty chain. +The `recvPacket` function is called by the IBC handler in order to receive an IBC packet sent on the corresponding client on the counterparty chain. -Atomically in conjunction with calling `recvPacket`, calling modules MUST either execute application logic or queue the packet for future execution. +Atomically in conjunction with calling `recvPacket`, the modules/application referred in the `packet.data` payload MUST execute the specific application logic callaback. The IBC handler performs the following steps in order: - Checks that the clients is properly set in IBC router - Checks that the packet metadata matches the channel & connection information -- Checks that the packet sequence is the next sequence the channel end expects to receive (for ordered and ordered_allow_timeout channels) -- Checks that the timeout height and timestamp have not yet passed +- Checks that the timeout timestamp is not yet passed - Checks the inclusion proof of packet data commitment in the outgoing chain's state -- Sets a store path to indicate that the packet has been received (unordered channels only) -- Increments the packet receive sequence associated with the channel end (ordered and ordered_allow_timeout channels only) +- Sets a store path to indicate that the packet has been received We pass the address of the `relayer` that signed and submitted the packet to enable a module to optionally provide some rewards. This provides a foundation for fee payment, but can be used for other techniques as well (like calculating a leaderboard). @@ -407,17 +358,23 @@ function recvPacket( proof: CommitmentProof, proofHeight: Height, relayer: string): Packet { + // in this specification, the destination channel is the clientId client = router.clients[packet.destClientId] assert(client !== null) - // assert source channel is destChannel's counterparty channel identifier - counterparty = getCounterparty(packet.sourceClientId) + // assert that our counterparty clientId is the packet.sourceClientId + counterparty = getCounterparty(packet.destClientId) assert(packet.sourceClientId == counterparty.clientId) - // assert source port is destPort's counterparty port identifier - assert(packet.sourcePort == ports[packet.destPort]) // Needed? + // verify timeout + assert(packet.timeoutTimestamp === 0 || currentTimestamp() < packet.timeoutTimestamp) + // verify the packet receipt for this packet does not exist already + packetReceipt = provableStore.get(packetReceiptPath(packet.sourceClientId, packet.sequence)) + abortUnless(packetReceipt === null) + + // verify commitment packetPath = packetCommitmentPath(packet.sourceClientId, packet.sequence) merklePath = applyPrefix(counterparty.keyPrefix, packetPath) // DISCUSSION NEEDED: Should we have an in-protocol notion of Prefixing the path @@ -430,20 +387,29 @@ function recvPacket( hash(packet.data, packet.timeoutTimestamp) )) - assert(packet.timeoutTimestamp === 0 || currentTimestamp() < packet.timeoutTimestamp) + // Executes Application logic ∀ Payload + for payload in packet.data + // assert source port is destPort's counterparty port identifier + port= router.ports[payload.destPort] + assert(payload.sourcePort == port) + cbs = router.callbacks[payload.destPort] + ack = cbs.onReceivePacket(payaload.version, payload.encoding, payload.appData) + // the onReceivePacket returns the ack but does not write it + // IMPORTANT: if the ack is error, then the callback reverts its internal state changes, but the entire tx continues + multiAck=multiAck.add(ack) - // we must set the receipt so it can be verified on the other side - // this receipt does not contain any data, since the packet has not yet been processed // it's the sentinel success receipt: []byte{0x01} - packetReceipt = channelStore.get(packetReceiptPath(packet.sourceChannelId, packet.sequence)) - assert(packetReceipt === null) - - channelStore.set( + provableStore.set( packetReceiptPath(packet.sourceChannelId, packet.sequence), SUCCESSFUL_RECEIPT ) + // NOTE: Currently only process synchronous acks. + if multiAck != nil { + writeAcknowledgement(packet, multiAck) + } + // log that a packet has been received emitLogEntry("recvPacket", { data: packet.data @@ -451,84 +417,104 @@ function recvPacket( sequence: packet.sequence, sourceClientId: packet.sourceClientId, destClientId: packet.destClientId, - // MMM app= packet.PacketData.destPort?? I mean shall we use the app in somehow here? + relayer: relayer }) - - cbs = router.callbacks[packet.destClientId] - // IMPORTANT: if the ack is error, then the callback reverts its internal state changes, but the entire tx continues - ack = cbs.OnRecvPacket(packet, relayer) - if ack != nil { - channelStore.set(packetAcknowledgementPath(packet.sourceClientID, packet.sequence), ack) - } } ``` #### Writing acknowledgements -TODO: All ack related stuff... Define multidata ack, adpat description.... - -The `writeAcknowledgement` function is called by a module in order to write data which resulted from processing an IBC packet that the sending chain can then verify, a sort of "execution receipt" or "RPC call response". +NOTE: Currently only process synchronous acks. -Calling modules MUST execute application logic atomically in conjunction with calling `writeAcknowledgement`. +The `writeAcknowledgement` function is called by the IBC handler once all `onRecvPacket` application modules callabacks have been triggered and have returned their specific acknowledgment in order to write data which resulted from processing an IBC packet that the sending chain can then verify, a sort of "execution receipt" or "RPC call response". -This is an asynchronous acknowledgement, the contents of which do not need to be determined when the packet is received, only when processing is complete. In the synchronous case, `writeAcknowledgement` can be called in the same transaction (atomically) with `recvPacket`. +This is a synchronous acknowledgement, thus `writeAcknowledgement` MUST be called in the same transaction (atomically) with `recvPacket` and and application callback logic execution. -Acknowledging packets is not required; however, if packets are not acknowledged, packet commitments cannot be deleted on the source chain. Future versions of IBC may include ways for modules to specify whether or not they will be acknowledging packets in order to allow for cleanup. - -`writeAcknowledgement` *does not* check if the packet being acknowledged was actually received, because this would result in proofs being verified twice for acknowledged packets. This aspect of correctness is the responsibility of the calling module. -The calling module MUST only call `writeAcknowledgement` with a packet previously received from `recvPacket`. +`writeAcknowledgement` *does not* check if the packet being acknowledged was actually received, because this would result in proofs being verified twice for acknowledged packets. This aspect of correctness is the responsibility of the IBC handler. The IBC handler performs the following steps in order: - Checks that an acknowledgement for this packet has not yet been written - Sets the opaque acknowledgement value at a store path unique to the packet +```typescript +function writeAcknowledgement( + packet: Packet, + acknowledgement: [bytes]) { + // acknowledgement must not be empty + abortTransactionUnless(len(acknowledgement) !== 0) + + // cannot already have written the acknowledgement + abortTransactionUnless(provableStore.get(packetAcknowledgementPath(packet.destChannelId, packet.sequence) === null)) + + // write the acknowledgement + provableStore.set( + packetAcknowledgementPath(packet.destClientId, packet.sequence), + hash(multiAck) + ) + + // log that a packet has been acknowledged + emitLogEntry("writeAcknowledgement", { + sequence: packet.sequence, + sourceClientId: packet.sourceClientId, + destClientId: packet.destClientId, + timeoutTimestamp: packet.timeoutTimestamp, + data: packet.data, + multiAck + }) +} +``` + #### Processing acknowledgements -The `acknowledgePacket` function is called by a module to process the acknowledgement of a packet previously sent by -the calling module on a channel to a counterparty module on the counterparty chain. -`acknowledgePacket` also cleans up the packet commitment, which is no longer necessary since the packet has been received and acted upon. +The `acknowledgePacket` function is called by the IBC handler to process the acknowledgement of a packet previously sent by the source chain. -Calling modules MAY atomically execute appropriate application acknowledgement-handling logic in conjunction with calling `acknowledgePacket`. +The `acknowledgePacket` also cleans up the packet commitment, which is no longer necessary since the packet has been received and acted upon. + +The IBC hanlder MUST atomically trigger the callbacks execution of appropriate application acknowledgement-handling logic in conjunction with calling `acknowledgePacket`. We pass the `relayer` address just as in [Receiving packets](#receiving-packets) to allow for possible incentivization here as well. ```typescript function acknowledgePacket( packet: OpaquePacket, - acknowledgement: bytes, + acknowledgement: [bytes], proof: CommitmentProof, proofHeight: Height, relayer: string ) { // in this specification, the source channel is the clientId - client = router.clients[packet.sourceChannel] + client = router.clients[packet.sourceClientId] assert(client !== null) // assert dest channel is sourceChannel's counterparty channel identifier - counterparty = getCounterparty(packet.destChannel) - assert(packet.sourceChannel == counterparty.channelId) + counterparty = getCounterparty(packet.destClientId) + assert(packet.sourceClientId == counterparty.ClientId) // assert dest port is sourcePort's counterparty port identifier assert(packet.destPort == ports[packet.sourcePort]) // verify we sent the packet and haven't cleared it out yet - assert(provableStore.get(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) - === hash(hash(packet.data), packet.timeoutHeight, packet.timeoutTimestamp)) + assert(provableStore.get(packetCommitmentPath(packet.sourceClientId, packet.sequence)) + === hash(hash(packet.data), packet.timeoutTimestamp)) - ackPath = packetAcknowledgementPath(packet.destPort, packet.destChannel) + ackPath = packetAcknowledgementPath(packet.destClientId, packet.sequence) merklePath = applyPrefix(counterparty.keyPrefix, ackPath) assert(client.verifyMembership( proofHeight, - 0, 0, proof, merklePath, hash(acknowledgement) )) - cbs = router.callbacks[packet.sourcePort] - cbs.OnAcknowledgePacket(packet, acknowledgement, relayer) + // Executes Application logic ∀ Payload + nAck=0 + for payload in packet.data + cbs = router.callbacks[payload.sourcePort] + success.OnAcknowledgePacket(packet, acknowledgement[nAck], relayer) + abortUnless(success) + nAck++ channelStore.delete(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) } @@ -560,8 +546,6 @@ or `0xb2` (error). #### Timeouts -TODO - Application semantics may require some timeout: an upper limit to how long the chain will wait for a transaction to be processed before considering it an error. Since the two chains have different local clocks, this is an obvious attack vector for a double spend - an attacker may delay the relay of the receipt or wait to send the packet until right after the timeout - so applications cannot safely implement naive timeout logic themselves. Note that in order to avoid any possible "double-spend" attacks, the timeout algorithm requires that the destination chain is running and reachable. One can prove nothing in a complete network partition, and must wait to connect; the timeout must be proven on the recipient chain, not simply the absence of a response on the sending chain. From 09572094ffaf3d69b91bdcfb11f33a619fd3a320 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Fri, 27 Sep 2024 17:27:23 +0200 Subject: [PATCH 17/71] fixes + timeout logic --- .../v2/ics-004-packet-semantics/README.md | 62 ++++++------------ .../Sketch_1P_Happy_Path.png | Bin 132782 -> 0 bytes .../Sketch_Happy_Path.png | Bin 0 -> 164076 bytes 3 files changed, 21 insertions(+), 41 deletions(-) delete mode 100644 spec/core/v2/ics-004-packet-semantics/Sketch_1P_Happy_Path.png create mode 100644 spec/core/v2/ics-004-packet-semantics/Sketch_Happy_Path.png diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 27af31fd3..c17214443 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -263,14 +263,13 @@ TODO : Adapt to new flow ##### A day in the life of a packet +TODO Modify Sketch +![V2 Happy Path Single Payload Sketch](Sketch_Happy_Path.png) + TODO Write Setup. The following sequence of steps must occur for a packet to be sent from module *1* on machine *A* to module *2* on machine *B*, starting from scratch. -TODO Modify Sketch - -![V2 Happy Path Single Payload Sketch](Sketch_1P_Happy_Path.png) - ##### Sending packets The `sendPacket` function is called by the IBC handler when an IBC packet is submitted to the newtwork in order to send *data* in the form of an IBC packet. ∀ `Payload` included in the `packet.data`, which may refer to a different application, the application specific callbacks are retrieved from the the IBC router and the `onSendPacket` is the then triggered on the specified application. The `onSendPacket` executes the application logic. Once all payloads contained in the `packet.data` have been acted upon, the packet commitment is generated and the sequence number specific to the sourceClientId is incremented. @@ -490,7 +489,7 @@ function acknowledgePacket( // assert dest channel is sourceChannel's counterparty channel identifier counterparty = getCounterparty(packet.destClientId) - assert(packet.sourceClientId == counterparty.ClientId) + assert(packet.sourceClientId == counterparty.clientId) // assert dest port is sourcePort's counterparty port identifier assert(packet.destPort == ports[packet.sourcePort]) @@ -512,7 +511,7 @@ function acknowledgePacket( nAck=0 for payload in packet.data cbs = router.callbacks[payload.sourcePort] - success.OnAcknowledgePacket(packet, acknowledgement[nAck], relayer) + success= cbs.OnAcknowledgePacket(packet, acknowledgement[nAck], relayer) abortUnless(success) nAck++ @@ -558,14 +557,8 @@ can no longer be executed and to allow the calling module to safely perform appr Calling modules MAY atomically execute appropriate application timeout-handling logic in conjunction with calling `timeoutPacket`. -In the case of an ordered channel, `timeoutPacket` checks the `recvSequence` of the receiving channel end and closes the channel if a packet has timed out. - In the case of an unordered channel, `timeoutPacket` checks the absence of the receipt key (which will have been written if the packet was received). Unordered channels are expected to continue in the face of timed-out packets. -If relations are enforced between timeout heights of subsequent packets, safe bulk timeouts of all packets prior to a timed-out packet can be performed. This specification omits details for now. - -Since we allow optimistic sending of packets (i.e. sending a packet before a channel opens), we must also allow optimistic timing out of packets. With optimistic sends, the packet may be sent on a channel that eventually opens or a channel that will never open. If the channel does open after the packet has timed out, then the packet will never be received on the counterparty so we can safely timeout optimistically. If the channel never opens, then we MUST timeout optimistically so that any state changes made during the optimistic send by the application can be safely reverted. - We pass the `relayer` address just as in [Receiving packets](#receiving-packets) to allow for possible incentivization here as well. ```typescript @@ -575,20 +568,19 @@ function timeoutPacket( proofHeight: Height, relayer: string ) { - // in this specification, the source channel is the clientId - client = router.clients[packet.sourceChannel] + client = router.clients[packet.sourceClientId] assert(client !== null) // assert dest channel is sourceChannel's counterparty channel identifier - counterparty = getCounterparty(packet.destChannel) - assert(packet.sourceChannel == counterparty.channelId) + counterparty = getCounterparty(packet.destClientId) + assert(packet.sourceClientId == counterparty.channelId) // assert dest port is sourcePort's counterparty port identifier assert(packet.destPort == ports[packet.sourcePort]) // verify we sent the packet and haven't cleared it out yet - assert(provableStore.get(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) - === hash(hash(packet.data), packet.timeoutHeight, packet.timeoutTimestamp)) + assert(provableStore.get(packetCommitmentPath(packet.sourceClientId, packet.sequence)) + === hash(hash(packet.data), packet.timeoutTimestamp)) // get the timestamp from the final consensus state in the channel path var proofTimestamp @@ -596,23 +588,22 @@ function timeoutPacket( assert(err != nil) // check that timeout height or timeout timestamp has passed on the other end - asert( - (packet.timeoutHeight > 0 && proofHeight >= packet.timeoutHeight) || - (packet.timeoutTimestamp > 0 && proofTimestamp >= packet.timeoutTimestamp)) + asert(packet.timeoutTimestamp > 0 && proofTimestamp >= packet.timeoutTimestamp) - receiptPath = packetReceiptPath(packet.destPort, packet.destChannel, packet.sequence) + receiptPath = packetReceiptPath(packet.destClientId, packet.sequence) merklePath = applyPrefix(counterparty.keyPrefix, receiptPath) assert(client.verifyNonMembership( proofHeight - 0, 0, proof, merklePath )) - cbs = router.callbacks[packet.sourcePort] - cbs.OnTimeoutPacket(packet, relayer) + for payload in packet.data + cbs = router.callbacks[payload.sourcePort] + success=cbs.OnTimeoutPacket(packet, relayer) + abortUnless(success) - channelStore.delete(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) + channelStore.delete(packetCommitmentPath(packet.sourceChannelId, packet.sequence)) } ``` @@ -622,6 +613,8 @@ Packets must be acknowledged in order to be cleaned-up. #### Reasoning about race conditions +TODO + ##### Identifier allocation There is an unavoidable race condition on identifier allocation on the destination chain. Modules would be well-advised to utilise pseudo-random, non-valuable identifiers. Managing to claim the identifier that another module wishes to use, however, while annoying, cannot man-in-the-middle a handshake since the receiving module must already own the port to which the handshake was targeted. @@ -638,29 +631,17 @@ Verification of cross-chain state prevents man-in-the-middle attacks for both co If a client has been frozen while packets are in-flight, the packets can no longer be received on the destination chain and can be timed-out on the source chain. -#### Querying channels - -Channels can be queried with `queryChannel`: - -```typescript -function queryChannel(connId: Identifier, chanId: Identifier): ChannelEnd | void { - return provableStore.get(channelPath(connId, chanId)) -} -``` - ### Properties & Invariants -- The unique combinations of channel & port identifiers are first-come-first-serve: once a pair has been allocated, only the modules owning the ports in question can send or receive on that channel. - Packets are delivered exactly once, assuming that the chains are live within the timeout window, and in case of timeout can be timed-out exactly once on the sending chain. -- The channel handshake cannot be man-in-the-middle attacked by another module on either blockchain or another blockchain's IBC handler. ## Backwards Compatibility -Not applicable. +TODO Mmmm ..Not applicable. ## Forwards Compatibility -Data structures & encoding can be versioned at the connection or channel level. Channel logic is completely agnostic to packet data formats, which can be changed by the modules any way they like at any time. +Data structures & encoding can be versioned at the application level. Core logic is completely agnostic to packet.data formats, which can be changed by the application modules any way they like at any time. ## Example Implementations @@ -689,7 +670,6 @@ Mar 28, 2023 - Add `writeChannel` function to write channel end after executing All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0). - /* NOTE What to do with this? #### Identifier validation diff --git a/spec/core/v2/ics-004-packet-semantics/Sketch_1P_Happy_Path.png b/spec/core/v2/ics-004-packet-semantics/Sketch_1P_Happy_Path.png deleted file mode 100644 index bab1c334e7c18ec830a723363f316a096ae2f93e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 132782 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%0AP zjgUG3E&hJbsaipthSm-~kmo429_!jJwZ-I1z{IvZ-rL2k+H4l$8pa~{yft;zm3QHt z?ZZoxF=p|b$3d1EDXZ3YlXhXq$rMNJM{AVmZ}C4ET(9f$Mbv$Ll;1g5EuCwq_pSQW zJBOPmYvg{5YVXsC0sb`>pliOPQ{&-%4a&jB`c9egZi|HEan2@PLV#)3IfrAY&!Xm0 zU)q7xU7@$;3_I)TCJvjrwR@2b?@P&P$L#FIDomLiAx<|%4-un^tIPuo8==NOtComz zlsnnFT6`bz)tnaDwAS;_41cIN+-Z6jpekdd6bKnt83{N0G_K^`tMAA8#rNx{=IUmH zQkGmC=mF$eZUnsN*t68{U&kcE=A2n1K*!+40vk;~~_tq(dBO1H{qib7!_5M~l=s60uZ zEDlaCvl^GR-jNySbTIsHo2V7)RnetCJg=@%?~RUl|9GFRVy|0 z-8&9{S_mz}cpUIq3CiG?qS(Ssnq9l@KCL1%Onb*iX{sJfKHAIP@s!N+Rycahg4Wb$ zkANv?KMX1r)-GePB8%`lSY%zs?aphq*Tvxs0c&gY%Tk;FfGqioLDfRw93~(^onWZc z`e<+jq&x#SI8hLDoG5ZfHhZ=WGXHy@zD8|>oPT$rM4p*DFUuqH<;+=~%X^;0E6tdG zNyP&>#(BAWPQO>U|GKI7Oz6|I{PkBY5}tac5bwi{34i~XKT6?3HHdnT<=IV^`(Yk} z!&*i0O5)62=^7BiPnhcAX_m78$=EE#MI#BN#AcX!)t@3~V$xexlaDuc3YbOLP z;pWm7O}T^e(#dB$AsvNq;QaYae%pNoyEE$c{NRk5eKqiDln(z1*k}t5f>uIY-;gIw z0N~B#seQXQot_I|bv!Z1A#wNDFaGm00rhlQw{47nAfmZgQx>6IeL;*o2TE@~0*uw* zu=tF9)?}XX*YXyq7OZ#0-C8X2l6PBiR2pz(Rz$`6Ob6qcbf@u@+#-zo(+GsvK?}$&I$Q^)%iDm+0+5*1H@%zyN^Nm&>LE`KRXtyUuHd%k0 z1xFaT)rkgPJOwn)(-C}UfR)Y1NM=jrm?%9U&$VICY65(~Vf;HZ`G1ZEj7831@{s?3 zzWhJVz={}k`~hT~_&+?&{_`Jc-3RArHFf0SziavbJ01V$!&&pe2JTvYHT~JqO?&hO zKKXp2doJ2SNq~w;fHRV|Em1HjD^)O)EJc@iJ`uqQy_u_ZJvzTNk+w^KkW@_R@n`dk zF)!0Gsj;ky-G=_j(}{kGRsX4e{|EgeJ-cf)qm9ysjiWCDnolbXu?P}7@5Dtz8D)d6 zU}59nL;m&QBcV#r8s{SXKm6C9zid{CeUmZ9iH4lzzgz(433=j`@AvxLe!uo#-?UiZ zvdU8Y5av|+&n>{gU*lxNdRKy;`>k<*UoyB8G?Kg4PA~R9J_7iyZ}()etqjCI{ny8g z0gW_r94eyvPkn&7O+`r%&gb&S@&ENPDY!r*zZGZ;{MVi`0Db*no>uw4Jtnt4Xk?{E zp2gpf^;=U~%0XX${3;gz-yYMj6g1M}xl-qU?WqT#ub(_~r~U2Pe_!(d4T!%d!2cT% ze+`lU-v)%M62aNZ+l!UGhM|)n2LXXa; zF3*?Cy&6~dQVS&?oh&6>ENKvn@kqM^W_DRqLi5b6CiD6)8qSaJ4;$T1=K!lo&l7E~ ze>Ra@XBq%7vmb6p8{7rPf4zGJE++*;PA+~wmb*lowkt>W6E2Su3T>oTjfNB{9|%v( zQoM)Pu6oP@Pe8|$V1}dMd;AA&M0}E?4q{?h*h70hIfp(&L=U$@mS2(hFYVnn-MOve z%ot2^xkZw5vBP)yxE`ok7rqZXeV-GsU=v_{GRt={yZrF5er&JC^t9%aPKou;#+7?- zvo-<_KWd8B55&dK)7W&Y2ClvjaH#(zwISgDJ>CENUJIVItM+?;U>9;1bYo!kP|w#w zsdc*{7W!@c(3=z^xrH{5SY?s@Pjnsr`Mk6xvT|RPWljc49L~l9&&T#a4^9m=U#vDG z-}3MPs)Tt(=#5 zEAA=!HMBqOsq^|Nrp-AQi-8w5oE4Xs7gLuKeccbeHk7_l{NjkxVqU1gJRV=kBm1;! zP1V*_nB)w>5az9D=Ik5gc&arlP#$A0G1A=h<$1%GO4E-4155jrHa%146H{deszvK7 z3NBmMTwK>Y;pIe*<9LT|=Qp3@PIB#Mp7cp~V|t`4`EfINOJe7pre~cinH*P6w<2Yp z`o_HXj`4rElHb2Cy}2u0^6T+MVJ^vA18oyUVja`|`-9lxl6>XS*;3~{} zS>4okiTk6M|Cm>{8p?kV=09kE=j)eL-S0V?1utOYPpUhkti4iMkHa*N!iqB`WsX8+ zyiM+XKWUKJVUZD(S~DjxraQ!3bjYu9%+JX)KCOAhho@Ngu`<(t3ItlHdAAA!PYO$3 zmrtJcPCfN4dGA{?l5D`^wGRv2ft6gjHxCP#g%yyTyd)8)h`Px&b+JCxBz}l#VA&{K zrcW?!?)VkM$*X0|B4NOidb!|oVSO^ga51yoF1^+szdKmso?td1(KsZb!S{5Ah@@c> zKO!604-Tc|oc-!#m228lZ}#v#sLE+nV{F_!ZQ86f?&aejZFXVx@uU2CwjesSx*vGx z73d}6TX*He^m5-+Q|8cJ=FnH@RTpl~E_(6q`;Qar2fpXJKJPS6rkhrFZ#8e<8tbhV z|1RsNTJ$1P^l7`ONv|H)_CPWc?^)XLaU0)RoBu@D!#<X_VeSW`cz)0^=MSoC^$ zUVj^}Uu+S5@8$<;Jg1P0mv~aPe1~wEFDe<6mluaQlXFs&mcDbM0dx0~-z>gg^hs^9 zQESmFeDJk+t^eb&ONwQe#HG9RqI>!CXLSKTskpZ=)1u_NggS@U#~%{~=?%Ybm#pkH zxSTpWA9tTj8~6hy8MCUqG>c+-7&z=cejE_b_K$e9#R&Gt!Uxt~~9MbJ?TBc*5TEJPrF6%TtF)b-u<1>ydo+ zxC-P! zCb~PBPqNYN=~z8+jC`^ga$Ki$o~1e>bQZsP6kn2KjU?~6jDC9*y$s%=`=RG8Ke2w5 zplXh%aoCm~`?qJOy}myyDky<5{-Aes$Jgg5y5_66XXA zFoyDEBO^UE>BG&BJ+WfX&V7szHDqLNGBO+|G1O%dXj{D9>`a!wosjkFn`}5*O*%d7 zq5LssjV;daWye&=IinghjxjTKnW$-+NdNR|jb@^}*?$OjgR8Wr(*u^%^}BQRUR<6n zYVsI7rk(F@KI?u<6y$mFV3cI4_F>KrM~)!IcXY@Qui5O0x(m50C)FWlZv1%hIIq9? zY@W}QXJ{x{3bRfJP7AP9+)JLpjMYLL$vNu(uf6Y#YAWs8hDb(*Q4|$K6rF*nNK-(1 zRSZh+Rf-~^22el*Xd6SeWc; zdE(&m%jz?qy<=G3)iA?K^iMASrxsi-gj}=}>J{a|t;s#J!+N~oaNRIv-K;JNJUz2WA%;DTqm zIB?BR_C#NwjdZ0NDs!RV+KBMBz3&bTV%gKW@<#Tc;==r8%HDMDG(EcfAsJXwuQDT? z#(k|Db`{M!;4j|GI>a%n{YwkLCtj#JaUapSe)BZRO+~HYISs1`G!r)7tH!%~1@%g> z;L+%np6Hd4AxE&Rkh7$bZ_pp{{Yj$mG=GDiZ!_%uY}vBzTOaLe7$t|_pw=!|BM5#Y zD@lQ&`J{4APgH+T#aiVVkFHLugF*q#4Mu%ZmuFu=N_Rk3AYpJ}gW&8)xrsYs_ZV8+ zTwra2*(4f`mg2>Jr9}}=Oq`t=xeI0q*J%tU9c!28x9V(wSg6WC`oKudR~TRu8tXji(eu zBkM8K0d_rRtY)8(#f*YdqS^y*w66MvkvcQ+V^Q$8v-vr1@7^>aB#E1z(+Vk~E=~g_ z$DvrV{&=!q30bz024Je^qZQQ;SR*o}V+_k&gxmcVDw9gnWw#J7yhd62vRwFAMUH?| zEz)vJd72i=b4{miQ5t*P+1fX-) znr1aMzsx$8iXh>4psWq(4jLCVQh!;*XS7sY=Uk+M#b%)7{-=PdIQX)xZ(6di??(W+ z#kep>x7bXpFb2Ec2Ee^50pa0hyIRn=O7J9i2FLE8FTZ|%z)|x4`q+lzjDuwrPfsuv zkYlD-qxobO9WJ)c7!`K9OUQ_o+!TJ4CXML#lnj|3vI0NddH!Ma$vWapkidMhb9bqS zjq=qoHH?l=W>vf!__0L#REJ%&D*F_-=xBvM>;_v)(NpweQIc(H(E)Fv6UkQ1nMaSp z_di2y6olBbmxrv)UE2t4A!OCs%kJA{NfA?u3x;Prs8u+oA%v8hZEviJBxwWTVAovUN({`5JHaxej znxy&?uX8`^533DNw60^@(O)GE7*>yOI^XkiJHd3%eXVoWII>a2&Y&M?SgPINp{^9I z2_;tT@%>NnK$5{%C1qW^6r*5FZkk}f2(#8YW|@P2_I)`V^1}IFfx}4h+*TRBTG7E) zq?Y~GV1J5;{b-xnD=JT~jbmT`-1&l!TXN*j&58C%i_PSS1!ZiD=8xZm5g(SZ7+?Du z6wY*v6VSd@(Fq!B#xW~X0zM8n;ps0m+|T+-2QTdZ0zgFzH=QH1uBZ{^x<0M2=hOn} zP^Mo?T@F153t}*=m40IYwzSH+o{iZ~^!bdQ^3C@2qv~a8DvQWqw@8bz>VsJaRA8eO zM7~lSB=8`zI*{{2(vGr4G4Tnbr;`sroQ(7^F@ev+8P{VI=T*5?k(>FaJ0j7E_bl%P zU^twWiQj;de7~W-O?r1t9s_}!g|bEY6dVT^8W{Sht}-mA6x*sW-o^xv9X$VGtf(2x z5sW-m&_U{0Yn(jPet3Y<_)SQl@pN~jOpDd0K%BdgK^HWl8Vwzbp#r$WPawieCw5Xt z?6^OoxE4W+0l;jP$T=yGsq3rpBrs!aPYakUG)6@8FKI#~%VzhbhOg-Tc&k@2_T_X} z?UAJ0#NrvY&f47+yAG`;NX#VuP8z5HZm$PQ0En;&zgg;~tG8D)EYHNeNb&R^u7&=z zf@{XRCfQ4xSf09X%0CJ!&p%or`^hD^#X6S0-y-6TpL>#37Q`Cl9aTgj(T&evj>pY+ zD)r^(diFj#tW7SeG!MmkLWg|Xn0H3Mqkc|N1eehs<%u0fIn=}Ifg|_0|4J(QzwrD-emu)sj!glfJ5@?&ZcI%28yWpQM-X{KWN2*Sn94`px1 zE8>Z?!idqUDy%IHAC@(HP$y|Vys3q1mQ%fM4qwoxC&=w!#R^!|DckP4SkH<4^-NaJ zqT3j@%Z#EYHLi>OVw>0YFq}(EJLAu~Y2VRSM%BI>1@vOvR%u7&ujCDR%EpJHle{|p z`}CRA^R&X~QJv~@;;IN}(<%|8dyX)-*x~B_F;CYu7>9*{R88iWEMhpzZKXUL&Wd(d zJwdK+vQYe~{4@J2uMFR?Q55zmpQ-RL`fzQc(~UgNwFLC?B>Q`ma3!3x@hnM$t51xq z?~G(c?oE3k` zpNqcO8!fEqBz0TpWb)v`Ib_!uM$zNuO!?snz>=7+@Xp&P{Ie~QbKnR;Th+Su*t<8f zph{}3xY&&-nP*Un=$)XEZ3y~ionS#C0d2%riag%?7|Ie0^-V)z%T+iHPNL}pXafv~ zywePl89J1ZV{=t$Y3F^ERYUMUd33zsqsw2phf^ZnEeFkNhcrKXxTJf~MZrs;TIgw` zp|!#AfV)hmeP?^F<^DnuJ#o8~wHC8nSxBjl*DZ1gd<>&=3F;`uw-O|!pN;Gq1l3KupxWmQ5Au*q$@&mz3{BG$dZlO7 zK7Fgd0Q#6@4~Y>B&G;tEKa}_g8m9NXOBfgEl%4#Nc-{|Y$ZNXyi7bKH?AjR!ltQuQ3D80s$&2_vvd(w>yNv!fCrn;axY3v1fD5HF+<(P8tMw-)XWzC%0W z33KdyMm{i_@137}k7&X3PrFo}r+R^Zsun|LC=f~4)f=Qow5=}VUvXTL87K|GQ1tgj z)TQvx3p{05tBOR0KEg{nrrQ*sDyid7Gx)v*B{5q#Kd29z_#3N?$q#wrXmmZzd zja>+~#sGy|Ce1z1q;S?>+^RQoCl}~&`=j{YzNozfJ4j%om+CRmDFbYlIP=4+2{n1) z;zQ%Mz&zMS2D^LY-05`I@>^c)O@r}x4B^iC8K*-X^S4!9>8K-3EG?f$R|+BWz{QRD zw4g!PU7HD{DTzCYj)4W?%7?lu9>Mt2o|GJol|Q5f(xG8W2GEQPXGSaHKtYUWWdzOh zGitE>xxvMi<>8X~j56^;d|0v-I#F=3Ilx+qW2E2SX-O(i%l0+ov?tC?a&)B`M3t%h zXA_M#{0CDE?F@=QskA=JV=D3yn!>oG+!=NH_2csmOONWMo{|ynoFQ}-R3BTCXtp4f zozf(UReU;SngWe)F22619sdS(wy!q8@u%BNZLIH&lw=9+4VC5BGpnlbFx#JU8p9&m zkSEJ%*Xmr30TOyLv`WMe{FSO~bfVIA$Y=Xkg3*xJ#nm84|1vOL$y7|K$Jsjfb`2hc zcxqa#+8heoB-G-shQTxJcu0({V`qfi-jTb^b-C_)<_tbwJ3YO`Kf|VQ+eNR+77sfZ zu#9-R{vu(d#n>?8*Ao1x#&b{!Gxh{x=qx=|u!G(mf+OfS;NjTnXVs9aB_2N zecCF%oi@tG$d6RCPWoxnrdad&81JuPH_j z`a!48L-v|D=JdDS9_FsMCt4~S@P-LxWf zaZ^LrGK6K%tEOtncd1Z-Hy}xY7khY>)lxBzZHb3}f(w;&%MzW0JQw-AGxL~2&jD9>ZKNwaK#*OBWgHEN-v`O}bEsvz}!g6j8}^O@7V z;3*K^#fGgVW-5Xgg%pxKwy$3}dF&9Jn5p&o%fa%p#vi6VDV|0sEqYW!PIQdz=?V*y zebQ&TWVhs*m-x)1U{|9$NPSLMMv?^gjLM$;2|Z6rS{1?)J6-_q_U_*BgNR77Y97O* z7PYPHF(bcBU)^RnIj{a!x1>Ygh)U8?%i>qdi1OP+vTY~|>#F1~&+1n!)#Y%hT2<(D z5O-S~8PU&vEuJv>EI?dp-Nban9&#y5oRsRF{s1O&YHS}v;Ck2AR`(YsG4G? zaOVP?-LYC+0crgKoN9Q_?%s3UICnl$u*zYK{q+U5#EB37-TBOFjn~aF#m^NPv%4aB!0uiipGDWlF>l4E6sl41^4C8Vb9ScZRSHuY5=oPYYQVu@ zw1jTFKOHJ9reD^e2$eE*!FdNPPHkL~@3oHYC^QxP+K_FL?<}O+_|TqvDAcrzlCe~B zlrJ!$0Gi6S@ih!K=-Db^d{Igj@Wek^E9I#RK1zu^rccuqyeZb1qevRQzFNy47FO1g zC2yr)%gNZ6c`lp7j@JA5qR{H;N-p6(byW=%2`QfZnI9Vwm~=7l{`I)`lirCe$@`GF ztO;e~xNjLMGEZEeRzvO>DHj-jgd8qlbZHib-P{3@uK{Su2n!W<>Hh$GwLy@+IPb>z73(uVi&*~LJ@4! zG!IAY`->EM9&Y5G7IS*p(xbt@=~lQNgvnupMn*dhvtBkA829EsU{Tb|gvWj=i%ZSW zyjKqoc2uYI2R7U%-mY(!WR@6J>XJ762vuc4o9&`=Ii!5PXjD5_F7w{Fbugpp1hR5u zFS1Ir3CprLe|U4*0Uya+&-pT0`tD5x)W4c6Gi^b1<^}7tOn%^*(Gh5Bd1nN1IO;~> zrKwkUOjp%2aOIL1lUfQv1h6DZl+LPkU8Ik<8l*|^Ze>3B8g_+T`<`z)KP?EMH}PcsLT*W!2c zt378gy2IvcI=@HRZWozM9uE>wCF?L;DDl<|_@GPtLT(bO z9NAS-YwJ+22AyeRBymK)!_$KK=b%nRbdI=xe@gB>{>6BG&-`zoPB305HEHk*BkB3n z$&O5wMw^6}h*(5KkdiT?zg4|#w$b1|CMqRU3x!iHF%zsi)sn z#{)lptR~o;F2{-mzQ>RL)NyS*UKLI6(16`Y6kzTfs&+ZKxh+#` zd9T`^nIrq##Z*NC%xBNqM@M`A?0S~mgmTQml)BR~r}@9JT*bL!gcSL8m)~D`jY4n3 zro8WD(@VBLQ=1Ux(BGG9459QkTuda(q%&m*nLA;J8K{xyAskt6bnhMI1krm2br}Uy z*Ar7_O_Ce-nkOve^xzb`V;|+`P->e8Z>?&^6qCH2TWbVnf4pAb&L##Ptk@%Tg{G=*nW2s^x)#Sr0 z&#j8B4`fiyDF>xnaxGR*YYJ|3VG+T&*y#Kumz;e4AgX^uRAFZr^@ItwA<7PEQjzj| z;416V$FLD)BJ5$BN{Pz9wgLAs7x>Aj!E0ib(WJx-=rgeh3>rGF=G<;bY!9^n4^6#8 zxrLtFfQ?mP`0d?dfMr_Erw|f>)rWm4f5;BMk}R7VCUV94IHrReQi$cma$bx@$Zl>IcO@*XhlTZ!a_H(2$IL^Ix*qw;lK zuqXROnJrr(_AclVzn0w8WWcy&ba?~Zj*+*KQ6EQ86PjxGLgPrB+g$^&{5Zy^jr1aNFU7-pGYAv1cJ&FfB7;XF^Em%SV zuU3bYtF#Tq&rz+sbaJy0eOJ>T%4AOTr@)32Gc{;biM|#lHzV8sQGevbW{%KW5H~8x zD`BkK3Dht{KM#%#f%oE4_jZWM&Z3k=)T9QRdb!0qqRzk9@bQip>aj*L75}sosS39- z*i80yYJ_>O)Tw-aQL8-pBbT+xvv&9CZMZab2iTg!+wFu9C0xwGsm%_jTa7MZ?iOb@ zr#F94^#rQ=9W|mo>029qY;jCJemf z2#srvy=^VxDyYXqw4-dx8noCqlH$ZCgMR@?VDct-l>(1Tq%Jo?wBgkU~mpMXDo3P#O4W{OOh@<_dxc@pDt57r5B7DKMjgP}=>GpOJESvce zg-u;=hE=H8ct!{G8KtZHy8n@SR{V0=eH8u~$PF;>9iUAI!R=78gn|jL`tdbi40BY% zz^xNsV91Xbahi58X>Ryo$$U{g>poo;1GfUvYH~Mkp}bKs@#`(1d%TJukluE*t;?zV z9UOhOy-3j@0W%W5B0s1xwI_FO{A{6D%<>dClUf)H>spK_dj`GOqO`g37Hgs-J~e6; zz4Pi5BJ>rZYR<&OkuM4PXXnqEDZ3cEjfrj0G^$cYihJC&82%ZZ{s~paz1=zMBBq3! zlMTY8;>qS>UAs4)Q%W$C%Aj@~Jz^0%D$oBkE9%(XslGWTdD(k7dYCErEBYx03FmAt zb-1RMN#CZYbD+vEG${sQ$GK8K+9d-_mrHB+S-8v3%yTl$D5&Zx@$qO3Ph*Kiv8~9-)!y%Md?98#@=NmO^KR)stQ2k(W8tDtZ!}TS>&`0AzBpcU$S@-F))EiMV(* z#!Qc+LXlO)4jpH{crimyCRZC&QM~77?*ovN5y4#j)S4G1>?9^Mh53z*+D5&sw)zf^ zAU$`$WaB-er3z$M&8&uIZP|aAaXn0EiSy;gp{E+d`_HoRo5YhO9kB|rQ%^kQ;GRRe#nXzyaJJ;)Q2PM2+SNgg zzv-W5()Kq9Sm&DyyCnO&J*$<2=4X>&W}6; zv~jsER_RCfrF}0UIA{3?IQMkZ9KBT*&X2 zc=+xwfc=s-qYn7N2zT_M0itafFbBeOxPTi^hhTv$uxNdF)_Y~e)r9qSs!mY;fiw%O zmYof#PwcqKC%a*>_8IVSWe|!s@8Eo3{~>i6kXQyd3VgddOyJ_Not~167fb6YF+W0q z_jFjmL!1F$7Om0(({~SL5Ms3?~ri7{_|-BiiXo zk0kwDE3;oV1aOn7fW>^?8VJYTl3iWa61oX+e8Yg+^wKoIm!p4-I}OGOn7id>p5Dnn z`@P98f7oTm0jK2xo=g+iLgoKeOeh4rJksg<)skO!2YL6FtN|QYp7NYDkNmF(90Csb zL*=30f6Fod({lauC;kfPKauz+690Xh{C~D5Le4z9!p8?LxnXEv`%ljJCwu-?Q~!y? z|7|43)%ouMUXJEFa!fI6_~?H+c*j=#pS+U4XQ4QhkoKa1aMq3*S<0gAE@&=?-Av%0m70$Q?y?R|NH!L^#+}}vGuRg zr)PTwH%}LQtOK|AWlZb(?Ru`DvkO!m@m^LnZ{j@vZB`F7L)Y-HK>a@kUR36cOveY9 zRcXC`XMMP+N}Y@Lt4_c@#OO&U8Qg?u4ou^#W%rP$KJ4EQEIOki_U-iDV#J;@U4D>k z9DF-vkJ9dTF*^_>0ns;a##p=W{&{yeW>!R>@@ce9QKpalCuo2B5~pkpa1x0D_G-K7 z*WLJNAP~Sr^=-Uqo!!6wRr`QAs!Q!a&JVCKoBs;xUr_M>=ud3xdAk^fT`jRA@d2M3 M*USy8u6V@%57z!ug#Z8m diff --git a/spec/core/v2/ics-004-packet-semantics/Sketch_Happy_Path.png b/spec/core/v2/ics-004-packet-semantics/Sketch_Happy_Path.png new file mode 100644 index 0000000000000000000000000000000000000000..cfc059c458c0075aae4fc649246d98e032eb1ff3 GIT binary patch literal 164076 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

5kle%Bc@MD^%kgZA`y9Jo`%{>b!RshkmAkWb8 zlDVTaM3ljjYqXYBQggKrJ-YU2$p4|9jgYlQpwQSbk>28DcV2@(O~>Q1J4KxZZ_2P~ ziXw~Wypqe|9p^4lB4JK=yylk%I5(t(KX5job3IZ;Px&}l{3tmjtbtQ9H2Vcn4NG5`pze&gZ4Ph0Us4y^HE-^sK_xe*!e*vwM$D&^?F@T{?aQhnb*|TV1qK< zb7ZQ+6Vn5!r^TMf&k*3DSx-!4pStVo%?_yrDz#e>(?L(r_PcX-@`=w-^_Ex=YCDU^ zohPt2_#eU?-;~Z?1N@pcaSY2ViG)}FcpvmtC5M)#W*t25$LiSKAjdPT`mA9bGMoCc z0}Yed2LJfb=Bu13q+a?0~QIU5#H*LSYA41p5G7E zB3vzTrk8dByCX*+JXN1K2Eb+KQyS6szPPht1?il}d=Fa( zj;6)agMKrY=u?~5j1eR17kuMj!zlP_*fZ6_Q{(77w6_Fbi_ve1#wRo4-!r$BhRE_O zfZ63~DRX;b1TYeM*dp2t_t9KTeNKoyH1T+GsF=;&m9KBuI+o}z#whyj>0LmstHwzk zO}$s}#wYF?h3XLu6AiP`3Wq7HtFu_3H}cNtlMyaCT$0$Tg6u2l^#Ssa&JRN?*E9u-$@_L{O zov$P@)K#vkT7rH-71?AN{+t2#(&w|1#jShv@ukN)j#Wjw`LVVRCbT|Xg!&&MA^wk_ z*cMh&3bYkZIF5Z`E_-gwMC3+YLA^n5_m^qOD}~_4q&1P3ijQ6CCP?bzIHP_)Gu1+F zw|74&USX?F@{?pHQ$0~T-o~GoC!gGYX3D`1C;N4`*jH~sbn#|qoGLAzI! z$ze@u&J$Z-Nz2&x)rMi~b=+b#gpf|rNo-GKAI;LJQmr_>=9x`ln4RTm?|P7pBFOey zmSWYJ^Mh-7WutE8^kHw4$L^CnV`4Xe;Qjx8Rtl<|w(Gphe>>>OjOxT)R;N!OBjxH% zccv>3haSzgik1+5)1cwEN^De}i{N*S7uMrR>(Y<4`lO)3x3F@_*GP~*C$_%Yt6(

JFJ{9GA?jbAJfu)t%c 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 literal 0 HcmV?d00001 From 1542fef3dab8fdd6c9c8497cd3f770b7ee788374 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Fri, 27 Sep 2024 18:13:42 +0200 Subject: [PATCH 18/71] start addressing review --- .../v2/ics-004-packet-semantics/README.md | 43 ++++++++----------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index c17214443..4c83ee7aa 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -163,7 +163,7 @@ The protocol defines the paths `packetCommitmentPath`, `packetRecepitPath` and ` Thus Constant-size commitments to packet data fields are stored under the packet sequence number: ```typescript -function packetCommitmentPath(sourceId: bytes, sequence: uint64): Path { +function packetCommitmentPath(sourceId: bytes, sequence: BigEndianUint64): Path { return "commitments/channels/{sourceId}/sequences/{sequence}" } ``` @@ -173,7 +173,7 @@ 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`. While in the case of a timeout, the destination chain MAY (May?Must?Should?Mmm) write a sentinel timeout value `TIMEOUT_RECEIPT` if the packet is received after the specified timeout. ```typescript -function packetReceiptPath(sourceId: bytes, sequence: uint64): Path { +function packetReceiptPath(sourceId: bytes, sequence: BigEndianUint64): Path { return "receipts/channels/{sourceId}/sequences/{sequence}" } ``` @@ -181,7 +181,7 @@ function packetReceiptPath(sourceId: bytes, sequence: uint64): Path { Packet acknowledgement data are stored under the `packetAcknowledgementPath`: ```typescript -function packetAcknowledgementPath(sourceId: bytes, sequence: uint64): Path { +function packetAcknowledgementPath(sourceId: bytes, sequence: BigEndianUint64): Path { return "acks/channels/{sourceId}/sequences/{sequence}" } ``` @@ -248,10 +248,8 @@ The IBC router contains a mapping from a reserved application port and the suppo ```typescript type IBCRouter struct { - versions: portId -> [Version] callbacks: portId -> [Callback] clients: clientId -> Client - ports: portId -> counterpartyPortId } ``` @@ -312,11 +310,9 @@ function sendPacket( // IMPORTANT: if the one of the onSendPacket fails, the transaction is aborted and the potential state changes that previosly onSendPacket should apply are automatically reverted. abortUnless(success) - // store commitment to the packet data & packet timeout - provableStore.set( - packetCommitmentPath(sourceClientId sequence), - hash(hash(data), timeoutTimestamp) - ) + + // store packet commitment using commit function defined in [packet specification](https://github.com/cosmos/ibc/blob/c7b2e6d5184b5310843719b428923e0c5ee5a026/spec/core/v2/ics-004-packet-semantics/PACKET.md) + commitV2Packet(packet) // increment the sequence. Thus there are monotonically increasing sequences for packet flow for a given clientId privateStore.set(nextSequenceSendPath(sourceClientID), sequence+1) @@ -386,16 +382,14 @@ function recvPacket( hash(packet.data, packet.timeoutTimestamp) )) + multiAck = Acknowledgement {} // Executes Application logic ∀ Payload - for payload in packet.data - // assert source port is destPort's counterparty port identifier - port= router.ports[payload.destPort] - assert(payload.sourcePort == port) + for payload in packet.data cbs = router.callbacks[payload.destPort] ack = cbs.onReceivePacket(payaload.version, payload.encoding, payload.appData) // the onReceivePacket returns the ack but does not write it // IMPORTANT: if the ack is error, then the callback reverts its internal state changes, but the entire tx continues - multiAck=multiAck.add(ack) + multiAck.add(ack) // we must set the receipt so it can be verified on the other side // it's the sentinel success receipt: []byte{0x01} @@ -440,19 +434,16 @@ The IBC handler performs the following steps in order: ```typescript function writeAcknowledgement( packet: Packet, - acknowledgement: [bytes]) { + acknowledgement: Acknowledgement) { // acknowledgement must not be empty abortTransactionUnless(len(acknowledgement) !== 0) // cannot already have written the acknowledgement abortTransactionUnless(provableStore.get(packetAcknowledgementPath(packet.destChannelId, packet.sequence) === null)) - // write the acknowledgement - provableStore.set( - packetAcknowledgementPath(packet.destClientId, packet.sequence), - hash(multiAck) - ) - + // write the acknowledgement using commit function defined in [packet specification](https://github.com/cosmos/ibc/blob/c7b2e6d5184b5310843719b428923e0c5ee5a026/spec/core/v2/ics-004-packet-semantics/PACKET.md) + commitV2Acknowledgment(acknowledgement) + // log that a packet has been acknowledged emitLogEntry("writeAcknowledgement", { sequence: packet.sequence, @@ -460,7 +451,7 @@ function writeAcknowledgement( destClientId: packet.destClientId, timeoutTimestamp: packet.timeoutTimestamp, data: packet.data, - multiAck + acknowledgement }) } ``` @@ -478,7 +469,7 @@ We pass the `relayer` address just as in [Receiving packets](#receiving-packets) ```typescript function acknowledgePacket( packet: OpaquePacket, - acknowledgement: [bytes], + acknowledgement: Acknowledgement, proof: CommitmentProof, proofHeight: Height, relayer: string @@ -511,11 +502,11 @@ function acknowledgePacket( nAck=0 for payload in packet.data cbs = router.callbacks[payload.sourcePort] - success= cbs.OnAcknowledgePacket(packet, acknowledgement[nAck], relayer) + success= cbs.OnAcknowledgePacket(packet, acknowledgement.appAcknowledgement[nAck], relayer) abortUnless(success) nAck++ - channelStore.delete(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) + channelStore.delete(packetCommitmentPath(packet.sourceClientId, packet.sequence)) } ``` From f77105e843f87405f7e05c3c3d73497786fce6e0 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Tue, 1 Oct 2024 13:17:18 +0200 Subject: [PATCH 19/71] addressing review comments --- .../v2/ics-004-packet-semantics/README.md | 271 +++++++++++------- 1 file changed, 160 insertions(+), 111 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 4c83ee7aa..ea365dded 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -13,10 +13,15 @@ modified: 2019-08-25 ## Synopsis -The ICS-04 defines the mechanism to bidirectionally set up clients for each pair of communicating chains with specific Identifiers to establish the ground truth for the secure packet delivery, the packet data strcuture including the multi-data packet, the packet-flow semantics, the mechanisms to route the verification to the underlying clients, and how to route packets to their specific IBC applications. +TODO + +The ICS-04 requires the ICS-02, the ICS-24 and the packet data strcuture including the multi-data packet as defined in [here](packet-data). +It defines the mechanism to register the IBC version 2 protocol channels, pairing up the clients for each pair of communicating chains with specific Identifiers to establish the ground truth for the secure packet delivery, the packet-flow semantics, the mechanisms to route the verification to the underlying clients, and how to route packets to their specific IBC applications. ### Motivation +TODO + 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. > **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 `B` when a particular IBC packet is committed to chain `B`, and require outgoing sends of that packet on chain `A` to escrow an equal amount of the asset on chain `A` until the vouchers are later redeemed back to chain `A` with 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 chain `B` can later be redeemed back to chain `A`. @@ -26,23 +31,31 @@ The IBC version 2 will provide packet delivery between two chains communicating ### Definitions `ConsensusState` is as defined in [ICS 2](../ics-002-client-semantics). -// NOTE what about Port and Capabilities? -`Port` and `authenticateCapability` are as defined in [ICS 5](../ics-005-port-allocation). `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](../ics-024-host-requirements). -`Counterparty` is the data structure responsible for maintaining the counterparty information necessary to establish the ground truth for securing the interchain communication. +The `Channel`, in the IBC version 2 context, represents the chain exit gate. By pairing two exit gates (channels) on distinct chain e.g. `A` and `B`, the ground truth for the packet messages flow's commitments verification get verifiably established and the chains can start to exchange data packets and verify them. Once two channel are paired, the packets can flow in both directions: from `A` to `B` and from `B` to `A`. + +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. + +A Channel is a data structure responsible for maintaining the counterparty information necessary to establish the ground truth for securing the interchain communication and is defined as: ```typescript -interface Counterparty { +interface Channel { clientId: bytes - channelId: bytes + counterpartyChannelId: bytes keyPrefix: CommitmentPrefix } ``` +Where : + +- `clientId` is the client id of the counterparty locally stored on our chain. It can be seen as the pointer to our light client of the counterparty chain where the light client is a light representation of the counterparty chain. +- `counterpartyChannelId` is the counterparty channel identifier that must be used by the packet. +- `keyPrefix` is the key path that the counterparty will use to prove its store packet flow messages. + The `Packet`, `Payload`, `Encoding` and the `Acknowledgement` interfaces are as defined in [packet specification](https://github.com/cosmos/ibc/blob/c7b2e6d5184b5310843719b428923e0c5ee5a026/spec/core/v2/ics-004-packet-semantics/PACKET.md). For convenience, following we recall their structures. A `Packet`, in the interblockchain communication protocol, is a particular interface defined as follows: @@ -102,8 +115,8 @@ The protocol introduces standardized packet receipts that will serve as sentinel ```typescript enum PacketReceipt { - SUCCESSFUL_RECEIPT, - TIMEOUT_RECEIPT, + SUCCESSFUL_RECEIPT = bytes(0x01), + TIMEOUT_RECEIPT = bytes(0x02), } ``` @@ -141,16 +154,15 @@ E.g. If a packet within 3 payloads intended for 3 different application is sent #### Permissioning -// NOTE - here what about capabilities and permissions? - -- 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. +- Channels should be permissioned to the application registered on the local router. Thus only the modules registered on the local router, and so associated with the channel, should be able to send or receive on it. ## Technical Specification ### Dataflow visualisation -TODO The architecture of clients, connections, channels and packets: +TODO + +The architecture of clients, connections, channels and packets: ![Dataflow Visualisation](../../ics-004-channel-and-packet-semantics/dataflow.png) @@ -163,8 +175,8 @@ The protocol defines the paths `packetCommitmentPath`, `packetRecepitPath` and ` Thus Constant-size commitments to packet data fields are stored under the packet sequence number: ```typescript -function packetCommitmentPath(sourceId: bytes, sequence: BigEndianUint64): Path { - return "commitments/channels/{sourceId}/sequences/{sequence}" +function packetCommitmentPath(sourceChannelId: bytes, sequence: BigEndianUint64): Path { + return "commitments/channels/{sourceChannelId}/sequences/{sequence}" } ``` @@ -173,8 +185,8 @@ 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`. While in the case of a timeout, the destination chain MAY (May?Must?Should?Mmm) write a sentinel timeout value `TIMEOUT_RECEIPT` if the packet is received after the specified timeout. ```typescript -function packetReceiptPath(sourceId: bytes, sequence: BigEndianUint64): Path { - return "receipts/channels/{sourceId}/sequences/{sequence}" +function packetReceiptPath(sourceChannelId: bytes, sequence: BigEndianUint64): Path { + return "receipts/channels/{sourceChannelId}/sequences/{sequence}" } ``` @@ -182,74 +194,98 @@ Packet acknowledgement data are stored under the `packetAcknowledgementPath`: ```typescript function packetAcknowledgementPath(sourceId: bytes, sequence: BigEndianUint64): Path { - return "acks/channels/{sourceId}/sequences/{sequence}" + return "acks/channels/{sourceChannelId}/sequences/{sequence}" } ``` +Additionally, the protocol suggests the privateStore paths for the `nextSequenceSend` and `channelPath` variable. Private paths are meant to be used locally in the chain. Thus their specification can be arbitrary changed by implementors at their will. + - The `nextSequenceSend` is stored separately in the privateStore and tracks the sequence number for the next packet to be sent for a given source clientId. ```typescript -function nextSequenceSendPath(sourceID: bytes): Path { - return "nextSequenceSend/clients/{sourceID} +function nextSequenceSendPath(sourceChannelID: bytes): Path { + return "nextSequenceSend/{sourceChannelID}" +} +``` + +- The `channelPath` is stored separately in the privateStore and tracks the channels paired with the other chains. + +```typescript +function channelPath(channelId: Identifier): Path { + return "channels/{channelId}" } ``` ### Sub-protocols -> Note: If the host state machine is utilising object capability authentication (see [ICS 005](../ics-005-port-allocation)), all functions utilising ports take an additional capability parameter. +The ICS-04 defines the channel registration and the application registration procedures and the packet handlers flow. + +In oder to send packets using IBC version 2, chain `A` and chain `B` are required to individually register the exact `Channel` pair that will be used by the two communicating chains. Thus, both chain `A` and chain `B` MUST execute: + +1. The `channel registration` procedure. +2. The `application registration` procedure, where the application used by the local chain are registered on the local chain router. + +The bidirectional packet stream can be only started after the channel registration and the application registration have been correctly executed individually by both chains. Otherwise, any sent packet cannot be received and will timeout. -#### Counterparty Idenfitifcation and Registration +#### Channel pairing and counterparty idenfitifcation -A client MUST have the ability to idenfity its counterparty. With a client, we can prove any key/value path on the counterparty. However, without knowing which identifier the counterparty uses when it sends messages to us, we cannot differentiate between messages sent from the counterparty to our chain vs messages sent from the counterparty with other chains. Most implementations will not be able to store the ICS-24 paths directly as a key in the global namespace, but will instead write to a reserved, prefixed keyspace so as not to conflict with other application state writes. Thus the counteparty information we must have includes both its identifier for our chain as well as the key prefix under which it will write the provable ICS-24 paths. +Each IBC chain MUST have the ability to idenfity its counterparty. With a client, we can prove any key/value path on the counterparty. However, without knowing which identifier the counterparty uses when it sends messages to us, we cannot differentiate between messages sent from the counterparty to our chain vs messages sent from the counterparty with other chains. Most implementations will not be able to store the ICS-24 paths directly as a key in the global namespace, but will instead write to a reserved, prefixed keyspace so as not to conflict with other application state writes. -Thus, IBC version 2 introduces a new message `RegisterCounterparty` that will associate the counterparty client of our chain with our client of the counterparty. Thus, if the `RegisterCounterparty` message is submitted to both sides correctly. Then both sides have mirrored pairs that can be treated as channel identifiers. Assuming they are correct, the client on each side is unique and provides an authenticated stream of packet data between the two chains. If the `RegisterCounterparty` message submits the wrong clientID, this can lead to invalid behaviour; but this is equivalent to a relayer submitting an invalid client in place of a correct client for the desired chain. In the simplest case, we can rely on out-of-band social consensus to only send on valid pairs that represent a connection between the desired chains of the user; just as we currently rely on out-of-band social consensus that a given clientID and channel built on top of it is the valid, canonical identifier of our desired chain. +To provide the chains a mechanism for the mutual and verifiable identification, the IBC version 2 defines the channel registration procedure to store the counterparty information including both its identifier for our chain and as well as the key prefix under which it will write the provable ICS-24 paths. + +Thus, IBC version 2 introduces a new message `RegisterChannel` that will associate the counterparty client of our chain with our client of the counterparty. Thus, if the `RegisterChannel` message is submitted to both sides correctly. Then both sides have mirrored pairs that can be treated as channel identifiers. Assuming they are correct, the client on each side is unique and provides an authenticated stream of packet data between the two chains. If the `RegisterChannel` message submits the wrong clientID, this can lead to invalid behaviour; but this is equivalent to a relayer submitting an invalid client in place of a correct client for the desired chain. In the simplest case, we can rely on out-of-band social consensus to only send on valid pairs that represent a connection between the desired chains of the user; just as we currently rely on out-of-band social consensus that a given clientID and channel built on top of it is the valid, canonical identifier of our desired chain. ```typescript -function RegisterCounterparty( - clientID: bytes, // this will be our own client identifier representing our channel to desired chain - counterpartyClientId: bytes, // this is the counterparty's identifier of our chain +function RegisterChannel( + clientID: bytes, // In a pure v2 connection this field will represent our actual clientId of the counterparty chain + counterpartyChannelId: bytes, // this is the counterparty's channel identifier counterpartyKeyPrefix: CommitmentPrefix, authentication: data, // implementation-specific authentication data ) { assert(verify(authentication)) - counterparty = Counterparty{ + channel = Channel{ clientId: clientId, - channelId: counterpartyClientId, + channelId: counterpartyChannelId, keyPrefix: counterpartyKeyPrefix } - privateStore.set(Map) + privateStore.set(channelPath(counterpartyChannelId), channel) } ``` -The `RegisterCounterparty` method allows for authentication data that implementations may verify before storing the provided counterparty identifier. The strongest authentication possible is to have a valid clientState and consensus state of our chain in the authentication along with a proof it was stored at the claimed counterparty identifier. -A simpler but weaker authentication would simply be to check that the `RegisterCounterparty` message is sent by the same relayer that initialized the client. This would make the client parameters completely initialized by the relayer. Thus, users must verify that the client is pointing to the correct chain and that the counterparty identifier is correct as well before using the lite channel identified by the provided client-client pair. +The `RegisterChannel` method allows for authentication data that implementations may verify before storing the provided counterparty identifier. The strongest authentication possible is to have a valid clientState and consensus state of our chain in the authentication along with a proof it was stored at the claimed counterparty identifier. +A simpler but weaker authentication would simply be to check that the `RegisterChannel` message is sent by the same relayer that initialized the client. This would make the client parameters completely initialized by the relayer. Thus, users must verify that the client is pointing to the correct chain and that the counterparty identifier is correct as well before using the lite channel identified by the provided client-client pair. ```typescript -// getCounterparty retrieves the stored counterparty identifier -// given the channelIdentifier on our chain once it is provided -function getCounterparty(clientId: bytes): Counterparty { - return privateStore.get(clientId) // retrieves from map the counterparty +// getChannel retrieves the stored channel given the counterparty channel-id. +function getChannel(counterpartyChannelId: bytes): Channel { + return privateStore.get(channelPath(counterpartyChannelId)) } ``` Thus, once two chains have set up clients for each other with specific Identifiers, they can send IBC packets using the packet interface defined before. -Since the packets are addressed **directly** with the underlying light clients, there are **no** more handshakes necessary. Instead the packet sender must be capable of providing the correct pair. +Since the packets are addressed **directly** with the underlying light clients, there are **no** more handshakes necessary. Instead the packet sender must be capable of providing the correct pair. Sending a packet with the wrong source client is equivalent to sending a packet with the wrong source channel. Sending a packet on a channel with the wrong provided counterparty is a new source of errors, however this is added to the burden of out-of-band social consensus. If the client and counterparty identifiers are setup correctly, then the correctness and soundness properties of IBC holds. IBC packet flow is guaranteed to succeed. If a user sends a packet with the wrong destination channel, then as we will see it will be impossible for the intended destination to correctly verify the packet thus, the packet will simply time out. +Once this is done, then the application registering procedure is required, + +The channel registration + #### Registering IBC applications on the router +The application registering procedure consist in storing the available callbacks associated within a portID (or application) and the linking it to the underlying client. + The IBC router contains a mapping from a reserved application port and the supported versions of that application as well as a mapping from channelIdentifiers to channels. ```typescript type IBCRouter struct { callbacks: portId -> [Callback] - clients: clientId -> Client + clients: channelId -> Client } ``` @@ -287,13 +323,13 @@ Note that the full packet is not stored in the state of the chain - merely a sho ```typescript function sendPacket( - sourceClientId: bytes, - destClientId: bytes, + sourceChannelId: bytes, + destChannelId: bytes, timeoutTimestamp: uint64, packet: []byte ): uint64 { - // in this specification, the source channel is the clientId - client = router.clients[packet.sourceClientId] + + client = router.clients[packet.sourceChannelId] assert(client !== null) // disallow packets with a zero timeoutHeight and timeoutTimestamp @@ -301,28 +337,30 @@ function sendPacket( assert(currentTimestamp()> timeoutTimestamp) // Mmm // if the sequence doesn't already exist, this call initializes the sequence to 0 - sequence = privateStore.get(nextSequenceSendPath(sourceClientId)) + sequence = privateStore.get(nextSequenceSendPath(sourceChannelId)) // Executes Application logic ∀ Payload for payload in packet.data cbs = router.callbacks[payload.sourcePort] - success = cbs.onSendPacket(version, encoding, appData) + success = cbs.onSendPacket(packet.sourceId,payload) // IMPORTANT: if the one of the onSendPacket fails, the transaction is aborted and the potential state changes that previosly onSendPacket should apply are automatically reverted. abortUnless(success) // store packet commitment using commit function defined in [packet specification](https://github.com/cosmos/ibc/blob/c7b2e6d5184b5310843719b428923e0c5ee5a026/spec/core/v2/ics-004-packet-semantics/PACKET.md) - commitV2Packet(packet) + commit=commitV2Packet(packet) + provableStore.set(packetCommitmentPath(packet.sourcelId, sequence),commit) + // increment the sequence. Thus there are monotonically increasing sequences for packet flow for a given clientId - privateStore.set(nextSequenceSendPath(sourceClientID), sequence+1) + privateStore.set(nextSequenceSendPath(sourceChannelId), sequence+1) // log that a packet can be safely sent // NOTE introducing sourceID and destID can be useful for monitoring - e.g. if one wants to monitor all packets between sourceID and destID emitting this in the event would simplify his life. emitLogEntry("sendPacket", { - sourceID: sourceClientId, - destID: destClientId, + sourceID: sourceChannelId, + destID: destChannelId, sequence: sequence, data: data, timeoutTimestamp: timeoutTimestamp @@ -331,6 +369,12 @@ function sendPacket( } ``` +TODO: + +- Preconditions +- Error Conditions +- Post Conditions + #### Receiving packets The `recvPacket` function is called by the IBC handler in order to receive an IBC packet sent on the corresponding client on the counterparty chain. @@ -355,38 +399,42 @@ function recvPacket( relayer: string): Packet { // in this specification, the destination channel is the clientId - client = router.clients[packet.destClientId] + client = router.clients[packet.destChannelId] assert(client !== null) // assert that our counterparty clientId is the packet.sourceClientId - counterparty = getCounterparty(packet.destClientId) - assert(packet.sourceClientId == counterparty.clientId) + channel = getChannel(packet.destChannelId) + assert(packet.sourceId == channel.clientId) // verify timeout assert(packet.timeoutTimestamp === 0 || currentTimestamp() < packet.timeoutTimestamp) // verify the packet receipt for this packet does not exist already - packetReceipt = provableStore.get(packetReceiptPath(packet.sourceClientId, packet.sequence)) + packetReceipt = provableStore.get(packetReceiptPath(packet.destId, packet.sequence)) abortUnless(packetReceipt === null) - // verify commitment - packetPath = packetCommitmentPath(packet.sourceClientId, packet.sequence) + //////// verify commitment + + // 1. retrieve keys + packetPath = packetCommitmentPath(packet.destId, packet.sequence) merklePath = applyPrefix(counterparty.keyPrefix, packetPath) - // DISCUSSION NEEDED: Should we have an in-protocol notion of Prefixing the path - // or should we make this a concern of the client's VerifyMembership - // proofPath = applyPrefix(client.counterpartyChannelStoreIdentifier, packetPath) + + // 2. reconstruct commit value based on the passed-in packet + commit = commitV2Packet(packet) + + // 3. call client verify memership assert(client.verifyMembership( + client.clientState proofHeight, proof, merklePath, - hash(packet.data, packet.timeoutTimestamp) - )) + commit)) multiAck = Acknowledgement {} // Executes Application logic ∀ Payload for payload in packet.data cbs = router.callbacks[payload.destPort] - ack = cbs.onReceivePacket(payaload.version, payload.encoding, payload.appData) + ack = cbs.onReceivePacket(payload) // the onReceivePacket returns the ack but does not write it // IMPORTANT: if the ack is error, then the callback reverts its internal state changes, but the entire tx continues multiAck.add(ack) @@ -394,7 +442,7 @@ function recvPacket( // we must set the receipt so it can be verified on the other side // it's the sentinel success receipt: []byte{0x01} provableStore.set( - packetReceiptPath(packet.sourceChannelId, packet.sequence), + packetReceiptPath(packet.destId, packet.sequence), SUCCESSFUL_RECEIPT ) @@ -408,14 +456,20 @@ function recvPacket( data: packet.data timeoutTimestamp: packet.timeoutTimestamp, sequence: packet.sequence, - sourceClientId: packet.sourceClientId, - destClientId: packet.destClientId, + sourceId: packet.sourceId, + destId: packet.destId, relayer: relayer }) } ``` +TODO: + +- Preconditions +- Error Conditions +- Post Conditions + #### Writing acknowledgements NOTE: Currently only process synchronous acks. @@ -439,11 +493,14 @@ function writeAcknowledgement( abortTransactionUnless(len(acknowledgement) !== 0) // cannot already have written the acknowledgement - abortTransactionUnless(provableStore.get(packetAcknowledgementPath(packet.destChannelId, packet.sequence) === null)) + abortTransactionUnless(provableStore.get(packetAcknowledgementPath(packet.destId, packet.sequence) === null)) // write the acknowledgement using commit function defined in [packet specification](https://github.com/cosmos/ibc/blob/c7b2e6d5184b5310843719b428923e0c5ee5a026/spec/core/v2/ics-004-packet-semantics/PACKET.md) - commitV2Acknowledgment(acknowledgement) + commit=commitV2Acknowledgment(acknowledgement) + provableStore.set( + packetAcknowledgementPath(packet.destId, packet.sequence),commit) + // log that a packet has been acknowledged emitLogEntry("writeAcknowledgement", { sequence: packet.sequence, @@ -456,6 +513,12 @@ function writeAcknowledgement( } ``` +TODO: + +- Preconditions +- Error Conditions +- Post Conditions + #### Processing acknowledgements The `acknowledgePacket` function is called by the IBC handler to process the acknowledgement of a packet previously sent by the source chain. @@ -475,41 +538,45 @@ function acknowledgePacket( relayer: string ) { // in this specification, the source channel is the clientId - client = router.clients[packet.sourceClientId] + client = router.clients[packet.sourceId] assert(client !== null) // assert dest channel is sourceChannel's counterparty channel identifier - counterparty = getCounterparty(packet.destClientId) - assert(packet.sourceClientId == counterparty.clientId) + channel = getChannel(packet.destId) + assert(packet.sourceId == channel.clientId) - // assert dest port is sourcePort's counterparty port identifier - assert(packet.destPort == ports[packet.sourcePort]) - // verify we sent the packet and haven't cleared it out yet - assert(provableStore.get(packetCommitmentPath(packet.sourceClientId, packet.sequence)) - === hash(hash(packet.data), packet.timeoutTimestamp)) + + assert(provableStore.get(packetCommitmentPath(packet.sourceId, packet.sequence)) === commitV2Packet(packet)) - ackPath = packetAcknowledgementPath(packet.destClientId, packet.sequence) + ackPath = packetAcknowledgementPath(packet.destId, packet.sequence) merklePath = applyPrefix(counterparty.keyPrefix, ackPath) assert(client.verifyMembership( + client.clientState proofHeight, proof, merklePath, - hash(acknowledgement) + acknowledgement )) // Executes Application logic ∀ Payload nAck=0 for payload in packet.data cbs = router.callbacks[payload.sourcePort] - success= cbs.OnAcknowledgePacket(packet, acknowledgement.appAcknowledgement[nAck], relayer) + success= cbs.OnAcknowledgePacket(payload, acknowledgement.appAcknowledgement[nAck], relayer) abortUnless(success) nAck++ - channelStore.delete(packetCommitmentPath(packet.sourceClientId, packet.sequence)) + channelStore.delete(packetCommitmentPath(packet.sourceId, packet.sequence)) } ``` +TODO: + +- Preconditions +- Error Conditions +- Post Conditions + ##### Acknowledgement Envelope The acknowledgement returned from the remote chain is defined as arbitrary bytes in the IBC protocol. This data @@ -559,19 +626,19 @@ function timeoutPacket( proofHeight: Height, relayer: string ) { - client = router.clients[packet.sourceClientId] + client = router.clients[packet.sourceId] assert(client !== null) // assert dest channel is sourceChannel's counterparty channel identifier - counterparty = getCounterparty(packet.destClientId) - assert(packet.sourceClientId == counterparty.channelId) + channel = getChannel(packet.destId) + assert(packet.sourceId == channel.channelId) // assert dest port is sourcePort's counterparty port identifier assert(packet.destPort == ports[packet.sourcePort]) // verify we sent the packet and haven't cleared it out yet - assert(provableStore.get(packetCommitmentPath(packet.sourceClientId, packet.sequence)) - === hash(hash(packet.data), packet.timeoutTimestamp)) + assert(provableStore.get(packetCommitmentPath(packet.sourceId, packet.sequence)) + === commitV2Packet(packet)) // get the timestamp from the final consensus state in the channel path var proofTimestamp @@ -581,23 +648,30 @@ function timeoutPacket( // check that timeout height or timeout timestamp has passed on the other end asert(packet.timeoutTimestamp > 0 && proofTimestamp >= packet.timeoutTimestamp) - receiptPath = packetReceiptPath(packet.destClientId, packet.sequence) + receiptPath = packetReceiptPath(packet.destId, packet.sequence) merklePath = applyPrefix(counterparty.keyPrefix, receiptPath) assert(client.verifyNonMembership( - proofHeight + client.clientState, + proofHeight, proof, merklePath )) for payload in packet.data cbs = router.callbacks[payload.sourcePort] - success=cbs.OnTimeoutPacket(packet, relayer) + success=cbs.OnTimeoutPacket(payload, relayer) abortUnless(success) - channelStore.delete(packetCommitmentPath(packet.sourceChannelId, packet.sequence)) + channelStore.delete(packetCommitmentPath(packet.sourceId, packet.sequence)) } ``` +TODO: + +- Preconditions +- Error Conditions +- Post Conditions + ##### Cleaning up state Packets must be acknowledged in order to be cleaned-up. @@ -660,28 +734,3 @@ Mar 28, 2023 - Add `writeChannel` function to write channel end after executing ## Copyright All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0). - -/* NOTE What to do with this? - -#### Identifier validation - -Channels are stored under a unique `(portIdentifier, channelIdentifier)` prefix. -The validation function `validatePortIdentifier` MAY be provided. - -```typescript -type validateChannelIdentifier = (portIdentifier: Identifier, channelIdentifier: Identifier) => boolean -``` - -If not provided, the default `validateChannelIdentifier` function will always return `true`. - -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: - -```typescript -type generateIdentifier = () -> Identifier -``` - -*/ From 6145e865c602a0c0437d13c4209fbb7383ad9d2f Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Tue, 1 Oct 2024 13:28:48 +0200 Subject: [PATCH 20/71] fixes --- .../v2/ics-004-packet-semantics/README.md | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index ea365dded..053dc58ed 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -62,16 +62,16 @@ A `Packet`, in the interblockchain communication protocol, is a particular inter ```typescript interface Packet { - sourceIdentifier: bytes, - destIdentifier: bytes, + sourceId: bytes, + destId: bytes, sequence: uint64 timeout: uint64, data: [Payload] } ``` -- The `sourceIdentifier` derived from the `clientIdentifier` will tell the IBC router which chain a received packet came from. -- The `destIdentifier` derived from the `clientIdentifier` will tell the IBC router which chain to send the packets to. +- The `sourceId` derived from the `clientIdentifier` will tell the IBC router which chain a received packet came from. +- The `destId` derived from the `clientIdentifier` will tell the IBC router which chain to send the packets to. - The `sequence` number corresponds to the order of sends and receives, where a packet with an earlier sequence number MUST be sent and received (NOTE: not sure about received) before a packet with a later sequence number. - The `timeout` indicates the UNIX timestamp in seconds and is encoded in LittleEndian. It must be passed on the destination chain and once elapsed, will no longer allow the packet processing, and will instead generate a time-out. @@ -175,8 +175,8 @@ The protocol defines the paths `packetCommitmentPath`, `packetRecepitPath` and ` Thus Constant-size commitments to packet data fields are stored under the packet sequence number: ```typescript -function packetCommitmentPath(sourceChannelId: bytes, sequence: BigEndianUint64): Path { - return "commitments/channels/{sourceChannelId}/sequences/{sequence}" +function packetCommitmentPath(sourceId: bytes, sequence: BigEndianUint64): Path { + return "commitments/channels/{sourceId}/sequences/{sequence}" } ``` @@ -185,8 +185,8 @@ 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`. While in the case of a timeout, the destination chain MAY (May?Must?Should?Mmm) write a sentinel timeout value `TIMEOUT_RECEIPT` if the packet is received after the specified timeout. ```typescript -function packetReceiptPath(sourceChannelId: bytes, sequence: BigEndianUint64): Path { - return "receipts/channels/{sourceChannelId}/sequences/{sequence}" +function packetReceiptPath(sourceId: bytes, sequence: BigEndianUint64): Path { + return "receipts/channels/{sourceId}/sequences/{sequence}" } ``` @@ -194,7 +194,7 @@ Packet acknowledgement data are stored under the `packetAcknowledgementPath`: ```typescript function packetAcknowledgementPath(sourceId: bytes, sequence: BigEndianUint64): Path { - return "acks/channels/{sourceChannelId}/sequences/{sequence}" + return "acks/channels/{sourceId}/sequences/{sequence}" } ``` @@ -203,8 +203,8 @@ Additionally, the protocol suggests the privateStore paths for the `nextSequence - The `nextSequenceSend` is stored separately in the privateStore and tracks the sequence number for the next packet to be sent for a given source clientId. ```typescript -function nextSequenceSendPath(sourceChannelID: bytes): Path { - return "nextSequenceSend/{sourceChannelID}" +function nextSequenceSendPath(sourceId: bytes): Path { + return "nextSequenceSend/{sourceId}" } ``` From c99dedf5d83236822bb08159f4f36c8788d75bb6 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Wed, 2 Oct 2024 13:21:39 +0200 Subject: [PATCH 21/71] client-creation-registration --- .../v2/ics-004-packet-semantics/README.md | 182 +++++++++++++----- 1 file changed, 134 insertions(+), 48 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 053dc58ed..50b1e082e 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -16,7 +16,7 @@ modified: 2019-08-25 TODO The ICS-04 requires the ICS-02, the ICS-24 and the packet data strcuture including the multi-data packet as defined in [here](packet-data). -It defines the mechanism to register the IBC version 2 protocol channels, pairing up the clients for each pair of communicating chains with specific Identifiers to establish the ground truth for the secure packet delivery, the packet-flow semantics, the mechanisms to route the verification to the underlying clients, and how to route packets to their specific IBC applications. +It defines the mechanism to create the IBC version 2 protocol channels, to pair two channels on different chains linking them up with the underlying clients to establish the root of trust for the secure packet delivery, the packet-flow semantics, the mechanisms to route the verification to the underlying clients, and how to route packets to their specific IBC applications. ### Motivation @@ -36,11 +36,7 @@ The IBC version 2 will provide packet delivery between two chains communicating `Identifier`, `get`, `set`, `delete`, `getCurrentHeight`, and module-system related primitives are as defined in [ICS 24](../ics-024-host-requirements). -The `Channel`, in the IBC version 2 context, represents the chain exit gate. By pairing two exit gates (channels) on distinct chain e.g. `A` and `B`, the ground truth for the packet messages flow's commitments verification get verifiably established and the chains can start to exchange data packets and verify them. Once two channel are paired, the packets can flow in both directions: from `A` to `B` and from `B` to `A`. - -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. - -A Channel is a data structure responsible for maintaining the counterparty information necessary to establish the ground truth for securing the interchain communication and is defined as: +A `channel` is a pipeline for exactly-once packet delivery between specific modules which are properly registered on separate blockchains, which has at least one end capable of sending packets and one end capable of receiving packets. 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. A `channel` is defined as a data structure responsible for maintaining the counterparty information necessary to establish the root of trust for securing the interchain communication: ```typescript interface Channel { @@ -52,8 +48,8 @@ interface Channel { Where : -- `clientId` is the client id of the counterparty locally stored on our chain. It can be seen as the pointer to our light client of the counterparty chain where the light client is a light representation of the counterparty chain. -- `counterpartyChannelId` is the counterparty channel identifier that must be used by the packet. +- `clientId` is the client id of the counterparty used by our chain. +- `counterpartyChannelId` is the counterparty channel identifier that MUST be used by the packet. - `keyPrefix` is the key path that the counterparty will use to prove its store packet flow messages. The `Packet`, `Payload`, `Encoding` and the `Acknowledgement` interfaces are as defined in [packet specification](https://github.com/cosmos/ibc/blob/c7b2e6d5184b5310843719b428923e0c5ee5a026/spec/core/v2/ics-004-packet-semantics/PACKET.md). For convenience, following we recall their structures. @@ -134,6 +130,17 @@ An application may not need to return an acknowledgment. In this case, it may re E.g. If a packet within 3 payloads intended for 3 different application is sent out, the expectation is that each of the payload is acted upon in the same order as it has been placed in the packet. Likewise, the array of appAcknowledgement is expected to be populated within the same order. +- The `IBCRouter` contains a mapping from the application port and the supported callbacks and as well as a mapping from channelId to the underlying client. + +```typescript +type IBCRouter struct { + callbacks: portId -> [Callback] + clients: channelId -> Client +} +``` + +The IBCRouter struct MUST be set by the application modules during the module setup to carry out the application registration procedure. + ### Desired Properties #### Efficiency @@ -198,7 +205,7 @@ function packetAcknowledgementPath(sourceId: bytes, sequence: BigEndianUint64): } ``` -Additionally, the protocol suggests the privateStore paths for the `nextSequenceSend` and `channelPath` variable. Private paths are meant to be used locally in the chain. Thus their specification can be arbitrary changed by implementors at their will. +Additionally, the protocol suggests the privateStore paths for the `nextSequenceSend` , `channelPath` and `channelCreator` variable. Private paths are meant to be used locally in the chain. Thus their specification can be arbitrary changed by implementors at their will. - The `nextSequenceSend` is stored separately in the privateStore and tracks the sequence number for the next packet to be sent for a given source clientId. @@ -216,41 +223,77 @@ function channelPath(channelId: Identifier): Path { } ``` +- The `creatorPath` is stored separately in the privateStore and tracks the channels creator address. + +```typescript +function creatorPath(channelId: Identifier, creator: address): Path { + return "channels/{channelId}/creator/{creator}" +} +``` + ### Sub-protocols -The ICS-04 defines the channel registration and the application registration procedures and the packet handlers flow. +In order to start sending packets using IBC version 2, chain `A` and chain `B` MUST execute the following set of procedures: -In oder to send packets using IBC version 2, chain `A` and chain `B` are required to individually register the exact `Channel` pair that will be used by the two communicating chains. Thus, both chain `A` and chain `B` MUST execute: +- Client creation: chain `A` MUST create the `B` light client; `B` MUST create the `A` light client. +- Application registration: during the application module setup the application MUST be registered on the local IBC router. +- Channel creation: both chain `A` and chain `B` MUST create local channels. +- Channel registration: both chain MUST register their counterpartyId in the channel previously created. -1. The `channel registration` procedure. -2. The `application registration` procedure, where the application used by the local chain are registered on the local chain router. +While the `createClient` procedure is defined in [ICS2](../ics-002-client-semantics/README.md) and the application registration MUST be defined in the setup section of the application module and being executed on the module startup, the ICS-04 defines the channel creation and registration procedure. -The bidirectional packet stream can be only started after the channel registration and the application registration have been correctly executed individually by both chains. Otherwise, any sent packet cannot be received and will timeout. +#### Channel creation -#### Channel pairing and counterparty idenfitifcation +```typescript +function createChannel( + clientId: bytes, + counterpartyKeyPrefix: CommitmentPrefix) (channelId: bytes){ + + channelId = generateIdentifier(clientId,counterpartyKeyPrefix) + abortTransactionUnless(validateChannelIdentifier(channelId)) + abortTransactionUnless(privateStore.get(channelPath(channelId)) === null) + + channel = Channel{ + clientId: clientId, + counterpartyChannelId: "", // This field it must be a blank field during the creation as it may be not known at the creation time. + keyPrefix: counterpartyKeyPrefix + } + + privateStore.set(channelPath(channelId), channel) + privateStore.set(creatorPath(channelId,msg.signer()), msg.signer()) + return channelId +} +``` + +Note that the `createClient` message can be coupled and executed in conjunction with a `createChannel` message in a single multiMsgTx. The execution of these messages on both chains are prerequisites for the channel registration procedure described below. + +#### Channel registration and counterparty idenfitifcation Each IBC chain MUST have the ability to idenfity its counterparty. With a client, we can prove any key/value path on the counterparty. However, without knowing which identifier the counterparty uses when it sends messages to us, we cannot differentiate between messages sent from the counterparty to our chain vs messages sent from the counterparty with other chains. Most implementations will not be able to store the ICS-24 paths directly as a key in the global namespace, but will instead write to a reserved, prefixed keyspace so as not to conflict with other application state writes. -To provide the chains a mechanism for the mutual and verifiable identification, the IBC version 2 defines the channel registration procedure to store the counterparty information including both its identifier for our chain and as well as the key prefix under which it will write the provable ICS-24 paths. +To provide the chains a mechanism for the mutual and verifiable identification, the IBC version 2 defines the channel registration procedure to complement the store of the counterparty information in the channel that will include both its identifier for our chain and as well as the key prefix under which it will write the provable ICS-24 paths. -Thus, IBC version 2 introduces a new message `RegisterChannel` that will associate the counterparty client of our chain with our client of the counterparty. Thus, if the `RegisterChannel` message is submitted to both sides correctly. Then both sides have mirrored pairs that can be treated as channel identifiers. Assuming they are correct, the client on each side is unique and provides an authenticated stream of packet data between the two chains. If the `RegisterChannel` message submits the wrong clientID, this can lead to invalid behaviour; but this is equivalent to a relayer submitting an invalid client in place of a correct client for the desired chain. In the simplest case, we can rely on out-of-band social consensus to only send on valid pairs that represent a connection between the desired chains of the user; just as we currently rely on out-of-band social consensus that a given clientID and channel built on top of it is the valid, canonical identifier of our desired chain. +Thus, IBC version 2 introduces a new message `RegisterChannel` that will store the counterpartyChannelId into the local channel structure. Thus, if the `RegisterChannel` message is submitted to both sides correctly, then both sides have mirrored pairs that can be treated as channel identifiers. Assuming they are correct, the underlying client on each side is unique and provides an authenticated stream of packet data between the two chains. If the `RegisterChannel` message submits the wrong counterpartyChannelId, this can lead to invalid behaviour; but this is equivalent to a relayer submitting an invalid client in place of a correct client for the desired chain. In the simplest case, we can rely on out-of-band social consensus to only send on valid pairs that represent a connection between the desired chains of the user; just as we currently rely on out-of-band social consensus that a given clientID and channel built on top of it is the valid, canonical identifier of our desired chain. ```typescript function RegisterChannel( - clientID: bytes, // In a pure v2 connection this field will represent our actual clientId of the counterparty chain - counterpartyChannelId: bytes, // this is the counterparty's channel identifier + clientID: bytes, // The clientId of the counterparty chain + counterpartyChannelId: bytes, // the counterparty's channel identifier counterpartyKeyPrefix: CommitmentPrefix, authentication: data, // implementation-specific authentication data ) { assert(verify(authentication)) - channel = Channel{ - clientId: clientId, - channelId: counterpartyChannelId, - keyPrefix: counterpartyKeyPrefix - } + channelId=generateIdentifier(clientId,counterpartyKeyPrefix) + abortTransactionUnless(validatedIdentifier(channelId)) + channel=getChannel(channelId)) + abortTransactionUnless(channel !== null) + abortTransactionUnless(msg.signer()===getCreator(channelId,msg.signer())) + + channel.counterpartyChannelId=counterpartyChannelId privateStore.set(channelPath(counterpartyChannelId), channel) + } ``` @@ -259,38 +302,28 @@ A simpler but weaker authentication would simply be to check that the `RegisterC ```typescript // getChannel retrieves the stored channel given the counterparty channel-id. -function getChannel(counterpartyChannelId: bytes): Channel { - return privateStore.get(channelPath(counterpartyChannelId)) +function getChannel(channelId: bytes): Channel { + return privateStore.get(channelPath(channelId)) } ``` -Thus, once two chains have set up clients for each other with specific Identifiers, they can send IBC packets using the packet interface defined before. - -Since the packets are addressed **directly** with the underlying light clients, there are **no** more handshakes necessary. Instead the packet sender must be capable of providing the correct pair. - -Sending a packet with the wrong source client is equivalent to sending a packet with the wrong source channel. Sending a packet on a channel with the wrong provided counterparty is a new source of errors, however this is added to the burden of out-of-band social consensus. - -If the client and counterparty identifiers are setup correctly, then the correctness and soundness properties of IBC holds. IBC packet flow is guaranteed to succeed. If a user sends a packet with the wrong destination channel, then as we will see it will be impossible for the intended destination to correctly verify the packet thus, the packet will simply time out. - -Once this is done, then the application registering procedure is required, - -The channel registration - -#### Registering IBC applications on the router - -The application registering procedure consist in storing the available callbacks associated within a portID (or application) and the linking it to the underlying client. - -The IBC router contains a mapping from a reserved application port and the supported versions of that application as well as a mapping from channelIdentifiers to channels. - ```typescript -type IBCRouter struct { - callbacks: portId -> [Callback] - clients: channelId -> Client +// getChannel retrieves the stored channel given the counterparty channel-id. +function getCreator(channelId: bytes, msgSigner: address): address { + return privateStore.get(creatorPath(channelId,msgSigner)) } ``` +Thus, once two chains have set up clients, created channel and registered channels for each other with specific Identifiers, they can send IBC packets using the packet interface defined before and the packet handlers that the ICS-04 defines below. + +The packets will be addressed **directly** with the channels that have links to the underlying light clients. Thus there are **no** more handshakes necessary. Instead the packet sender must be capable of providing the correct pair. + +If the setup has been executed correctly, then the correctness and soundness properties of IBC holds. IBC packet flow is guaranteed to succeed. If a user sends a packet with the wrong destination channel, then as we will see it will be impossible for the intended destination to correctly verify the packet thus, the packet will simply time out. + #### Packet Flow through the Router & handling +The bidirectional packet stream can be only started after all the procedure above have been succesfully executed, individually, by both chains. Otherwise, any sent packet cannot be received and will timeout. + TODO : Adapt to new flow ![Packet State Machine](../../ics-004-channel-and-packet-semantics/packet-state-machine.png) @@ -306,7 +339,7 @@ The following sequence of steps must occur for a packet to be sent from module * ##### Sending packets -The `sendPacket` function is called by the IBC handler when an IBC packet is submitted to the newtwork in order to send *data* in the form of an IBC packet. ∀ `Payload` included in the `packet.data`, which may refer to a different application, the application specific callbacks are retrieved from the the IBC router and the `onSendPacket` is the then triggered on the specified application. The `onSendPacket` executes the application logic. Once all payloads contained in the `packet.data` have been acted upon, the packet commitment is generated and the sequence number specific to the sourceClientId is incremented. +The `sendPacket` function is called by the IBC handler when an IBC packet is submitted to the newtwork in order to send *data* in the form of an IBC packet. ∀ `Payload` included in the `packet.data`, which may refer to a different application, the application specific callbacks are retrieved from the the IBC router and the `onSendPacket` is the then triggered on the specified application. The `onSendPacket` executes the application logic. Once all payloads contained in the `packet.data` have been acted upon, the packet commitment is generated and the sequence number specific to the sourceId is incremented. The `sendPacket` core function MUST execute the applications logic atomically triggering the `onSendPacket` callback ∀ application contained in the `packet.data` payload. @@ -321,6 +354,59 @@ The IBC handler performs the following steps in order: Note that the full packet is not stored in the state of the chain - merely a short hash-commitment to the data & timeout value. The packet data can be calculated from the transaction execution and possibly returned as log output which relayers can index. +We first define the `conditions = {C}` set as the union set of `ante-conditions = {AC}` (or pre-conditions), `error-conditions = {EC}` and `post-conditions = {PC}` such that `C={AC U EC U PC}`. + +To adhere to the protocol rules, the sendPacket handler MUST enforce the set C. + +###### Ante-Conditions + +The Ante-Conditions defines what MUST be accomplished before two chains can start sending IBC v2 packets. + +Both chains `A` and `B` MUST have executed independently: + +- AC.0 = the client creation, +- AC.1 = channel creation +- AC.2 = channel registration. +- AC.3 = application registration, + +Thus the following checks MUST return true on any end attempting to send a packet: + +```typescript +// TODO +// AC.0 +clientState=queryClientState(clientId) +clientState!=null; + +channel = getChannel(channelId) +channel != null; +channel.clientId == clientId + +client=IBCRouter.clients[channelId] +client==channel.clientId +``` + +###### Error-Conditions + +The Error-Conditions defines the set of condition that MUST trigger an error. + +- EC.0 The underlying clients is not properly registered in the IBC router. +- EC.1 The timeout specified has already passed on the destination chain +- EC.2 One of the payload has falied its execution. + +###### Post-Conditions On Success + +The Post-Conditions on Success defines which state changes MUST have occurred if the `sendPacket` handler has been sucessfully executed. + +- PC.0 The packetCommitment has been generated. +- PC.1 All the application contained in the payload have properly terminated the `onSendPacket` callback execution. + +###### Post-Conditions On Error + +- PC.2 No packetCommitment has been generated. +- PC.3 If one payload fail, then all state changes happened on the sucessfull application execution must be reverted. + +Then we provide an example pseudo-code that enforce the conditions sets. + ```typescript function sendPacket( sourceChannelId: bytes, From 4324774c1ec2fcffda791c96b7481633b0513e6d Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Wed, 2 Oct 2024 16:25:08 +0200 Subject: [PATCH 22/71] ante-error-post conditions sendPacket --- .../v2/ics-004-packet-semantics/README.md | 148 +++++++++++------- 1 file changed, 89 insertions(+), 59 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 50b1e082e..db9097505 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -141,6 +141,10 @@ type IBCRouter struct { The IBCRouter struct MUST be set by the application modules during the module setup to carry out the application registration procedure. +```typescript +const MAX_TIMEOUT_DELTA = TBD +``` + ### Desired Properties #### Efficiency @@ -320,26 +324,49 @@ The packets will be addressed **directly** with the channels that have links to If the setup has been executed correctly, then the correctness and soundness properties of IBC holds. IBC packet flow is guaranteed to succeed. If a user sends a packet with the wrong destination channel, then as we will see it will be impossible for the intended destination to correctly verify the packet thus, the packet will simply time out. -#### Packet Flow through the Router & handling +#### Packet Flow Function Handlers -The bidirectional packet stream can be only started after all the procedure above have been succesfully executed, individually, by both chains. Otherwise, any sent packet cannot be received and will timeout. +In the IBC protocol version 2, the packet flow is managed by four key function handlers, each of which is responsible for distinct stages in the packet lifecycle: -TODO : Adapt to new flow +- `sendPacket` +- `receivePacket` +- `acknowledgePacket` +- `timeoutPacket` -![Packet State Machine](../../ics-004-channel-and-packet-semantics/packet-state-machine.png) +For each handler, the ICS-04 specification defines a set of conditions that the IBC protocol must adhere to. These conditions ensure the proper execution of the handler by establishing requirements before execution (ante-conditions), possible error states during execution (error-conditions), and expected outcomes after execution (post-conditions). -##### A day in the life of a packet +Let the set of conditions be represented as +\[ +C = \{c_0, c_1, c_2, c_3\} +\] +where each \(c_n\) corresponds to the conditions for a specific handler. These conditions are composed of: -TODO Modify Sketch -![V2 Happy Path Single Payload Sketch](Sketch_Happy_Path.png) +- **Ante-conditions (Pre-conditions) {ac}**: The state that must be true before the function executes. +- **Error-conditions {er}**: The states that would result in the function failing. +- **Post-conditions {pc}**: The required state of the system after successful execution. -TODO Write Setup. +For each function handler, the conditions are defined as follows: + +- `c_0 = ac_0 ∪ er_0 ∪ pc_0` corresponds to `sendPacket` +- `c_1 = ac_1 ∪ er_1 ∪ pc_1` corresponds to `receivePacket` +- `c_2 = ac_2 ∪ er_2 ∪ pc_2` corresponds to `acknowledgePacket` +- `c_3 = ac_3 ∪ er_3 ∪ pc_3` corresponds to `timeoutPacket` -The following sequence of steps must occur for a packet to be sent from module *1* on machine *A* to module *2* on machine *B*, starting from scratch. +Thus, the IBC version 2 protocol adheres to a condition set \(C\), where: + +\[ +C = \{c_0 = (ac_0 ∪ er_0 ∪ pc_0), \, c_1 = (ac_1 ∪ er_1 ∪ pc_1), \, c_2 = (ac_2 ∪ er_2 ∪ pc_2), \, c_3 = (ac_3 ∪ er_3 ∪ pc_3)\} +\] + +Each condition set \(c_n\) is tailored to the specific function handler and ensures that the lifecycle of an IBC packet is managed correctly across all stages. + +#### Packet Flow & handling + +TODO : Adapt to new flow ##### Sending packets -The `sendPacket` function is called by the IBC handler when an IBC packet is submitted to the newtwork in order to send *data* in the form of an IBC packet. ∀ `Payload` included in the `packet.data`, which may refer to a different application, the application specific callbacks are retrieved from the the IBC router and the `onSendPacket` is the then triggered on the specified application. The `onSendPacket` executes the application logic. Once all payloads contained in the `packet.data` have been acted upon, the packet commitment is generated and the sequence number specific to the sourceId is incremented. +The `sendPacket` function is called by the IBC handler when an IBC packet is submitted to the newtwork in order to send *data* in the form of an IBC packet. ∀ `Payload` included in the `packet.data`, which may refer to a different application, the application specific callbacks are retrieved from the IBC router and the `onSendPacket` is the then triggered on the specified application. The `onSendPacket` executes the application logic. Once all payloads contained in the `packet.data` have been acted upon, the packet commitment is generated and the sequence number bind to the sourceId is incremented. The `sendPacket` core function MUST execute the applications logic atomically triggering the `onSendPacket` callback ∀ application contained in the `packet.data` payload. @@ -354,76 +381,69 @@ The IBC handler performs the following steps in order: Note that the full packet is not stored in the state of the chain - merely a short hash-commitment to the data & timeout value. The packet data can be calculated from the transaction execution and possibly returned as log output which relayers can index. -We first define the `conditions = {C}` set as the union set of `ante-conditions = {AC}` (or pre-conditions), `error-conditions = {EC}` and `post-conditions = {PC}` such that `C={AC U EC U PC}`. - -To adhere to the protocol rules, the sendPacket handler MUST enforce the set C. - ###### Ante-Conditions The Ante-Conditions defines what MUST be accomplished before two chains can start sending IBC v2 packets. Both chains `A` and `B` MUST have executed independently: -- AC.0 = the client creation, -- AC.1 = channel creation +- AC.0 = client creation. +- AC.1 = channel creation. - AC.2 = channel registration. -- AC.3 = application registration, - -Thus the following checks MUST return true on any end attempting to send a packet: +- AC.3 = application registration. -```typescript -// TODO -// AC.0 -clientState=queryClientState(clientId) -clientState!=null; +###### Error-Conditions -channel = getChannel(channelId) -channel != null; -channel.clientId == clientId +The Error-Conditions defines the set of condition that MUST trigger an error. -client=IBCRouter.clients[channelId] -client==channel.clientId -``` +If `AC` conditions have not been meet, sending an IBC version 2 packet may trigger one of the following errors: -###### Error-Conditions +- EC.0 the underlying clients do not exists +- EC.1 the channels do not exists +- EC.2 the channel is not fully configured +- EC.3 the application cannot be resolved on the local router -The Error-Conditions defines the set of condition that MUST trigger an error. +While other source of potential errors can be defined as: -- EC.0 The underlying clients is not properly registered in the IBC router. -- EC.1 The timeout specified has already passed on the destination chain -- EC.2 One of the payload has falied its execution. +- EC.4 The underlying clients is not properly registered in the IBC router. +- EC.5 The timeout specified has already passed on the destination chain +- EC.6 One of the payload has falied its execution. ###### Post-Conditions On Success The Post-Conditions on Success defines which state changes MUST have occurred if the `sendPacket` handler has been sucessfully executed. -- PC.0 The packetCommitment has been generated. -- PC.1 All the application contained in the payload have properly terminated the `onSendPacket` callback execution. +- PC.0 All the application contained in the payload have properly terminated the `onSendPacket` callback execution. +- PC.1 The packetCommitment has been generated. +- PC.2 The sequence number bind to sourceId MUST have been incremented by 1. ###### Post-Conditions On Error -- PC.2 No packetCommitment has been generated. - PC.3 If one payload fail, then all state changes happened on the sucessfull application execution must be reverted. +- PC.4 No packetCommitment has been generated. +- PC.5 The sequence number bind to sourceId MUST be unchanged. -Then we provide an example pseudo-code that enforce the conditions sets. - +The ICS04 provides an example pseudo-code that enforce the conditions sets C so that the following sequence of steps must occur for a packet to be sent from module *1* on machine *A* to module *2* on machine *B*, starting from scratch. + ```typescript function sendPacket( - sourceChannelId: bytes, - destChannelId: bytes, + // sourceChannelId: bytes, unnecessary? + // destChannelId: bytes, unnecessary? timeoutTimestamp: uint64, packet: []byte ): uint64 { - client = router.clients[packet.sourceChannelId] + // AC.3 + client = router.clients[packet.sourceId] assert(client !== null) - // disallow packets with a zero timeoutHeight and timeoutTimestamp - assert(timeoutTimestamp !== 0) // Maybe this can be enforced even for unreal timeouts value and not only for 0 - assert(currentTimestamp()> timeoutTimestamp) // Mmm + // disallow packets with a zero timeoutTimestamp + assert(timeoutTimestamp !== 0) + // disallow packet with timeoutTimestamp less than currentTimestamp and timeoutTimestamp value bigger than currentTimestamp + MaxTimeoutDelta + assert(currentTimestamp() < timeoutTimestamp < currentTimestamp() + MAX_TIMEOUT_DELTA) // Mmm // if the sequence doesn't already exist, this call initializes the sequence to 0 - sequence = privateStore.get(nextSequenceSendPath(sourceChannelId)) + sequence = privateStore.get(nextSequenceSendPath(packet.sourceId)) // Executes Application logic ∀ Payload for payload in packet.data @@ -439,14 +459,14 @@ function sendPacket( provableStore.set(packetCommitmentPath(packet.sourcelId, sequence),commit) // increment the sequence. Thus there are monotonically increasing sequences for packet flow for a given clientId - privateStore.set(nextSequenceSendPath(sourceChannelId), sequence+1) + privateStore.set(nextSequenceSendPath(packet.sourceId), sequence+1) // log that a packet can be safely sent // NOTE introducing sourceID and destID can be useful for monitoring - e.g. if one wants to monitor all packets between sourceID and destID emitting this in the event would simplify his life. emitLogEntry("sendPacket", { - sourceID: sourceChannelId, - destID: destChannelId, + sourceId: packet.sourceId, + destId: packet.destId, sequence: sequence, data: data, timeoutTimestamp: timeoutTimestamp @@ -485,15 +505,16 @@ function recvPacket( relayer: string): Packet { // in this specification, the destination channel is the clientId - client = router.clients[packet.destChannelId] + client = router.clients[packet.destId] assert(client !== null) // assert that our counterparty clientId is the packet.sourceClientId - channel = getChannel(packet.destChannelId) + channel = getChannel(packet.destId) assert(packet.sourceId == channel.clientId) // verify timeout - assert(packet.timeoutTimestamp === 0 || currentTimestamp() < packet.timeoutTimestamp) + assert(packet.timeoutTimestamp === 0) + assert(currentTimestamp() + MAX_TIMEOUT_DELTA < packet.timeoutTimestamp) // verify the packet receipt for this packet does not exist already packetReceipt = provableStore.get(packetReceiptPath(packet.destId, packet.sequence)) @@ -503,7 +524,7 @@ function recvPacket( // 1. retrieve keys packetPath = packetCommitmentPath(packet.destId, packet.sequence) - merklePath = applyPrefix(counterparty.keyPrefix, packetPath) + merklePath = applyPrefix(channel.keyPrefix, packetPath) // 2. reconstruct commit value based on the passed-in packet commit = commitV2Packet(packet) @@ -520,7 +541,7 @@ function recvPacket( // Executes Application logic ∀ Payload for payload in packet.data cbs = router.callbacks[payload.destPort] - ack = cbs.onReceivePacket(payload) + ack = cbs.onReceivePacket(packet.destId,payload) // the onReceivePacket returns the ack but does not write it // IMPORTANT: if the ack is error, then the callback reverts its internal state changes, but the entire tx continues multiAck.add(ack) @@ -636,7 +657,7 @@ function acknowledgePacket( assert(provableStore.get(packetCommitmentPath(packet.sourceId, packet.sequence)) === commitV2Packet(packet)) ackPath = packetAcknowledgementPath(packet.destId, packet.sequence) - merklePath = applyPrefix(counterparty.keyPrefix, ackPath) + merklePath = applyPrefix(channel.keyPrefix, ackPath) assert(client.verifyMembership( client.clientState proofHeight, @@ -649,7 +670,7 @@ function acknowledgePacket( nAck=0 for payload in packet.data cbs = router.callbacks[payload.sourcePort] - success= cbs.OnAcknowledgePacket(payload, acknowledgement.appAcknowledgement[nAck], relayer) + success= cbs.OnAcknowledgePacket(packet.sourceId,payload, acknowledgement.appAcknowledgement[nAck], relayer) abortUnless(success) nAck++ @@ -735,7 +756,7 @@ function timeoutPacket( asert(packet.timeoutTimestamp > 0 && proofTimestamp >= packet.timeoutTimestamp) receiptPath = packetReceiptPath(packet.destId, packet.sequence) - merklePath = applyPrefix(counterparty.keyPrefix, receiptPath) + merklePath = applyPrefix(channel.keyPrefix, receiptPath) assert(client.verifyNonMembership( client.clientState, proofHeight, @@ -745,7 +766,7 @@ function timeoutPacket( for payload in packet.data cbs = router.callbacks[payload.sourcePort] - success=cbs.OnTimeoutPacket(payload, relayer) + success=cbs.OnTimeoutPacket(packet.sourceId,payload, relayer) abortUnless(success) channelStore.delete(packetCommitmentPath(packet.sourceId, packet.sequence)) @@ -762,6 +783,15 @@ TODO: Packets must be acknowledged in order to be cleaned-up. +#### A day in the life of a packet + +The bidirectional packet stream can be only started after all the procedure above have been succesfully executed, individually, by both chains. Otherwise, any sent packet cannot be received and will timeout. + +TODO Write Setup. + +TODO Modify Sketch +![V2 Happy Path Single Payload Sketch](Sketch_Happy_Path.png) + #### Reasoning about race conditions TODO From de17196e0f07ef85092afbefd9d2a6f960da3f44 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Wed, 2 Oct 2024 17:51:44 +0200 Subject: [PATCH 23/71] fixes --- .../v2/ics-004-packet-semantics/README.md | 97 ++++++++++--------- 1 file changed, 52 insertions(+), 45 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index db9097505..558276996 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -41,15 +41,15 @@ A `channel` is a pipeline for exactly-once packet delivery between specific modu ```typescript interface Channel { clientId: bytes - counterpartyChannelId: bytes + counterpartyChannelId: bytes keyPrefix: CommitmentPrefix } ``` Where : -- `clientId` is the client id of the counterparty used by our chain. -- `counterpartyChannelId` is the counterparty channel identifier that MUST be used by the packet. +- `clientId` is the local light client id of the counterparty chain. +- `counterpartyChannelId` is the counterparty channel id. - `keyPrefix` is the key path that the counterparty will use to prove its store packet flow messages. The `Packet`, `Payload`, `Encoding` and the `Acknowledgement` interfaces are as defined in [packet specification](https://github.com/cosmos/ibc/blob/c7b2e6d5184b5310843719b428923e0c5ee5a026/spec/core/v2/ics-004-packet-semantics/PACKET.md). For convenience, following we recall their structures. @@ -66,9 +66,9 @@ interface Packet { } ``` -- The `sourceId` derived from the `clientIdentifier` will tell the IBC router which chain a received packet came from. -- The `destId` derived from the `clientIdentifier` will tell the IBC router which chain to send the packets to. -- The `sequence` number corresponds to the order of sends and receives, where a packet with an earlier sequence number MUST be sent and received (NOTE: not sure about received) before a packet with a later sequence number. +- The `sourceId` is the identifier on the source chain. +- The `destId` is the identifier on the dest chain. +- The `sequence` number corresponds to the order of sent packets. - The `timeout` indicates the UNIX timestamp in seconds and is encoded in LittleEndian. It must be passed on the destination chain and once elapsed, will no longer allow the packet processing, and will instead generate a time-out. The `Payload` is a particular interface defined as follows: @@ -141,6 +141,8 @@ type IBCRouter struct { The IBCRouter struct MUST be set by the application modules during the module setup to carry out the application registration procedure. +- The `MAX_TIMEOUT_DELTA` is intendend as the max difference between currentTimestamp and timeoutTimestamp that can be given in input. + ```typescript const MAX_TIMEOUT_DELTA = TBD ``` @@ -169,19 +171,11 @@ const MAX_TIMEOUT_DELTA = TBD ## Technical Specification -### Dataflow visualisation - -TODO - -The architecture of clients, connections, channels and packets: - -![Dataflow Visualisation](../../ics-004-channel-and-packet-semantics/dataflow.png) - ### Preliminaries #### Store paths -The protocol defines the paths `packetCommitmentPath`, `packetRecepitPath` and `packetAcknowledgementPath` that MUST be used as the referece locations in the provableStore to prove respectilvey the packet commitment, the receipt and the acknowledgment to the counterparty chain. +The ICS-04 use the protocol paths, defined in [ICS-24](../ics-024-host-requirements/README.md), `packetCommitmentPath`, `packetRecepitPath` and `packetAcknowledgementPath`. These paths MUST be used as the referece locations in the provableStore to prove respectilvey the packet commitment, the receipt and the acknowledgment to the counterparty chain. Thus Constant-size commitments to packet data fields are stored under the packet sequence number: @@ -193,7 +187,7 @@ function packetCommitmentPath(sourceId: bytes, sequence: BigEndianUint64): Path 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`. While in the case of a timeout, the destination chain MAY (May?Must?Should?Mmm) write a sentinel timeout value `TIMEOUT_RECEIPT` if the packet is received after the specified timeout. +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`. While in the case of a timeout, the destination chain MUST write a sentinel timeout value `TIMEOUT_RECEIPT` if the packet is received after the specified timeout. ```typescript function packetReceiptPath(sourceId: bytes, sequence: BigEndianUint64): Path { @@ -209,7 +203,7 @@ function packetAcknowledgementPath(sourceId: bytes, sequence: BigEndianUint64): } ``` -Additionally, the protocol suggests the privateStore paths for the `nextSequenceSend` , `channelPath` and `channelCreator` variable. Private paths are meant to be used locally in the chain. Thus their specification can be arbitrary changed by implementors at their will. +Additionally, the ICS-04 suggests the privateStore paths for the `nextSequenceSend` , `channelPath` and `channelCreator` variable. Private paths are meant to be used locally in the chain. Thus their specification can be arbitrary changed by implementors at their will. - The `nextSequenceSend` is stored separately in the privateStore and tracks the sequence number for the next packet to be sent for a given source clientId. @@ -237,47 +231,58 @@ function creatorPath(channelId: Identifier, creator: address): Path { ### Sub-protocols -In order to start sending packets using IBC version 2, chain `A` and chain `B` MUST execute the following set of procedures: +TODO The architecture of clients, connections, channels and packets: + +#### Setup + +In order to start sending packets using IBC version 2, chain `A` and chain `B` MUST execute the entire setup following this set of procedures: -- Client creation: chain `A` MUST create the `B` light client; `B` MUST create the `A` light client. - Application registration: during the application module setup the application MUST be registered on the local IBC router. -- Channel creation: both chain `A` and chain `B` MUST create local channels. +- Client creation: chain `A` MUST create the `B` light client; `B` MUST create the `A` light client. +- Channel creation: both chain `A` and chain `B` MUST create local IBC version 2 channels. - Channel registration: both chain MUST register their counterpartyId in the channel previously created. -While the `createClient` procedure is defined in [ICS2](../ics-002-client-semantics/README.md) and the application registration MUST be defined in the setup section of the application module and being executed on the module startup, the ICS-04 defines the channel creation and registration procedure. +While the application registration MUST be defined in the setup section of the application module and being executed on the module startup and the `createClient` procedure is defined in [ICS2](../ics-002-client-semantics/README.md), the ICS-04 defines the channel creation and registration procedure. -#### Channel creation +##### Channel creation ```typescript function createChannel( clientId: bytes, counterpartyKeyPrefix: CommitmentPrefix) (channelId: bytes){ + // Implementation-Specific Input Validation + // All implementations MUST ensure the inputs value are properly validated and compliant with this specification + + // Channel Checks channelId = generateIdentifier(clientId,counterpartyKeyPrefix) abortTransactionUnless(validateChannelIdentifier(channelId)) - abortTransactionUnless(privateStore.get(channelPath(channelId)) === null) + abortTransactionUnless(getChannel(channelId)) === null) + // Channel manipulation channel = Channel{ clientId: clientId, counterpartyChannelId: "", // This field it must be a blank field during the creation as it may be not known at the creation time. keyPrefix: counterpartyKeyPrefix } + // Local stores privateStore.set(channelPath(channelId), channel) privateStore.set(creatorPath(channelId,msg.signer()), msg.signer()) + return channelId } ``` Note that the `createClient` message can be coupled and executed in conjunction with a `createChannel` message in a single multiMsgTx. The execution of these messages on both chains are prerequisites for the channel registration procedure described below. -#### Channel registration and counterparty idenfitifcation +##### Channel registration and counterparty idenfitifcation Each IBC chain MUST have the ability to idenfity its counterparty. With a client, we can prove any key/value path on the counterparty. However, without knowing which identifier the counterparty uses when it sends messages to us, we cannot differentiate between messages sent from the counterparty to our chain vs messages sent from the counterparty with other chains. Most implementations will not be able to store the ICS-24 paths directly as a key in the global namespace, but will instead write to a reserved, prefixed keyspace so as not to conflict with other application state writes. To provide the chains a mechanism for the mutual and verifiable identification, the IBC version 2 defines the channel registration procedure to complement the store of the counterparty information in the channel that will include both its identifier for our chain and as well as the key prefix under which it will write the provable ICS-24 paths. -Thus, IBC version 2 introduces a new message `RegisterChannel` that will store the counterpartyChannelId into the local channel structure. Thus, if the `RegisterChannel` message is submitted to both sides correctly, then both sides have mirrored pairs that can be treated as channel identifiers. Assuming they are correct, the underlying client on each side is unique and provides an authenticated stream of packet data between the two chains. If the `RegisterChannel` message submits the wrong counterpartyChannelId, this can lead to invalid behaviour; but this is equivalent to a relayer submitting an invalid client in place of a correct client for the desired chain. In the simplest case, we can rely on out-of-band social consensus to only send on valid pairs that represent a connection between the desired chains of the user; just as we currently rely on out-of-band social consensus that a given clientID and channel built on top of it is the valid, canonical identifier of our desired chain. +Thus, IBC version 2 introduces a new message `RegisterChannel` that will store the counterpartyChannelId into the local channel structure. Thus, if the `RegisterChannel` message is submitted to both sides correctly, then both sides have mirrored pairs. Assuming they are correct, the underlying client on each side associated with the channel is unique and provides an authenticated stream of packet data between the two chains. If the `RegisterChannel` message submits the wrong counterpartyChannelId, this can lead to invalid behaviour; but this is equivalent to a relayer submitting an invalid channelId in place of a correct channelId for the desired chain. In the simplest case, we can rely on out-of-band social consensus to only send on valid pairs that represent a connection between the desired chains of the user; just as we currently rely on out-of-band social consensus that a given clientID and channel built on top of it is the valid, canonical identifier of our desired chain. ```typescript function RegisterChannel( @@ -286,16 +291,26 @@ function RegisterChannel( counterpartyKeyPrefix: CommitmentPrefix, authentication: data, // implementation-specific authentication data ) { + // Implementation-Specific Input Validation + // All implementations MUST ensure the inputs value are properly validated and compliant with this specification + + // custom-authentication assert(verify(authentication)) + // Channel Checks channelId=generateIdentifier(clientId,counterpartyKeyPrefix) abortTransactionUnless(validatedIdentifier(channelId)) - channel=getChannel(channelId)) abortTransactionUnless(channel !== null) + abortTransactionUnless(channel.clientId === clientId) + + // Creator Address Checks abortTransactionUnless(msg.signer()===getCreator(channelId,msg.signer())) + // Channel manipulation channel.counterpartyChannelId=counterpartyChannelId + + // Local Store privateStore.set(channelPath(counterpartyChannelId), channel) } @@ -320,7 +335,7 @@ function getCreator(channelId: bytes, msgSigner: address): address { Thus, once two chains have set up clients, created channel and registered channels for each other with specific Identifiers, they can send IBC packets using the packet interface defined before and the packet handlers that the ICS-04 defines below. -The packets will be addressed **directly** with the channels that have links to the underlying light clients. Thus there are **no** more handshakes necessary. Instead the packet sender must be capable of providing the correct pair. +The packets will be addressed **directly** with the channels that have semantic link to the underlying light clients. Thus there are **no** more handshakes necessary. Instead the packet sender must be capable of providing the correct pair. If the setup has been executed correctly, then the correctness and soundness properties of IBC holds. IBC packet flow is guaranteed to succeed. If a user sends a packet with the wrong destination channel, then as we will see it will be impossible for the intended destination to correctly verify the packet thus, the packet will simply time out. @@ -352,13 +367,9 @@ For each function handler, the conditions are defined as follows: - `c_2 = ac_2 ∪ er_2 ∪ pc_2` corresponds to `acknowledgePacket` - `c_3 = ac_3 ∪ er_3 ∪ pc_3` corresponds to `timeoutPacket` -Thus, the IBC version 2 protocol adheres to a condition set \(C\), where: +Thus, each condition set \(c_n\) is tailored to the specific function handler and ensures that the lifecycle of an IBC packet is managed correctly across all stages. -\[ -C = \{c_0 = (ac_0 ∪ er_0 ∪ pc_0), \, c_1 = (ac_1 ∪ er_1 ∪ pc_1), \, c_2 = (ac_2 ∪ er_2 ∪ pc_2), \, c_3 = (ac_3 ∪ er_3 ∪ pc_3)\} -\] - -Each condition set \(c_n\) is tailored to the specific function handler and ensures that the lifecycle of an IBC packet is managed correctly across all stages. +Any implementation that wants to comply with the specification of the IBC version 2 protocol MUST adheres to the condition set \(C\). #### Packet Flow & handling @@ -387,10 +398,10 @@ The Ante-Conditions defines what MUST be accomplished before two chains can star Both chains `A` and `B` MUST have executed independently: -- AC.0 = client creation. -- AC.1 = channel creation. -- AC.2 = channel registration. -- AC.3 = application registration. +- AC.0 = application registration. +- AC.1 = client creation. +- AC.2 = channel creation. +- AC.3 = channel registration. ###### Error-Conditions @@ -423,7 +434,7 @@ The Post-Conditions on Success defines which state changes MUST have occurred if - PC.4 No packetCommitment has been generated. - PC.5 The sequence number bind to sourceId MUST be unchanged. -The ICS04 provides an example pseudo-code that enforce the conditions sets C so that the following sequence of steps must occur for a packet to be sent from module *1* on machine *A* to module *2* on machine *B*, starting from scratch. +The ICS04 provides an example pseudo-code that enforce the conditions sets c0 so that the following sequence of steps must occur for a packet to be sent from module *1* on machine *A* to module *2* on machine *B*, starting from scratch. ```typescript function sendPacket( @@ -475,12 +486,6 @@ function sendPacket( } ``` -TODO: - -- Preconditions -- Error Conditions -- Post Conditions - #### Receiving packets The `recvPacket` function is called by the IBC handler in order to receive an IBC packet sent on the corresponding client on the counterparty chain. @@ -783,7 +788,9 @@ TODO: Packets must be acknowledged in order to be cleaned-up. -#### A day in the life of a packet +### Dataflow visualisation: A day in the life of a packet + +TODO The bidirectional packet stream can be only started after all the procedure above have been succesfully executed, individually, by both chains. Otherwise, any sent packet cannot be received and will timeout. From df9e05059e3a66a64c7ead7ce9a5eed5c05951ea Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Thu, 3 Oct 2024 17:32:46 +0200 Subject: [PATCH 24/71] handlers pre-error-post conditions --- .../v2/ics-004-packet-semantics/README.md | 337 +++++++++++------- 1 file changed, 204 insertions(+), 133 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 558276996..53faa2ff0 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -111,8 +111,8 @@ The protocol introduces standardized packet receipts that will serve as sentinel ```typescript enum PacketReceipt { - SUCCESSFUL_RECEIPT = bytes(0x01), - TIMEOUT_RECEIPT = bytes(0x02), + SUCCESSFUL_RECEIPT = []byte{0x01}, + TIMEOUT_RECEIPT = []byte{0x02}, } ``` @@ -187,7 +187,7 @@ function packetCommitmentPath(sourceId: bytes, sequence: BigEndianUint64): Path 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`. While in the case of a timeout, the destination chain MUST write a sentinel timeout value `TIMEOUT_RECEIPT` if the packet is received after the specified timeout. +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`. While in the case of a timeout, the destination chain SHOULD write a sentinel timeout value `TIMEOUT_RECEIPT` if the packet is received after the specified timeout. ```typescript function packetReceiptPath(sourceId: bytes, sequence: BigEndianUint64): Path { @@ -242,10 +242,12 @@ In order to start sending packets using IBC version 2, chain `A` and chain `B` M - Channel creation: both chain `A` and chain `B` MUST create local IBC version 2 channels. - Channel registration: both chain MUST register their counterpartyId in the channel previously created. -While the application registration MUST be defined in the setup section of the application module and being executed on the module startup and the `createClient` procedure is defined in [ICS2](../ics-002-client-semantics/README.md), the ICS-04 defines the channel creation and registration procedure. +While application registration is handled by the application module during initialization, and client creation is governed by [ICS-2](](../ics-002-client-semantics/README.md)), the channel creation and registration procedures are defined by ICS-04 and are detailed below. ##### Channel creation +The channel creation process establishes the communication pathway between two chains. The procedure ensures both chains have a mutually recognized channel, facilitating packet transmission through authenticated streams. + ```typescript function createChannel( clientId: bytes, @@ -255,7 +257,7 @@ function createChannel( // All implementations MUST ensure the inputs value are properly validated and compliant with this specification // Channel Checks - channelId = generateIdentifier(clientId,counterpartyKeyPrefix) + channelId = generateIdentifier() abortTransactionUnless(validateChannelIdentifier(channelId)) abortTransactionUnless(getChannel(channelId)) === null) @@ -274,7 +276,7 @@ function createChannel( } ``` -Note that the `createClient` message can be coupled and executed in conjunction with a `createChannel` message in a single multiMsgTx. The execution of these messages on both chains are prerequisites for the channel registration procedure described below. +Note that `createClient` message (as defined in ICS-02) may be bundled with the `createChannel` message in a single multiMsgTx. Successful execution of these messages on both chains is a prerequisite for the channel registration procedure, described below. ##### Channel registration and counterparty idenfitifcation @@ -282,7 +284,7 @@ Each IBC chain MUST have the ability to idenfity its counterparty. With a client To provide the chains a mechanism for the mutual and verifiable identification, the IBC version 2 defines the channel registration procedure to complement the store of the counterparty information in the channel that will include both its identifier for our chain and as well as the key prefix under which it will write the provable ICS-24 paths. -Thus, IBC version 2 introduces a new message `RegisterChannel` that will store the counterpartyChannelId into the local channel structure. Thus, if the `RegisterChannel` message is submitted to both sides correctly, then both sides have mirrored pairs. Assuming they are correct, the underlying client on each side associated with the channel is unique and provides an authenticated stream of packet data between the two chains. If the `RegisterChannel` message submits the wrong counterpartyChannelId, this can lead to invalid behaviour; but this is equivalent to a relayer submitting an invalid channelId in place of a correct channelId for the desired chain. In the simplest case, we can rely on out-of-band social consensus to only send on valid pairs that represent a connection between the desired chains of the user; just as we currently rely on out-of-band social consensus that a given clientID and channel built on top of it is the valid, canonical identifier of our desired chain. +Thus, IBC version 2 introduces a new message `registerChannel` that will store the counterpartyChannelId into the local channel structure. Thus, if the `registerChannel` message is submitted to both sides correctly, then both sides have mirrored pairs. Assuming they are correct, the underlying client on each side associated with the channel is unique and provides an authenticated stream of packet data between the two chains. If the `registerChannel` message submits the wrong counterpartyChannelId, this can lead to invalid behaviour; but this is equivalent to a relayer submitting an invalid channelId in place of a correct channelId for the desired chain. In the simplest case, we can rely on out-of-band social consensus to only send on valid pairs that represent a connection between the desired chains of the user; just as we currently rely on out-of-band social consensus that a given clientID and channel built on top of it is the valid, canonical identifier of our desired chain. ```typescript function RegisterChannel( @@ -316,8 +318,8 @@ function RegisterChannel( } ``` -The `RegisterChannel` method allows for authentication data that implementations may verify before storing the provided counterparty identifier. The strongest authentication possible is to have a valid clientState and consensus state of our chain in the authentication along with a proof it was stored at the claimed counterparty identifier. -A simpler but weaker authentication would simply be to check that the `RegisterChannel` message is sent by the same relayer that initialized the client. This would make the client parameters completely initialized by the relayer. Thus, users must verify that the client is pointing to the correct chain and that the counterparty identifier is correct as well before using the lite channel identified by the provided client-client pair. +The `registerChannel` method allows for authentication data that implementations may verify before storing the provided counterparty identifier. The strongest authentication possible is to have a valid clientState and consensus state of our chain in the authentication along with a proof it was stored at the claimed counterparty identifier. +A simpler but weaker authentication would simply be to check that the `registerChannel` message is sent by the same relayer that initialized the client. This would make the client parameters completely initialized by the relayer. Thus, users must verify that the client is pointing to the correct chain and that the counterparty identifier is correct as well before using the lite channel identified by the provided client-client pair. ```typescript // getChannel retrieves the stored channel given the counterparty channel-id. @@ -350,26 +352,7 @@ In the IBC protocol version 2, the packet flow is managed by four key function h For each handler, the ICS-04 specification defines a set of conditions that the IBC protocol must adhere to. These conditions ensure the proper execution of the handler by establishing requirements before execution (ante-conditions), possible error states during execution (error-conditions), and expected outcomes after execution (post-conditions). -Let the set of conditions be represented as -\[ -C = \{c_0, c_1, c_2, c_3\} -\] -where each \(c_n\) corresponds to the conditions for a specific handler. These conditions are composed of: - -- **Ante-conditions (Pre-conditions) {ac}**: The state that must be true before the function executes. -- **Error-conditions {er}**: The states that would result in the function failing. -- **Post-conditions {pc}**: The required state of the system after successful execution. - -For each function handler, the conditions are defined as follows: - -- `c_0 = ac_0 ∪ er_0 ∪ pc_0` corresponds to `sendPacket` -- `c_1 = ac_1 ∪ er_1 ∪ pc_1` corresponds to `receivePacket` -- `c_2 = ac_2 ∪ er_2 ∪ pc_2` corresponds to `acknowledgePacket` -- `c_3 = ac_3 ∪ er_3 ∪ pc_3` corresponds to `timeoutPacket` - -Thus, each condition set \(c_n\) is tailored to the specific function handler and ensures that the lifecycle of an IBC packet is managed correctly across all stages. - -Any implementation that wants to comply with the specification of the IBC version 2 protocol MUST adheres to the condition set \(C\). +Thus, implementation that wants to comply with the specification of the IBC version 2 protocol MUST adheres to the condition set defined here. #### Packet Flow & handling @@ -396,97 +379,105 @@ Note that the full packet is not stored in the state of the chain - merely a sho The Ante-Conditions defines what MUST be accomplished before two chains can start sending IBC v2 packets. -Both chains `A` and `B` MUST have executed independently: +Both chains `A` and `B` MUST have executed independently the following set of actions: -- AC.0 = application registration. -- AC.1 = client creation. -- AC.2 = channel creation. -- AC.3 = channel registration. +- Application registration +- Client creation +- Channel creation +- channel registration ###### Error-Conditions -The Error-Conditions defines the set of condition that MUST trigger an error. - -If `AC` conditions have not been meet, sending an IBC version 2 packet may trigger one of the following errors: +The Error-Conditions defines the set of condition that MUST trigger an error. For the `sendPacket` handler we can divide the source of errors in three main categories: -- EC.0 the underlying clients do not exists -- EC.1 the channels do not exists -- EC.2 the channel is not fully configured -- EC.3 the application cannot be resolved on the local router - -While other source of potential errors can be defined as: - -- EC.4 The underlying clients is not properly registered in the IBC router. -- EC.5 The timeout specified has already passed on the destination chain -- EC.6 One of the payload has falied its execution. +- Incorrect setup +- Invalid timeoutTimestamp +- Unsuccessful payload execution ###### Post-Conditions On Success The Post-Conditions on Success defines which state changes MUST have occurred if the `sendPacket` handler has been sucessfully executed. -- PC.0 All the application contained in the payload have properly terminated the `onSendPacket` callback execution. -- PC.1 The packetCommitment has been generated. -- PC.2 The sequence number bind to sourceId MUST have been incremented by 1. +- All the application contained in the payload have properly terminated the `onSendPacket` callback execution. +- The packetCommitment has been generated. +- The sequence number bind to sourceId MUST have been incremented by 1. ###### Post-Conditions On Error -- PC.3 If one payload fail, then all state changes happened on the sucessfull application execution must be reverted. -- PC.4 No packetCommitment has been generated. -- PC.5 The sequence number bind to sourceId MUST be unchanged. +The Post-Conditions on Error defines the states that Must be unchanged given an error occurred during the `sendPacket` handler. + +- If one payload fail, then all state changes happened on the sucessfull application execution must be reverted. +- No packetCommitment has been generated. +- The sequence number bind to sourceId MUST be unchanged. + +###### Pseudo-Code -The ICS04 provides an example pseudo-code that enforce the conditions sets c0 so that the following sequence of steps must occur for a packet to be sent from module *1* on machine *A* to module *2* on machine *B*, starting from scratch. +The ICS04 provides an example pseudo-code that enforce the above described conditions so that the following sequence of steps must occur for a packet to be sent from module *1* on machine *A* to module *2* on machine *B*, starting from scratch. ```typescript function sendPacket( - // sourceChannelId: bytes, unnecessary? - // destChannelId: bytes, unnecessary? + sourceChannelId: bytes, timeoutTimestamp: uint64, - packet: []byte -): uint64 { + payloads: []byte + ) { - // AC.3 - client = router.clients[packet.sourceId] + // Setup checks - channel and client + channel = getChannel(sourceChannelId) + client = router.clients[sourceChannelId] assert(client !== null) - + + // Evaluate usefulness of this check + assert(client.id === channel.clientId) + + // timeoutTimestamp checks // disallow packets with a zero timeoutTimestamp assert(timeoutTimestamp !== 0) // disallow packet with timeoutTimestamp less than currentTimestamp and timeoutTimestamp value bigger than currentTimestamp + MaxTimeoutDelta assert(currentTimestamp() < timeoutTimestamp < currentTimestamp() + MAX_TIMEOUT_DELTA) // Mmm // if the sequence doesn't already exist, this call initializes the sequence to 0 - sequence = privateStore.get(nextSequenceSendPath(packet.sourceId)) + sequence = privateStore.get(nextSequenceSendPath(sourceChannelId)) // Executes Application logic ∀ Payload - for payload in packet.data + for payload in payloads cbs = router.callbacks[payload.sourcePort] - success = cbs.onSendPacket(packet.sourceId,payload) - // IMPORTANT: if the one of the onSendPacket fails, the transaction is aborted and the potential state changes that previosly onSendPacket should apply are automatically reverted. + success = cbs.onSendPacket(sourceChannelId,channel.counterpartyChannelId,payload) + // IMPORTANT: if the one of the onSendPacket fails, the transaction is aborted and the potential state changes are reverted. This ensure that + // the post conditions on error are always respected. + + // payload execution check abortUnless(success) + packet = Packet { + sourceId: sourceChannelId, + destId: channel.counterpartyChannelId, + sequence: sequence, + timeoutTimestamp: timeoutTimestamp, + payload: paylodas + } // store packet commitment using commit function defined in [packet specification](https://github.com/cosmos/ibc/blob/c7b2e6d5184b5310843719b428923e0c5ee5a026/spec/core/v2/ics-004-packet-semantics/PACKET.md) - commit=commitV2Packet(packet) + commitment=commitV2Packet(packet) - provableStore.set(packetCommitmentPath(packet.sourcelId, sequence),commit) + provableStore.set(packetCommitmentPath(sourceChannelId, sequence),commitment) // increment the sequence. Thus there are monotonically increasing sequences for packet flow for a given clientId - privateStore.set(nextSequenceSendPath(packet.sourceId), sequence+1) + privateStore.set(nextSequenceSendPath(sourceChannelId), sequence+1) // log that a packet can be safely sent - // NOTE introducing sourceID and destID can be useful for monitoring - e.g. if one wants to monitor all packets between sourceID and destID emitting this in the event would simplify his life. - + // Discussion needed: What we need to emit the log? emitLogEntry("sendPacket", { - sourceId: packet.sourceId, - destId: packet.destId, + sourceId: sourceChannelId, + destId: channel.counterpartyChannelId, sequence: sequence, data: data, - timeoutTimestamp: timeoutTimestamp + timeoutTimestamp: timeoutTimestamp, }) - + } ``` -#### Receiving packets +##### Receiving packets The `recvPacket` function is called by the IBC handler in order to receive an IBC packet sent on the corresponding client on the counterparty chain. @@ -499,23 +490,60 @@ The IBC handler performs the following steps in order: - Checks that the timeout timestamp is not yet passed - Checks the inclusion proof of packet data commitment in the outgoing chain's state - Sets a store path to indicate that the packet has been received +- Write the acknowledgement into the provableStore. We pass the address of the `relayer` that signed and submitted the packet to enable a module to optionally provide some rewards. This provides a foundation for fee payment, but can be used for other techniques as well (like calculating a leaderboard). +###### Ante-Conditions + +Given that chain `A` has sucessfully sent a packet, the ante-conditions defines what MUST be accomplished before chain `B` can properly receive the IBC v2 packets. + +- Chain `A` MUST have stored the packetCommitment under the keyPrefix registered in the chain `B` channelEnd. +- TimeoutTimestamp MUST not have elasped yet. +- PacketReceipt for the specific keyPrefix and sequence MUST be empty (e.g. receivePacket has not been called yet) + +###### Error-Conditions + +The Error-Conditions defines the set of condition that MUST trigger an error. For the `receivePacket` handler we can divide the source of errors in three main categories: + +- Packet Errors: invalid packetCommitment, packetReceipt already exist +- Invalid timeoutTimestamp +- Unsuccessful payload execution + +###### Post-Conditions On Success + +The Post-Conditions on Success defines which state changes MUST have occurred if the `receivePacket` handler has been sucessfully executed. + +- All the application pointed in the payload have properly terminated the `onReceivePacket` callback execution. +- The packetReceipt has been written. +- The acknowledgemnet has been written. + +###### Post-Conditions On Error + +The Post-Conditions on Error defines the states that Must be unchanged given an error occurred during the `onReceivePacket` handler. + +- If one payload fail, then all state changes happened on the sucessfull `onReceive` application callback execution MUST be reverted. +- If timeoutTimestamp has elapsed then no state changes occurred. (Is this true? Shall we write the timeout_sentinel_receipt?) +- mmmm. + +###### Pseudo-Code + +The ICS04 provides an example pseudo-code that enforce the above described conditions so that the following sequence of steps must occur for a packet to be received from module *1* on machine *A* to module *2* on machine *B*. + ```typescript function recvPacket( packet: OpaquePacket, proof: CommitmentProof, proofHeight: Height, - relayer: string): Packet { + relayer: string) { - // in this specification, the destination channel is the clientId + // Channel and Client Checks + channel = getChannel(packet.destId) client = router.clients[packet.destId] assert(client !== null) - - // assert that our counterparty clientId is the packet.sourceClientId - channel = getChannel(packet.destId) - assert(packet.sourceId == channel.clientId) + assert(client.id === channel.clientId) + + //assert(packet.sourceId == channel.counterpartyChannelId) Unnecessary? // verify timeout assert(packet.timeoutTimestamp === 0) @@ -551,6 +579,12 @@ function recvPacket( // IMPORTANT: if the ack is error, then the callback reverts its internal state changes, but the entire tx continues multiAck.add(ack) + // NOTE: Currently only process synchronous acks. + if multiAck != nil { + writeAcknowledgement(packet, multiAck) + } + + // Provable Stores // we must set the receipt so it can be verified on the other side // it's the sentinel success receipt: []byte{0x01} provableStore.set( @@ -558,11 +592,6 @@ function recvPacket( SUCCESSFUL_RECEIPT ) - // NOTE: Currently only process synchronous acks. - if multiAck != nil { - writeAcknowledgement(packet, multiAck) - } - // log that a packet has been received emitLogEntry("recvPacket", { data: packet.data @@ -576,13 +605,7 @@ function recvPacket( } ``` -TODO: - -- Preconditions -- Error Conditions -- Post Conditions - -#### Writing acknowledgements +##### Writing acknowledgements NOTE: Currently only process synchronous acks. @@ -607,7 +630,7 @@ function writeAcknowledgement( // cannot already have written the acknowledgement abortTransactionUnless(provableStore.get(packetAcknowledgementPath(packet.destId, packet.sequence) === null)) - // write the acknowledgement using commit function defined in [packet specification](https://github.com/cosmos/ibc/blob/c7b2e6d5184b5310843719b428923e0c5ee5a026/spec/core/v2/ics-004-packet-semantics/PACKET.md) + // create the acknowledgement coomit using the function defined in [packet specification](https://github.com/cosmos/ibc/blob/c7b2e6d5184b5310843719b428923e0c5ee5a026/spec/core/v2/ics-004-packet-semantics/PACKET.md) commit=commitV2Acknowledgment(acknowledgement) provableStore.set( @@ -616,8 +639,8 @@ function writeAcknowledgement( // log that a packet has been acknowledged emitLogEntry("writeAcknowledgement", { sequence: packet.sequence, - sourceClientId: packet.sourceClientId, - destClientId: packet.destClientId, + sourceId: packet.sourceId, + destId: packet.destId, timeoutTimestamp: packet.timeoutTimestamp, data: packet.data, acknowledgement @@ -625,13 +648,7 @@ function writeAcknowledgement( } ``` -TODO: - -- Preconditions -- Error Conditions -- Post Conditions - -#### Processing acknowledgements +##### Processing acknowledgements The `acknowledgePacket` function is called by the IBC handler to process the acknowledgement of a packet previously sent by the source chain. @@ -641,6 +658,40 @@ The IBC hanlder MUST atomically trigger the callbacks execution of appropriate a We pass the `relayer` address just as in [Receiving packets](#receiving-packets) to allow for possible incentivization here as well. +###### Ante-Conditions + +Given that at this point of the packet flow, chain `B` has sucessfully received a packet, the ante-conditions defines what MUST be accomplished before chain `A` can properly execute the `acknowledgePacket` for the IBC v2 packet. + +- Acknowledgment MUST be set in the ackPath. +- PacketCommitment has not been cleared out yet. + +###### Error-Conditions + +The Error-Conditions defines the set of condition that MUST trigger an error. For the `acknowledgePacket` handler we can divide the source of errors in three main categories: + +- packetCommitment already clreared out +- Unset Acknowledgment +- Unsuccessful payload execution + +###### Post-Conditions On Success + +The Post-Conditions on Success defines which state changes MUST have occurred if the `acknowledgePacket` handler has been sucessfully executed. + +- All the application pointed in the payload have properly terminated the `onAcknowledgePacket` callback execution. +- The packetCommitment has been cleared out. + +###### Post-Conditions On Error + +The Post-Conditions on Error defines the states that Must be unchanged given an error occurred during the `onAcknowledgePacket` handler. + +- If one payload fail, then all state changes happened on the sucessfull `onAcknowledgePacket` application callback execution MUST be reverted. +- The packetCommitment has not been cleared out +- mmmm. + +###### Pseudo-Code + +The ICS04 provides an example pseudo-code that enforce the above described conditions so that the following sequence of steps must occur for a packet to be acknowledged from module *1* on machine *A* to module *2* on machine *B*. + ```typescript function acknowledgePacket( packet: OpaquePacket, @@ -649,18 +700,20 @@ function acknowledgePacket( proofHeight: Height, relayer: string ) { - // in this specification, the source channel is the clientId + + // Channel and Client Checks + channel = getChannel(packet.sourceId) client = router.clients[packet.sourceId] - assert(client !== null) - // assert dest channel is sourceChannel's counterparty channel identifier - channel = getChannel(packet.destId) - assert(packet.sourceId == channel.clientId) + assert(client !== null) + assert(client.id === channel.clientId) + + //assert(packet.destId == channel.counterpartyChannelId) // verify we sent the packet and haven't cleared it out yet - assert(provableStore.get(packetCommitmentPath(packet.sourceId, packet.sequence)) === commitV2Packet(packet)) + // verify that the acknowledgement exist at the desired path ackPath = packetAcknowledgementPath(packet.destId, packet.sequence) merklePath = applyPrefix(channel.keyPrefix, ackPath) assert(client.verifyMembership( @@ -675,7 +728,7 @@ function acknowledgePacket( nAck=0 for payload in packet.data cbs = router.callbacks[payload.sourcePort] - success= cbs.OnAcknowledgePacket(packet.sourceId,payload, acknowledgement.appAcknowledgement[nAck], relayer) + success= cbs.OnAcknowledgePacket(packet.sourceId,payload, acknowledgement.appAcknowledgement[nAck]) abortUnless(success) nAck++ @@ -683,12 +736,6 @@ function acknowledgePacket( } ``` -TODO: - -- Preconditions -- Error Conditions -- Post Conditions - ##### Acknowledgement Envelope The acknowledgement returned from the remote chain is defined as arbitrary bytes in the IBC protocol. This data @@ -727,26 +774,55 @@ can no longer be executed and to allow the calling module to safely perform appr Calling modules MAY atomically execute appropriate application timeout-handling logic in conjunction with calling `timeoutPacket`. -In the case of an unordered channel, `timeoutPacket` checks the absence of the receipt key (which will have been written if the packet was received). Unordered channels are expected to continue in the face of timed-out packets. - +The `timeoutPacket` checks the absence of the receipt key (which will have been written if the packet was received). We pass the `relayer` address just as in [Receiving packets](#receiving-packets) to allow for possible incentivization here as well. +###### Ante-Conditions + +The ante-conditions defines what MUST be accomplished before chain `A` can properly execute the `timeoutPacket` for the IBC v2 packet. + +- PacketReceipt MUST be empty. +- PacketCommitment has not been cleared out yet. + +###### Error-Conditions + +The Error-Conditions defines the set of condition that MUST trigger an error. For the `acknowledgePacket` handler we can divide the source of errors in three main categories: + +- packetCommitment already clreared out +- PacketReceipt is not empty +- Unsuccessful payload execution + +###### Post-Conditions On Success + +The Post-Conditions on Success defines which state changes MUST have occurred if the `timeoutPacket` handler has been sucessfully executed. + +- All the application pointed in the payload have properly terminated the `onTimeoutPacket` callback execution, reverting the state changes occured in the `onSendPacket`. +- The packetCommitment has been cleared out. + +###### Post-Conditions On Error + +The Post-Conditions on Error defines the states that Must be unchanged given an error occurred during the `onAcknowledgePacket` handler. + +- If one payload fail, then all state changes happened on the sucessfull `onTimeoutPacket` application callback execution MUST be reverted. Mmm Note that here we may get stucked if one onTimeoutPacket applications always fails. +- The packetCommitment has not been cleared out + +###### Pseudo-Code + ```typescript function timeoutPacket( packet: OpaquePacket, proof: CommitmentProof, proofHeight: Height, relayer: string -) { +) { + // Channel and Client Checks + channel = getChannel(packet.sourceId) client = router.clients[packet.sourceId] - assert(client !== null) - // assert dest channel is sourceChannel's counterparty channel identifier - channel = getChannel(packet.destId) - assert(packet.sourceId == channel.channelId) - - // assert dest port is sourcePort's counterparty port identifier - assert(packet.destPort == ports[packet.sourcePort]) + assert(client !== null) + assert(client.id === channel.clientId) + + //assert(packet.destId == channel.counterpartyChannelId) // verify we sent the packet and haven't cleared it out yet assert(provableStore.get(packetCommitmentPath(packet.sourceId, packet.sequence)) @@ -760,6 +836,7 @@ function timeoutPacket( // check that timeout height or timeout timestamp has passed on the other end asert(packet.timeoutTimestamp > 0 && proofTimestamp >= packet.timeoutTimestamp) + // verify there is no packet receipt --> receivePacket has not been called receiptPath = packetReceiptPath(packet.destId, packet.sequence) merklePath = applyPrefix(channel.keyPrefix, receiptPath) assert(client.verifyNonMembership( @@ -771,19 +848,13 @@ function timeoutPacket( for payload in packet.data cbs = router.callbacks[payload.sourcePort] - success=cbs.OnTimeoutPacket(packet.sourceId,payload, relayer) + success=cbs.OnTimeoutPacket(packet.sourceId,payload) abortUnless(success) channelStore.delete(packetCommitmentPath(packet.sourceId, packet.sequence)) } ``` -TODO: - -- Preconditions -- Error Conditions -- Post Conditions - ##### Cleaning up state Packets must be acknowledged in order to be cleaned-up. From 34b1ea19cac9a61455eb2f3d8664bc3cdfbb0f74 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Thu, 3 Oct 2024 17:41:16 +0200 Subject: [PATCH 25/71] fixes --- spec/core/v2/ics-004-packet-semantics/README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 53faa2ff0..3ae922de5 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -11,9 +11,17 @@ created: 2019-03-07 modified: 2019-08-25 --- -## Synopsis +TODO : -TODO +- Synopsis +- Motivation +- Architectural Sketch +- Packet Flow Sketch +- Setup :: better explaination/division and Sketch +- Race condition reasoning +- Improve conditions set and presentation + +## Synopsis The ICS-04 requires the ICS-02, the ICS-24 and the packet data strcuture including the multi-data packet as defined in [here](packet-data). It defines the mechanism to create the IBC version 2 protocol channels, to pair two channels on different chains linking them up with the underlying clients to establish the root of trust for the secure packet delivery, the packet-flow semantics, the mechanisms to route the verification to the underlying clients, and how to route packets to their specific IBC applications. From c0f261aadb7bb70ae38f68ca45a6dcd9e0c1a077 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Thu, 3 Oct 2024 20:36:07 +0200 Subject: [PATCH 26/71] add mermaid diagrams, fixes --- .../v2/ics-004-packet-semantics/README.md | 124 ++++++++++++------ 1 file changed, 82 insertions(+), 42 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 3ae922de5..2e55e0663 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -15,15 +15,13 @@ TODO : - Synopsis - Motivation -- Architectural Sketch -- Packet Flow Sketch -- Setup :: better explaination/division and Sketch +- Architectural Sketch - Race condition reasoning - Improve conditions set and presentation ## Synopsis -The ICS-04 requires the ICS-02, the ICS-24 and the packet data strcuture including the multi-data packet as defined in [here](packet-data). +The ICS-04 requires the ICS-02, the ICS-24 and the packet data strcuture including the multi-data packet as defined in [here](https://github.com/cosmos/ibc/pull/1149). // Note change with file location when merged It defines the mechanism to create the IBC version 2 protocol channels, to pair two channels on different chains linking them up with the underlying clients to establish the root of trust for the secure packet delivery, the packet-flow semantics, the mechanisms to route the verification to the underlying clients, and how to route packets to their specific IBC applications. ### Motivation @@ -143,7 +141,7 @@ E.g. If a packet within 3 payloads intended for 3 different application is sent ```typescript type IBCRouter struct { callbacks: portId -> [Callback] - clients: channelId -> Client + clients: channelId -> Client // Needed? Maybe not anymore } ``` @@ -243,14 +241,34 @@ TODO The architecture of clients, connections, channels and packets: #### Setup -In order to start sending packets using IBC version 2, chain `A` and chain `B` MUST execute the entire setup following this set of procedures: +In order to create the conditions for the IBC version 2 packet stream, chain `A` and chain `B` MUST execute the entire setup following this set of procedures: -- Application registration: during the application module setup the application MUST be registered on the local IBC router. +- Application registration: during the application module setup the application callbacks MUST be registered on the local IBC router. - Client creation: chain `A` MUST create the `B` light client; `B` MUST create the `A` light client. - Channel creation: both chain `A` and chain `B` MUST create local IBC version 2 channels. - Channel registration: both chain MUST register their counterpartyId in the channel previously created. -While application registration is handled by the application module during initialization, and client creation is governed by [ICS-2](](../ics-002-client-semantics/README.md)), the channel creation and registration procedures are defined by ICS-04 and are detailed below. +If any of the steps has been missed, this would result in an incorrect setup error during the packet handlers execution. + +Below we provide the setup sequence diagram. +Note that as shown in the below setup sequence diagram the `createClient` message (as defined in ICS-02) may be bundled with the `createChannel` message in a single multiMsgTx. + +```mermaid +sequenceDiagram + Participant Chain A + Participant Relayer + Participant Chain B + Chain A --> Chain A: App callbacks registration on IBC Router + Chain B --> Chain B: App callbacks registration on IBC Router + Relayer ->> Chain A : createClient(B chain) + createChannel + Chain A ->> Relayer : clientId= X , channelId = Y + Relayer ->> Chain B : createClient(A chain) + createChannel + Chain B ->> Relayer : clientId= Z , channelId = W + Relayer ->> Chain A : registerChannel(channelId = W) + Relayer ->> Chain B : registerChannel(channelId = Y) +``` + +While the application callbacks registration MUST be handled by the application module during initialization, and client creation is governed by [ICS-2](.ics-002-client-semantics/README.md), the channel creation and registration procedures are defined by ICS-04 and are detailed below. ##### Channel creation @@ -295,10 +313,8 @@ To provide the chains a mechanism for the mutual and verifiable identification, Thus, IBC version 2 introduces a new message `registerChannel` that will store the counterpartyChannelId into the local channel structure. Thus, if the `registerChannel` message is submitted to both sides correctly, then both sides have mirrored pairs. Assuming they are correct, the underlying client on each side associated with the channel is unique and provides an authenticated stream of packet data between the two chains. If the `registerChannel` message submits the wrong counterpartyChannelId, this can lead to invalid behaviour; but this is equivalent to a relayer submitting an invalid channelId in place of a correct channelId for the desired chain. In the simplest case, we can rely on out-of-band social consensus to only send on valid pairs that represent a connection between the desired chains of the user; just as we currently rely on out-of-band social consensus that a given clientID and channel built on top of it is the valid, canonical identifier of our desired chain. ```typescript -function RegisterChannel( - clientID: bytes, // The clientId of the counterparty chain +function registerChannel( counterpartyChannelId: bytes, // the counterparty's channel identifier - counterpartyKeyPrefix: CommitmentPrefix, authentication: data, // implementation-specific authentication data ) { // Implementation-Specific Input Validation @@ -308,18 +324,20 @@ function RegisterChannel( assert(verify(authentication)) // Channel Checks - channelId=generateIdentifier(clientId,counterpartyKeyPrefix) + channelId=generateIdentifier() abortTransactionUnless(validatedIdentifier(channelId)) - channel=getChannel(channelId)) + channel=getChannel(channelId) abortTransactionUnless(channel !== null) - abortTransactionUnless(channel.clientId === clientId) - + // Creator Address Checks abortTransactionUnless(msg.signer()===getCreator(channelId,msg.signer())) // Channel manipulation channel.counterpartyChannelId=counterpartyChannelId + // Client registration on the router + router.clients[sourceChannelId]=channel.clientId + // Local Store privateStore.set(channelPath(counterpartyChannelId), channel) @@ -358,13 +376,54 @@ In the IBC protocol version 2, the packet flow is managed by four key function h - `acknowledgePacket` - `timeoutPacket` -For each handler, the ICS-04 specification defines a set of conditions that the IBC protocol must adhere to. These conditions ensure the proper execution of the handler by establishing requirements before execution (ante-conditions), possible error states during execution (error-conditions), and expected outcomes after execution (post-conditions). - -Thus, implementation that wants to comply with the specification of the IBC version 2 protocol MUST adheres to the condition set defined here. +For each handler, the ICS-04 specification defines a set of conditions that the IBC protocol must adhere to. These conditions ensure the proper execution of the handler by establishing requirements before execution (ante-conditions), possible error states during execution (error-conditions), and expected outcomes after execution (post-conditions). Thus, implementation that wants to comply with the specification of the IBC version 2 protocol MUST adheres to the condition set defined in the specific handler section below. + +Note that the execution of the four handler above described, upon a unique packet, cannot be combined in any arbitrary order. We provide the two possible example scenarios described with sequence diagrmas. + +Scenario execution with acknowledgement `A` to `B` - set of actions: `sendPacket` -> `receivePacket` -> `acknowledgePacket` + +```mermaid +sequenceDiagram + participant Chain A + participant B Light Client + participant Relayer + participant Chain B + participant A Light Client + Chain A ->> Chain A : sendPacket + Chain A --> Chain A : app execution + Chain A --> Chain A : packetCommitment + Relayer ->> Chain B: relayPacket + Chain B ->> Chain B: receivePacket + Chain B -->> A Light Client: verifyMembership(packetCommitment) + Chain B --> Chain B : app execution + Chain B --> Chain B: writeAck + Chain B --> Chain B: writePacketReceipt + Relayer ->> Chain A: relayAck + Chain A ->> Chain A : acknowldgePacket + Chain A -->> B Light Client: verifyMembership(packetAck) + Chain A --> Chain A : app execution + Chain A --> Chain A : Delete packetCommitment +``` -#### Packet Flow & handling +Scenario timeout execution `A` to `B` - set of actions: `sendPacket` -> `timeoutPacket` + +```mermaid +sequenceDiagram + participant Chain A + participant B Light Client + participant Relayer + participant Chain B + participant A Light Client + Chain A ->> Chain A : sendPacket + Chain A --> Chain A : app execution + Chain A --> Chain A : packetCommitment + Chain A ->> Chain A : TimeoutPacket + Chain A -->> B Light Client: verifyNonMembership(PacketReceipt) + Chain A --> Chain A : app execution + Chain A --> Chain A : Delete packetCommitment +``` -TODO : Adapt to new flow +Given a configuration where we are sending a packet from `A` to `B` then chain `A` can call either, `sendPacket`,`acknowledgePacket` and `timeoutPacket` while chain `B` can only execute the `receivePacket` handler. ##### Sending packets @@ -387,12 +446,7 @@ Note that the full packet is not stored in the state of the chain - merely a sho The Ante-Conditions defines what MUST be accomplished before two chains can start sending IBC v2 packets. -Both chains `A` and `B` MUST have executed independently the following set of actions: - -- Application registration -- Client creation -- Channel creation -- channel registration +For the `sendPacket` handler, both chains `A` and `B` MUST have executed independently the setup procedure previosuly described. ###### Error-Conditions @@ -530,7 +584,7 @@ The Post-Conditions on Success defines which state changes MUST have occurred if The Post-Conditions on Error defines the states that Must be unchanged given an error occurred during the `onReceivePacket` handler. -- If one payload fail, then all state changes happened on the sucessfull `onReceive` application callback execution MUST be reverted. +- If one payload fail, then all state changes happened on the sucessfull `onReceivePacket` application callback execution MUST be reverted. - If timeoutTimestamp has elapsed then no state changes occurred. (Is this true? Shall we write the timeout_sentinel_receipt?) - mmmm. @@ -917,21 +971,7 @@ Data structures & encoding can be versioned at the application level. Core logic ## History -Jun 5, 2019 - Draft submitted - -Jul 4, 2019 - Modifications for unordered channels & acknowledgements - -Jul 16, 2019 - Alterations for multi-hop routing future compatibility - -Jul 29, 2019 - Revisions to handle timeouts after connection closure - -Aug 13, 2019 - Various edits - -Aug 25, 2019 - Cleanup - -Jan 10, 2022 - Add ORDERED_ALLOW_TIMEOUT channel type and appropriate logic - -Mar 28, 2023 - Add `writeChannel` function to write channel end after executing application callback +Oct X, 2024 - [Draft submitted](https://github.com/cosmos/ibc/pull/1148) ## Copyright From d679315649bd99db6675b40db86397a6e5ad64b0 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Fri, 4 Oct 2024 12:54:11 +0200 Subject: [PATCH 27/71] router fixes --- .../v2/ics-004-packet-semantics/README.md | 38 ++++++++++-------- .../setup_final_state.png | Bin 0 -> 44721 bytes 2 files changed, 21 insertions(+), 17 deletions(-) create mode 100644 spec/core/v2/ics-004-packet-semantics/setup_final_state.png diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 2e55e0663..79889d3d4 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -117,8 +117,8 @@ The protocol introduces standardized packet receipts that will serve as sentinel ```typescript enum PacketReceipt { - SUCCESSFUL_RECEIPT = []byte{0x01}, - TIMEOUT_RECEIPT = []byte{0x02}, + SUCCESSFUL_RECEIPT = byte{0x01}, + TIMEOUT_RECEIPT = byte{0x02}, } ``` @@ -141,7 +141,7 @@ E.g. If a packet within 3 payloads intended for 3 different application is sent ```typescript type IBCRouter struct { callbacks: portId -> [Callback] - clients: channelId -> Client // Needed? Maybe not anymore + // clients: channelId -> Client // Needed? Maybe not anymore } ``` @@ -264,10 +264,14 @@ sequenceDiagram Chain A ->> Relayer : clientId= X , channelId = Y Relayer ->> Chain B : createClient(A chain) + createChannel Chain B ->> Relayer : clientId= Z , channelId = W - Relayer ->> Chain A : registerChannel(channelId = W) - Relayer ->> Chain B : registerChannel(channelId = Y) + Relayer ->> Chain A : registerChannel(channelId = Y, counterpartyChannelId = W) + Relayer ->> Chain B : registerChannel(channelId = W, counterpartyChannelId = Y) ``` +Once the set up is executed the system should be in a similar state: + +![Setup Final State](setup_final_state.png) + While the application callbacks registration MUST be handled by the application module during initialization, and client creation is governed by [ICS-2](.ics-002-client-semantics/README.md), the channel creation and registration procedures are defined by ICS-04 and are detailed below. ##### Channel creation @@ -314,6 +318,7 @@ Thus, IBC version 2 introduces a new message `registerChannel` that will store t ```typescript function registerChannel( + channelId: bytes, // local chain channel identifier counterpartyChannelId: bytes, // the counterparty's channel identifier authentication: data, // implementation-specific authentication data ) { @@ -324,7 +329,6 @@ function registerChannel( assert(verify(authentication)) // Channel Checks - channelId=generateIdentifier() abortTransactionUnless(validatedIdentifier(channelId)) channel=getChannel(channelId) abortTransactionUnless(channel !== null) @@ -336,10 +340,10 @@ function registerChannel( channel.counterpartyChannelId=counterpartyChannelId // Client registration on the router - router.clients[sourceChannelId]=channel.clientId + //router.clients[sourceChannelId]=channel.clientId // clients on router removed // Local Store - privateStore.set(channelPath(counterpartyChannelId), channel) + privateStore.set(channelPath(channelId), channel) } ``` @@ -485,11 +489,11 @@ function sendPacket( // Setup checks - channel and client channel = getChannel(sourceChannelId) - client = router.clients[sourceChannelId] + client = channel.clientId // removed client on router --> client = router.clients[sourceChannelId] // can it be client = channel.clientId assert(client !== null) // Evaluate usefulness of this check - assert(client.id === channel.clientId) + // assert(client.id === channel.clientId) // timeoutTimestamp checks // disallow packets with a zero timeoutTimestamp @@ -600,10 +604,10 @@ function recvPacket( relayer: string) { // Channel and Client Checks - channel = getChannel(packet.destId) - client = router.clients[packet.destId] + channel = getChannel(packet.destId) // if I use packet.dest which is a channelId + client = channel.clientId // removed client on router --> client = router.clients[packet.destId] // client = channel.clientId assert(client !== null) - assert(client.id === channel.clientId) + //assert(client.id === channel.clientId) // useful? //assert(packet.sourceId == channel.counterpartyChannelId) Unnecessary? @@ -765,10 +769,10 @@ function acknowledgePacket( // Channel and Client Checks channel = getChannel(packet.sourceId) - client = router.clients[packet.sourceId] + client = channel.clientId //client = router.clients[packet.sourceId] assert(client !== null) - assert(client.id === channel.clientId) + //assert(client.id === channel.clientId) //assert(packet.destId == channel.counterpartyChannelId) @@ -879,10 +883,10 @@ function timeoutPacket( ) { // Channel and Client Checks channel = getChannel(packet.sourceId) - client = router.clients[packet.sourceId] + client = channel.clientId //client = router.clients[packet.sourceId] assert(client !== null) - assert(client.id === channel.clientId) + // assert(client.id === channel.clientId) //assert(packet.destId == channel.counterpartyChannelId) diff --git a/spec/core/v2/ics-004-packet-semantics/setup_final_state.png b/spec/core/v2/ics-004-packet-semantics/setup_final_state.png new file mode 100644 index 0000000000000000000000000000000000000000..2413c8bfa4dc9eff48a05ac8dfef50fd1ace7ade GIT binary patch literal 44721 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≧Q%%_;X_HSMp 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 O@YB}RN7te3qy7i++8ww6 literal 0 HcmV?d00001 From 6cb638a26fca61933f2222aceb39b64040317801 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Fri, 4 Oct 2024 16:54:09 +0200 Subject: [PATCH 28/71] improvements and fixes --- .../v2/ics-004-packet-semantics/README.md | 215 +++++++++--------- 1 file changed, 111 insertions(+), 104 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 79889d3d4..603af69c9 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -4,8 +4,8 @@ title: Packet Semantics stage: draft category: IBC/TAO kind: instantiation -requires: 2, 24 -version compatibility: ibc-go v7.0.0 +requires: [2](../ics-002-client-semantics/), [24](../ics-024-host-requirements/), [packet-data](https://github.com/cosmos/ibc/pull/1149) +version compatibility: ibc-go v10.0.0 author: Christopher Goes created: 2019-03-07 modified: 2019-08-25 @@ -13,16 +13,21 @@ modified: 2019-08-25 TODO : -- Synopsis - Motivation -- Architectural Sketch +- Improve Sketchs - Race condition reasoning - Improve conditions set and presentation +- Review Timeout carefully ## Synopsis -The ICS-04 requires the ICS-02, the ICS-24 and the packet data strcuture including the multi-data packet as defined in [here](https://github.com/cosmos/ibc/pull/1149). // Note change with file location when merged -It defines the mechanism to create the IBC version 2 protocol channels, to pair two channels on different chains linking them up with the underlying clients to establish the root of trust for the secure packet delivery, the packet-flow semantics, the mechanisms to route the verification to the underlying clients, and how to route packets to their specific IBC applications. +This standard defines the channel and packet semantics necessary for state machines implementing the Inter-Blockchain Communication (IBC) protocol, specifically version 2, to enable secure, verifiable, and efficient cross-chain messaging. + +It specifies the mechanisms required to establish channels between two state machines (blockchains) where a channel serves as a semantic link between the chains and their counterparty light client representation, ensuring that both chains can process and verify packets exchanged between them. + +The standard then details the processes for transmitting, receiving, acknowledging, and timing out data packets. The packet-flow semantics guarantee exactly-once packet delivery between chains, utilizing on-chain light clients for state verification and providing efficient routing of packet data to specific IBC applications. + +Additionally, this document defines the ante-conditions, error conditions, and post-conditions for each packet flow handler. By using a well-defined packet interface and clear handling processes, ICS-04 aims to ensure consistency and security without imposing constraints on the internal workings of the state machines involved. Instead, it focuses on the mechanisms to establish the root of trust between two distinct chains, routing, verification, and application-level delivery guarantees required by the IBC protocol. ### Motivation @@ -36,56 +41,43 @@ The IBC version 2 will provide packet delivery between two chains communicating ### Definitions -`ConsensusState` is as defined in [ICS 2](../ics-002-client-semantics). - -`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. +`get`, `set`, `delete`, and module-system related primitives are as defined in [ICS 24](../ics-024-host-requirements). -`Identifier`, `get`, `set`, `delete`, `getCurrentHeight`, and module-system related primitives are as defined in [ICS 24](../ics-024-host-requirements). - -A `channel` is a pipeline for exactly-once packet delivery between specific modules which are properly registered on separate blockchains, which has at least one end capable of sending packets and one end capable of receiving packets. 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. A `channel` is defined as a data structure responsible for maintaining the counterparty information necessary to establish the root of trust for securing the interchain communication: +A `channel` is a data structure that facilitates exactly-once packet delivery between two blockchains acting as a communication pipeline between specific modules registered on separate chains, allowing for secure, verifiable transmission of packets. Channels can be registered to establish a semantic link between two chains and their respective light clients, ensuring that both chains can process and verify the packets exchanged. To establish the root of trust for secure interchain communication with a counterparty chain, each chain MUST register a channel maintaining the necessary counterparty information, such as the channel identifier of the counterparty chain, the light client identifier of the counterparty chain and the path used to store packet flow messages. ```typescript interface Channel { - clientId: bytes - counterpartyChannelId: bytes - keyPrefix: CommitmentPrefix + clientId: bytes // local light client id of the counterparty chain. + counterpartyChannelId: bytes // counterparty channel id. + keyPrefix: CommitmentPrefix // key path that the counterparty will use to prove its store packet flow messages. } ``` -Where : - -- `clientId` is the local light client id of the counterparty chain. -- `counterpartyChannelId` is the counterparty channel id. -- `keyPrefix` is the key path that the counterparty will use to prove its store packet flow messages. +The `Packet`, `Payload`, `Encoding` and the `Acknowledgement` interfaces are as defined in [packet specification](https://github.com/cosmos/ibc/blob/c7b2e6d5184b5310843719b428923e0c5ee5a026/spec/core/v2/ics-004-packet-semantics/PACKET.md). -The `Packet`, `Payload`, `Encoding` and the `Acknowledgement` interfaces are as defined in [packet specification](https://github.com/cosmos/ibc/blob/c7b2e6d5184b5310843719b428923e0c5ee5a026/spec/core/v2/ics-004-packet-semantics/PACKET.md). For convenience, following we recall their structures. +For convenience, following we recall their structures. A `Packet`, in the interblockchain communication protocol, is a particular interface defined as follows: ```typescript interface Packet { - sourceId: bytes, - destId: bytes, - sequence: uint64 - timeout: uint64, - data: [Payload] + sourceId: bytes, // identifier on the source chain. + destId: bytes, // identifier on the dest chain. + sequence: uint64, // number that corresponds to the order of sent packets. + timeout: uint64, // indicates the UNIX timestamp in seconds and is encoded in LittleEndian. It must be passed on the destination chain and once elapsed, will no longer allow the packet processing, and will instead generate a time-out. + data: [Payload] // data } ``` -- The `sourceId` is the identifier on the source chain. -- The `destId` is the identifier on the dest chain. -- The `sequence` number corresponds to the order of sent packets. -- The `timeout` indicates the UNIX timestamp in seconds and is encoded in LittleEndian. It must be passed on the destination chain and once elapsed, will no longer allow the packet processing, and will instead generate a time-out. - The `Payload` is a particular interface defined as follows: ```typescript interface Payload { - sourcePort: bytes, - destPort: bytes, - version: string, - encoding: Encoding, - appData: bytes, + sourcePort: bytes, // identifies the source application port + destPort: bytes, // identifies the dest application port + version: string, // application version + encoding: Encoding, // used encoding - allows the specification of custom data encoding among those agreed in the `Encoding` enum + appData: bytes, // app specific data } enum Encoding { @@ -97,12 +89,6 @@ enum Encoding { } ``` -- The `sourcePort` identifies the source application. -- The `destPort` identifies the destination application. -- The `version` specifies the application version to be used. -- The `encoding` allows the specification of custom data encoding among those agreed in the `Encoding` enum. -- The `appData` is defined by the application of the associated modules. - When the array of payloads, passed-in the packet, is populated with multiple values, the system will handle the packet as a multi-data packet. 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. @@ -118,7 +104,7 @@ The protocol introduces standardized packet receipts that will serve as sentinel ```typescript enum PacketReceipt { SUCCESSFUL_RECEIPT = byte{0x01}, - TIMEOUT_RECEIPT = byte{0x02}, + //TIMEOUT_RECEIPT = byte{0x02}, // Should we allow the recivePacket to store a timeout_receipt and do nothing or we just abort the tx? } ``` @@ -126,17 +112,16 @@ The `Acknowledgement` is a particular interface defined as follows: ```typescript interface Acknowledgement { - appAcknowledgement: [bytes] + appAcknowledgement: [bytes] // array of bytes. Each element of the array contains an acknowledgement from a specific application } ``` -- The `appAcknowledgement` is an array of bytes. Each element of the array identifies the source application acknowledgement. - An application may not need to return an acknowledgment. In this case, it may return a sentinel acknowledgement value `SENTINEL_ACKNOWLEDGMENT` which will be the single byte in the byte array: `bytes(0x01)`. In this case, the IBC `acknowledgePacket` handler will still do the core IBC acknowledgment logic but it will not call the application's acknowledgePacket callback. -E.g. If a packet within 3 payloads intended for 3 different application is sent out, the expectation is that each of the payload is acted upon in the same order as it has been placed in the packet. Likewise, the array of appAcknowledgement is expected to be populated within the same order. +> **Example**: If a packet within 3 payloads intended for 3 different application is sent out, the expectation is that each of the payload is acted upon in the same order as it has been placed in the packet. Likewise, the array of `appAcknowledgement` is expected to be populated within the same order. -- The `IBCRouter` contains a mapping from the application port and the supported callbacks and as well as a mapping from channelId to the underlying client. +- The `IBCRouter` contains a mapping from the application port and the supported callbacks. +// and as well as a mapping from channelId to the underlying client. ```typescript type IBCRouter struct { @@ -145,7 +130,7 @@ type IBCRouter struct { } ``` -The IBCRouter struct MUST be set by the application modules during the module setup to carry out the application registration procedure. +The proper registration of the application callbacks in the local `IBCRouter`, is responsibility of the chain. Without this registration process, the packets cannot be processed by the application. - The `MAX_TIMEOUT_DELTA` is intendend as the max difference between currentTimestamp and timeoutTimestamp that can be given in input. @@ -153,13 +138,18 @@ The IBCRouter struct MUST be set by the application modules during the module se const MAX_TIMEOUT_DELTA = TBD ``` +- The `ante-conditions` define what MUST hold true before an action can occurr in the IBC v2 packet flow. +- The `error-conditions` define the set of expected errors. +- The `post-conditions on success` defines the expected final state changes on success. +- The `post-conditions on error` the expected final state on error + ### 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. -- The system MUST be able to process the multiple payloads contained in a single IBC packet, to reduce the amount of packet flows. +- The system MUST be able to process atomically the multiple payloads contained in a single IBC packet, to reduce the amount of packet flows. #### Exactly-once delivery @@ -181,9 +171,9 @@ const MAX_TIMEOUT_DELTA = TBD #### Store paths -The ICS-04 use the protocol paths, defined in [ICS-24](../ics-024-host-requirements/README.md), `packetCommitmentPath`, `packetRecepitPath` and `packetAcknowledgementPath`. These paths MUST be used as the referece locations in the provableStore to prove respectilvey the packet commitment, the receipt and the acknowledgment to the counterparty chain. +The ICS-04 use the protocol paths, defined in [ICS-24](../ics-024-host-requirements/README.md), `packetCommitmentPath`, `packetRecepitPath` and `packetAcknowledgementPath`. The paths MUST be used as the referece locations in the provableStore to prove respectilvey the packet commitment, the receipt and the acknowledgment to the counterparty chain. -Thus Constant-size commitments to packet data fields are stored under the packet sequence number: +Thus, Constant-size commitments to packet data fields are stored under the packet sequence number: ```typescript function packetCommitmentPath(sourceId: bytes, sequence: BigEndianUint64): Path { @@ -193,7 +183,9 @@ function packetCommitmentPath(sourceId: bytes, sequence: BigEndianUint64): Path 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`. While in the case of a timeout, the destination chain SHOULD write a sentinel timeout value `TIMEOUT_RECEIPT` if the packet is received after the specified timeout. +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`. + +//NOTE: Do we want this? Maybe not useful. While in the case of a timeout, the destination chain SHOULD write a sentinel timeout value `TIMEOUT_RECEIPT` if the packet is received after the specified timeout. ```typescript function packetReceiptPath(sourceId: bytes, sequence: BigEndianUint64): Path { @@ -237,11 +229,9 @@ function creatorPath(channelId: Identifier, creator: address): Path { ### Sub-protocols -TODO The architecture of clients, connections, channels and packets: - #### Setup -In order to create the conditions for the IBC version 2 packet stream, chain `A` and chain `B` MUST execute the entire setup following this set of procedures: +To start the secure packet stream between the chains, chain `A` and chain `B` MUST execute the entire setup following this set of procedures: - Application registration: during the application module setup the application callbacks MUST be registered on the local IBC router. - Client creation: chain `A` MUST create the `B` light client; `B` MUST create the `A` light client. @@ -274,10 +264,34 @@ Once the set up is executed the system should be in a similar state: While the application callbacks registration MUST be handled by the application module during initialization, and client creation is governed by [ICS-2](.ics-002-client-semantics/README.md), the channel creation and registration procedures are defined by ICS-04 and are detailed below. +The ICS-04 specification defines a set of conditions that the IBC protocol must adhere to. These conditions ensure the proper execution of the function handlers by establishing requirements before execution (ante-conditions), possible error conditions during execution (error-conditions), and expected outcomes after execution (post-conditions). Thus, implementation that wants to comply with the specification of the IBC version 2 protocol MUST adheres to the specified conditions. + ##### Channel creation The channel creation process establishes the communication pathway between two chains. The procedure ensures both chains have a mutually recognized channel, facilitating packet transmission through authenticated streams. +###### Ante-Conditions + +- The clientId provided in input to createChannel MUST exist. + +###### Error-Conditions + +- Incorrect clientId +- Unexpected keyPrefix format + +###### Post-Conditions On Success + +- A channel is set in store and it's accessible with key channelId. +- The creator is set in store and it's accessible with key [channelId,address]. + +###### Post-Conditions On Error + +- If one payload fail, then all state changes happened on the sucessfull application execution must be reverted. +- No packetCommitment has been generated. +- The sequence number bind to sourceId MUST be unchanged. + +###### Pseudo-Code + ```typescript function createChannel( clientId: bytes, @@ -285,6 +299,9 @@ function createChannel( // Implementation-Specific Input Validation // All implementations MUST ensure the inputs value are properly validated and compliant with this specification + client=getClient(clientId) + assert(client!==null) + assert(isFormatOk(counterpartyKeyPrefix)) // Channel Checks channelId = generateIdentifier() @@ -310,11 +327,29 @@ Note that `createClient` message (as defined in ICS-02) may be bundled with the ##### Channel registration and counterparty idenfitifcation -Each IBC chain MUST have the ability to idenfity its counterparty. With a client, we can prove any key/value path on the counterparty. However, without knowing which identifier the counterparty uses when it sends messages to us, we cannot differentiate between messages sent from the counterparty to our chain vs messages sent from the counterparty with other chains. Most implementations will not be able to store the ICS-24 paths directly as a key in the global namespace, but will instead write to a reserved, prefixed keyspace so as not to conflict with other application state writes. +Each IBC chain MUST have the ability to idenfity its counterparty to ensure valid communication. While a client can prove any key/value path on the counterparty, knowing which identifier the counterparty uses when it sends messages to us is essential to prevent confusion between messages intended for different chains. + +To enable mutual and verifiable identification, IBC version 2 introduces a `registerChannel` procedure. This process stores the `counterpartyChannelId` in the local channel structure, ensuring both chains have mirrored pairs. With the correct registration, the unique clients on each side provide an authenticated stream of packet data. Social consensus outside the protocol is relied upon to ensure only valid pairs are used, representing connections between the correct chains. -To provide the chains a mechanism for the mutual and verifiable identification, the IBC version 2 defines the channel registration procedure to complement the store of the counterparty information in the channel that will include both its identifier for our chain and as well as the key prefix under which it will write the provable ICS-24 paths. +###### Ante-Conditions + +- The channelID provided in input MUST properly resolve to a channel. + +###### Error-Conditions + +- Incorrect channelId +- Authentication Failed + +###### Post-Conditions On Success + +- The channel in store contains the counterpartyChannelId information and it's accessible with key channelId. -Thus, IBC version 2 introduces a new message `registerChannel` that will store the counterpartyChannelId into the local channel structure. Thus, if the `registerChannel` message is submitted to both sides correctly, then both sides have mirrored pairs. Assuming they are correct, the underlying client on each side associated with the channel is unique and provides an authenticated stream of packet data between the two chains. If the `registerChannel` message submits the wrong counterpartyChannelId, this can lead to invalid behaviour; but this is equivalent to a relayer submitting an invalid channelId in place of a correct channelId for the desired chain. In the simplest case, we can rely on out-of-band social consensus to only send on valid pairs that represent a connection between the desired chains of the user; just as we currently rely on out-of-band social consensus that a given clientID and channel built on top of it is the valid, canonical identifier of our desired chain. +###### Post-Conditions On Error + +- On the first call the channel in store contains the counterpartyChannelId as an empty field. +- On the second call the channel in store contains the old counterpartyChannelId information. + +###### Pseudo-Code ```typescript function registerChannel( @@ -348,8 +383,7 @@ function registerChannel( } ``` -The `registerChannel` method allows for authentication data that implementations may verify before storing the provided counterparty identifier. The strongest authentication possible is to have a valid clientState and consensus state of our chain in the authentication along with a proof it was stored at the claimed counterparty identifier. -A simpler but weaker authentication would simply be to check that the `registerChannel` message is sent by the same relayer that initialized the client. This would make the client parameters completely initialized by the relayer. Thus, users must verify that the client is pointing to the correct chain and that the counterparty identifier is correct as well before using the lite channel identified by the provided client-client pair. +The `registerChannel` method allows for authentication data that implementations may verify before storing the provided counterparty identifier. The strongest authentication possible is to have a valid clientState and consensus state of our chain in the authentication along with a proof it was stored at the claimed counterparty identifier. A simpler but weaker authentication would simply be to check that the `registerChannel` message is sent by the same relayer that initialized the client. This would make the client parameters completely initialized by the relayer. Thus, users must verify that the client is pointing to the correct chain and that the counterparty identifier is correct as well before using the pair. ```typescript // getChannel retrieves the stored channel given the counterparty channel-id. @@ -380,8 +414,6 @@ In the IBC protocol version 2, the packet flow is managed by four key function h - `acknowledgePacket` - `timeoutPacket` -For each handler, the ICS-04 specification defines a set of conditions that the IBC protocol must adhere to. These conditions ensure the proper execution of the handler by establishing requirements before execution (ante-conditions), possible error states during execution (error-conditions), and expected outcomes after execution (post-conditions). Thus, implementation that wants to comply with the specification of the IBC version 2 protocol MUST adheres to the condition set defined in the specific handler section below. - Note that the execution of the four handler above described, upon a unique packet, cannot be combined in any arbitrary order. We provide the two possible example scenarios described with sequence diagrmas. Scenario execution with acknowledgement `A` to `B` - set of actions: `sendPacket` -> `receivePacket` -> `acknowledgePacket` @@ -427,7 +459,7 @@ sequenceDiagram Chain A --> Chain A : Delete packetCommitment ``` -Given a configuration where we are sending a packet from `A` to `B` then chain `A` can call either, `sendPacket`,`acknowledgePacket` and `timeoutPacket` while chain `B` can only execute the `receivePacket` handler. +Given a configuration where we are sending a packet from `A` to `B` then chain `A` can call either, `sendPacket`,`acknowledgePacket` and `timeoutPacket` while chain `B` can only execute the `receivePacket` handler. The `acknowledgePacket` is not a valid action if `receivePacket` has not been executed. `timeoutPacket` is not a valid action if `receivePacket` occurred. ##### Sending packets @@ -437,7 +469,7 @@ The `sendPacket` core function MUST execute the applications logic atomically tr The IBC handler performs the following steps in order: -- Checks that the underlying clients is properly registered in the IBC router. +- Checks that the underlying clients is valid. - Checks that the timeout specified has not already passed on the destination chain - Executes the `onSendPacket` ∀ Payload included in the packet. - Stores a constant-size commitment to the packet data & packet timeout @@ -448,30 +480,23 @@ Note that the full packet is not stored in the state of the chain - merely a sho ###### Ante-Conditions -The Ante-Conditions defines what MUST be accomplished before two chains can start sending IBC v2 packets. - -For the `sendPacket` handler, both chains `A` and `B` MUST have executed independently the setup procedure previosuly described. +- Chains `A` and `B` MUST be in a setup final state. +- Inputs channelId and timeoutTimestamp are valid. ###### Error-Conditions -The Error-Conditions defines the set of condition that MUST trigger an error. For the `sendPacket` handler we can divide the source of errors in three main categories: - -- Incorrect setup +- Incorrect setup - includes invalid client and invalid channelId - Invalid timeoutTimestamp - Unsuccessful payload execution ###### Post-Conditions On Success -The Post-Conditions on Success defines which state changes MUST have occurred if the `sendPacket` handler has been sucessfully executed. - - All the application contained in the payload have properly terminated the `onSendPacket` callback execution. - The packetCommitment has been generated. - The sequence number bind to sourceId MUST have been incremented by 1. ###### Post-Conditions On Error -The Post-Conditions on Error defines the states that Must be unchanged given an error occurred during the `sendPacket` handler. - - If one payload fail, then all state changes happened on the sucessfull application execution must be reverted. - No packetCommitment has been generated. - The sequence number bind to sourceId MUST be unchanged. @@ -551,8 +576,7 @@ Atomically in conjunction with calling `recvPacket`, the modules/application ref The IBC handler performs the following steps in order: -- Checks that the clients is properly set in IBC router -- Checks that the packet metadata matches the channel & connection information +- Checks that the clients is valid - Checks that the timeout timestamp is not yet passed - Checks the inclusion proof of packet data commitment in the outgoing chain's state - Sets a store path to indicate that the packet has been received @@ -562,32 +586,24 @@ We pass the address of the `relayer` that signed and submitted the packet to ena ###### Ante-Conditions -Given that chain `A` has sucessfully sent a packet, the ante-conditions defines what MUST be accomplished before chain `B` can properly receive the IBC v2 packets. - - Chain `A` MUST have stored the packetCommitment under the keyPrefix registered in the chain `B` channelEnd. - TimeoutTimestamp MUST not have elasped yet. - PacketReceipt for the specific keyPrefix and sequence MUST be empty (e.g. receivePacket has not been called yet) ###### Error-Conditions -The Error-Conditions defines the set of condition that MUST trigger an error. For the `receivePacket` handler we can divide the source of errors in three main categories: - - Packet Errors: invalid packetCommitment, packetReceipt already exist - Invalid timeoutTimestamp - Unsuccessful payload execution ###### Post-Conditions On Success -The Post-Conditions on Success defines which state changes MUST have occurred if the `receivePacket` handler has been sucessfully executed. - - All the application pointed in the payload have properly terminated the `onReceivePacket` callback execution. - The packetReceipt has been written. - The acknowledgemnet has been written. ###### Post-Conditions On Error -The Post-Conditions on Error defines the states that Must be unchanged given an error occurred during the `onReceivePacket` handler. - - If one payload fail, then all state changes happened on the sucessfull `onReceivePacket` application callback execution MUST be reverted. - If timeoutTimestamp has elapsed then no state changes occurred. (Is this true? Shall we write the timeout_sentinel_receipt?) - mmmm. @@ -673,13 +689,13 @@ function recvPacket( ##### Writing acknowledgements -NOTE: Currently only process synchronous acks. +NOTE: Currently the system only handles synchronous acks. -The `writeAcknowledgement` function is called by the IBC handler once all `onRecvPacket` application modules callabacks have been triggered and have returned their specific acknowledgment in order to write data which resulted from processing an IBC packet that the sending chain can then verify, a sort of "execution receipt" or "RPC call response". +The `writeAcknowledgement` function is called by the IBC handler once all `onRecvPacket` application modules callabacks have been triggered and have returned their specific acknowledgment in order to write data which resulted from processing an IBC packet that the sending chain can then verify. Writing acknowledgement serves as a sort of "execution receipt" or "RPC call response". -This is a synchronous acknowledgement, thus `writeAcknowledgement` MUST be called in the same transaction (atomically) with `recvPacket` and and application callback logic execution. +Since at the day of writing, IBC version 2 only support synchronous acknowledgement, `writeAcknowledgement` MUST be called in the same transaction (atomically) with `recvPacket` and the application callback logic execution. -`writeAcknowledgement` *does not* check if the packet being acknowledged was actually received, because this would result in proofs being verified twice for acknowledged packets. This aspect of correctness is the responsibility of the IBC handler. +`writeAcknowledgement` is called in a `recvPacket`, thus it *does not* check if the packet being acknowledged was actually received, because this would result in proofs being verified twice for acknowledged packets. This aspect of correctness is the responsibility of the IBC handler. The IBC handler performs the following steps in order: @@ -716,14 +732,10 @@ function writeAcknowledgement( ##### Processing acknowledgements -The `acknowledgePacket` function is called by the IBC handler to process the acknowledgement of a packet previously sent by the source chain. - -The `acknowledgePacket` also cleans up the packet commitment, which is no longer necessary since the packet has been received and acted upon. +The `acknowledgePacket` function is called by the IBC handler to process the acknowledgement of a packet previously sent by the source chain that has been received on the destination chain. The `acknowledgePacket` also cleans up the packet commitment, which is no longer necessary since the packet has been received and acted upon. The IBC hanlder MUST atomically trigger the callbacks execution of appropriate application acknowledgement-handling logic in conjunction with calling `acknowledgePacket`. -We pass the `relayer` address just as in [Receiving packets](#receiving-packets) to allow for possible incentivization here as well. - ###### Ante-Conditions Given that at this point of the packet flow, chain `B` has sucessfully received a packet, the ante-conditions defines what MUST be accomplished before chain `A` can properly execute the `acknowledgePacket` for the IBC v2 packet. @@ -758,6 +770,9 @@ The Post-Conditions on Error defines the states that Must be unchanged given an The ICS04 provides an example pseudo-code that enforce the above described conditions so that the following sequence of steps must occur for a packet to be acknowledged from module *1* on machine *A* to module *2* on machine *B*. +// What to do with the relayer? Do we want to keep it? +// We pass the `relayer` address just as in [Receiving packets](#receiving-packets) to allow for possible incentivization here as well. + ```typescript function acknowledgePacket( packet: OpaquePacket, @@ -834,7 +849,7 @@ Note that in order to avoid any possible "double-spend" attacks, the timeout alg ##### Sending end -The `timeoutPacket` function is called by a module which originally attempted to send a packet to a counterparty module, +The `timeoutPacket` function is called by the IBC hanlder by the chain that attempted to send a packet to a counterparty module, where the timeout height or timeout timestamp has passed on the counterparty chain without the packet being committed, to prove that the packet can no longer be executed and to allow the calling module to safely perform appropriate state transitions. @@ -845,30 +860,22 @@ We pass the `relayer` address just as in [Receiving packets](#receiving-packets) ###### Ante-Conditions -The ante-conditions defines what MUST be accomplished before chain `A` can properly execute the `timeoutPacket` for the IBC v2 packet. - - PacketReceipt MUST be empty. - PacketCommitment has not been cleared out yet. ###### Error-Conditions -The Error-Conditions defines the set of condition that MUST trigger an error. For the `acknowledgePacket` handler we can divide the source of errors in three main categories: - - packetCommitment already clreared out - PacketReceipt is not empty - Unsuccessful payload execution ###### Post-Conditions On Success -The Post-Conditions on Success defines which state changes MUST have occurred if the `timeoutPacket` handler has been sucessfully executed. - - All the application pointed in the payload have properly terminated the `onTimeoutPacket` callback execution, reverting the state changes occured in the `onSendPacket`. - The packetCommitment has been cleared out. ###### Post-Conditions On Error -The Post-Conditions on Error defines the states that Must be unchanged given an error occurred during the `onAcknowledgePacket` handler. - - If one payload fail, then all state changes happened on the sucessfull `onTimeoutPacket` application callback execution MUST be reverted. Mmm Note that here we may get stucked if one onTimeoutPacket applications always fails. - The packetCommitment has not been cleared out @@ -923,7 +930,7 @@ function timeoutPacket( ##### Cleaning up state -Packets must be acknowledged in order to be cleaned-up. +Packets must be acknowledged or timed-out in order to be cleaned-up. ### Dataflow visualisation: A day in the life of a packet From 12edbeeaec66d126cc05cf2bbfa9c097ba996591 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Fri, 4 Oct 2024 16:58:36 +0200 Subject: [PATCH 29/71] minor fix --- spec/core/v2/ics-004-packet-semantics/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 603af69c9..576ead136 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -4,7 +4,7 @@ title: Packet Semantics stage: draft category: IBC/TAO kind: instantiation -requires: [2](../ics-002-client-semantics/), [24](../ics-024-host-requirements/), [packet-data](https://github.com/cosmos/ibc/pull/1149) +requires: 2, 24, packet-data version compatibility: ibc-go v10.0.0 author: Christopher Goes created: 2019-03-07 @@ -262,7 +262,7 @@ Once the set up is executed the system should be in a similar state: ![Setup Final State](setup_final_state.png) -While the application callbacks registration MUST be handled by the application module during initialization, and client creation is governed by [ICS-2](.ics-002-client-semantics/README.md), the channel creation and registration procedures are defined by ICS-04 and are detailed below. +While the application callbacks registration MUST be handled by the application module during initialization, and client creation is governed by [ICS-2](../ics-002-client-semantics/README.md), the channel creation and registration procedures are defined by ICS-04 and are detailed below. The ICS-04 specification defines a set of conditions that the IBC protocol must adhere to. These conditions ensure the proper execution of the function handlers by establishing requirements before execution (ante-conditions), possible error conditions during execution (error-conditions), and expected outcomes after execution (post-conditions). Thus, implementation that wants to comply with the specification of the IBC version 2 protocol MUST adheres to the specified conditions. From e8a6798afd03e3f845217089c606ee3d9e0a47d8 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Fri, 4 Oct 2024 18:01:04 +0200 Subject: [PATCH 30/71] improvements --- .../v2/ics-004-packet-semantics/README.md | 62 ++++++++----------- 1 file changed, 27 insertions(+), 35 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 576ead136..55aa793b6 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -13,11 +13,12 @@ modified: 2019-08-25 TODO : +- Resolve Need discussion - Motivation - Improve Sketchs -- Race condition reasoning -- Improve conditions set and presentation -- Review Timeout carefully +- Improve conditions set / think about more condition an proper presentation +- Review Ack and Timeout carefully +- FROM Race condition UP to END ## Synopsis @@ -121,7 +122,8 @@ An application may not need to return an acknowledgment. In this case, it may re > **Example**: If a packet within 3 payloads intended for 3 different application is sent out, the expectation is that each of the payload is acted upon in the same order as it has been placed in the packet. Likewise, the array of `appAcknowledgement` is expected to be populated within the same order. - The `IBCRouter` contains a mapping from the application port and the supported callbacks. -// and as well as a mapping from channelId to the underlying client. + +// NEED DSICUSSION - and as well as a mapping from channelId to the underlying client. ```typescript type IBCRouter struct { @@ -135,13 +137,10 @@ The proper registration of the application callbacks in the local `IBCRouter`, i - The `MAX_TIMEOUT_DELTA` is intendend as the max difference between currentTimestamp and timeoutTimestamp that can be given in input. ```typescript -const MAX_TIMEOUT_DELTA = TBD +const MAX_TIMEOUT_DELTA = TBD // NEED DISCUSSION ``` -- The `ante-conditions` define what MUST hold true before an action can occurr in the IBC v2 packet flow. -- The `error-conditions` define the set of expected errors. -- The `post-conditions on success` defines the expected final state changes on success. -- The `post-conditions on error` the expected final state on error +The ICS-04 specification defines a set of conditions that the IBC protocol must adhere to. These conditions ensure the proper execution of the function handlers by establishing requirements before execution `ante-conditions`, possible error conditions during execution `error-conditions`, expected outcomes after succesful execution `post-conditions-on-success`, and expected outcomes after error execution `post-conditions-on-error`. Thus, implementation that wants to comply with the specification of the IBC version 2 protocol MUST adheres to the specified conditions. ### Desired Properties @@ -175,6 +174,8 @@ The ICS-04 use the protocol paths, defined in [ICS-24](../ics-024-host-requireme Thus, Constant-size commitments to packet data fields are stored under the packet sequence number: +// NEED DISCUSSION -- what happens if we use "commitments/{sourceId}/{sequence}" + ```typescript function packetCommitmentPath(sourceId: bytes, sequence: BigEndianUint64): Path { return "commitments/channels/{sourceId}/sequences/{sequence}" @@ -185,7 +186,8 @@ 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`. -//NOTE: Do we want this? Maybe not useful. While in the case of a timeout, the destination chain SHOULD write a sentinel timeout value `TIMEOUT_RECEIPT` if the packet is received after the specified timeout. +// NEED DISCUSSION: Do we want this? Maybe not useful. +// While in the case of a timeout, the destination chain SHOULD write a sentinel timeout value `TIMEOUT_RECEIPT` if the packet is received after the specified timeout. ```typescript function packetReceiptPath(sourceId: bytes, sequence: BigEndianUint64): Path { @@ -201,6 +203,8 @@ function packetAcknowledgementPath(sourceId: bytes, sequence: BigEndianUint64): } ``` +// NEED DISCUSSION privatePaths should we use it or instead just declare mappings/variables? + Additionally, the ICS-04 suggests the privateStore paths for the `nextSequenceSend` , `channelPath` and `channelCreator` variable. Private paths are meant to be used locally in the chain. Thus their specification can be arbitrary changed by implementors at their will. - The `nextSequenceSend` is stored separately in the privateStore and tracks the sequence number for the next packet to be sent for a given source clientId. @@ -233,10 +237,12 @@ function creatorPath(channelId: Identifier, creator: address): Path { To start the secure packet stream between the chains, chain `A` and chain `B` MUST execute the entire setup following this set of procedures: -- Application registration: during the application module setup the application callbacks MUST be registered on the local IBC router. -- Client creation: chain `A` MUST create the `B` light client; `B` MUST create the `A` light client. -- Channel creation: both chain `A` and chain `B` MUST create local IBC version 2 channels. -- Channel registration: both chain MUST register their counterpartyId in the channel previously created. +| **Procedure** | **Responsible** | **Outcome** | +|-----------------------------|---------------------|-----------------------------------------------------------------------------| +| **Application Registration** | Application Module | Registers application callbacks on the IBC router during module setup. | +| **Client Creation** | Relayer | Both chains create a light client for the counterparty chain. | +| **Channel Creation** | Relayer | A channel is created and linked to an underlying light client on both chains. | +| **Channel Registration** | Relayer | Registers the `counterpartyChannelId` on both chains, linking the channels. | If any of the steps has been missed, this would result in an incorrect setup error during the packet handlers execution. @@ -264,8 +270,6 @@ Once the set up is executed the system should be in a similar state: While the application callbacks registration MUST be handled by the application module during initialization, and client creation is governed by [ICS-2](../ics-002-client-semantics/README.md), the channel creation and registration procedures are defined by ICS-04 and are detailed below. -The ICS-04 specification defines a set of conditions that the IBC protocol must adhere to. These conditions ensure the proper execution of the function handlers by establishing requirements before execution (ante-conditions), possible error conditions during execution (error-conditions), and expected outcomes after execution (post-conditions). Thus, implementation that wants to comply with the specification of the IBC version 2 protocol MUST adheres to the specified conditions. - ##### Channel creation The channel creation process establishes the communication pathway between two chains. The procedure ensures both chains have a mutually recognized channel, facilitating packet transmission through authenticated streams. @@ -459,7 +463,7 @@ sequenceDiagram Chain A --> Chain A : Delete packetCommitment ``` -Given a configuration where we are sending a packet from `A` to `B` then chain `A` can call either, `sendPacket`,`acknowledgePacket` and `timeoutPacket` while chain `B` can only execute the `receivePacket` handler. The `acknowledgePacket` is not a valid action if `receivePacket` has not been executed. `timeoutPacket` is not a valid action if `receivePacket` occurred. +Given a configuration where we are sending a packet from `A` to `B` then chain `A` can call either, `sendPacket`,`acknowledgePacket` and `timeoutPacket` while chain `B` can only execute the `receivePacket` handler. The `acknowledgePacket` is not a valid action if `receivePacket` has not been executed. `timeoutPacket` is not a valid action if `receivePacket` occurred. // NEED DISCUSSION ##### Sending packets @@ -605,7 +609,7 @@ We pass the address of the `relayer` that signed and submitted the packet to ena ###### Post-Conditions On Error - If one payload fail, then all state changes happened on the sucessfull `onReceivePacket` application callback execution MUST be reverted. -- If timeoutTimestamp has elapsed then no state changes occurred. (Is this true? Shall we write the timeout_sentinel_receipt?) +- If timeoutTimestamp has elapsed then no state changes occurred. // NEED DISCUSSION (Is this ok? Shall we write the timeout_sentinel_receipt?) - mmmm. ###### Pseudo-Code @@ -621,11 +625,11 @@ function recvPacket( // Channel and Client Checks channel = getChannel(packet.destId) // if I use packet.dest which is a channelId - client = channel.clientId // removed client on router --> client = router.clients[packet.destId] // client = channel.clientId + client = channel.clientId // NEED DISCUSSION removed client on router --> client = router.clients[packet.destId] // client = channel.clientId assert(client !== null) //assert(client.id === channel.clientId) // useful? - //assert(packet.sourceId == channel.counterpartyChannelId) Unnecessary? + //assert(packet.sourceId == channel.counterpartyChannelId) Unnecessary? // NEED DISCUSSION // verify timeout assert(packet.timeoutTimestamp === 0) @@ -689,7 +693,7 @@ function recvPacket( ##### Writing acknowledgements -NOTE: Currently the system only handles synchronous acks. +> NOTE: Currently the system only handles synchronous acks. The `writeAcknowledgement` function is called by the IBC handler once all `onRecvPacket` application modules callabacks have been triggered and have returned their specific acknowledgment in order to write data which resulted from processing an IBC packet that the sending chain can then verify. Writing acknowledgement serves as a sort of "execution receipt" or "RPC call response". @@ -770,8 +774,7 @@ The Post-Conditions on Error defines the states that Must be unchanged given an The ICS04 provides an example pseudo-code that enforce the above described conditions so that the following sequence of steps must occur for a packet to be acknowledged from module *1* on machine *A* to module *2* on machine *B*. -// What to do with the relayer? Do we want to keep it? -// We pass the `relayer` address just as in [Receiving packets](#receiving-packets) to allow for possible incentivization here as well. +// NEED DISCUSSION: What to do with the relayer? Do we want to keep it? // We pass the `relayer` address just as in [Receiving packets](#receiving-packets) to allow for possible incentivization here as well. ```typescript function acknowledgePacket( @@ -930,18 +933,7 @@ function timeoutPacket( ##### Cleaning up state -Packets must be acknowledged or timed-out in order to be cleaned-up. - -### Dataflow visualisation: A day in the life of a packet - -TODO - -The bidirectional packet stream can be only started after all the procedure above have been succesfully executed, individually, by both chains. Otherwise, any sent packet cannot be received and will timeout. - -TODO Write Setup. - -TODO Modify Sketch -![V2 Happy Path Single Payload Sketch](Sketch_Happy_Path.png) +Packets MUST be acknowledged or timed-out in order to be cleaned-up. #### Reasoning about race conditions From 7201d5f6b4bf4f802ed5f4c616b6ca0d5fea9ad4 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Fri, 4 Oct 2024 18:57:32 +0200 Subject: [PATCH 31/71] adding considerations --- spec/core/v2/ics-004-packet-semantics/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 55aa793b6..2d9fa388d 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -105,7 +105,9 @@ The protocol introduces standardized packet receipts that will serve as sentinel ```typescript enum PacketReceipt { SUCCESSFUL_RECEIPT = byte{0x01}, - //TIMEOUT_RECEIPT = byte{0x02}, // Should we allow the recivePacket to store a timeout_receipt and do nothing or we just abort the tx? + //TIMEOUT_RECEIPT = byte{0x02}, + // NEED DISCUSSION Should we allow the recivePacket to store a timeout_receipt and do nothing or we just abort the tx? + // This would introduce another packet flow: send - recv - timeout } ``` @@ -127,7 +129,7 @@ An application may not need to return an acknowledgment. In this case, it may re ```typescript type IBCRouter struct { - callbacks: portId -> [Callback] + callbacks: portId -> [Callback] // Maybe should be callbacks: portId,version -> [Callback] // clients: channelId -> Client // Needed? Maybe not anymore } ``` From fc7a5eff0085394e466ee151eb9c7b7ed16b0bcc Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Mon, 7 Oct 2024 11:28:10 +0200 Subject: [PATCH 32/71] add condition table --- .../v2/ics-004-packet-semantics/README.md | 211 +++++++----------- 1 file changed, 84 insertions(+), 127 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 2d9fa388d..5cc20d84d 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -276,25 +276,18 @@ While the application callbacks registration MUST be handled by the application The channel creation process establishes the communication pathway between two chains. The procedure ensures both chains have a mutually recognized channel, facilitating packet transmission through authenticated streams. -###### Ante-Conditions - -- The clientId provided in input to createChannel MUST exist. - -###### Error-Conditions - -- Incorrect clientId -- Unexpected keyPrefix format - -###### Post-Conditions On Success - -- A channel is set in store and it's accessible with key channelId. -- The creator is set in store and it's accessible with key [channelId,address]. - -###### Post-Conditions On Error - -- If one payload fail, then all state changes happened on the sucessfull application execution must be reverted. -- No packetCommitment has been generated. -- The sequence number bind to sourceId MUST be unchanged. +###### Conditions Table + +| Condition Type | Description | +|-------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------| +| **Ante-Conditions** | - The clientId provided in input to createChannel MUST exist. | +| **Error-Conditions** | - Incorrect clientId. | +| | - Unexpected keyPrefix format. | +| **Post-Conditions (Success)** | - A channel is set in store and it's accessible with key channelId. | +| | - The creator is set in store and it's accessible with key [channelId,address]. | +| **Post-Conditions (Error)** | - If one payload fails, then all state changes happened on the successful application execution must be reverted. | +| | - No packetCommitment has been generated. | +| | - The sequence number bound to sourceId MUST be unchanged. | ###### Pseudo-Code @@ -337,24 +330,17 @@ Each IBC chain MUST have the ability to idenfity its counterparty to ensure vali To enable mutual and verifiable identification, IBC version 2 introduces a `registerChannel` procedure. This process stores the `counterpartyChannelId` in the local channel structure, ensuring both chains have mirrored pairs. With the correct registration, the unique clients on each side provide an authenticated stream of packet data. Social consensus outside the protocol is relied upon to ensure only valid pairs are used, representing connections between the correct chains. -###### Ante-Conditions - -- The channelID provided in input MUST properly resolve to a channel. - -###### Error-Conditions - -- Incorrect channelId -- Authentication Failed - -###### Post-Conditions On Success - -- The channel in store contains the counterpartyChannelId information and it's accessible with key channelId. - -###### Post-Conditions On Error - -- On the first call the channel in store contains the counterpartyChannelId as an empty field. -- On the second call the channel in store contains the old counterpartyChannelId information. +###### Conditions Table +| Condition Type | Description | +|-------------------------|------------------------------------------------------------------------------------------------------------------------------------------------| +| **Ante-Conditions** | - The channelID provided in input MUST properly resolve to a channel. | +| **Error-Conditions** | - Incorrect channelId. | +| | - Authentication Failed. | +| **Post-Conditions (Success)** | - The channel in store contains the counterpartyChannelId information and it's accessible with key channelId. | +| **Post-Conditions (Error)** | - On the first call, the channel in store contains the counterpartyChannelId as an empty field. | +| | - On the second call, the channel in store contains the old counterpartyChannelId information. | + ###### Pseudo-Code ```typescript @@ -484,28 +470,21 @@ The IBC handler performs the following steps in order: Note that the full packet is not stored in the state of the chain - merely a short hash-commitment to the data & timeout value. The packet data can be calculated from the transaction execution and possibly returned as log output which relayers can index. -###### Ante-Conditions - -- Chains `A` and `B` MUST be in a setup final state. -- Inputs channelId and timeoutTimestamp are valid. - -###### Error-Conditions - -- Incorrect setup - includes invalid client and invalid channelId -- Invalid timeoutTimestamp -- Unsuccessful payload execution - -###### Post-Conditions On Success - -- All the application contained in the payload have properly terminated the `onSendPacket` callback execution. -- The packetCommitment has been generated. -- The sequence number bind to sourceId MUST have been incremented by 1. - -###### Post-Conditions On Error - -- If one payload fail, then all state changes happened on the sucessfull application execution must be reverted. -- No packetCommitment has been generated. -- The sequence number bind to sourceId MUST be unchanged. +###### Conditions Table + +| Condition Type | Description | +|-------------------------|------------------------------------------------------------------------------------------------------------------------------------------------| +| **Ante-Conditions** | - Chains `A` and `B` MUST be in a setup final state. | +| | - Inputs channelId and timeoutTimestamp are valid. | +| **Error-Conditions** | - Incorrect setup (includes invalid client and invalid channelId). | +| | - Invalid timeoutTimestamp. | +| | - Unsuccessful payload execution. | +| **Post-Conditions (Success)** | - All the applications contained in the payload have properly terminated the `onSendPacket` callback execution. | +| | - The packetCommitment has been generated. | +| | - The sequence number bound to sourceId MUST have been incremented by 1. | +| **Post-Conditions (Error)** | - If one payload fails, then all state changes happened on the successful application execution must be reverted. | +| | - No packetCommitment has been generated. | +| | - The sequence number bound to sourceId MUST be unchanged. | ###### Pseudo-Code @@ -590,29 +569,22 @@ The IBC handler performs the following steps in order: We pass the address of the `relayer` that signed and submitted the packet to enable a module to optionally provide some rewards. This provides a foundation for fee payment, but can be used for other techniques as well (like calculating a leaderboard). -###### Ante-Conditions - -- Chain `A` MUST have stored the packetCommitment under the keyPrefix registered in the chain `B` channelEnd. -- TimeoutTimestamp MUST not have elasped yet. -- PacketReceipt for the specific keyPrefix and sequence MUST be empty (e.g. receivePacket has not been called yet) - -###### Error-Conditions - -- Packet Errors: invalid packetCommitment, packetReceipt already exist -- Invalid timeoutTimestamp -- Unsuccessful payload execution - -###### Post-Conditions On Success - -- All the application pointed in the payload have properly terminated the `onReceivePacket` callback execution. -- The packetReceipt has been written. -- The acknowledgemnet has been written. - -###### Post-Conditions On Error - -- If one payload fail, then all state changes happened on the sucessfull `onReceivePacket` application callback execution MUST be reverted. -- If timeoutTimestamp has elapsed then no state changes occurred. // NEED DISCUSSION (Is this ok? Shall we write the timeout_sentinel_receipt?) -- mmmm. +###### Conditions Table + +| Condition Type | Description | +|-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------| +| **Ante-Conditions** | - Chain `A` MUST have stored the packetCommitment under the keyPrefix registered in the chain `B` channelEnd. | +| | - TimeoutTimestamp MUST not have elapsed yet. | +| | - PacketReceipt for the specific keyPrefix and sequence MUST be empty (e.g. `receivePacket` has not been called yet). | +| **Error-Conditions** | - Packet Errors: invalid packetCommitment, packetReceipt already exists. | +| | - Invalid timeoutTimestamp. | +| | - Unsuccessful payload execution. | +| **Post-Conditions (Success)** | - All the applications pointed in the payload have properly terminated the `onReceivePacket` callback execution. | +| | - The packetReceipt has been written. | +| | - The acknowledgement has been written. | +| **Post-Conditions (Error)** | - If one payload fails, then all state changes happened on the successful `onReceivePacket` application callback execution MUST be reverted. | +| | - If timeoutTimestamp has elapsed then no state changes occurred. // NEED DISCUSSION (Is this ok? Shall we write the `timeout_sentinel_receipt`?) | +| | - mmmm. | ###### Pseudo-Code @@ -742,35 +714,26 @@ The `acknowledgePacket` function is called by the IBC handler to process the ack The IBC hanlder MUST atomically trigger the callbacks execution of appropriate application acknowledgement-handling logic in conjunction with calling `acknowledgePacket`. -###### Ante-Conditions +###### Conditions Table Given that at this point of the packet flow, chain `B` has sucessfully received a packet, the ante-conditions defines what MUST be accomplished before chain `A` can properly execute the `acknowledgePacket` for the IBC v2 packet. -- Acknowledgment MUST be set in the ackPath. -- PacketCommitment has not been cleared out yet. - -###### Error-Conditions - -The Error-Conditions defines the set of condition that MUST trigger an error. For the `acknowledgePacket` handler we can divide the source of errors in three main categories: - -- packetCommitment already clreared out -- Unset Acknowledgment -- Unsuccessful payload execution - -###### Post-Conditions On Success - -The Post-Conditions on Success defines which state changes MUST have occurred if the `acknowledgePacket` handler has been sucessfully executed. - -- All the application pointed in the payload have properly terminated the `onAcknowledgePacket` callback execution. -- The packetCommitment has been cleared out. - -###### Post-Conditions On Error - -The Post-Conditions on Error defines the states that Must be unchanged given an error occurred during the `onAcknowledgePacket` handler. - -- If one payload fail, then all state changes happened on the sucessfull `onAcknowledgePacket` application callback execution MUST be reverted. -- The packetCommitment has not been cleared out -- mmmm. +| Condition Type | Description | +|-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------| +| **Ante-Conditions** | Given that at this point of the packet flow, chain `B` has successfully received a packet, the ante-conditions define what MUST be accomplished before chain `A` can properly execute the `acknowledgePacket` for the IBC v2 packet. | +| | - Acknowledgment MUST be set in the `ackPath`. | +| | - PacketCommitment has not been cleared out yet. | +| **Error-Conditions** | The Error-Conditions define the set of conditions that MUST trigger an error. For the `acknowledgePacket` handler, errors can come from three main categories: | +| | - PacketCommitment already cleared out. | +| | - Unset Acknowledgment. | +| | - Unsuccessful payload execution. | +| **Post-Conditions (Success)** | The Post-Conditions on Success define which state changes MUST have occurred if the `acknowledgePacket` handler has been successfully executed. | +| | - All the applications pointed in the payload have properly terminated the `onAcknowledgePacket` callback execution. | +| | - The packetCommitment has been cleared out. | +| **Post-Conditions (Error)** | The Post-Conditions on Error define the states that MUST remain unchanged if an error occurred during the `onAcknowledgePacket` handler. | +| | - If one payload fails, then all state changes that happened on the successful `onAcknowledgePacket` application callback execution MUST be reverted. | +| | - The packetCommitment has not been cleared out. | +| | - mmmm. | ###### Pseudo-Code @@ -863,26 +826,20 @@ Calling modules MAY atomically execute appropriate application timeout-handling The `timeoutPacket` checks the absence of the receipt key (which will have been written if the packet was received). We pass the `relayer` address just as in [Receiving packets](#receiving-packets) to allow for possible incentivization here as well. -###### Ante-Conditions - -- PacketReceipt MUST be empty. -- PacketCommitment has not been cleared out yet. - -###### Error-Conditions - -- packetCommitment already clreared out -- PacketReceipt is not empty -- Unsuccessful payload execution - -###### Post-Conditions On Success - -- All the application pointed in the payload have properly terminated the `onTimeoutPacket` callback execution, reverting the state changes occured in the `onSendPacket`. -- The packetCommitment has been cleared out. - -###### Post-Conditions On Error - -- If one payload fail, then all state changes happened on the sucessfull `onTimeoutPacket` application callback execution MUST be reverted. Mmm Note that here we may get stucked if one onTimeoutPacket applications always fails. -- The packetCommitment has not been cleared out +###### Conditions Table + +| Condition Type | Description | +|-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------| +| **Ante-Conditions** | - PacketReceipt MUST be empty. | +| | - PacketCommitment has not been cleared out yet. | +| **Error-Conditions** | - PacketCommitment already cleared out. | +| | - PacketReceipt is not empty. | +| | - Unsuccessful payload execution. | +| **Post-Conditions (Success)** | - All the applications pointed in the payload have properly terminated the `onTimeoutPacket` callback execution, reverting the state changes occurred in `onSendPacket`. | +| | - The packetCommitment has been cleared out. | +| **Post-Conditions (Error)** | - If one payload fails, then all state changes that happened on the successful `onTimeoutPacket` application callback execution MUST be reverted. | +| | - Note that here we may get stuck if one `onTimeoutPacket` application always fails. | +| | - The packetCommitment has not been cleared out. | ###### Pseudo-Code From fe4b0e0809d51f234306d5ee3ea22d1898afd884 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Mon, 7 Oct 2024 11:43:49 +0200 Subject: [PATCH 33/71] change condition table format --- .../v2/ics-004-packet-semantics/README.md | 113 ++++++------------ 1 file changed, 38 insertions(+), 75 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 5cc20d84d..e42f0530a 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -278,16 +278,12 @@ The channel creation process establishes the communication pathway between two c ###### Conditions Table -| Condition Type | Description | -|-------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------| -| **Ante-Conditions** | - The clientId provided in input to createChannel MUST exist. | -| **Error-Conditions** | - Incorrect clientId. | -| | - Unexpected keyPrefix format. | -| **Post-Conditions (Success)** | - A channel is set in store and it's accessible with key channelId. | -| | - The creator is set in store and it's accessible with key [channelId,address]. | -| **Post-Conditions (Error)** | - If one payload fails, then all state changes happened on the successful application execution must be reverted. | -| | - No packetCommitment has been generated. | -| | - The sequence number bound to sourceId MUST be unchanged. | +| **Condition Type** | **Description** | +|-------------------------------|-----------------------------------------------------------------------------------------------------| +| **Ante-Conditions** | - The clientId provided in input to createChannel MUST exist. | +| **Error-Conditions** | - Incorrect clientId.
- Unexpected keyPrefix format. | +| **Post-Conditions (Success)** | - A channel is set in store and it's accessible with key channelId.
- The creator is set in store and it's accessible with key [channelId, address]. | +| **Post-Conditions (Error)** | - If one payload fails, then all state changes happened on the successful application execution must be reverted.
- No packetCommitment has been generated.
- The sequence number bound to sourceId MUST be unchanged. | ###### Pseudo-Code @@ -332,14 +328,12 @@ To enable mutual and verifiable identification, IBC version 2 introduces a `regi ###### Conditions Table -| Condition Type | Description | -|-------------------------|------------------------------------------------------------------------------------------------------------------------------------------------| -| **Ante-Conditions** | - The channelID provided in input MUST properly resolve to a channel. | -| **Error-Conditions** | - Incorrect channelId. | -| | - Authentication Failed. | -| **Post-Conditions (Success)** | - The channel in store contains the counterpartyChannelId information and it's accessible with key channelId. | -| **Post-Conditions (Error)** | - On the first call, the channel in store contains the counterpartyChannelId as an empty field. | -| | - On the second call, the channel in store contains the old counterpartyChannelId information. | +| **Condition Type** | **Description** | +|-------------------------------|---------------------------------------------------------------------------------------------------------------------------------| +| **Ante-Conditions** | - The channelID provided in input MUST properly resolve to a channel. | +| **Error-Conditions** | - Incorrect channelId.
- Authentication Failed. | +| **Post-Conditions (Success)** | - The channel in store contains the counterpartyChannelId information and it's accessible with key channelId. | +| **Post-Conditions (Error)** | - On the first call, the channel in store contains the counterpartyChannelId as an empty field.
- On the second call, the channel in store contains the old counterpartyChannelId information. | ###### Pseudo-Code @@ -472,19 +466,12 @@ Note that the full packet is not stored in the state of the chain - merely a sho ###### Conditions Table -| Condition Type | Description | -|-------------------------|------------------------------------------------------------------------------------------------------------------------------------------------| -| **Ante-Conditions** | - Chains `A` and `B` MUST be in a setup final state. | -| | - Inputs channelId and timeoutTimestamp are valid. | -| **Error-Conditions** | - Incorrect setup (includes invalid client and invalid channelId). | -| | - Invalid timeoutTimestamp. | -| | - Unsuccessful payload execution. | -| **Post-Conditions (Success)** | - All the applications contained in the payload have properly terminated the `onSendPacket` callback execution. | -| | - The packetCommitment has been generated. | -| | - The sequence number bound to sourceId MUST have been incremented by 1. | -| **Post-Conditions (Error)** | - If one payload fails, then all state changes happened on the successful application execution must be reverted. | -| | - No packetCommitment has been generated. | -| | - The sequence number bound to sourceId MUST be unchanged. | +| **Condition Type** | **Description** | +|-------------------------------|---------------------------------------------------------------------------------------------------------------------------------| +| **Ante-Conditions** | - Chains `A` and `B` MUST be in a setup final state.
- Inputs channelId and timeoutTimestamp are valid. | +| **Error-Conditions** | - Incorrect setup (includes invalid client and invalid channelId).
- Invalid timeoutTimestamp.
- Unsuccessful payload execution. | +| **Post-Conditions (Success)** | - All the applications contained in the payload have properly terminated the `onSendPacket` callback execution.
- The packetCommitment has been generated.
- The sequence number bound to sourceId MUST have been incremented by 1. | +| **Post-Conditions (Error)** | - If one payload fails, then all state changes happened on the successful application execution must be reverted.
- No packetCommitment has been generated.
- The sequence number bound to sourceId MUST be unchanged. | ###### Pseudo-Code @@ -571,21 +558,13 @@ We pass the address of the `relayer` that signed and submitted the packet to ena ###### Conditions Table -| Condition Type | Description | -|-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------| -| **Ante-Conditions** | - Chain `A` MUST have stored the packetCommitment under the keyPrefix registered in the chain `B` channelEnd. | -| | - TimeoutTimestamp MUST not have elapsed yet. | -| | - PacketReceipt for the specific keyPrefix and sequence MUST be empty (e.g. `receivePacket` has not been called yet). | -| **Error-Conditions** | - Packet Errors: invalid packetCommitment, packetReceipt already exists. | -| | - Invalid timeoutTimestamp. | -| | - Unsuccessful payload execution. | -| **Post-Conditions (Success)** | - All the applications pointed in the payload have properly terminated the `onReceivePacket` callback execution. | -| | - The packetReceipt has been written. | -| | - The acknowledgement has been written. | -| **Post-Conditions (Error)** | - If one payload fails, then all state changes happened on the successful `onReceivePacket` application callback execution MUST be reverted. | -| | - If timeoutTimestamp has elapsed then no state changes occurred. // NEED DISCUSSION (Is this ok? Shall we write the `timeout_sentinel_receipt`?) | -| | - mmmm. | - +| **Condition Type** | **Description** | +|-------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------| +| **Ante-Conditions** | - Chain `A` MUST have stored the packetCommitment under the keyPrefix registered in the chain `B` channelEnd.
- TimeoutTimestamp MUST not have elapsed yet.
- PacketReceipt for the specific keyPrefix and sequence MUST be empty (e.g. `receivePacket` has not been called yet). | +| **Error-Conditions** | - Packet Errors: invalid packetCommitment, packetReceipt already exists.
- Invalid timeoutTimestamp.
- Unsuccessful payload execution. | +| **Post-Conditions (Success)** | - All the applications pointed in the payload have properly terminated the `onReceivePacket` callback execution.
- The packetReceipt has been written.
- The acknowledgement has been written. | +| **Post-Conditions (Error)** | - If one payload fails, then all state changes happened on the successful `onReceivePacket` application callback execution MUST be reverted.
- If timeoutTimestamp has elapsed then no state changes occurred. // NEED DISCUSSION (Is this ok? Shall we write the `timeout_sentinel_receipt`?) | + ###### Pseudo-Code The ICS04 provides an example pseudo-code that enforce the above described conditions so that the following sequence of steps must occur for a packet to be received from module *1* on machine *A* to module *2* on machine *B*. @@ -718,23 +697,13 @@ The IBC hanlder MUST atomically trigger the callbacks execution of appropriate a Given that at this point of the packet flow, chain `B` has sucessfully received a packet, the ante-conditions defines what MUST be accomplished before chain `A` can properly execute the `acknowledgePacket` for the IBC v2 packet. -| Condition Type | Description | -|-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------| -| **Ante-Conditions** | Given that at this point of the packet flow, chain `B` has successfully received a packet, the ante-conditions define what MUST be accomplished before chain `A` can properly execute the `acknowledgePacket` for the IBC v2 packet. | -| | - Acknowledgment MUST be set in the `ackPath`. | -| | - PacketCommitment has not been cleared out yet. | -| **Error-Conditions** | The Error-Conditions define the set of conditions that MUST trigger an error. For the `acknowledgePacket` handler, errors can come from three main categories: | -| | - PacketCommitment already cleared out. | -| | - Unset Acknowledgment. | -| | - Unsuccessful payload execution. | -| **Post-Conditions (Success)** | The Post-Conditions on Success define which state changes MUST have occurred if the `acknowledgePacket` handler has been successfully executed. | -| | - All the applications pointed in the payload have properly terminated the `onAcknowledgePacket` callback execution. | -| | - The packetCommitment has been cleared out. | -| **Post-Conditions (Error)** | The Post-Conditions on Error define the states that MUST remain unchanged if an error occurred during the `onAcknowledgePacket` handler. | -| | - If one payload fails, then all state changes that happened on the successful `onAcknowledgePacket` application callback execution MUST be reverted. | -| | - The packetCommitment has not been cleared out. | -| | - mmmm. | - +| **Condition Type** | **Description** | +|-------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------| +| **Ante-Conditions** | Given that at this point of the packet flow, chain `B` has successfully received a packet, the ante-conditions define what MUST be accomplished before chain `A` can properly execute the `acknowledgePacket` for the IBC v2 packet.
- Acknowledgment MUST be set in the `ackPath`.
- PacketCommitment has not been cleared out yet. | +| **Error-Conditions** | The Error-Conditions define the set of conditions that MUST trigger an error. For the `acknowledgePacket` handler, errors can come from three main categories:
- PacketCommitment already cleared out.
- Unset Acknowledgment.
- Unsuccessful payload execution. | +| **Post-Conditions (Success)** | The Post-Conditions on Success define which state changes MUST have occurred if the `acknowledgePacket` handler has been successfully executed.
- All the applications pointed in the payload have properly terminated the `onAcknowledgePacket` callback execution.
- The packetCommitment has been cleared out. | +| **Post-Conditions (Error)** | The Post-Conditions on Error define the states that MUST remain unchanged if an error occurred during the `onAcknowledgePacket` handler.
- If one payload fails, then all state changes that happened on the successful `onAcknowledgePacket` application callback execution MUST be reverted.
- The packetCommitment has not been cleared out.
| + ###### Pseudo-Code The ICS04 provides an example pseudo-code that enforce the above described conditions so that the following sequence of steps must occur for a packet to be acknowledged from module *1* on machine *A* to module *2* on machine *B*. @@ -828,18 +797,12 @@ We pass the `relayer` address just as in [Receiving packets](#receiving-packets) ###### Conditions Table -| Condition Type | Description | -|-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------| -| **Ante-Conditions** | - PacketReceipt MUST be empty. | -| | - PacketCommitment has not been cleared out yet. | -| **Error-Conditions** | - PacketCommitment already cleared out. | -| | - PacketReceipt is not empty. | -| | - Unsuccessful payload execution. | -| **Post-Conditions (Success)** | - All the applications pointed in the payload have properly terminated the `onTimeoutPacket` callback execution, reverting the state changes occurred in `onSendPacket`. | -| | - The packetCommitment has been cleared out. | -| **Post-Conditions (Error)** | - If one payload fails, then all state changes that happened on the successful `onTimeoutPacket` application callback execution MUST be reverted. | -| | - Note that here we may get stuck if one `onTimeoutPacket` application always fails. | -| | - The packetCommitment has not been cleared out. | +| **Condition Type** | **Description** | +|-------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------| +| **Ante-Conditions** | - PacketReceipt MUST be empty.
- PacketCommitment has not been cleared out yet. | +| **Error-Conditions** | - PacketCommitment already cleared out.
- PacketReceipt is not empty.
- Unsuccessful payload execution. | +| **Post-Conditions (Success)** | - All the applications pointed in the payload have properly terminated the `onTimeoutPacket` callback execution, reverting the state changes occurred in `onSendPacket`.
- The packetCommitment has been cleared out. | +| **Post-Conditions (Error)** | - If one payload fails, then all state changes that happened on the successful `onTimeoutPacket` application callback execution MUST be reverted.
- Note that here we may get stuck if one `onTimeoutPacket` application always fails.
- The packetCommitment has not been cleared out. | ###### Pseudo-Code From c2367617d44e507f1903421af0a2f30d8a709d41 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Mon, 7 Oct 2024 19:54:27 +0200 Subject: [PATCH 34/71] multi-payload,paths,router,acks fixes --- .../v2/ics-004-packet-semantics/README.md | 234 +++++++++--------- 1 file changed, 114 insertions(+), 120 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index e42f0530a..edd6eee6c 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -90,7 +90,7 @@ enum Encoding { } ``` -When the array of payloads, passed-in the packet, is populated with multiple values, the system will handle the packet as a multi-data packet. +When the array of payloads, passed-in the packet, is populated with multiple values, the system will handle the packet as a multi-data packet. The multi-data packet handling logic is out of the scope of the current version of this spec. 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. @@ -105,9 +105,6 @@ The protocol introduces standardized packet receipts that will serve as sentinel ```typescript enum PacketReceipt { SUCCESSFUL_RECEIPT = byte{0x01}, - //TIMEOUT_RECEIPT = byte{0x02}, - // NEED DISCUSSION Should we allow the recivePacket to store a timeout_receipt and do nothing or we just abort the tx? - // This would introduce another packet flow: send - recv - timeout } ``` @@ -119,27 +116,27 @@ interface Acknowledgement { } ``` -An application may not need to return an acknowledgment. In this case, it may return a sentinel acknowledgement value `SENTINEL_ACKNOWLEDGMENT` which will be the single byte in the byte array: `bytes(0x01)`. In this case, the IBC `acknowledgePacket` handler will still do the core IBC acknowledgment logic but it will not call the application's acknowledgePacket callback. +An application may not need to return an acknowledgment with after processing relevant data. In this case, it is advised to return a sentinel acknowledgement value `SENTINEL_ACKNOWLEDGMENT`, which will be the single byte in the byte array: `bytes(0x01)`. -> **Example**: If a packet within 3 payloads intended for 3 different application is sent out, the expectation is that each of the payload is acted upon in the same order as it has been placed in the packet. Likewise, the array of `appAcknowledgement` is expected to be populated within the same order. +Returning this `SENTINEL_ACKNOWLEDGMENT` value allows the sender chain to still call the `acknowledgePacket` handler, e.g. to delete the packet commitment, without triggering the `onAcknowledgePacket` callback. -- The `IBCRouter` contains a mapping from the application port and the supported callbacks. +> **Example**: In the multi-data packet world, if a packet within 3 payloads intended for 3 different application is sent out, the expectation is that each of the payload is acted upon in the same order as it has been placed in the packet. Likewise, the array of `appAcknowledgement` is expected to be populated within the same order. -// NEED DSICUSSION - and as well as a mapping from channelId to the underlying client. +- The `IBCRouter` contains a mapping from the application port and the supported callbacks and as well as a mapping from channelId to the underlying client. ```typescript type IBCRouter struct { callbacks: portId -> [Callback] // Maybe should be callbacks: portId,version -> [Callback] - // clients: channelId -> Client // Needed? Maybe not anymore + clients: channelId -> Client } ``` -The proper registration of the application callbacks in the local `IBCRouter`, is responsibility of the chain. Without this registration process, the packets cannot be processed by the application. +The proper registration of the application callbacks in the local `IBCRouter`, is responsibility of the chain. While the registration of the client under the key channelId is part of the setup procedure. Without the execution of registration process, the packets cannot be processed by the application. - The `MAX_TIMEOUT_DELTA` is intendend as the max difference between currentTimestamp and timeoutTimestamp that can be given in input. ```typescript -const MAX_TIMEOUT_DELTA = TBD // NEED DISCUSSION +const MAX_TIMEOUT_DELTA = Implementation specific // We recommend MAX_TIMEOUT_DELTA = TDB ``` The ICS-04 specification defines a set of conditions that the IBC protocol must adhere to. These conditions ensure the proper execution of the function handlers by establishing requirements before execution `ante-conditions`, possible error conditions during execution `error-conditions`, expected outcomes after succesful execution `post-conditions-on-success`, and expected outcomes after error execution `post-conditions-on-error`. Thus, implementation that wants to comply with the specification of the IBC version 2 protocol MUST adheres to the specified conditions. @@ -176,7 +173,7 @@ The ICS-04 use the protocol paths, defined in [ICS-24](../ics-024-host-requireme Thus, Constant-size commitments to packet data fields are stored under the packet sequence number: -// NEED DISCUSSION -- what happens if we use "commitments/{sourceId}/{sequence}" +// NEED DISCUSSION -- we could use "commitments/{sourceId}/{sequence}" or "0x01/{sourceId}/{sequence}". For now we keep going with more or less standard paths ```typescript function packetCommitmentPath(sourceId: bytes, sequence: BigEndianUint64): Path { @@ -188,9 +185,6 @@ 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`. -// NEED DISCUSSION: Do we want this? Maybe not useful. -// While in the case of a timeout, the destination chain SHOULD write a sentinel timeout value `TIMEOUT_RECEIPT` if the packet is received after the specified timeout. - ```typescript function packetReceiptPath(sourceId: bytes, sequence: BigEndianUint64): Path { return "receipts/channels/{sourceId}/sequences/{sequence}" @@ -205,31 +199,21 @@ function packetAcknowledgementPath(sourceId: bytes, sequence: BigEndianUint64): } ``` -// NEED DISCUSSION privatePaths should we use it or instead just declare mappings/variables? - -Additionally, the ICS-04 suggests the privateStore paths for the `nextSequenceSend` , `channelPath` and `channelCreator` variable. Private paths are meant to be used locally in the chain. Thus their specification can be arbitrary changed by implementors at their will. +#### Private Utility Store -- The `nextSequenceSend` is stored separately in the privateStore and tracks the sequence number for the next packet to be sent for a given source clientId. - -```typescript -function nextSequenceSendPath(sourceId: bytes): Path { - return "nextSequenceSend/{sourceId}" -} -``` +Additionally, the ICS-04 defines the following variables: `nextSequenceSend` , `channelPath` and `channelCreator`. These variables are defined for the IBC handler and meant to be used locally in the chain, thus, as long as they maintain the semantic value defined with the IBC protocol, the specification of their structure can be arbitrary changed by implementors at their conveinience. -- The `channelPath` is stored separately in the privateStore and tracks the channels paired with the other chains. +- The `nextSequenceSend` tracks the sequence number for the next packet to be sent for a given source channelId. +- The `channelCreator` tracks the channels creator address given the channelId. +- The `storedChannels` tracks the channels paired with the other chains. ```typescript -function channelPath(channelId: Identifier): Path { - return "channels/{channelId}" -} -``` +type nextSequenceSend : channelId -> BigEndianUint64 +type channelCreator : channelId -> address +type storedChannels : channelId -> Channel -- The `creatorPath` is stored separately in the privateStore and tracks the channels creator address. - -```typescript -function creatorPath(channelId: Identifier, creator: address): Path { - return "channels/{channelId}/creator/{creator}" +function getChannel(channelId: bytes) Channel { + return storedChannels[channelId] } ``` @@ -311,8 +295,14 @@ function createChannel( } // Local stores - privateStore.set(channelPath(channelId), channel) - privateStore.set(creatorPath(channelId,msg.signer()), msg.signer()) + // Update the IBC router registering the specific client under the channelId + router[channelId]=client + // Store channel info + storedChannels[channelId]=channel + // Store creator address info + channelCreator[channelId]=msg.signer() + // Initialise the nextSequenceSend + nextSequenceSend[channelId]=1 return channelId } @@ -346,44 +336,32 @@ function registerChannel( // Implementation-Specific Input Validation // All implementations MUST ensure the inputs value are properly validated and compliant with this specification - // custom-authentication - assert(verify(authentication)) - // Channel Checks abortTransactionUnless(validatedIdentifier(channelId)) channel=getChannel(channelId) abortTransactionUnless(channel !== null) + // Check that a valid client is associated with the channelId + client=router.clients[channelId] + abortTransactionUnless(client !== null) + // Creator Address Checks - abortTransactionUnless(msg.signer()===getCreator(channelId,msg.signer())) + abortTransactionUnless(msg.signer()===channelCreator[channelId]) // Channel manipulation channel.counterpartyChannelId=counterpartyChannelId - // Client registration on the router - //router.clients[sourceChannelId]=channel.clientId // clients on router removed - // Local Store - privateStore.set(channelPath(channelId), channel) - + storedChannels[channelId]=channel } ``` -The `registerChannel` method allows for authentication data that implementations may verify before storing the provided counterparty identifier. The strongest authentication possible is to have a valid clientState and consensus state of our chain in the authentication along with a proof it was stored at the claimed counterparty identifier. A simpler but weaker authentication would simply be to check that the `registerChannel` message is sent by the same relayer that initialized the client. This would make the client parameters completely initialized by the relayer. Thus, users must verify that the client is pointing to the correct chain and that the counterparty identifier is correct as well before using the pair. +// REWORK THIS -```typescript -// getChannel retrieves the stored channel given the counterparty channel-id. -function getChannel(channelId: bytes): Channel { - return privateStore.get(channelPath(channelId)) -} -``` +The `registerChannel` method allows for authentication data that implementations may verify before storing the provided counterparty identifier. The strongest authentication possible is to have a valid clientState and consensus state of our chain in the authentication along with a proof it was stored at the claimed counterparty identifier. A simpler but weaker authentication would simply be to check that the `registerChannel` message is sent by the same relayer that initialized the client. This would make the client parameters completely initialized by the relayer. Thus, users must verify that the client is pointing to the correct chain and that the counterparty identifier is correct as well before using the pair. -```typescript -// getChannel retrieves the stored channel given the counterparty channel-id. -function getCreator(channelId: bytes, msgSigner: address): address { - return privateStore.get(creatorPath(channelId,msgSigner)) -} -``` + // custom-authentication + assert(verify(authentication)) Thus, once two chains have set up clients, created channel and registered channels for each other with specific Identifiers, they can send IBC packets using the packet interface defined before and the packet handlers that the ICS-04 defines below. @@ -400,14 +378,14 @@ In the IBC protocol version 2, the packet flow is managed by four key function h - `acknowledgePacket` - `timeoutPacket` -Note that the execution of the four handler above described, upon a unique packet, cannot be combined in any arbitrary order. We provide the two possible example scenarios described with sequence diagrmas. +Note that the execution of the four handler above described, upon a unique packet, cannot be combined in any arbitrary order. We provide the three possible example scenarios described with sequence diagrmas. -Scenario execution with acknowledgement `A` to `B` - set of actions: `sendPacket` -> `receivePacket` -> `acknowledgePacket` +Scenario execution with synchronous acknowledgement `A` to `B` - set of actions: `sendPacket` -> `receivePacket` -> `acknowledgePacket` ```mermaid sequenceDiagram - participant Chain A - participant B Light Client + participant B Light Client + participant Chain A participant Relayer participant Chain B participant A Light Client @@ -427,12 +405,40 @@ sequenceDiagram Chain A --> Chain A : Delete packetCommitment ``` +Scenario execution with asynchronous acknowledgement `A` to `B` - set of actions: `sendPacket` -> `receivePacket` -> `acknowledgePacket` + +Note that the key difference with the synchronous scenario is that the receivePacket writes only the packetReceipt and not the acknowledgement. The acknowledgement is instead written asynchronously for effect of the application callback call to the core function `writeAcknowledgement`, call that happens after the `receivePacket` execution. + +```mermaid +sequenceDiagram + participant B Light Client + participant Chain A + participant Relayer + participant Chain B + participant A Light Client + Chain A ->> Chain A : sendPacket + Chain A --> Chain A : app execution + Chain A --> Chain A : packetCommitment + Relayer ->> Chain B: relayPacket + Chain B ->> Chain B: receivePacket + Chain B -->> A Light Client: verifyMembership(packetCommitment) + Chain B --> Chain B : app execution + Chain B --> Chain B: writePacketReceipt + Chain B --> Chain B : app execution - async ack processing + Chain B --> Chain B: writeAck + Relayer ->> Chain A: relayAck + Chain A ->> Chain A : acknowldgePacket + Chain A -->> B Light Client: verifyMembership(packetAck) + Chain A --> Chain A : app execution + Chain A --> Chain A : Delete packetCommitment +``` + Scenario timeout execution `A` to `B` - set of actions: `sendPacket` -> `timeoutPacket` ```mermaid sequenceDiagram - participant Chain A participant B Light Client + participant Chain A participant Relayer participant Chain B participant A Light Client @@ -445,7 +451,8 @@ sequenceDiagram Chain A --> Chain A : Delete packetCommitment ``` -Given a configuration where we are sending a packet from `A` to `B` then chain `A` can call either, `sendPacket`,`acknowledgePacket` and `timeoutPacket` while chain `B` can only execute the `receivePacket` handler. The `acknowledgePacket` is not a valid action if `receivePacket` has not been executed. `timeoutPacket` is not a valid action if `receivePacket` occurred. // NEED DISCUSSION +Given a configuration where we are sending a packet from `A` to `B` then chain `A` can call either, `sendPacket`,`acknowledgePacket` or `timeoutPacket` while chain `B` can only execute the `receivePacket` handler. +The `acknowledgePacket` is not a valid action if `receivePacket` has not been executed. `timeoutPacket` is not a valid action if `receivePacket` occurred. ##### Sending packets @@ -486,47 +493,41 @@ function sendPacket( // Setup checks - channel and client channel = getChannel(sourceChannelId) - client = channel.clientId // removed client on router --> client = router.clients[sourceChannelId] // can it be client = channel.clientId + client = router.clients[sourceChannelId] assert(client !== null) - // Evaluate usefulness of this check - // assert(client.id === channel.clientId) - // timeoutTimestamp checks // disallow packets with a zero timeoutTimestamp assert(timeoutTimestamp !== 0) // disallow packet with timeoutTimestamp less than currentTimestamp and timeoutTimestamp value bigger than currentTimestamp + MaxTimeoutDelta - assert(currentTimestamp() < timeoutTimestamp < currentTimestamp() + MAX_TIMEOUT_DELTA) // Mmm + assert(currentTimestamp() < timeoutTimestamp < currentTimestamp() + MAX_TIMEOUT_DELTA) // if the sequence doesn't already exist, this call initializes the sequence to 0 - sequence = privateStore.get(nextSequenceSendPath(sourceChannelId)) + sequence = nextSequenceSend[sourecChannelId] // Executes Application logic ∀ Payload - for payload in payloads - cbs = router.callbacks[payload.sourcePort] - success = cbs.onSendPacket(sourceChannelId,channel.counterpartyChannelId,payload) - // IMPORTANT: if the one of the onSendPacket fails, the transaction is aborted and the potential state changes are reverted. This ensure that - // the post conditions on error are always respected. - - // payload execution check - abortUnless(success) + cbs = router.callbacks[payload.sourcePort] + success = cbs.onSendPacket(sourceChannelId,channel.counterpartyChannelId,payload) + // IMPORTANT: if the one of the onSendPacket fails, the transaction is aborted and the potential state changes are reverted. This ensure that + // the post conditions on error are always respected. + // payload execution check + abortUnless(success) packet = Packet { sourceId: sourceChannelId, destId: channel.counterpartyChannelId, sequence: sequence, timeoutTimestamp: timeoutTimestamp, - payload: paylodas + payload: payloads } // store packet commitment using commit function defined in [packet specification](https://github.com/cosmos/ibc/blob/c7b2e6d5184b5310843719b428923e0c5ee5a026/spec/core/v2/ics-004-packet-semantics/PACKET.md) commitment=commitV2Packet(packet) - provableStore.set(packetCommitmentPath(sourceChannelId, sequence),commitment) // increment the sequence. Thus there are monotonically increasing sequences for packet flow for a given clientId - privateStore.set(nextSequenceSendPath(sourceChannelId), sequence+1) - + nextSequenceSend[sourceChannelId]=sequence+1 + // log that a packet can be safely sent // Discussion needed: What we need to emit the log? emitLogEntry("sendPacket", { @@ -577,16 +578,15 @@ function recvPacket( relayer: string) { // Channel and Client Checks - channel = getChannel(packet.destId) // if I use packet.dest which is a channelId - client = channel.clientId // NEED DISCUSSION removed client on router --> client = router.clients[packet.destId] // client = channel.clientId + channel = getChannel(packet.destId) + client = router.clients[packet.destId] assert(client !== null) - //assert(client.id === channel.clientId) // useful? - //assert(packet.sourceId == channel.counterpartyChannelId) Unnecessary? // NEED DISCUSSION + //assert(packet.sourceId == channel.counterpartyChannelId) This should be always true, redundant // NEED DISCUSSION // verify timeout assert(packet.timeoutTimestamp === 0) - assert(currentTimestamp() + MAX_TIMEOUT_DELTA < packet.timeoutTimestamp) + assert(currentTimestamp() < packet.timeoutTimestamp) // verify the packet receipt for this packet does not exist already packetReceipt = provableStore.get(packetReceiptPath(packet.destId, packet.sequence)) @@ -609,19 +609,17 @@ function recvPacket( merklePath, commit)) - multiAck = Acknowledgement {} + // Executes Application logic ∀ Payload - for payload in packet.data - cbs = router.callbacks[payload.destPort] - ack = cbs.onReceivePacket(packet.destId,payload) - // the onReceivePacket returns the ack but does not write it - // IMPORTANT: if the ack is error, then the callback reverts its internal state changes, but the entire tx continues - multiAck.add(ack) - - // NOTE: Currently only process synchronous acks. - if multiAck != nil { - writeAcknowledgement(packet, multiAck) + cbs = router.callbacks[payload.destPort] + ack,success = cbs.onReceivePacket(packet.destId,payload) + abortTransactionUnless(success) + if ack != nil { + // NOTE: Synchronous ack. + writeAcknowledgement(packet, ack) } + // NOTE Asynchronous ack. + //else: ack is nil and won't be written || ack is nil and will be written asynchronously // Provable Stores // we must set the receipt so it can be verified on the other side @@ -646,13 +644,13 @@ function recvPacket( ##### Writing acknowledgements -> NOTE: Currently the system only handles synchronous acks. +> NOTE: The system handles synchronous and asynchronous acknowledgement logic. -The `writeAcknowledgement` function is called by the IBC handler once all `onRecvPacket` application modules callabacks have been triggered and have returned their specific acknowledgment in order to write data which resulted from processing an IBC packet that the sending chain can then verify. Writing acknowledgement serves as a sort of "execution receipt" or "RPC call response". +The `writeAcknowledgement` function can be called either synchronously by the IBC handler during the `receivePacket` execution or it can be called asynchronously by an application callback. -Since at the day of writing, IBC version 2 only support synchronous acknowledgement, `writeAcknowledgement` MUST be called in the same transaction (atomically) with `recvPacket` and the application callback logic execution. +Writing acknowledgements ensures that application modules callabacks have been triggered and have returned their specific acknowledgment in order to write data which resulted from processing an IBC packet that the sending chain can then verify. Writing acknowledgement serves as a sort of "execution receipt" or "RPC call response". -`writeAcknowledgement` is called in a `recvPacket`, thus it *does not* check if the packet being acknowledged was actually received, because this would result in proofs being verified twice for acknowledged packets. This aspect of correctness is the responsibility of the IBC handler. +`writeAcknowledgement` can be called either in a `receivePacket`, or after the `receivePacket` execution in a later on application callback. Given that the `receivePacket` logic is always execute before the `writeAcknowledgement` it *does not* check if the packet being acknowledged was actually received, because this would result in proofs being verified twice for acknowledged packets. This aspect of correctness is the responsibility of the IBC handler. The IBC handler performs the following steps in order: @@ -721,12 +719,11 @@ function acknowledgePacket( // Channel and Client Checks channel = getChannel(packet.sourceId) - client = channel.clientId //client = router.clients[packet.sourceId] + client = router.clients[packet.sourceId] assert(client !== null) - //assert(client.id === channel.clientId) - //assert(packet.destId == channel.counterpartyChannelId) + //assert(packet.destId == channel.counterpartyChannelId) // Tautology // verify we sent the packet and haven't cleared it out yet assert(provableStore.get(packetCommitmentPath(packet.sourceId, packet.sequence)) === commitV2Packet(packet)) @@ -741,14 +738,13 @@ function acknowledgePacket( merklePath, acknowledgement )) - - // Executes Application logic ∀ Payload - nAck=0 - for payload in packet.data + + if(acknowledgement!= SENTINEL_ACKNOWLEDGEMENT){ // Do we want this? + // Executes Application logic ∀ Payload cbs = router.callbacks[payload.sourcePort] - success= cbs.OnAcknowledgePacket(packet.sourceId,payload, acknowledgement.appAcknowledgement[nAck]) - abortUnless(success) - nAck++ + success= cbs.OnAcknowledgePacket(packet.sourceId,payload, acknowledgement) + abortUnless(success) + } channelStore.delete(packetCommitmentPath(packet.sourceId, packet.sequence)) } @@ -815,10 +811,9 @@ function timeoutPacket( ) { // Channel and Client Checks channel = getChannel(packet.sourceId) - client = channel.clientId //client = router.clients[packet.sourceId] + client = router.clients[packet.sourceId] assert(client !== null) - // assert(client.id === channel.clientId) //assert(packet.destId == channel.counterpartyChannelId) @@ -844,10 +839,9 @@ function timeoutPacket( merklePath )) - for payload in packet.data - cbs = router.callbacks[payload.sourcePort] - success=cbs.OnTimeoutPacket(packet.sourceId,payload) - abortUnless(success) + cbs = router.callbacks[payload.sourcePort] + success=cbs.OnTimeoutPacket(packet.sourceId,payload) + abortUnless(success) channelStore.delete(packetCommitmentPath(packet.sourceId, packet.sequence)) } @@ -887,7 +881,7 @@ TODO Mmmm ..Not applicable. ## Forwards Compatibility -Data structures & encoding can be versioned at the application level. Core logic is completely agnostic to packet.data formats, which can be changed by the application modules any way they like at any time. +Future updates of this spec are expected to cover the multi-payload and multi-acknowledgment execution logic. ## Example Implementations From 1bf83351bacf190af3c0b93a3e5878dc62890258 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Tue, 8 Oct 2024 12:50:43 +0200 Subject: [PATCH 35/71] improve createClient conditions --- .../v2/ics-004-packet-semantics/README.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index edd6eee6c..915eac496 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -262,12 +262,12 @@ The channel creation process establishes the communication pathway between two c ###### Conditions Table -| **Condition Type** | **Description** | +| **Condition Type** | **Description** | **Code Checks** | |-------------------------------|-----------------------------------------------------------------------------------------------------| -| **Ante-Conditions** | - The clientId provided in input to createChannel MUST exist. | -| **Error-Conditions** | - Incorrect clientId.
- Unexpected keyPrefix format. | -| **Post-Conditions (Success)** | - A channel is set in store and it's accessible with key channelId.
- The creator is set in store and it's accessible with key [channelId, address]. | -| **Post-Conditions (Error)** | - If one payload fails, then all state changes happened on the successful application execution must be reverted.
- No packetCommitment has been generated.
- The sequence number bound to sourceId MUST be unchanged. | +| **ante-conditions** | - The used clientId exist. `createClient` has been called at least once. | +| **error-conditions** | - Incorrect clientId.
- Unexpected keyPrefix format.
- Invalid channelId .
| - `client==null`.
- `isFormatOk(counterpartyKeyPrefix)==False`.
- `validatedChannelIdentifier(channelId)==False`.
- `getChannel(channelId)!=null`.
| +| **post-conditions (success)** | - A channel is set in store and it's accessible with key channelId.
- The creator is set in store and it's accessible with key channelId.
- nextSequenceSend is initialized.
- client is stored in the router | - `storedChannel[channelId]!=null`.
- `channelCreator[channelId]!=null`.
- `router[channelId]!=null`.
- `nextSequenceSend[channelId]==1` | +| **post-conditions (error)** | - None of the post-conditions (success) is true.
| - `storedChannel[channelId]==null`.
- `channelCreator[channelId]==null`.
- `router[channelId]==null`.
- `nextSequenceSend[channelId]!=1| ###### Pseudo-Code @@ -657,6 +657,15 @@ The IBC handler performs the following steps in order: - Checks that an acknowledgement for this packet has not yet been written - Sets the opaque acknowledgement value at a store path unique to the packet +###### Conditions Table + +| **Condition Type** | **Description** | +|-------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------| +| **Ante-Conditions** | - `receivePacket` has been called on chain `B`.
`onReceivePacket` application callback has been executed.| +| **Error-Conditions** | - acknowledgement is empty.
- The `packetAcknowledgementPath` stores already a value. | +| **Post-Conditions (Success)** | - The opaque acknowledgement has been written at `packetAcknowledgementPath`. | +| **Post-Conditions (Error)** | - No value is stored at the `packetAcknowledgementPath`. | + ```typescript function writeAcknowledgement( packet: Packet, From 8233ba2a8b7ece0a742524f3c4b6fce4524da1c7 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Tue, 8 Oct 2024 12:53:35 +0200 Subject: [PATCH 36/71] table fix --- spec/core/v2/ics-004-packet-semantics/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 915eac496..202670c6e 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -263,8 +263,8 @@ The channel creation process establishes the communication pathway between two c ###### Conditions Table | **Condition Type** | **Description** | **Code Checks** | -|-------------------------------|-----------------------------------------------------------------------------------------------------| -| **ante-conditions** | - The used clientId exist. `createClient` has been called at least once. | +|-------------------------------|------------------| ----------------| +| **ante-conditions** | - The used clientId exist. `createClient` has been called at least once.| | | **error-conditions** | - Incorrect clientId.
- Unexpected keyPrefix format.
- Invalid channelId .
| - `client==null`.
- `isFormatOk(counterpartyKeyPrefix)==False`.
- `validatedChannelIdentifier(channelId)==False`.
- `getChannel(channelId)!=null`.
| | **post-conditions (success)** | - A channel is set in store and it's accessible with key channelId.
- The creator is set in store and it's accessible with key channelId.
- nextSequenceSend is initialized.
- client is stored in the router | - `storedChannel[channelId]!=null`.
- `channelCreator[channelId]!=null`.
- `router[channelId]!=null`.
- `nextSequenceSend[channelId]==1` | | **post-conditions (error)** | - None of the post-conditions (success) is true.
| - `storedChannel[channelId]==null`.
- `channelCreator[channelId]==null`.
- `router[channelId]==null`.
- `nextSequenceSend[channelId]!=1| From 47b188ed6f5d7c06d226667d19f6fb38053e03bf Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Tue, 8 Oct 2024 13:18:07 +0200 Subject: [PATCH 37/71] registerChannel table --- .../v2/ics-004-packet-semantics/README.md | 57 +++++++++++++++---- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 202670c6e..4a4b39dca 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -265,9 +265,9 @@ The channel creation process establishes the communication pathway between two c | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|------------------| ----------------| | **ante-conditions** | - The used clientId exist. `createClient` has been called at least once.| | -| **error-conditions** | - Incorrect clientId.
- Unexpected keyPrefix format.
- Invalid channelId .
| - `client==null`.
- `isFormatOk(counterpartyKeyPrefix)==False`.
- `validatedChannelIdentifier(channelId)==False`.
- `getChannel(channelId)!=null`.
| +| **error-conditions** | - Incorrect clientId.
- Unexpected keyPrefix format.
- Invalid channelId .
| - `client==null`.
- `isFormatOk(counterpartyKeyPrefix)==False`.
- `validatedChannelId(channelId)==False`.
- `getChannel(channelId)!=null`.
| | **post-conditions (success)** | - A channel is set in store and it's accessible with key channelId.
- The creator is set in store and it's accessible with key channelId.
- nextSequenceSend is initialized.
- client is stored in the router | - `storedChannel[channelId]!=null`.
- `channelCreator[channelId]!=null`.
- `router[channelId]!=null`.
- `nextSequenceSend[channelId]==1` | -| **post-conditions (error)** | - None of the post-conditions (success) is true.
| - `storedChannel[channelId]==null`.
- `channelCreator[channelId]==null`.
- `router[channelId]==null`.
- `nextSequenceSend[channelId]!=1| +| **post-conditions (error)** | - None of the post-conditions (success) is true.
| - `storedChannel[channelId]==null`.
- `channelCreator[channelId]==null`.
- `router[channelId]==null`.
- `nextSequenceSend[channelId]!=1`| ###### Pseudo-Code @@ -304,7 +304,14 @@ function createChannel( // Initialise the nextSequenceSend nextSequenceSend[channelId]=1 - return channelId + // Event Emission + emitLogEntry("sendPacket", { + channelId: channelId, + channel: channel, + creatorAddress: msg.signer(), + }) + + return channelId } ``` @@ -318,12 +325,12 @@ To enable mutual and verifiable identification, IBC version 2 introduces a `regi ###### Conditions Table -| **Condition Type** | **Description** | -|-------------------------------|---------------------------------------------------------------------------------------------------------------------------------| -| **Ante-Conditions** | - The channelID provided in input MUST properly resolve to a channel. | -| **Error-Conditions** | - Incorrect channelId.
- Authentication Failed. | -| **Post-Conditions (Success)** | - The channel in store contains the counterpartyChannelId information and it's accessible with key channelId. | -| **Post-Conditions (Error)** | - On the first call, the channel in store contains the counterpartyChannelId as an empty field.
- On the second call, the channel in store contains the old counterpartyChannelId information. | +| **Condition Type** | **Description** | **Code Checks** | +|-------------------------------|-----------------------------------|----------------------------| +| **Ante-Conditions** | - The `createChannel` has been called at least once| | +| **Error-Conditions** | - Incorrect channelId.
- Unregistered client.
- Creator authentication failed | - `validatedChannelId(channelId)==False`.
- `getChannel(channelId)==null`.
- `router[channelId]==null`.
- `channelCreator[channelId]!=msg.signer()`.
| +| **Post-Conditions (Success)** | - The channel in store contains the counterpartyChannelId information and it's accessible with key channelId. | - `storedChannel[channelId].counterpartyChannelId!=""`.
| +| **Post-Conditions (Error)** | - On the first call, the channel in store contains the counterpartyChannelId as an empty field.
| - `storedChannel[channelId].counterpartyChannelId==""` | ###### Pseudo-Code @@ -353,6 +360,14 @@ function registerChannel( // Local Store storedChannels[channelId]=channel + + // log that a packet can be safely sent + // Event Emission + emitLogEntry("sendPacket", { + channelId: channelId, + channel: channel, + creatorAddress: msg.signer(), + }) } ``` @@ -529,7 +544,7 @@ function sendPacket( nextSequenceSend[sourceChannelId]=sequence+1 // log that a packet can be safely sent - // Discussion needed: What we need to emit the log? + // Event Emission emitLogEntry("sendPacket", { sourceId: sourceChannelId, destId: channel.counterpartyChannelId, @@ -630,6 +645,7 @@ function recvPacket( ) // log that a packet has been received + // Event Emission emitLogEntry("recvPacket", { data: packet.data timeoutTimestamp: packet.timeoutTimestamp, @@ -683,6 +699,7 @@ function writeAcknowledgement( packetAcknowledgementPath(packet.destId, packet.sequence),commit) // log that a packet has been acknowledged + // Event Emission emitLogEntry("writeAcknowledgement", { sequence: packet.sequence, sourceId: packet.sourceId, @@ -756,6 +773,16 @@ function acknowledgePacket( } channelStore.delete(packetCommitmentPath(packet.sourceId, packet.sequence)) + + // Event Emission // Check fields + emitLogEntry("acknowledgePacket", { + sequence: packet.sequence, + sourceId: packet.sourceId, + destId: packet.destId, + timeoutTimestamp: packet.timeoutTimestamp, + data: packet.data, + acknowledgement + }) } ``` @@ -853,6 +880,16 @@ function timeoutPacket( abortUnless(success) channelStore.delete(packetCommitmentPath(packet.sourceId, packet.sequence)) + + // Event Emission // See fields + emitLogEntry("timeoutPacket", { + sequence: packet.sequence, + sourceId: packet.sourceId, + destId: packet.destId, + timeoutTimestamp: packet.timeoutTimestamp, + data: packet.data, + acknowledgement + }) } ``` From bdbc46f73b1121cd04979fff91cb914151f40ff5 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Tue, 8 Oct 2024 15:57:18 +0200 Subject: [PATCH 38/71] fixes --- .../v2/ics-004-packet-semantics/README.md | 131 +++++++++--------- 1 file changed, 67 insertions(+), 64 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 4a4b39dca..2a7a8eb95 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -62,8 +62,8 @@ A `Packet`, in the interblockchain communication protocol, is a particular inter ```typescript interface Packet { - sourceId: bytes, // identifier on the source chain. - destId: bytes, // identifier on the dest chain. + sourceChannelId: bytes, // channel identifier on the source chain. + destChannelId: bytes, // channel identifier on the dest chain. sequence: uint64, // number that corresponds to the order of sent packets. timeout: uint64, // indicates the UNIX timestamp in seconds and is encoded in LittleEndian. It must be passed on the destination chain and once elapsed, will no longer allow the packet processing, and will instead generate a time-out. data: [Payload] // data @@ -116,6 +116,8 @@ interface Acknowledgement { } ``` +// NEED DISCUSSION, Can we delete the SENTINEL_ACKNOWLEDGMENT? + An application may not need to return an acknowledgment with after processing relevant data. In this case, it is advised to return a sentinel acknowledgement value `SENTINEL_ACKNOWLEDGMENT`, which will be the single byte in the byte array: `bytes(0x01)`. Returning this `SENTINEL_ACKNOWLEDGMENT` value allows the sender chain to still call the `acknowledgePacket` handler, e.g. to delete the packet commitment, without triggering the `onAcknowledgePacket` callback. @@ -133,7 +135,7 @@ type IBCRouter struct { The proper registration of the application callbacks in the local `IBCRouter`, is responsibility of the chain. While the registration of the client under the key channelId is part of the setup procedure. Without the execution of registration process, the packets cannot be processed by the application. -- The `MAX_TIMEOUT_DELTA` is intendend as the max difference between currentTimestamp and timeoutTimestamp that can be given in input. +- The `MAX_TIMEOUT_DELTA` is intendend as the max, absolute, difference between currentTimestamp and timeoutTimestamp that can be given in input to `sendPacket`. ```typescript const MAX_TIMEOUT_DELTA = Implementation specific // We recommend MAX_TIMEOUT_DELTA = TDB @@ -176,8 +178,8 @@ Thus, Constant-size commitments to packet data fields are stored under the packe // NEED DISCUSSION -- we could use "commitments/{sourceId}/{sequence}" or "0x01/{sourceId}/{sequence}". For now we keep going with more or less standard paths ```typescript -function packetCommitmentPath(sourceId: bytes, sequence: BigEndianUint64): Path { - return "commitments/channels/{sourceId}/sequences/{sequence}" +function packetCommitmentPath(channelSourceId: bytes, sequence: BigEndianUint64): Path { + return "commitments/channels/{channelSourceId}/sequences/{sequence}" } ``` @@ -186,16 +188,16 @@ 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`. ```typescript -function packetReceiptPath(sourceId: bytes, sequence: BigEndianUint64): Path { - return "receipts/channels/{sourceId}/sequences/{sequence}" +function packetReceiptPath(channelDestId: bytes, sequence: BigEndianUint64): Path { + return "receipts/channels/{channelDestId}/sequences/{sequence}" } ``` Packet acknowledgement data are stored under the `packetAcknowledgementPath`: ```typescript -function packetAcknowledgementPath(sourceId: bytes, sequence: BigEndianUint64): Path { - return "acks/channels/{sourceId}/sequences/{sequence}" +function packetAcknowledgementPath(channelSourceId: bytes, sequence: BigEndianUint64): Path { + return "acks/channels/{channelSourceId}/sequences/{sequence}" } ``` @@ -266,7 +268,7 @@ The channel creation process establishes the communication pathway between two c |-------------------------------|------------------| ----------------| | **ante-conditions** | - The used clientId exist. `createClient` has been called at least once.| | | **error-conditions** | - Incorrect clientId.
- Unexpected keyPrefix format.
- Invalid channelId .
| - `client==null`.
- `isFormatOk(counterpartyKeyPrefix)==False`.
- `validatedChannelId(channelId)==False`.
- `getChannel(channelId)!=null`.
| -| **post-conditions (success)** | - A channel is set in store and it's accessible with key channelId.
- The creator is set in store and it's accessible with key channelId.
- nextSequenceSend is initialized.
- client is stored in the router | - `storedChannel[channelId]!=null`.
- `channelCreator[channelId]!=null`.
- `router[channelId]!=null`.
- `nextSequenceSend[channelId]==1` | +| **post-conditions (success)** | - A channel is set in store and it's accessible with key channelId.
- The creator is set in store and it's accessible with key channelId.
- nextSequenceSend is initialized.
- client is stored in the router.
- an event with relevant fields is emitted | - `storedChannel[channelId]!=null`.
- `channelCreator[channelId]!=null`.
- `router[channelId]!=null`.
- `nextSequenceSend[channelId]==1` | | **post-conditions (error)** | - None of the post-conditions (success) is true.
| - `storedChannel[channelId]==null`.
- `channelCreator[channelId]==null`.
- `router[channelId]==null`.
- `nextSequenceSend[channelId]!=1`| ###### Pseudo-Code @@ -305,7 +307,7 @@ function createChannel( nextSequenceSend[channelId]=1 // Event Emission - emitLogEntry("sendPacket", { + emitLogEntry("createChannel", { channelId: channelId, channel: channel, creatorAddress: msg.signer(), @@ -329,7 +331,7 @@ To enable mutual and verifiable identification, IBC version 2 introduces a `regi |-------------------------------|-----------------------------------|----------------------------| | **Ante-Conditions** | - The `createChannel` has been called at least once| | | **Error-Conditions** | - Incorrect channelId.
- Unregistered client.
- Creator authentication failed | - `validatedChannelId(channelId)==False`.
- `getChannel(channelId)==null`.
- `router[channelId]==null`.
- `channelCreator[channelId]!=msg.signer()`.
| -| **Post-Conditions (Success)** | - The channel in store contains the counterpartyChannelId information and it's accessible with key channelId. | - `storedChannel[channelId].counterpartyChannelId!=""`.
| +| **Post-Conditions (Success)** | - The channel in store contains the counterpartyChannelId information and it's accessible with key channelId.
An event with relevant information has been emitted | - `storedChannel[channelId].counterpartyChannelId!=""`.
| | **Post-Conditions (Error)** | - On the first call, the channel in store contains the counterpartyChannelId as an empty field.
| - `storedChannel[channelId].counterpartyChannelId==""` | ###### Pseudo-Code @@ -363,7 +365,7 @@ function registerChannel( // log that a packet can be safely sent // Event Emission - emitLogEntry("sendPacket", { + emitLogEntry("registerChannel", { channelId: channelId, channel: channel, creatorAddress: msg.signer(), @@ -371,18 +373,9 @@ function registerChannel( } ``` -// REWORK THIS - -The `registerChannel` method allows for authentication data that implementations may verify before storing the provided counterparty identifier. The strongest authentication possible is to have a valid clientState and consensus state of our chain in the authentication along with a proof it was stored at the claimed counterparty identifier. A simpler but weaker authentication would simply be to check that the `registerChannel` message is sent by the same relayer that initialized the client. This would make the client parameters completely initialized by the relayer. Thus, users must verify that the client is pointing to the correct chain and that the counterparty identifier is correct as well before using the pair. - - // custom-authentication - assert(verify(authentication)) +The protocol recommended authentication is to check that the `registerChannel` message is sent by the same relayer that initialized the client such that the `msg.signer()==channelCreator[channelId]`. This would make the client and channel parameters completely initialized by the relayer. Thus, users must verify that the client is pointing to the correct chain and that the counterparty identifier is correct as well before using the pair. -Thus, once two chains have set up clients, created channel and registered channels for each other with specific Identifiers, they can send IBC packets using the packet interface defined before and the packet handlers that the ICS-04 defines below. - -The packets will be addressed **directly** with the channels that have semantic link to the underlying light clients. Thus there are **no** more handshakes necessary. Instead the packet sender must be capable of providing the correct pair. - -If the setup has been executed correctly, then the correctness and soundness properties of IBC holds. IBC packet flow is guaranteed to succeed. If a user sends a packet with the wrong destination channel, then as we will see it will be impossible for the intended destination to correctly verify the packet thus, the packet will simply time out. +Thus, once two chains have set up clients, created channel and registered channels for each other with specific Identifiers, they can send IBC packets using the packet interface defined before and the packet handlers that the ICS-04 defines below. The packets will be addressed **directly** with the channels that have semantic link to the underlying light clients. Thus there are **no** more handshakes necessary. Instead the packet sender must be capable of providing the correct pair. If the setup has been executed correctly, then the correctness and soundness properties of IBC holds. IBC packet flow is guaranteed to succeed. If a user sends a packet with the wrong destination channel, then as we will see it will be impossible for the intended destination to correctly verify the packet thus, the packet will simply time out. #### Packet Flow Function Handlers @@ -488,12 +481,12 @@ Note that the full packet is not stored in the state of the chain - merely a sho ###### Conditions Table -| **Condition Type** | **Description** | -|-------------------------------|---------------------------------------------------------------------------------------------------------------------------------| -| **Ante-Conditions** | - Chains `A` and `B` MUST be in a setup final state.
- Inputs channelId and timeoutTimestamp are valid. | -| **Error-Conditions** | - Incorrect setup (includes invalid client and invalid channelId).
- Invalid timeoutTimestamp.
- Unsuccessful payload execution. | -| **Post-Conditions (Success)** | - All the applications contained in the payload have properly terminated the `onSendPacket` callback execution.
- The packetCommitment has been generated.
- The sequence number bound to sourceId MUST have been incremented by 1. | -| **Post-Conditions (Error)** | - If one payload fails, then all state changes happened on the successful application execution must be reverted.
- No packetCommitment has been generated.
- The sequence number bound to sourceId MUST be unchanged. | +| **Condition Type** |**Description** | **Code Checks**| +|-------------------------------|--------------------------------------------------------|------------------------| +| **Ante-Conditions** | - Chains `A` and `B` MUST be in a setup final state.
| | +| **Error-Conditions** | - Incorrect setup (includes invalid client and invalid channelId).
- Invalid timeoutTimestamp.
- Unsuccessful payload execution. | - `getChannel(sourceChannelId)==null`.
, -`router[sourceChannelId]==null`.
- `timeoutTimestamp==0`.
- `timeoutTimestamp < currentTimestamp()`.
- `timeoutTimestamp > currentTimestamp() + MAX_TIMEOUT_DELTA`.
- `success=onSendPacket(..), success==False`.
| +| **Post-Conditions (Success)** | - All the applications contained in the payload have properly terminated the `onSendPacket` callback execution.
- The packetCommitment has been generated and stored under the right packetCommitmentPath.
- The sequence number bound to sourceId MUST have been incremented by 1.
| An event with relevant information has been emitted | +| **Post-Conditions (Error)** | - If one payload fails, then all state changes happened on the successful application execution must be reverted.
- No packetCommitment has been generated.
- The sequence number bound to sourceId MUST be unchanged. | | ###### Pseudo-Code @@ -501,13 +494,14 @@ The ICS04 provides an example pseudo-code that enforce the above described condi ```typescript function sendPacket( - sourceChannelId: bytes, + sourceChannelId: bytes, timeoutTimestamp: uint64, payloads: []byte ) { // Setup checks - channel and client channel = getChannel(sourceChannelId) + assert(channel !== null) client = router.clients[sourceChannelId] assert(client !== null) @@ -517,14 +511,20 @@ function sendPacket( // disallow packet with timeoutTimestamp less than currentTimestamp and timeoutTimestamp value bigger than currentTimestamp + MaxTimeoutDelta assert(currentTimestamp() < timeoutTimestamp < currentTimestamp() + MAX_TIMEOUT_DELTA) - // if the sequence doesn't already exist, this call initializes the sequence to 0 + //assert(packet.sourceId == channel.counterpartyChannelId) This should be always true, redundant // NEED DISCUSSION + + // retrieve sequence sequence = nextSequenceSend[sourecChannelId] + // Check that the Sequence has been correctly initialized before hand. + abortTransactionUnless(sequence!==0) // Executes Application logic ∀ Payload + // Currently we support only len(payloads)==1 + payload=payloads[0] cbs = router.callbacks[payload.sourcePort] success = cbs.onSendPacket(sourceChannelId,channel.counterpartyChannelId,payload) - // IMPORTANT: if the one of the onSendPacket fails, the transaction is aborted and the potential state changes are reverted. This ensure that - // the post conditions on error are always respected. + // IMPORTANT: if the onSendPacket fails, the transaction is aborted and the potential state changes are reverted. + // This ensure that the post conditions on error are always respected. // payload execution check abortUnless(success) @@ -533,7 +533,7 @@ function sendPacket( destId: channel.counterpartyChannelId, sequence: sequence, timeoutTimestamp: timeoutTimestamp, - payload: payloads + payloads: payloads } // store packet commitment using commit function defined in [packet specification](https://github.com/cosmos/ibc/blob/c7b2e6d5184b5310843719b428923e0c5ee5a026/spec/core/v2/ics-004-packet-semantics/PACKET.md) @@ -549,7 +549,7 @@ function sendPacket( sourceId: sourceChannelId, destId: channel.counterpartyChannelId, sequence: sequence, - data: data, + packet: packet, timeoutTimestamp: timeoutTimestamp, }) @@ -593,8 +593,8 @@ function recvPacket( relayer: string) { // Channel and Client Checks - channel = getChannel(packet.destId) - client = router.clients[packet.destId] + channel = getChannel(packet.channelDestId) + client = router.clients[packet.channelDestId] assert(client !== null) //assert(packet.sourceId == channel.counterpartyChannelId) This should be always true, redundant // NEED DISCUSSION @@ -604,13 +604,13 @@ function recvPacket( assert(currentTimestamp() < packet.timeoutTimestamp) // verify the packet receipt for this packet does not exist already - packetReceipt = provableStore.get(packetReceiptPath(packet.destId, packet.sequence)) + packetReceipt = provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence)) abortUnless(packetReceipt === null) //////// verify commitment // 1. retrieve keys - packetPath = packetCommitmentPath(packet.destId, packet.sequence) + packetPath = packetCommitmentPath(packet.channelDestId, packet.sequence) merklePath = applyPrefix(channel.keyPrefix, packetPath) // 2. reconstruct commit value based on the passed-in packet @@ -626,8 +626,9 @@ function recvPacket( // Executes Application logic ∀ Payload + payload=packet.data[0] cbs = router.callbacks[payload.destPort] - ack,success = cbs.onReceivePacket(packet.destId,payload) + ack,success = cbs.onReceivePacket(packet.channelDestId,payload) abortTransactionUnless(success) if ack != nil { // NOTE: Synchronous ack. @@ -640,7 +641,7 @@ function recvPacket( // we must set the receipt so it can be verified on the other side // it's the sentinel success receipt: []byte{0x01} provableStore.set( - packetReceiptPath(packet.destId, packet.sequence), + packetReceiptPath(packet.channelDestId, packet.sequence), SUCCESSFUL_RECEIPT ) @@ -650,8 +651,8 @@ function recvPacket( data: packet.data timeoutTimestamp: packet.timeoutTimestamp, sequence: packet.sequence, - sourceId: packet.sourceId, - destId: packet.destId, + sourceId: packet.channelSourceId, + destId: packet.channelDestId, relayer: relayer }) @@ -690,20 +691,20 @@ function writeAcknowledgement( abortTransactionUnless(len(acknowledgement) !== 0) // cannot already have written the acknowledgement - abortTransactionUnless(provableStore.get(packetAcknowledgementPath(packet.destId, packet.sequence) === null)) + abortTransactionUnless(provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) === null)) // create the acknowledgement coomit using the function defined in [packet specification](https://github.com/cosmos/ibc/blob/c7b2e6d5184b5310843719b428923e0c5ee5a026/spec/core/v2/ics-004-packet-semantics/PACKET.md) commit=commitV2Acknowledgment(acknowledgement) provableStore.set( - packetAcknowledgementPath(packet.destId, packet.sequence),commit) + packetAcknowledgementPath(packet.channelDestId, packet.sequence),commit) // log that a packet has been acknowledged // Event Emission emitLogEntry("writeAcknowledgement", { sequence: packet.sequence, - sourceId: packet.sourceId, - destId: packet.destId, + sourceId: packet.channelSourceId, + destId: packet.channelDestId, timeoutTimestamp: packet.timeoutTimestamp, data: packet.data, acknowledgement @@ -744,18 +745,18 @@ function acknowledgePacket( ) { // Channel and Client Checks - channel = getChannel(packet.sourceId) - client = router.clients[packet.sourceId] + channel = getChannel(packet.channelSourceId) + client = router.clients[packet.channelSourceId] assert(client !== null) //assert(packet.destId == channel.counterpartyChannelId) // Tautology // verify we sent the packet and haven't cleared it out yet - assert(provableStore.get(packetCommitmentPath(packet.sourceId, packet.sequence)) === commitV2Packet(packet)) + assert(provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === commitV2Packet(packet)) // verify that the acknowledgement exist at the desired path - ackPath = packetAcknowledgementPath(packet.destId, packet.sequence) + ackPath = packetAcknowledgementPath(packet.channelDestId, packet.sequence) merklePath = applyPrefix(channel.keyPrefix, ackPath) assert(client.verifyMembership( client.clientState @@ -767,18 +768,19 @@ function acknowledgePacket( if(acknowledgement!= SENTINEL_ACKNOWLEDGEMENT){ // Do we want this? // Executes Application logic ∀ Payload + payload=packet.data[0] cbs = router.callbacks[payload.sourcePort] - success= cbs.OnAcknowledgePacket(packet.sourceId,payload, acknowledgement) + success= cbs.OnAcknowledgePacket(packet.channelSourceId,payload, acknowledgement) abortUnless(success) } - channelStore.delete(packetCommitmentPath(packet.sourceId, packet.sequence)) + channelStore.delete(packetCommitmentPath(packet.channelSourceId, packet.sequence)) // Event Emission // Check fields emitLogEntry("acknowledgePacket", { sequence: packet.sequence, - sourceId: packet.sourceId, - destId: packet.destId, + sourceId: packet.channelSourceId, + destId: packet.channelDestId, timeoutTimestamp: packet.timeoutTimestamp, data: packet.data, acknowledgement @@ -846,15 +848,15 @@ function timeoutPacket( relayer: string ) { // Channel and Client Checks - channel = getChannel(packet.sourceId) - client = router.clients[packet.sourceId] + channel = getChannel(packet.channelSourceId) + client = router.clients[packet.channelSourceId] assert(client !== null) //assert(packet.destId == channel.counterpartyChannelId) // verify we sent the packet and haven't cleared it out yet - assert(provableStore.get(packetCommitmentPath(packet.sourceId, packet.sequence)) + assert(provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === commitV2Packet(packet)) // get the timestamp from the final consensus state in the channel path @@ -866,7 +868,7 @@ function timeoutPacket( asert(packet.timeoutTimestamp > 0 && proofTimestamp >= packet.timeoutTimestamp) // verify there is no packet receipt --> receivePacket has not been called - receiptPath = packetReceiptPath(packet.destId, packet.sequence) + receiptPath = packetReceiptPath(packet.channelDestId, packet.sequence) merklePath = applyPrefix(channel.keyPrefix, receiptPath) assert(client.verifyNonMembership( client.clientState, @@ -875,17 +877,18 @@ function timeoutPacket( merklePath )) + payload=packet.data[0] cbs = router.callbacks[payload.sourcePort] - success=cbs.OnTimeoutPacket(packet.sourceId,payload) + success=cbs.OnTimeoutPacket(packet.channelSourceId,payload) abortUnless(success) - channelStore.delete(packetCommitmentPath(packet.sourceId, packet.sequence)) + channelStore.delete(packetCommitmentPath(packet.channelSourceId, packet.sequence)) // Event Emission // See fields emitLogEntry("timeoutPacket", { sequence: packet.sequence, - sourceId: packet.sourceId, - destId: packet.destId, + sourceId: packet.channelSourceId, + destId: packet.channelDestId, timeoutTimestamp: packet.timeoutTimestamp, data: packet.data, acknowledgement From 12d6f49d9cb758981c89cd263cdc4f52b8570179 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Wed, 9 Oct 2024 14:47:33 +0200 Subject: [PATCH 39/71] improvements --- .../v2/ics-004-packet-semantics/README.md | 103 +++++++++--------- 1 file changed, 52 insertions(+), 51 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 2a7a8eb95..cd6bb3008 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -14,7 +14,6 @@ modified: 2019-08-25 TODO : - Resolve Need discussion -- Motivation - Improve Sketchs - Improve conditions set / think about more condition an proper presentation - Review Ack and Timeout carefully @@ -22,29 +21,25 @@ TODO : ## Synopsis -This standard defines the channel and packet semantics necessary for state machines implementing the Inter-Blockchain Communication (IBC) protocol, specifically version 2, to enable secure, verifiable, and efficient cross-chain messaging. +This standard defines the channel and packet semantics necessary for state machines implementing the Inter-Blockchain Communication (IBC) protocol version 2 to enable secure, verifiable, and efficient cross-chain messaging. -It specifies the mechanisms required to establish channels between two state machines (blockchains) where a channel serves as a semantic link between the chains and their counterparty light client representation, ensuring that both chains can process and verify packets exchanged between them. +It specifies the mechanisms to create channels and register them between two distinct state machines (blockchains) where a channel serves as a semantic link between the chains and their counterparty light client representation, ensuring that both chains can process and verify packets exchanged between them. The standard then details the processes for transmitting, receiving, acknowledging, and timing out data packets. The packet-flow semantics guarantee exactly-once packet delivery between chains, utilizing on-chain light clients for state verification and providing efficient routing of packet data to specific IBC applications. -Additionally, this document defines the ante-conditions, error conditions, and post-conditions for each packet flow handler. By using a well-defined packet interface and clear handling processes, ICS-04 aims to ensure consistency and security without imposing constraints on the internal workings of the state machines involved. Instead, it focuses on the mechanisms to establish the root of trust between two distinct chains, routing, verification, and application-level delivery guarantees required by the IBC protocol. - ### Motivation -TODO +The motivation for this specification is to formalize the semantics for both packet handling and channel creation and registration in the IBC version 2 protocol. These are fundamental components for enabling reliable, secure, and verifiable communication between independent blockchains. The document focuses on the mechanisms to establish the root of trust for starting the communication between two distinct chains, routing, verification, and application-level delivery guarantees required by the IBC protocol. -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. +This specification focuses on defining the mechanisms for creating channels, securely registering them between chains, and ensuring that packets sent across these channels are processed consistently and verifiably. By utilizing on-chain light clients for state verification, it enables chains to exchange data without requiring synchronous communication, ensuring that all packets are delivered exactly once, even in the presence of network delays or reordering. -> **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 `B` when a particular IBC packet is committed to chain `B`, and require outgoing sends of that packet on chain `A` to escrow an equal amount of the asset on chain `A` until the vouchers are later redeemed back to chain `A` with 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 chain `B` can later be redeemed back to chain `A`. - -The IBC version 2 will provide packet delivery between two chains communicating and identifying each other by on-chain light clients as specified in ICS-02. +To standardize both channel creation and packet flow semantics, this document also defines the ante-conditions, error conditions, and post-conditions for each defined function handler. By using a well-defined packet interface and clear handling processes, ICS-04 aims to ensure consistency and security across distinct implementations of the protocol ensuring reliability and security and tries not to impose constraints on the internal workings of the state machines. ### Definitions `get`, `set`, `delete`, and module-system related primitives are as defined in [ICS 24](../ics-024-host-requirements). -A `channel` is a data structure that facilitates exactly-once packet delivery between two blockchains acting as a communication pipeline between specific modules registered on separate chains, allowing for secure, verifiable transmission of packets. Channels can be registered to establish a semantic link between two chains and their respective light clients, ensuring that both chains can process and verify the packets exchanged. To establish the root of trust for secure interchain communication with a counterparty chain, each chain MUST register a channel maintaining the necessary counterparty information, such as the channel identifier of the counterparty chain, the light client identifier of the counterparty chain and the path used to store packet flow messages. +A `channel` is a data structure that facilitates exactly-once packet delivery between two blockchains acting as a communication pipeline between specific modules registered on separate chains, allowing for secure, verifiable transmission of packets. Channels can be created and registered to establish a semantic link between two chains and their respective light clients, ensuring that both chains can process and verify the packets exchanged. To establish the root of trust for secure interchain communication with a counterparty chain, each chain MUST register a channel maintaining the necessary counterparty information, such as the channel identifier of the counterparty chain, the light client identifier of the counterparty chain and the path used to store packet flow messages. ```typescript interface Channel { @@ -120,7 +115,7 @@ interface Acknowledgement { An application may not need to return an acknowledgment with after processing relevant data. In this case, it is advised to return a sentinel acknowledgement value `SENTINEL_ACKNOWLEDGMENT`, which will be the single byte in the byte array: `bytes(0x01)`. -Returning this `SENTINEL_ACKNOWLEDGMENT` value allows the sender chain to still call the `acknowledgePacket` handler, e.g. to delete the packet commitment, without triggering the `onAcknowledgePacket` callback. +When the receiver chain returns this `SENTINEL_ACKNOWLEDGMENT` it allows the sender chain to still call the `acknowledgePacket` handler, e.g. to delete the packet commitment, without triggering the `onAcknowledgePacket` callback. > **Example**: In the multi-data packet world, if a packet within 3 payloads intended for 3 different application is sent out, the expectation is that each of the payload is acted upon in the same order as it has been placed in the packet. Likewise, the array of `appAcknowledgement` is expected to be populated within the same order. @@ -133,7 +128,7 @@ type IBCRouter struct { } ``` -The proper registration of the application callbacks in the local `IBCRouter`, is responsibility of the chain. While the registration of the client under the key channelId is part of the setup procedure. Without the execution of registration process, the packets cannot be processed by the application. +The proper registration of the application callbacks in the local `IBCRouter`, is responsibility of the chain. While the registration of the client under the key channelId is part of the setup procedure. Note that without the execution of registration process, the packets cannot be processed by the applications. - The `MAX_TIMEOUT_DELTA` is intendend as the max, absolute, difference between currentTimestamp and timeoutTimestamp that can be given in input to `sendPacket`. @@ -165,6 +160,10 @@ The ICS-04 specification defines a set of conditions that the IBC protocol must - Channels should be permissioned to the application registered on the local router. Thus only the modules registered on the local router, and so associated with the channel, should be able to send or receive on it. +#### Supply conservation + +> **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 `B` when a particular IBC packet is committed to chain `B`, and require outgoing sends of that packet on chain `A` to escrow an equal amount of the asset on chain `A` until the vouchers are later redeemed back to chain `A` with 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 chain `B` can later be redeemed back to chain `A`. + ## Technical Specification ### Preliminaries @@ -223,27 +222,26 @@ function getChannel(channelId: bytes) Channel { #### Setup -To start the secure packet stream between the chains, chain `A` and chain `B` MUST execute the entire setup following this set of procedures: +The prerequisite for this setup procedure is the registration of the application's callback in the IBC router, which is stored within the IBC module. The responsibility for application registration lies with the chain during the module's setup. + +Once application's callback are registered, to start the secure packet stream between the chains, chain `A` and chain `B` MUST execute the setup following this set of procedures: | **Procedure** | **Responsible** | **Outcome** | |-----------------------------|---------------------|-----------------------------------------------------------------------------| -| **Application Registration** | Application Module | Registers application callbacks on the IBC router during module setup. | | **Client Creation** | Relayer | Both chains create a light client for the counterparty chain. | | **Channel Creation** | Relayer | A channel is created and linked to an underlying light client on both chains. | | **Channel Registration** | Relayer | Registers the `counterpartyChannelId` on both chains, linking the channels. | If any of the steps has been missed, this would result in an incorrect setup error during the packet handlers execution. -Below we provide the setup sequence diagram. -Note that as shown in the below setup sequence diagram the `createClient` message (as defined in ICS-02) may be bundled with the `createChannel` message in a single multiMsgTx. +Below we provide the setup sequence diagram. Note that, as shown in the sequence diagram, the `createClient` message (as defined in ICS-02) may be bundled with the `createChannel` message in a single multiMsgTx. ```mermaid sequenceDiagram Participant Chain A Participant Relayer Participant Chain B - Chain A --> Chain A: App callbacks registration on IBC Router - Chain B --> Chain B: App callbacks registration on IBC Router + Note over Chain A, Chain B: App callbacks were registered on the IBC Router as a prerequisite Relayer ->> Chain A : createClient(B chain) + createChannel Chain A ->> Relayer : clientId= X , channelId = Y Relayer ->> Chain B : createClient(A chain) + createChannel @@ -260,7 +258,7 @@ While the application callbacks registration MUST be handled by the application ##### Channel creation -The channel creation process establishes the communication pathway between two chains. The procedure ensures both chains have a mutually recognized channel, facilitating packet transmission through authenticated streams. +The channel creation process enables the creation of the two channel ends that can be linked to establishes the communication pathway between two chains. ###### Conditions Table @@ -323,7 +321,9 @@ Note that `createClient` message (as defined in ICS-02) may be bundled with the Each IBC chain MUST have the ability to idenfity its counterparty to ensure valid communication. While a client can prove any key/value path on the counterparty, knowing which identifier the counterparty uses when it sends messages to us is essential to prevent confusion between messages intended for different chains. -To enable mutual and verifiable identification, IBC version 2 introduces a `registerChannel` procedure. This process stores the `counterpartyChannelId` in the local channel structure, ensuring both chains have mirrored pairs. With the correct registration, the unique clients on each side provide an authenticated stream of packet data. Social consensus outside the protocol is relied upon to ensure only valid pairs are used, representing connections between the correct chains. +To enable mutual and verifiable identification, IBC version 2 introduces a `registerChannel` procedure. The channel registration procedure ensures both chains have a mutually recognized channel that facilitates the packet transmission. + +This process stores the `counterpartyChannelId` in the local channel structure, ensuring both chains have mirrored pairs. With the correct registration, the unique clients on each side provide an authenticated stream of packet data. Social consensus outside the protocol is relied upon to ensure only valid pairs are used, representing connections between the correct chains. ###### Conditions Table @@ -375,11 +375,11 @@ function registerChannel( The protocol recommended authentication is to check that the `registerChannel` message is sent by the same relayer that initialized the client such that the `msg.signer()==channelCreator[channelId]`. This would make the client and channel parameters completely initialized by the relayer. Thus, users must verify that the client is pointing to the correct chain and that the counterparty identifier is correct as well before using the pair. -Thus, once two chains have set up clients, created channel and registered channels for each other with specific Identifiers, they can send IBC packets using the packet interface defined before and the packet handlers that the ICS-04 defines below. The packets will be addressed **directly** with the channels that have semantic link to the underlying light clients. Thus there are **no** more handshakes necessary. Instead the packet sender must be capable of providing the correct pair. If the setup has been executed correctly, then the correctness and soundness properties of IBC holds. IBC packet flow is guaranteed to succeed. If a user sends a packet with the wrong destination channel, then as we will see it will be impossible for the intended destination to correctly verify the packet thus, the packet will simply time out. +Thus, once two chains have set up clients, created channel and registered channels for each other with specific Identifiers, they can send IBC packets using the packet interface defined before and the packet handlers that the ICS-04 defines below. The packets will be addressed **directly** with the channels that have semantic link to the underlying light clients. Thus there are **no** more handshakes necessary. Instead the packet sender must be capable of providing the correct pair. If the setup has been executed correctly, then the correctness and soundness properties of IBC holds and the IBC packet flow is guaranteed to succeed. If a user sends a packet with the wrong destination channel, then as we will see it will be impossible for the intended destination to correctly verify the packet, thus, the packet will simply time out. #### Packet Flow Function Handlers -In the IBC protocol version 2, the packet flow is managed by four key function handlers, each of which is responsible for distinct stages in the packet lifecycle: +In the IBC protocol version 2, the packet flow is managed by four key function handlers, each of which is responsible for a distinct stage in the packet lifecycle: - `sendPacket` - `receivePacket` @@ -464,7 +464,7 @@ The `acknowledgePacket` is not a valid action if `receivePacket` has not been ex ##### Sending packets -The `sendPacket` function is called by the IBC handler when an IBC packet is submitted to the newtwork in order to send *data* in the form of an IBC packet. ∀ `Payload` included in the `packet.data`, which may refer to a different application, the application specific callbacks are retrieved from the IBC router and the `onSendPacket` is the then triggered on the specified application. The `onSendPacket` executes the application logic. Once all payloads contained in the `packet.data` have been acted upon, the packet commitment is generated and the sequence number bind to the sourceId is incremented. +The `sendPacket` function is called by the IBC handler when an IBC packet is submitted to the newtwork in order to send *data* in the form of an IBC packet. ∀ `Payload` included in the `packet.data`, which may refer to a different application, the application specific callbacks are retrieved from the IBC router and the `onSendPacket` is the then triggered on the specified application. The `onSendPacket` executes the application logic. Once all payloads contained in the `packet.data` have been acted upon, the packet commitment is generated and the sequence number bound to the `channelSourceId` is incremented. The `sendPacket` core function MUST execute the applications logic atomically triggering the `onSendPacket` callback ∀ application contained in the `packet.data` payload. @@ -485,7 +485,7 @@ Note that the full packet is not stored in the state of the chain - merely a sho |-------------------------------|--------------------------------------------------------|------------------------| | **Ante-Conditions** | - Chains `A` and `B` MUST be in a setup final state.
| | | **Error-Conditions** | - Incorrect setup (includes invalid client and invalid channelId).
- Invalid timeoutTimestamp.
- Unsuccessful payload execution. | - `getChannel(sourceChannelId)==null`.
, -`router[sourceChannelId]==null`.
- `timeoutTimestamp==0`.
- `timeoutTimestamp < currentTimestamp()`.
- `timeoutTimestamp > currentTimestamp() + MAX_TIMEOUT_DELTA`.
- `success=onSendPacket(..), success==False`.
| -| **Post-Conditions (Success)** | - All the applications contained in the payload have properly terminated the `onSendPacket` callback execution.
- The packetCommitment has been generated and stored under the right packetCommitmentPath.
- The sequence number bound to sourceId MUST have been incremented by 1.
| An event with relevant information has been emitted | +| **Post-Conditions (Success)** | - All the applications contained in the payload have properly terminated the `onSendPacket` callback execution.
- The packetCommitment has been generated and stored under the right packetCommitmentPath.
- The sequence number bound to sourceId MUST has been incremented by 1.
| An event with relevant information has been emitted | | **Post-Conditions (Error)** | - If one payload fails, then all state changes happened on the successful application execution must be reverted.
- No packetCommitment has been generated.
- The sequence number bound to sourceId MUST be unchanged. | | ###### Pseudo-Code @@ -560,37 +560,38 @@ function sendPacket( The `recvPacket` function is called by the IBC handler in order to receive an IBC packet sent on the corresponding client on the counterparty chain. -Atomically in conjunction with calling `recvPacket`, the modules/application referred in the `packet.data` payload MUST execute the specific application logic callaback. +Atomically in conjunction with calling the core `receivePacket`, the modules/application referred in the `packet.data` payload MUST execute the specific application logic callaback. The IBC handler performs the following steps in order: -- Checks that the clients is valid -- Checks that the timeout timestamp is not yet passed -- Checks the inclusion proof of packet data commitment in the outgoing chain's state +- Checks that the client is valid +- Checks that the timeout timestamp is not yet passed on the receiving chain +- Checks the inclusion proof of packet data commitment in the sender chain's state - Sets a store path to indicate that the packet has been received -- Write the acknowledgement into the provableStore. +- If the flows supports synchronous acknowledgement, it writes the acknowledgement into the receiver provableStore. We pass the address of the `relayer` that signed and submitted the packet to enable a module to optionally provide some rewards. This provides a foundation for fee payment, but can be used for other techniques as well (like calculating a leaderboard). ###### Conditions Table -| **Condition Type** | **Description** | -|-------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------| -| **Ante-Conditions** | - Chain `A` MUST have stored the packetCommitment under the keyPrefix registered in the chain `B` channelEnd.
- TimeoutTimestamp MUST not have elapsed yet.
- PacketReceipt for the specific keyPrefix and sequence MUST be empty (e.g. `receivePacket` has not been called yet). | -| **Error-Conditions** | - Packet Errors: invalid packetCommitment, packetReceipt already exists.
- Invalid timeoutTimestamp.
- Unsuccessful payload execution. | -| **Post-Conditions (Success)** | - All the applications pointed in the payload have properly terminated the `onReceivePacket` callback execution.
- The packetReceipt has been written.
- The acknowledgement has been written. | -| **Post-Conditions (Error)** | - If one payload fails, then all state changes happened on the successful `onReceivePacket` application callback execution MUST be reverted.
- If timeoutTimestamp has elapsed then no state changes occurred. // NEED DISCUSSION (Is this ok? Shall we write the `timeout_sentinel_receipt`?) | +| **Condition Type** | **Description** | **Code Checks** | +|-------------------------------|-----------------------------------------------|-----------------------------------------------| +| **Ante-Conditions** | - Chain `A` MUST have stored the packetCommitment under the keyPrefix registered in the chain `B` channelEnd.
- TimeoutTimestamp MUST not have elapsed yet on the receiving chain.
- PacketReceipt for the specific keyPrefix and sequence MUST be empty (e.g. `receivePacket` has not been called yet). | | +| **Error-Conditions** | - Packet Errors: invalid packetCommitment, packetReceipt already exists.
- Invalid timeoutTimestamp.
- Unsuccessful payload execution. | | +| **Post-Conditions (Success)** | - All the applications pointed in the payload have properly terminated the `onReceivePacket` callback execution.
- The packetReceipt has been written.
- The acknowledgement has been written. | | +| **Post-Conditions (Error)** | - If one payload fails, then all state changes happened on the successful `onReceivePacket` application callback execution MUST be reverted.
- If timeoutTimestamp has elapsed then no state changes occurred. // NEED DISCUSSION (Is this ok? Shall we write the `timeout_sentinel_receipt`?) | | ###### Pseudo-Code -The ICS04 provides an example pseudo-code that enforce the above described conditions so that the following sequence of steps must occur for a packet to be received from module *1* on machine *A* to module *2* on machine *B*. +The ICS-04 provides an example pseudo-code that enforce the above described conditions so that the following sequence of steps SHOULD occur for a packet to be received from module *1* on machine *A* to module *2* on machine *B*. ```typescript function recvPacket( packet: OpaquePacket, proof: CommitmentProof, proofHeight: Height, - relayer: string) { + relayer: string // Need discussion + ) { // Channel and Client Checks channel = getChannel(packet.channelDestId) @@ -634,7 +635,7 @@ function recvPacket( // NOTE: Synchronous ack. writeAcknowledgement(packet, ack) } - // NOTE Asynchronous ack. + // NOTE No ack || Asynchronous ack. //else: ack is nil and won't be written || ack is nil and will be written asynchronously // Provable Stores @@ -676,12 +677,12 @@ The IBC handler performs the following steps in order: ###### Conditions Table -| **Condition Type** | **Description** | -|-------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------| -| **Ante-Conditions** | - `receivePacket` has been called on chain `B`.
`onReceivePacket` application callback has been executed.| -| **Error-Conditions** | - acknowledgement is empty.
- The `packetAcknowledgementPath` stores already a value. | -| **Post-Conditions (Success)** | - The opaque acknowledgement has been written at `packetAcknowledgementPath`. | -| **Post-Conditions (Error)** | - No value is stored at the `packetAcknowledgementPath`. | +| **Condition Type** | **Description** | **Code Checks** | +|-------------------------------|------------|------------| +| **Ante-Conditions** | - `receivePacket` has been called on chain `B`.
- `onReceivePacket` application callback has been executed.
- `writeAcknowledgement` has not been called yet | `provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) === null` | +| **Error-Conditions** | - acknowledgement is empty.
- The `packetAcknowledgementPath` stores already a value. | - `len(acknowledgement) === 0`.
- `provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) !== null` | +| **Post-Conditions (Success)** | - The opaque acknowledgement has been written at `packetAcknowledgementPath`. | - `provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) !== null` | +| **Post-Conditions (Error)** | - No value is stored at the `packetAcknowledgementPath`. | - `provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) === null` | ```typescript function writeAcknowledgement( @@ -722,12 +723,12 @@ The IBC hanlder MUST atomically trigger the callbacks execution of appropriate a Given that at this point of the packet flow, chain `B` has sucessfully received a packet, the ante-conditions defines what MUST be accomplished before chain `A` can properly execute the `acknowledgePacket` for the IBC v2 packet. -| **Condition Type** | **Description** | -|-------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------| -| **Ante-Conditions** | Given that at this point of the packet flow, chain `B` has successfully received a packet, the ante-conditions define what MUST be accomplished before chain `A` can properly execute the `acknowledgePacket` for the IBC v2 packet.
- Acknowledgment MUST be set in the `ackPath`.
- PacketCommitment has not been cleared out yet. | -| **Error-Conditions** | The Error-Conditions define the set of conditions that MUST trigger an error. For the `acknowledgePacket` handler, errors can come from three main categories:
- PacketCommitment already cleared out.
- Unset Acknowledgment.
- Unsuccessful payload execution. | -| **Post-Conditions (Success)** | The Post-Conditions on Success define which state changes MUST have occurred if the `acknowledgePacket` handler has been successfully executed.
- All the applications pointed in the payload have properly terminated the `onAcknowledgePacket` callback execution.
- The packetCommitment has been cleared out. | -| **Post-Conditions (Error)** | The Post-Conditions on Error define the states that MUST remain unchanged if an error occurred during the `onAcknowledgePacket` handler.
- If one payload fails, then all state changes that happened on the successful `onAcknowledgePacket` application callback execution MUST be reverted.
- The packetCommitment has not been cleared out.
| +| **Condition Type** | **Description** | **Code Checks** | +|-------------------------------|---------------------------------|---------------------------------| +| **Ante-Conditions** | - chain `B` has successfully received a packet and has written the acknowledgment.
- PacketCommitment has not been cleared out yet. |- `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === commitV2Packet(packet)`.
- `verifyMembership(packetacknowledgementPath,...,) == True` | +| **Error-Conditions** | - PacketCommitment already cleared out.
- Unset Acknowledgment.
- Unsuccessful payload execution. | - `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null`.
- `verifyMembership(packetacknowledgementPath,...,) == False`.
- `OnAcknowledgePacket(packet.channelSourceId,payload, acknowledgement) == False` | +| **Post-Conditions (Success)** | - All the applications pointed in the payload have properly terminated the `onAcknowledgePacket` callback execution.
- The packetCommitment has been cleared out. | - `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null` | +| **Post-Conditions (Error)** | - If one payload fails, then all state changes that happened on the successful `onAcknowledgePacket` application callback execution are reverted.
- The packetCommitment has not been cleared out.
| - `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === commitV2Packet(packet)` | ###### Pseudo-Code From eaa56e576dd1e171065304799666d809ab312352 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Wed, 9 Oct 2024 18:59:38 +0200 Subject: [PATCH 40/71] fixes --- .../v2/ics-004-packet-semantics/README.md | 112 +++++++++--------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index cd6bb3008..b6c925dc3 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -33,7 +33,7 @@ The motivation for this specification is to formalize the semantics for both pac This specification focuses on defining the mechanisms for creating channels, securely registering them between chains, and ensuring that packets sent across these channels are processed consistently and verifiably. By utilizing on-chain light clients for state verification, it enables chains to exchange data without requiring synchronous communication, ensuring that all packets are delivered exactly once, even in the presence of network delays or reordering. -To standardize both channel creation and packet flow semantics, this document also defines the ante-conditions, error conditions, and post-conditions for each defined function handler. By using a well-defined packet interface and clear handling processes, ICS-04 aims to ensure consistency and security across distinct implementations of the protocol ensuring reliability and security and tries not to impose constraints on the internal workings of the state machines. +To standardize both channel creation and packet flow semantics, this document also defines the pre-conditions, error conditions, and post-conditions for each defined function handler. By using a well-defined packet interface and clear handling processes, ICS-04 aims to ensure consistency and security across distinct implementations of the protocol ensuring reliability and security and tries not to impose constraints on the internal workings of the state machines. ### Definitions @@ -85,9 +85,9 @@ enum Encoding { } ``` -When the array of payloads, passed-in the packet, is populated with multiple values, the system will handle the packet as a multi-data packet. The multi-data packet handling logic is out of the scope of the current version of this spec. +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 interacting with the IBC handler. -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. +When the array of payloads, passed-in the packet, is populated with multiple values, the system will handle the packet as a multi-data packet. The multi-data packet handling logic is out of the scope of the current version of this spec. 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. @@ -95,7 +95,7 @@ An `OpaquePacket` is a packet, but cloaked in an obscuring data type by the host type OpaquePacket = object ``` -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`. +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 `receivePacket`. ```typescript enum PacketReceipt { @@ -117,18 +117,21 @@ An application may not need to return an acknowledgment with after processing re When the receiver chain returns this `SENTINEL_ACKNOWLEDGMENT` it allows the sender chain to still call the `acknowledgePacket` handler, e.g. to delete the packet commitment, without triggering the `onAcknowledgePacket` callback. -> **Example**: In the multi-data packet world, if a packet within 3 payloads intended for 3 different application is sent out, the expectation is that each of the payload is acted upon in the same order as it has been placed in the packet. Likewise, the array of `appAcknowledgement` is expected to be populated within the same order. +> **Example**: In the multi-data packet world, if a packet within 3 payloads intended for 3 different application is sent out, the expectation is that each payload is processed in the same order in which it was placed in the packet. Similarly, the `appAcknowledgement` array is expected to be populated within the same order. -- The `IBCRouter` contains a mapping from the application port and the supported callbacks and as well as a mapping from channelId to the underlying client. +- The `IBCRouter` contains a mapping from the application `portId` and the supported callbacks and as well as a mapping from `channelId` to the underlying client. ```typescript type IBCRouter struct { - callbacks: portId -> [Callback] // Maybe should be callbacks: portId,version -> [Callback] - clients: channelId -> Client + callbacks: (portId,version) -> [Callback] // The double key (portId,version) clearly separate the appVxCallbacks from appVyCallbacks simplifying the routing to app process + clients: clientId -> Client // The IBCRouter stores the client under the clientId key } ``` -The proper registration of the application callbacks in the local `IBCRouter`, is responsibility of the chain. While the registration of the client under the key channelId is part of the setup procedure. Note that without the execution of registration process, the packets cannot be processed by the applications. +The registration of the application callbacks in the local `IBCRouter`, is responsibility of the chain modules. +The registration of the client in the local `IBCRouter` is responsibility of the ICS-02 initialise client procedure. + +> **Note** The proper configuration of the IBCRouter is a prerequisite for starting the stream of packets. - The `MAX_TIMEOUT_DELTA` is intendend as the max, absolute, difference between currentTimestamp and timeoutTimestamp that can be given in input to `sendPacket`. @@ -136,7 +139,7 @@ The proper registration of the application callbacks in the local `IBCRouter`, i const MAX_TIMEOUT_DELTA = Implementation specific // We recommend MAX_TIMEOUT_DELTA = TDB ``` -The ICS-04 specification defines a set of conditions that the IBC protocol must adhere to. These conditions ensure the proper execution of the function handlers by establishing requirements before execution `ante-conditions`, possible error conditions during execution `error-conditions`, expected outcomes after succesful execution `post-conditions-on-success`, and expected outcomes after error execution `post-conditions-on-error`. Thus, implementation that wants to comply with the specification of the IBC version 2 protocol MUST adheres to the specified conditions. +Additionally the ICS-04 specification defines a set of conditions that the implementations of the IBC protocol version 2 MUST adhere to. These conditions ensure the proper execution of the function handlers by establishing requirements before execution `pre-conditions`, the conditions that MUST trigger errors during execution `error-conditions`, expected outcomes after succesful execution `post-conditions-on-success`, and expected outcomes after error execution `post-conditions-on-error`. ### Desired Properties @@ -144,13 +147,11 @@ The ICS-04 specification defines a set of conditions that the IBC protocol must - The speed of packet transmission and confirmation should be limited only by the speed of the underlying chains. - Proofs should be batchable where possible. -- The system MUST be able to process atomically the multiple payloads contained in a single IBC packet, to reduce the amount of packet flows. #### 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. +- 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 @@ -160,7 +161,7 @@ The ICS-04 specification defines a set of conditions that the IBC protocol must - Channels should be permissioned to the application registered on the local router. Thus only the modules registered on the local router, and so associated with the channel, should be able to send or receive on it. -#### Supply conservation +#### Fungibility conservation > **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 `B` when a particular IBC packet is committed to chain `B`, and require outgoing sends of that packet on chain `A` to escrow an equal amount of the asset on chain `A` until the vouchers are later redeemed back to chain `A` with 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 chain `B` can later be redeemed back to chain `A`. @@ -222,9 +223,7 @@ function getChannel(channelId: bytes) Channel { #### Setup -The prerequisite for this setup procedure is the registration of the application's callback in the IBC router, which is stored within the IBC module. The responsibility for application registration lies with the chain during the module's setup. - -Once application's callback are registered, to start the secure packet stream between the chains, chain `A` and chain `B` MUST execute the setup following this set of procedures: +To start the secure packet stream between the chains, chain `A` and chain `B` MUST execute the setup following this set of procedures: | **Procedure** | **Responsible** | **Outcome** | |-----------------------------|---------------------|-----------------------------------------------------------------------------| @@ -232,7 +231,7 @@ Once application's callback are registered, to start the secure packet stream be | **Channel Creation** | Relayer | A channel is created and linked to an underlying light client on both chains. | | **Channel Registration** | Relayer | Registers the `counterpartyChannelId` on both chains, linking the channels. | -If any of the steps has been missed, this would result in an incorrect setup error during the packet handlers execution. +> **Note** The setup procedure is a prerequisite for starting the packet stream. If any of the steps has been missed, this would result in an incorrect setup error during the packet handlers execution. Below we provide the setup sequence diagram. Note that, as shown in the sequence diagram, the `createClient` message (as defined in ICS-02) may be bundled with the `createChannel` message in a single multiMsgTx. @@ -241,7 +240,6 @@ sequenceDiagram Participant Chain A Participant Relayer Participant Chain B - Note over Chain A, Chain B: App callbacks were registered on the IBC Router as a prerequisite Relayer ->> Chain A : createClient(B chain) + createChannel Chain A ->> Relayer : clientId= X , channelId = Y Relayer ->> Chain B : createClient(A chain) + createChannel @@ -264,7 +262,7 @@ The channel creation process enables the creation of the two channel ends that c | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|------------------| ----------------| -| **ante-conditions** | - The used clientId exist. `createClient` has been called at least once.| | +| **pre-conditions** | - The used clientId exist. `createClient` has been called at least once.| | | **error-conditions** | - Incorrect clientId.
- Unexpected keyPrefix format.
- Invalid channelId .
| - `client==null`.
- `isFormatOk(counterpartyKeyPrefix)==False`.
- `validatedChannelId(channelId)==False`.
- `getChannel(channelId)!=null`.
| | **post-conditions (success)** | - A channel is set in store and it's accessible with key channelId.
- The creator is set in store and it's accessible with key channelId.
- nextSequenceSend is initialized.
- client is stored in the router.
- an event with relevant fields is emitted | - `storedChannel[channelId]!=null`.
- `channelCreator[channelId]!=null`.
- `router[channelId]!=null`.
- `nextSequenceSend[channelId]==1` | | **post-conditions (error)** | - None of the post-conditions (success) is true.
| - `storedChannel[channelId]==null`.
- `channelCreator[channelId]==null`.
- `router[channelId]==null`.
- `nextSequenceSend[channelId]!=1`| @@ -274,7 +272,7 @@ The channel creation process enables the creation of the two channel ends that c ```typescript function createChannel( clientId: bytes, - counterpartyKeyPrefix: CommitmentPrefix) (channelId: bytes){ + counterpartyKeyPrefix: CommitmentPrefix): bytes { // Implementation-Specific Input Validation // All implementations MUST ensure the inputs value are properly validated and compliant with this specification @@ -295,8 +293,6 @@ function createChannel( } // Local stores - // Update the IBC router registering the specific client under the channelId - router[channelId]=client // Store channel info storedChannels[channelId]=channel // Store creator address info @@ -329,10 +325,10 @@ This process stores the `counterpartyChannelId` in the local channel structure, | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|-----------------------------------|----------------------------| -| **Ante-Conditions** | - The `createChannel` has been called at least once| | -| **Error-Conditions** | - Incorrect channelId.
- Unregistered client.
- Creator authentication failed | - `validatedChannelId(channelId)==False`.
- `getChannel(channelId)==null`.
- `router[channelId]==null`.
- `channelCreator[channelId]!=msg.signer()`.
| -| **Post-Conditions (Success)** | - The channel in store contains the counterpartyChannelId information and it's accessible with key channelId.
An event with relevant information has been emitted | - `storedChannel[channelId].counterpartyChannelId!=""`.
| -| **Post-Conditions (Error)** | - On the first call, the channel in store contains the counterpartyChannelId as an empty field.
| - `storedChannel[channelId].counterpartyChannelId==""` | +| **pre-conditions** | - The `createChannel` has been called at least once| | +| **error-conditions** | - Incorrect channelId.
- Creator authentication failed | - `validatedChannelId(channelId)==False`.
- `getChannel(channelId)==null`.
- `channelCreator[channelId]!=msg.signer()`.
| +| **post-conditions (success)** | - The channel in store contains the counterpartyChannelId information and it's accessible with key channelId.
An event with relevant information has been emitted | - `storedChannel[channelId].counterpartyChannelId!=""`.
| +| **post-conditions (error)** | - On the first call, the channel in store contains the counterpartyChannelId as an empty field.
| - `storedChannel[channelId].counterpartyChannelId==""` | ###### Pseudo-Code @@ -350,10 +346,6 @@ function registerChannel( channel=getChannel(channelId) abortTransactionUnless(channel !== null) - // Check that a valid client is associated with the channelId - client=router.clients[channelId] - abortTransactionUnless(client !== null) - // Creator Address Checks abortTransactionUnless(msg.signer()===channelCreator[channelId]) @@ -373,7 +365,7 @@ function registerChannel( } ``` -The protocol recommended authentication is to check that the `registerChannel` message is sent by the same relayer that initialized the client such that the `msg.signer()==channelCreator[channelId]`. This would make the client and channel parameters completely initialized by the relayer. Thus, users must verify that the client is pointing to the correct chain and that the counterparty identifier is correct as well before using the pair. +The protocol uses as an authentication mechanisms checking that the `registerChannel` message is sent by the same relayer that initialized the client such that the `msg.signer()==channelCreator[channelId]`. This would make the client and channel parameters completely initialized by the relayer. Thus, users must verify that the client is pointing to the correct chain and that the counterparty identifier is correct as well before using the pair. Thus, once two chains have set up clients, created channel and registered channels for each other with specific Identifiers, they can send IBC packets using the packet interface defined before and the packet handlers that the ICS-04 defines below. The packets will be addressed **directly** with the channels that have semantic link to the underlying light clients. Thus there are **no** more handshakes necessary. Instead the packet sender must be capable of providing the correct pair. If the setup has been executed correctly, then the correctness and soundness properties of IBC holds and the IBC packet flow is guaranteed to succeed. If a user sends a packet with the wrong destination channel, then as we will see it will be impossible for the intended destination to correctly verify the packet, thus, the packet will simply time out. @@ -388,6 +380,8 @@ In the IBC protocol version 2, the packet flow is managed by four key function h Note that the execution of the four handler above described, upon a unique packet, cannot be combined in any arbitrary order. We provide the three possible example scenarios described with sequence diagrmas. +--- + Scenario execution with synchronous acknowledgement `A` to `B` - set of actions: `sendPacket` -> `receivePacket` -> `acknowledgePacket` ```mermaid @@ -413,6 +407,8 @@ sequenceDiagram Chain A --> Chain A : Delete packetCommitment ``` +--- + Scenario execution with asynchronous acknowledgement `A` to `B` - set of actions: `sendPacket` -> `receivePacket` -> `acknowledgePacket` Note that the key difference with the synchronous scenario is that the receivePacket writes only the packetReceipt and not the acknowledgement. The acknowledgement is instead written asynchronously for effect of the application callback call to the core function `writeAcknowledgement`, call that happens after the `receivePacket` execution. @@ -441,6 +437,8 @@ sequenceDiagram Chain A --> Chain A : Delete packetCommitment ``` +--- + Scenario timeout execution `A` to `B` - set of actions: `sendPacket` -> `timeoutPacket` ```mermaid @@ -459,11 +457,15 @@ sequenceDiagram Chain A --> Chain A : Delete packetCommitment ``` +--- + Given a configuration where we are sending a packet from `A` to `B` then chain `A` can call either, `sendPacket`,`acknowledgePacket` or `timeoutPacket` while chain `B` can only execute the `receivePacket` handler. The `acknowledgePacket` is not a valid action if `receivePacket` has not been executed. `timeoutPacket` is not a valid action if `receivePacket` occurred. ##### Sending packets +>**Note** Prerequisites: The `IBCRouter`s and the `channel`s have been properly configured on both chains. + The `sendPacket` function is called by the IBC handler when an IBC packet is submitted to the newtwork in order to send *data* in the form of an IBC packet. ∀ `Payload` included in the `packet.data`, which may refer to a different application, the application specific callbacks are retrieved from the IBC router and the `onSendPacket` is the then triggered on the specified application. The `onSendPacket` executes the application logic. Once all payloads contained in the `packet.data` have been acted upon, the packet commitment is generated and the sequence number bound to the `channelSourceId` is incremented. The `sendPacket` core function MUST execute the applications logic atomically triggering the `onSendPacket` callback ∀ application contained in the `packet.data` payload. @@ -483,7 +485,7 @@ Note that the full packet is not stored in the state of the chain - merely a sho | **Condition Type** |**Description** | **Code Checks**| |-------------------------------|--------------------------------------------------------|------------------------| -| **Ante-Conditions** | - Chains `A` and `B` MUST be in a setup final state.
| | +| **pre-conditions** | - Chains `A` and `B` MUST be in a setup final state.
| | | **Error-Conditions** | - Incorrect setup (includes invalid client and invalid channelId).
- Invalid timeoutTimestamp.
- Unsuccessful payload execution. | - `getChannel(sourceChannelId)==null`.
, -`router[sourceChannelId]==null`.
- `timeoutTimestamp==0`.
- `timeoutTimestamp < currentTimestamp()`.
- `timeoutTimestamp > currentTimestamp() + MAX_TIMEOUT_DELTA`.
- `success=onSendPacket(..), success==False`.
| | **Post-Conditions (Success)** | - All the applications contained in the payload have properly terminated the `onSendPacket` callback execution.
- The packetCommitment has been generated and stored under the right packetCommitmentPath.
- The sequence number bound to sourceId MUST has been incremented by 1.
| An event with relevant information has been emitted | | **Post-Conditions (Error)** | - If one payload fails, then all state changes happened on the successful application execution must be reverted.
- No packetCommitment has been generated.
- The sequence number bound to sourceId MUST be unchanged. | | @@ -497,12 +499,12 @@ function sendPacket( sourceChannelId: bytes, timeoutTimestamp: uint64, payloads: []byte - ) { + ) : BigEndianUint64 { // Setup checks - channel and client channel = getChannel(sourceChannelId) assert(channel !== null) - client = router.clients[sourceChannelId] + client = router.clients[channel.clientId] assert(client !== null) // timeoutTimestamp checks @@ -511,8 +513,7 @@ function sendPacket( // disallow packet with timeoutTimestamp less than currentTimestamp and timeoutTimestamp value bigger than currentTimestamp + MaxTimeoutDelta assert(currentTimestamp() < timeoutTimestamp < currentTimestamp() + MAX_TIMEOUT_DELTA) - //assert(packet.sourceId == channel.counterpartyChannelId) This should be always true, redundant // NEED DISCUSSION - + // retrieve sequence sequence = nextSequenceSend[sourecChannelId] // Check that the Sequence has been correctly initialized before hand. @@ -521,13 +522,14 @@ function sendPacket( // Executes Application logic ∀ Payload // Currently we support only len(payloads)==1 payload=payloads[0] - cbs = router.callbacks[payload.sourcePort] + cbs = router.callbacks[payload.sourcePort,payload.version] success = cbs.onSendPacket(sourceChannelId,channel.counterpartyChannelId,payload) // IMPORTANT: if the onSendPacket fails, the transaction is aborted and the potential state changes are reverted. // This ensure that the post conditions on error are always respected. // payload execution check abortUnless(success) + // Construct the packet packet = Packet { sourceId: sourceChannelId, destId: channel.counterpartyChannelId, @@ -553,6 +555,7 @@ function sendPacket( timeoutTimestamp: timeoutTimestamp, }) + return sequence } ``` @@ -576,7 +579,7 @@ We pass the address of the `relayer` that signed and submitted the packet to ena | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|-----------------------------------------------|-----------------------------------------------| -| **Ante-Conditions** | - Chain `A` MUST have stored the packetCommitment under the keyPrefix registered in the chain `B` channelEnd.
- TimeoutTimestamp MUST not have elapsed yet on the receiving chain.
- PacketReceipt for the specific keyPrefix and sequence MUST be empty (e.g. `receivePacket` has not been called yet). | | +| **pre-conditions** | - Chain `A` MUST have stored the packetCommitment under the keyPrefix registered in the chain `B` channelEnd.
- TimeoutTimestamp MUST not have elapsed yet on the receiving chain.
- PacketReceipt for the specific keyPrefix and sequence MUST be empty (e.g. `receivePacket` has not been called yet). | | | **Error-Conditions** | - Packet Errors: invalid packetCommitment, packetReceipt already exists.
- Invalid timeoutTimestamp.
- Unsuccessful payload execution. | | | **Post-Conditions (Success)** | - All the applications pointed in the payload have properly terminated the `onReceivePacket` callback execution.
- The packetReceipt has been written.
- The acknowledgement has been written. | | | **Post-Conditions (Error)** | - If one payload fails, then all state changes happened on the successful `onReceivePacket` application callback execution MUST be reverted.
- If timeoutTimestamp has elapsed then no state changes occurred. // NEED DISCUSSION (Is this ok? Shall we write the `timeout_sentinel_receipt`?) | | @@ -595,7 +598,8 @@ function recvPacket( // Channel and Client Checks channel = getChannel(packet.channelDestId) - client = router.clients[packet.channelDestId] + assert(channel !== null) + client = router.clients[channel.clientId] assert(client !== null) //assert(packet.sourceId == channel.counterpartyChannelId) This should be always true, redundant // NEED DISCUSSION @@ -628,7 +632,7 @@ function recvPacket( // Executes Application logic ∀ Payload payload=packet.data[0] - cbs = router.callbacks[payload.destPort] + cbs = router.callbacks[payload.destPort,payload.version] ack,success = cbs.onReceivePacket(packet.channelDestId,payload) abortTransactionUnless(success) if ack != nil { @@ -679,7 +683,7 @@ The IBC handler performs the following steps in order: | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|------------|------------| -| **Ante-Conditions** | - `receivePacket` has been called on chain `B`.
- `onReceivePacket` application callback has been executed.
- `writeAcknowledgement` has not been called yet | `provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) === null` | +| **pre-conditions** | - `receivePacket` has been called on chain `B`.
- `onReceivePacket` application callback has been executed.
- `writeAcknowledgement` has not been called yet | `provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) === null` | | **Error-Conditions** | - acknowledgement is empty.
- The `packetAcknowledgementPath` stores already a value. | - `len(acknowledgement) === 0`.
- `provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) !== null` | | **Post-Conditions (Success)** | - The opaque acknowledgement has been written at `packetAcknowledgementPath`. | - `provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) !== null` | | **Post-Conditions (Error)** | - No value is stored at the `packetAcknowledgementPath`. | - `provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) === null` | @@ -721,11 +725,11 @@ The IBC hanlder MUST atomically trigger the callbacks execution of appropriate a ###### Conditions Table -Given that at this point of the packet flow, chain `B` has sucessfully received a packet, the ante-conditions defines what MUST be accomplished before chain `A` can properly execute the `acknowledgePacket` for the IBC v2 packet. +Given that at this point of the packet flow, chain `B` has sucessfully received a packet, the pre-conditions defines what MUST be accomplished before chain `A` can properly execute the `acknowledgePacket` for the IBC v2 packet. | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|---------------------------------|---------------------------------| -| **Ante-Conditions** | - chain `B` has successfully received a packet and has written the acknowledgment.
- PacketCommitment has not been cleared out yet. |- `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === commitV2Packet(packet)`.
- `verifyMembership(packetacknowledgementPath,...,) == True` | +| **pre-conditions** | - chain `B` has successfully received a packet and has written the acknowledgment.
- PacketCommitment has not been cleared out yet. |- `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === commitV2Packet(packet)`.
- `verifyMembership(packetacknowledgementPath,...,) == True` | | **Error-Conditions** | - PacketCommitment already cleared out.
- Unset Acknowledgment.
- Unsuccessful payload execution. | - `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null`.
- `verifyMembership(packetacknowledgementPath,...,) == False`.
- `OnAcknowledgePacket(packet.channelSourceId,payload, acknowledgement) == False` | | **Post-Conditions (Success)** | - All the applications pointed in the payload have properly terminated the `onAcknowledgePacket` callback execution.
- The packetCommitment has been cleared out. | - `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null` | | **Post-Conditions (Error)** | - If one payload fails, then all state changes that happened on the successful `onAcknowledgePacket` application callback execution are reverted.
- The packetCommitment has not been cleared out.
| - `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === commitV2Packet(packet)` | @@ -747,8 +751,8 @@ function acknowledgePacket( // Channel and Client Checks channel = getChannel(packet.channelSourceId) - client = router.clients[packet.channelSourceId] - + assert(channel !== null) + client = router.clients[channel.clientId] assert(client !== null) //assert(packet.destId == channel.counterpartyChannelId) // Tautology @@ -770,7 +774,7 @@ function acknowledgePacket( if(acknowledgement!= SENTINEL_ACKNOWLEDGEMENT){ // Do we want this? // Executes Application logic ∀ Payload payload=packet.data[0] - cbs = router.callbacks[payload.sourcePort] + cbs = router.callbacks[payload.sourcePort,payload.version] success= cbs.OnAcknowledgePacket(packet.channelSourceId,payload, acknowledgement) abortUnless(success) } @@ -834,7 +838,7 @@ We pass the `relayer` address just as in [Receiving packets](#receiving-packets) | **Condition Type** | **Description** | |-------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------| -| **Ante-Conditions** | - PacketReceipt MUST be empty.
- PacketCommitment has not been cleared out yet. | +| **pre-conditions** | - PacketReceipt MUST be empty.
- PacketCommitment has not been cleared out yet. | | **Error-Conditions** | - PacketCommitment already cleared out.
- PacketReceipt is not empty.
- Unsuccessful payload execution. | | **Post-Conditions (Success)** | - All the applications pointed in the payload have properly terminated the `onTimeoutPacket` callback execution, reverting the state changes occurred in `onSendPacket`.
- The packetCommitment has been cleared out. | | **Post-Conditions (Error)** | - If one payload fails, then all state changes that happened on the successful `onTimeoutPacket` application callback execution MUST be reverted.
- Note that here we may get stuck if one `onTimeoutPacket` application always fails.
- The packetCommitment has not been cleared out. | @@ -850,7 +854,7 @@ function timeoutPacket( ) { // Channel and Client Checks channel = getChannel(packet.channelSourceId) - client = router.clients[packet.channelSourceId] + client = router.clients[channel.clientId] assert(client !== null) @@ -879,7 +883,7 @@ function timeoutPacket( )) payload=packet.data[0] - cbs = router.callbacks[payload.sourcePort] + cbs = router.callbacks[payload.sourcePort,payload.version] success=cbs.OnTimeoutPacket(packet.channelSourceId,payload) abortUnless(success) @@ -899,7 +903,7 @@ function timeoutPacket( ##### Cleaning up state -Packets MUST be acknowledged or timed-out in order to be cleaned-up. +Packets MUST be acknowledged or timed-out in order to be cleaned-up. #### Reasoning about race conditions @@ -913,10 +917,6 @@ There is an unavoidable race condition on identifier allocation on the destinati There is no race condition between a packet timeout and packet confirmation, as the packet will either have passed the timeout height prior to receipt or not. -##### Man-in-the-middle attacks during handshakes - -Verification of cross-chain state prevents man-in-the-middle attacks for both connection handshakes & channel handshakes since all information (source, destination client, channel, etc.) is known by the module which starts the handshake and confirmed prior to handshake completion. - ##### Clients unreachability with in-flight packets If a client has been frozen while packets are in-flight, the packets can no longer be received on the destination chain and can be timed-out on the source chain. @@ -931,7 +931,7 @@ TODO Mmmm ..Not applicable. ## Forwards Compatibility -Future updates of this spec are expected to cover the multi-payload and multi-acknowledgment execution logic. +Future updates to this specification will enable the IBC protocol version 2 to process multiple payloads within a single IBC packet atomically, reducing the number of packet flows. ## Example Implementations From 319c1819fff7bae0aab1587af7641d53b767f8ab Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Thu, 10 Oct 2024 11:54:44 +0200 Subject: [PATCH 41/71] setup fixes --- .../v2/ics-004-packet-semantics/README.md | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index b6c925dc3..7cf3bd1d9 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -214,7 +214,7 @@ type nextSequenceSend : channelId -> BigEndianUint64 type channelCreator : channelId -> address type storedChannels : channelId -> Channel -function getChannel(channelId: bytes) Channel { +function getChannel(channelId: bytes): Channel { return storedChannels[channelId] } ``` @@ -227,15 +227,17 @@ To start the secure packet stream between the chains, chain `A` and chain `B` MU | **Procedure** | **Responsible** | **Outcome** | |-----------------------------|---------------------|-----------------------------------------------------------------------------| -| **Client Creation** | Relayer | Both chains create a light client for the counterparty chain. | | **Channel Creation** | Relayer | A channel is created and linked to an underlying light client on both chains. | | **Channel Registration** | Relayer | Registers the `counterpartyChannelId` on both chains, linking the channels. | -> **Note** The setup procedure is a prerequisite for starting the packet stream. If any of the steps has been missed, this would result in an incorrect setup error during the packet handlers execution. +> **Note** The relayer is required to execute `createClient` (as defined in ICS-02) before calling `createChannel`, since the `clientId` input parameter MUST be known. The `createClient` message (as defined in ICS-02) may be bundled with the `createChannel` message in a single multiMsgTx. The setup procedure is a prerequisite for starting the packet stream. If any of the steps has been missed, this would result in an incorrect setup error during the packet handlers execution. -Below we provide the setup sequence diagram. Note that, as shown in the sequence diagram, the `createClient` message (as defined in ICS-02) may be bundled with the `createChannel` message in a single multiMsgTx. +Below we provide the setup sequence diagram. ```mermaid +--- +title: Setup with `createClient` and `createChannel` bundle together. +--- sequenceDiagram Participant Chain A Participant Relayer @@ -248,11 +250,29 @@ sequenceDiagram Relayer ->> Chain B : registerChannel(channelId = W, counterpartyChannelId = Y) ``` +```mermaid +--- +title: Setup with light client previously created. +--- +sequenceDiagram + Participant B LightClient - clientId=x + Participant Chain A + Participant Relayer + Participant Chain B + Participant A LightClient - clientId=z + Relayer ->> Chain A : createChannel(B LightClient) + Chain A ->> Relayer : clientId= x , channelId = y + Relayer ->> Chain B : createChannel () + Chain B ->> Relayer : clientId= z , channelId = w + Relayer ->> Chain A : registerChannel(channelId = y, counterpartyChannelId = w) + Relayer ->> Chain B : registerChannel(channelId = w, counterpartyChannelId = y) +``` + Once the set up is executed the system should be in a similar state: ![Setup Final State](setup_final_state.png) -While the application callbacks registration MUST be handled by the application module during initialization, and client creation is governed by [ICS-2](../ics-002-client-semantics/README.md), the channel creation and registration procedures are defined by ICS-04 and are detailed below. +While the client creation is defined by [ICS-2](../ics-002-client-semantics/README.md), the channel creation and registration procedures are defined by ICS-04 and are detailed below. ##### Channel creation @@ -311,8 +331,6 @@ function createChannel( } ``` -Note that `createClient` message (as defined in ICS-02) may be bundled with the `createChannel` message in a single multiMsgTx. Successful execution of these messages on both chains is a prerequisite for the channel registration procedure, described below. - ##### Channel registration and counterparty idenfitifcation Each IBC chain MUST have the ability to idenfity its counterparty to ensure valid communication. While a client can prove any key/value path on the counterparty, knowing which identifier the counterparty uses when it sends messages to us is essential to prevent confusion between messages intended for different chains. From bf6f87c816061871eec9d6e4f2d9a3ec70635926 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Thu, 10 Oct 2024 11:59:17 +0200 Subject: [PATCH 42/71] diagram fixes --- .../v2/ics-004-packet-semantics/README.md | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 7cf3bd1d9..3f4195004 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -243,27 +243,27 @@ sequenceDiagram Participant Relayer Participant Chain B Relayer ->> Chain A : createClient(B chain) + createChannel - Chain A ->> Relayer : clientId= X , channelId = Y + Chain A ->> Relayer : clientId= x , channelId = y Relayer ->> Chain B : createClient(A chain) + createChannel - Chain B ->> Relayer : clientId= Z , channelId = W - Relayer ->> Chain A : registerChannel(channelId = Y, counterpartyChannelId = W) - Relayer ->> Chain B : registerChannel(channelId = W, counterpartyChannelId = Y) + Chain B ->> Relayer : clientId= z , channelId = w + Relayer ->> Chain A : registerChannel(channelId = y, counterpartyChannelId = w) + Relayer ->> Chain B : registerChannel(channelId = w, counterpartyChannelId = y) ``` ```mermaid --- -title: Setup with light client previously created. +title: Setup with light clients previously created. --- sequenceDiagram - Participant B LightClient - clientId=x + Participant B LightClient(clientId=x) Participant Chain A Participant Relayer Participant Chain B - Participant A LightClient - clientId=z - Relayer ->> Chain A : createChannel(B LightClient) - Chain A ->> Relayer : clientId= x , channelId = y - Relayer ->> Chain B : createChannel () - Chain B ->> Relayer : clientId= z , channelId = w + Participant A LightClient(clientId=z) + Relayer ->> Chain A : createChannel(x) + Chain A ->> Relayer : channelId = y + Relayer ->> Chain B : createChannel(z) + Chain B ->> Relayer : channelId = w Relayer ->> Chain A : registerChannel(channelId = y, counterpartyChannelId = w) Relayer ->> Chain B : registerChannel(channelId = w, counterpartyChannelId = y) ``` From afafaef4e8ec65d1b967d8e5b81fc87dfd84a5e4 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Thu, 10 Oct 2024 13:06:49 +0200 Subject: [PATCH 43/71] fix diagram --- spec/core/v2/ics-004-packet-semantics/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 3f4195004..c54932545 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -232,7 +232,7 @@ To start the secure packet stream between the chains, chain `A` and chain `B` MU > **Note** The relayer is required to execute `createClient` (as defined in ICS-02) before calling `createChannel`, since the `clientId` input parameter MUST be known. The `createClient` message (as defined in ICS-02) may be bundled with the `createChannel` message in a single multiMsgTx. The setup procedure is a prerequisite for starting the packet stream. If any of the steps has been missed, this would result in an incorrect setup error during the packet handlers execution. -Below we provide the setup sequence diagram. +Below we provide the setup sequence diagrams. ```mermaid --- @@ -255,11 +255,11 @@ sequenceDiagram title: Setup with light clients previously created. --- sequenceDiagram - Participant B LightClient(clientId=x) + Participant B Light Client with clientId=x Participant Chain A Participant Relayer Participant Chain B - Participant A LightClient(clientId=z) + Participant A Light Client with clientId=z Relayer ->> Chain A : createChannel(x) Chain A ->> Relayer : channelId = y Relayer ->> Chain B : createChannel(z) From 6b5f5c20549ead468c440aabe3983e0e4b8fabe3 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Thu, 10 Oct 2024 13:12:11 +0200 Subject: [PATCH 44/71] test fix --- spec/core/v2/ics-004-packet-semantics/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index c54932545..c7dcabeaf 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -255,11 +255,11 @@ sequenceDiagram title: Setup with light clients previously created. --- sequenceDiagram - Participant B Light Client with clientId=x + Participant B Light Client as B Light Client with clientId=x Participant Chain A Participant Relayer Participant Chain B - Participant A Light Client with clientId=z + Participant A Light Client as A Light Client with clientId=z Relayer ->> Chain A : createChannel(x) Chain A ->> Relayer : channelId = y Relayer ->> Chain B : createChannel(z) From bbc35434200eb8b56598dd25655e9b4e56cfe6a4 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Thu, 10 Oct 2024 14:57:56 +0200 Subject: [PATCH 45/71] two and three step setup --- .../v2/ics-004-packet-semantics/README.md | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index c7dcabeaf..379127032 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -131,7 +131,7 @@ type IBCRouter struct { The registration of the application callbacks in the local `IBCRouter`, is responsibility of the chain modules. The registration of the client in the local `IBCRouter` is responsibility of the ICS-02 initialise client procedure. -> **Note** The proper configuration of the IBCRouter is a prerequisite for starting the stream of packets. +> **Note:** The proper configuration of the IBCRouter is a prerequisite for starting the stream of packets. - The `MAX_TIMEOUT_DELTA` is intendend as the max, absolute, difference between currentTimestamp and timeoutTimestamp that can be given in input to `sendPacket`. @@ -223,20 +223,22 @@ function getChannel(channelId: bytes): Channel { #### Setup -To start the secure packet stream between the chains, chain `A` and chain `B` MUST execute the setup following this set of procedures: +In order to ensure valid communication, each IBC chain MUST be able to identify its counterparty. While a client can prove any key/value path on the counterparty, knowing which identifier the counterparty uses when it sends messages to us is essential to prevent confusion between messages intended for different chains. IBC classic was granting the chains this ability by enforcing handshakes for the creation of connections and channels. IBC version 2 simplifies the process by eliminating the need for any handshakes. Instead it provides a two or three step procedure that requires minimal coordination between the executing chains. Indeed the only information that MUST be shared among chains is the `channelId` pointing to the counterparty, information that can be obtained by social consensus outside the protocol. Thus, to achieve mutual and verifiable identification, IBC version 2 introduces the `createChannel` and `registerChannel` procedures. The setup process ensures that both chains recognize and agree on a mutually identified channel that will facilitate packet transmission. + +Thus, to start the secure packet stream between the chains, chain `A` and chain `B` MUST execute the setup following this set of procedures: | **Procedure** | **Responsible** | **Outcome** | |-----------------------------|---------------------|-----------------------------------------------------------------------------| | **Channel Creation** | Relayer | A channel is created and linked to an underlying light client on both chains. | | **Channel Registration** | Relayer | Registers the `counterpartyChannelId` on both chains, linking the channels. | -> **Note** The relayer is required to execute `createClient` (as defined in ICS-02) before calling `createChannel`, since the `clientId` input parameter MUST be known. The `createClient` message (as defined in ICS-02) may be bundled with the `createChannel` message in a single multiMsgTx. The setup procedure is a prerequisite for starting the packet stream. If any of the steps has been missed, this would result in an incorrect setup error during the packet handlers execution. +> **Note:** The relayer is required to execute `createClient` (as defined in ICS-02) before calling `createChannel`, since the `clientId` input parameter MUST be known. The `createClient` message (as defined in ICS-02) may be bundled with the `createChannel` message in a single multiMsgTx. The setup procedure is a prerequisite for starting the packet stream. If any of the steps has been missed, this would result in an incorrect setup error during the packet handlers execution. Below we provide the setup sequence diagrams. ```mermaid --- -title: Setup with `createClient` and `createChannel` bundle together. +title: Two Step Setup Procedure :: `createClient` and `createChannel` are bundled together. --- sequenceDiagram Participant Chain A @@ -252,7 +254,7 @@ sequenceDiagram ```mermaid --- -title: Setup with light clients previously created. +title: Three Step Setup Procedure :: `createClient` has been previosly executed. --- sequenceDiagram Participant B Light Client as B Light Client with clientId=x @@ -268,15 +270,17 @@ sequenceDiagram Relayer ->> Chain B : registerChannel(channelId = w, counterpartyChannelId = y) ``` -Once the set up is executed the system should be in a similar state: +When the two or three step setup has been executed, the system should end up in a similar state: ![Setup Final State](setup_final_state.png) -While the client creation is defined by [ICS-2](../ics-002-client-semantics/README.md), the channel creation and registration procedures are defined by ICS-04 and are detailed below. +Once two chains have set up clients, created channel and registered channels for each other with specific identifiers, they can send IBC packets using the packet interface defined before and the packet handlers that the ICS-04 defines below. The packets will be addressed **directly** with the channels that have semantic link to the underlying counterparty light clients. Thus there are **no** more handshakes necessary. Instead the packet sender must be capable of providing the correct pair. If the setup has been executed correctly, then the correctness and soundness properties of IBC holds and the IBC packet flow is guaranteed to succeed. If a user sends a packet with the wrong destination channel it will be impossible for the intended destination to correctly verify the packet, and the packet will simply time out. + +While the above mentioned `createClient` procedure is defined by [ICS-2](../ics-002-client-semantics/README.md), the ICS-04 defines below the `createChannel` and `registerChannel` procedures. ##### Channel creation -The channel creation process enables the creation of the two channel ends that can be linked to establishes the communication pathway between two chains. +The channel creation process enables the creation of the two channels that can be linked to establishes the communication pathway between two chains. ###### Conditions Table @@ -333,9 +337,7 @@ function createChannel( ##### Channel registration and counterparty idenfitifcation -Each IBC chain MUST have the ability to idenfity its counterparty to ensure valid communication. While a client can prove any key/value path on the counterparty, knowing which identifier the counterparty uses when it sends messages to us is essential to prevent confusion between messages intended for different chains. - -To enable mutual and verifiable identification, IBC version 2 introduces a `registerChannel` procedure. The channel registration procedure ensures both chains have a mutually recognized channel that facilitates the packet transmission. +IBC version 2 introduces a `registerChannel` procedure. The channel registration procedure ensures both chains have a mutually recognized channel that facilitates the packet transmission. This process stores the `counterpartyChannelId` in the local channel structure, ensuring both chains have mirrored pairs. With the correct registration, the unique clients on each side provide an authenticated stream of packet data. Social consensus outside the protocol is relied upon to ensure only valid pairs are used, representing connections between the correct chains. @@ -385,8 +387,6 @@ function registerChannel( The protocol uses as an authentication mechanisms checking that the `registerChannel` message is sent by the same relayer that initialized the client such that the `msg.signer()==channelCreator[channelId]`. This would make the client and channel parameters completely initialized by the relayer. Thus, users must verify that the client is pointing to the correct chain and that the counterparty identifier is correct as well before using the pair. -Thus, once two chains have set up clients, created channel and registered channels for each other with specific Identifiers, they can send IBC packets using the packet interface defined before and the packet handlers that the ICS-04 defines below. The packets will be addressed **directly** with the channels that have semantic link to the underlying light clients. Thus there are **no** more handshakes necessary. Instead the packet sender must be capable of providing the correct pair. If the setup has been executed correctly, then the correctness and soundness properties of IBC holds and the IBC packet flow is guaranteed to succeed. If a user sends a packet with the wrong destination channel, then as we will see it will be impossible for the intended destination to correctly verify the packet, thus, the packet will simply time out. - #### Packet Flow Function Handlers In the IBC protocol version 2, the packet flow is managed by four key function handlers, each of which is responsible for a distinct stage in the packet lifecycle: @@ -684,7 +684,7 @@ function recvPacket( ##### Writing acknowledgements -> NOTE: The system handles synchronous and asynchronous acknowledgement logic. +> **Note:** The system handles synchronous and asynchronous acknowledgement logic. The `writeAcknowledgement` function can be called either synchronously by the IBC handler during the `receivePacket` execution or it can be called asynchronously by an application callback. From 9b6df940bf50d47abf821b98d97bc20049359939 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Thu, 10 Oct 2024 16:28:22 +0200 Subject: [PATCH 46/71] send-receive conditions --- .../v2/ics-004-packet-semantics/README.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 379127032..c0bcfb502 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -238,7 +238,7 @@ Below we provide the setup sequence diagrams. ```mermaid --- -title: Two Step Setup Procedure :: `createClient` and `createChannel` are bundled together. +title: Two Step Setup Procedure :: createClient and createChannel are bundled together. --- sequenceDiagram Participant Chain A @@ -254,7 +254,7 @@ sequenceDiagram ```mermaid --- -title: Three Step Setup Procedure :: `createClient` has been previosly executed. +title: Three Step Setup Procedure :: createClient has been previosly executed. --- sequenceDiagram Participant B Light Client as B Light Client with clientId=x @@ -503,10 +503,10 @@ Note that the full packet is not stored in the state of the chain - merely a sho | **Condition Type** |**Description** | **Code Checks**| |-------------------------------|--------------------------------------------------------|------------------------| -| **pre-conditions** | - Chains `A` and `B` MUST be in a setup final state.
| | -| **Error-Conditions** | - Incorrect setup (includes invalid client and invalid channelId).
- Invalid timeoutTimestamp.
- Unsuccessful payload execution. | - `getChannel(sourceChannelId)==null`.
, -`router[sourceChannelId]==null`.
- `timeoutTimestamp==0`.
- `timeoutTimestamp < currentTimestamp()`.
- `timeoutTimestamp > currentTimestamp() + MAX_TIMEOUT_DELTA`.
- `success=onSendPacket(..), success==False`.
| -| **Post-Conditions (Success)** | - All the applications contained in the payload have properly terminated the `onSendPacket` callback execution.
- The packetCommitment has been generated and stored under the right packetCommitmentPath.
- The sequence number bound to sourceId MUST has been incremented by 1.
| An event with relevant information has been emitted | -| **Post-Conditions (Error)** | - If one payload fails, then all state changes happened on the successful application execution must be reverted.
- No packetCommitment has been generated.
- The sequence number bound to sourceId MUST be unchanged. | | +| **pre-conditions** | - Chains `A` and `B` are assumed to be in a setup final state.
| | +| **Error-Conditions** | - Invalid clientId.
- Invalid channelId.
- Invalid timeoutTimestamp.
- Unsuccessful payload execution. | - `getChannel(sourceChannelId)==null`.
, -`router[sourceChannelId]==null`.
- `timeoutTimestamp==0`.
- `timeoutTimestamp < currentTimestamp()`.
- `timeoutTimestamp > currentTimestamp() + MAX_TIMEOUT_DELTA`.
- `onSendPacket(..)==False`.
| +| **Post-Conditions (Success)** | - All the applications contained in the payload have properly terminated the `onSendPacket` callback execution and applied state changes.
- The packetCommitment has been generated and stored under the right packetCommitmentPath.
- The sequence number bound to sourceId MUST has been incremented by 1.
| - `onSendPacket(..)==True; app.State(beforeSendPacket)!=app.State(afterSendPacket)` - `commitment=commitV2Packet(packet), provableStore.get(packetCommitmentPath(sourceChannelId, sequence))==commitment`.
- `nextSequenceSend[sourecChannelId]+1==SendPacket(..)`.
- An event with relevant information has been emitted | +| **Post-Conditions (Error)** | - If one payload fails, then all state changes happened on the successful application execution must be reverted.
- No packetCommitment has been generated.
- The sequence number bound to `sourceId` MUST be unchanged. | - `app.State(beforeSendPacket)=app.State(afterSendPacket)`.
- `commitment=commitV2Packet(packet), provableStore.get(packetCommitmentPath(sourceChannelId, sequence))==commitment`.
- `nextSequenceSend[sourecChannelId]==SendPacket(..)` | ###### Pseudo-Code @@ -597,10 +597,10 @@ We pass the address of the `relayer` that signed and submitted the packet to ena | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|-----------------------------------------------|-----------------------------------------------| -| **pre-conditions** | - Chain `A` MUST have stored the packetCommitment under the keyPrefix registered in the chain `B` channelEnd.
- TimeoutTimestamp MUST not have elapsed yet on the receiving chain.
- PacketReceipt for the specific keyPrefix and sequence MUST be empty (e.g. `receivePacket` has not been called yet). | | -| **Error-Conditions** | - Packet Errors: invalid packetCommitment, packetReceipt already exists.
- Invalid timeoutTimestamp.
- Unsuccessful payload execution. | | -| **Post-Conditions (Success)** | - All the applications pointed in the payload have properly terminated the `onReceivePacket` callback execution.
- The packetReceipt has been written.
- The acknowledgement has been written. | | -| **Post-Conditions (Error)** | - If one payload fails, then all state changes happened on the successful `onReceivePacket` application callback execution MUST be reverted.
- If timeoutTimestamp has elapsed then no state changes occurred. // NEED DISCUSSION (Is this ok? Shall we write the `timeout_sentinel_receipt`?) | | +| **pre-conditions** | - Chain `A` MUST have stored a verifiable the packetCommitment.
- TimeoutTimestamp MUST not have elapsed yet on the receiving chain.
- PacketReceipt for the specific keyPrefix and sequence MUST be empty (implies `receivePacket` has not been called yet). | | +| **Error-Conditions** | - Packet Errors: invalid packetCommitment, packetReceipt already exists.
- Invalid timeoutTimestamp.
- Unsuccessful payload execution. | - `verifyMembership(packetCommitment)==false`.
- `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))!=null`.
- `timeoutTimestamp === 0`.
- `currentTimestamp() < packet.timeoutTimestamp)`.
- `onReceivePacket(..)==False` | +| **Post-Conditions (Success)** | - All the applications pointed in the payload have properly terminated the `onReceivePacket` callback execution.
- The packetReceipt has been written.
- The acknowledgement has been written. | - `onReceivePacket(..)==True; app.State(beforeReceivePacket)!=app.State(afterSendPacket)`.
- `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))!=null`.
| +| **Post-Conditions (Error)** | - If one payload fails, then all state changes happened on the successful application execution must be reverted.
- packetReceipt is not written.
| - `app.State(beforeReceivePacket)==app.State(afterReceivePacket)`.
- `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))==null` | ###### Pseudo-Code From 077d93d54613f253d1954691e92e1e8b069b3939 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Thu, 10 Oct 2024 17:23:50 +0200 Subject: [PATCH 47/71] router fixes --- .../v2/ics-004-packet-semantics/README.md | 46 +++++++++---------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index c0bcfb502..334d2cff6 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -23,7 +23,7 @@ TODO : This standard defines the channel and packet semantics necessary for state machines implementing the Inter-Blockchain Communication (IBC) protocol version 2 to enable secure, verifiable, and efficient cross-chain messaging. -It specifies the mechanisms to create channels and register them between two distinct state machines (blockchains) where a channel serves as a semantic link between the chains and their counterparty light client representation, ensuring that both chains can process and verify packets exchanged between them. +It specifies the mechanisms to create channels and register them between two distinct state machines (blockchains) where the channels have a semantic link between the chains and their counterparty light client representation, ensuring that both chains can process and verify packets exchanged between them. The standard then details the processes for transmitting, receiving, acknowledging, and timing out data packets. The packet-flow semantics guarantee exactly-once packet delivery between chains, utilizing on-chain light clients for state verification and providing efficient routing of packet data to specific IBC applications. @@ -33,7 +33,7 @@ The motivation for this specification is to formalize the semantics for both pac This specification focuses on defining the mechanisms for creating channels, securely registering them between chains, and ensuring that packets sent across these channels are processed consistently and verifiably. By utilizing on-chain light clients for state verification, it enables chains to exchange data without requiring synchronous communication, ensuring that all packets are delivered exactly once, even in the presence of network delays or reordering. -To standardize both channel creation and packet flow semantics, this document also defines the pre-conditions, error conditions, and post-conditions for each defined function handler. By using a well-defined packet interface and clear handling processes, ICS-04 aims to ensure consistency and security across distinct implementations of the protocol ensuring reliability and security and tries not to impose constraints on the internal workings of the state machines. +To standardize both channel creation, registration and packet flow semantics, this document also defines the pre-conditions, error conditions, and post-conditions for each defined function handler. By using a well-defined packet interface and clear handling processes, ICS-04 aims to ensure consistency and security across distinct implementations of the protocol ensuring reliability and security and tries not to impose constraints on the internal workings of the state machines. ### Definitions @@ -111,19 +111,17 @@ interface Acknowledgement { } ``` -// NEED DISCUSSION, Can we delete the SENTINEL_ACKNOWLEDGMENT? - An application may not need to return an acknowledgment with after processing relevant data. In this case, it is advised to return a sentinel acknowledgement value `SENTINEL_ACKNOWLEDGMENT`, which will be the single byte in the byte array: `bytes(0x01)`. When the receiver chain returns this `SENTINEL_ACKNOWLEDGMENT` it allows the sender chain to still call the `acknowledgePacket` handler, e.g. to delete the packet commitment, without triggering the `onAcknowledgePacket` callback. > **Example**: In the multi-data packet world, if a packet within 3 payloads intended for 3 different application is sent out, the expectation is that each payload is processed in the same order in which it was placed in the packet. Similarly, the `appAcknowledgement` array is expected to be populated within the same order. -- The `IBCRouter` contains a mapping from the application `portId` and the supported callbacks and as well as a mapping from `channelId` to the underlying client. +- The `IBCRouter` contains a mapping from the application `portId` and the supported callbacks and as well as a mapping from `clientlId` to the underlying client. ```typescript type IBCRouter struct { - callbacks: (portId,version) -> [Callback] // The double key (portId,version) clearly separate the appVxCallbacks from appVyCallbacks simplifying the routing to app process + callbacks: portId -> [Callback] clients: clientId -> Client // The IBCRouter stores the client under the clientId key } ``` @@ -173,7 +171,7 @@ Additionally the ICS-04 specification defines a set of conditions that the imple The ICS-04 use the protocol paths, defined in [ICS-24](../ics-024-host-requirements/README.md), `packetCommitmentPath`, `packetRecepitPath` and `packetAcknowledgementPath`. The paths MUST be used as the referece locations in the provableStore to prove respectilvey the packet commitment, the receipt and the acknowledgment to the counterparty chain. -Thus, Constant-size commitments to packet data fields are stored under the packet sequence number: +Thus, constant-size commitments to packet data fields are stored under the packet sequence number: // NEED DISCUSSION -- we could use "commitments/{sourceId}/{sequence}" or "0x01/{sourceId}/{sequence}". For now we keep going with more or less standard paths @@ -223,22 +221,22 @@ function getChannel(channelId: bytes): Channel { #### Setup -In order to ensure valid communication, each IBC chain MUST be able to identify its counterparty. While a client can prove any key/value path on the counterparty, knowing which identifier the counterparty uses when it sends messages to us is essential to prevent confusion between messages intended for different chains. IBC classic was granting the chains this ability by enforcing handshakes for the creation of connections and channels. IBC version 2 simplifies the process by eliminating the need for any handshakes. Instead it provides a two or three step procedure that requires minimal coordination between the executing chains. Indeed the only information that MUST be shared among chains is the `channelId` pointing to the counterparty, information that can be obtained by social consensus outside the protocol. Thus, to achieve mutual and verifiable identification, IBC version 2 introduces the `createChannel` and `registerChannel` procedures. The setup process ensures that both chains recognize and agree on a mutually identified channel that will facilitate packet transmission. +In order to ensure valid communication, each IBC chain MUST be able to identify its counterparty. While a client can prove any key/value path on the counterparty, knowing which identifier the counterparty uses when it sends messages to us is essential to prevent confusion between messages intended for different chains. Thus, to achieve mutual and verifiable identification, IBC version 2 introduces the `createChannel` and `registerChannel` procedures. Below the ICS-04 defines the setup process that ensures that both chains recognize and agree on a mutually identified channel that will facilitate packet transmission. -Thus, to start the secure packet stream between the chains, chain `A` and chain `B` MUST execute the setup following this set of procedures: +To start the secure packet stream between the chains, chain `A` and chain `B` MUST execute the setup following this set of procedures: | **Procedure** | **Responsible** | **Outcome** | |-----------------------------|---------------------|-----------------------------------------------------------------------------| | **Channel Creation** | Relayer | A channel is created and linked to an underlying light client on both chains. | | **Channel Registration** | Relayer | Registers the `counterpartyChannelId` on both chains, linking the channels. | -> **Note:** The relayer is required to execute `createClient` (as defined in ICS-02) before calling `createChannel`, since the `clientId` input parameter MUST be known. The `createClient` message (as defined in ICS-02) may be bundled with the `createChannel` message in a single multiMsgTx. The setup procedure is a prerequisite for starting the packet stream. If any of the steps has been missed, this would result in an incorrect setup error during the packet handlers execution. +> **Note:** The relayer is required to execute `createClient` (as defined in ICS-02) before calling `createChannel`, since the `clientId` input parameter MUST be known. The `createClient` message (as defined in ICS-02) may be bundled with the `createChannel` message in a single multiMsgTx. The setup procedure is a prerequisite for starting the packet stream. If any of the steps has been missed, this would trigger an error during the packet handlers execution. Below we provide the setup sequence diagrams. ```mermaid --- -title: Two Step Setup Procedure :: createClient and createChannel are bundled together. +title: Two Step Setup Procedure, createClient and createChannel are bundled together. --- sequenceDiagram Participant Chain A @@ -254,7 +252,7 @@ sequenceDiagram ```mermaid --- -title: Three Step Setup Procedure :: createClient has been previosly executed. +title: Three Step Setup Procedure, createClient has been previosly executed. --- sequenceDiagram Participant B Light Client as B Light Client with clientId=x @@ -306,7 +304,7 @@ function createChannel( // Channel Checks channelId = generateIdentifier() - abortTransactionUnless(validateChannelIdentifier(channelId)) + abortTransactionUnless(validateIdentifier(channelId)) abortTransactionUnless(getChannel(channelId)) === null) // Channel manipulation @@ -540,8 +538,8 @@ function sendPacket( // Executes Application logic ∀ Payload // Currently we support only len(payloads)==1 payload=payloads[0] - cbs = router.callbacks[payload.sourcePort,payload.version] - success = cbs.onSendPacket(sourceChannelId,channel.counterpartyChannelId,payload) + cbs = router.callbacks[payload.sourcePort] + success = cbs.onSendPacket(sourceChannelId,channel.counterpartyChannelId,payload) // Note that payload includes the version. The application is required to inspect the version to route the data to the proper callback // IMPORTANT: if the onSendPacket fails, the transaction is aborted and the potential state changes are reverted. // This ensure that the post conditions on error are always respected. // payload execution check @@ -611,7 +609,7 @@ function recvPacket( packet: OpaquePacket, proof: CommitmentProof, proofHeight: Height, - relayer: string // Need discussion + relayer: string ) { // Channel and Client Checks @@ -650,8 +648,8 @@ function recvPacket( // Executes Application logic ∀ Payload payload=packet.data[0] - cbs = router.callbacks[payload.destPort,payload.version] - ack,success = cbs.onReceivePacket(packet.channelDestId,payload) + cbs = router.callbacks[payload.destPort] + ack,success = cbs.onReceivePacket(packet.channelDestId,payload,relayer) // Note that payload includes the version. The application is required to inspect the version to route the data to the proper callback abortTransactionUnless(success) if ack != nil { // NOTE: Synchronous ack. @@ -792,8 +790,8 @@ function acknowledgePacket( if(acknowledgement!= SENTINEL_ACKNOWLEDGEMENT){ // Do we want this? // Executes Application logic ∀ Payload payload=packet.data[0] - cbs = router.callbacks[payload.sourcePort,payload.version] - success= cbs.OnAcknowledgePacket(packet.channelSourceId,payload, acknowledgement) + cbs = router.callbacks[payload.sourcePort] + success= cbs.OnAcknowledgePacket(packet.channelSourceId,payload,acknowledgement, relayer) // Note that payload includes the version. The application is required to inspect the version to route the data to the proper callback abortUnless(success) } @@ -891,7 +889,7 @@ function timeoutPacket( asert(packet.timeoutTimestamp > 0 && proofTimestamp >= packet.timeoutTimestamp) // verify there is no packet receipt --> receivePacket has not been called - receiptPath = packetReceiptPath(packet.channelDestId, packet.sequence) + receiptPath = packetReceiptPath(packet.channelDestId, packet.sequence,relayer) merklePath = applyPrefix(channel.keyPrefix, receiptPath) assert(client.verifyNonMembership( client.clientState, @@ -901,11 +899,11 @@ function timeoutPacket( )) payload=packet.data[0] - cbs = router.callbacks[payload.sourcePort,payload.version] - success=cbs.OnTimeoutPacket(packet.channelSourceId,payload) + cbs = router.callbacks[payload.sourcePort] + success=cbs.OnTimeoutPacket(packet.channelSourceId,payload) // Note that payload includes the version. The application is required to inspect the version to route the data to the proper callback abortUnless(success) - channelStore.delete(packetCommitmentPath(packet.channelSourceId, packet.sequence)) + channelStore.delete(packetCommitmentPath(packet.channelSourceId, packet.sequence, relayer)) // Event Emission // See fields emitLogEntry("timeoutPacket", { From 4602566427367282a6239d7f293cd7d6d06437e1 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Thu, 10 Oct 2024 18:06:40 +0200 Subject: [PATCH 48/71] createChannl conditions --- .../v2/ics-004-packet-semantics/README.md | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 334d2cff6..4a0f3a21a 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -284,10 +284,10 @@ The channel creation process enables the creation of the two channels that can b | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|------------------| ----------------| -| **pre-conditions** | - The used clientId exist. `createClient` has been called at least once.| | -| **error-conditions** | - Incorrect clientId.
- Unexpected keyPrefix format.
- Invalid channelId .
| - `client==null`.
- `isFormatOk(counterpartyKeyPrefix)==False`.
- `validatedChannelId(channelId)==False`.
- `getChannel(channelId)!=null`.
| -| **post-conditions (success)** | - A channel is set in store and it's accessible with key channelId.
- The creator is set in store and it's accessible with key channelId.
- nextSequenceSend is initialized.
- client is stored in the router.
- an event with relevant fields is emitted | - `storedChannel[channelId]!=null`.
- `channelCreator[channelId]!=null`.
- `router[channelId]!=null`.
- `nextSequenceSend[channelId]==1` | -| **post-conditions (error)** | - None of the post-conditions (success) is true.
| - `storedChannel[channelId]==null`.
- `channelCreator[channelId]==null`.
- `router[channelId]==null`.
- `nextSequenceSend[channelId]!=1`| +| **pre-conditions** | - `createClient` has been called at least once.| | +| **error-conditions** | 1. Invalid `clientId`.
2. Unexpected keyPrefix format.
3. Invalid channelId .
| 1. `client==null`.
2. `isFormatOk(counterpartyKeyPrefix)==False`.
3. `validatedChannelId(channelId)==False`.
- `getChannel(channelId)!=null`.
| +| **post-conditions (success)** | 1. A channel is set in store and it's accessible with key `channelId`.
2. The creator is set in store and it's accessible with key `channelId`.
3. `nextSequenceSend` is initialized.
- client is stored in the router.
- an event with relevant fields is emitted | 1. `storedChannel[channelId]!=null`.
2. `channelCreator[channelId]!=null`.
3. `nextSequenceSend[channelId]==1`.
4. `router[channelId]!=null` | +| **post-conditions (error)** | - None of the post-conditions (success) is true.
| 1. `storedChannel[channelId]==null`.
2. `channelCreator[channelId]==null`.
3. `nextSequenceSend[channelId]!=1`.
4.`router[channelId]==null` | ###### Pseudo-Code @@ -475,8 +475,13 @@ sequenceDiagram --- -Given a configuration where we are sending a packet from `A` to `B` then chain `A` can call either, `sendPacket`,`acknowledgePacket` or `timeoutPacket` while chain `B` can only execute the `receivePacket` handler. -The `acknowledgePacket` is not a valid action if `receivePacket` has not been executed. `timeoutPacket` is not a valid action if `receivePacket` occurred. +Given a scenario where we are sending a packet from `A` to `B`: + +- Chain `A` can call either {`sendPacket`,`acknowledgePacket`,`timeoutPacket`} +- Chain `B` can call only {`receivePacket`} +- Chain `B` can only execute the `receivePacket` if `sendPacket` has been executed by chain `A` +- Chain `A` can only execute `timeoutPacket` if `sendPacket` has been executed by chain `A` and `receivePacket` has not been executed by chain `B`. +- Chain `A` can only execute `acknowledgePacket` if `sendPacket` has been executed by chain `A` and `receivePacket` has been executed by chain `B`. ##### Sending packets @@ -502,8 +507,8 @@ Note that the full packet is not stored in the state of the chain - merely a sho | **Condition Type** |**Description** | **Code Checks**| |-------------------------------|--------------------------------------------------------|------------------------| | **pre-conditions** | - Chains `A` and `B` are assumed to be in a setup final state.
| | -| **Error-Conditions** | - Invalid clientId.
- Invalid channelId.
- Invalid timeoutTimestamp.
- Unsuccessful payload execution. | - `getChannel(sourceChannelId)==null`.
, -`router[sourceChannelId]==null`.
- `timeoutTimestamp==0`.
- `timeoutTimestamp < currentTimestamp()`.
- `timeoutTimestamp > currentTimestamp() + MAX_TIMEOUT_DELTA`.
- `onSendPacket(..)==False`.
| -| **Post-Conditions (Success)** | - All the applications contained in the payload have properly terminated the `onSendPacket` callback execution and applied state changes.
- The packetCommitment has been generated and stored under the right packetCommitmentPath.
- The sequence number bound to sourceId MUST has been incremented by 1.
| - `onSendPacket(..)==True; app.State(beforeSendPacket)!=app.State(afterSendPacket)` - `commitment=commitV2Packet(packet), provableStore.get(packetCommitmentPath(sourceChannelId, sequence))==commitment`.
- `nextSequenceSend[sourecChannelId]+1==SendPacket(..)`.
- An event with relevant information has been emitted | +| **Error-Conditions** | - Invalid clientId.
- Invalid channelId.
- Invalid timeoutTimestamp.
- Unsuccessful payload execution. | - `getChannel(sourceChannelId)==null`.
-`router[sourceChannelId]==null`.
- `timeoutTimestamp==0`.
- `timeoutTimestamp < currentTimestamp()`.
- `timeoutTimestamp > currentTimestamp() + MAX_TIMEOUT_DELTA`.
- `onSendPacket(..)==False`.
| +| **Post-Conditions (Success)** | - All the applications contained in the payload have properly terminated the `onSendPacket` callback execution and applied state changes.
- The packetCommitment has been generated and stored under the right packetCommitmentPath.
- The sequence number bound to sourceId MUST has been incremented by 1.
- An event with relevant information has been emitted | - `onSendPacket(..)==True; app.State(beforeSendPacket)!=app.State(afterSendPacket)`.
- `commitment=commitV2Packet(packet), provableStore.get(packetCommitmentPath(sourceChannelId, sequence))==commitment`.
- `nextSequenceSend[sourecChannelId]+1==SendPacket(..)`.
- An event with relevant information has been emitted | | **Post-Conditions (Error)** | - If one payload fails, then all state changes happened on the successful application execution must be reverted.
- No packetCommitment has been generated.
- The sequence number bound to `sourceId` MUST be unchanged. | - `app.State(beforeSendPacket)=app.State(afterSendPacket)`.
- `commitment=commitV2Packet(packet), provableStore.get(packetCommitmentPath(sourceChannelId, sequence))==commitment`.
- `nextSequenceSend[sourecChannelId]==SendPacket(..)` | ###### Pseudo-Code @@ -770,8 +775,6 @@ function acknowledgePacket( assert(channel !== null) client = router.clients[channel.clientId] assert(client !== null) - - //assert(packet.destId == channel.counterpartyChannelId) // Tautology // verify we sent the packet and haven't cleared it out yet assert(provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === commitV2Packet(packet)) @@ -787,7 +790,7 @@ function acknowledgePacket( acknowledgement )) - if(acknowledgement!= SENTINEL_ACKNOWLEDGEMENT){ // Do we want this? + if(acknowledgement!= SENTINEL_ACKNOWLEDGEMENT){ // Executes Application logic ∀ Payload payload=packet.data[0] cbs = router.callbacks[payload.sourcePort] From b76f2e3528de225f10b429f585bffee16a0aa8c4 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Thu, 10 Oct 2024 18:32:01 +0200 Subject: [PATCH 49/71] register table fixes --- spec/core/v2/ics-004-packet-semantics/README.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 4a0f3a21a..acdaa3801 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -285,8 +285,8 @@ The channel creation process enables the creation of the two channels that can b | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|------------------| ----------------| | **pre-conditions** | - `createClient` has been called at least once.| | -| **error-conditions** | 1. Invalid `clientId`.
2. Unexpected keyPrefix format.
3. Invalid channelId .
| 1. `client==null`.
2. `isFormatOk(counterpartyKeyPrefix)==False`.
3. `validatedChannelId(channelId)==False`.
- `getChannel(channelId)!=null`.
| -| **post-conditions (success)** | 1. A channel is set in store and it's accessible with key `channelId`.
2. The creator is set in store and it's accessible with key `channelId`.
3. `nextSequenceSend` is initialized.
- client is stored in the router.
- an event with relevant fields is emitted | 1. `storedChannel[channelId]!=null`.
2. `channelCreator[channelId]!=null`.
3. `nextSequenceSend[channelId]==1`.
4. `router[channelId]!=null` | +| **error-conditions** | 1. Invalid `clientId`.
2. `Invalid channelId`.
3. Unexpected keyPrefix format | 1. `client==null`.
2.1 `validateId(channelId)==False`.
2.2 `getChannel(channelId)!=null`.
3. `isFormatOk(KeyPrefix)==False`.
| +| **post-conditions (success)** | 1. A channel is set in store and it's accessible with key `channelId`.
2. The creator is set in store and it's accessible with key `channelId`.
3. `nextSequenceSend` is initialized.
- an event with relevant fields is emitted | 1. `storedChannel[channelId]!=null`.
2. `channelCreator[channelId]!=null`.
3. `nextSequenceSend[channelId]==1`.
4. `router[channelId]!=null` | | **post-conditions (error)** | - None of the post-conditions (success) is true.
| 1. `storedChannel[channelId]==null`.
2. `channelCreator[channelId]==null`.
3. `nextSequenceSend[channelId]!=1`.
4.`router[channelId]==null` | ###### Pseudo-Code @@ -344,9 +344,9 @@ This process stores the `counterpartyChannelId` in the local channel structure, | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|-----------------------------------|----------------------------| | **pre-conditions** | - The `createChannel` has been called at least once| | -| **error-conditions** | - Incorrect channelId.
- Creator authentication failed | - `validatedChannelId(channelId)==False`.
- `getChannel(channelId)==null`.
- `channelCreator[channelId]!=msg.signer()`.
| -| **post-conditions (success)** | - The channel in store contains the counterpartyChannelId information and it's accessible with key channelId.
An event with relevant information has been emitted | - `storedChannel[channelId].counterpartyChannelId!=""`.
| -| **post-conditions (error)** | - On the first call, the channel in store contains the counterpartyChannelId as an empty field.
| - `storedChannel[channelId].counterpartyChannelId==""` | +| **error-conditions** | 1. Invalid `channelId`.
2. Creator authentication failed | 1.1 `validateId(channelId)==False`.
1.2 `getChannel(channelId)==null`.
2. `channelCreator[channelId]!=msg.signer()`.
| +| **post-conditions (success)** | 1. The channel in store contains the `counterpartyChannelId` information and it's accessible with key `channelId`.
2. An event with relevant information has been emitted | 1. `storedChannel[channelId].counterpartyChannelId!=null`.
| +| **post-conditions (error)** | 1. On the first call, the channel in store contains the `counterpartyChannelId` as an empty field.
| 1. `storedChannel[channelId].counterpartyChannelId==null` | ###### Pseudo-Code @@ -354,7 +354,6 @@ This process stores the `counterpartyChannelId` in the local channel structure, function registerChannel( channelId: bytes, // local chain channel identifier counterpartyChannelId: bytes, // the counterparty's channel identifier - authentication: data, // implementation-specific authentication data ) { // Implementation-Specific Input Validation // All implementations MUST ensure the inputs value are properly validated and compliant with this specification @@ -368,6 +367,10 @@ function registerChannel( abortTransactionUnless(msg.signer()===channelCreator[channelId]) // Channel manipulation + /* NEED DISCUSSION : Do we want to allow multiple call to this function? + If not than we have to impose the check + abortTransactionUnless(channel.counterpartyChannelId===null) + */ channel.counterpartyChannelId=counterpartyChannelId // Local Store From e772e7efe33e1e4717c750bb116b127f6b1b5369 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Thu, 10 Oct 2024 19:52:34 +0200 Subject: [PATCH 50/71] conditions tables --- .../v2/ics-004-packet-semantics/README.md | 67 +++++++++---------- 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index acdaa3801..e48b10ca7 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -284,10 +284,10 @@ The channel creation process enables the creation of the two channels that can b | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|------------------| ----------------| -| **pre-conditions** | - `createClient` has been called at least once.| | -| **error-conditions** | 1. Invalid `clientId`.
2. `Invalid channelId`.
3. Unexpected keyPrefix format | 1. `client==null`.
2.1 `validateId(channelId)==False`.
2.2 `getChannel(channelId)!=null`.
3. `isFormatOk(KeyPrefix)==False`.
| -| **post-conditions (success)** | 1. A channel is set in store and it's accessible with key `channelId`.
2. The creator is set in store and it's accessible with key `channelId`.
3. `nextSequenceSend` is initialized.
- an event with relevant fields is emitted | 1. `storedChannel[channelId]!=null`.
2. `channelCreator[channelId]!=null`.
3. `nextSequenceSend[channelId]==1`.
4. `router[channelId]!=null` | -| **post-conditions (error)** | - None of the post-conditions (success) is true.
| 1. `storedChannel[channelId]==null`.
2. `channelCreator[channelId]==null`.
3. `nextSequenceSend[channelId]!=1`.
4.`router[channelId]==null` | +| **pre-conditions** | - `createClient` has been called at least once| | +| **error-conditions** | 1. Invalid `clientId`
2. `Invalid channelId`
3. Unexpected keyPrefix format | 1. `client==null`
2.1 `validateId(channelId)==False`
2.2 `getChannel(channelId)!=null`
3. `isFormatOk(KeyPrefix)==False`
| +| **post-conditions (success)** | 1. A channel is set in store
2. The creator is set in store
3. `nextSequenceSend` is initialized
4. Event with relevant fields is emitted | 1. `storedChannel[channelId]!=null`
2. `channelCreator[channelId]!=null`
3. `nextSequenceSend[channelId]==1`
4. checkEventEmission | +| **post-conditions (error)** | - None of the post-conditions (success) is true
| 1. `storedChannel[channelId]==null`
2. `channelCreator[channelId]==null`
3. `nextSequenceSend[channelId]!=1`
4.`router[channelId]==null` | ###### Pseudo-Code @@ -344,9 +344,9 @@ This process stores the `counterpartyChannelId` in the local channel structure, | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|-----------------------------------|----------------------------| | **pre-conditions** | - The `createChannel` has been called at least once| | -| **error-conditions** | 1. Invalid `channelId`.
2. Creator authentication failed | 1.1 `validateId(channelId)==False`.
1.2 `getChannel(channelId)==null`.
2. `channelCreator[channelId]!=msg.signer()`.
| -| **post-conditions (success)** | 1. The channel in store contains the `counterpartyChannelId` information and it's accessible with key `channelId`.
2. An event with relevant information has been emitted | 1. `storedChannel[channelId].counterpartyChannelId!=null`.
| -| **post-conditions (error)** | 1. On the first call, the channel in store contains the `counterpartyChannelId` as an empty field.
| 1. `storedChannel[channelId].counterpartyChannelId==null` | +| **error-conditions** | 1. Invalid `channelId`
2. Creator authentication failed | 1.1 `validateId(channelId)==False`
1.2 `getChannel(channelId)==null`
2. `channelCreator[channelId]!=msg.signer()`
| +| **post-conditions (success)** | 1. The channel in store contains the `counterpartyChannelId` information
2. An event with relevant information has been emitted | 1. `storedChannel[channelId].counterpartyChannelId!=null`
| +| **post-conditions (error)** | 1. On the first call, the channel in store contains the `counterpartyChannelId` as an empty field
| 1. `storedChannel[channelId].counterpartyChannelId==null` | ###### Pseudo-Code @@ -509,10 +509,10 @@ Note that the full packet is not stored in the state of the chain - merely a sho | **Condition Type** |**Description** | **Code Checks**| |-------------------------------|--------------------------------------------------------|------------------------| -| **pre-conditions** | - Chains `A` and `B` are assumed to be in a setup final state.
| | -| **Error-Conditions** | - Invalid clientId.
- Invalid channelId.
- Invalid timeoutTimestamp.
- Unsuccessful payload execution. | - `getChannel(sourceChannelId)==null`.
-`router[sourceChannelId]==null`.
- `timeoutTimestamp==0`.
- `timeoutTimestamp < currentTimestamp()`.
- `timeoutTimestamp > currentTimestamp() + MAX_TIMEOUT_DELTA`.
- `onSendPacket(..)==False`.
| -| **Post-Conditions (Success)** | - All the applications contained in the payload have properly terminated the `onSendPacket` callback execution and applied state changes.
- The packetCommitment has been generated and stored under the right packetCommitmentPath.
- The sequence number bound to sourceId MUST has been incremented by 1.
- An event with relevant information has been emitted | - `onSendPacket(..)==True; app.State(beforeSendPacket)!=app.State(afterSendPacket)`.
- `commitment=commitV2Packet(packet), provableStore.get(packetCommitmentPath(sourceChannelId, sequence))==commitment`.
- `nextSequenceSend[sourecChannelId]+1==SendPacket(..)`.
- An event with relevant information has been emitted | -| **Post-Conditions (Error)** | - If one payload fails, then all state changes happened on the successful application execution must be reverted.
- No packetCommitment has been generated.
- The sequence number bound to `sourceId` MUST be unchanged. | - `app.State(beforeSendPacket)=app.State(afterSendPacket)`.
- `commitment=commitV2Packet(packet), provableStore.get(packetCommitmentPath(sourceChannelId, sequence))==commitment`.
- `nextSequenceSend[sourecChannelId]==SendPacket(..)` | +| **pre-conditions** | - Chains `A` and `B` are assumed to be in a setup final state
| | +| **Error-Conditions** | 1. Invalid `clientId`
2. Invalid `channelId`
3. Invalid `timeoutTimestamp`
4. Unsuccessful payload execution. | 1. `router.clients[channel.clientId]==null`
2. `getChannel(sourceChannelId)==null`
3.1 `timeoutTimestamp==0`
3.2 `timeoutTimestamp < currentTimestamp()`
3.3 `timeoutTimestamp > currentTimestamp() + MAX_TIMEOUT_DELTA`
4. `onSendPacket(..)==False`
| +| **Post-Conditions (Success)** | 1. `onSendPacket` is executed and the application state is modified
2. The `packetCommitment` is generated and stored under the expected `packetCommitmentPath`
3. The sequence number bound to `sourceId` is incremented by 1
4. Event with relevant information is emitted | 1. `onSendPacket(..)==True; app.State(beforeSendPacket)!=app.State(afterSendPacket)`
2. `commitment=commitV2Packet(packet), provableStore.get(packetCommitmentPath(sourceChannelId, sequence))==commitment`
3. `nextSequenceSend[sourecChannelId]+1==SendPacket(..)`
4. CheckEventEmission | +| **Post-Conditions (Error)** | 1. if `onSendPacket` fails the application state is unchanged
2. No `packetCommitment` has been generated
3. The sequence number bound to `sourceId` is unchanged. | 1. `app.State(beforeSendPacket)=app.State(afterSendPacket)`
2. `commitment=commitV2Packet(packet), provableStore.get(packetCommitmentPath(sourceChannelId, sequence))==commitment`
3. `nextSequenceSend[sourecChannelId]==nextSequenceSend(beforeSendPacket)` | ###### Pseudo-Code @@ -603,10 +603,10 @@ We pass the address of the `relayer` that signed and submitted the packet to ena | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|-----------------------------------------------|-----------------------------------------------| -| **pre-conditions** | - Chain `A` MUST have stored a verifiable the packetCommitment.
- TimeoutTimestamp MUST not have elapsed yet on the receiving chain.
- PacketReceipt for the specific keyPrefix and sequence MUST be empty (implies `receivePacket` has not been called yet). | | -| **Error-Conditions** | - Packet Errors: invalid packetCommitment, packetReceipt already exists.
- Invalid timeoutTimestamp.
- Unsuccessful payload execution. | - `verifyMembership(packetCommitment)==false`.
- `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))!=null`.
- `timeoutTimestamp === 0`.
- `currentTimestamp() < packet.timeoutTimestamp)`.
- `onReceivePacket(..)==False` | -| **Post-Conditions (Success)** | - All the applications pointed in the payload have properly terminated the `onReceivePacket` callback execution.
- The packetReceipt has been written.
- The acknowledgement has been written. | - `onReceivePacket(..)==True; app.State(beforeReceivePacket)!=app.State(afterSendPacket)`.
- `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))!=null`.
| -| **Post-Conditions (Error)** | - If one payload fails, then all state changes happened on the successful application execution must be reverted.
- packetReceipt is not written.
| - `app.State(beforeReceivePacket)==app.State(afterReceivePacket)`.
- `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))==null` | +| **pre-conditions** | - Chain `A` has stored a verifiable `packetCommitment`
- `TimeoutTimestamp` is not elapsed on the receiving chain
- `PacketReceipt` for the specific keyPrefix and sequence MUST be empty (implies `receivePacket` has not been called yet). | | +| **Error-Conditions** | 1. Packet Errors: invalid packetCommitment, packetReceipt already exists
2. Invalid timeoutTimestamp
3. Unsuccessful payload execution. | 1.1 `verifyMembership(packetCommitment)==false`
1.2 `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))!=null`
3. `timeoutTimestamp === 0`
3.1 `currentTimestamp() < packet.timeoutTimestamp)`
4. `onReceivePacket(..)==False` | +| **Post-Conditions (Success)** | 1. `onReceivePacket` is executed and the application state is modified
2. The `packetReceipt` is written
| 1. `onReceivePacket(..)==True; app.State(beforeReceivePacket)!=app.State(afterSendPacket)`
2. `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))!=null`
| +| **Post-Conditions (Error)** | 1. if `onReceivePacket` fails the application state is unchanged
2. `packetReceipt is not written`
| 1. `app.State(beforeReceivePacket)==app.State(afterReceivePacket)`
2. `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))==null` | ###### Pseudo-Code @@ -634,7 +634,7 @@ function recvPacket( // verify the packet receipt for this packet does not exist already packetReceipt = provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence)) - abortUnless(packetReceipt === null) + abortTransactionUnless(packetReceipt === null) //////// verify commitment @@ -707,10 +707,10 @@ The IBC handler performs the following steps in order: | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|------------|------------| -| **pre-conditions** | - `receivePacket` has been called on chain `B`.
- `onReceivePacket` application callback has been executed.
- `writeAcknowledgement` has not been called yet | `provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) === null` | -| **Error-Conditions** | - acknowledgement is empty.
- The `packetAcknowledgementPath` stores already a value. | - `len(acknowledgement) === 0`.
- `provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) !== null` | -| **Post-Conditions (Success)** | - The opaque acknowledgement has been written at `packetAcknowledgementPath`. | - `provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) !== null` | -| **Post-Conditions (Error)** | - No value is stored at the `packetAcknowledgementPath`. | - `provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) === null` | +| **pre-conditions** | - `receivePacket` has been called on chain `B`
- `onReceivePacket` application callback has been executed
- `writeAcknowledgement` has not been called yet | | +| **Error-Conditions** | 1. acknowledgement is empty
2. The `packetAcknowledgementPath` stores already a value. | 1. `len(acknowledgement) === 0`
2. `provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) !== null` | +| **Post-Conditions (Success)** | 1. The opaque acknowledgement has been written at `packetAcknowledgementPath` | 1. `provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) !== null` | +| **Post-Conditions (Error)** | 1. No value is stored at the `packetAcknowledgementPath`. | 1. `provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) === null` | ```typescript function writeAcknowledgement( @@ -721,7 +721,7 @@ function writeAcknowledgement( // cannot already have written the acknowledgement abortTransactionUnless(provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) === null)) - + // create the acknowledgement coomit using the function defined in [packet specification](https://github.com/cosmos/ibc/blob/c7b2e6d5184b5310843719b428923e0c5ee5a026/spec/core/v2/ics-004-packet-semantics/PACKET.md) commit=commitV2Acknowledgment(acknowledgement) @@ -753,11 +753,11 @@ Given that at this point of the packet flow, chain `B` has sucessfully received | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|---------------------------------|---------------------------------| -| **pre-conditions** | - chain `B` has successfully received a packet and has written the acknowledgment.
- PacketCommitment has not been cleared out yet. |- `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === commitV2Packet(packet)`.
- `verifyMembership(packetacknowledgementPath,...,) == True` | -| **Error-Conditions** | - PacketCommitment already cleared out.
- Unset Acknowledgment.
- Unsuccessful payload execution. | - `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null`.
- `verifyMembership(packetacknowledgementPath,...,) == False`.
- `OnAcknowledgePacket(packet.channelSourceId,payload, acknowledgement) == False` | -| **Post-Conditions (Success)** | - All the applications pointed in the payload have properly terminated the `onAcknowledgePacket` callback execution.
- The packetCommitment has been cleared out. | - `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null` | -| **Post-Conditions (Error)** | - If one payload fails, then all state changes that happened on the successful `onAcknowledgePacket` application callback execution are reverted.
- The packetCommitment has not been cleared out.
| - `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === commitV2Packet(packet)` | - +| **pre-conditions** | - chain `B` has successfully received a packet and has written the acknowledgment
- `packetCommitment` has not been cleared out yet. || +| **Error-Conditions** | 1. `packetCommitment` already cleared out
2. Unset Acknowledgment
3. Unsuccessful payload execution. | 1. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null`
2. `verifyMembership(packetacknowledgementPath,...,) == False`
3. `onAcknowledgePacket(packet.channelSourceId,payload, acknowledgement) == False` | +| **Post-Conditions (Success)** | 1. `onAcknowledgePacket` is executed and the application state is modified
2. The `packetCommitment` has been cleared out. | 1. `onAcknowledgePacket(..)==True; app.State(beforeReceivePacket)!=app.State(afterSendPacket)`
2. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null` | +| **Post-Conditions (Error)** | 1. If `onAcknowledgePacket` fails the application state is unchanged
2. The `packetCommitment` has not been cleared out
3. The acknowledgement is stil in store | 1. `onAcknowledgePacket(..)==False; app.State(beforeReceivePacket)==app.State(afterSendPacket)`
2. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === commitV2Packet(packet)` 3. `verifyMembership(packetAcknowledgementPath,...,) == True`| + ###### Pseudo-Code The ICS04 provides an example pseudo-code that enforce the above described conditions so that the following sequence of steps must occur for a packet to be acknowledged from module *1* on machine *A* to module *2* on machine *B*. @@ -858,12 +858,12 @@ We pass the `relayer` address just as in [Receiving packets](#receiving-packets) ###### Conditions Table -| **Condition Type** | **Description** | -|-------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------| -| **pre-conditions** | - PacketReceipt MUST be empty.
- PacketCommitment has not been cleared out yet. | -| **Error-Conditions** | - PacketCommitment already cleared out.
- PacketReceipt is not empty.
- Unsuccessful payload execution. | -| **Post-Conditions (Success)** | - All the applications pointed in the payload have properly terminated the `onTimeoutPacket` callback execution, reverting the state changes occurred in `onSendPacket`.
- The packetCommitment has been cleared out. | -| **Post-Conditions (Error)** | - If one payload fails, then all state changes that happened on the successful `onTimeoutPacket` application callback execution MUST be reverted.
- Note that here we may get stuck if one `onTimeoutPacket` application always fails.
- The packetCommitment has not been cleared out. | +| **Condition Type** | **Description**| **Code Checks**| +|-------------------------------|--------------------|--------------------| +| **pre-conditions** | - `packetReceipt` is empty
- `packetCommitment` has not been cleared out yet | | +| **Error-Conditions** | 1. `packetCommitment` already cleared out
2. `packetReceipt` is not empty
3. Unsuccessful payload execution 4. `timeoutTimestamp` not elapsed on the receiving chain| 1. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null`
2. `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))!=null`
3. `onTimeoutPacket(packet.channelSourceId,payload) == False`
4.1 `packet.timeoutTimestamp > 0`
4.2 `proofTimestamp = client.getTimestampAtHeight(proofHeight); proofTimestamp >= packet.timeoutTimestamp` | +| **Post-Conditions (Success)** | 1. `onTimeoutPacket` is executed and the application state is modified
2. `packetCommitment` has been cleared out
3. `packetReceipt` is empty | 1. `onTimeoutPacket(..)==True; app.State(beforeTimeoutPacket)!=app.State(afterTimeoutPacket)`
2. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null`
3. `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))==null`
| +| **Post-Conditions (Error)** | 1. If `onTimeoutPacket` fails and the application state is unchanged
2. `packetCommitment` is not cleared out | 1. `onTimeoutPacket(..)==True; app.State(beforTimeoutPacket)!=app.State(afterTimeoutPacket)`
2. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null` | ###### Pseudo-Code @@ -880,14 +880,11 @@ function timeoutPacket( assert(client !== null) - //assert(packet.destId == channel.counterpartyChannelId) - // verify we sent the packet and haven't cleared it out yet assert(provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === commitV2Packet(packet)) // get the timestamp from the final consensus state in the channel path - var proofTimestamp proofTimestamp = client.getTimestampAtHeight(proofHeight) assert(err != nil) From 2574ec160e076c878f616d92bea2e4856cf8de3c Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Thu, 10 Oct 2024 20:15:40 +0200 Subject: [PATCH 51/71] table fixes --- .../v2/ics-004-packet-semantics/README.md | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index e48b10ca7..6e78336ce 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -230,7 +230,7 @@ To start the secure packet stream between the chains, chain `A` and chain `B` MU | **Channel Creation** | Relayer | A channel is created and linked to an underlying light client on both chains. | | **Channel Registration** | Relayer | Registers the `counterpartyChannelId` on both chains, linking the channels. | -> **Note:** The relayer is required to execute `createClient` (as defined in ICS-02) before calling `createChannel`, since the `clientId` input parameter MUST be known. The `createClient` message (as defined in ICS-02) may be bundled with the `createChannel` message in a single multiMsgTx. The setup procedure is a prerequisite for starting the packet stream. If any of the steps has been missed, this would trigger an error during the packet handlers execution. +> **Note:** The relayer is required to execute `createClient` (as defined in ICS-02) before calling `createChannel`, since the `clientId` input parameter MUST be known. The `createClient` message may be bundled with the `createChannel` message in a single multiMsgTx. The setup procedure is a prerequisite for starting the packet stream. If any of the steps has been missed, this would trigger an error during the packet handlers execution. Below we provide the setup sequence diagrams. @@ -272,7 +272,7 @@ When the two or three step setup has been executed, the system should end up in ![Setup Final State](setup_final_state.png) -Once two chains have set up clients, created channel and registered channels for each other with specific identifiers, they can send IBC packets using the packet interface defined before and the packet handlers that the ICS-04 defines below. The packets will be addressed **directly** with the channels that have semantic link to the underlying counterparty light clients. Thus there are **no** more handshakes necessary. Instead the packet sender must be capable of providing the correct pair. If the setup has been executed correctly, then the correctness and soundness properties of IBC holds and the IBC packet flow is guaranteed to succeed. If a user sends a packet with the wrong destination channel it will be impossible for the intended destination to correctly verify the packet, and the packet will simply time out. +Once two chains have set up clients, created channel and registered channels for each other with specific identifiers, they can send IBC packets using the packet interface defined before and the packet handlers that the ICS-04 defines below. The packets will be addressed **directly** with the channels that have semantic link to the underlying counterparty light clients. Thus there are **no** more handshakes necessary. Instead the packet sender must be capable of providing the correct **** pair. If the setup has been executed correctly, then the correctness and soundness properties of IBC holds and the IBC packet flow is guaranteed to succeed. If a user sends a packet with the wrong destination channel it will be impossible for the intended destination to correctly verify the packet, and the packet will simply time out. While the above mentioned `createClient` procedure is defined by [ICS-2](../ics-002-client-semantics/README.md), the ICS-04 defines below the `createChannel` and `registerChannel` procedures. @@ -287,7 +287,7 @@ The channel creation process enables the creation of the two channels that can b | **pre-conditions** | - `createClient` has been called at least once| | | **error-conditions** | 1. Invalid `clientId`
2. `Invalid channelId`
3. Unexpected keyPrefix format | 1. `client==null`
2.1 `validateId(channelId)==False`
2.2 `getChannel(channelId)!=null`
3. `isFormatOk(KeyPrefix)==False`
| | **post-conditions (success)** | 1. A channel is set in store
2. The creator is set in store
3. `nextSequenceSend` is initialized
4. Event with relevant fields is emitted | 1. `storedChannel[channelId]!=null`
2. `channelCreator[channelId]!=null`
3. `nextSequenceSend[channelId]==1`
4. checkEventEmission | -| **post-conditions (error)** | - None of the post-conditions (success) is true
| 1. `storedChannel[channelId]==null`
2. `channelCreator[channelId]==null`
3. `nextSequenceSend[channelId]!=1`
4.`router[channelId]==null` | +| **post-conditions (error)** | - None of the post-conditions (success) is true
| 1. `storedChannel[channelId]==null`
2. `channelCreator[channelId]==null`
3. `nextSequenceSend[channelId]!=1`
| ###### Pseudo-Code @@ -509,10 +509,10 @@ Note that the full packet is not stored in the state of the chain - merely a sho | **Condition Type** |**Description** | **Code Checks**| |-------------------------------|--------------------------------------------------------|------------------------| -| **pre-conditions** | - Chains `A` and `B` are assumed to be in a setup final state
| | +| **pre-conditions** | - Sender and receiver chains are assumed to be in a setup final state
| | | **Error-Conditions** | 1. Invalid `clientId`
2. Invalid `channelId`
3. Invalid `timeoutTimestamp`
4. Unsuccessful payload execution. | 1. `router.clients[channel.clientId]==null`
2. `getChannel(sourceChannelId)==null`
3.1 `timeoutTimestamp==0`
3.2 `timeoutTimestamp < currentTimestamp()`
3.3 `timeoutTimestamp > currentTimestamp() + MAX_TIMEOUT_DELTA`
4. `onSendPacket(..)==False`
| -| **Post-Conditions (Success)** | 1. `onSendPacket` is executed and the application state is modified
2. The `packetCommitment` is generated and stored under the expected `packetCommitmentPath`
3. The sequence number bound to `sourceId` is incremented by 1
4. Event with relevant information is emitted | 1. `onSendPacket(..)==True; app.State(beforeSendPacket)!=app.State(afterSendPacket)`
2. `commitment=commitV2Packet(packet), provableStore.get(packetCommitmentPath(sourceChannelId, sequence))==commitment`
3. `nextSequenceSend[sourecChannelId]+1==SendPacket(..)`
4. CheckEventEmission | -| **Post-Conditions (Error)** | 1. if `onSendPacket` fails the application state is unchanged
2. No `packetCommitment` has been generated
3. The sequence number bound to `sourceId` is unchanged. | 1. `app.State(beforeSendPacket)=app.State(afterSendPacket)`
2. `commitment=commitV2Packet(packet), provableStore.get(packetCommitmentPath(sourceChannelId, sequence))==commitment`
3. `nextSequenceSend[sourecChannelId]==nextSequenceSend(beforeSendPacket)` | +| **Post-Conditions (Success)** | 1. `onSendPacket` is executed and the application state is modified
2. The `packetCommitment` is generated and stored under the expected `packetCommitmentPath`
3. The sequence number bound to `sourceId` is incremented by 1
4. Event with relevant information is emitted | 1. `onSendPacket(..)==True; app.State(beforeSendPacket)!=app.State(afterSendPacket)`
2. `commitment=commitV2Packet(packet), provableStore.get(packetCommitmentPath(sourceChannelId, sequence))==commitment`
3. `nextSequenceSend(beforeSendPacket[sourecChannelId])+1==SendPacket(..)`
4. CheckEventEmission | +| **Post-Conditions (Error)** | 1. if `onSendPacket` fails the application state is unchanged
2. No `packetCommitment` has been generated
3. The sequence number bound to `sourceId` is unchanged. | 1. `app.State(beforeSendPacket)==app.State(afterSendPacket)`
2. `commitment=commitV2Packet(packet), provableStore.get(packetCommitmentPath(sourceChannelId, sequence))==commitment`
3. `nextSequenceSend[sourecChannelId]==nextSequenceSend(beforeSendPacket)` | ###### Pseudo-Code @@ -603,9 +603,9 @@ We pass the address of the `relayer` that signed and submitted the packet to ena | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|-----------------------------------------------|-----------------------------------------------| -| **pre-conditions** | - Chain `A` has stored a verifiable `packetCommitment`
- `TimeoutTimestamp` is not elapsed on the receiving chain
- `PacketReceipt` for the specific keyPrefix and sequence MUST be empty (implies `receivePacket` has not been called yet). | | -| **Error-Conditions** | 1. Packet Errors: invalid packetCommitment, packetReceipt already exists
2. Invalid timeoutTimestamp
3. Unsuccessful payload execution. | 1.1 `verifyMembership(packetCommitment)==false`
1.2 `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))!=null`
3. `timeoutTimestamp === 0`
3.1 `currentTimestamp() < packet.timeoutTimestamp)`
4. `onReceivePacket(..)==False` | -| **Post-Conditions (Success)** | 1. `onReceivePacket` is executed and the application state is modified
2. The `packetReceipt` is written
| 1. `onReceivePacket(..)==True; app.State(beforeReceivePacket)!=app.State(afterSendPacket)`
2. `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))!=null`
| +| **pre-conditions** | - The sender chain has stored a verifiable `packetCommitment`
- `TimeoutTimestamp` is not elapsed on the receiving chain
- `PacketReceipt` for the specific keyPrefix and sequence MUST be empty (implies `receivePacket` has not been called yet). | | +| **Error-Conditions** | 1. invalid `packetCommitment`, 2.`packetReceipt` already exists
3. Invalid timeoutTimestamp
4. Unsuccessful payload execution. | 1.1 `verifyMembership(packetCommitment)==false`
1.2 `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))!=null`
3. `timeoutTimestamp === 0`
3.1 `currentTimestamp() < packet.timeoutTimestamp)`
4. `onReceivePacket(..)==False` | +| **Post-Conditions (Success)** | 1. `onReceivePacket` is executed and the application state is modified
2. The `packetReceipt` is written
| 1. `onReceivePacket(..)==True; app.State(beforeReceivePacket)!=app.State(afterReceivePacket)`
2. `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))!=null`
| | **Post-Conditions (Error)** | 1. if `onReceivePacket` fails the application state is unchanged
2. `packetReceipt is not written`
| 1. `app.State(beforeReceivePacket)==app.State(afterReceivePacket)`
2. `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))==null` | ###### Pseudo-Code @@ -707,9 +707,9 @@ The IBC handler performs the following steps in order: | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|------------|------------| -| **pre-conditions** | - `receivePacket` has been called on chain `B`
- `onReceivePacket` application callback has been executed
- `writeAcknowledgement` has not been called yet | | +| **pre-conditions** | - `receivePacket` has been called on receiver chain
- `onReceivePacket` application callback has been executed
- `writeAcknowledgement` has not been called yet | | | **Error-Conditions** | 1. acknowledgement is empty
2. The `packetAcknowledgementPath` stores already a value. | 1. `len(acknowledgement) === 0`
2. `provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) !== null` | -| **Post-Conditions (Success)** | 1. The opaque acknowledgement has been written at `packetAcknowledgementPath` | 1. `provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) !== null` | +| **Post-Conditions (Success)** | 1. opaque acknowledgement has been written at `packetAcknowledgementPath` | 1. `provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) !== null` | | **Post-Conditions (Error)** | 1. No value is stored at the `packetAcknowledgementPath`. | 1. `provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) === null` | ```typescript @@ -753,10 +753,10 @@ Given that at this point of the packet flow, chain `B` has sucessfully received | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|---------------------------------|---------------------------------| -| **pre-conditions** | - chain `B` has successfully received a packet and has written the acknowledgment
- `packetCommitment` has not been cleared out yet. || +| **pre-conditions** | - receiver chain has successfully received a packet and has written the acknowledgment
- `packetCommitment` has not been cleared out yet. || | **Error-Conditions** | 1. `packetCommitment` already cleared out
2. Unset Acknowledgment
3. Unsuccessful payload execution. | 1. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null`
2. `verifyMembership(packetacknowledgementPath,...,) == False`
3. `onAcknowledgePacket(packet.channelSourceId,payload, acknowledgement) == False` | -| **Post-Conditions (Success)** | 1. `onAcknowledgePacket` is executed and the application state is modified
2. The `packetCommitment` has been cleared out. | 1. `onAcknowledgePacket(..)==True; app.State(beforeReceivePacket)!=app.State(afterSendPacket)`
2. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null` | -| **Post-Conditions (Error)** | 1. If `onAcknowledgePacket` fails the application state is unchanged
2. The `packetCommitment` has not been cleared out
3. The acknowledgement is stil in store | 1. `onAcknowledgePacket(..)==False; app.State(beforeReceivePacket)==app.State(afterSendPacket)`
2. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === commitV2Packet(packet)` 3. `verifyMembership(packetAcknowledgementPath,...,) == True`| +| **Post-Conditions (Success)** | 1. `onAcknowledgePacket` is executed and the application state is modified
2. `packetCommitment` has been cleared out. | 1. `onAcknowledgePacket(..)==True; app.State(beforeAcknowledgePacket)!=app.State(afterAcknowledgePacket)`
2. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null` | +| **Post-Conditions (Error)** | 1. If `onAcknowledgePacket` fails the application state is unchanged
2. `packetCommitment` has not been cleared out
3. acknowledgement is stil in store | 1. `onAcknowledgePacket(..)==False; app.State(beforeAcknowledgePacket)==app.State(afterAcknowledgePacket)`
2. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === commitV2Packet(packet)` 3. `verifyMembership(packetAcknowledgementPath,...,) == True`| ###### Pseudo-Code @@ -861,9 +861,9 @@ We pass the `relayer` address just as in [Receiving packets](#receiving-packets) | **Condition Type** | **Description**| **Code Checks**| |-------------------------------|--------------------|--------------------| | **pre-conditions** | - `packetReceipt` is empty
- `packetCommitment` has not been cleared out yet | | -| **Error-Conditions** | 1. `packetCommitment` already cleared out
2. `packetReceipt` is not empty
3. Unsuccessful payload execution 4. `timeoutTimestamp` not elapsed on the receiving chain| 1. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null`
2. `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))!=null`
3. `onTimeoutPacket(packet.channelSourceId,payload) == False`
4.1 `packet.timeoutTimestamp > 0`
4.2 `proofTimestamp = client.getTimestampAtHeight(proofHeight); proofTimestamp >= packet.timeoutTimestamp` | +| **Error-Conditions** | 1. `packetCommitment` already cleared out
2. `packetReceipt` is not empty
3. Unsuccessful payload execution
4. `timeoutTimestamp` not elapsed on the receiving chain| 1. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null`
2. `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))!=null`
3. `onTimeoutPacket(packet.channelSourceId,payload) == False`
4.1 `packet.timeoutTimestamp > 0`
4.2 `proofTimestamp = client.getTimestampAtHeight(proofHeight); proofTimestamp >= packet.timeoutTimestamp` | | **Post-Conditions (Success)** | 1. `onTimeoutPacket` is executed and the application state is modified
2. `packetCommitment` has been cleared out
3. `packetReceipt` is empty | 1. `onTimeoutPacket(..)==True; app.State(beforeTimeoutPacket)!=app.State(afterTimeoutPacket)`
2. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null`
3. `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))==null`
| -| **Post-Conditions (Error)** | 1. If `onTimeoutPacket` fails and the application state is unchanged
2. `packetCommitment` is not cleared out | 1. `onTimeoutPacket(..)==True; app.State(beforTimeoutPacket)!=app.State(afterTimeoutPacket)`
2. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null` | +| **Post-Conditions (Error)** | 1. If `onTimeoutPacket` fails and the application state is unchanged
2. `packetCommitment` is not cleared out | 1. `onTimeoutPacket(..)==True; app.State(beforeTimeoutPacket)!=app.State(afterTimeoutPacket)`
2. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null` | ###### Pseudo-Code @@ -946,11 +946,11 @@ If a client has been frozen while packets are in-flight, the packets can no long ## Backwards Compatibility -TODO Mmmm ..Not applicable. +Not applicable. ## Forwards Compatibility -Future updates to this specification will enable the IBC protocol version 2 to process multiple payloads within a single IBC packet atomically, reducing the number of packet flows. +Future updates of this specification will enable the atomic processing of multiple payloads within a single IBC packet, reducing the number of packet flows. ## Example Implementations From c18060f9ed614d85d6236713cb373273e0d19ff3 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Thu, 10 Oct 2024 21:31:36 +0200 Subject: [PATCH 52/71] self review --- .../v2/ics-004-packet-semantics/README.md | 57 +++++++++++++------ 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 6e78336ce..29af4dba9 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -230,7 +230,12 @@ To start the secure packet stream between the chains, chain `A` and chain `B` MU | **Channel Creation** | Relayer | A channel is created and linked to an underlying light client on both chains. | | **Channel Registration** | Relayer | Registers the `counterpartyChannelId` on both chains, linking the channels. | -> **Note:** The relayer is required to execute `createClient` (as defined in ICS-02) before calling `createChannel`, since the `clientId` input parameter MUST be known. The `createClient` message may be bundled with the `createChannel` message in a single multiMsgTx. The setup procedure is a prerequisite for starting the packet stream. If any of the steps has been missed, this would trigger an error during the packet handlers execution. +The relayer is required to execute `createClient` (as defined in ICS-02) before calling `createChannel`, since the `clientId` input parameter MUST be known at execution time. Eventually, the `createClient` message can be bundled with the `createChannel` message in a single multiMsgTx. + +Calling indipendently `createClient`, `createChannel` and `registerChannel` result in a three step setup process. +Bundling `createClient` and `createChannel` into a single operation simplifies this process and reduces the number of interactions required between the relayer and the chains to two. + +The setup procedure is a prerequisite for starting the packet stream. If any of the steps has been missed, this would trigger an error during the packet handlers execution. Below we provide the setup sequence diagrams. @@ -397,7 +402,17 @@ In the IBC protocol version 2, the packet flow is managed by four key function h - `acknowledgePacket` - `timeoutPacket` -Note that the execution of the four handler above described, upon a unique packet, cannot be combined in any arbitrary order. We provide the three possible example scenarios described with sequence diagrmas. +Note that the execution of the four handlers, upon a unique packet, cannot be combined in any arbitrary order. + +Given a scenario where we are sending a packet from `A` to `B` the protocol follows the following rules: + +- Chain `A` can call either {`sendPacket`,`acknowledgePacket`,`timeoutPacket`} +- Chain `B` can call only {`receivePacket`} +- Chain `B` can only execute the `receivePacket` if `sendPacket` has been executed by chain `A` +- Chain `A` can only execute `timeoutPacket` if `sendPacket` has been executed by chain `A` and `receivePacket` has not been executed by chain `B`. +- Chain `A` can only execute `acknowledgePacket` if `sendPacket` has been executed by chain `A` and `receivePacket` has been executed by chain `B`. + +Below we provide the three possible example scenarios described with sequence diagrmas. --- @@ -410,20 +425,27 @@ sequenceDiagram participant Relayer participant Chain B participant A Light Client + Note over Chain A: start send packet execution Chain A ->> Chain A : sendPacket Chain A --> Chain A : app execution Chain A --> Chain A : packetCommitment + Note over Chain A: end send packet execution Relayer ->> Chain B: relayPacket + Note over Chain B: start receive packet execution Chain B ->> Chain B: receivePacket Chain B -->> A Light Client: verifyMembership(packetCommitment) Chain B --> Chain B : app execution Chain B --> Chain B: writeAck Chain B --> Chain B: writePacketReceipt + Note over Chain B: end receive packet execution Relayer ->> Chain A: relayAck + Note over Chain A: start acknowldge packet execution Chain A ->> Chain A : acknowldgePacket Chain A -->> B Light Client: verifyMembership(packetAck) Chain A --> Chain A : app execution - Chain A --> Chain A : Delete packetCommitment + Chain A --> Chain A : Delete packetCommitment + Note over Chain A: end acknowldge packet execution + ``` --- @@ -439,21 +461,29 @@ sequenceDiagram participant Relayer participant Chain B participant A Light Client + Note over Chain A: start send packet execution Chain A ->> Chain A : sendPacket Chain A --> Chain A : app execution - Chain A --> Chain A : packetCommitment + Chain A --> Chain A : packetCommitment + Note over Chain A: end send packet execution Relayer ->> Chain B: relayPacket + Note over Chain B: start receive packet execution Chain B ->> Chain B: receivePacket Chain B -->> A Light Client: verifyMembership(packetCommitment) Chain B --> Chain B : app execution Chain B --> Chain B: writePacketReceipt + Note over Chain B: end receive packet execution + Note over Chain B: start async ack writing Chain B --> Chain B : app execution - async ack processing Chain B --> Chain B: writeAck + Note over Chain B: end async ack writing Relayer ->> Chain A: relayAck + Note over Chain A: start acknowldge packet execution Chain A ->> Chain A : acknowldgePacket Chain A -->> B Light Client: verifyMembership(packetAck) Chain A --> Chain A : app execution - Chain A --> Chain A : Delete packetCommitment + Chain A --> Chain A : Delete packetCommitment + Note over Chain A: end acknowldge packet execution ``` --- @@ -467,25 +497,20 @@ sequenceDiagram participant Relayer participant Chain B participant A Light Client + Note over Chain A: start send packet execution Chain A ->> Chain A : sendPacket Chain A --> Chain A : app execution Chain A --> Chain A : packetCommitment + Note over Chain B: start send packet execution + Note over Chain A: start timeout packet execution Chain A ->> Chain A : TimeoutPacket Chain A -->> B Light Client: verifyNonMembership(PacketReceipt) Chain A --> Chain A : app execution - Chain A --> Chain A : Delete packetCommitment + Chain A --> Chain A : Delete packetCommitment + Note over Chain A: end timeout packet execution + ``` ---- - -Given a scenario where we are sending a packet from `A` to `B`: - -- Chain `A` can call either {`sendPacket`,`acknowledgePacket`,`timeoutPacket`} -- Chain `B` can call only {`receivePacket`} -- Chain `B` can only execute the `receivePacket` if `sendPacket` has been executed by chain `A` -- Chain `A` can only execute `timeoutPacket` if `sendPacket` has been executed by chain `A` and `receivePacket` has not been executed by chain `B`. -- Chain `A` can only execute `acknowledgePacket` if `sendPacket` has been executed by chain `A` and `receivePacket` has been executed by chain `B`. - ##### Sending packets >**Note** Prerequisites: The `IBCRouter`s and the `channel`s have been properly configured on both chains. From 2fe1a53e9d9081026a1029ab1c679f50daaf7320 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Fri, 11 Oct 2024 15:06:24 +0200 Subject: [PATCH 53/71] self review --- .../v2/ics-004-packet-semantics/README.md | 128 +++++++++++------- 1 file changed, 76 insertions(+), 52 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 29af4dba9..c83244567 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -1,6 +1,6 @@ --- ics: 4 -title: Packet Semantics +title: Channel and Packet Semantics stage: draft category: IBC/TAO kind: instantiation @@ -111,9 +111,11 @@ interface Acknowledgement { } ``` -An application may not need to return an acknowledgment with after processing relevant data. In this case, it is advised to return a sentinel acknowledgement value `SENTINEL_ACKNOWLEDGMENT`, which will be the single byte in the byte array: `bytes(0x01)`. +An application may not need to return an acknowledgment after processing relevant data. In this case, implementors may decide to return a sentinel acknowledgement value `SENTINEL_ACKNOWLEDGMENT`, which will be the single byte in the byte array: `bytes(0x01)`. -When the receiver chain returns this `SENTINEL_ACKNOWLEDGMENT` it allows the sender chain to still call the `acknowledgePacket` handler, e.g. to delete the packet commitment, without triggering the `onAcknowledgePacket` callback. +If the receiver chain returns the `SENTINEL_ACKNOWLEDGMENT`, the sender chain will execute the `acknowledgePacket` handler without triggering the `onAcknowledgePacket` callback. + +As we will see later, the presence in the provable store of the acknowledgement is a prerequisite for executing the `acknowledgePacket` handler. If the receiver chain does not write the acknowledgement, will be impossible for the sender chain to execute `acknowledgePacket` to delete the packet commitment. > **Example**: In the multi-data packet world, if a packet within 3 payloads intended for 3 different application is sent out, the expectation is that each payload is processed in the same order in which it was placed in the packet. Similarly, the `appAcknowledgement` array is expected to be populated within the same order. @@ -129,15 +131,15 @@ type IBCRouter struct { The registration of the application callbacks in the local `IBCRouter`, is responsibility of the chain modules. The registration of the client in the local `IBCRouter` is responsibility of the ICS-02 initialise client procedure. -> **Note:** The proper configuration of the IBCRouter is a prerequisite for starting the stream of packets. +> **Note:** The proper configuration of the `IBCRouter` is a prerequisite for starting the stream of packets. -- The `MAX_TIMEOUT_DELTA` is intendend as the max, absolute, difference between currentTimestamp and timeoutTimestamp that can be given in input to `sendPacket`. +- The `MAX_TIMEOUT_DELTA` is intendend as the max, absolute, difference between `currentTimestamp` and `timeoutTimestamp` that can be given in input to `sendPacket`. ```typescript const MAX_TIMEOUT_DELTA = Implementation specific // We recommend MAX_TIMEOUT_DELTA = TDB ``` -Additionally the ICS-04 specification defines a set of conditions that the implementations of the IBC protocol version 2 MUST adhere to. These conditions ensure the proper execution of the function handlers by establishing requirements before execution `pre-conditions`, the conditions that MUST trigger errors during execution `error-conditions`, expected outcomes after succesful execution `post-conditions-on-success`, and expected outcomes after error execution `post-conditions-on-error`. +Additionally, the ICS-04 specification defines a set of conditions that the implementations of the IBC protocol version 2 MUST adhere to. These conditions ensure the proper execution of the function handlers by establishing requirements before execution `pre-conditions`, the conditions that MUST trigger errors during execution `error-conditions`, expected outcomes after succesful execution `post-conditions-on-success`, and expected outcomes after error execution `post-conditions-on-error`. ### Desired Properties @@ -173,8 +175,6 @@ The ICS-04 use the protocol paths, defined in [ICS-24](../ics-024-host-requireme Thus, constant-size commitments to packet data fields are stored under the packet sequence number: -// NEED DISCUSSION -- we could use "commitments/{sourceId}/{sequence}" or "0x01/{sourceId}/{sequence}". For now we keep going with more or less standard paths - ```typescript function packetCommitmentPath(channelSourceId: bytes, sequence: BigEndianUint64): Path { return "commitments/channels/{channelSourceId}/sequences/{sequence}" @@ -225,19 +225,17 @@ In order to ensure valid communication, each IBC chain MUST be able to identify To start the secure packet stream between the chains, chain `A` and chain `B` MUST execute the setup following this set of procedures: -| **Procedure** | **Responsible** | **Outcome** | +| **Procedure** | **Responsible** | **Outcome** | |-----------------------------|---------------------|-----------------------------------------------------------------------------| -| **Channel Creation** | Relayer | A channel is created and linked to an underlying light client on both chains. | -| **Channel Registration** | Relayer | Registers the `counterpartyChannelId` on both chains, linking the channels. | +| **Channel Creation** | Relayer | A channel is created and linked to an underlying light client on both chains| +| **Channel Registration** | Relayer | Registers the `counterpartyChannelId` on both chains, linking the channels | -The relayer is required to execute `createClient` (as defined in ICS-02) before calling `createChannel`, since the `clientId` input parameter MUST be known at execution time. Eventually, the `createClient` message can be bundled with the `createChannel` message in a single multiMsgTx. +The relayer is required to execute `createClient` (as defined in ICS-02) before calling `createChannel`, since the `clientId`, input parameter for `createChannel`, MUST be known at execution time. Eventually, the `createClient` message can be bundled with the `createChannel` message in a single multiMsgTx. Calling indipendently `createClient`, `createChannel` and `registerChannel` result in a three step setup process. Bundling `createClient` and `createChannel` into a single operation simplifies this process and reduces the number of interactions required between the relayer and the chains to two. -The setup procedure is a prerequisite for starting the packet stream. If any of the steps has been missed, this would trigger an error during the packet handlers execution. - -Below we provide the setup sequence diagrams. +The setup procedure is a prerequisite for starting the packet stream. If any of the steps has been missed, this would trigger an error during the packet handlers execution. Below we provide the setup sequence diagrams. ```mermaid --- @@ -273,7 +271,7 @@ sequenceDiagram Relayer ->> Chain B : registerChannel(channelId = w, counterpartyChannelId = y) ``` -When the two or three step setup has been executed, the system should end up in a similar state: +After completing the two- or three-step setup, the system should end up in a similar state. ![Setup Final State](setup_final_state.png) @@ -287,9 +285,12 @@ The channel creation process enables the creation of the two channels that can b ###### Conditions Table +Pre-conditions: + +- `createClient` has been previously executed such that the `clientId` that will be provided in input to `createChannel` exist and it's valid. + | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|------------------| ----------------| -| **pre-conditions** | - `createClient` has been called at least once| | | **error-conditions** | 1. Invalid `clientId`
2. `Invalid channelId`
3. Unexpected keyPrefix format | 1. `client==null`
2.1 `validateId(channelId)==False`
2.2 `getChannel(channelId)!=null`
3. `isFormatOk(KeyPrefix)==False`
| | **post-conditions (success)** | 1. A channel is set in store
2. The creator is set in store
3. `nextSequenceSend` is initialized
4. Event with relevant fields is emitted | 1. `storedChannel[channelId]!=null`
2. `channelCreator[channelId]!=null`
3. `nextSequenceSend[channelId]==1`
4. checkEventEmission | | **post-conditions (error)** | - None of the post-conditions (success) is true
| 1. `storedChannel[channelId]==null`
2. `channelCreator[channelId]==null`
3. `nextSequenceSend[channelId]!=1`
| @@ -344,11 +345,14 @@ IBC version 2 introduces a `registerChannel` procedure. The channel registration This process stores the `counterpartyChannelId` in the local channel structure, ensuring both chains have mirrored pairs. With the correct registration, the unique clients on each side provide an authenticated stream of packet data. Social consensus outside the protocol is relied upon to ensure only valid pairs are used, representing connections between the correct chains. +Pre-conditions: + +- The `createChannel` has been previously executed such that the `channelId` that will be provided in input to `registerChannel` exist and it's valid. + ###### Conditions Table | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|-----------------------------------|----------------------------| -| **pre-conditions** | - The `createChannel` has been called at least once| | | **error-conditions** | 1. Invalid `channelId`
2. Creator authentication failed | 1.1 `validateId(channelId)==False`
1.2 `getChannel(channelId)==null`
2. `channelCreator[channelId]!=msg.signer()`
| | **post-conditions (success)** | 1. The channel in store contains the `counterpartyChannelId` information
2. An event with relevant information has been emitted | 1. `storedChannel[channelId].counterpartyChannelId!=null`
| | **post-conditions (error)** | 1. On the first call, the channel in store contains the `counterpartyChannelId` as an empty field
| 1. `storedChannel[channelId].counterpartyChannelId==null` | @@ -404,19 +408,19 @@ In the IBC protocol version 2, the packet flow is managed by four key function h Note that the execution of the four handlers, upon a unique packet, cannot be combined in any arbitrary order. -Given a scenario where we are sending a packet from `A` to `B` the protocol follows the following rules: +Given a scenario where we are sending a packet from a sender chain `A` to a receiver chain `B` the protocol follows the following rules: -- Chain `A` can call either {`sendPacket`,`acknowledgePacket`,`timeoutPacket`} -- Chain `B` can call only {`receivePacket`} -- Chain `B` can only execute the `receivePacket` if `sendPacket` has been executed by chain `A` -- Chain `A` can only execute `timeoutPacket` if `sendPacket` has been executed by chain `A` and `receivePacket` has not been executed by chain `B`. -- Chain `A` can only execute `acknowledgePacket` if `sendPacket` has been executed by chain `A` and `receivePacket` has been executed by chain `B`. +- Sender `A` can call either {`sendPacket`,`acknowledgePacket`,`timeoutPacket`} +- Receiver `B` can call only {`receivePacket`} +- Receiver `B` can only execute the `receivePacket` if `sendPacket` has been executed by sender `A` +- Sender `A` can only execute `timeoutPacket` if `sendPacket` has been executed by sender `A` and `receivePacket` has not been executed by receiver `B`. +- Sender `A` can only execute `acknowledgePacket` if `sendPacket` has been executed by sender `A` and `receivePacket` has been executed by receiver `B`. Below we provide the three possible example scenarios described with sequence diagrmas. --- -Scenario execution with synchronous acknowledgement `A` to `B` - set of actions: `sendPacket` -> `receivePacket` -> `acknowledgePacket` +Scenario execution with synchronous acknowledgement `A` to `B` - set of actions: `A.sendPacket` -> `B.receivePacket` -> `A.acknowledgePacket` ```mermaid sequenceDiagram @@ -435,7 +439,9 @@ sequenceDiagram Chain B ->> Chain B: receivePacket Chain B -->> A Light Client: verifyMembership(packetCommitment) Chain B --> Chain B : app execution + Note over Chain B: start sync ack writing Chain B --> Chain B: writeAck + Note over Chain B: end async ack writing Chain B --> Chain B: writePacketReceipt Note over Chain B: end receive packet execution Relayer ->> Chain A: relayAck @@ -450,9 +456,9 @@ sequenceDiagram --- -Scenario execution with asynchronous acknowledgement `A` to `B` - set of actions: `sendPacket` -> `receivePacket` -> `acknowledgePacket` +Scenario execution with asynchronous acknowledgement `A` to `B` - set of actions: `A.sendPacket` -> `B.receivePacket` -> `A.acknowledgePacket` -Note that the key difference with the synchronous scenario is that the receivePacket writes only the packetReceipt and not the acknowledgement. The acknowledgement is instead written asynchronously for effect of the application callback call to the core function `writeAcknowledgement`, call that happens after the `receivePacket` execution. +Note that the key difference with the synchronous scenario is that the `writeAcknowledgement` function is called after that `receivePacket` completes its execution. ```mermaid sequenceDiagram @@ -488,7 +494,7 @@ sequenceDiagram --- -Scenario timeout execution `A` to `B` - set of actions: `sendPacket` -> `timeoutPacket` +Scenario timeout execution `A` to `B` - set of actions: `A.sendPacket` -> `A.timeoutPacket` ```mermaid sequenceDiagram @@ -501,7 +507,6 @@ sequenceDiagram Chain A ->> Chain A : sendPacket Chain A --> Chain A : app execution Chain A --> Chain A : packetCommitment - Note over Chain B: start send packet execution Note over Chain A: start timeout packet execution Chain A ->> Chain A : TimeoutPacket Chain A -->> B Light Client: verifyNonMembership(PacketReceipt) @@ -513,8 +518,6 @@ sequenceDiagram ##### Sending packets ->**Note** Prerequisites: The `IBCRouter`s and the `channel`s have been properly configured on both chains. - The `sendPacket` function is called by the IBC handler when an IBC packet is submitted to the newtwork in order to send *data* in the form of an IBC packet. ∀ `Payload` included in the `packet.data`, which may refer to a different application, the application specific callbacks are retrieved from the IBC router and the `onSendPacket` is the then triggered on the specified application. The `onSendPacket` executes the application logic. Once all payloads contained in the `packet.data` have been acted upon, the packet commitment is generated and the sequence number bound to the `channelSourceId` is incremented. The `sendPacket` core function MUST execute the applications logic atomically triggering the `onSendPacket` callback ∀ application contained in the `packet.data` payload. @@ -530,11 +533,15 @@ The IBC handler performs the following steps in order: Note that the full packet is not stored in the state of the chain - merely a short hash-commitment to the data & timeout value. The packet data can be calculated from the transaction execution and possibly returned as log output which relayers can index. -###### Conditions Table +###### Execution requirements and outcomes + +Pre-conditions: + +- The `IBCRouters` and the `channels` have been properly configured on both chains. +- Sender and receiver chains are assumed to be in a setup final state | **Condition Type** |**Description** | **Code Checks**| |-------------------------------|--------------------------------------------------------|------------------------| -| **pre-conditions** | - Sender and receiver chains are assumed to be in a setup final state
| | | **Error-Conditions** | 1. Invalid `clientId`
2. Invalid `channelId`
3. Invalid `timeoutTimestamp`
4. Unsuccessful payload execution. | 1. `router.clients[channel.clientId]==null`
2. `getChannel(sourceChannelId)==null`
3.1 `timeoutTimestamp==0`
3.2 `timeoutTimestamp < currentTimestamp()`
3.3 `timeoutTimestamp > currentTimestamp() + MAX_TIMEOUT_DELTA`
4. `onSendPacket(..)==False`
| | **Post-Conditions (Success)** | 1. `onSendPacket` is executed and the application state is modified
2. The `packetCommitment` is generated and stored under the expected `packetCommitmentPath`
3. The sequence number bound to `sourceId` is incremented by 1
4. Event with relevant information is emitted | 1. `onSendPacket(..)==True; app.State(beforeSendPacket)!=app.State(afterSendPacket)`
2. `commitment=commitV2Packet(packet), provableStore.get(packetCommitmentPath(sourceChannelId, sequence))==commitment`
3. `nextSequenceSend(beforeSendPacket[sourecChannelId])+1==SendPacket(..)`
4. CheckEventEmission | | **Post-Conditions (Error)** | 1. if `onSendPacket` fails the application state is unchanged
2. No `packetCommitment` has been generated
3. The sequence number bound to `sourceId` is unchanged. | 1. `app.State(beforeSendPacket)==app.State(afterSendPacket)`
2. `commitment=commitV2Packet(packet), provableStore.get(packetCommitmentPath(sourceChannelId, sequence))==commitment`
3. `nextSequenceSend[sourecChannelId]==nextSequenceSend(beforeSendPacket)` | @@ -624,11 +631,16 @@ The IBC handler performs the following steps in order: We pass the address of the `relayer` that signed and submitted the packet to enable a module to optionally provide some rewards. This provides a foundation for fee payment, but can be used for other techniques as well (like calculating a leaderboard). -###### Conditions Table +###### Execution requirements and outcomes + +Pre-conditions: + +- The sender chain has executed `sendPacket` --> stored a verifiable `packetCommitment` +- `TimeoutTimestamp` is not elapsed on the receiving chain +- `PacketReceipt` for the specific keyPrefix and sequence MUST be empty --> receiver chain has not executed `receivePacket` | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|-----------------------------------------------|-----------------------------------------------| -| **pre-conditions** | - The sender chain has stored a verifiable `packetCommitment`
- `TimeoutTimestamp` is not elapsed on the receiving chain
- `PacketReceipt` for the specific keyPrefix and sequence MUST be empty (implies `receivePacket` has not been called yet). | | | **Error-Conditions** | 1. invalid `packetCommitment`, 2.`packetReceipt` already exists
3. Invalid timeoutTimestamp
4. Unsuccessful payload execution. | 1.1 `verifyMembership(packetCommitment)==false`
1.2 `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))!=null`
3. `timeoutTimestamp === 0`
3.1 `currentTimestamp() < packet.timeoutTimestamp)`
4. `onReceivePacket(..)==False` | | **Post-Conditions (Success)** | 1. `onReceivePacket` is executed and the application state is modified
2. The `packetReceipt` is written
| 1. `onReceivePacket(..)==True; app.State(beforeReceivePacket)!=app.State(afterReceivePacket)`
2. `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))!=null`
| | **Post-Conditions (Error)** | 1. if `onReceivePacket` fails the application state is unchanged
2. `packetReceipt is not written`
| 1. `app.State(beforeReceivePacket)==app.State(afterReceivePacket)`
2. `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))==null` | @@ -715,24 +727,27 @@ function recvPacket( ##### Writing acknowledgements -> **Note:** The system handles synchronous and asynchronous acknowledgement logic. +> **Note:** The system handles synchronous and asynchronous acknowledgement logic. Writing acknowledgements ensures that application modules callabacks have been triggered and have returned their specific acknowledgment in order to write data which resulted from processing an IBC packet that the sending chain can then verify. Writing acknowledgement serves as a sort of "execution receipt" or "RPC call response". -The `writeAcknowledgement` function can be called either synchronously by the IBC handler during the `receivePacket` execution or it can be called asynchronously by an application callback. +The `writeAcknowledgement` function can be activated either synchronously by the IBC handler during the `receivePacket` execution or it can be activated asynchronously by an application callback after the `receivePacket` execution. -Writing acknowledgements ensures that application modules callabacks have been triggered and have returned their specific acknowledgment in order to write data which resulted from processing an IBC packet that the sending chain can then verify. Writing acknowledgement serves as a sort of "execution receipt" or "RPC call response". - -`writeAcknowledgement` can be called either in a `receivePacket`, or after the `receivePacket` execution in a later on application callback. Given that the `receivePacket` logic is always execute before the `writeAcknowledgement` it *does not* check if the packet being acknowledged was actually received, because this would result in proofs being verified twice for acknowledged packets. This aspect of correctness is the responsibility of the IBC handler. +Given that the `receivePacket` logic is expected to be executed before the `writeAcknowledgement` is activated, `writeAcknowledgement` *does not* check if the packet being acknowledged was actually received, because this would result in proofs being verified twice for acknowledged packets. This aspect of correctness is the responsibility of the IBC handler. The IBC handler performs the following steps in order: - Checks that an acknowledgement for this packet has not yet been written - Sets the opaque acknowledgement value at a store path unique to the packet -###### Conditions Table +###### Execution requirements and outcomes + +Pre-conditions: + +- `receivePacket` has been called by receiver chain +- `onReceivePacket` application callback has been executed on the receiver chain +- `writeAcknowledgement` has not been executed yet | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|------------|------------| -| **pre-conditions** | - `receivePacket` has been called on receiver chain
- `onReceivePacket` application callback has been executed
- `writeAcknowledgement` has not been called yet | | | **Error-Conditions** | 1. acknowledgement is empty
2. The `packetAcknowledgementPath` stores already a value. | 1. `len(acknowledgement) === 0`
2. `provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) !== null` | | **Post-Conditions (Success)** | 1. opaque acknowledgement has been written at `packetAcknowledgementPath` | 1. `provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) !== null` | | **Post-Conditions (Error)** | 1. No value is stored at the `packetAcknowledgementPath`. | 1. `provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) === null` | @@ -768,17 +783,20 @@ function writeAcknowledgement( ##### Processing acknowledgements -The `acknowledgePacket` function is called by the IBC handler to process the acknowledgement of a packet previously sent by the source chain that has been received on the destination chain. The `acknowledgePacket` also cleans up the packet commitment, which is no longer necessary since the packet has been received and acted upon. +The `acknowledgePacket` function is called by the IBC handler to process the acknowledgement of a packet previously sent by the sender chain that has been received on the receiver chain. The `acknowledgePacket` also cleans up the packet commitment, which is no longer necessary since the packet has been received and acted upon. The IBC hanlder MUST atomically trigger the callbacks execution of appropriate application acknowledgement-handling logic in conjunction with calling `acknowledgePacket`. -###### Conditions Table +###### Execution requirements and outcomes + +Pre-conditions: -Given that at this point of the packet flow, chain `B` has sucessfully received a packet, the pre-conditions defines what MUST be accomplished before chain `A` can properly execute the `acknowledgePacket` for the IBC v2 packet. +- Sender chain has sent a packet. +- Receiver chain has successfully received a packet and has written the acknowledgment --> `packetReceipt` and `acknowledgment` have been written in the provable store. Note that if the `acknowledgment` is written, this implies that `receivePacket` has been executed, thus there is no need to verify the presence of the `packetReceipt`. +- Sender chain has not cleared out the `packetCommitment` | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|---------------------------------|---------------------------------| -| **pre-conditions** | - receiver chain has successfully received a packet and has written the acknowledgment
- `packetCommitment` has not been cleared out yet. || | **Error-Conditions** | 1. `packetCommitment` already cleared out
2. Unset Acknowledgment
3. Unsuccessful payload execution. | 1. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null`
2. `verifyMembership(packetacknowledgementPath,...,) == False`
3. `onAcknowledgePacket(packet.channelSourceId,payload, acknowledgement) == False` | | **Post-Conditions (Success)** | 1. `onAcknowledgePacket` is executed and the application state is modified
2. `packetCommitment` has been cleared out. | 1. `onAcknowledgePacket(..)==True; app.State(beforeAcknowledgePacket)!=app.State(afterAcknowledgePacket)`
2. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null` | | **Post-Conditions (Error)** | 1. If `onAcknowledgePacket` fails the application state is unchanged
2. `packetCommitment` has not been cleared out
3. acknowledgement is stil in store | 1. `onAcknowledgePacket(..)==False; app.State(beforeAcknowledgePacket)==app.State(afterAcknowledgePacket)`
2. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === commitV2Packet(packet)` 3. `verifyMembership(packetAcknowledgementPath,...,) == True`| @@ -787,7 +805,7 @@ Given that at this point of the packet flow, chain `B` has sucessfully received The ICS04 provides an example pseudo-code that enforce the above described conditions so that the following sequence of steps must occur for a packet to be acknowledged from module *1* on machine *A* to module *2* on machine *B*. -// NEED DISCUSSION: What to do with the relayer? Do we want to keep it? // We pass the `relayer` address just as in [Receiving packets](#receiving-packets) to allow for possible incentivization here as well. +>**Note:** We pass the `relayer` address just as in [Receiving packets](#receiving-packets) to allow for possible incentivization here as well. ```typescript function acknowledgePacket( @@ -881,11 +899,17 @@ Calling modules MAY atomically execute appropriate application timeout-handling The `timeoutPacket` checks the absence of the receipt key (which will have been written if the packet was received). We pass the `relayer` address just as in [Receiving packets](#receiving-packets) to allow for possible incentivization here as well. -###### Conditions Table +###### Execution requirements and outcomes + +Pre-conditions: + +- Sender chain has sent a packet +- Receiver chain has not called `receivePacket` --> `packetReceipt` is empty +- `packetCommitment` has not been cleared out yet +- `timeoutTimestamp` is elapsed on the receiver chain | **Condition Type** | **Description**| **Code Checks**| |-------------------------------|--------------------|--------------------| -| **pre-conditions** | - `packetReceipt` is empty
- `packetCommitment` has not been cleared out yet | | | **Error-Conditions** | 1. `packetCommitment` already cleared out
2. `packetReceipt` is not empty
3. Unsuccessful payload execution
4. `timeoutTimestamp` not elapsed on the receiving chain| 1. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null`
2. `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))!=null`
3. `onTimeoutPacket(packet.channelSourceId,payload) == False`
4.1 `packet.timeoutTimestamp > 0`
4.2 `proofTimestamp = client.getTimestampAtHeight(proofHeight); proofTimestamp >= packet.timeoutTimestamp` | | **Post-Conditions (Success)** | 1. `onTimeoutPacket` is executed and the application state is modified
2. `packetCommitment` has been cleared out
3. `packetReceipt` is empty | 1. `onTimeoutPacket(..)==True; app.State(beforeTimeoutPacket)!=app.State(afterTimeoutPacket)`
2. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null`
3. `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))==null`
| | **Post-Conditions (Error)** | 1. If `onTimeoutPacket` fails and the application state is unchanged
2. `packetCommitment` is not cleared out | 1. `onTimeoutPacket(..)==True; app.State(beforeTimeoutPacket)!=app.State(afterTimeoutPacket)`
2. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null` | @@ -965,7 +989,7 @@ There is no race condition between a packet timeout and packet confirmation, as If a client has been frozen while packets are in-flight, the packets can no longer be received on the destination chain and can be timed-out on the source chain. -### Properties & Invariants +### Properties - Packets are delivered exactly once, assuming that the chains are live within the timeout window, and in case of timeout can be timed-out exactly once on the sending chain. @@ -979,8 +1003,8 @@ Future updates of this specification will enable the atomic processing of multip ## Example Implementations -- Implementation of ICS 04 in Go can be found in [ibc-go repository](https://github.com/cosmos/ibc-go). -- Implementation of ICS 04 in Rust can be found in [ibc-rs repository](https://github.com/cosmos/ibc-rs). +- Implementation of ICS 04 version 2 in Go can be found in [ibc-go repository](https://github.com/cosmos/ibc-go). +- Implementation of ICS 04 version 2 in Rust can be found in [ibc-rs repository](https://github.com/cosmos/ibc-rs). ## History From 8ca1f448ae9b2185afb4c85931c16bab439982c8 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Fri, 11 Oct 2024 15:55:37 +0200 Subject: [PATCH 54/71] add soundness and correctness --- .../v2/ics-004-packet-semantics/README.md | 57 +++++++++++++------ 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index c83244567..0c7af4014 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -6,15 +6,14 @@ category: IBC/TAO kind: instantiation requires: 2, 24, packet-data version compatibility: ibc-go v10.0.0 -author: Christopher Goes +author: Stefano Angieri , Aditya Sripal created: 2019-03-07 modified: 2019-08-25 --- TODO : -- Resolve Need discussion -- Improve Sketchs +- Rename file - Improve conditions set / think about more condition an proper presentation - Review Ack and Timeout carefully - FROM Race condition UP to END @@ -283,7 +282,7 @@ While the above mentioned `createClient` procedure is defined by [ICS-2](../ics- The channel creation process enables the creation of the two channels that can be linked to establishes the communication pathway between two chains. -###### Conditions Table +###### Execution requirements and outcomes Pre-conditions: @@ -349,7 +348,7 @@ Pre-conditions: - The `createChannel` has been previously executed such that the `channelId` that will be provided in input to `registerChannel` exist and it's valid. -###### Conditions Table +###### Execution requirements and outcomes | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|-----------------------------------|----------------------------| @@ -414,9 +413,9 @@ Given a scenario where we are sending a packet from a sender chain `A` to a rece - Receiver `B` can call only {`receivePacket`} - Receiver `B` can only execute the `receivePacket` if `sendPacket` has been executed by sender `A` - Sender `A` can only execute `timeoutPacket` if `sendPacket` has been executed by sender `A` and `receivePacket` has not been executed by receiver `B`. -- Sender `A` can only execute `acknowledgePacket` if `sendPacket` has been executed by sender `A` and `receivePacket` has been executed by receiver `B`. +- Sender `A` can only execute `acknowledgePacket` if `sendPacket` has been executed by sender `A`, `receivePacket` has been executed by receiver `B`, `writeAcknowledgePacket` has been executed by receiver `B`. -Below we provide the three possible example scenarios described with sequence diagrmas. +Below we provide the three possible example scenarios described with sequence diagrams. --- @@ -629,7 +628,7 @@ The IBC handler performs the following steps in order: - Sets a store path to indicate that the packet has been received - If the flows supports synchronous acknowledgement, it writes the acknowledgement into the receiver provableStore. -We pass the address of the `relayer` that signed and submitted the packet to enable a module to optionally provide some rewards. This provides a foundation for fee payment, but can be used for other techniques as well (like calculating a leaderboard). +>**Note:** We pass the address of the `relayer` that signed and submitted the packet to enable a module to optionally provide some rewards. This provides a foundation for fee payment, but can be used for other techniques as well (like calculating a leaderboard). ###### Execution requirements and outcomes @@ -752,6 +751,10 @@ Pre-conditions: | **Post-Conditions (Success)** | 1. opaque acknowledgement has been written at `packetAcknowledgementPath` | 1. `provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) !== null` | | **Post-Conditions (Error)** | 1. No value is stored at the `packetAcknowledgementPath`. | 1. `provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) === null` | +###### Pseudo-Code + +The ICS-04 provides an example pseudo-code that enforce the above described conditions so that the following sequence of steps SHOULD occur when the receiver chain writes the acknowledgement in its provable store. + ```typescript function writeAcknowledgement( packet: Packet, @@ -891,13 +894,12 @@ Note that in order to avoid any possible "double-spend" attacks, the timeout alg ##### Sending end The `timeoutPacket` function is called by the IBC hanlder by the chain that attempted to send a packet to a counterparty module, -where the timeout height or timeout timestamp has passed on the counterparty chain without the packet being committed, to prove that the packet +where the timeout timestamp has passed on the counterparty chain without the packet being committed, to prove that the packet can no longer be executed and to allow the calling module to safely perform appropriate state transitions. Calling modules MAY atomically execute appropriate application timeout-handling logic in conjunction with calling `timeoutPacket`. The `timeoutPacket` checks the absence of the receipt key (which will have been written if the packet was received). -We pass the `relayer` address just as in [Receiving packets](#receiving-packets) to allow for possible incentivization here as well. ###### Execution requirements and outcomes @@ -916,6 +918,10 @@ Pre-conditions: ###### Pseudo-Code +The ICS-04 provides an example pseudo-code that enforce the above described conditions so that the following sequence of steps MUST occur for a packet to be timed-out by the sender chain. + +>**Note:** We pass the `relayer` address just as in [Receiving packets](#receiving-packets) to allow for possible incentivization here as well. + ```typescript function timeoutPacket( packet: OpaquePacket, @@ -977,21 +983,40 @@ Packets MUST be acknowledged or timed-out in order to be cleaned-up. TODO -##### Identifier allocation - -There is an unavoidable race condition on identifier allocation on the destination chain. Modules would be well-advised to utilise pseudo-random, non-valuable identifiers. Managing to claim the identifier that another module wishes to use, however, while annoying, cannot man-in-the-middle a handshake since the receiving module must already own the port to which the handshake was targeted. - ##### Timeouts / packet confirmation There is no race condition between a packet timeout and packet confirmation, as the packet will either have passed the timeout height prior to receipt or not. ##### Clients unreachability with in-flight packets -If a client has been frozen while packets are in-flight, the packets can no longer be received on the destination chain and can be timed-out on the source chain. +If the source client pointed in the destination chain channel has been frozen while packets are in-flight, the packets can no longer be received on the destination chain and can be timed-out on the source chain. ### Properties -- Packets are delivered exactly once, assuming that the chains are live within the timeout window, and in case of timeout can be timed-out exactly once on the sending chain. +#### Correctness + +Claim: If clients and channels are setup correctly, then a chain can always verify packet flow messages sent by a valid counterparty. + +If the clients are properly registered in the channels, then they allow the verification of any key/value membership proof as well as a key non-membership proof. + +All packet flow message (SendPacket, RecvPacket, and TimeoutPacket) are sent with the full packet. The packet contains both sender and receiver channels identifiers. Thus on packet flow messages sent to the receiver (RecvPacket), we use the receiver channel identifier in the packet to retrieve our local client and the path the sender stored the packet under. We can thus use our retrieved client to verify a key/value membership proof to validate that the packet was sent by the counterparty. + +Similarly, for packet flow messages sent to the sender (AcknowledgePacket, TimeoutPacket); the packet is provided again. This time, we use the sender channel identifier to retrieve the local client and the key path that the receiver must have written to when it received the packet. We can thus use our retrieved client to verify a key/value membership proof to validate that the packet was sent by the counterparty. In the case of timeout, if the packet receipt wasn't written to the receipt path determined by the destination identifier this can be verified by our retrieved client using the key nonmembership proof. + +#### Soundness + +Claim: If the clients and channels are setup correctly, then a chain cannot mistake a packet flow message intended for a different chain as a valid message from a valid counterparty. + +We must note that client and channel identifiers are unique to each chain but are not globally unique. Let us first consider a user that correctly specifies the source and destination channel identifiers in the packet. + +We wish to ensure that well-formed packets (i.e. packets with correctly setup channels ids) cannot have packet flow messages succeed on third-party chains. Ill-formed packets (i.e. packets with invalid channel ids) may in some cases complete in invalid states; however we must ensure that any completed state from these packets cannot mix with the state of other valid packets. + +We are guaranteed that the source channel identifier is unique on the source chain, the destination channel identifier is unique on the destination chain. Additionally, the destination channel identifier points to a valid client of the source chain, and the source channel identifier points to a valid client of the destination chain. + +Suppose the RecvPacket is sent to a chain other than the one identified by the the clientId on the source chain. + +In the packet flow messages sent to the receiver (RecvPacket), the packet send is verified using the client and the packet commitment path on the destination chain (retrieved using destination channel identifier) which are linked to the source chain. +This verification check can only pass if the chain identified by the client stored in the destination channel committed the packet we received under the counterparty keyPrefix path specified in the channel. This is only possible if the destination client is pointing to the original source chain, or if it is pointing to a different chain that committed the exact same packet. Pointing to the original source chain would mean we sent the packet to the correct chain. Since the sender only sends packets intended for the desination chain by setting to a unique source identifier, we can be sure the packet was indeed intended for us. Since our client on the receiver is also correctly pointing to the sender chain, we are verifying the proof against a specific consensus algorithm that we assume to be honest. If the packet is committed to the wrong key path, then we will not accept the packet. Similarly, if the packet is committed by the wrong chain then we will not be able to verify correctly. ## Backwards Compatibility From e745df50fcfbec609314e144e5ca8923d7daaff4 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Fri, 11 Oct 2024 16:14:33 +0200 Subject: [PATCH 55/71] self review --- .../v2/ics-004-packet-semantics/README.md | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 0c7af4014..921c85cee 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -291,8 +291,8 @@ Pre-conditions: | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|------------------| ----------------| | **error-conditions** | 1. Invalid `clientId`
2. `Invalid channelId`
3. Unexpected keyPrefix format | 1. `client==null`
2.1 `validateId(channelId)==False`
2.2 `getChannel(channelId)!=null`
3. `isFormatOk(KeyPrefix)==False`
| -| **post-conditions (success)** | 1. A channel is set in store
2. The creator is set in store
3. `nextSequenceSend` is initialized
4. Event with relevant fields is emitted | 1. `storedChannel[channelId]!=null`
2. `channelCreator[channelId]!=null`
3. `nextSequenceSend[channelId]==1`
4. checkEventEmission | -| **post-conditions (error)** | - None of the post-conditions (success) is true
| 1. `storedChannel[channelId]==null`
2. `channelCreator[channelId]==null`
3. `nextSequenceSend[channelId]!=1`
| +| **post-conditions (success)** | 1. A channel is set in store
2. The creator is set in store
3. `nextSequenceSend` is initialized
4. Event with relevant fields is emitted | 1. `storedChannel[channelId]!=null`
2. `channelCreator[channelId]!=null`
3. `nextSequenceSend[channelId]==1`
4. Check Event Emission | +| **post-conditions (error)** | None of the post-conditions (success) is true
| 1. `storedChannel[channelId]==null`
2. `channelCreator[channelId]==null`
3. `nextSequenceSend[channelId]!=1`
4. No Event is Emitted
| ###### Pseudo-Code @@ -353,8 +353,8 @@ Pre-conditions: | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|-----------------------------------|----------------------------| | **error-conditions** | 1. Invalid `channelId`
2. Creator authentication failed | 1.1 `validateId(channelId)==False`
1.2 `getChannel(channelId)==null`
2. `channelCreator[channelId]!=msg.signer()`
| -| **post-conditions (success)** | 1. The channel in store contains the `counterpartyChannelId` information
2. An event with relevant information has been emitted | 1. `storedChannel[channelId].counterpartyChannelId!=null`
| -| **post-conditions (error)** | 1. On the first call, the channel in store contains the `counterpartyChannelId` as an empty field
| 1. `storedChannel[channelId].counterpartyChannelId==null` | +| **post-conditions (success)** | 1. The channel in store contains the `counterpartyChannelId` information
2. An event with relevant information has been emitted | 1. `storedChannel[channelId].counterpartyChannelId!=null`
2. Check Event Emission | +| **post-conditions (error)** | 1. On the first call, the channel in store contains the `counterpartyChannelId` as an empty field
| 1. `storedChannel[channelId].counterpartyChannelId==null`
2. No Event is Emitted
| ###### Pseudo-Code @@ -542,8 +542,8 @@ Pre-conditions: | **Condition Type** |**Description** | **Code Checks**| |-------------------------------|--------------------------------------------------------|------------------------| | **Error-Conditions** | 1. Invalid `clientId`
2. Invalid `channelId`
3. Invalid `timeoutTimestamp`
4. Unsuccessful payload execution. | 1. `router.clients[channel.clientId]==null`
2. `getChannel(sourceChannelId)==null`
3.1 `timeoutTimestamp==0`
3.2 `timeoutTimestamp < currentTimestamp()`
3.3 `timeoutTimestamp > currentTimestamp() + MAX_TIMEOUT_DELTA`
4. `onSendPacket(..)==False`
| -| **Post-Conditions (Success)** | 1. `onSendPacket` is executed and the application state is modified
2. The `packetCommitment` is generated and stored under the expected `packetCommitmentPath`
3. The sequence number bound to `sourceId` is incremented by 1
4. Event with relevant information is emitted | 1. `onSendPacket(..)==True; app.State(beforeSendPacket)!=app.State(afterSendPacket)`
2. `commitment=commitV2Packet(packet), provableStore.get(packetCommitmentPath(sourceChannelId, sequence))==commitment`
3. `nextSequenceSend(beforeSendPacket[sourecChannelId])+1==SendPacket(..)`
4. CheckEventEmission | -| **Post-Conditions (Error)** | 1. if `onSendPacket` fails the application state is unchanged
2. No `packetCommitment` has been generated
3. The sequence number bound to `sourceId` is unchanged. | 1. `app.State(beforeSendPacket)==app.State(afterSendPacket)`
2. `commitment=commitV2Packet(packet), provableStore.get(packetCommitmentPath(sourceChannelId, sequence))==commitment`
3. `nextSequenceSend[sourecChannelId]==nextSequenceSend(beforeSendPacket)` | +| **Post-Conditions (Success)** | 1. `onSendPacket` is executed and the application state is modified
2. The `packetCommitment` is generated and stored under the expected `packetCommitmentPath`
3. The sequence number bound to `sourceId` is incremented by 1
4. Event with relevant information is emitted | 1. `onSendPacket(..)==True; app.State(beforeSendPacket)!=app.State(afterSendPacket)`
2. `commitment=commitV2Packet(packet), provableStore.get(packetCommitmentPath(sourceChannelId, sequence))==commitment`
3. `nextSequenceSend(beforeSendPacket[sourecChannelId])+1==SendPacket(..)`
4. Check Event Emission | +| **Post-Conditions (Error)** | 1. if `onSendPacket` fails the application state is unchanged
2. No `packetCommitment` has been generated
3. The sequence number bound to `sourceId` is unchanged
4. No Event Emission | 1. `app.State(beforeSendPacket)==app.State(afterSendPacket)`
2. `commitment=commitV2Packet(packet), provableStore.get(packetCommitmentPath(sourceChannelId, sequence))==commitment`
3. `nextSequenceSend[sourecChannelId]==nextSequenceSend(beforeSendPacket)`
4. Check No Event is Emitted
| ###### Pseudo-Code @@ -641,8 +641,8 @@ Pre-conditions: | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|-----------------------------------------------|-----------------------------------------------| | **Error-Conditions** | 1. invalid `packetCommitment`, 2.`packetReceipt` already exists
3. Invalid timeoutTimestamp
4. Unsuccessful payload execution. | 1.1 `verifyMembership(packetCommitment)==false`
1.2 `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))!=null`
3. `timeoutTimestamp === 0`
3.1 `currentTimestamp() < packet.timeoutTimestamp)`
4. `onReceivePacket(..)==False` | -| **Post-Conditions (Success)** | 1. `onReceivePacket` is executed and the application state is modified
2. The `packetReceipt` is written
| 1. `onReceivePacket(..)==True; app.State(beforeReceivePacket)!=app.State(afterReceivePacket)`
2. `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))!=null`
| -| **Post-Conditions (Error)** | 1. if `onReceivePacket` fails the application state is unchanged
2. `packetReceipt is not written`
| 1. `app.State(beforeReceivePacket)==app.State(afterReceivePacket)`
2. `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))==null` | +| **Post-Conditions (Success)** | 1. `onReceivePacket` is executed and the application state is modified
2. The `packetReceipt` is written
3. Event is Emitted
| 1. `onReceivePacket(..)==True; app.State(beforeReceivePacket)!=app.State(afterReceivePacket)`
2. `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))!=null`
3. Check Event Emission
| +| **Post-Conditions (Error)** | 1. if `onReceivePacket` fails the application state is unchanged
2. `packetReceipt is not written`

3. No Event Emission
| 1. `app.State(beforeReceivePacket)==app.State(afterReceivePacket)`
2. `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))==null`
3. Check No Event is Emitted
| ###### Pseudo-Code @@ -748,8 +748,8 @@ Pre-conditions: | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|------------|------------| | **Error-Conditions** | 1. acknowledgement is empty
2. The `packetAcknowledgementPath` stores already a value. | 1. `len(acknowledgement) === 0`
2. `provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) !== null` | -| **Post-Conditions (Success)** | 1. opaque acknowledgement has been written at `packetAcknowledgementPath` | 1. `provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) !== null` | -| **Post-Conditions (Error)** | 1. No value is stored at the `packetAcknowledgementPath`. | 1. `provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) === null` | +| **Post-Conditions (Success)** | 1. opaque acknowledgement has been written at `packetAcknowledgementPath`
2. Event is Emitted
| 1. `provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) !== null`
2. Check Event Emission
| +| **Post-Conditions (Error)** | 1. No value is stored at the `packetAcknowledgementPath`.
2. No Event is Emitted
| 1. `provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) === null`
2. Check No Event is Emitted
| ###### Pseudo-Code @@ -801,8 +801,8 @@ Pre-conditions: | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|---------------------------------|---------------------------------| | **Error-Conditions** | 1. `packetCommitment` already cleared out
2. Unset Acknowledgment
3. Unsuccessful payload execution. | 1. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null`
2. `verifyMembership(packetacknowledgementPath,...,) == False`
3. `onAcknowledgePacket(packet.channelSourceId,payload, acknowledgement) == False` | -| **Post-Conditions (Success)** | 1. `onAcknowledgePacket` is executed and the application state is modified
2. `packetCommitment` has been cleared out. | 1. `onAcknowledgePacket(..)==True; app.State(beforeAcknowledgePacket)!=app.State(afterAcknowledgePacket)`
2. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null` | -| **Post-Conditions (Error)** | 1. If `onAcknowledgePacket` fails the application state is unchanged
2. `packetCommitment` has not been cleared out
3. acknowledgement is stil in store | 1. `onAcknowledgePacket(..)==False; app.State(beforeAcknowledgePacket)==app.State(afterAcknowledgePacket)`
2. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === commitV2Packet(packet)` 3. `verifyMembership(packetAcknowledgementPath,...,) == True`| +| **Post-Conditions (Success)** | 1. `onAcknowledgePacket` is executed and the application state is modified
2. `packetCommitment` has been cleared out
4. Event is Emission
| 1. `onAcknowledgePacket(..)==True; app.State(beforeAcknowledgePacket)!=app.State(afterAcknowledgePacket)`
2. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null`,
4. Check Event is Emitted
| +| **Post-Conditions (Error)** | 1. If `onAcknowledgePacket` fails the application state is unchanged
2. `packetCommitment` has not been cleared out
3. acknowledgement is stil in store
4. No Event Emission
| 1. `onAcknowledgePacket(..)==False; app.State(beforeAcknowledgePacket)==app.State(afterAcknowledgePacket)`
2. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === commitV2Packet(packet)` 3. `verifyMembership(packetAcknowledgementPath,...,) == True`
4. Check No Event is Emitted
| ###### Pseudo-Code @@ -913,8 +913,8 @@ Pre-conditions: | **Condition Type** | **Description**| **Code Checks**| |-------------------------------|--------------------|--------------------| | **Error-Conditions** | 1. `packetCommitment` already cleared out
2. `packetReceipt` is not empty
3. Unsuccessful payload execution
4. `timeoutTimestamp` not elapsed on the receiving chain| 1. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null`
2. `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))!=null`
3. `onTimeoutPacket(packet.channelSourceId,payload) == False`
4.1 `packet.timeoutTimestamp > 0`
4.2 `proofTimestamp = client.getTimestampAtHeight(proofHeight); proofTimestamp >= packet.timeoutTimestamp` | -| **Post-Conditions (Success)** | 1. `onTimeoutPacket` is executed and the application state is modified
2. `packetCommitment` has been cleared out
3. `packetReceipt` is empty | 1. `onTimeoutPacket(..)==True; app.State(beforeTimeoutPacket)!=app.State(afterTimeoutPacket)`
2. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null`
3. `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))==null`
| -| **Post-Conditions (Error)** | 1. If `onTimeoutPacket` fails and the application state is unchanged
2. `packetCommitment` is not cleared out | 1. `onTimeoutPacket(..)==True; app.State(beforeTimeoutPacket)!=app.State(afterTimeoutPacket)`
2. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null` | +| **Post-Conditions (Success)** | 1. `onTimeoutPacket` is executed and the application state is modified
2. `packetCommitment` has been cleared out
3. `packetReceipt` is empty
4. Event is Emitted
| 1. `onTimeoutPacket(..)==True; app.State(beforeTimeoutPacket)!=app.State(afterTimeoutPacket)`
2. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null`
3. `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))==null`
4. Check Event is Emitted
| +| **Post-Conditions (Error)** | 1. If `onTimeoutPacket` fails and the application state is unchanged
2. `packetCommitment` is not cleared out
3. No Event Emission
| 1. `onTimeoutPacket(..)==True; app.State(beforeTimeoutPacket)!=app.State(afterTimeoutPacket)`
2. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null`
3. Check No Event is Emitted
| ###### Pseudo-Code From e44da3f337cbbaf9a6476d6dd8039e116d22679d Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Fri, 11 Oct 2024 16:28:42 +0200 Subject: [PATCH 56/71] self review --- spec/core/v2/ics-004-packet-semantics/README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 921c85cee..89398da1b 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -354,7 +354,7 @@ Pre-conditions: |-------------------------------|-----------------------------------|----------------------------| | **error-conditions** | 1. Invalid `channelId`
2. Creator authentication failed | 1.1 `validateId(channelId)==False`
1.2 `getChannel(channelId)==null`
2. `channelCreator[channelId]!=msg.signer()`
| | **post-conditions (success)** | 1. The channel in store contains the `counterpartyChannelId` information
2. An event with relevant information has been emitted | 1. `storedChannel[channelId].counterpartyChannelId!=null`
2. Check Event Emission | -| **post-conditions (error)** | 1. On the first call, the channel in store contains the `counterpartyChannelId` as an empty field
| 1. `storedChannel[channelId].counterpartyChannelId==null`
2. No Event is Emitted
| +| **post-conditions (error)** | 1. On the first call, the channel in store contains the `counterpartyChannelId` as an empty field
2. No Event is Emitted
| 1. `storedChannel[channelId].counterpartyChannelId==null`
2. Check No Event is Emitted
| ###### Pseudo-Code @@ -662,8 +662,6 @@ function recvPacket( client = router.clients[channel.clientId] assert(client !== null) - //assert(packet.sourceId == channel.counterpartyChannelId) This should be always true, redundant // NEED DISCUSSION - // verify timeout assert(packet.timeoutTimestamp === 0) assert(currentTimestamp() < packet.timeoutTimestamp) From c9dd5b9b352f5ab4fa3fb0396c855ad3c66b229c Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Mon, 14 Oct 2024 12:23:03 +0200 Subject: [PATCH 57/71] fixes --- spec/core/v2/ics-004-packet-semantics/README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index 89398da1b..ea8f8e9f7 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -28,7 +28,7 @@ The standard then details the processes for transmitting, receiving, acknowledgi ### Motivation -The motivation for this specification is to formalize the semantics for both packet handling and channel creation and registration in the IBC version 2 protocol. These are fundamental components for enabling reliable, secure, and verifiable communication between independent blockchains. The document focuses on the mechanisms to establish the root of trust for starting the communication between two distinct chains, routing, verification, and application-level delivery guarantees required by the IBC protocol. +The motivation for this specification is to formalize the semantics for both packet handling and channel creation and registration in the IBC version 2 protocol. These are fundamental components for enabling reliable, secure, and verifiable communication between independent blockchains. This specification focuses on defining the mechanisms for creating channels, securely registering them between chains, and ensuring that packets sent across these channels are processed consistently and verifiably. By utilizing on-chain light clients for state verification, it enables chains to exchange data without requiring synchronous communication, ensuring that all packets are delivered exactly once, even in the presence of network delays or reordering. @@ -162,7 +162,7 @@ Additionally, the ICS-04 specification defines a set of conditions that the impl #### Fungibility conservation -> **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 `B` when a particular IBC packet is committed to chain `B`, and require outgoing sends of that packet on chain `A` to escrow an equal amount of the asset on chain `A` until the vouchers are later redeemed back to chain `A` with 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 chain `B` can later be redeemed back to chain `A`. +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 `B` when a particular IBC packet is committed to chain `B`, and require outgoing sends of that packet on chain `A` to escrow an equal amount of the asset on chain `A` until the vouchers are later redeemed back to chain `A` with 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 chain `B` can later be redeemed back to chain `A`. ## Technical Specification @@ -342,7 +342,7 @@ function createChannel( IBC version 2 introduces a `registerChannel` procedure. The channel registration procedure ensures both chains have a mutually recognized channel that facilitates the packet transmission. -This process stores the `counterpartyChannelId` in the local channel structure, ensuring both chains have mirrored pairs. With the correct registration, the unique clients on each side provide an authenticated stream of packet data. Social consensus outside the protocol is relied upon to ensure only valid pairs are used, representing connections between the correct chains. +This process stores the `counterpartyChannelId` in the local channel structure, ensuring both chains have mirrored **** pairs. With the correct registration, the unique clients on each side provide an authenticated stream of packet data. Social consensus outside the protocol is relied upon to ensure only valid **** pairs are used, representing connections between the correct chains. Pre-conditions: @@ -517,7 +517,7 @@ sequenceDiagram ##### Sending packets -The `sendPacket` function is called by the IBC handler when an IBC packet is submitted to the newtwork in order to send *data* in the form of an IBC packet. ∀ `Payload` included in the `packet.data`, which may refer to a different application, the application specific callbacks are retrieved from the IBC router and the `onSendPacket` is the then triggered on the specified application. The `onSendPacket` executes the application logic. Once all payloads contained in the `packet.data` have been acted upon, the packet commitment is generated and the sequence number bound to the `channelSourceId` is incremented. +The `sendPacket` function is called by the IBC handler when an IBC packet is submitted to the newtwork in order to send *data* in the form of an IBC packet. The `sendPacket` function executes the IBC core logic and atomically triggers the application logic execution via the activation of the `onSendPacket` callback. Indeed ∀ `Payload` included in the `packet.data`, which refers to a specific application, the callbacks are retrieved from the IBC router and the `onSendPacket` is the then triggered on the application specified in the `payload` content. Once all payloads contained in the `packet.data` have been processed, the packet commitment is generated and the sequence number bound to the `channelSourceId` is incremented. The `sendPacket` core function MUST execute the applications logic atomically triggering the `onSendPacket` callback ∀ application contained in the `packet.data` payload. @@ -613,7 +613,7 @@ function sendPacket( return sequence } ``` - + ##### Receiving packets The `recvPacket` function is called by the IBC handler in order to receive an IBC packet sent on the corresponding client on the counterparty chain. @@ -628,8 +628,6 @@ The IBC handler performs the following steps in order: - Sets a store path to indicate that the packet has been received - If the flows supports synchronous acknowledgement, it writes the acknowledgement into the receiver provableStore. ->**Note:** We pass the address of the `relayer` that signed and submitted the packet to enable a module to optionally provide some rewards. This provides a foundation for fee payment, but can be used for other techniques as well (like calculating a leaderboard). - ###### Execution requirements and outcomes Pre-conditions: @@ -647,7 +645,9 @@ Pre-conditions: ###### Pseudo-Code The ICS-04 provides an example pseudo-code that enforce the above described conditions so that the following sequence of steps SHOULD occur for a packet to be received from module *1* on machine *A* to module *2* on machine *B*. - + +>**Note:** We pass the address of the `relayer` that signed and submitted the packet to enable a module to optionally provide some rewards. This provides a foundation for fee payment, but can be used for other techniques as well (like calculating a leaderboard). + ```typescript function recvPacket( packet: OpaquePacket, From 147645deb09c899d876c1454062664a91c2cce17 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Mon, 14 Oct 2024 16:09:51 +0200 Subject: [PATCH 58/71] minor fixes --- spec/core/v2/ics-004-packet-semantics/README.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-packet-semantics/README.md index ea8f8e9f7..f4137aa32 100644 --- a/spec/core/v2/ics-004-packet-semantics/README.md +++ b/spec/core/v2/ics-004-packet-semantics/README.md @@ -16,7 +16,6 @@ TODO : - Rename file - Improve conditions set / think about more condition an proper presentation - Review Ack and Timeout carefully -- FROM Race condition UP to END ## Synopsis @@ -158,7 +157,7 @@ Additionally, the ICS-04 specification defines a set of conditions that the impl #### Permissioning -- Channels should be permissioned to the application registered on the local router. Thus only the modules registered on the local router, and so associated with the channel, should be able to send or receive on it. +- Channels should be permissioned to the application registered on the local router. Thus only the modules registered on the local router should be able to send or receive on it. #### Fungibility conservation @@ -929,8 +928,9 @@ function timeoutPacket( ) { // Channel and Client Checks channel = getChannel(packet.channelSourceId) - client = router.clients[channel.clientId] + assert(client !== null) + client = router.clients[channel.clientId] assert(client !== null) // verify we sent the packet and haven't cleared it out yet @@ -945,7 +945,7 @@ function timeoutPacket( asert(packet.timeoutTimestamp > 0 && proofTimestamp >= packet.timeoutTimestamp) // verify there is no packet receipt --> receivePacket has not been called - receiptPath = packetReceiptPath(packet.channelDestId, packet.sequence,relayer) + receiptPath = packetReceiptPath(packet.channelDestId, packet.sequence) merklePath = applyPrefix(channel.keyPrefix, receiptPath) assert(client.verifyNonMembership( client.clientState, @@ -959,7 +959,7 @@ function timeoutPacket( success=cbs.OnTimeoutPacket(packet.channelSourceId,payload) // Note that payload includes the version. The application is required to inspect the version to route the data to the proper callback abortUnless(success) - channelStore.delete(packetCommitmentPath(packet.channelSourceId, packet.sequence, relayer)) + channelStore.delete(packetCommitmentPath(packet.channelSourceId, packet.sequence)) // Event Emission // See fields emitLogEntry("timeoutPacket", { @@ -979,8 +979,6 @@ Packets MUST be acknowledged or timed-out in order to be cleaned-up. #### Reasoning about race conditions -TODO - ##### Timeouts / packet confirmation There is no race condition between a packet timeout and packet confirmation, as the packet will either have passed the timeout height prior to receipt or not. From 0dafbe9b008d6e8810154f7cb389d508e7765dc5 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Mon, 14 Oct 2024 16:16:25 +0200 Subject: [PATCH 59/71] rename folder --- .../README.md | 0 .../setup_final_state.png | Bin .../Sketch_Happy_Path.png | Bin 164076 -> 0 bytes 3 files changed, 0 insertions(+), 0 deletions(-) rename spec/core/v2/{ics-004-packet-semantics => ics-004-channel-and-packet-semantics}/README.md (100%) rename spec/core/v2/{ics-004-packet-semantics => ics-004-channel-and-packet-semantics}/setup_final_state.png (100%) delete mode 100644 spec/core/v2/ics-004-packet-semantics/Sketch_Happy_Path.png diff --git a/spec/core/v2/ics-004-packet-semantics/README.md b/spec/core/v2/ics-004-channel-and-packet-semantics/README.md similarity index 100% rename from spec/core/v2/ics-004-packet-semantics/README.md rename to spec/core/v2/ics-004-channel-and-packet-semantics/README.md diff --git a/spec/core/v2/ics-004-packet-semantics/setup_final_state.png b/spec/core/v2/ics-004-channel-and-packet-semantics/setup_final_state.png similarity index 100% rename from spec/core/v2/ics-004-packet-semantics/setup_final_state.png rename to spec/core/v2/ics-004-channel-and-packet-semantics/setup_final_state.png diff --git a/spec/core/v2/ics-004-packet-semantics/Sketch_Happy_Path.png b/spec/core/v2/ics-004-packet-semantics/Sketch_Happy_Path.png deleted file mode 100644 index cfc059c458c0075aae4fc649246d98e032eb1ff3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 164076 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

5kle%Bc@MD^%kgZA`y9Jo`%{>b!RshkmAkWb8 zlDVTaM3ljjYqXYBQggKrJ-YU2$p4|9jgYlQpwQSbk>28DcV2@(O~>Q1J4KxZZ_2P~ ziXw~Wypqe|9p^4lB4JK=yylk%I5(t(KX5job3IZ;Px&}l{3tmjtbtQ9H2Vcn4NG5`pze&gZ4Ph0Us4y^HE-^sK_xe*!e*vwM$D&^?F@T{?aQhnb*|TV1qK< zb7ZQ+6Vn5!r^TMf&k*3DSx-!4pStVo%?_yrDz#e>(?L(r_PcX-@`=w-^_Ex=YCDU^ zohPt2_#eU?-;~Z?1N@pcaSY2ViG)}FcpvmtC5M)#W*t25$LiSKAjdPT`mA9bGMoCc z0}Yed2LJfb=Bu13q+a?0~QIU5#H*LSYA41p5G7E zB3vzTrk8dByCX*+JXN1K2Eb+KQyS6szPPht1?il}d=Fa( zj;6)agMKrY=u?~5j1eR17kuMj!zlP_*fZ6_Q{(77w6_Fbi_ve1#wRo4-!r$BhRE_O zfZ63~DRX;b1TYeM*dp2t_t9KTeNKoyH1T+GsF=;&m9KBuI+o}z#whyj>0LmstHwzk zO}$s}#wYF?h3XLu6AiP`3Wq7HtFu_3H}cNtlMyaCT$0$Tg6u2l^#Ssa&JRN?*E9u-$@_L{O zov$P@)K#vkT7rH-71?AN{+t2#(&w|1#jShv@ukN)j#Wjw`LVVRCbT|Xg!&&MA^wk_ z*cMh&3bYkZIF5Z`E_-gwMC3+YLA^n5_m^qOD}~_4q&1P3ijQ6CCP?bzIHP_)Gu1+F zw|74&USX?F@{?pHQ$0~T-o~GoC!gGYX3D`1C;N4`*jH~sbn#|qoGLAzI! z$ze@u&J$Z-Nz2&x)rMi~b=+b#gpf|rNo-GKAI;LJQmr_>=9x`ln4RTm?|P7pBFOey zmSWYJ^Mh-7WutE8^kHw4$L^CnV`4Xe;Qjx8Rtl<|w(Gphe>>>OjOxT)R;N!OBjxH% zccv>3haSzgik1+5)1cwEN^De}i{N*S7uMrR>(Y<4`lO)3x3F@_*GP~*C$_%Yt6(

JFJ{9GA?jbAJfu)t%c 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 From 57bc98cc47f33fbc8ad16df298e1d73926d1c58e Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Tue, 15 Oct 2024 12:28:16 +0200 Subject: [PATCH 60/71] polish --- .../README.md | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/spec/core/v2/ics-004-channel-and-packet-semantics/README.md b/spec/core/v2/ics-004-channel-and-packet-semantics/README.md index f4137aa32..26b055114 100644 --- a/spec/core/v2/ics-004-channel-and-packet-semantics/README.md +++ b/spec/core/v2/ics-004-channel-and-packet-semantics/README.md @@ -7,16 +7,10 @@ kind: instantiation requires: 2, 24, packet-data version compatibility: ibc-go v10.0.0 author: Stefano Angieri , Aditya Sripal -created: 2019-03-07 -modified: 2019-08-25 +created: 2024-10-15 +modified: 2024-10-15 --- -TODO : - -- Rename file -- Improve conditions set / think about more condition an proper presentation -- Review Ack and Timeout carefully - ## Synopsis This standard defines the channel and packet semantics necessary for state machines implementing the Inter-Blockchain Communication (IBC) protocol version 2 to enable secure, verifiable, and efficient cross-chain messaging. @@ -113,7 +107,7 @@ An application may not need to return an acknowledgment after processing relevan If the receiver chain returns the `SENTINEL_ACKNOWLEDGMENT`, the sender chain will execute the `acknowledgePacket` handler without triggering the `onAcknowledgePacket` callback. -As we will see later, the presence in the provable store of the acknowledgement is a prerequisite for executing the `acknowledgePacket` handler. If the receiver chain does not write the acknowledgement, will be impossible for the sender chain to execute `acknowledgePacket` to delete the packet commitment. +As we will see later, the presence in the provable store of the acknowledgement is a prerequisite for executing the `acknowledgePacket` handler. If the receiver chain does not write the acknowledgement, will be impossible for the sender chain to execute `acknowledgePacket` and delete the packet commitment. > **Example**: In the multi-data packet world, if a packet within 3 payloads intended for 3 different application is sent out, the expectation is that each payload is processed in the same order in which it was placed in the packet. Similarly, the `appAcknowledgement` array is expected to be populated within the same order. @@ -134,7 +128,7 @@ The registration of the client in the local `IBCRouter` is responsibility of the - The `MAX_TIMEOUT_DELTA` is intendend as the max, absolute, difference between `currentTimestamp` and `timeoutTimestamp` that can be given in input to `sendPacket`. ```typescript -const MAX_TIMEOUT_DELTA = Implementation specific // We recommend MAX_TIMEOUT_DELTA = TDB +const MAX_TIMEOUT_DELTA = Implementation specific // We recommend MAX_TIMEOUT_DELTA = 24h ``` Additionally, the ICS-04 specification defines a set of conditions that the implementations of the IBC protocol version 2 MUST adhere to. These conditions ensure the proper execution of the function handlers by establishing requirements before execution `pre-conditions`, the conditions that MUST trigger errors during execution `error-conditions`, expected outcomes after succesful execution `post-conditions-on-success`, and expected outcomes after error execution `post-conditions-on-error`. @@ -374,10 +368,6 @@ function registerChannel( abortTransactionUnless(msg.signer()===channelCreator[channelId]) // Channel manipulation - /* NEED DISCUSSION : Do we want to allow multiple call to this function? - If not than we have to impose the check - abortTransactionUnless(channel.counterpartyChannelId===null) - */ channel.counterpartyChannelId=counterpartyChannelId // Local Store @@ -1029,7 +1019,7 @@ Future updates of this specification will enable the atomic processing of multip ## History -Oct X, 2024 - [Draft submitted](https://github.com/cosmos/ibc/pull/1148) +Oct 15, 2024 - [Draft submitted](https://github.com/cosmos/ibc/pull/1148) ## Copyright From 6d0aaaa7790c310497cda4e8e939c257438b5ad5 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Tue, 15 Oct 2024 14:10:00 +0200 Subject: [PATCH 61/71] change registerChannel to registerCounterparty --- .../README.md | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/spec/core/v2/ics-004-channel-and-packet-semantics/README.md b/spec/core/v2/ics-004-channel-and-packet-semantics/README.md index 26b055114..6489dd3ba 100644 --- a/spec/core/v2/ics-004-channel-and-packet-semantics/README.md +++ b/spec/core/v2/ics-004-channel-and-packet-semantics/README.md @@ -213,7 +213,7 @@ function getChannel(channelId: bytes): Channel { #### Setup -In order to ensure valid communication, each IBC chain MUST be able to identify its counterparty. While a client can prove any key/value path on the counterparty, knowing which identifier the counterparty uses when it sends messages to us is essential to prevent confusion between messages intended for different chains. Thus, to achieve mutual and verifiable identification, IBC version 2 introduces the `createChannel` and `registerChannel` procedures. Below the ICS-04 defines the setup process that ensures that both chains recognize and agree on a mutually identified channel that will facilitate packet transmission. +In order to ensure valid communication, each IBC chain MUST be able to identify its counterparty. While a client can prove any key/value path on the counterparty, knowing which identifier the counterparty uses when it sends messages to us is essential to prevent confusion between messages intended for different chains. Thus, to achieve mutual and verifiable identification, IBC version 2 introduces the `createChannel` and `registerCounterparty` procedures. Below the ICS-04 defines the setup process that ensures that both chains recognize and agree on a mutually identified channel that will facilitate packet transmission. To start the secure packet stream between the chains, chain `A` and chain `B` MUST execute the setup following this set of procedures: @@ -224,7 +224,7 @@ To start the secure packet stream between the chains, chain `A` and chain `B` MU The relayer is required to execute `createClient` (as defined in ICS-02) before calling `createChannel`, since the `clientId`, input parameter for `createChannel`, MUST be known at execution time. Eventually, the `createClient` message can be bundled with the `createChannel` message in a single multiMsgTx. -Calling indipendently `createClient`, `createChannel` and `registerChannel` result in a three step setup process. +Calling indipendently `createClient`, `createChannel` and `registerCounterparty` result in a three step setup process. Bundling `createClient` and `createChannel` into a single operation simplifies this process and reduces the number of interactions required between the relayer and the chains to two. The setup procedure is a prerequisite for starting the packet stream. If any of the steps has been missed, this would trigger an error during the packet handlers execution. Below we provide the setup sequence diagrams. @@ -241,8 +241,8 @@ sequenceDiagram Chain A ->> Relayer : clientId= x , channelId = y Relayer ->> Chain B : createClient(A chain) + createChannel Chain B ->> Relayer : clientId= z , channelId = w - Relayer ->> Chain A : registerChannel(channelId = y, counterpartyChannelId = w) - Relayer ->> Chain B : registerChannel(channelId = w, counterpartyChannelId = y) + Relayer ->> Chain A : registerCounterparty(channelId = y, counterpartyChannelId = w) + Relayer ->> Chain B : registerCounterparty(channelId = w, counterpartyChannelId = y) ``` ```mermaid @@ -259,8 +259,8 @@ sequenceDiagram Chain A ->> Relayer : channelId = y Relayer ->> Chain B : createChannel(z) Chain B ->> Relayer : channelId = w - Relayer ->> Chain A : registerChannel(channelId = y, counterpartyChannelId = w) - Relayer ->> Chain B : registerChannel(channelId = w, counterpartyChannelId = y) + Relayer ->> Chain A : registerCounterparty(channelId = y, counterpartyChannelId = w) + Relayer ->> Chain B : registerCounterparty(channelId = w, counterpartyChannelId = y) ``` After completing the two- or three-step setup, the system should end up in a similar state. @@ -269,7 +269,7 @@ After completing the two- or three-step setup, the system should end up in a sim Once two chains have set up clients, created channel and registered channels for each other with specific identifiers, they can send IBC packets using the packet interface defined before and the packet handlers that the ICS-04 defines below. The packets will be addressed **directly** with the channels that have semantic link to the underlying counterparty light clients. Thus there are **no** more handshakes necessary. Instead the packet sender must be capable of providing the correct **** pair. If the setup has been executed correctly, then the correctness and soundness properties of IBC holds and the IBC packet flow is guaranteed to succeed. If a user sends a packet with the wrong destination channel it will be impossible for the intended destination to correctly verify the packet, and the packet will simply time out. -While the above mentioned `createClient` procedure is defined by [ICS-2](../ics-002-client-semantics/README.md), the ICS-04 defines below the `createChannel` and `registerChannel` procedures. +While the above mentioned `createClient` procedure is defined by [ICS-2](../ics-002-client-semantics/README.md), the ICS-04 defines below the `createChannel` and `registerCounterparty` procedures. ##### Channel creation @@ -333,13 +333,13 @@ function createChannel( ##### Channel registration and counterparty idenfitifcation -IBC version 2 introduces a `registerChannel` procedure. The channel registration procedure ensures both chains have a mutually recognized channel that facilitates the packet transmission. +IBC version 2 introduces a `registerCounterparty` procedure. The channel registration procedure ensures both chains have a mutually recognized channel that facilitates the packet transmission. This process stores the `counterpartyChannelId` in the local channel structure, ensuring both chains have mirrored **** pairs. With the correct registration, the unique clients on each side provide an authenticated stream of packet data. Social consensus outside the protocol is relied upon to ensure only valid **** pairs are used, representing connections between the correct chains. Pre-conditions: -- The `createChannel` has been previously executed such that the `channelId` that will be provided in input to `registerChannel` exist and it's valid. +- The `createChannel` has been previously executed such that the `channelId` that will be provided in input to `registerCounterparty` exist and it's valid. ###### Execution requirements and outcomes @@ -352,7 +352,7 @@ Pre-conditions: ###### Pseudo-Code ```typescript -function registerChannel( +function registerCounterparty( channelId: bytes, // local chain channel identifier counterpartyChannelId: bytes, // the counterparty's channel identifier ) { @@ -375,7 +375,7 @@ function registerChannel( // log that a packet can be safely sent // Event Emission - emitLogEntry("registerChannel", { + emitLogEntry("registerCounterparty", { channelId: channelId, channel: channel, creatorAddress: msg.signer(), @@ -383,7 +383,7 @@ function registerChannel( } ``` -The protocol uses as an authentication mechanisms checking that the `registerChannel` message is sent by the same relayer that initialized the client such that the `msg.signer()==channelCreator[channelId]`. This would make the client and channel parameters completely initialized by the relayer. Thus, users must verify that the client is pointing to the correct chain and that the counterparty identifier is correct as well before using the pair. +The protocol uses as an authentication mechanisms checking that the `registerCounterparty` message is sent by the same relayer that initialized the client such that the `msg.signer()==channelCreator[channelId]`. This would make the client and channel parameters completely initialized by the relayer. Thus, users must verify that the client is pointing to the correct chain and that the counterparty identifier is correct as well before using the pair. #### Packet Flow Function Handlers From 119a71935d63e6a9cf6a56825c9da8e267081df4 Mon Sep 17 00:00:00 2001 From: sangier <45793271+sangier@users.noreply.github.com> Date: Wed, 23 Oct 2024 15:53:32 +0200 Subject: [PATCH 62/71] Apply suggestions from code review Co-authored-by: Aditya <14364734+AdityaSripal@users.noreply.github.com> --- .../v2/ics-004-channel-and-packet-semantics/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/core/v2/ics-004-channel-and-packet-semantics/README.md b/spec/core/v2/ics-004-channel-and-packet-semantics/README.md index 6489dd3ba..1058e86f0 100644 --- a/spec/core/v2/ics-004-channel-and-packet-semantics/README.md +++ b/spec/core/v2/ics-004-channel-and-packet-semantics/README.md @@ -13,11 +13,11 @@ modified: 2024-10-15 ## Synopsis -This standard defines the channel and packet semantics necessary for state machines implementing the Inter-Blockchain Communication (IBC) protocol version 2 to enable secure, verifiable, and efficient cross-chain messaging. +This standard defines the channel and packet semantics necessary for state machines implementing the Inter-Blockchain Communication (IBC) protocol version 2 to enable secure, verifiable, and efficient cross-chain messaging. It specifies the mechanisms to create channels and register them between two distinct state machines (blockchains) where the channels have a semantic link between the chains and their counterparty light client representation, ensuring that both chains can process and verify packets exchanged between them. -The standard then details the processes for transmitting, receiving, acknowledging, and timing out data packets. The packet-flow semantics guarantee exactly-once packet delivery between chains, utilizing on-chain light clients for state verification and providing efficient routing of packet data to specific IBC applications. +The standard then details the processes for sending, receiving, acknowledging, and timing out data packets. The packet-flow semantics guarantee exactly-once packet delivery between chains, utilizing on-chain light clients for state verification and providing efficient routing of packet data to specific IBC applications. ### Motivation @@ -515,7 +515,7 @@ The IBC handler performs the following steps in order: - Checks that the underlying clients is valid. - Checks that the timeout specified has not already passed on the destination chain - Executes the `onSendPacket` ∀ Payload included in the packet. -- Stores a constant-size commitment to the packet data & packet timeout +- Stores a constant-size commitment of the packet - Increments the send sequence counter associated with the channel - Returns the sequence number of the sent packet @@ -932,7 +932,7 @@ function timeoutPacket( assert(err != nil) // check that timeout height or timeout timestamp has passed on the other end - asert(packet.timeoutTimestamp > 0 && proofTimestamp >= packet.timeoutTimestamp) + assert(packet.timeoutTimestamp > 0 && proofTimestamp >= packet.timeoutTimestamp) // verify there is no packet receipt --> receivePacket has not been called receiptPath = packetReceiptPath(packet.channelDestId, packet.sequence) From e14a470bf35afb5475cd5e8f0868337685032233 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Wed, 23 Oct 2024 16:49:46 +0200 Subject: [PATCH 63/71] address review comments --- .../README.md | 52 ++++++++++--------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/spec/core/v2/ics-004-channel-and-packet-semantics/README.md b/spec/core/v2/ics-004-channel-and-packet-semantics/README.md index 1058e86f0..c6e19d2d1 100644 --- a/spec/core/v2/ics-004-channel-and-packet-semantics/README.md +++ b/spec/core/v2/ics-004-channel-and-packet-semantics/README.md @@ -53,7 +53,7 @@ interface Packet { destChannelId: bytes, // channel identifier on the dest chain. sequence: uint64, // number that corresponds to the order of sent packets. timeout: uint64, // indicates the UNIX timestamp in seconds and is encoded in LittleEndian. It must be passed on the destination chain and once elapsed, will no longer allow the packet processing, and will instead generate a time-out. - data: [Payload] // data + data: Payload[] // data } ``` @@ -81,12 +81,6 @@ Note that a `Packet` is never directly serialised. Rather it is an intermediary When the array of payloads, passed-in the packet, is populated with multiple values, the system will handle the packet as a multi-data packet. The multi-data packet handling logic is out of the scope of the current version of this spec. -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. - -```typescript -type OpaquePacket = object -``` - 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 `receivePacket`. ```typescript @@ -99,7 +93,7 @@ The `Acknowledgement` is a particular interface defined as follows: ```typescript interface Acknowledgement { - appAcknowledgement: [bytes] // array of bytes. Each element of the array contains an acknowledgement from a specific application + appAcknowledgement: byte[][] // array of an array of bytes. Each element of the array contains an acknowledgement from a specific application } ``` @@ -115,7 +109,7 @@ As we will see later, the presence in the provable store of the acknowledgement ```typescript type IBCRouter struct { - callbacks: portId -> [Callback] + callbacks: portId -> Callback[] clients: clientId -> Client // The IBCRouter stores the client under the clientId key } ``` @@ -398,10 +392,9 @@ Note that the execution of the four handlers, upon a unique packet, cannot be co Given a scenario where we are sending a packet from a sender chain `A` to a receiver chain `B` the protocol follows the following rules: -- Sender `A` can call either {`sendPacket`,`acknowledgePacket`,`timeoutPacket`} -- Receiver `B` can call only {`receivePacket`} +- Sender `A` can only call `sendPacket` to start the packet flow. - Receiver `B` can only execute the `receivePacket` if `sendPacket` has been executed by sender `A` -- Sender `A` can only execute `timeoutPacket` if `sendPacket` has been executed by sender `A` and `receivePacket` has not been executed by receiver `B`. +- Sender `A` can only execute `timeoutPacket` if Sender `A` has previously executed `sendPacket` and `receivePacket` has not been executed by receiver `B`. - Sender `A` can only execute `acknowledgePacket` if `sendPacket` has been executed by sender `A`, `receivePacket` has been executed by receiver `B`, `writeAcknowledgePacket` has been executed by receiver `B`. Below we provide the three possible example scenarios described with sequence diagrams. @@ -542,7 +535,7 @@ The ICS04 provides an example pseudo-code that enforce the above described condi function sendPacket( sourceChannelId: bytes, timeoutTimestamp: uint64, - payloads: []byte + payloads: Payload[] ) : BigEndianUint64 { // Setup checks - channel and client @@ -551,6 +544,8 @@ function sendPacket( client = router.clients[channel.clientId] assert(client !== null) + //assert(packet.sourceId == channel.counterpartyChannelId) This should be always true, redundant // NEED DISCUSSION + // timeoutTimestamp checks // disallow packets with a zero timeoutTimestamp assert(timeoutTimestamp !== 0) @@ -627,10 +622,10 @@ Pre-conditions: | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|-----------------------------------------------|-----------------------------------------------| -| **Error-Conditions** | 1. invalid `packetCommitment`, 2.`packetReceipt` already exists
3. Invalid timeoutTimestamp
4. Unsuccessful payload execution. | 1.1 `verifyMembership(packetCommitment)==false`
1.2 `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))!=null`
3. `timeoutTimestamp === 0`
3.1 `currentTimestamp() < packet.timeoutTimestamp)`
4. `onReceivePacket(..)==False` | +| **Error-Conditions** | 1. invalid `packetCommitment`, 2.`packetReceipt` already exists
3. Invalid timeoutTimestamp
4. Unsuccessful payload execution.
5. Unexpected counterparty channel id | 1.1 `verifyMembership(packetCommitment)==false`
1.2 `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))!=null`
3. `timeoutTimestamp === 0`
3.1 `currentTimestamp() < packet.timeoutTimestamp)`
4. `onReceivePacket(..)==False`
5. `packet.sourceChannelId != channel.counterpartyChannelId` | | **Post-Conditions (Success)** | 1. `onReceivePacket` is executed and the application state is modified
2. The `packetReceipt` is written
3. Event is Emitted
| 1. `onReceivePacket(..)==True; app.State(beforeReceivePacket)!=app.State(afterReceivePacket)`
2. `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))!=null`
3. Check Event Emission
| | **Post-Conditions (Error)** | 1. if `onReceivePacket` fails the application state is unchanged
2. `packetReceipt is not written`

3. No Event Emission
| 1. `app.State(beforeReceivePacket)==app.State(afterReceivePacket)`
2. `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))==null`
3. Check No Event is Emitted
| - + ###### Pseudo-Code The ICS-04 provides an example pseudo-code that enforce the above described conditions so that the following sequence of steps SHOULD occur for a packet to be received from module *1* on machine *A* to module *2* on machine *B*. @@ -639,7 +634,7 @@ The ICS-04 provides an example pseudo-code that enforce the above described cond ```typescript function recvPacket( - packet: OpaquePacket, + packet: Packet, proof: CommitmentProof, proofHeight: Height, relayer: string @@ -650,6 +645,9 @@ function recvPacket( assert(channel !== null) client = router.clients[channel.clientId] assert(client !== null) + + // Check that counterparty channel id is as expected + assert(packet.sourceChannelId == channel.counterpartyChannelId) // verify timeout assert(packet.timeoutTimestamp === 0) @@ -680,7 +678,7 @@ function recvPacket( // Executes Application logic ∀ Payload payload=packet.data[0] cbs = router.callbacks[payload.destPort] - ack,success = cbs.onReceivePacket(packet.channelDestId,payload,relayer) // Note that payload includes the version. The application is required to inspect the version to route the data to the proper callback + ack,success = cbs.onReceivePacket(packet.channelDestId,payload,relayer,packet.sequence) // Note that payload includes the version. The application is required to inspect the version to route the data to the proper callback abortTransactionUnless(success) if ack != nil { // NOTE: Synchronous ack. @@ -787,7 +785,7 @@ Pre-conditions: | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|---------------------------------|---------------------------------| -| **Error-Conditions** | 1. `packetCommitment` already cleared out
2. Unset Acknowledgment
3. Unsuccessful payload execution. | 1. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null`
2. `verifyMembership(packetacknowledgementPath,...,) == False`
3. `onAcknowledgePacket(packet.channelSourceId,payload, acknowledgement) == False` | +| **Error-Conditions** | 1. `packetCommitment` already cleared out
2. Unset Acknowledgment
3. Unsuccessful payload execution.
4. Unexpected counterparty channel id | 1. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null`
2. `verifyMembership(packetacknowledgementPath,...,) == False`
3. `onAcknowledgePacket(packet.channelSourceId,payload, acknowledgement) == False`
4. `packet.sourceChannelId != channel.counterpartyChannelId` | | **Post-Conditions (Success)** | 1. `onAcknowledgePacket` is executed and the application state is modified
2. `packetCommitment` has been cleared out
4. Event is Emission
| 1. `onAcknowledgePacket(..)==True; app.State(beforeAcknowledgePacket)!=app.State(afterAcknowledgePacket)`
2. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null`,
4. Check Event is Emitted
| | **Post-Conditions (Error)** | 1. If `onAcknowledgePacket` fails the application state is unchanged
2. `packetCommitment` has not been cleared out
3. acknowledgement is stil in store
4. No Event Emission
| 1. `onAcknowledgePacket(..)==False; app.State(beforeAcknowledgePacket)==app.State(afterAcknowledgePacket)`
2. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === commitV2Packet(packet)` 3. `verifyMembership(packetAcknowledgementPath,...,) == True`
4. Check No Event is Emitted
| @@ -799,7 +797,7 @@ The ICS04 provides an example pseudo-code that enforce the above described condi ```typescript function acknowledgePacket( - packet: OpaquePacket, + packet: Packet, acknowledgement: Acknowledgement, proof: CommitmentProof, proofHeight: Height, @@ -811,7 +809,10 @@ function acknowledgePacket( assert(channel !== null) client = router.clients[channel.clientId] assert(client !== null) - + + // Check that counterparty channel id is as expected + assert(packet.sourceChannelId == channel.counterpartyChannelId) + // verify we sent the packet and haven't cleared it out yet assert(provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === commitV2Packet(packet)) @@ -830,7 +831,7 @@ function acknowledgePacket( // Executes Application logic ∀ Payload payload=packet.data[0] cbs = router.callbacks[payload.sourcePort] - success= cbs.OnAcknowledgePacket(packet.channelSourceId,payload,acknowledgement, relayer) // Note that payload includes the version. The application is required to inspect the version to route the data to the proper callback + success= cbs.OnAcknowledgePacket(packet.channelSourceId,payload,packet.sequence,acknowledgement, relayer) // Note that payload includes the version. The application is required to inspect the version to route the data to the proper callback abortUnless(success) } @@ -899,7 +900,7 @@ Pre-conditions: | **Condition Type** | **Description**| **Code Checks**| |-------------------------------|--------------------|--------------------| -| **Error-Conditions** | 1. `packetCommitment` already cleared out
2. `packetReceipt` is not empty
3. Unsuccessful payload execution
4. `timeoutTimestamp` not elapsed on the receiving chain| 1. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null`
2. `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))!=null`
3. `onTimeoutPacket(packet.channelSourceId,payload) == False`
4.1 `packet.timeoutTimestamp > 0`
4.2 `proofTimestamp = client.getTimestampAtHeight(proofHeight); proofTimestamp >= packet.timeoutTimestamp` | +| **Error-Conditions** | 1. `packetCommitment` already cleared out
2. `packetReceipt` is not empty
3. Unsuccessful payload execution
4. `timeoutTimestamp` not elapsed on the receiving chain
5. Unexpected counterparty channel id| 1. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null`
2. `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))!=null`
3. `onTimeoutPacket(packet.channelSourceId,payload) == False`
4.1 `packet.timeoutTimestamp > 0`
4.2 `proofTimestamp = client.getTimestampAtHeight(proofHeight); proofTimestamp >= packet.timeoutTimestamp`
5. `packet.sourceChannelId != channel.counterpartyChannelId` | | **Post-Conditions (Success)** | 1. `onTimeoutPacket` is executed and the application state is modified
2. `packetCommitment` has been cleared out
3. `packetReceipt` is empty
4. Event is Emitted
| 1. `onTimeoutPacket(..)==True; app.State(beforeTimeoutPacket)!=app.State(afterTimeoutPacket)`
2. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null`
3. `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))==null`
4. Check Event is Emitted
| | **Post-Conditions (Error)** | 1. If `onTimeoutPacket` fails and the application state is unchanged
2. `packetCommitment` is not cleared out
3. No Event Emission
| 1. `onTimeoutPacket(..)==True; app.State(beforeTimeoutPacket)!=app.State(afterTimeoutPacket)`
2. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null`
3. Check No Event is Emitted
| @@ -911,7 +912,7 @@ The ICS-04 provides an example pseudo-code that enforce the above described cond ```typescript function timeoutPacket( - packet: OpaquePacket, + packet: Packet, proof: CommitmentProof, proofHeight: Height, relayer: string @@ -923,6 +924,9 @@ function timeoutPacket( client = router.clients[channel.clientId] assert(client !== null) + // Check that counterparty channel id is as expected + assert(packet.sourceChannelId == channel.counterpartyChannelId) + // verify we sent the packet and haven't cleared it out yet assert(provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === commitV2Packet(packet)) @@ -946,7 +950,7 @@ function timeoutPacket( payload=packet.data[0] cbs = router.callbacks[payload.sourcePort] - success=cbs.OnTimeoutPacket(packet.channelSourceId,payload) // Note that payload includes the version. The application is required to inspect the version to route the data to the proper callback + success=cbs.OnTimeoutPacket(packet.channelSourceId,payload,packet.sequence) // Note that payload includes the version. The application is required to inspect the version to route the data to the proper callback abortUnless(success) channelStore.delete(packetCommitmentPath(packet.channelSourceId, packet.sequence)) From d234b5be813f412f8fb47e234ccb1d542f0995a6 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Wed, 23 Oct 2024 16:52:51 +0200 Subject: [PATCH 64/71] rm counterparty channel id check in send packet --- spec/core/v2/ics-004-channel-and-packet-semantics/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/spec/core/v2/ics-004-channel-and-packet-semantics/README.md b/spec/core/v2/ics-004-channel-and-packet-semantics/README.md index c6e19d2d1..4376124e2 100644 --- a/spec/core/v2/ics-004-channel-and-packet-semantics/README.md +++ b/spec/core/v2/ics-004-channel-and-packet-semantics/README.md @@ -544,8 +544,6 @@ function sendPacket( client = router.clients[channel.clientId] assert(client !== null) - //assert(packet.sourceId == channel.counterpartyChannelId) This should be always true, redundant // NEED DISCUSSION - // timeoutTimestamp checks // disallow packets with a zero timeoutTimestamp assert(timeoutTimestamp !== 0) From 98d9151e3477ebedef8847637ddbecc624bd9ea4 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Thu, 31 Oct 2024 15:52:18 +0100 Subject: [PATCH 65/71] mod writeAck function --- .../README.md | 57 +++++++++++++------ 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/spec/core/v2/ics-004-channel-and-packet-semantics/README.md b/spec/core/v2/ics-004-channel-and-packet-semantics/README.md index 4376124e2..7aa183df2 100644 --- a/spec/core/v2/ics-004-channel-and-packet-semantics/README.md +++ b/spec/core/v2/ics-004-channel-and-packet-semantics/README.md @@ -192,15 +192,21 @@ Additionally, the ICS-04 defines the following variables: `nextSequenceSend` , - The `nextSequenceSend` tracks the sequence number for the next packet to be sent for a given source channelId. - The `channelCreator` tracks the channels creator address given the channelId. - The `storedChannels` tracks the channels paired with the other chains. +- The `storedPacket` tracks the full packet for asynchronous ack management. ```typescript type nextSequenceSend : channelId -> BigEndianUint64 type channelCreator : channelId -> address type storedChannels : channelId -> Channel +type storedPacket : (channelId,bigEndianUint64) -> Packet // channelId,sequence --> Packet function getChannel(channelId: bytes): Channel { return storedChannels[channelId] } + +function getPacket(channelId: bytes, sequence: bigEndianUint64): Packet { + return storedPacket[channelId,sequence] +} ``` ### Sub-protocols @@ -536,7 +542,7 @@ function sendPacket( sourceChannelId: bytes, timeoutTimestamp: uint64, payloads: Payload[] - ) : BigEndianUint64 { + ) : bigEndianUint64 { // Setup checks - channel and client channel = getChannel(sourceChannelId) @@ -560,7 +566,7 @@ function sendPacket( // Currently we support only len(payloads)==1 payload=payloads[0] cbs = router.callbacks[payload.sourcePort] - success = cbs.onSendPacket(sourceChannelId,channel.counterpartyChannelId,payload) // Note that payload includes the version. The application is required to inspect the version to route the data to the proper callback + success = cbs.onSendPacket(sourceChannelId,payload) // Note that payload includes the version. The application is required to inspect the version to route the data to the proper callback // IMPORTANT: if the onSendPacket fails, the transaction is aborted and the potential state changes are reverted. // This ensure that the post conditions on error are always respected. // payload execution check @@ -680,11 +686,21 @@ function recvPacket( abortTransactionUnless(success) if ack != nil { // NOTE: Synchronous ack. - writeAcknowledgement(packet, ack) - } + writeAcknowledgement(packet.channelDestId,packet.sequence,ack) + // In case of Synchronous ack we emit the event here as we have all the necessary information, while writeAcknowledgement can only retrieve this in case of asynchronous ack. + emitLogEntry("writeAcknowledgement", { + sequence: packet.sequence, + sourceId: packet.channelSourceId, + destId: packet.channelDestId, + timeoutTimestamp: packet.timeoutTimestamp, + data: packet.data, + ack + }) + }else { // NOTE No ack || Asynchronous ack. - //else: ack is nil and won't be written || ack is nil and will be written asynchronously - + // ack is nil and will be written asynchronously, so we store the full packet in the private store + storedPacket[packet.channelDestId,packet.sequence]=packet + } // Provable Stores // we must set the receipt so it can be verified on the other side // it's the sentinel success receipt: []byte{0x01} @@ -740,30 +756,35 @@ The ICS-04 provides an example pseudo-code that enforce the above described cond ```typescript function writeAcknowledgement( - packet: Packet, + destChannelId: bytes, + sequence: bigEndianUint64, acknowledgement: Acknowledgement) { // acknowledgement must not be empty abortTransactionUnless(len(acknowledgement) !== 0) // cannot already have written the acknowledgement - abortTransactionUnless(provableStore.get(packetAcknowledgementPath(packet.channelDestId, packet.sequence) === null)) + abortTransactionUnless(provableStore.get(packetAcknowledgementPath(destChannelId, sequence) === null)) // create the acknowledgement coomit using the function defined in [packet specification](https://github.com/cosmos/ibc/blob/c7b2e6d5184b5310843719b428923e0c5ee5a026/spec/core/v2/ics-004-packet-semantics/PACKET.md) commit=commitV2Acknowledgment(acknowledgement) provableStore.set( - packetAcknowledgementPath(packet.channelDestId, packet.sequence),commit) + packetAcknowledgementPath(destChannelId, sequence),commit) // log that a packet has been acknowledged - // Event Emission - emitLogEntry("writeAcknowledgement", { - sequence: packet.sequence, - sourceId: packet.channelSourceId, - destId: packet.channelDestId, - timeoutTimestamp: packet.timeoutTimestamp, - data: packet.data, - acknowledgement - }) + // Event Emission + // Note that the event should be emitted by this function only in the asynchrounous ack case. Otherwise the event is emitted during the onReceive + packet=getPacket(destChannelId,sequence) + if(packet!=nil){ + emitLogEntry("writeAcknowledgement", { + sequence: packet.sequence, + sourceId: packet.channelSourceId, + destId: packet.channelDestId, + timeoutTimestamp: packet.timeoutTimestamp, + data: packet.data, + acknowledgement + }) + } } ``` From 69688647df0f8ecb17b15739a4ce81e36a556345 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Thu, 31 Oct 2024 15:54:43 +0100 Subject: [PATCH 66/71] delete packet once async ack has been written --- spec/core/v2/ics-004-channel-and-packet-semantics/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/core/v2/ics-004-channel-and-packet-semantics/README.md b/spec/core/v2/ics-004-channel-and-packet-semantics/README.md index 7aa183df2..020ac9f52 100644 --- a/spec/core/v2/ics-004-channel-and-packet-semantics/README.md +++ b/spec/core/v2/ics-004-channel-and-packet-semantics/README.md @@ -784,6 +784,8 @@ function writeAcknowledgement( data: packet.data, acknowledgement }) + // delete the packet from state + storedPacket[destChannelId,sequence]=nil } } ``` From 84341283c480bcfbd982a58b2d0626255eeaadf1 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Thu, 31 Oct 2024 17:06:16 +0100 Subject: [PATCH 67/71] fix callbacks --- spec/core/v2/ics-004-channel-and-packet-semantics/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/core/v2/ics-004-channel-and-packet-semantics/README.md b/spec/core/v2/ics-004-channel-and-packet-semantics/README.md index 020ac9f52..436998071 100644 --- a/spec/core/v2/ics-004-channel-and-packet-semantics/README.md +++ b/spec/core/v2/ics-004-channel-and-packet-semantics/README.md @@ -682,7 +682,7 @@ function recvPacket( // Executes Application logic ∀ Payload payload=packet.data[0] cbs = router.callbacks[payload.destPort] - ack,success = cbs.onReceivePacket(packet.channelDestId,payload,relayer,packet.sequence) // Note that payload includes the version. The application is required to inspect the version to route the data to the proper callback + ack,success = cbs.onReceivePacket(packet.channelDestId,packet.channelSourceId,packet.sequence,payload,relayer) // Note that payload includes the version. The application is required to inspect the version to route the data to the proper callback abortTransactionUnless(success) if ack != nil { // NOTE: Synchronous ack. @@ -852,7 +852,7 @@ function acknowledgePacket( // Executes Application logic ∀ Payload payload=packet.data[0] cbs = router.callbacks[payload.sourcePort] - success= cbs.OnAcknowledgePacket(packet.channelSourceId,payload,packet.sequence,acknowledgement, relayer) // Note that payload includes the version. The application is required to inspect the version to route the data to the proper callback + success= cbs.OnAcknowledgePacket(packet.channelSourceId,packet.channelDestId,packet.sequence,payload,acknowledgement, relayer) // Note that payload includes the version. The application is required to inspect the version to route the data to the proper callback abortUnless(success) } @@ -971,7 +971,7 @@ function timeoutPacket( payload=packet.data[0] cbs = router.callbacks[payload.sourcePort] - success=cbs.OnTimeoutPacket(packet.channelSourceId,payload,packet.sequence) // Note that payload includes the version. The application is required to inspect the version to route the data to the proper callback + success=cbs.OnTimeoutPacket(packet.channelSourceId,packet.channelDestId,packet.sequence,payload,relayer) // Note that payload includes the version. The application is required to inspect the version to route the data to the proper callback abortUnless(success) channelStore.delete(packetCommitmentPath(packet.channelSourceId, packet.sequence)) From 0879212801989f8391ee698792d835e7e33e823f Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Tue, 5 Nov 2024 15:06:52 +0100 Subject: [PATCH 68/71] fixes --- .../README.md | 181 +++++++++--------- .../setup_final_state.png | Bin 44721 -> 44967 bytes 2 files changed, 95 insertions(+), 86 deletions(-) diff --git a/spec/core/v2/ics-004-channel-and-packet-semantics/README.md b/spec/core/v2/ics-004-channel-and-packet-semantics/README.md index 436998071..114c1587f 100644 --- a/spec/core/v2/ics-004-channel-and-packet-semantics/README.md +++ b/spec/core/v2/ics-004-channel-and-packet-semantics/README.md @@ -195,16 +195,16 @@ Additionally, the ICS-04 defines the following variables: `nextSequenceSend` , - The `storedPacket` tracks the full packet for asynchronous ack management. ```typescript -type nextSequenceSend : channelId -> BigEndianUint64 +type nextSequenceSend : channelId -> uint64 type channelCreator : channelId -> address type storedChannels : channelId -> Channel -type storedPacket : (channelId,bigEndianUint64) -> Packet // channelId,sequence --> Packet +type storedPacket : (channelId,uint64) -> Packet // channelId,sequence --> Packet function getChannel(channelId: bytes): Channel { return storedChannels[channelId] } -function getPacket(channelId: bytes, sequence: bigEndianUint64): Packet { +function getPacket(channelId: bytes, sequence: uint64): Packet { return storedPacket[channelId,sequence] } ``` @@ -234,33 +234,35 @@ The setup procedure is a prerequisite for starting the packet stream. If any of title: Two Step Setup Procedure, createClient and createChannel are bundled together. --- sequenceDiagram - Participant Chain A + Participant IBCModule on Chain A Participant Relayer - Participant Chain B - Relayer ->> Chain A : createClient(B chain) + createChannel - Chain A ->> Relayer : clientId= x , channelId = y - Relayer ->> Chain B : createClient(A chain) + createChannel - Chain B ->> Relayer : clientId= z , channelId = w - Relayer ->> Chain A : registerCounterparty(channelId = y, counterpartyChannelId = w) - Relayer ->> Chain B : registerCounterparty(channelId = w, counterpartyChannelId = y) + Participant IBCModule on Chain B + Relayer ->> IBCModule on Chain A : createClient(B chain) + createChannel + IBCModule on Chain A ->> Relayer : clientId= x , channelId = y + Relayer ->> IBCModule on Chain B : createClient(A chain) + createChannel + IBCModule on Chain B ->> Relayer : clientId= z , channelId = w + Relayer ->> IBCModule on Chain A : registerCounterparty(channelId = y, counterpartyChannelId = w) + Relayer ->> IBCModule on Chain B : registerCounterparty(channelId = w, counterpartyChannelId = y) ``` - + ```mermaid --- title: Three Step Setup Procedure, createClient has been previosly executed. --- sequenceDiagram Participant B Light Client as B Light Client with clientId=x - Participant Chain A + Participant IBCModule on Chain A Participant Relayer - Participant Chain B - Participant A Light Client as A Light Client with clientId=z - Relayer ->> Chain A : createChannel(x) - Chain A ->> Relayer : channelId = y - Relayer ->> Chain B : createChannel(z) - Chain B ->> Relayer : channelId = w - Relayer ->> Chain A : registerCounterparty(channelId = y, counterpartyChannelId = w) - Relayer ->> Chain B : registerCounterparty(channelId = w, counterpartyChannelId = y) + Participant IBCModule on Chain B + Participant A Light Client as A Light Client with clientId=z + Note over IBCModule on Chain A, B Light Client: Chain A State + Note over IBCModule on Chain B, A Light Client: Chain B State + Relayer ->> IBCModule on Chain A : createChannel(x) + IBCModule on Chain A ->> Relayer : channelId = y + Relayer ->> IBCModule on Chain B : createChannel(z) + IBCModule on Chain B ->> Relayer : channelId = w + Relayer ->> IBCModule on Chain A : registerCounterparty(channelId = y, counterpartyChannelId = w) + Relayer ->> IBCModule on Chain B : registerCounterparty(channelId = w, counterpartyChannelId = y) ``` After completing the two- or three-step setup, the system should end up in a similar state. @@ -412,32 +414,34 @@ Scenario execution with synchronous acknowledgement `A` to `B` - set of actions: ```mermaid sequenceDiagram participant B Light Client - participant Chain A + participant IBCModule on Chain A participant Relayer - participant Chain B + participant IBCModule on Chain B participant A Light Client - Note over Chain A: start send packet execution - Chain A ->> Chain A : sendPacket - Chain A --> Chain A : app execution - Chain A --> Chain A : packetCommitment - Note over Chain A: end send packet execution - Relayer ->> Chain B: relayPacket - Note over Chain B: start receive packet execution - Chain B ->> Chain B: receivePacket - Chain B -->> A Light Client: verifyMembership(packetCommitment) - Chain B --> Chain B : app execution - Note over Chain B: start sync ack writing - Chain B --> Chain B: writeAck - Note over Chain B: end async ack writing - Chain B --> Chain B: writePacketReceipt - Note over Chain B: end receive packet execution - Relayer ->> Chain A: relayAck - Note over Chain A: start acknowldge packet execution - Chain A ->> Chain A : acknowldgePacket - Chain A -->> B Light Client: verifyMembership(packetAck) - Chain A --> Chain A : app execution - Chain A --> Chain A : Delete packetCommitment - Note over Chain A: end acknowldge packet execution + Note over IBCModule on Chain A, B Light Client: Chain A State + Note over IBCModule on Chain B, A Light Client: Chain B State + Note over IBCModule on Chain A: start send packet execution + IBCModule on Chain A ->> IBCModule on Chain A : sendPacket + IBCModule on Chain A --> IBCModule on Chain A : app execution + IBCModule on Chain A --> IBCModule on Chain A : packetCommitment + Note over IBCModule on Chain A: end send packet execution + Relayer ->> IBCModule on Chain B: relayPacket + Note over IBCModule on Chain B: start receive packet execution + IBCModule on Chain B ->> IBCModule on Chain B: receivePacket + IBCModule on Chain B -->> A Light Client: verifyMembership(packetCommitment) + IBCModule on Chain B --> IBCModule on Chain B : app execution + Note over IBCModule on Chain B: start sync ack writing + IBCModule on Chain B --> IBCModule on Chain B: writeAck + Note over IBCModule on Chain B: end async ack writing + IBCModule on Chain B --> IBCModule on Chain B: writePacketReceipt + Note over IBCModule on Chain B: end receive packet execution + Relayer ->> IBCModule on Chain A: relayAck + Note over IBCModule on Chain A: start acknowldge packet execution + IBCModule on Chain A ->> IBCModule on Chain A : acknowldgePacket + IBCModule on Chain A -->> IBCModule on B Light Client: verifyMembership(packetAck) + IBCModule on Chain A --> IBCModule on Chain A : app execution + IBCModule on Chain A --> IBCModule on Chain A : Delete packetCommitment + Note over IBCModule on Chain A: end acknowldge packet execution ``` @@ -450,33 +454,35 @@ Note that the key difference with the synchronous scenario is that the `writeAck ```mermaid sequenceDiagram participant B Light Client - participant Chain A + participant IBCModule on Chain A participant Relayer - participant Chain B + participant IBCModule on Chain B participant A Light Client - Note over Chain A: start send packet execution - Chain A ->> Chain A : sendPacket - Chain A --> Chain A : app execution - Chain A --> Chain A : packetCommitment - Note over Chain A: end send packet execution - Relayer ->> Chain B: relayPacket - Note over Chain B: start receive packet execution - Chain B ->> Chain B: receivePacket - Chain B -->> A Light Client: verifyMembership(packetCommitment) - Chain B --> Chain B : app execution - Chain B --> Chain B: writePacketReceipt - Note over Chain B: end receive packet execution - Note over Chain B: start async ack writing - Chain B --> Chain B : app execution - async ack processing - Chain B --> Chain B: writeAck - Note over Chain B: end async ack writing - Relayer ->> Chain A: relayAck - Note over Chain A: start acknowldge packet execution - Chain A ->> Chain A : acknowldgePacket - Chain A -->> B Light Client: verifyMembership(packetAck) - Chain A --> Chain A : app execution - Chain A --> Chain A : Delete packetCommitment - Note over Chain A: end acknowldge packet execution + Note over IBCModule on Chain A, B Light Client: Chain A State + Note over IBCModule on Chain B, A Light Client: Chain B State + Note over IBCModule on Chain A: start send packet execution + IBCModule on Chain A ->> IBCModule on Chain A : sendPacket + IBCModule on Chain A --> IBCModule on Chain A : app execution + IBCModule on Chain A --> IBCModule on Chain A : packetCommitment + Note over IBCModule on Chain A: end send packet execution + Relayer ->> IBCModule on Chain B: relayPacket + Note over IBCModule on Chain B: start receive packet execution + IBCModule on Chain B ->> IBCModule on Chain B: receivePacket + IBCModule on Chain B -->> A Light Client: verifyMembership(packetCommitment) + IBCModule on Chain B --> IBCModule on Chain B : app execution + IBCModule on Chain B --> IBCModule on Chain B: writePacketReceipt + Note over IBCModule on Chain B: end receive packet execution + Note over IBCModule on Chain B: start async ack writing + IBCModule on Chain B --> IBCModule on Chain B : app execution - async ack processing + IBCModule on Chain B --> IBCModule on Chain B: writeAck + Note over IBCModule on Chain B: end async ack writing + Relayer ->> IBCModule on Chain A: relayAck + Note over IBCModule on Chain A: start acknowldge packet execution + IBCModule on Chain A ->> IBCModule on Chain A : acknowldgePacket + IBCModule on Chain A -->> B Light Client: verifyMembership(packetAck) + IBCModule on Chain A --> IBCModule on Chain A : app execution + IBCModule on Chain A --> IBCModule on Chain A : Delete packetCommitment + Note over IBCModule on Chain A: end acknowldge packet execution ``` --- @@ -486,20 +492,22 @@ Scenario timeout execution `A` to `B` - set of actions: `A.sendPacket` -> `A.tim ```mermaid sequenceDiagram participant B Light Client - participant Chain A + participant IBCModule on Chain A participant Relayer - participant Chain B + participant IBCModule on Chain B participant A Light Client - Note over Chain A: start send packet execution - Chain A ->> Chain A : sendPacket - Chain A --> Chain A : app execution - Chain A --> Chain A : packetCommitment - Note over Chain A: start timeout packet execution - Chain A ->> Chain A : TimeoutPacket - Chain A -->> B Light Client: verifyNonMembership(PacketReceipt) - Chain A --> Chain A : app execution - Chain A --> Chain A : Delete packetCommitment - Note over Chain A: end timeout packet execution + Note over IBCModule on Chain A, B Light Client: Chain A State + Note over IBCModule on Chain B, A Light Client: Chain B State + Note over IBCModule on Chain A: start send packet execution + IBCModule on Chain A ->> IBCModule on Chain A : sendPacket + IBCModule on Chain A --> IBCModule on Chain A : app execution + IBCModule on Chain A --> IBCModule on Chain A : packetCommitment + Note over IBCModule on Chain A: start timeout packet execution + IBCModule on Chain A ->> IBCModule on Chain A : TimeoutPacket + IBCModule on Chain A -->> B Light Client: verifyNonMembership(PacketReceipt) + IBCModule on Chain A --> IBCModule on Chain A : app execution + IBCModule on Chain A --> IBCModule on Chain A : Delete packetCommitment + Note over IBCModule on Chain A: end timeout packet execution ``` @@ -541,8 +549,9 @@ The ICS04 provides an example pseudo-code that enforce the above described condi function sendPacket( sourceChannelId: bytes, timeoutTimestamp: uint64, - payloads: Payload[] - ) : bigEndianUint64 { + payloads: Payload[], + relayer: address + ) : uint64 { // Setup checks - channel and client channel = getChannel(sourceChannelId) @@ -566,7 +575,7 @@ function sendPacket( // Currently we support only len(payloads)==1 payload=payloads[0] cbs = router.callbacks[payload.sourcePort] - success = cbs.onSendPacket(sourceChannelId,payload) // Note that payload includes the version. The application is required to inspect the version to route the data to the proper callback + success = cbs.onSendPacket(sourceChannelId,payload,relayer) // Note that payload includes the version. The application is required to inspect the version to route the data to the proper callback // IMPORTANT: if the onSendPacket fails, the transaction is aborted and the potential state changes are reverted. // This ensure that the post conditions on error are always respected. // payload execution check @@ -757,7 +766,7 @@ The ICS-04 provides an example pseudo-code that enforce the above described cond ```typescript function writeAcknowledgement( destChannelId: bytes, - sequence: bigEndianUint64, + sequence: uint64, acknowledgement: Acknowledgement) { // acknowledgement must not be empty abortTransactionUnless(len(acknowledgement) !== 0) diff --git a/spec/core/v2/ics-004-channel-and-packet-semantics/setup_final_state.png b/spec/core/v2/ics-004-channel-and-packet-semantics/setup_final_state.png index 2413c8bfa4dc9eff48a05ac8dfef50fd1ace7ade..a7305ab677ff83604bd0ff33c8f43959425a8856 100644 GIT binary patch literal 44967 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 literal 44721 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≧Q%%_;X_HSMp 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 O@YB}RN7te3qy7i++8ww6 From 39fd0a3279c00c6f82f96c954829e7de657f7e57 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Tue, 5 Nov 2024 15:14:11 +0100 Subject: [PATCH 69/71] fix mermaid --- spec/core/v2/ics-004-channel-and-packet-semantics/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/core/v2/ics-004-channel-and-packet-semantics/README.md b/spec/core/v2/ics-004-channel-and-packet-semantics/README.md index 114c1587f..19b6a893f 100644 --- a/spec/core/v2/ics-004-channel-and-packet-semantics/README.md +++ b/spec/core/v2/ics-004-channel-and-packet-semantics/README.md @@ -438,7 +438,7 @@ sequenceDiagram Relayer ->> IBCModule on Chain A: relayAck Note over IBCModule on Chain A: start acknowldge packet execution IBCModule on Chain A ->> IBCModule on Chain A : acknowldgePacket - IBCModule on Chain A -->> IBCModule on B Light Client: verifyMembership(packetAck) + IBCModule on Chain A -->> B Light Client: verifyMembership(packetAck) IBCModule on Chain A --> IBCModule on Chain A : app execution IBCModule on Chain A --> IBCModule on Chain A : Delete packetCommitment Note over IBCModule on Chain A: end acknowldge packet execution @@ -579,7 +579,7 @@ function sendPacket( // IMPORTANT: if the onSendPacket fails, the transaction is aborted and the potential state changes are reverted. // This ensure that the post conditions on error are always respected. // payload execution check - abortUnless(success) + abortTransactionUnless(success) // Construct the packet packet = Packet { From 64a5776e12416e8573015889f6c96d9294c660c6 Mon Sep 17 00:00:00 2001 From: Stefano Angieri Date: Tue, 5 Nov 2024 17:24:30 +0100 Subject: [PATCH 70/71] rm relayer from SendPack --- spec/core/v2/ics-004-channel-and-packet-semantics/README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/spec/core/v2/ics-004-channel-and-packet-semantics/README.md b/spec/core/v2/ics-004-channel-and-packet-semantics/README.md index 19b6a893f..c03bab17a 100644 --- a/spec/core/v2/ics-004-channel-and-packet-semantics/README.md +++ b/spec/core/v2/ics-004-channel-and-packet-semantics/README.md @@ -549,8 +549,7 @@ The ICS04 provides an example pseudo-code that enforce the above described condi function sendPacket( sourceChannelId: bytes, timeoutTimestamp: uint64, - payloads: Payload[], - relayer: address + payloads: Payload[] ) : uint64 { // Setup checks - channel and client @@ -575,7 +574,7 @@ function sendPacket( // Currently we support only len(payloads)==1 payload=payloads[0] cbs = router.callbacks[payload.sourcePort] - success = cbs.onSendPacket(sourceChannelId,payload,relayer) // Note that payload includes the version. The application is required to inspect the version to route the data to the proper callback + success = cbs.onSendPacket(sourceChannelId,payload) // Note that payload includes the version. The application is required to inspect the version to route the data to the proper callback // IMPORTANT: if the onSendPacket fails, the transaction is aborted and the potential state changes are reverted. // This ensure that the post conditions on error are always respected. // payload execution check From 0641ba1269ce31ec273781e12b7de73a1c264290 Mon Sep 17 00:00:00 2001 From: Aditya <14364734+AdityaSripal@users.noreply.github.com> Date: Wed, 6 Nov 2024 19:12:51 +0100 Subject: [PATCH 71/71] Apply suggestions from code review --- spec/core/v2/ics-004-channel-and-packet-semantics/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/core/v2/ics-004-channel-and-packet-semantics/README.md b/spec/core/v2/ics-004-channel-and-packet-semantics/README.md index c03bab17a..b49b3dadf 100644 --- a/spec/core/v2/ics-004-channel-and-packet-semantics/README.md +++ b/spec/core/v2/ics-004-channel-and-packet-semantics/README.md @@ -634,7 +634,7 @@ Pre-conditions: | **Condition Type** | **Description** | **Code Checks** | |-------------------------------|-----------------------------------------------|-----------------------------------------------| -| **Error-Conditions** | 1. invalid `packetCommitment`, 2.`packetReceipt` already exists
3. Invalid timeoutTimestamp
4. Unsuccessful payload execution.
5. Unexpected counterparty channel id | 1.1 `verifyMembership(packetCommitment)==false`
1.2 `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))!=null`
3. `timeoutTimestamp === 0`
3.1 `currentTimestamp() < packet.timeoutTimestamp)`
4. `onReceivePacket(..)==False`
5. `packet.sourceChannelId != channel.counterpartyChannelId` | +| **Error-Conditions** | 1. invalid `packetCommitment`, 2.`packetReceipt` already exists
3. Invalid timeoutTimestamp
4. Unsuccessful payload execution.
5. Unexpected counterparty channel id | 1.1 `verifyMembership(packetCommitment)==false`
1.2 `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))!=null`
3. `timeoutTimestamp === 0`
3.1 `currentTimestamp() > packet.timeoutTimestamp`
4. `onReceivePacket(..)==False`
5. `packet.sourceChannelId != channel.counterpartyChannelId` | | **Post-Conditions (Success)** | 1. `onReceivePacket` is executed and the application state is modified
2. The `packetReceipt` is written
3. Event is Emitted
| 1. `onReceivePacket(..)==True; app.State(beforeReceivePacket)!=app.State(afterReceivePacket)`
2. `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))!=null`
3. Check Event Emission
| | **Post-Conditions (Error)** | 1. if `onReceivePacket` fails the application state is unchanged
2. `packetReceipt is not written`

3. No Event Emission
| 1. `app.State(beforeReceivePacket)==app.State(afterReceivePacket)`
2. `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))==null`
3. Check No Event is Emitted
| @@ -662,7 +662,7 @@ function recvPacket( assert(packet.sourceChannelId == channel.counterpartyChannelId) // verify timeout - assert(packet.timeoutTimestamp === 0) + assert(packet.timeoutTimestamp !== 0) assert(currentTimestamp() < packet.timeoutTimestamp) // verify the packet receipt for this packet does not exist already @@ -931,7 +931,7 @@ Pre-conditions: |-------------------------------|--------------------|--------------------| | **Error-Conditions** | 1. `packetCommitment` already cleared out
2. `packetReceipt` is not empty
3. Unsuccessful payload execution
4. `timeoutTimestamp` not elapsed on the receiving chain
5. Unexpected counterparty channel id| 1. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null`
2. `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))!=null`
3. `onTimeoutPacket(packet.channelSourceId,payload) == False`
4.1 `packet.timeoutTimestamp > 0`
4.2 `proofTimestamp = client.getTimestampAtHeight(proofHeight); proofTimestamp >= packet.timeoutTimestamp`
5. `packet.sourceChannelId != channel.counterpartyChannelId` | | **Post-Conditions (Success)** | 1. `onTimeoutPacket` is executed and the application state is modified
2. `packetCommitment` has been cleared out
3. `packetReceipt` is empty
4. Event is Emitted
| 1. `onTimeoutPacket(..)==True; app.State(beforeTimeoutPacket)!=app.State(afterTimeoutPacket)`
2. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null`
3. `provableStore.get(packetReceiptPath(packet.channelDestId, packet.sequence))==null`
4. Check Event is Emitted
| -| **Post-Conditions (Error)** | 1. If `onTimeoutPacket` fails and the application state is unchanged
2. `packetCommitment` is not cleared out
3. No Event Emission
| 1. `onTimeoutPacket(..)==True; app.State(beforeTimeoutPacket)!=app.State(afterTimeoutPacket)`
2. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null`
3. Check No Event is Emitted
| +| **Post-Conditions (Error)** | 1. If `onTimeoutPacket` fails and the application state is unchanged
2. `packetCommitment` is not cleared out
3. No Event Emission
| 1. `onTimeoutPacket(..)==False; app.State(beforeTimeoutPacket)==app.State(afterTimeoutPacket)`
2. `provableStore.get(packetCommitmentPath(packet.channelSourceId, packet.sequence)) === null`
3. Check No Event is Emitted
| ###### Pseudo-Code