{"content":{"title":"关于IBC Channel的一切信息（第一部分）","body":"### Channel排序、握手、超时等\r\n\r\n![](https://img.learnblockchain.cn/2025/03/11/1-bYfHNaqUzAUDtahcWZf3g.jpeg)\r\n\r\n[渠道](https://github.com/cosmos/ibc/blob/main/spec/core/ics-004-channel-and-packet-semantics/README.md) 是 [核心跨链通信（IBC）](https://github.com/cosmos/ibc/tree/main/spec/core) 协议的重要组成部分。它们充当在一条链上的 [模块](https://docs.cosmos.network/main/building-modules/intro)/应用和另一条链上的模块之间传输数据包的通道。对于应用开发者来说，渠道是 IBC 抽象中最重要的层次。\r\n\r\n这篇博客旨在解释渠道，并解答开发者对 IBC Channel的常见问题/误解。涵盖的主要主题包括频道排序、握手、关闭、重新打开、渠道的安全属性以及数据包超时。\r\n\r\n内容很多，所以这是一个两部分系列的第一部分。第一部分关注 IBC Channel的基础知识，而第二部分将解答应用开发者对渠道的一些常见问题。\r\n\r\n## Channel类型\r\n\r\n一个渠道是用于在两条不同链上的两个不同模块/应用之间传输数据包的通道。它们确保数据包仅执行一次，并且仅交付给拥有渠道另一端的对应模块。\r\n\r\n> 每个渠道与特定的 [连接](https://tutorials.cosmos.network/academy/3-ibc/2-connections.html#connections) 相关联，一个连接可以有任意数量的相关渠道。\r\n\r\n![](https://img.learnblockchain.cn/2025/03/11/93332477_image.jpg)\r\n\r\n**图 1：** 从链 A 上的模块发送到链 B 上模块的数据包的高层次描绘\r\n\r\n目前，主要有两种类型的渠道：\r\n\r\n1. **有序：** 数据包按发送顺序（从源）精确地送达（到目的地）。\r\n2. **无序：** 数据包可以以任意顺序送达，与它们被发送的顺序无关。\r\n\r\n渠道排序是通过 **数据包** **序列** 来强制执行的。\r\n\r\n每个数据包都有一个数据包序列，表示发送和接收数据包的顺序。序列号为 1 的数据包将在序列号为 2 或更高的数据包之前在目的地被接收和处理。\r\n\r\n一个渠道端维护三个不同的序列 — `nextSequenceSend`、`nextSequenceRecv` 和 `nextSequenceAck`。每当一个渠道发送、接收或确认一个数据包时，`nextSequenceSend`、`nextSequenceRecv` 和 `nextSequenceAck` 计数器分别递增。\r\n\r\n![](https://img.learnblockchain.cn/2025/03/11/68119221_image.jpg)\r\n\r\n**图 2：** 有序渠道中数据包与渠道序列的关系\r\n\r\n**在** **有序渠道**中，只有当数据包序列与渠道端预期的 `nextSequenceRecv` 相符时，数据包才会被接收和处理。如果数据包序列小于 `nextSequenceRecv`，意味着数据包已被接收，则核心 IBC 进行无操作，[如这里所示](https://github.com/cosmos/ibc-go/blob/887cba1658bbb2db46b96d1ae492cbe8095b1871/modules/core/04-channel/keeper/packet.go#L236-L242)。\r\n\r\n如果数据包序列大于 `nextSequenceRecv`，意味着在当前的数据包之前还有一个较早序列的数据包需要接收，[数据包将被拒绝](https://github.com/cosmos/ibc-go/blob/887cba1658bbb2db46b96d1ae492cbe8095b1871/modules/core/04-channel/keeper/packet.go#L244-L249)，并且中继者最终会提交正确的数据包。\r\n\r\n当数据包在**无序渠道**上被接收时，核心 IBC 仅确保它尚未被接收（因排序无关紧要），通过查找数据包收据 — 一个指示数据包已经接收的单个位。如果收据已存在，则渠道无操作。如果不存在，则 [收据被设置](https://github.com/cosmos/ibc-go/blob/887cba1658bbb2db46b96d1ae492cbe8095b1871/modules/core/04-channel/keeper/packet.go#L224)，并处理数据包。\r\n\r\n## Channel开启/关闭握手\r\n\r\n渠道通过四步握手建立。在握手过程的每一步，核心 IBC 执行基本验证和逻辑，同时回调可以执行自定义握手逻辑的应用。\r\n\r\n在握手过程中，两个模块之间进行版本协商。这使得应用可以就将通过该渠道发送的数据包数据的结构、与使用中间件相关的逻辑等达成一致。\r\n\r\n在 [ICS-20](https://github.com/cosmos/ibc/blob/main/spec/app/ics-020-fungible-token-transfer/README.md) 的情况下，渠道握手如下：\r\n\r\n1. 中继者在链 A 上调用 `chanOpenInit` [函数](https://github.com/cosmos/ibc-go/blob/c0acd5bd1778f2b7ecdf593006f56bd3e273bd49/modules/core/keeper/msg_server.go#L164)。随后，`onChanOpenInit` 回调允许链 A 上的应用执行自定义逻辑。这将渠道端状态（在 A 上）设置为 INIT。中继者将此版本传递给链 B，作为第 2 步的 `counterpartyVersion` [字符串](https://github.com/cosmos/ibc-go/blob/ceb9464ced75d4c64b3a71abf6ceab4a50e908be/modules/apps/transfer/ibc_module.go#L104)。\r\n2. 在链 B 上调用 `chanOpenTry`，其中应用执行自定义握手逻辑 `onChanOpenTry`，将渠道端状态设置为 TRY。链 B 上的应用确保其版本与对方应用所提议的版本相同， [如这里所示](https://github.com/cosmos/ibc-go/blob/ceb9464ced75d4c64b3a71abf6ceab4a50e908be/modules/apps/transfer/ibc_module.go#L105-L112)。\r\n3. 在 A 上调用 `chanOpenAck`。此步骤中[版本协商已经完成](https://github.com/cosmos/ibc-go/blob/ceb9464ced75d4c64b3a71abf6ceab4a50e908be/modules/apps/transfer/ibc_module.go#L130-L134)，A 上的渠道端状态被设置为 OPEN。\r\n4. 在 B 上调用 `chanOpenConfirm`，其中渠道端也设置为 OPEN。\r\n\r\n![](https://img.learnblockchain.cn/2025/03/11/45043150_image.jpg)\r\n\r\n**图 3：** Channel开启握手\r\n\r\n_¹chanOpenInit 也可以由应用模块（例如 ICA）调用_\r\n\r\n在握手过程的任何阶段，回调函数都可能返回错误。在这种情况下，渠道握手失败，需要协商新的握手。\r\n\r\n一旦在两个模块之间建立了渠道，升级该模块或使用中间件包裹模块是不可行的，除非打开新的渠道或协调整个网络的升级。我们目前关于 [**渠道可升级性**](https://learnblockchain.cn/article/12613) 的工作解决了这一问题，以便模块能够升级以利用新功能，或在两侧添加中间件，同时保持相同的渠道。\r\n\r\n一个渠道仅在异常情况下关闭。我们将在系列的第二部分中讨论所有关闭渠道的原因。关闭渠道通过两步渠道关闭握手进行。中继者提交 `ChannelCloseInit`，这允许该链上的应用执行自定义逻辑 `onChanCloseInit`。`ChannelCloseConfirm` 在对方链提交，其中调用 `onChanCloseConfirm` 回调。\r\n\r\n![](https://img.learnblockchain.cn/2025/03/11/90358099_image.jpg)\r\n\r\n**图 4：** Channel关闭握手\r\n\r\n## 数据包超时\r\n\r\n核心 IBC 提供的关键保证之一是 [_如果超时已过，则数据包不会在目的链上被接收_](https://github.com/cosmos/ibc-go/blob/c56f78905a5d2db01d867381d106c403fa9e5c4b/modules/core/04-channel/keeper/packet.go#L189-L195)。超时是在 [数据包接口](https://github.com/cosmos/ibc-go/blob/887cba1658bbb2db46b96d1ae492cbe8095b1871/modules/core/04-channel/types/packet.go#L47-L63) 中由 `timeoutHeight` 和 `timoutTimestamp` **设置为对方（接收）链的。**\r\n\r\n```\r\nfunc NewPacket(\r\n data []byte,\r\n sequence uint64, sourcePort, sourceChannel,\r\n destinationPort, destinationChannel string,\r\n timeoutHeight clienttypes.Height, timeoutTimestamp uint64,\r\n) Packet {\r\n return Packet{\r\n  Data:               data,\r\n  Sequence:           sequence,\r\n  SourcePort:         sourcePort,\r\n  SourceChannel:      sourceChannel,\r\n  DestinationPort:    destinationPort,\r\n  DestinationChannel: destinationChannel,\r\n  TimeoutHeight:      timeoutHeight,\r\n  TimeoutTimestamp:   timeoutTimestamp,\r\n }\r\n}\r\n```\r\n\r\n> **_注意：_** _超时需要在发送链上进行证明，基于接收链的超时高度或超时时间戳。_\r\n\r\n根据接收者的本地时钟指定超时确保不会出现以下情况：发送者认为数据包超时并采取行动，例如在Token转移中释放托管Token，而数据包实际上被对方及时接收，对方继续铸造Token——导致双重支出。\r\n\r\n有两个场景可能导致数据包超时：**1)** 标准情况下接收链上的超时时间戳或超时高度已过，或者 **2)** Channel关闭，因此所有未接收的数据包将超时。\r\n\r\n我们将分别深入探讨这两种情况。首先，让我们看看最常见的场景。\r\n\r\n### 标准数据包超时\r\n\r\n1. 当数据包被发送到超时时间戳/高度已过的目的链时，`RecvPacket` 函数返回错误。\r\n2. 中继者扫描这些错误（ `ErrPacketTimeout` ）并向源链发送 `TimeoutPacket`。\r\n3. `TimeoutPacket` 包含一个 `proof` 和一个 `ProofHeight`。`proof` 用于证明在这个 _特定高度_，数据包未被接收。\r\n4. 如果你熟悉轻客户端的工作原理，你会知道，每个 `ProofHeight` 与特定 `ConsensusState` 相关联。每个 `ConsensusState` 都有特定的时间戳。因此，我们使用 `GetTimestampAtHeight` 函数获取与特定 `ProofHeight` 相关的时间戳。\r\n5. 因此，`ProofHeight` 证明对方时间戳（与 `ProofHeight` 相关）大于在数据包中指定的超时时间戳。\r\n6. 如果指定超时高度而不是时间戳，则我们证明 `ProofHeight` 大于或等于超时高度。\r\n7. 一旦证明了第 5 或 6 步，我们继续证明在此高度上数据包未被接收。此步骤根据渠道排序有所不同：\r\n\r\n**7.a.** 在无序渠道的简单情况下，我们证明在此 `ProofHeight` 上不存在数据包收据。换句话说，这是一个某个键在状态树中不存在的证明。²\r\n\r\n**7.b.** 对于有序渠道，我们验证 `nextSequenceReceive` [小于或等于数据包序列](https://github.com/cosmos/ibc-go/blob/887cba1658bbb2db46b96d1ae492cbe8095b1871/modules/core/04-channel/keeper/timeout.go#L101-L108)。这确保目的链未接收该数据包。例如，如果 `nextSequenceReceive` 为 3，而数据包序列为 4，则渠道期望在接收 4 之前接收序列为 3 的数据包。与第 7.a 不同，这是 `nextSequenceReceive` 的成员资格证明。³\r\n\r\n一旦我们证明了第 5（或 6）和第 7 步，发送模块调用 `OnTimeoutPacket` 以恢复任何状态更改或执行自定义逻辑。在 ICS-20 代币转账的情况下，托管的代币会被 [解托管并退还给用户](https://github.com/cosmos/ibc-go/blob/c86d27fc280cfb342a9e4689b381e5823441b694/modules/apps/transfer/ibc_module.go#L268-L280)。\r\n\r\n² _也称为_ **_不成员/不存在的证明_** _。 IBC 模块在_ [_IAVL 树_](https://github.com/cosmos/iavl) _(与其他 Cosmos SDK 模块类似) 中维护其状态。在 IAVL 树中，子键按字母顺序排列。因此，要证明键 'b' 不存在（即不存在证明），需要证明 'a' 和 'c' 存在，并且在它们之间不存在其他键。_\r\n\r\n³ _除了超时，IBC 中的所有证明都是 **_成员资格证明_** _，即证明特定键在状态树中存在。_\r\n\r\n### Channel关闭时的超时\r\n\r\n发送模块调用 `TimeoutOnClose` 函数，以证明发送数据包的渠道已关闭。\r\n\r\n在渠道关闭时执行超时逻辑是直接的。与上述提到的超时不同，核心 IBC 证明对方的时间已超过数据包超时，在这种情况下，我们只需证明对方的渠道状态已关闭（如 [此处所示](https://github.com/cosmos/ibc-go/blob/c56f78905a5d2db01d867381d106c403fa9e5c4b/modules/core/04-channel/keeper/timeout.go#L188)）。\r\n\r\n## 代币可替代性\r\n\r\n在 IBC 中，通过两条不同渠道发送的相同代币并不可替代。这不是一个错误，而是 IBC 安全模型的一个基本方面。\r\n\r\n即使存在两条不同的渠道 _1_ 和 _2_，连接同两条链 _A_ 和 _B_，通过这些渠道发送的相同代币 _$FOO_ 也不可替代。这是因为每条渠道都有其自己的安全属性。例如，两个不同的渠道可能具有不同的版本。或者这些渠道可能与不同的连接相关联，而不是与同一个连接（可能连接不同的轻客户端）。但最重要的是，模块无法知道对方的渠道端属于哪个链。换句话说，模块只知道它们正在发送数据的渠道，而不知道它们正在与哪个链进行通信。\r\n\r\n因此，必须确保每个渠道的安全边界是隔离的，并且不要对渠道及其相关轻客户端做出天真的假设。\r\n\r\n## 结论\r\n\r\n如上所述，渠道在 IBC 内的数据包传输和排序中起着重要作用。由于模块和渠道紧密耦合，应用开发者了解 IBC Channel的方方面面是有益的。\r\n\r\n在系列的第二部分中，我们将澄清应用开发者关于渠道的一些常见问题。同时，请随时查看我们的 [开发者门户](https://tutorials.cosmos.network/academy/3-ibc/3-channels.html)，了解更多关于 IBC Channel的信息。\r\n\r\n[IBC](https://ibcprotocol.org/) 是保证跨区块链之间以最小信任、安全和可扩展的方式进行任意数据传输的基本范式转变。[请查看这里](https://learnblockchain.cn/article/12612) 以了解 IBC 的工作原理的介绍。\r\n\r\n**_关于作者：_**\r\n\r\n[_Adi Ravi Raj_](https://twitter.com/adi_r_r) _在_ [_Interchain GmbH_](https://www.interchain.berlin/) _工作，是 IBC 团队的协议分析师。_\r\n\r\n_感谢_ [_Susannah Evans,_](https://twitter.com/susevans) _和 Thomas Dekeyser 审阅本文草稿。_\r\n\r\n>- 原文链接： [medium.com/the-interchai...](https://medium.com/the-interchain-foundation/everything-you-need-to-know-about-ibc-channels-part-1-6f37a3060a)\r\n>- 登链社区 AI 助手，为大家转译优秀英文文章，如有翻译不通的地方，还请包涵～"},"author":{"user":"https://learnblockchain.cn/people/27157","address":null},"history":null,"timestamp":1741691677,"version":1}