Cosmos IBC 跨链机制

作者:cosmos白皮书团队
翻译:coinex chain技术团队 逵

Inter-blockchain Communication (IBC)

Cosmos关于跨链的描述,首先出现在白皮书中。之后,又写了个Spec来详细描述。
但是github.com/cosmos/ibc这个Repo现在已经被删了(只能找到一些残留的中文翻译https://steemit.com/cn/@legendx/cosmos-1), 取而代之的是cosmos-sdk中的module spec。 不过,现在这个module spec也已经被删除了,最新的spec放在了新的repo里:https://github.com/cosmos/ics。 这个最新的repo的完成度非常之低。

不管怎样,白皮书作为最初的描述,还是有它的价值的,如下是白皮书当中和跨链相关部分的翻译。

Cosmos IBC原文链接

现在我们来看看Hub和Zone之间是如何通讯的。举一个例子,如果有三条区块链:Zone1、Zone2和Hub,我们希望Zone1产生一个报文,经由Hub送给Zone2。把一个报文从一条区块链送到另外一条,需要把一个证明贴到接收方的区块链上。这个证明表明发送方的链的确发布了这样的一个报文。接收方的链要能检查这个证明,就必须跟上发送方的区块头。这一机制同侧链很类似,侧链要求两条有交互的链必须能够感知到对方,通过双向的proof-of-existence交易流。

IBC协议可以自然地用两种类型交易来定义:一个IBCBlockCommitTx交易,它使得一条链可以向任何观察者证明自己最新的区块哈希;一个 IBCPacketTx 交易,它使得一条链可以让任何观察者证明给定的packet的确是被某发送方发出的,通过提供一个指向最新区块哈希的Merkle-Proof。

通过将IBC分开为两个单独的交易,我们允许接收方的手续费市场来决定响应哪些跨链的报文,同时,也允许发送方自由决定跨链报文最多可以有多少处在发送过程中。

Figure of Zone1, Zone2, and Hub IBC without acknowledgement

在上面的例子中,为了将Zone1的区块哈希更新到Hub上,必须将一个IBCBlockCommitTx交易提交到Hub上,此交易内部包含着Zone1的区块哈希。

参见下文中对IBCBlockCommitTx和IBCPacketTx的详细描述。

IBCBlockCommitTx

一个IBCBlockCommitTx交易中,包含如下这些信息:

  • ChainID (string): 区块链的ID。
  • BlockHash ([]byte): 区块哈希,它是一个Merkle树的根,这个Merkle树要包含AppHash。
  • BlockPartsHeader (PartSetHeader): 区块的分块集合的Header,用途仅限于验证投票的签名。The block part-set header bytes, only needed to verify vote signatures(注:Cosmos把一个区块拆分成若干个分块,每个分块独立地在p2p网络上传递,以此降低网络的压力;Header是每个分块的哈希的Merkle Root,可以用来证明分块的内容是正确的。参见:https://godoc.org/github.com/tendermint/tendermint/types#PartSet https://godoc.org/github.com/tendermint/tendermint/types#PartSetHeader
  • BlockHeight (int): 这次提交的区块高度。
  • BlockRound (int): 这次提交的轮数。(注:Tendermint共识协议中,对一个区块达成共识需要若干轮)
  • Commit ([]Vote): 大于三分之二成员所签署的 Precommit 投票,这些投票在Tendermint中可以达成一个区块的提交。
  • ValidatorsHash ([]byte): Validator集合的Merkle Root。
  • ValidatorsHashProof (SimpleProof): 一个SimpleTree格式的Merkle Proof,以BlockHash为目标来证明 ValidatorsHash
  • AppHash ([]byte): 一颗IAVLTree的Merkle Root,它是对整个应用的全部状态的哈希。
  • AppHashProof (SimpleProof): 一个SimpleTree格式的Merkle Proof,以BlockHash为目标来证明 AppHash

IBCPacketTx

一个IBCPacket中,包含如下这些信息:

  • Header (IBCPacketHeader): 报文头
  • Payload ([]byte): 报文载荷,可选
  • PayloadHash ([]byte): 报文载荷哈希值,可选

PayloadPayloadHash二者之中,至少有一个必须存在。IBCPacket是一个简单的Merkle Root,产生自两个东西: HeaderPayload。一个没有包含完整PayloadIBCPacket被称为abbreviated packet

IBCPacketHeader包含如下这些信息:

  • SrcChainID (string): 发送方的区块链ID
  • DstChainID (string): 接收方的区块链ID
  • Number (int): 给各个packet的一个唯一序号
  • Status (enum): 四种可能的状态: AckPending, AckSent, AckReceived, NoAck, 或Timeout
  • Type (string): 类型是和应用密切相关的,Cosmos保留名为的“coin”的packet类型。
  • MaxHeight (int): 如果到达这个区块高度的时候,状态不是NoAckWantedAckReceived的话,就变成Timeout

一个IBCPacketTx交易中,包含如下这些信息:

  • FromChainID (string): 产生出这个报文的区块链的ID,此链未必是发送方(考虑Zone1通过Hub向Zone2发送消息的情形)。
  • FromBlockHeight (int): 在哪个高度上这个报文被打包进入发送方的块内的。
  • Packet (IBCPacket): 数据包,它有四种可能的状态: AckPending, AckSent, AckReceived, NoAck, 或Timeout
  • PacketProof (IAVLProof): 一个IAVLTree的Merkle-Proof,以某个高度上发送方链上的AppHash为目标,证明数据包的哈希。

经由Hub,从Zone1向Zone2发送报文的流程是:首先,一个IBCPacketTx向Hub证明这个报文被包括在Zone1的app-state中了,接着,另一个IBCPacketTx向Zone证明这个报文被包括在Hub的app-state中了。在这个过程中,交易里所附带的IBCPacket信息是完全一样:SrcChainID始终是Zone1,而DstChainID始终是Zone2。

PacketProof必须具有正确的Merkle证明路径,如下所示:

IBC/<SrcChainID>/<DstChainID>/<Number>

当Zone1经由Hub发送一个Packet给Zone2时,IBCPacket的数据是完全相同的,无论它是在Zone1、Hub还是在Zone2。唯一可变的数据域是Status(它被用来跟踪传输的状态)。

IBC Packet Delivery Acknowledgement

一个发送者有若干理由想要获得来自报文接收方的响应。举一个例子,发送方可能并不知道目标链上的状态(比如它是否已经处于错误的状态中了?)。或者,发送者希望给报文施加一个超时限制(通过报文的MaxHeight域),以防止目标链被DoS攻击、接收到的报文出现爆发式的增加。

在这些情况下,发送者可以请求接收者发送一个响应,通过把报文的初始状态设置为AckPending。这样接收链有责任确认报文已经被送达了,通过在app-state中包含一个简短的IBCPacket(它只包含报文载荷的哈希,而不包含报文载荷本身,因此被称为简短的)。

Figure of Zone1, Zone2, and Hub IBC with acknowledgement

首先,一个IBCBlockCommit交易和一个IBCPacketTx 交易被贴到Hub链上,证明在Zone1存在IBCPacket。这个IBCPacketTx包含有如下的值:

  • FromChainID: “Zone1”
  • FromBlockHeight: 100 (say)
  • Packet: an IBCPacket:
    • Header: an IBCPacketHeader:
      • SrcChainID: “Zone1”
      • DstChainID: “Zone2”
      • Number: 200 (say)
      • Status: AckPending
      • Type: “coin”
      • MaxHeight: 350 (say “Hub” is currently at height 300)
    • Payload: <The bytes of a “coin” payload>

然后,一个IBCBlockCommit交易和一个IBCPacketTx交易被贴到Zone上,证明在Hub存在IBCPacket。这个IBCPacketTx包含有如下的值:

  • FromChainID: “Hub”
  • FromBlockHeight: 300
  • Packet: an IBCPacket:
    • Header: an IBCPacketHeader:
      • SrcChainID: “Zone1”
      • DstChainID: “Zone2”
      • Number: 200
      • Status: AckPending
      • Type: “coin”
      • MaxHeight: 350
    • Payload: <The same bytes of a “coin” payload>

再然后,Zone2必须在它的app-state中包含一个简短的packet,此packet的状态应该是AckSent。一个 IBCBlockCommit交易和一个IBCPacketTx 交易被贴回到Hub上,证明Zone上存在一个简短的IBCPacket。这个IBCPacketTx包含有如下的值:

  • FromChainID: “Zone2”
  • FromBlockHeight: 400 (say)
  • Packet: an IBCPacket:
    • Header: an IBCPacketHeader:
      • SrcChainID: “Zone1”
      • DstChainID: “Zone2”
      • Number: 200
      • Status: AckSent
      • Type: “coin”
      • MaxHeight: 350
    • PayloadHash: <The hash bytes of the same “coin” payload>

最后,Hub必须把packet的状态从AckPending变成AckReceived,而这一改变的证据,必须回传给Zone2。这个IBCPacketTx包含有如下的值:

  • FromChainID: “Hub”
  • FromBlockHeight: 301
  • Packet: an IBCPacket:
    • Header: an IBCPacketHeader:
      • SrcChainID: “Zone1”
      • DstChainID: “Zone2”
      • Number: 200
      • Status: AckReceived
      • Type: “coin”
      • MaxHeight: 350
    • PayloadHash: <The hash bytes of the same “coin” payload>

与此同时,Zone1可能会乐观地假设一个“coin”类型的报文已经被成功地传递了,除非有相反的证据在Hub上被证明。在上面的例子中,如果Hub没有在区块高度350之前收到Zone2传来的AckSent状态,它就会把报文的状态自动设置为Timeout。而timeout的证据会贴回到Zone1,相应的Token也会回传给Zone1。

Figure of Zone1, Zone2, and Hub IBC with acknowledgement and timeout