概述

了解 IBC、它的组成部分及其使用场景。

什么是跨链通信协议(IBC)?

本文档旨在为希望针对自定义用例编写自己的跨链通信协议(IBC)应用程序的开发者提供指南。
IBC 应用程序必须编写为自包含模块。
由于 IBC 协议采用模块化设计,IBC 应用开发者无需关注客户端、连接和证明验证等底层细节。 对栈中较低层级的这一简要说明,可以帮助应用开发者从整体上理解 IBC 协议。对应用开发者而言,通道和端口的抽象层细节最为相关,这些内容描述了如何定义自定义数据包以及 IBCModule 回调。 要让你的模块通过 IBC 进行交互,需要满足以下要求:
  • 绑定一个或多个端口。
  • 定义你的数据包数据。
  • 使用 core IBC 提供的默认确认结构体,或根据需要定义自定义确认结构体。
  • 为数据包数据制定标准化编码。
  • 实现 IBCModule 接口。
  • 实现 UpgradableModule 接口(可选)。
继续阅读,了解如何编写自包含 IBC 应用模块的详细说明。

组件概览

客户端

IBC 客户端是链上的轻客户端。每个轻客户端都由唯一的客户端 ID 标识。 IBC 客户端会跟踪其他区块链的共识状态,以及针对客户端共识状态正确验证证明所需的证明规范。一个客户端可以与对手链上的任意数量连接关联。客户端标识符会根据客户端类型和全局客户端计数器自动生成,格式为:{client-type}-{N}。 ClientState 应包含用于验证 IBC 客户端更新和升级所需的链特定信息与轻客户端特定信息。ClientState 可包含链 ID、最新高度、证明规范、解绑期或轻客户端状态等信息。ClientState 不应包含特定于某一高度区块的信息,这属于 ConsensusState 的职责。每个 ConsensusState 都应与唯一区块关联,并通过高度进行引用。IBC 客户端会获得一个以前缀为客户端标识符的存储空间,用于保存其关联的客户端状态、共识状态以及与共识状态相关的任何元数据。共识状态按照其关联高度存储。 支持的 IBC 客户端包括:

IBC 客户端高度

IBC 客户端高度由以下结构体表示:
type Height struct {
    RevisionNumber uint64
  RevisionHeight uint64
}
RevisionNumber 表示该高度所属链的修订版本。 一个修订版本通常表示一个连续且单调递增的区块高度范围。 RevisionHeight 表示该链在给定修订版本内的高度。 每当 RevisionHeight 被重置时,例如 Tendermint 链发生硬分叉时, RevisionNumber 都会递增。这样,IBC 客户端就能够区分链的先前修订版本(修订号为 p)中的区块高度 n,以及链当前修订版本(修订号为 e)中的区块高度 n。 具有相同修订号的 Height,可以通过比较各自的 RevisionHeight 直接进行比较。 修订号不同的 Height,只会根据各自的 RevisionNumber 进行比较。 因此,修订号为 e+1 的高度 h,总是大于修订号为 e 的高度 g, 无论它们的修订高度差距有多大。 例如:
Height{
    RevisionNumber: 3,
    RevisionHeight: 0
} > Height{
    RevisionNumber: 2,
    RevisionHeight: 100000000000
}
当 Tendermint 链运行在某个特定修订版本时,中继者只需提交修订号为链的 chainID 所给出的值、修订高度为 Tendermint 区块高度的头信息和证明即可。当链通过硬分叉升级并重置区块高度时,它有责任更新其 chainID 以增加修订号。IBC Tendermint 客户端随后会根据其 chainID 验证修订号,并将 RevisionHeight 视为 Tendermint 区块高度。 希望使用修订版本来在重置高度的升级之后仍保持持久 IBC 连接的 Tendermint 链,必须按以下格式设置其 chainID:{chainID}-{revision_number}。在任何会重置高度的升级中,chainID 必须 更新为比先前值更高的修订号。 例如:
  • 升级前 chainID:gaiamainnet-3
  • 升级后 chainID:gaiamainnet-4
不需要修订版本的客户端,例如 06-solomachine 客户端,在实现 IBC 接口并需要返回 IBC 高度时,可以直接将修订号硬编码为 0,并仅使用 RevisionHeight。 其他客户端类型则可以在各自的 Update、Misbehavior 和 Verify 函数中,实现自己的逻辑来验证中继者提供的 IBC 高度。 IBC 接口期望的是 ibcexported.Height 接口,不过所有客户端都必须使用 02-client/types 中提供的具体实现,即上面复现的实现。

连接

连接封装了位于两条独立区块链上的两个 ConnectionEnd 对象。每个 ConnectionEnd 都与另一条区块链的一个客户端相关联(例如对手方区块链)。连接握手负责验证每条链上的轻客户端对于其各自对手方是否正确。连接一旦建立,就负责支持所有 IBC 状态的跨链验证。一个连接可以与任意数量的通道关联。 连接握手是一个 4 步握手过程。简而言之,如果给定链 A 想使用两条链上已经建立的轻客户端与链 B 打开连接:
  1. 链 A 发送 ConnectionOpenInit 消息,表示尝试与链 B 初始化连接。
  2. 链 B 发送 ConnectionOpenTry 消息,尝试在链 A 上打开连接。
  3. 链 A 发送 ConnectionOpenAck 消息,将其连接端状态标记为打开。
  4. 链 B 发送 ConnectionOpenConfirm 消息,将其连接端状态标记为打开。

延时连接

连接可以通过在 MsgConnectionOpenInit 中设置 delay_period 字段(单位为纳秒)来实现延时打开。 该时间延迟用于要求底层轻客户端在执行承诺验证之前,必须已经更新到某个指定高度。 delayPeriod 会与连接子模块的 max_expected_time_per_block 参数一起使用,以确定 blockDelay,即该连接必须延迟的区块数量。 执行承诺验证时,连接子模块会将 delayPeriod 和 blockDelay 传递给轻客户端。是否已经更新到所需高度,由轻客户端自行判断。目前 ibc-go 中只有以下轻客户端支持延时连接:
  • 07-tendermint
  • 08-wasm(传递给合约)

证明 与 路径

在 IBC 中,区块链不会通过网络直接彼此传递消息。相反,为了进行通信,一条区块链会把某些状态提交到一个明确定义的路径上,该路径专门保留给某种特定消息类型和某个特定对手方使用。例如,在握手过程中存储某个特定的 connectionEnd,或存储一个准备中继到对手链模块的数据包。中继进程会监控这些路径上的更新,并通过向对手链提交存储在该路径下的数据及其证明来中继消息。 证明会作为字节从 core IBC 传递给轻客户端。如何正确解释这些字节,取决于轻客户端的具体实现。

端口

一个 IBC 模块可以绑定任意数量的端口。每个端口都必须由唯一的 portID 标识。 由于 IBC 的设计目标是在同一账本上由彼此互不信任的模块安全运行,绑定端口时会返回一个动态对象能力。为了对某个特定端口执行操作(例如,使用其端口 ID 打开通道),模块必须向 IBC 处理器提供该动态对象能力。这一要求可防止恶意模块使用其并不拥有的端口打开通道。因此,IBC 模块有责任声明在 BindPort 时返回的能力。

通道

可以在两个 IBC 端口之间建立一个 IBC 通道。目前,一个端口只由单个模块独占拥有。IBC 数据包通过通道发送。就像 IP 数据包包含目标 IP 地址和 IP 端口,以及源 IP 地址和源 IP 端口一样,IBC 数据包包含目标端口 ID 和通道 ID,以及源端口 ID 和通道 ID。这种数据包结构使 IBC 能够将数据包正确路由到目标模块,同时也让接收数据包的模块知道发送方模块是谁。 通道可以是 ORDERED,即发送模块发出的数据包必须由接收模块按发送顺序处理。通道也可以是 UNORDERED,即发送模块发出的数据包按其到达顺序处理(这可能与发送顺序不同)。 模块可以选择希望通过哪些通道进行通信,因此 IBC 要求模块实现会在通道握手期间被调用的回调。这些回调可以执行自定义的通道初始化逻辑。如果任一回调返回错误,通道握手就会失败。因此,模块可以通过在回调中返回错误,以编程方式拒绝或接受通道。 通道握手是一个 4 步握手过程。简要来说,如果链 A 想要使用一条已建立的连接与链 B 打开一个通道:
  1. 链 A 发送 ChanOpenInit 消息,表示尝试与链 B 初始化通道。
  2. 链 B 发送 ChanOpenTry 消息,尝试在链 A 上打开该通道。
  3. 链 A 发送 ChanOpenAck 消息,将其通道端状态标记为打开。
  4. 链 B 发送 ChanOpenConfirm 消息,将其通道端状态标记为打开。
如果所有握手步骤都成功,通道会在两侧都被打开。在握手的每一步中,与 ChannelEnd 关联的模块都会执行其回调。因此,在 ChanOpenInit 时,链 A 上的模块会执行其回调 OnChanOpenInit。 通道标识符会自动按如下格式派生:channel-{N},其中 N 是下一个要使用的序号。

关闭通道

关闭通道会经过 ICS 04 中定义的 2 个握手步骤。通道一旦关闭,就不能重新打开。通道关闭握手步骤如下: ChanCloseInit 会在执行链上关闭一个通道,前提是: 任何用户都可以通过提交 MsgChannelCloseInit 交易来发起 ChanCloseInit。 请注意,当 ORDERED 通道上的数据包超时时,通道会被自动关闭。 ORDERED 通道上的超时会跳过 ChanCloseInit 步骤,并立即关闭通道。 ChanCloseConfirm 是对对手方通道执行 ChanCloseInit 的响应。执行链上的通道会在以下条件满足时关闭: 目前,ibc-go 提供的 IBC 应用都不支持 ChanCloseInit。

数据包

模块通过在 IBC 通道上发送数据包彼此通信。所有 IBC 数据包都包含目标 portID 和 channelID,以及源 portID 和 channelID。这种数据包结构使模块能够知道某个数据包的发送方模块。IBC 数据包还包含一个序号,用于可选地强制顺序。 IBC 数据包还包含 TimeoutHeight 和 TimeoutTimestamp,用于确定接收模块必须在何时之前处理该数据包。 模块通过 IBC 数据包中的 Data []byte 字段相互发送自定义应用数据。因此,对 IBC 处理器来说,数据包数据是不透明的。发送模块有责任将其应用特定的数据包信息编码到数据包的 Data 字段中。接收模块必须将该 Data 解码回原始应用数据。

回执与超时

由于 IBC 工作在分布式网络之上,并依赖可能出错的中继器在账本之间转发消息,因此 IBC 必须处理数据包未能及时发送到目标地址,甚至完全未被发送的情况。数据包必须为超时高度(TimeoutHeight)或超时时间戳(TimeoutTimestamp)指定一个非零值,在该时间点之后,数据包将不能再在目标链上被成功接收。
  • timeoutHeight 表示目标链上的一个共识高度,超过该高度后,数据包将不再被处理,而是被视为已超时。
  • timeoutTimestamp 表示目标链上的一个时间戳,超过该时间点后,数据包将不再被处理,而是被视为已超时。
如果超时时间过去后数据包仍未被成功接收,那么该数据包将不能再在目标链上被接收。发送模块可以将该数据包标记为超时,并采取适当措施。 如果达到超时条件,就可以向原始链提交数据包超时证明。随后,原始链可以执行应用特定的超时逻辑来处理该数据包,例如回滚发送数据包时的变更(退还发送者被锁定的资金等)。
  • 在 ORDERED 通道中,通道内任意一个数据包的超时都会导致通道关闭。
    • 如果序号为 n 的数据包超时,那么序号为 k > n 的数据包将无法被接收,否则就会违反 ORDERED 通道“数据包按发送顺序处理”的约定。
    • 由于 ORDERED 通道强制这一不变式,因此只要证明序号 n 在数据包 n 指定的超时时间前未在目标链上被接收,就足以使数据包 n 超时并关闭该通道。
  • 在 UNORDERED 通道中,只会对该数据包应用应用特定的超时逻辑,通道不会被关闭。
    • 数据包可以按任意顺序接收。
    • IBC 会为 UNORDERED 通道中每个已接收的序号写入一条数据包回执。该回执不包含信息;它只是一个标记,用于表示 UNORDERED 通道已经接收了指定序号的数据包。
    • 要使 UNORDERED 通道上的数据包超时,需要证明在指定超时时间前,该数据包序号对应的数据包回执不存在。
基于这个原因,大多数模块都应使用 UNORDERED 通道,因为它们对活性保证的要求更低,更能有效服务该通道的用户。

确认

模块在处理数据包时,也可以选择写入应用特定的确认。确认可以通过以下方式完成:
  • 同步地在 OnRecvPacket 中完成,如果模块在从 IBC 模块接收到数据包后立即处理该数据包。
  • 异步完成,如果模块是在接收到数据包后的稍晚某个时间点再处理该数据包。
这种确认数据与数据包 Data 类似,对 IBC 来说也是不透明的,IBC 仅将其视为一个简单的字节串 []byte。接收模块必须对其确认进行编码,以便发送模块能够正确解码。编码方式必须在通道握手期间的版本协商中由双方协商确定。 确认可以编码数据包处理是成功还是失败,以及允许发送模块采取适当行动的附加信息。 在接收链写入确认之后,中继器会将该确认中继回原始发送模块。 随后,原始发送模块会使用确认中的内容执行应用特定的确认处理逻辑。
  • 确认失败后,可以回滚数据包发送时的变更(例如,在 ICS 20 中向发送者退款)。
  • 当原始链上的发送方成功接收到确认后,对应的数据包承诺会被删除,因为它已不再需要。

进一步阅读与规范

如果你想进一步了解 IBC,请查看以下规范:

Synopsis

Learn about IBC, its components, and its use cases.

What is the Inter-Blockchain Communication Protocol (IBC)?

This document serves as a guide for developers who want to write their own Inter-Blockchain Communication Protocol (IBC) applications for custom use cases.
IBC applications must be written as self-contained modules.
Due to the modular design of the IBC Protocol, IBC application developers do not need to be concerned with the low-level details of clients, connections, and proof verification. This brief explanation of the lower levels of the stack gives application developers a broad understanding of the IBC Protocol. Abstraction layer details for channels and ports are most relevant for application developers and describe how to define custom packets and IBCModule callbacks. The requirements to have your module interact over IBC are:
  • Bind to a port or ports.
  • Define your packet data.
  • Use the default acknowledgment struct provided by core IBC or optionally define a custom acknowledgment struct.
  • Standardize an encoding of the packet data.
  • Implement the IBCModule interface.
  • Implement the UpgradableModule interface (optional).
Read on for a detailed explanation of how to write a self-contained IBC application module.

Components overview

Clients

IBC clients are on-chain light clients. Each light client is identified by a unique client ID. IBC clients track the consensus states of other blockchains, along with the proof spec necessary to properly verify proofs against the client’s consensus state. A client can be associated with any number of connections to the counterparty chain. The client identifier is auto generated using the client type and the global client counter appended in the format: {client-type}-{N}. A ClientState should contain chain specific and light client specific information necessary for verifying updates and upgrades to the IBC client. The ClientState may contain information such as chain ID, latest height, proof specs, unbonding periods or the status of the light client. The ClientState should not contain information that is specific to a given block at a certain height, this is the function of the ConsensusState. Each ConsensusState should be associated with a unique block and should be referenced using a height. IBC clients are given a client identifier prefixed store to store their associated client state and consensus states along with any metadata associated with the consensus states. Consensus states are stored using their associated height. The supported IBC clients are:

IBC client heights

IBC Client Heights are represented by the struct:
type Height struct {
    RevisionNumber uint64
  RevisionHeight uint64
}
The RevisionNumber represents the revision of the chain that the height is representing. A revision typically represents a continuous, monotonically increasing range of block-heights. The RevisionHeight represents the height of the chain within the given revision. On any reset of the RevisionHeight—for example, when hard-forking a Tendermint chain, the RevisionNumber will get incremented. This allows IBC clients to distinguish between a block height n of a previous revision of the chain (at revision p) and block-height n of the current revision of the chain (at revision e). Heights that share the same revision number can be compared by simply comparing their respective RevisionHeights. Heights that do not share the same revision number will only be compared using their respective RevisionNumbers. Thus a height h with revision number e+1 will always be greater than a height g with revision number e, REGARDLESS of the difference in revision heights. For example:
Height{
    RevisionNumber: 3,
    RevisionHeight: 0
} > Height{
    RevisionNumber: 2,
    RevisionHeight: 100000000000
}
When a Tendermint chain is running a particular revision, relayers can simply submit headers and proofs with the revision number given by the chain’s chainID, and the revision height given by the Tendermint block height. When a chain updates using a hard-fork and resets its block-height, it is responsible for updating its chainID to increment the revision number. IBC Tendermint clients then verifies the revision number against their chainID and treat the RevisionHeight as the Tendermint block-height. Tendermint chains wishing to use revisions to maintain persistent IBC connections even across height-resetting upgrades must format their chainIDs in the following manner: {chainID}-{revision_number}. On any height-resetting upgrade, the chainID MUST be updated with a higher revision number than the previous value. For example:
  • Before upgrade chainID: gaiamainnet-3
  • After upgrade chainID: gaiamainnet-4
Clients that do not require revisions, such as the 06-solomachine client, can simply hardcode 0 into the revision number whenever they need to return an IBC height when implementing IBC interfaces and use the RevisionHeight exclusively. Other client types can implement their own logic to verify the IBC heights that relayers provide in their Update, Misbehavior, and Verify functions respectively. The IBC interfaces expect an ibcexported.Height interface, however all clients must use the concrete implementation provided in 02-client/types and reproduced above.

Connections

Connections encapsulate two ConnectionEnd objects on two separate blockchains. Each ConnectionEnd is associated with a client of the other blockchain (for example, the counterparty blockchain). The connection handshake is responsible for verifying that the light clients on each chain are correct for their respective counterparties. Connections, once established, are responsible for facilitating all cross-chain verifications of IBC state. A connection can be associated with any number of channels. The connection handshake is a 4-step handshake. Briefly, if a given chain A wants to open a connection with chain B using already established light clients on both chains:
  1. chain A sends a ConnectionOpenInit message to signal a connection initialization attempt with chain B.
  2. chain B sends a ConnectionOpenTry message to try opening the connection on chain A.
  3. chain A sends a ConnectionOpenAck message to mark its connection end state as open.
  4. chain B sends a ConnectionOpenConfirm message to mark its connection end state as open.

Time delayed connections

Connections can be opened with a time delay by setting the delay_period field (in nanoseconds) in the MsgConnectionOpenInit. The time delay is used to require that the underlying light clients have been updated to a certain height before commitment verification can be performed. delayPeriod is used in conjunction with the max_expected_time_per_block parameter of the connection submodule to determine the blockDelay, which is number of blocks that the connection must be delayed by. When commitment verification is performed, the connection submodule will pass delayPeriod and blockDelay to the light client. It is up to the light client to determine whether the light client has been updated to the required height. Only the following light clients in ibc-go support time delayed connections:
  • 07-tendermint
  • 08-wasm (passed to the contact)

Proofs and paths

In IBC, blockchains do not directly pass messages to each other over the network. Instead, to communicate, a blockchain commits some state to a specifically defined path that is reserved for a specific message type and a specific counterparty. For example, for storing a specific connectionEnd as part of a handshake or a packet intended to be relayed to a module on the counterparty chain. A relayer process monitors for updates to these paths and relays messages by submitting the data stored under the path and a proof to the counterparty chain. Proofs are passed from core IBC to light clients as bytes. It is up to light client implementations to interpret these bytes appropriately.

Ports

An IBC module can bind to any number of ports. Each port must be identified by a unique portID. Since IBC is designed to be secure with mutually distrusted modules operating on the same ledger, binding a port returns a dynamic object capability. In order to take action on a particular port (for example, an open channel with its port ID), a module must provide the dynamic object capability to the IBC handler. This requirement prevents a malicious module from opening channels with ports it does not own. Thus, IBC modules are responsible for claiming the capability that is returned on BindPort.

Channels

An IBC channel can be established between two IBC ports. Currently, a port is exclusively owned by a single module. IBC packets are sent over channels. Just as IP packets contain the destination IP address and IP port, and the source IP address and source IP port, IBC packets contain the destination port ID and channel ID, and the source port ID and channel ID. This packet structure enables IBC to correctly route packets to the destination module while allowing modules receiving packets to know the sender module. A channel can be ORDERED, where packets from a sending module must be processed by the receiving module in the order they were sent. Or a channel can be UNORDERED, where packets from a sending module are processed in the order they arrive (might be in a different order than they were sent). Modules can choose which channels they wish to communicate over with, thus IBC expects modules to implement callbacks that are called during the channel handshake. These callbacks can do custom channel initialization logic. If any callback returns an error, the channel handshake fails. Thus, by returning errors on callbacks, modules can programmatically reject and accept channels. The channel handshake is a 4-step handshake. Briefly, if a given chain A wants to open a channel with chain B using an already established connection:
  1. chain A sends a ChanOpenInit message to signal a channel initialization attempt with chain B.
  2. chain B sends a ChanOpenTry message to try opening the channel on chain A.
  3. chain A sends a ChanOpenAck message to mark its channel end status as open.
  4. chain B sends a ChanOpenConfirm message to mark its channel end status as open.
If all handshake steps are successful, the channel is opened on both sides. At each step in the handshake, the module associated with the ChannelEnd executes its callback. So on ChanOpenInit, the module on chain A executes its callback OnChanOpenInit. The channel identifier is auto derived in the format: channel-{N} where N is the next sequence to be used.

Closing channels

Closing a channel occurs in 2 handshake steps as defined in ICS 04. Once a channel is closed, it cannot be reopened. The channel handshake steps are: ChanCloseInit closes a channel on the executing chain if ChanCloseInit can be initiated by any user by submitting a MsgChannelCloseInit transaction. Note that channels are automatically closed when a packet times out on an ORDERED channel. A timeout on an ORDERED channel skips the ChanCloseInit step and immediately closes the channel. ChanCloseConfirm is a response to a counterparty channel executing ChanCloseInit. The channel on the executing chain closes if
  • the channel exists and is not already closed,
  • the connection the channel exists upon is OPEN,
  • the executing chain successfully verifies that the counterparty channel has been closed
  • the IBC module callback OnChanCloseConfirm returns nil.
Currently, none of the IBC applications provided in ibc-go support ChanCloseInit.

Packets

Modules communicate with each other by sending packets over IBC channels. All IBC packets contain the destination portID and channelID along with the source portID and channelID. This packet structure allows modules to know the sender module of a given packet. IBC packets contain a sequence to optionally enforce ordering. IBC packets also contain a TimeoutHeight and a TimeoutTimestamp that determine the deadline before the receiving module must process a packet. Modules send custom application data to each other inside the Data []byte field of the IBC packet. Thus, packet data is opaque to IBC handlers. It is incumbent on a sender module to encode their application-specific packet information into the Data field of packets. The receiver module must decode that Data back to the original application data.

Receipts and timeouts

Since IBC works over a distributed network and relies on potentially faulty relayers to relay messages between ledgers, IBC must handle the case where a packet does not get sent to its destination in a timely manner or at all. Packets must specify a non-zero value for timeout height (TimeoutHeight) or timeout timestamp (TimeoutTimestamp ) after which a packet can no longer be successfully received on the destination chain.
  • The timeoutHeight indicates a consensus height on the destination chain after which the packet is no longer to be processed, and instead counts as having timed-out.
  • The timeoutTimestamp indicates a timestamp on the destination chain after which the packet is no longer to be processed, and instead counts as having timed-out.
If the timeout passes without the packet being successfully received, the packet can no longer be received on the destination chain. The sending module can timeout the packet and take appropriate actions. If the timeout is reached, then a proof of packet timeout can be submitted to the original chain. The original chain can then perform application-specific logic to timeout the packet, perhaps by rolling back the packet send changes (refunding senders any locked funds, etc).
  • In ORDERED channels, a timeout of a single packet in the channel causes the channel to close.
    • If packet sequence n times out, then a packet at sequence k > n cannot be received without violating the contract of ORDERED channels that packets are processed in the order that they are sent.
    • Since ORDERED channels enforce this invariant, a proof that sequence n has not been received on the destination chain by the specified timeout of packet n is sufficient to timeout packet n and close the channel.
  • In UNORDERED channels, the application-specific timeout logic for that packet is applied and the channel is not closed.
    • Packets can be received in any order.
    • IBC writes a packet receipt for each sequence received in the UNORDERED channel. This receipt does not contain information; it is simply a marker intended to signify that the UNORDERED channel has received a packet at the specified sequence.
    • To timeout a packet on an UNORDERED channel, a proof is required that a packet receipt does not exist for the packet’s sequence by the specified timeout.
For this reason, most modules should use UNORDERED channels as they require fewer liveness guarantees to function effectively for users of that channel.

Acknowledgments

Modules can also choose to write application-specific acknowledgments upon processing a packet. Acknowledgments can be done:
  • Synchronously on OnRecvPacket if the module processes packets as soon as they are received from IBC module.
  • Asynchronously if module processes packets at some later point after receiving the packet.
This acknowledgment data is opaque to IBC much like the packet Data and is treated by IBC as a simple byte string []byte. Receiver modules must encode their acknowledgment so that the sender module can decode it correctly. The encoding must be negotiated between the two parties during version negotiation in the channel handshake. The acknowledgment can encode whether the packet processing succeeded or failed, along with additional information that allows the sender module to take appropriate action. After the acknowledgment has been written by the receiving chain, a relayer relays the acknowledgment back to the original sender module. The original sender module then executes application-specific acknowledgment logic using the contents of the acknowledgment.
  • After an acknowledgement fails, packet-send changes can be rolled back (for example, refunding senders in ICS 20).
  • After an acknowledgment is received successfully on the original sender on the chain, the corresponding packet commitment is deleted since it is no longer needed.

Further readings and specs

If you want to learn more about IBC, check the following specifications: