概述
了解 IBC、它的组成部分及其使用场景。什么是跨链通信协议(IBC)?
本文档旨在为希望针对自定义用例编写自己的跨链通信协议(IBC)应用程序的开发者提供指南。IBC 应用程序必须编写为自包含模块。由于 IBC 协议采用模块化设计,IBC 应用开发者无需关注客户端、连接和证明验证等底层细节。 对栈中较低层级的这一简要说明,可以帮助应用开发者从整体上理解 IBC 协议。对应用开发者而言,通道和端口的抽象层细节最为相关,这些内容描述了如何定义自定义数据包以及
IBCModule 回调。
要让你的模块通过 IBC 进行交互,需要满足以下要求:
- 绑定一个或多个端口。
- 定义你的数据包数据。
- 使用 core IBC 提供的默认确认结构体,或根据需要定义自定义确认结构体。
- 为数据包数据制定标准化编码。
- 实现
IBCModule接口。 - 实现
UpgradableModule接口(可选)。
组件概览
客户端
IBC 客户端是链上的轻客户端。每个轻客户端都由唯一的客户端 ID 标识。 IBC 客户端会跟踪其他区块链的共识状态,以及针对客户端共识状态正确验证证明所需的证明规范。一个客户端可以与对手链上的任意数量连接关联。客户端标识符会根据客户端类型和全局客户端计数器自动生成,格式为:{client-type}-{N}。
ClientState 应包含用于验证 IBC 客户端更新和升级所需的链特定信息与轻客户端特定信息。ClientState 可包含链 ID、最新高度、证明规范、解绑期或轻客户端状态等信息。ClientState 不应包含特定于某一高度区块的信息,这属于 ConsensusState 的职责。每个 ConsensusState 都应与唯一区块关联,并通过高度进行引用。IBC 客户端会获得一个以前缀为客户端标识符的存储空间,用于保存其关联的客户端状态、共识状态以及与共识状态相关的任何元数据。共识状态按照其关联高度存储。
支持的 IBC 客户端包括:
- Solo Machine 轻客户端:手机、浏览器或笔记本电脑等设备。
- Tendermint 轻客户端:基于 Cosmos SDK 的链的默认选择。
- Wasm 客户端:一种代理客户端,适用于运行使用可编译为 Wasm 的语言编写的轻客户端。
- Localhost(回环)客户端:适用于测试、模拟,以及将数据包中继到同一应用内的模块。
IBC 客户端高度
IBC 客户端高度由以下结构体表示:RevisionNumber 表示该高度所属链的修订版本。
一个修订版本通常表示一个连续且单调递增的区块高度范围。
RevisionHeight 表示该链在给定修订版本内的高度。
每当 RevisionHeight 被重置时,例如 Tendermint 链发生硬分叉时,
RevisionNumber 都会递增。这样,IBC 客户端就能够区分链的先前修订版本(修订号为 p)中的区块高度 n,以及链当前修订版本(修订号为 e)中的区块高度 n。
具有相同修订号的 Height,可以通过比较各自的 RevisionHeight 直接进行比较。
修订号不同的 Height,只会根据各自的 RevisionNumber 进行比较。
因此,修订号为 e+1 的高度 h,总是大于修订号为 e 的高度 g,
无论它们的修订高度差距有多大。
例如:
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 打开连接:
- 链 A 发送
ConnectionOpenInit消息,表示尝试与链 B 初始化连接。 - 链 B 发送
ConnectionOpenTry消息,尝试在链 A 上打开连接。 - 链 A 发送
ConnectionOpenAck消息,将其连接端状态标记为打开。 - 链 B 发送
ConnectionOpenConfirm消息,将其连接端状态标记为打开。
延时连接
连接可以通过在MsgConnectionOpenInit 中设置 delay_period 字段(单位为纳秒)来实现延时打开。
该时间延迟用于要求底层轻客户端在执行承诺验证之前,必须已经更新到某个指定高度。
delayPeriod 会与连接子模块的 max_expected_time_per_block 参数一起使用,以确定 blockDelay,即该连接必须延迟的区块数量。
执行承诺验证时,连接子模块会将 delayPeriod 和 blockDelay 传递给轻客户端。是否已经更新到所需高度,由轻客户端自行判断。目前 ibc-go 中只有以下轻客户端支持延时连接:
07-tendermint08-wasm(传递给合约)
证明 与 路径
在 IBC 中,区块链不会通过网络直接彼此传递消息。相反,为了进行通信,一条区块链会把某些状态提交到一个明确定义的路径上,该路径专门保留给某种特定消息类型和某个特定对手方使用。例如,在握手过程中存储某个特定的 connectionEnd,或存储一个准备中继到对手链模块的数据包。中继进程会监控这些路径上的更新,并通过向对手链提交存储在该路径下的数据及其证明来中继消息。 证明会作为字节从 core IBC 传递给轻客户端。如何正确解释这些字节,取决于轻客户端的具体实现。- 所有 IBC 实现提交 IBC 消息时必须使用的路径定义在 ICS-24 Host 状态机要求中。
- 所有实现都必须能够生成和验证的证明格式定义在 ICS-23 Proofs 实现中。
端口
一个 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 打开一个通道:
- 链 A 发送
ChanOpenInit消息,表示尝试与链 B 初始化通道。 - 链 B 发送
ChanOpenTry消息,尝试在链 A 上打开该通道。 - 链 A 发送
ChanOpenAck消息,将其通道端状态标记为打开。 - 链 B 发送
ChanOpenConfirm消息,将其通道端状态标记为打开。
ChannelEnd 关联的模块都会执行其回调。因此,在 ChanOpenInit 时,链 A 上的模块会执行其回调 OnChanOpenInit。
通道标识符会自动按如下格式派生:channel-{N},其中 N 是下一个要使用的序号。
关闭通道
关闭通道会经过 ICS 04 中定义的 2 个握手步骤。通道一旦关闭,就不能重新打开。通道关闭握手步骤如下:ChanCloseInit 会在执行链上关闭一个通道,前提是:
- 通道存在且尚未关闭;
- 其所在连接为
OPEN; - IBC 模块回调
OnChanCloseInit返回nil。
MsgChannelCloseInit 交易来发起 ChanCloseInit。
请注意,当 ORDERED 通道上的数据包超时时,通道会被自动关闭。
ORDERED 通道上的超时会跳过 ChanCloseInit 步骤,并立即关闭通道。
ChanCloseConfirm 是对对手方通道执行 ChanCloseInit 的响应。执行链上的通道会在以下条件满足时关闭:
- 通道存在且尚未关闭;
- 通道所在连接为
OPEN; - 执行链成功验证对手方通道已关闭;
- IBC 模块回调
OnChanCloseConfirm返回nil。
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
IBCModuleinterface. - Implement the
UpgradableModuleinterface (optional).
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:
- Solo Machine light client: Devices such as phones, browsers, or laptops.
- Tendermint light client: The default for Cosmos SDK-based chains.
- Wasm client: Proxy client useful for running light clients written in a Wasm-compilable language.
- Localhost (loopback) client: Useful for testing, simulation, and relaying packets to modules on the same application.
IBC client heights
IBC Client Heights are represented by the struct: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:
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
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 twoConnectionEnd
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:
- chain A sends a
ConnectionOpenInitmessage to signal a connection initialization attempt with chain B. - chain B sends a
ConnectionOpenTrymessage to try opening the connection on chain A. - chain A sends a
ConnectionOpenAckmessage to mark its connection end state as open. - chain B sends a
ConnectionOpenConfirmmessage to mark its connection end state as open.
Time delayed connections
Connections can be opened with a time delay by setting thedelay_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-tendermint08-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.- The paths that all IBC implementations must use for committing IBC messages is defined in ICS-24 Host State Machine Requirements.
- The proof format that all implementations must be able to produce and verify is defined in ICS-23 Proofs implementation.
Ports
An IBC module can bind to any number of ports. Each port must be identified by a uniqueportID.
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 beORDERED, 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:
- chain A sends a
ChanOpenInitmessage to signal a channel initialization attempt with chain B. - chain B sends a
ChanOpenTrymessage to try opening the channel on chain A. - chain A sends a
ChanOpenAckmessage to mark its channel end status as open. - chain B sends a
ChanOpenConfirmmessage to mark its channel end status as open.
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
- the channel exists and it is not already closed,
- the connection it exists upon is
OPEN, - the IBC module callback
OnChanCloseInitreturnsnil.
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
OnChanCloseConfirmreturnsnil.
ChanCloseInit.
Packets
Modules communicate with each other by sending packets over IBC channels. All IBC packets contain the destinationportID 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
timeoutHeightindicates 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
timeoutTimestampindicates a timestamp on the destination chain after which the packet is no longer to be processed, and instead counts as having timed-out.
-
In
ORDEREDchannels, a timeout of a single packet in the channel causes the channel to close.- If packet sequence
ntimes out, then a packet at sequencek > ncannot be received without violating the contract ofORDEREDchannels that packets are processed in the order that they are sent. - Since
ORDEREDchannels enforce this invariant, a proof that sequencenhas not been received on the destination chain by the specified timeout of packetnis sufficient to timeout packetnand close the channel.
- If packet sequence
-
In
UNORDEREDchannels, 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
UNORDEREDchannel. This receipt does not contain information; it is simply a marker intended to signify that theUNORDEREDchannel has received a packet at the specified sequence. - To timeout a packet on an
UNORDEREDchannel, a proof is required that a packet receipt does not exist for the packet’s sequence by the specified timeout.
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
OnRecvPacketif 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.
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.