IC 内部结构:子网的 XNet 协议
介绍
本文继续上一篇关于互联网计算机(IC)中状态机复制的文章,这是一组复制的状态机,这一次,我们将研究独立状态机(也称为子网)通信的协议,即 XNet 协议。
子网
子网是参与共识协议单个实例的节点集合,子网中的所有节点都具有相同的状态并应用相同的区块。
认为子网的节点是物理并置的可能很诱人,然而,通常情况正好相反:子网节点通常在地理上分布在独立的数据中心,仍然有充分的理由将数据中心中的多个节点分配到同一个子网,例如增加查询调用容量和在副本重启的情况下加快恢复。
目标是提高可用性:单个数据中的灾难中心无法关闭整个子网,注册表容器维护节点到物理机器的分配,网络神经系统管理注册表的所有更改。
来自不同子网的节点可以位于同一个数据中心,这种设置可能违反直觉:来自不同子网的节点有时可能比同一子网中的节点具有更好的网络连接性。
位于三个数据中心的两个三节点子网,实线表示子网内的点对点通信,虚线 —— 跨子网通信,并非所有 XNet 连接都出现在图片中。
XNet 协议不限于同数据中心通信,一个子网中的任何节点都可以与另一个子网中的任何其他节点通信。但是,副本更喜欢联系紧密的对等方以减少消息传递延迟。
副本使用网络延迟作为接近度度量,它随机联系来自其他子网的节点,为延迟较低的节点分配更高的权重。
消息流
消息在源子网上托管的容器的输出队列中开始它们的旅程,子网根据目标子网对这些消息进行分类,并将它们交织成平面消息流(每个目标子网一个流),流中的每条消息都有一个唯一的单调递增索引。
子网内的消息流,容器将消息推送到它们的输出队列中,流构建器获取这些消息并为每个子网构造一个扁平的传出消息流。
合并队列的组件(恰当地称为流构建器)应该满足一些约束:
确定性:子网中的所有节点必须就流的确切内容达成一致,为了完成它的工作,流构建器需要从容器标识符到子网标识符的映射,即路由表,路由表来自注册表并随时间变化,为了确保确定性,每个区块都固定状态机用于消息处理的注册表版本。
排序:如果容器 A 向容器 B、R1 和 R2 发送两个请求,则 R1 应在流中出现在 R2 之前。
公平性:我们不希望来自单个健谈容器的消息主导流,流构建器尝试交错消息,以便每个容器具有相同的带宽。
当子网 X 中的节点决定从子网 Y 中获取新消息时,它会从 Y 中选择一个节点(注册表存储整个网络拓扑)并使用第一个未见过消息的索引和要获取的消息数调用 Y 的 XNet 端点。
区块有效载荷
共识协议的工作是聚合来自外部世界的消息并将它们打包成一个整洁的区块,共识将几种类型的消息包含在区块中,例如用户入口消息、比特币交易(对于启用了比特币集成的子网)以及来自其他子网的容器间消息。
我们将与验证所需数据配对的消息称为有效负载,我们称从网络负载构建器中提取负载的组件。
共识算法将来自外界的消息聚合成区块
XNet 有效负载构建器使用简单的 HTTP 协议从分配给其他子网的节点中提取消息,XNet 端点是一个组件,它通过安全的 TLS 连接为发往其他子网的消息提供服务,只接受来自其他节点的连接。
此措施并不意味着隐私,因为恶意节点可以访问数据,但确保网络提供商无法读取消息。XNet 端点获取节点的完整列表、它们的子网分配、IP 地址和来自注册表的公钥(需要建立一个 TLS 连接)。
XNet 协议中的数据流,子网 Y 为子网 X 生成一个签名的消息流,并通过 HTTP 端点公开该流,子网 X 从子网 Y 上的一个节点中提取消息。
垃圾收集
我们现在知道一个子网如何积累发往另一个子网的消息,这个知识引出了另一个问题:副本如何删除目标子网已经使用的消息?我们需要一种反馈机制,允许消费者子网告诉生产者子网它不再需要某些流前缀,我们称这种机制为信号。
信号是 XNet 有效负载的一部分,指定发送子网可以丢弃的反向流的前缀。当子网 X 中的节点从子网 Y 中的节点获取 XNet 有效负载时,除了实际消息外,Y 节点还包括流标头,流标头描述了 X — Y 通信的状态:
前向流 X → Y 中的全部消息索引。
反向流的信号(Y → X):对于反向流中的每个消息索引,Y 告诉 X 是否可以垃圾收集消息(ACK 信号)或应该重新路由消息(REJECT 信号),REJECT 信号指示目标容器已移动,因此 X 应将消息路由到另一个流。
信号解决了收集过时消息的问题,但它们引入了另一个问题,现在我们还需要垃圾收集信号!幸运的是,我们已经拥有了我们需要的所有信息:我们只为仍然存在于反向流中的消息保留信号。
一旦我们注意到(通过查看消息索引的范围)远程子网从其流中丢弃了消息,我们就可以从我们的标头中删除相应的信号。
根据我的经验,众所周知,消息索引和信号的相互作用很难掌握,所以让我们看一个例子。下图描绘了两个子网 X 和 Y,它们处于通信中间,子网 X 获取 Y 的流和标头的前缀,允许 X 引入新消息并垃圾收集其消息和信号。
X 还为新收到的消息发布信号并相应地更新其索引,以便 Y 可以在下一轮通信中垃圾收集其消息和信号。
两个子网 X 和 Y 通过 XNet 协议进行通信,在之前的通信中,子网 Y 从 X 的流中消费了消息 [0,10),并且最近收到了消息 10 和 11。现在,子网 X 接收到 Y 的流的前缀和匹配的标头,并从其流中删除消息 10 和 11,因为 Y 将不再需要它们。X 还将新接收到的消息的信号包含到 X → Y 流标头中,并删除它之前使用的消息 Y1 的过时信号。
信号和流消息就像在 Auryn 上互相吃尾巴的蛇:发送者在看到信号时丢弃消息,而接收者在看到流边界前进时丢弃信号。
流认证
由于网络中没有两个节点可以盲目地相互信任,因此我们需要一种机制来验证 XNet 有效载荷的内容。更具体地说,我们想要证明有效负载中的流前缀在产生流的子网中的所有诚实节点上都是相同的。
在上一篇文章中,我们已经看到节点如何使用状态树上的阈值签名作为呼叫回复的真实性证明。XNet 协议依赖于相同的技巧:节点将发往其他子网的流包含在其状态树中,并使用阈值签名作为来自其他子网的节点的真实性证明。
状态树中子网消息流的编码
从概念上讲,从子网请求消息的节点与请求响应的客户端没有区别。这种相似性允许我们在两种情况下使用相同的身份验证机制。
目前,我们在 XNet 协议和 IC HTTP 接口中表示证书的方式存在细微差别,HTTP 接口证书将检查真实性所需的数据和哈希组合在一个数据结构哈希树中,XNet 协议将原始消息数据和哈希(称为见证)分离为单独的数据结构。
这种区别的原因纯粹是历史性的,我们实现 XNet 协议的时间明显早于响应身份验证,因此我们无法从中受益于 Joachim Breitner 的才华。Joachim 以 XNet 身份验证方案为出发点,并将其简化为 IC 接口规范。
证书的树形结构可以方便地在接收方上缓冲消息:接收方可以维护比单个区块容纳的稍大的消息池,异步获取新消息并将它们附加到池中,适当地合并证书。
当我们将来自多个子网的消息包含到单个区块中时,这种优化使我们能够更有效地使用子网的窄 XNet 通道并提高公平性。
限制
XNet 协议是一项了不起的工程壮举,但它有一些固有的局限性:
所有 XNet 消息都必须经过共识区块,这个约束限制了协议的理论吞吐量,由区块速率和大小决定。例如,如果一个子网每秒产生一个区块且区块大小为 2MiB,则该子网上的 XNet 吞吐量最多为 2MiB/秒。
传递消息需要两轮共识(发送者一轮,接收者一轮),此约束将协议的延迟限制在区块速率上。如果接收子网和发送子网都需要一秒钟来生成区块,则请求至少需要两秒钟才能到达目的地,传递响应将需要另外两轮,因此呼叫往返时间至少为四秒。
代码参考
一些指向本文中实现设计的代码的术语:
路由表
流生成器
有效负载生成器和 XNet 消息缓冲区
XNet 端点和节点邻近度度量计算
将消息引入状态机状态
消息和信号的垃圾收集
流到树结构、树编码和验证的映射
致谢
最后,我要对在协议开发中发挥重要作用的人给予应有的赞扬:
Allen Clement 是 XNet 协议大部分功能背后的策划者。
David Derler 提炼了 Allen 的许多想法,并为该协议编写了正式的数学规范。
Alin Sinpalean 在 IC 副本中实现了该协议,并进行了许多优化。
我的主要贡献是流认证和 TLS 集成。
在 smartcontracts.org 上开始构建,并在 forum.dfinity.org 加入开发者社区。
作者:Roman Kashitsyn
翻译:Catherine
- 往 期 推 荐 -
Yumi NFT Art Festival - DFINITY 上的首个 NFT 艺术节
IC 生态项目 ICPort 以 2000 万美元估值完成种子轮融资
长按关注 DFINITY 微信公众号
随时答疑解惑
*添加小助手微信 comiocn 进交流社群