BFT 排序器架构
Canton 同步器拜占庭容错(BFT)Sequencer架构说明。
用于 Canton 同步器的拜占庭容错Sequencer架构。
BFT Orderer 提供拜占庭容错 (BFT) 全序广播服务。它确保在出现拜占庭故障时的安全性(一致性)和活性(最终进展)。它还保证所有客户端读取相同的消息流。
BFT Orderer 在 Canton 生态系统中运行,并作为 同步器s 的主要 Sequencer 后端选项之一。当活跃时,BFT Orderer 作为 Canton Sequencer 节点的一部分在同一 JVM 中运行,并依赖于 Sequencer 来实现各种功能,包括消息签名、消息验证、密钥管理和去中心化治理。
背景
信任与威胁模型
BFT Orderer 依赖于以下假设:
- 不到三分之一的 BFT Orderer 节点可能同时出现拜占庭故障。这是大多数 BFT 排序协议的标准和强制性假设,以确保安全性(一致性)。
- 攻击者可能拥有大量网络带宽、内存和计算能力,但没有足够的资源来规避标准加密机制,例如数字签名和加密哈希。
- 每个BFT Orderer节点及其关联的Canton Sequencer节点在共享命运模型下运行;如果 BFT Orderer 节点受到损害,则其关联的 Sequencer 也应该被假定受到损害,反之亦然。由于 BFT Orderer 依赖于其连接的 Sequencer 提供的某些功能,因此声明这一假设很重要。
- 去中心化的治理流程使用多个受信任的(通常是离线的)系统管理员来正确管理和配置 BFT Orderer。
- 在新的 BFT Orderer 节点加入期间,节点操作员进行带外通信,以从对等 Sequencer 节点获取相关加入状态的快照;目前,登录状态的内容必须正确。
- 底层数据库存储系统中没有数据损坏。
架构
以下数据流图说明了 BFT Orderer API 和网络信任边界。请注意,远程对等节点包含的元素比本地节点少,只是为了简化说明。实际上,所有 BFT Orderer 节点都是相同的。
<img src=“https://mintcdn.com/cantonfoundation/QAGFSphBsRkeZIBi/images/docs_website/bft-orderer-api-architecture.svg?fit=max&auto=format&n=QAGFSphBsRkeZIBi&q=85&s=469a69f06a18783def2b82fc3534ccce” id=“bft-orderer-api-architecture” className=“align-center” style={{width: “100.0%”}} alt=“image” width=“2101” height=“1038” data-path=“images/docs_website/bft-orderer-api-architecture.svg” />
API
BFT Orderer 提供了多个跨越不同信任边界的 API。上面的架构图说明了每个 API 的范围和位置。
点对点服务(不受信任)
BFT Orderer 节点使用点对点连接直接相互通信,共同形成分布式排序服务。每个 BFT Orderer 节点都会向每个其他对等点建立单向 gRPC/HTTP2 流。这意味着对于给定的一对节点,每个节点 (i) 向其对等方(充当服务器)发起传出连接(作为客户端),并且 (ii) 接受来自其对等方(充当客户端)的传入连接(作为服务器)。对于网络传输安全性,gRPC 连接支持传输层安全性 (TLS) 以提供点对点消息身份验证和完整性。通常,对点对点 API 的网络访问仅限于其他对等点,并且不能公开访问。有关对等网络安全和配置的更多信息,请参阅 Sequencer 后端页面。
Sequencer 客户端(不受信任)
由于 BFT Orderer 是 Sequencer 的后端排序服务,因此 BFT Orderer 节点不会直接接收来自 Sequencer 客户端(参与者节点和 Mediator)的排序请求。然而,BFT Orderer 的关联 Sequencer 确实收到这些请求。在 Sequencer 验证和验证排序请求后,它会将该请求(通过下面的 BlockOrderer API)转发(写入)到其并置的 BFT Orderer,以使用底层分布式服务对请求进行排序。最终,Sequencer 客户端 API 仅间接影响 BFT Orderer,但由于请求源自何处(在 Sequencer 外部),它仍然是一个不受信任的 API。
BlockOrderer API(可信)
与所有 Sequencer 后端一样,BFT Orderer 实现了 BlockOrderer API。该 API 主要允许并置的 Sequencer 向后端发送(写入)请求以进行排序,并订阅(读取)包含 同步器 中所有 Sequencer 中所有已排序事务的全局事务流。由于 Sequencer 和 BFT Orderer 在单个 JVM 进程中一起运行,因此该 BlockOrderer API 是可信的。
与其他 Sequencer 后端(例如集中式数据库)不同,BFT Orderer 后端的发送(写入)功能背后的成本和复杂性往往要高得多。集中式数据库只是按照收到的顺序为传入请求分配序列号,而 BFT Orderer 则在许多并发节点上执行多阶段分布式协议协议。
拓扑和加密提供商(可信)
虽然 BFT Orderer 实现了 BlockOrderer API 来为其 Sequencer 提供排序,但 Sequencer 还实现了 API 来为其 BFT Orderer 提供关键功能。这些 API 将 Sequencer 的去中心化拓扑管理(治理)和加密操作(消息签名和验证)扩展到 BFT Orderer。通过重用这个 Sequencer 拥有的功能,BFT Orderer 避免了重新实现一组复杂功能的需要,并能够使用相同的拓扑事务同时管理 Sequencer 及其各自的 BFT Orderer。
在使用方面,BFT Orderer 经常向其并置的 Sequencer 分别提交所有发送和接收的网络消息的消息签名和消息验证请求。对于拓扑管理,BFT Orderer 在完成离散工作单元(称为纪元)后更新其活动拓扑视图。在每个纪元边界,BFT Sequencer从其Sequencer中获取最新的活动拓扑,然后再恢复排序过程。
Sequencer快照提供程序(可信)
要动态加入新的 Sequencer,Sequencer 需要启动状态才能成功加入网络。启动状态包括反映新 Sequencer 处于活动状态的最新拓扑快照以及每个 Sequencer 客户端的时间戳,以保持一致的事件传递。在加入期间,新的 Sequencer 通过首先与受信任的对等 Sequencer 进行带外通信来获取此加入状态快照。然后,新的 Sequencer 使用检索到的快照启动并成功引导。
当激活时,BFT Orderer 还需要启动状态。特别是,BFT Orderer 需要共识协议特定的元数据,例如起始纪元号、起始块号和先前分配的 BFT 时间戳。该元数据使 BFT Orderer 能够正确执行状态传输并最终参与分布式排序协议。
为了简化入职,当新节点询问时,Sequencer将其 BFT Orderer 的附加启动状态捆绑在自己的入职状态快照中。这使得使用 BFT Orderer 的Sequencer能够重用现有的带外通信,以在单个包中获取所有必要的启动状态。
节点管理员(可信)在大多数情况下,Sequencer 管理会自动扩展到 BFT Orderer。任何与 Sequencer 相关的拓扑命令,例如添加 Sequencer、删除 Sequencer 或旋转密钥,都适用于 Sequencer 及其关联的 BFT Orderer。
然而,BFT Orderer 也直接与节点连接和通信。除了拓扑状态(包括公钥)之外,BFT Orderer 还需要了解特定的端点信息,例如 IP 地址和端口,以便与对等点连接。
因此,节点管理员必须直接使用其 BFT Orderer 单独管理端点信息。 BFT Orderer 公开了一个小型 gRPC 管理服务,允许节点管理员添加、删除或列出当前注册的对等端点。当向网络添加新的 BFT Orderer 时,新节点的端点信息必须传达给所有对等点(可能在带外或通过查找服务),然后由每个节点管理员使用 gRPC 管理服务添加。
交易的生命周期
BFT Orderer 利用四个主要子协议(分为单独的模块)来对分布式节点网络上的交易进行排序。下图和后续部分解释了每个模块的主要职责以及它们如何对订购流程做出贡献。
<img src=“https://mintcdn.com/cantonfoundation/QAGFSphBsRkeZIBi/images/docs_website/bft-orderer-pipeline.svg?fit=max&auto=format&n=QAGFSphBsRkeZIBi&q=85&s=513c653ece5116c7d9c2a1976635f1b6” id=“bft-orderer-pipeline” className=“align-center” style={{width: “100.0%”}} alt=“image” width=“1399” height=“886” data-path=“images/docs_website/bft-orderer-pipeline.svg” />
内存池
mempool 模块接收由并置 Sequencer 使用 BlockOrderer API 提交的排序请求(交易)。内存池将这些排序请求保存在内存中的队列中。
- 如果存在足够的空间,内存池会将排序请求保留在队列中。
- 如果存在“空间不足”,mempool 会通过向 Sequencer 发回否定响应来拒绝排序请求,从而有效地施加反压。
当可用性模块(下游的下一个模块)请求新的排序请求时,内存池队列会重新获得空间。当可用性要求时,内存池将待处理的订购请求批量处理在一起,并将结果批次提供给可用性以继续数据传播。
可用性
回想一下,BFT Orderer 从 Narwhal 中汲取了灵感,Narwhal 是最近的 BFT 共识协议,它将数据传播和数据排序分为两个独立的阶段,以提高可扩展性。在BFT Orderer中,可用性模块实现了数据传播。它在数据排序之前将排序请求数据传播到更正的 BFT Orderer 节点。因此,共识模块(下游的下一个模块)中的数据排序可以使用数据引用而不是完整的请求数据。该策略通过保持共识提案消息较小且传输到对等方的成本较低,消除了 BFT 共识协议关键路径中的主要通信开销。
为了安全地使用 BFT 共识提案中的引用,节点必须保证它们已经拥有或可以在排序完成后检索完整的请求。否则,节点可能会无限期地阻塞,试图获取它们根本无法获取的请求。例如,敌对的 BFT Orderer 节点可能会通过提出对任何正确节点上不存在的请求的引用来尝试阻止网络范围内的排序进度。
BFT 订购者使用可用性证明 (PoA) 来保证引用的请求存在并且可以检索,即使在订购完成后也是如此。当可用性从内存池中提取一批请求时,它会将这批请求存储在本地数据库中。然后,该始发节点的可用性模块将批次转发到所有对等可用性模块。收到有效批次后,对等方还将请求存储到其本地数据库,然后使用签名的可用性确认 (ACK) 响应原始节点。一旦始发节点收集到“足够”数量的有效且不同的可用性 ACK,它就会创建一个 PoA。在具有可容忍拜占庭故障的网络中,最多收集到的可用性 ACK 可能是非法的。所有其他 ACK 必须来自正确的 BFT Orderer 节点,这些节点承诺持久保存并服务引用的请求。因此,PoA 必须包含至少 f+1 个不同的可用性 ACK,以确保至少一个列出的对等方可以向需要它的节点提供请求。或者,由于最多 f 个节点可能拒绝贡献可用性 ACK,因此始发节点最多只能等待 N-f 个 ACK,其中 N 是网络中的节点总数。在后一种情况下,始发节点可能会等待更长的时间来完成 PoA,但更正确的节点可以根据请求为引用的请求提供服务。
当共识模块请求提案时,可用性模块将完成的 PoA 捆绑到排序块中并将其转发到下游。这些排序区块,加上通过共识添加的额外元数据和安全措施,就是 BFT 共识协议的排序内容。
共识
共识模块实现数据排序阶段。它从 ISS 中汲取灵感,提供并发领导者驱动的 BFT 共识协议。共识模块将排序分成离散的工作单元,称为“时期”。共识以区块数量来定义纪元长度,并且所有共识模块使用全网络参数就纪元长度达成一致。
在每个纪元开始时,所有正确的共识模块都就当前拓扑和一组合格的领导者(BFT Orderer)节点达成一致。为了实现并发性,共识确定性地将一个时期内区块的排序责任分配给合格的领导者。每个领导者使用 BFT 排序协议的独立实例为其在该纪元中的区块并行排序区块。 BFT Orderer(和 ISS)使用实用拜占庭容错 (PBFT),这是一种标准 BFT 协议,使用三个阶段:预准备、准备和提交。
领导者通过向本地可用性模块请求包含一组 PoA 的排序块来开始对每个块进行排序。领导者向网络中的所有对等共识模块广播包含排序块的预准备消息。在收到区块的有效预准备后,所有节点(领导者和追随者)都会发送一条准备消息来确认预准备。一旦节点收到来自领导者的有效预准备和来自超过 2/3 网络的有效准备,它就会发送一条提交消息。最后,当一个节点从超过 2/3 的节点收到有效的 pre-prepare 和有效的 commit 消息时,它认为该块是有序的,并将该块向下游转发到输出模块。
一旦共识模块将所有有序块转发到输出模块,它就会等待来自输出模块的反馈,其中包含更新的拓扑和下一个纪元的合格领导集。此信息允许共识模块以与所有正确节点一致(或将一致)的方式正确启动新纪元。
输出
输出模块从上游共识模块接收有序区块。但在将块传送到同位Sequencer之前,输出模块必须执行三个额外步骤。
首先,请记住,有序块仅包含对请求的“引用”(通过 PoA),而不包含实际的请求数据。收到有序块后,输出模块与本地可用性模块通信,以获取每个 PoA 中请求的关联数据:
- 如果请求的数据已本地存储在该 BFT Orderer 节点中,则可用性会立即使用数据响应输出模块。
- 如果请求的数据在本地丢失,则可用性会根据向 PoA 提供 ACK 的对等点来获取丢失的数据。一旦获取,可用性将数据存储在本地并将数据转发到输出模块。
其次,由于共识并行运行 BFT 协议的多个独立实例,因此区块可能会乱序到达输出模块。输出模块使用队列来纠正潜在的重新排序。为了提高效率,此队列在获取数据后保存有序块,以允许同时从可用性模块获取数据。第三,输出模块为所有有序块上的有序请求分配严格递增的 BFT 时间戳。共识模块根据用于对区块进行排序的共识消息的时间戳来计算每个区块的“候选”BFT 时间戳。然而,由于并发的 BFT 协议实例,最终的时间戳分配无法达成共识。最终,输出模块将所有有序块组合回单个序列。使用每个块的候选 BFT 时间戳作为输入,输出模块确定性地为请求分配时间戳,根据需要将值稍微移动到未来,以确保严格增加时间戳。
一旦输出模块完成上述步骤,它就会使用 BlockOrderer API 将块按顺序流式传输到并置的 Sequencer。
最后,在每个 epoch 结束时,输出模块从并置 Sequencer 的拓扑管理器获取更新的拓扑,该拓扑反映了截至该时间点的所有拓扑更改。输出模块将此拓扑以及一组更新的合格领导者发送到共识,以正确启动下一个纪元。
本文由 CC Privacy Club 根据 Canton Network 官方文档(CC-BY-4.0)整理翻译,仅供学习;实现细节以官方最新版本为准。