写点什么

Polkadot 系列(四)——Polkadot 茶溪岸啤(XCMP),干杯!

用户头像
QTech
关注
发布于: 2021 年 01 月 04 日
Polkadot系列(四)——Polkadot茶溪岸啤(XCMP),干杯!

导读

这是本次 Polkadot 系列文章的最后一部分内容,但跨链的技术和研究远不止如此,敬请期待下季度更多深度好文。


作者简介

楼嵩

来自致力于「构建区块链互联网络,打通价值孤岛」的 BitXHub 团队

研究方向:Web3+


概述

Polkadot XCMP(Cross-chain Message Passing)是 Polkadot 上的链间消息传输协议,主要用于平行链间传递消息。XCMP 目前处于开发中,有些内容可能会发生变动,不过其架构已经差不多成型。

根据 Polkadot 自己的说法,XCMP 利用基于 Merkle 树的简单队列机制确保跨链交易的保真度(fidelity)。中继链上的验证人负责把平行链出口队列中的交易转移到目标链的入口队列中,但是中继链并不会存这个跨链交易(或者说跨链消息)的原文,而只会存一些少量的消息元数据。

XCMP 的目标

1. 快速:消息能快速发至目的链;

2. 有序:消息能按序到达目的链;

3. 可验证:能验证到达的消息确实是发送链发送的、能验证该消息在接收链已经被处理;

4. 无遗漏:接受链公平地接收每条消息,发送链不会无限期等待其消息被接收链接收和处理;

XCMP 的一些特点

1. 跨链消息是跨平行链之间的消息,消息原文不上中继链。但是目前 XCMP 还没有完全实现,现在 Polkadot 用的是 Horizontal Relay-routed Message Passing (HRMP),HRMP 将所有的消息存在中继链,未来会用 XCMP 取代 HRMP。

2. 某条平行链的收集人构造新区块时会把以自己为目的地、还未被处理的跨链消息都处理下。

3. 验证人需要对收集人出的块进行跨链消息的检验看看跨链消息是否真的被处理过了。因此消息的一些元数据还是会上中继链的。

4. 任意两条平行链之间传递消息必须开一个通道(Channel),通道是单向的,所以双向通信必须开 2 个。开一个通道需要抵押 DOT,通道关闭后会退回 DOT。

XCMP 的一个简例

Polkadot 的官网举了一个简单的例子:假设平行链 A 上部署的一个合约要发一条跨链调用的消息给平行链 B,从而调用位于链 B 上的合约完成资产转移,其整体流程如下:

1. (如图中 1)调用者(用户)在链 A 上调用部署在链 A 上的智能合约,从而初始化一条以链 B 为目的地的跨链消息 M;

2. (如图中 2)链 A 的收集人(Collator)节点会将这条消息 M 连同其目的地(destination)、时间戳(timestamp)放到 A 的出口队列中;

3. (如图中 3.1 和 3.2)链 B 的收集人在正常情况下会轮询(routinely ping)其他所有的平行链的收集者节点以查看是否有自己的消息(以链 B 为目的地的消息)。如果在新一轮询问中发现有以自己为目的地的消息,那么其会将这条消息(比如这里的消息 M)放到自己的入口队列中,以待在产生下一个区块的时候处理该消息;

4. (如图中 4)另外,链 A 的验证人(Validator)也会通过读取链 A 的出口队列从而知道这条消息;链 B 的验证人(Validator)也是。验证人也需要知道这些消息,因为之后(见步骤 6)它们都会对这条消息进行验证(比如这里的消息 M);

5. (如图中 5)当链 B 的收集人(Collator)节点开始构建(build)一个新区块的时候,它会处理当前入口队列中所有的消息(包括消息 M);在处理过程中,消息 M 会执行链 B 中相应的智能合约以此完成预期的(跨链)资产转移;

6. (如图中 6)然后收集人(Collator)将这个区块提交给验证人(Validator),验证人(Validator)会验证消息 M(以及其他消息)是否真的被处理了;如果这条消息被验证确实处理了,并且这个区块没有其他不合法的地方,验证者就会把该块确认(include)进中继链中。

这个例子比较简陋,只是一次对 XCMP 的管中窥豹,而且还留下了一些坑,如:链 B 是如何知道链 A 给自己发了消息的?跨链消息真的是链 B 自己去拿的吗?这些问题在接下去的内容中会澄清。


总体而言,XCMP 主要分为 2 部分:XCMP 消息的分发、XCMP 消息的存取。


XCMP 消息的分发

XCMP 消息的分发(distribution)其实也是属于 Polkadot 网络的一部分,XCMP 不仅要实现接收链获取发送链发送的跨链消息的,而且要实现高效获取,尽量降低网络的整体通信开销。

虽然 XCMP 已经设计得尽可能减少中继链的负担,但作为跨链消息双方的唯一信任中介,中继链仍然是不可或缺的。中继链主要负责与跨链消息元数据的相关工作(如中继链的 Channel State Table 记录了跨链消息传输通道的状态,这在「XMCP 消息的存取」中会详细介绍)。

当发送链发出跨链消息后,会将消息包含到自己新出的块里,经过验证人验证后,平行链的区块头会上中继链,于是中继链就能够知道发送链对接收链发送了消息,并将此更新到自己的状态中。然后接收链通过向中继链询问相关消息的元数据,就能知道自己是否有待接收的跨链消息。

假如接收链通过中继链知道了发送链有一条发给自己的消息,那么消息具体如何传递过来,就要看 XCMP 消息的路由方式。


XCMP 消息的路由方式大概有这么几种:

1. 当发送链有一个全节点也属于接收链的域(domain)的一部分时,发送链用 gossip 发送消息就能使得接收链收到消息(通过该全节点的转发)。

2. 当中继链有一个全节点既位于发送链的域(domain)中也位于接收链的域(domain)中,则发送链用 gossip 发送消息也能使得接收链最终收到(通过该中继链全节点的转发)。

3. 如果以上条件都不满足,则接收链的验证人(Validator)会发现没有收到跨链消息。因此接收链 Validator 会主动找发送链 Validator 要这个消息;然后验证人节点需要在本链的网络中 gossip 这个消息,使得其他节点都获得这条消息。


所以说消息传递既有可能是发送链直接发过来,也可能是接收链自己去拿。

「概述」中的简例是接收链自己去拿,而且是收集人找收集人拿,不属于以上 3 种情况之一。为什么会如此呢?

因为「XCMP 消息的分发」这部分内容其实还没完全设计好,而且是变动比较多的部分。

XCMP 消息的存取

当接收链接收到消息后,接下去要进行的流程是:处理消息,然后将消息及相关证明放进新块构建好后交给验证人,并最终确认出块。这个流程是比较复杂的,涉及到的新数据结构也很多。

先来看看一些接下去要用到名词的解释:

Para X

表示平行链 X 或者平行线程 X。

因为平行链要接入 Polkadot 需要购买一个专用插槽 Slot,Slot 数量少且贵,所以更精益的方式是使用平行线程。平行线程接入 Polkadot 不需要专门购买一个 Slot,而是按块付费。平行线程与平行链在开发上基本上一样,而且也能使用 Polkadot 的各种功能。

平行链

当我们说平行链的时候,其实是指由平行链收集人节点构成的区块链网络。如果提到了验证人,一般会专门指出这是验证人节点。

好,接下去我们借助一张看起来有点复杂、但其实比较清晰的图来讲解 XCMP 消息的存取。

上图是我们自己总结的 XCMP 消息存取的全景图,基本上概括了 XCMP 消息存取的全部内容。

图中描述了 Para B 要给 Para A 发送消息 m2020。虽然 Para 还可能表示平行线程,但接下去以平行链为例(平行线程也是类似的)来解释这张图,阐述 XCMP 的消息存取流程。

在平行链 B 要向平行链 A 发送 XCMP 消息前,首先要开一个链 B 通向链 A 的通道(Channel),然后接下去的流程是:

1. 平行链 B 发出消息 m2020(放至出口队列),并在新出的块 (假设为 #3 号块) 中包含了此消息。

2. 平行链 B 作为发送链,会维护对每个接收链的一条 MQHC(Message Queue Hash Chain),可以看成是一种链表(如图中,平行链 B 到 A、C、D、E 都建立了通道并发送过跨链消息,所以平行链 B 有 4 条 MQHC)。

  1.  MQHC 链的每个元素对应于一条跨链消息,元素的结构是一个三元组(parent_hash、message_hash、block_number)。parent_hash 是该元素的父元素的哈希,message_hash 跨链消息的哈希,block_number 是父元素消息发出时的中继链区块号(可以当做全局时间来用)。

  2. 平行链 B 的所有 MQHC(这里是 4 条)的链表头元素会作为 Merkle 树的叶节点从而构成一棵树,树根是 Message Root,简称 MR。MR 具有非常牛逼的作用。如果你有了 MR,再加上到某个 MQHC 元素的 Merkle 路径,就能验证 MQHC 上的元素,再加上发送链的消息队列里的消息原文,你就能验证所有已经发送、但没有被处理的所有消息。

  3. MR 会存储到平行链 B 的区块头里(这里刚好是 #3 号块的区块头)。#3 号块的区块头除了有 MR,还有 bitfield、watermark 这两个也是和 XCMP 相关的数据。

  4.  bitfield 是一个位域数组,每一位的含义是发送链对该位表示的接收链在本块中是否发送了跨链消息。假设 bitfield 的 1-4 位表示链 B 对链 A、C、D、E 的跨链消息发送情况,则区块头里的 bitfield =1111 说明这个区块包含了对这四条链的跨链消息。

  5. watermark 可以理解为跨链消息的序号,不过这个序号不是一维的,而是二维的, watermark 的结构为(block_number,para_id),block_number 中继链的区块号,para_id 是平行链的 id。两个 watermark 组成的区间就可以确定一个消息集合,比如,若 w1=(100,A),w2=(300,A),则区间[w1,w2) 表示在中继链 #100 号区块到 #300 号区块这段时间里,链 B 对 para_id=A 的这条链发送的所有消息。

3. 当平行链 B 将消息 m2020 包含进区块并最终被验证人验证确认后,其区块头会存储到中继链的区块中(如本例中 B 的 #3 号区块头)。这样一来,中继链就拿到了链 B 的 #3 号块的 MR,bitfield、watermark 三个关键数据结构。根据这三个数据结构,中继链就会做一些有趣的事情。

4. 中继链上有一个数据结构叫做 CST(Channel State Table),中继链于是会使用该新提交的平行链区块头里的 MR 去更新自己的 CST 里相应的一行。

  1. CST 的作用是跟踪某条发送链对某条接收链所发送的消息的状态,从而提供某个通道的 MQHC 的链表头的证明(proof)。

  2. CST 作为一张表,其行标是发送链的 ID,列标是接收链的 ID,每个表项其实可以表示一个通道,表项的结构是一个二元组(sender_mr,block_number),易知通过表项的 block_number +列对应的 para_id 可以得出 watermark。sender_mr 表示该通道的最新 mr,block_number 为该表项上次更新时的中继链区块号。

  3.  CST 其实是按行来进行存储的,一行中会有很多表项;因为是同一行所以这些表项的发送链都相同;而因为发送链都相同,同行的表项里的最新 MR 也都相同。因此当新的平行链区块头到来时,CST 是按行进行更新的。

  4. CST 其实除了一张表外,还有第二部分是映射(para_id => row_root)。row_root 是 CST 的行里的表项构成的 Merkle 树的树根。这样,某一行只要有一个表项变化了,其 row_root 就会变化。

  5. row_root 会作为叶子继续构出一棵 Merkle 树,其根为 XCMP_Root,其会关联至中继链的状态根(State Root)。

5. 当平行链 A 开始构建新 PoV 区块时,将会需要其所有消息的 proof。

  1. 链 A 需向中继链获取:以 A 为发送链的 MR 的 light client proof,以 A 为接收链的所有通道的 watermark 的 light client proof,而且这些 proof 需要同时构建,这样这些 proof 都能基于中继链的同一个 State Root;light client proof 是通过 Merkle proof 转化而成的。

  2. 因为中继链状态根(State Root)路径到 CST 的表项其实要分 2 部分,先从 State Root 到 row_root,再从 row_root 到表项。所以从 State Root 到表项里的 MR 的整个 Merkle proof 被称为「Nested Proof」。

  3. 某个通道的最新消息根 MR 可以通过中继链能够获取,MR 的作用在前面提到过,MR 、MQHC 链表头的 Merkle root、MQHC、对应消息的原文这四样东西能验证这个通道目前未被处理的消息。这样,本次构建区块就会把目前还没有处理的消息都处理下。

  4. 因此,链 A 还需要所有消息的从 MR 到 MQHC 元素的 merkle root,以及消息的原文。

  5. 这样,包含消息哈希的 MQHC 元素——消息根 MR ——MR 构成的 CST 表项—— CST 的 row_root ——State Root,就能整合成一个总的 proof,从中继链状态根 State Root 到跨链消息的哈希。

  6. 最后 PoV 区块里需要包含内容有:所有的消息原文,及其对应的 MQHC 元素,及其整个的 proof。


另外还有一些细节:

1)平行链的状态里不光要存储 MQHC 的链表头的 Merkle proof,还要存储 MQHC 所有元素的 Merkle proof(它们也曾当过链表头)。

2)如果确认消息被执行,则 MQHC 里的链表可以丢弃已经执行过的消息的对应元素(但不一定要求立即丢弃,因为可以多存一会起到冗余的作用)。

对上面第 5 点——平行链 PoV 区块的构造,Polkadot 官网上有一个简例。

以上面这张图中的链 B 为例。链 B 给链 A 发送了跨链消息,且通过 Channel State Table 可知,链 A 上一次处理链 B 发过来的消息的时间是中继链块号位 rc1 的时候,上一次的 watermark1 为(rc1, A),而当前的 watermark2 为(rc_6, A)。

假设链 A 要出新块,其要把[watermark1,watermark2) 之间的跨链消息都进行处理。链 A 出块需要的数据有(以下的 1、2、3 就是构成整个的 proof):

1) 中继链 State Root 到 CST row_root 的 Merkle proof(红色部分);

2) CST rowroot 到 (MR,blocknumber) 构成的表项的 Merkle proof(蓝色部分);

3) MR 到 MQHC 链表头的 Merkle proof(橙色部分);

4) 所有消息对应 MQHC 的元素(图中左下方绿色矩形);

5) 所有消息的原文(图中左下方绿色信封);

最后当平行链构造完 PoV 区块后,验证人会对其进行验证,验证通过则完成出块,然后区块头会上到中继链,中继链更新状态,而发送链通过查询中继链也能知道自己发出的消息最终被处理完成了。


总结

本次 Polkadot XCMP 的干货文章到此就结束了。

这杯茶(X)溪(C)岸(M)啤(P)的味道如何?

XCMP 的内容,尤其是本篇中讲到的 XCMP 消息存取部分,还是比较复杂的,用到了很多新创造的概念,比如消息哈希链表(MQHC),以及各种类型的 Merkle 树(消息树、CST Item 构成的树、CST Row 构成树、状态树等等)。确实很难一次性看懂,建议大家多看几遍(划重点:可以仔细看看我们画的那张 XCMP 消息存取的图)

之所以设计得这么复杂,是为了 XCMP 的设计目标:快速、有序、可验证、无遗漏

XCMP 的设计主要分为 XCMP 消息分发和 XCMP 消息存取,现在我们回顾下,Polkadot 是否实现了其设计目标:

消息分发:通过平行链直接直接发消息,而不中继链达到了快速

消息存取:通过 watermark 及其相关机制达到有序无遗漏的目的;通过 MQHC、各种 Merkle 树及 Proof 做到了可验证

综上,Polkadota XCMP 的设计大致是完成其目标的。但也因为 XCMP 设计比较复杂,目前还在实现中,未来可能会有一些变动,让我们共同关注。


课后小习题

一共准备了 5 个小题目

(都是单选,包括了上篇文章的 2 个题目,把你的答案集齐 5 个给桔子,前五名全对的小伙伴有惊喜好礼哦😯)

1. 以下内容中,不是 Polkadot XCMP 设计的目标的是?

【A】快速

【B】消除跨链消息的「饥饿」现象

【C】高效

【D】可验证

2. 根据本文内容,目前在 Polkadot XCMP 不可能发生的消息路由方式的是?

【W】发送链将跨链消息发送给一个自己的全节点,该全节点转发至接收链

【X】接收链的收集人去找发送链的钓鱼人拿跨链消息,然后在本链的网络中 gossip 这个消息

【Y】发送链将跨链消息发送给一个中继链的全节点,该中继链全节点转发至接收链

【Z】接收链的验证人主动去找发送链的验证人拿跨链消息,然后在本链的网络中 gossip 这个消息

3. 下列关于 MQHC 的说法错误的是?

【H】 MQHC 会存储在平行链的区块头里,而且因为平行区块头上中继链,所以中继链也会存储 MQHC

【I】 MQHC 是指 Message Queue Hash Chain,MR 是指 Message Root

【J】MQHC 的链表头作为叶节点构成的树的树根为称为消息根 Message Root

【K】 当拥有 MR、MQHC 链表头的 Merkle Proof、MQHC 链表头、链表头包含的消息原文时可以证明链表头里的消息原文的存在性和正确性

4. 下列关于 CST 的说法正确的是?

【L】CST 的全称是 Channel Send Table

【M】CST 是按列存储的,每一列的表项对应相同的接收链

【N】CST 除了一张表外,还有第二部分映射(para_id => row_root),其中 row_root 会作为叶子继续构出一棵 Merkle 树,其根为 XCMP_Root

【O】当新的平行链区块头到来时,中继链会根据区块头里的交易树的树根去更新 CST

5. 关于接收链进行 PoV 区块的构建的下列说法中错误的是?

【A】中继链状态根(State Root)路径到 CST 的表项分为 2 部分,先从 State Root 到 row_root,再从 row_root 到表项,因此从 State Root 直接到表项里的 MR 的整个 merkle proof 被称为「nested proof」

【B】PoV 区块出块是一步完成的,没有比较明显先构建、再验证确认的分割阶段

【C】基于 State Root 的那些 proof 需要在同一时刻构建,这样这些 proof 才能基于中继链的同一个 State Root

【D】PoV 区块里需要包含内容至少有:所有的消息原文,及其对应的 MQHC 元素,及其整个的 proof


快快把你选定的答案发给小助手桔子(微信:18458407117 )

5 题全对前五名小伙伴

会有好礼相送哟!



发布于: 2021 年 01 月 04 日阅读数: 40
用户头像

QTech

关注

趣探索区块链技术世界 2020.08.13 加入

趣链科技官方技术内容输出平台

评论

发布
暂无评论
Polkadot系列(四)——Polkadot茶溪岸啤(XCMP),干杯!