BFT 共识:从 PBFT、Tendermint 到 HotStuff 家族
BFT 共识:从 PBFT、Tendermint 到 HotStuff 家族
本文是 dex_qa.md 第 2 章的独立版本,内容相同,按"理论基础、经典协议、HotStuff 家族演化、乐观路径、数据传播与 DAG、对比与工程"组织。文中"设计文档"指 Crustle-DEX交易引擎设计.md。涉及协议参数与性能的数字来自论文与公开资料,未在本项目环境复现。
BFT(拜占庭容错)共识是高性能链的核心。本文按"理论基础、经典协议、HotStuff 家族演化、乐观路径、数据传播与 DAG、对比与工程"的顺序组织:
| 部分 | 问题 |
|---|---|
| 理论基础 | 第 1 节系统模型;第 2 节为什么 n ≥ 3f+1 |
| 经典协议 | 第 3 节 PBFT;第 4 节 Tendermint;第 5 节 Malachite;第 6 节 HotStuff 与两链、三链规则;第 7 节 HotStuff 什么情况下换 leader;第 8 节 PBFT、Tendermint 与 HotStuff 的区别与选型 |
| HotStuff 家族演化 | 第 9 节演化路线与起点;第 10 节 Fast HotStuff;第 11 节 Jolteon 与 Ditto;第 12 节 HotStuff-2 与 HotStuff-1;第 13 节 MonadBFT 与尾部分叉;第 14 节演化总表与规律 |
| 乐观路径 | 第 15 节 Optimistic BFT,以及 HotStuff 家族为什么不属于它 |
| 数据传播与 DAG | 第 16 节 leader 带宽瓶颈、Narwhal 与 Quorum Store 的区别;第 17 节 DAG 类共识为什么提出、怎么排序(含例子) |
| 对比与工程 | 第 18 节各协议对比总表;第 19 节如何做到 200 ms 以内;第 20 节从零实现一个 BFT 共识 |
1. BFT 共识的系统模型是什么?安全性、活性、响应性指什么?
讨论任何 BFT 协议,先说清三件事:节点会怎么出错、网络有多可靠、要保证什么。
故障模型:
| 类型 | 行为 | 需要的节点数 |
|---|---|---|
| 崩溃故障 | 节点只会停机,不会撒谎 | n ≥ 2f+1(Raft、Paxos) |
| 拜占庭故障 | 节点可以任意行为:撒谎、双签、选择性发消息 | n ≥ 3f+1(第 2 节) |
网络模型:
| 模型 | 假设 | 代表协议 |
|---|---|---|
| 同步 | 消息延迟有已知上界 Δ | 理论上最强,现实网络难以保证 |
| 异步 | 消息最终会到,但延迟没有上界 | HoneyBadger BFT、Tusk、Ditto 的回退路径 |
| 部分同步 | 存在一个未知的全局稳定时间 GST,之后延迟不超过 Δ | PBFT、Tendermint、HotStuff 家族 |
FLP 不可能性(1985 年): 在异步网络里,只要可能有一个节点崩溃,任何确定性协议都无法同时保证总能达成一致(活性)与不出错(安全性)。实用协议的应对:
- 部分同步协议保证安全性任何时候都成立,活性只在 GST 之后成立:网络差时可能停下来,但不会提交冲突的块。
- 异步协议用随机数(公共随机币)绕开 FLP,以概率 1 最终达成一致。
三个性质:
| 性质 | 含义 |
|---|---|
| 安全性 | 诚实节点永远不会提交冲突的块;一旦提交就不可回滚(确定性最终性) |
| 活性 | 诚实客户端的交易最终会被提交 |
| 乐观响应性 | GST 之后且 leader 诚实时,推进速度只取决于实际网络延迟 δ,而不必等待保守的上界 Δ |
衡量协议的三个维度:
- 轮数(消息延迟):提交一个块要经过几次单向消息,决定延迟下限。
- 通信复杂度:每个块全网要发多少条消息,O(n) 还是 O(n²)。
- 认证复杂度:每个节点要验证多少个签名;聚合签名可以把 2f+1 个签名压成一个。
本文后面的协议对比(第 18 节)都按这几个维度展开。
2. 为什么 BFT 要求 n ≥ 3f+1,法定人数是 2f+1?
要让任意两个法定人数至少交在一个诚实节点上。
- 最多 f 个节点作恶,而且可能有 f 个诚实节点消息迟到,所以只能等 n − f 个回复。
- 两个大小为 n − f 的集合至少交于 n − 2f 个节点;要保证交集里至少有一个诚实节点,需要 n − 2f > f,即 n ≥ 3f+1。
- n = 3f+1 时法定人数 n − f = 2f+1。例如 21 个验证者最多容忍 6 个拜占庭节点,法定人数 15。
- 实际实现按投票权而不是人数计算:法定人数是总投票权的三分之二以上。
3. PBFT 的流程是怎样的?
PBFT(Castro 与 Liskov,1999)是第一个实用的拜占庭容错状态机复制协议,正常情况下三个阶段:pre-prepare、prepare、commit。
sequenceDiagram participant C as 客户端 participant P as 主节点 participant R as 副本 C->>P: 请求 P->>R: pre-prepare(视图 v, 序号 n, 请求摘要) R->>R: prepare 全员广播 Note over R: 收到 2f 个一致的 prepare,加上 pre-prepare 共 2f+1:prepared R->>R: commit 全员广播 Note over R: 收到 2f+1 个 commit:committed,执行 R-->>C: 回复;客户端收到 f+1 个相同结果即确认
- pre-prepare 由主节点给请求分配序号;prepare 让所有诚实副本对"视图 v 里序号 n 就是这个请求"达成一致;commit 保证这个一致在换主节点之后依然成立。
- 通信量:prepare 与 commit 都是全员广播,正常路径 O(n²);视图切换要带上每个副本的 prepared 证书,朴素实现 O(n³)。
- 检查点与回收:每隔若干序号做一次检查点,2f+1 个副本签名后即可删除更早的日志。
- 不足:主节点固定到被怀疑才换,性能依赖主节点;平方级消息让它难以扩展到上百个节点;每个请求单独走三阶段,没有链式流水线。
4. Tendermint 的流程是怎样的?
Tendermint(现名 CometBFT)以"高度"为单位逐块共识,每个高度内按"轮"推进,每轮三步:propose、prevote、precommit。
flowchart LR
P[propose:本轮提议者广播区块] --> PV[prevote:验证者对区块或空投票]
PV --> PC{收到 2/3 以上的 prevote?}
PC -- 是,形成 polka,锁定该块 --> PCV[precommit]
PC -- 超时 --> PCN[precommit 空]
PCV --> CM{收到 2/3 以上的 precommit?}
CM -- 是 --> COMMIT[提交,进入下一高度]
CM -- 否或超时 --> NR[进入下一轮,换提议者]
PCN --> NR
NR --> P
- 锁机制:验证者对某块 precommit 后就锁定它,之后只在看到更高轮次对另一块的 2/3 prevote 时才解锁,这是安全性的关键。
- 即时最终性:块一旦提交不可回滚,没有分叉,这是它适合应用链(Cosmos 生态、dYdX v4)的原因。
- 延迟:正常情况一个块三次消息延迟;但每个高度要完整走完两轮投票才开始下一个高度,没有流水线,常见配置下出块在秒级。
- 通信量:投票全员 gossip,O(n²)。
- 活性:依赖超时,超时参数要按最坏网络延迟设置;新轮次要等超时才能推进,不具备乐观响应性(只要网络好,推进速度只取决于实际延迟)。
5. Malachite 是什么?和 Tendermint 什么关系?
Malachite 是 Rust 写的 BFT 共识引擎,核心是 Tendermint 算法的实现。它原由 Informal Systems 开发,2025 年随团队一起被 Circle 收购,现在是 Circle 的 Arc 链的共识。
- 算法:Tendermint 的 propose、prevote、precommit 三步与锁机制(第 4 节),即时最终性。
- 动机:解决 CometBFT(Tendermint 的 Go 实现)里共识与网络、mempool、执行耦合过紧的技术债,把共识做成可嵌入的库。
- 分层:
| 层 | 内容 |
|---|---|
| 核心共识库 | 纯算法:状态机、投票统计、驱动器,不做任何 I/O |
| 共识引擎 | 网络(点对点)、预写日志、同步、配置、指标,把核心库跑起来 |
| 应用接口 | 应用负责产生要共识的"值"(通常是区块)、执行与提交;Malachite 只负责对值达成一致 |
- 形式化方法:部分实现与 Quint 形式化规约一起设计,并用基于模型的测试检查实现是否符合规约。
- 公开数据:项目 README 称早期实验在 100 个验证者、1 MB 区块下平均最终确认约 780 ms,最多每秒 2.5 个块或 13.5 MB 数据,约 5 万笔交易。Arc 的公开资料称最终性低于 500 ms。均未在本项目环境复现。
- 状态:README 自述为 alpha 阶段、尚未外部审计。
- 典型组合:Malachite 作共识客户端,Reth 作执行客户端,两者之间用以太坊的 Engine API 衔接(dex_qa.md 12.10 节)。
6. HotStuff 是什么?两链与三链规则有什么区别?
HotStuff 是 leader 制的 BFT 协议,特点是线性通信(投票发给 leader)和链式流水线:每个块的投票同时推进前面块的提交。
原版 HotStuff 的设计要点(2018 年提出,2019 年发表于 PODC):
| 改进 | 做法 |
|---|---|
| 线性通信 | 投票只发给 leader;leader 用门限签名把 2f+1 票合成一个 QC 再广播。正常路径与换主都是 O(n) |
| 乐观响应 | 新 leader 收到 n − f 个新视图消息就能推进,速度只取决于实际延迟 |
| 三阶段 | prepare、pre-commit、commit 三次 QC 后 decide。多出一阶段,是为了让换主也保持线性:新 leader 只需带上最高 QC,不必收集并转发所有人的锁 |
| 链式流水线 | 每个块的 QC 同时充当前面块下一阶段的投票,三阶段摊到三个连续块上;每轮一个块 |
| pacemaker 分离 | 活性(何时换轮、怎么同步轮次)与安全性(投什么票)分成独立模块 |
代价:提交一个块约 7 次消息延迟,两链为什么能少一轮、HotStuff 为什么不用两链,见第 11 节。
两链与三链规则:
- 三链规则(原版 HotStuff):块 B 之后连续出现三个直接相连且都有 QC 的块,B 才提交,正常情况下需要三轮投票。
- 两链规则(Jolteon,Aptos 现用的 AptosBFT):两个直接相连的已认证块即可提交,少一轮延迟;代价是换 leader 时要多带一个超时证书来证明安全,视图切换的通信量是平方级(第 11 节)。
- Aptos 在此基础上加了顺序票:节点本地聚合出 QC 后广播顺序票,2f+1 张顺序票就确定顺序,再少等一轮。详见设计文档 3.8 节。
7. HotStuff 什么情况下会换 leader?
两种情况。一是正常轮换:每一轮按确定性规则换一个 leader,不需要额外消息,这就是正常出块路径本身。二是超时换主:本轮在超时时间内没有推进,节点广播超时消息,凑够 2f+1 条组成超时证书后进入下一轮,由下一轮的 leader 接手。
正常轮换。 链式 HotStuff 里,投票发给下一轮 leader,由它聚合 QC 并提出下一个块,所以每出一个块就换一次 leader。原始论文也允许"稳定 leader",出错时才换,类似 PBFT;LibraBFT、Aptos 等生产实现都是每轮轮换。下一轮由谁当 leader,所有节点按同一规则各自算出,结果一致:
| 方式 | 做法 |
|---|---|
| 轮流 | 按验证者列表或投票权依次轮换 |
| 按信誉 | 按最近一段历史里的出块成功率与投票参与率加权选择,近期出块失败的节点很少被选中 |
Aptos 的链上共识配置默认按信誉选举(types/src/on_chain_config/consensus_config.rs 的 LeaderReputation(ProposerAndVoterV2));超时轮次的 leader 会记进下一个块元数据的 failed_proposer_indices,作为信誉计算的输入。
超时换主的触发条件。 Aptos 在超时消息里记录超时原因(consensus/consensus-types/src/round_timeout.rs 的 RoundTimeoutReason,判断逻辑在 round_manager.rs 的 compute_timeout_reason):
| 情况 | 记录的原因 | 常见根因 |
|---|---|---|
| 没收到本轮提议 | ProposalNotReceived |
leader 宕机、重启、网络断开,或者落后正在同步 |
| 收到提议,但批次数据取不齐 | PayloadUnavailable |
批次作者的数据没有传到,拉取超时 |
| 投了票,但没有形成 QC | NoQC |
票没汇齐:部分节点离线、网络分区,或者负责聚合的节点不聚合 |
| 收到提议且数据齐全,但没有投票 | Unknown |
提议没有通过安全规则校验,例如没有接在安全的 QC 之后 |
超时换主的流程(Jolteon 与 Aptos):
flowchart TD
S[进入第 r 轮,启动本地计时器] --> Q{计时器到期前收到第 r 轮的 QC,<br/>或更高轮次的证书?}
Q -- 是 --> N[进入第 r+1 轮,由第 r+1 轮的 leader 提议]
Q -- 否 --> T[广播超时消息:轮次 r,加上自己见过的最高 QC]
T --> TC{收齐 2f+1 条超时消息?}
TC -- 是 --> C[组成超时证书 TC,进入第 r+1 轮]
C --> P[第 r+1 轮的 leader 带上 TC 提议,<br/>必须接在 TC 里最高的 QC 之后]
TC -- 否 --> W[继续等待,收到别人转发的 QC 或 TC 也能直接进入新轮次]
- 轮次从哪来。 第 r 轮在两种情况下开始:收到第 r − 1 轮的 QC,或者收到第 r − 1 轮的超时证书。落后的节点从别人的消息里拿到更高轮次的证书,就直接跳到那一轮。
- 超时时长。 Aptos 的轮次超时是"初始值 × 底数的 k 次方",k 是距离上一次成功排序的轮数,有上限(
consensus/src/liveness/round_state.rs的ExponentialTimeInterval)。默认初始值 500 ms、底数 1.2、上限 10(config/src/config/consensus_config.rs,实现细节,可能随版本变化),连续失败时超时逐渐拉长,避免网络异常时轮次空转。 - 与原版 HotStuff 的区别。 原版 HotStuff 超时后只把带最高 QC 的新视图消息发给下一轮 leader,换主 O(n);Jolteon 与 Aptos 把超时消息广播给所有节点,换主 O(n²),换来两链提交(第 11 节)。
其他会换 leader 的情况:
- epoch 切换:验证者集合或共识配置变更后,按新的集合重新计算 leader 顺序。
- 信誉降权:连续出块失败的节点,在信誉窗口内很少再被选中;它不必被"罢免",只是轮不到。
换主的代价主要是等超时:至少一个超时时长,再加一轮超时消息和一次提议。所以压低换主代价的手段是调小超时(Crustle 部署假设下把初始轮次超时从 500 ms 调到约 50 ms,见设计文档 3.8 节)、按信誉避开慢节点,以及让一次 leader 故障只损失一轮(见第 13 节的 MonadBFT)。
8. PBFT、Tendermint 与 HotStuff 有什么区别?各自优劣如何?
三者都工作在部分同步网络下,都要求 n ≥ 3f+1、用 2f+1 的法定人数,都有确定性最终性。区别在于 leader 怎么换、票怎么收、块怎么排。
| 维度 | PBFT(1999) | Tendermint(2014 起) | HotStuff(2018) |
|---|---|---|---|
| 设计目标 | 许可环境下的状态机复制 | 公链、应用链上的逐块共识 | 大规模验证者下的线性通信 |
| leader | 固定主节点,被怀疑作恶才换 | 每轮轮换提议者 | 每轮轮换 leader |
| 正常路径阶段 | pre-prepare、prepare、commit | propose、prevote、precommit | prepare、pre-commit、commit,再 decide |
| 投票怎么收 | 全员广播,每个节点自己统计 | 全员 gossip,每个节点自己统计 | 只发给 leader,由它聚合成一个 QC 再广播 |
| 正常路径通信 | O(n²) | O(n²) | O(n) |
| 换主通信 | O(n³)(朴素实现要转发各节点的证书) | O(n²) | O(n) |
| 提交延迟(消息延迟 δ 的量级) | 约 3δ | 约 3δ,但每个高度都要完整走完 | 约 7δ(链式版本) |
| 流水线 | 无,可并发处理多个序号 | 无,一个高度提交完才开始下一个 | 有,每个块的 QC 同时推进前面的块 |
| 乐观响应性 | 正常路径有;换主依赖超时 | 没有,新一轮要等超时 | 有,新 leader 收齐 n − f 条新视图消息即可推进 |
| 分叉 | 无分叉,提交即最终 | 无分叉,提交即最终 | 未提交的链尾可能被分叉掉,提交后即最终 |
| 签名要求 | 普通签名或消息认证码 | 普通签名 | 门限签名或聚合签名,把 2f+1 票压成一个 QC |
PBFT
- 优势:协议最成熟、证明最完整;正常路径约 3δ,延迟最低;固定主节点时吞吐稳定,适合节点少的许可联盟链。
- 代价:平方级消息,实践中通常只到几十个节点;换主要带上所有节点的证书,朴素实现 O(n³),是最复杂、最容易写错的部分;主节点固定,恶意主节点可以刻意放慢速度而不被判定为故障。
Tendermint
- 优势:以高度为单位、每轮三步,锁机制清楚,容易理解和实现;提交即最终、没有分叉,对应用链和跨链桥友好;轮换提议者,天然公平;生态成熟,CometBFT 是 Cosmos 生态的标准,Rust 实现 Malachite 用于 Arc(第 5 节)。
- 代价:仍是平方级 gossip;不具备乐观响应性,超时要按最坏网络设置,出问题时恢复慢;没有流水线,每块都要走完两轮投票,常见配置下出块在秒级。
HotStuff
- 优势:正常路径与换主都是 O(n),能扩到上百个验证者;乐观响应;链式流水线,每轮都能出一个块;换主与正常路径走同一套流程,实现与证明比 PBFT 简单;后续演化最活跃(第 9 节)。
- 代价:基本版本提交约 7δ,为了换主线性多付了一整轮;所有投票汇到 leader,leader 是带宽与验签热点;流水线带来分叉攻击与尾部分叉两类新攻击面(第 10 节、第 13 节);依赖 BLS 一类聚合签名,计算与实现更复杂。
为什么现在主流是 HotStuff 的两链变体。 HotStuff 最大的短板是 7δ 的提交延迟。Jolteon 用平方级换主换来两链提交,把延迟降到约 5δ(原因与例子见第 11 节),再加上顺序票、乐观提议与推测最终性,定序延迟回到约 3δ 的量级,同时保留线性的正常路径与流水线。AptosBFT、Plasma 的 Fast HotStuff、MonadBFT 都属于这一支。
选型:
| 场景 | 选择 | 理由 |
|---|---|---|
| 十个左右节点的许可联盟链 | PBFT 或其变种 | 平方通信可以接受,延迟最低,协议最成熟 |
| 应用链,需要即时最终性、成熟工具链、实现简单 | Tendermint(CometBFT、Malachite) | 没有分叉,生态成熟 |
| 验证者多,要高吞吐与低延迟 | HotStuff 两链变体(Jolteon、Fast HotStuff、MonadBFT) | 线性正常路径加流水线,定序接近 3δ |
| 同机房的少量验证者(如 Crustle 的 21 个) | 三者都可行;HotStuff 系的收益主要来自流水线与乐观提议 | δ 很小,平方通信的代价被压缩,瓶颈转到签名、投票持久化与执行 |
协议优劣要结合部署前提来看:节点少、网络快时,O(n²) 与 O(n) 的差别不再决定性能,决定延迟的是轮数、签名聚合的计算量与投票落盘的耗时。
9. HotStuff 系列是如何演化的?
主线是三件事交替推进:通信量从平方降到线性,提交轮数从三轮降到两轮再到一轮推测,以及修补流水线带来的新问题(分叉、尾部分叉、leader 作恶)。数据传播与执行也逐步从共识里拆了出去。 本节给出演化路线和起点,各代协议的细节在第 10 节到第 13 节,总表与规律在第 14 节。
flowchart TD PBFT["PBFT 1999<br/>三阶段,O(n²),换主 O(n³),固定主节点"] --> TM["Tendermint / Casper FFG<br/>轮换提议者,锁机制,非乐观响应"] PBFT --> HS["HotStuff 2018<br/>三阶段,线性通信,乐观响应,链式流水线"] TM --> HS HS --> LB["LibraBFT / DiemBFT v1–v3<br/>工程化:pacemaker、安全规则模块、epoch 切换"] HS --> FHS["Fast HotStuff 2020<br/>两链提交,AggQC 防分叉"] LB --> JOL["Jolteon 2021 = DiemBFT v4<br/>两链提交,换主 O(n²),leader 信誉"] JOL --> DIT["Ditto 2021<br/>异步网络下回退到异步协议"] JOL --> APT["AptosBFT<br/>Quorum Store、顺序票、乐观提议,演进到 Raptr"] HS --> HS2["HotStuff-2 2023<br/>两阶段 + 乐观响应 + 乐观线性"] HS2 --> HS1["HotStuff-1 2024<br/>一阶段推测,slotting"] FHS --> MON["MonadBFT 2025<br/>抗尾部分叉,一轮推测最终"] JOL --> MON
9.1 起点:PBFT 的三个痛点
- 平方级通信:prepare、commit 都是全员广播,O(n²);换主要带上 prepared 证书,朴素实现 O(n³)。
- 固定主节点:主节点只在被怀疑时才换,性能与公平都依赖一个节点。
- 没有流水线:每个请求独立走三阶段。
Tendermint 与以太坊的 Casper FFG 引入了轮换提议者与锁的思想,但仍是全员投票、平方通信,且换轮要等超时,不具备乐观响应性(第 4 节)。
HotStuff 用线性通信、乐观响应与链式流水线回应这三点(第 6 节)。
9.2 LibraBFT 与 DiemBFT v1 到 v3(2019 到 2020 年)
Facebook 的 Libra(后改名 Diem)把 HotStuff 做成生产协议,仍是三链提交,主要是工程化:
- pacemaker:超时后广播超时消息,2f+1 个组成超时证书(TC),据此进入下一轮。
- 安全规则独立:记录最后投票的轮次与锁定轮次,投票前落盘;可以跑在单独进程或安全硬件里(第 20 节)。
- epoch 切换:验证者集合变更通过特殊区块完成,新旧集合之间安全交接。
- leader 选举:仍以轮换为主;按信誉选择 leader 是在 v4(Jolteon)中引入的(第 11 节)。
10. Fast HotStuff 是什么?解决了 HotStuff 的什么问题?
Fast HotStuff(Jalalzai、Niu、Feng,2020 年提出)是两链版的 HotStuff:正常路径两轮投票即可提交,提交约 5δ;换 leader 时,新 leader 必须附上 n − f 个节点的最高 QC 聚合成的 AggQC,证明自己的提议接在多数节点见过的最新块之后,从而消除分叉攻击。
正常路径(与链式 HotStuff 相同的流水线):
sequenceDiagram participant L1 as leader(v 轮) participant R as 副本 participant L2 as leader(v+1 轮) L1->>R: 提议块 B_v,携带父块的 QC R->>L2: 对 B_v 投票(签名) L2->>L2: 聚合 n−f 票得到 QC(B_v) L2->>R: 提议块 B_v+1,携带 QC(B_v) Note over R: B_v 有了 QC,且 B_v+1 直接接在 B_v 之后 R->>R: 当 B_v+1 也拿到 QC 时,B_v 满足两链规则,提交
- 两链提交规则:块 B 有 QC,它的直接子块也有 QC,B 就提交。比基本 HotStuff 的三链少一轮。
- 正常路径线性:投票只发给下一轮 leader,leader 聚合成一个 QC 广播,O(n)。
分叉攻击与 AggQC。 在两链规则下,如果新 leader 故意拿一个较旧的 QC 当父块来提议,就会把最近那个已认证、但尚未提交的块分叉掉。Fast HotStuff 的规则是:
- 超时后,每个副本把自己见过的最高 QC 签名发给新 leader。
- 新 leader 收齐 n − f 个,把它们聚合成 AggQC,放进提议里。
- 副本校验 AggQC,要求提议的父块就是 AggQC 里最高的那个 QC。
这样 leader 无法用旧 QC 替换多数节点已经见过的新 QC,分叉攻击做不成。代价是换 leader 时有 O(n²) 条消息,但每个副本只需验证两个聚合签名(AggQC 与其中最高的 QC),计算量比逐个验签低一个数量级。
和 Jolteon 的关系。 两者都是两链 HotStuff,思路接近:Jolteon 用超时证书证明安全,Fast HotStuff 用 AggQC 证明提议的父块是最新的。Plasma 的 PlasmaBFT 是流水线化的 Fast HotStuff,用 Rust 实现。
仍然存在的问题:尾部分叉。 Fast HotStuff 的超时证书能证明"上一个 leader 错过了自己的轮次",下一个 leader 可以合法地跳过前一个 leader 刚提议、还没拿到 QC 的块。恶意 leader 可以借此丢掉前一个块,抢走其中的 MEV。MonadBFT 专门解决了这一点(第 13 节、13.1 节)。
11. Jolteon 与 Ditto 是什么?为什么用平方级换主换两链提交?
Jolteon(2021 年提出,部署为 DiemBFT v4)保持正常路径线性,把换 leader 的通信改为 O(n²),换来两链提交,提交延迟从约 7δ 降到约 5δ。 理解这笔交易需要两个概念:
- QC(法定人数证书):某个块收到 2f+1 张票后聚合成的证书,表示多数节点认可了这个块。
- 锁:节点认为某个块可能已经被别人提交,就锁在它上面,之后只给延伸这个块的提议投票,防止在它旁边另起分叉。锁得早提交快,但换 leader 时容易出问题;锁得晚更安全,但要多等一轮。
延迟怎么数。 每一轮是"leader 广播提议"加"节点把票发给下一个 leader",共 2 次单向消息;每个新提议都带上一个块的 QC。
sequenceDiagram participant L as 各轮 leader participant V as 验证者 L->>V: ① 提议块 B V->>L: ② 对 B 投票 L->>V: ③ 提议 B1,带 QC(B) V->>L: ④ 对 B1 投票 L->>V: ⑤ 提议 B2,带 QC(B1) Note over V: 两链规则:B 有 QC,直接子块 B1 也有 QC,此刻提交 B,共 5 次消息 V->>L: ⑥ 对 B2 投票 L->>V: ⑦ 提议 B3,带 QC(B2) Note over V: 三链规则:还要 B2 也有 QC,此刻才提交 B,共 7 次消息
少一代后代就少 2δ,而且每个块都省。HotStuff 不直接用两链,是因为两链加上线性换主会遇到隐藏锁。
线性换主怎么做。 超时后,每个节点只给新 leader 发一条新视图消息,里面是自己见过的最高 QC;新 leader 收齐 n − f 条就从中选最高的 QC 接着提议,提议里只带这一个 QC,全程 O(n) 条消息。新 leader 收齐 n − f 条就动手、不再多等,这是乐观响应性的来源;代价是它收到的 n − f 条里可能恰好没有某个诚实节点的那条。
隐藏锁的例子。 4 个验证者 A、B、C、D,f = 1,法定人数 3 张票,其中 A 是拜占庭节点。两链协议里,节点看到某块的 QC 就锁在它上面。
sequenceDiagram participant A as A(本轮 leader,拜占庭) participant B as B(下一轮 leader) participant C as C participant D as D A->>B: 提议块 X A->>C: 提议块 X A->>D: 提议块 X B-->>A: 对 X 投票 C-->>A: 对 X 投票 D-->>A: 对 X 投票 A->>A: 聚合出 QC(X) A->>C: 下一个提议带着 QC(X),只发给 C Note over C: C 看到 QC(X),锁在 X 上 Note over A,D: 本轮超时,换 B 当 leader A->>B: 新视图消息:谎称最高 QC 是 X 的父块 D->>B: 新视图消息:最高 QC 是 X 的父块 Note over B: 加上自己已收齐 3 条(n − f),不再等 C B->>C: 提议 Y,接在 X 的父块后面,与 X 冲突 B->>D: 同上 C-->>B: C 的新视图消息(带 QC(X))这时才到 Note over C: C 锁在 X 上,拒绝给 Y 投票 Note over A: A 也不投票 Note over B,D: 只有 B、D 两张票,凑不够 3 张,这一轮失败
C 锁住了 X,新 leader B 却不知道,这就是隐藏锁。C 拒投是对的:Y 的提议里只有 X 父块的 QC,C 无从判断 B 是没收到 QC(X),还是故意藏起了它;如果 X 已经被别的节点提交,给 Y 投票就会造成分叉。所以隐藏锁不会破坏安全性,破坏的是活性。
"卡住活性"是什么意思。 不是永久停链,也不是分叉,而是诚实 leader 也可能出不了块:
- 这一轮失败后超时,换下一个 leader。轮到知道 QC(X) 的节点当 leader(例子里是 C),或者 QC(X) 经由其他消息传到了 leader 手里,链就恢复。
- 但协议给不出"哪一轮一定恢复"的保证。拜占庭节点每次当 leader 都可以重新制造一个隐藏锁(把新的 QC 只给一个诚实节点),再利用消息先后顺序让后面诚实 leader 的轮次失败。每失败一轮就白等一个超时,这段时间吞吐为零;能拖多久取决于拜占庭节点的数量和 leader 的轮换方式。
- BFT 协议对活性的要求是:GST 之后,只要轮到诚实 leader,就一定能出块。两链加线性换主达不到这个要求,所以说它没有活性保证,而不是"一定会卡死"。
三种修法:
| 协议 | 修法 | 代价 |
|---|---|---|
| Tendermint | 新一轮开始前等一个足够长的超时,保证能听到所有诚实节点的锁 | 每次换轮都要等超时,失去乐观响应性 |
| HotStuff | 加一个阶段,让锁晚一轮落下 | 每个块多等 2δ,提交从 5δ 变成 7δ |
| Jolteon | 保持两链;超时消息广播给所有节点,2f+1 条组成超时证书,证书本身就是"可以安全解锁"的证明 | 只在换 leader 时多一些消息,从 O(n) 变成 O(n²) |
- HotStuff 为什么加一阶段就够。 三链里,C 只拿到 QC(X) 时还不算锁,要等 X 的子块也有 QC 才锁。例子里 C 没锁,会给 Y 投票,B、C、D 凑够 3 票,链继续前进。一般地,多出来的阶段保证:任何诚实节点真正锁住一个块之前,已经有 2f+1 个节点见过相应的 QC,新 leader 从任意 n − f 个节点收集消息都能拿到它,所以提议里只需带一个最高 QC。这一轮等待加在了每一个块上,不管有没有换 leader。
- Jolteon 怎么做。 超时后,每个节点把超时消息广播给所有人,里面签上自己最高 QC 的轮次;凑够 2f+1 条组成超时证书 TC。新 leader 的提议必须带上 TC,并接在轮次不低于 TC 里最高 QC 的块之后。节点投票时检查的是这个条件,而不是拿自己的锁去拒绝(Aptos 的实现见
consensus/safety-rules/src/safety_rules_2chain.rs的safe_to_vote)。
flowchart TD
T[B 的这一轮超时] --> BC[每个节点广播超时消息<br/>签上自己最高 QC 的轮次]
BC --> TC[凑够 3 条组成超时证书 TC]
TC --> Q{TC 里有 C 的那条吗?}
Q -- 有 --> H1["TC 里最高的是 QC(X)<br/>新 leader 必须接在 X 后面提议"]
H1 --> OK1[C 的锁得到满足,B、C、D 投票,链继续前进]
Q -- 没有,3 条来自 A、B、D --> H2["TC 里最高的是 X 父块的 QC<br/>新 leader 接在 X 的父块后面提议,附上 TC"]
H2 --> OK2["C 看到 TC,确认 X 不可能已被提交,放心投票<br/>B、C、D 投票,链继续前进,X 被放弃"]
- 为什么 TC 足以让 C 放心。 X 要被提交,X 的子块必须拿到 QC,也就是 2f+1 个节点都见过 QC(X),它们的最高 QC 轮次都不低于 X。TC 同样由 2f+1 个节点签名,两组至少交于 f+1 个节点,其中至少一个诚实节点会如实签上不低于 X 的轮次。所以只要 TC 里最高的轮次低于 X,X 就一定没有被提交,放弃它不会造成分叉。
- 线性换主为什么做不到。 线性换主里新 leader 只转发一个最高 QC,C 拿不到"2f+1 个节点的最高 QC 都低于 X"的证据,只能拒投。要给出这个证据,就得让 2f+1 个节点各自签名并让所有节点都看到,这正是 TC,每个节点把超时消息发给所有人,换主就是 O(n²)。
为什么这笔交易划算。
- HotStuff 每个块都多付 2δ,无论网络好坏;Jolteon 只在换 leader 时多付消息,而正常情况下 leader 诚实、网络良好,大多数块不需要换 leader。
- 线性换主在实践中本来就省不下多少:换 leader 通常由超时触发,超时后要让所有节点进入同一轮,节点之间本来就要互相通知"我超时了",这一步几乎总是全员通信。HotStuff 为换主省下的消息,在同步轮次时又花掉了,却让每个块都多等了一轮。
Jolteon 部署为 DiemBFT v4 后,提交延迟从 7 次降到 5 次消息延迟,约快 30%。
- leader 信誉:换主既然更贵,就按历史表现选 leader,尽量不让表现差的节点当 leader。
- Ditto:网络进入异步时,从 leader 驱动的路径切换到不依赖超时的异步协议,恢复后再切回;解决的是"部分同步协议在长期异步下吞吐归零"的问题。
Aptos 的 AptosBFT 从 DiemBFT v4 继续演进:Quorum Store 把数据传播移出共识(第 16 节);顺序票让节点本地聚合 QC 后就广播顺序票,2f+1 张即确定顺序;乐观提议让下一轮 leader 不等 QC 就出块;执行与排序解耦,提交票单独确认执行结果(设计文档第 3 节);再往后是结合 DAG 思路的 Raptr。
12. HotStuff-2 与 HotStuff-1 改进了什么?
HotStuff-2 表明两阶段就能同时做到乐观响应与乐观线性通信;HotStuff-1 在此基础上加一阶段推测,让客户端更早得到确认。
12.1 HotStuff-2(Malkhi 与 Nayak,2023 年)
- 结论:两阶段就够了。它同时做到:视图内两阶段提交、乐观响应、乐观情况下线性通信、最坏情况 O(n²)。此前普遍认为这几个性质不能兼得,HotStuff 为此用了三阶段。
- 要点:新 leader 如果手里有上一个视图产生的证书,就说明没有人能锁在更高的块上,可以立即推进,保持响应性;只有在前一个视图失败时,才需要多等一段时间来收集各节点的锁,再安全地提议。正常情况不等,异常情况才付等待的代价。
- 意义:在不牺牲响应性的前提下把 HotStuff 的三阶段降到两阶段,协议几乎没有增加复杂度。
12.2 HotStuff-1(Kang、Gupta、Malkhi、Sadoghi,2024 年)
- 做法:在 HotStuff-2 基础上加一阶段推测:副本在第一阶段之后就推测执行,并提前向客户端发出"最终确认",客户端收到足够多一致的推测回复即可确认,比 HotStuff-2 少两跳网络延迟,同时保持线性通信。
- 前缀推测困境:流水线协议里,前一个块的推测结果依赖更早的块,而流水线协议不能像固定主节点协议那样停下来回滚修复,推测一旦出错影响一整串前缀。HotStuff-1 是第一个在流水线协议里解决这个问题的协议。
- slotting:每个 leader 在自己的任期内可以连续提议多个块(多个 slot),抵御两类 leader:出于利益故意拖延的理性 leader,以及故意破坏别人进度的恶意 leader。
13. MonadBFT 是什么?怎样防止尾部分叉?
MonadBFT 是 HotStuff 家族的流水线式协议,正常路径线性通信,一轮后给出推测最终性、两轮后最终确定,并且抵抗尾部分叉。 以下按 Monad 官方文档描述。
正常路径:
sequenceDiagram participant A as Alice(K 轮 leader) participant V as 验证者 participant B as Bob(K+1 轮 leader) participant C as Charlie(K+2 轮 leader) A->>V: 提议块 A V->>B: 对块 A 投票,直接发给下一轮 leader B->>B: 聚合超多数票得到 QC(A) B->>V: 提议块 B,携带 QC(A) Note over V: 块 A 进入 Voted 状态:推测最终 V->>C: 对块 B 投票 C->>V: 提议块 C,携带 QC(B) Note over V: 块 A 进入 Finalized 状态:最终确定
- 提议内容:轮次、区块(轮次、有序交易列表、QC)、可选的超时证书或无背书证书、leader 签名。
- 流水线:每一轮同时带来一个新载荷和对上一个提议的 QC,于是父块推测最终、祖父块最终确定。
- 推测最终性:只有在该块的提议者双签(同一高度签了两个不同的块)时才会回滚;双签有两块签名为证,可以问责。所以应用可以在一轮后就基于它执行交易。Monad 文档给出的是 1 个 slot 推测最终、2 个 slot 最终确定;slot 时长在公开资料里有 300 ms 与 400 ms 两种说法,属于公开数字,未在本项目环境复现。
超时与尾部分叉防护:
- leader 没有按时出块或下一个 leader 没聚合出 QC 时,验证者全员广播超时消息,每条消息带上自己的"tip",即自己见过的最新提议去掉载荷后的部分。
- 超多数超时消息组成超时证书,其中记录所有 tip 和轮次最高的"high tip"。
- 重新提议规则:新 leader 必须重新提议 high tip 指向的那个块,除非能证明它不可能拿到 QC。
- 无背书证书(NEC):要跳过那个块,新 leader 必须收集 2f+1 个"我没见过这个块"的签名声明。即使其中 f 个是拜占庭节点,仍有 f+1 个诚实节点没见过它,说明它不可能凑够法定人数。
这样,诚实 leader 提议的块不会因为下一个 leader 故意不聚合投票而被丢掉,MEV 抢夺式的尾部分叉被消除。
特点汇总: 正常路径消息数与验证数都线性,采用"leader 扇出、下一轮 leader 扇入"的通信模式;一个 leader 故障只造成一次超时延迟;具备乐观响应性。实现是 Rust 的 category-labs/monad-bft,与 C++ 的执行客户端 category-labs/monad 分开,均以 GPL-3.0 开源。
13.1 Fast HotStuff、Jolteon、MonadBFT 还会尾部分叉吗?
按论文描述的协议,Fast HotStuff 与 Jolteon 会尾部分叉,MonadBFT 不会。Aptos 实现的 Jolteon 默认把投票广播给所有验证者,"下一轮 leader 扣住投票"这条攻击路径不再成立。
尾部分叉是怎么发生的(以第 r 轮的块 B_r 为例):
- 诚实 leader 提议 B_r,2f+1 个验证者投票。
- 线性通信的流水线 HotStuff 里,投票只发给下一轮 leader L_{r+1},只有它能聚合出 QC(B_r)。
- L_{r+1} 故意不聚合、不提议,全网超时。
- 超时消息只携带各节点的最高 QC,没有人持有 QC(B_r),新 leader 可以合法地接在 B_{r−1} 之后提议,B_r 被丢掉。
- L_{r+1} 把 B_r 里的交易和 MEV 挪进自己后面的块。
成立的条件有两个:投票只汇到下一轮 leader,以及超时消息只记录 QC,不记录"投过票但还没形成 QC 的块"。去掉任意一个,攻击就做不成。
| 协议 | 会不会尾部分叉 | 原因 |
|---|---|---|
| Fast HotStuff | 会 | AggQC 由 n − f 个节点的最高 QC 聚合而成,防的是新 leader 用旧 QC 分叉掉已经有 QC 的块(第 10 节)。QC(B_r) 从未形成,AggQC 里最高的是 QC(B_{r−1}),跳过 B_r 完全合规 |
| Jolteon(论文) | 会 | 投票同样只发给下一轮 leader;超时证书只记录各节点最高 QC 的轮次,B_r 没有 QC,不受保护 |
| Jolteon(Aptos 实现) | 这条攻击路径不成立 | 默认广播投票,收齐 2f+1 票的诚实节点都能自己聚合出 QC(B_r);它们的超时消息带上这个 QC,新 leader 必须接在 B_r 之后 |
| MonadBFT | 不会 | 超时消息携带 tip,新 leader 必须重新提议 high tip 指向的块,除非拿出无背书证书(第 13 节);重新提议的是原块,内容不能替换 |
Aptos 的依据(aptos-core 源码):
config/src/config/consensus_config.rs:broadcast_vote默认为true。consensus/src/round_manager.rs:broadcast_vote为真时调用self.network.broadcast_vote(vote_msg),否则只send_vote给get_valid_proposer(proposal_round + 1)。consensus/consensus-types/src/timeout_2chain.rs:超时消息带hqc_round,超时证书校验hqc_round等于其中签名轮次的最大值。consensus/safety-rules/src/safety_rules_2chain.rs的safe_to_vote:块的轮次等于其 QC 轮次加一,或者等于超时证书轮次加一且 QC 轮次不低于证书里的最高 QC 轮次,才投票。新 leader 因此无法绕过证书里记录的 QC(B_r)。
投票广播之后,B_r 仍可能被放弃的情况只剩一种:投票在网络里延迟过久,组成超时证书的 2f+1 个节点都还没聚合出 QC(B_r)。这是网络异步造成的,leader 无法主动制造。
两种修法:
| 修法 | 代表 | 正常路径通信 | 代价 |
|---|---|---|---|
| 投票广播给所有验证者,人人都能聚合 QC | Aptos | O(n²) | 消息数与验签量随 n² 增长,节点多时开销明显 |
| 超时消息携带 tip,强制重新提议 | MonadBFT | O(n),保持线性 | 超时路径更复杂,要处理 tip 与无背书证书 |
Crustle 部署假设下验证者固定为 21 个且同机房,每轮投票广播约 21 × 20 = 420 条消息,开销可以忽略,沿用 Aptos 默认的投票广播即可,不需要引入 MonadBFT 的超时机制。
14. HotStuff 系列演化总表:各代解决了什么问题?
14.1 一张表看演化
| 协议 | 年份 | 正常路径提交(消息延迟量级) | 正常路径通信 | 换主通信 | 乐观响应 | 解决的主要问题 | 详见 |
|---|---|---|---|---|---|---|---|
| PBFT | 1999 | 约 3δ | O(n²) | O(n³) | 否 | 实用 BFT 的起点 | 第 3 节 |
| Tendermint | 2014 起 | 约 3δ,逐高度 | O(n²) | O(n²) | 否 | 轮换提议者、即时最终性 | 第 4 节 |
| HotStuff | 2018 | 约 7δ(链式) | O(n) | O(n) | 是 | 线性通信、流水线 | 第 6 节 |
| LibraBFT / DiemBFT v1–v3 | 2019 | 约 7δ | O(n) | O(n) | 是 | 工程化落地 | 9.2 节 |
| Fast HotStuff | 2020 | 约 5δ | O(n) | O(n²) 消息,验签 O(1) | 是 | 两链提交、防分叉 | 第 10 节 |
| Jolteon(DiemBFT v4) | 2021 | 约 5δ | O(n) | O(n²) | 是 | 两链提交、比 v3 快约 30% | 第 11 节 |
| HotStuff-2 | 2023 | 约 5δ | 乐观 O(n) | 最坏 O(n²) | 是 | 两阶段与响应性兼得 | 12.1 节 |
| HotStuff-1 | 2024 | 推测确认比 HotStuff-2 少两跳 | O(n) | 最坏 O(n²) | 是 | 流水线下的安全推测 | 12.2 节 |
| MonadBFT | 2025 | 推测最终约 3δ,最终约 5δ | O(n) | O(n²) | 是 | 抗尾部分叉、推测最终性 | 第 13 节 |
延迟一列只是消息轮数的量级,不同论文的计数口径略有差异;实际延迟还要加上签名、验签、持久化与打包时间。
14.2 演化背后的几条规律
- 正常路径优先。 大部分时间网络正常、leader 诚实,协议越来越把优化集中在正常路径,把代价推到换主路径(Jolteon 用平方级换主换两链提交)。
- 轮数是延迟的主要来源。 三链到两链、两链到一轮推测,每减一轮都直接减少数个 δ;同机房部署时 δ 很小,轮数的影响相应变小,签名与持久化的开销变得显眼。
- 流水线带来新攻击面。 链式结构让下一个 leader 能影响前一个块的命运,于是出现分叉攻击(Fast HotStuff 修补)与尾部分叉(MonadBFT 修补)。
- 把不必要的事移出共识。 数据传播交给 Quorum Store 或 DAG,执行在排序之后异步进行,共识只对顺序和结果的摘要投票。
15. 什么是 Optimistic BFT?
"乐观"指假设网络良好、节点都诚实在线,走一条消息轮数更少的快速路径;一旦假设不成立,就回退到常规路径。安全性始终由 2f+1 的法定人数保证,乐观只影响常态下的速度。
这个词在共识文献里有三种用法,面试时先说清楚指哪一种:
| 用法 | 含义 | 例子 |
|---|---|---|
| 乐观快速路径 | 所有节点或超多数节点都及时回复时,一轮完成;否则补一轮 | Zyzzyva、SBFT、Alpenglow |
| 乐观响应性 | 网络好时,推进速度只取决于实际消息延迟,不必等最坏情况的超时 | HotStuff、Fast HotStuff、Tendermint 的部分实现 |
| 乐观执行 / 推测最终性 | 排序还没最终确定就先执行,或者先给出一个极少回滚的"推测最终" | Aptos 在收到提议时就开始执行(设计文档 3.7 节)、MonadBFT 的推测最终性(第 13 节) |
HotStuff 家族不属于狭义的 Optimistic BFT。 狭义的 Optimistic BFT 指第一种用法:票数凑够比 2f+1 更多(通常是全部 3f+1)时少走一轮,凑不够再退回常规路径。Fast HotStuff、Jolteon、MonadBFT 只有一条路径,每一步都只要 2f+1 票,减少轮数靠的是链式流水线和两链提交规则;它们只在后两种意义上"乐观":都具备乐观响应性,MonadBFT 还有推测最终性。
| 协议 | 乐观快速路径 | 推测执行 / 推测最终 |
|---|---|---|
| Zyzzyva | 有:3f+1 个一致回复一轮完成,否则多一轮 | 有:副本推测执行,必要时回滚 |
| SBFT | 有:全部节点签名一轮完成,否则退回两轮 | 没有 |
| Alpenglow(Votor) | 有:80% 质押一轮最终确认,否则两轮各 60% | 没有 |
| Fast HotStuff | 没有:始终 2f+1,两链提交 | 没有 |
| Jolteon | 没有:始终 2f+1,两链提交 | 没有 |
| MonadBFT | 没有:始终 2f+1 | 有:一轮后推测最终,只有提议者双签才会回滚 |
- Fast HotStuff 的 "Fast" 指从三链提交降到两链提交,是在单一路径上少一轮,不是快速路径。
- Jolteon 与 Ditto 论文标题里的"网络自适应、异步回退",指网络进入异步时回退到异步协议(第 11 节),是按网络状况切换路径,也不是按票数走快速路径。
- 两类设计的取舍:快速路径要求几乎所有节点同时及时回复,一个节点慢就退回慢路径;HotStuff 家族每一步只要 2f+1 票,最多 f 个节点慢或故障也不影响速度,尾延迟更稳定。
Zyzzyva:推测执行加客户端提交。
- 主节点给请求分配序号,发给所有副本。
- 副本不等协商,直接按序号推测执行,把结果回给客户端。
- 客户端收到全部 3f+1 个一致的回复,请求完成,只用一轮。
- 只收到 2f+1 到 3f 个一致回复时,客户端把这 2f+1 个回复组成提交证书发给副本,副本确认后完成,多一轮。
- 副本之间的执行历史不一致时,靠视图切换与回滚修复,所以副本要能撤销推测执行。
SBFT:聚合签名加快速路径。 由收集者聚合各副本的签名,所有节点都签时一轮完成;有节点缺席时退回两轮的常规路径。聚合签名让每个节点只需验证一个签名,通信量从平方级降到线性。
Solana Alpenglow(2025 年提出)的 Votor。 一轮投票凑到 80% 质押即最终确认;凑不到时,两轮各凑 60% 也能最终确认。两条路径同时跑,谁先满足用谁。
Aptos 的乐观提议与顺序票。 下一轮 leader 投完票就发下一轮提议,不等 QC 形成;节点本地聚合出 QC 就发顺序票,2f+1 张顺序票即确定顺序。网络异常时退回常规的超时与换主。
代价。 快速路径要求更多节点同时在线、网络更稳定;回退路径多出一轮,而且要额外处理"快速路径与常规路径同时进行"时的一致性,协议与证明都更复杂。要和 Optimistic Rollup 的"乐观"区分:后者指默认状态正确、靠欺诈证明纠错,与共识协议无关。
16. leader 制共识的瓶颈在哪里?Narwhal、Quorum Store、DAG 如何解决?
瓶颈是 leader 的上行带宽:提议块携带全部交易时,leader 一个节点要把整块数据发给所有验证者。三种方案从两个方向解决:Narwhal 与 Quorum Store 把"数据传播"从共识里拆出去,由所有验证者并行完成;DAG 类共识更进一步,连"排序"也不再依赖单一 leader。
| 方案 | 数据传播 | 排序 |
|---|---|---|
| 传统 leader 制 | leader 在提议块里携带全部交易 | leader 提议,全员投票 |
| Quorum Store | 每个验证者各自广播批次,凑 2f+1 签名得到可用性证明 | 仍由 leader 提议,只放批次摘要 |
| Narwhal 加 HotStuff 类共识 | 每个验证者每轮广播一个 DAG 顶点,顶点带批次摘要和对上一轮顶点的链接 | 由 leader 制共识对顶点排序 |
| DAG 类共识(Tusk、Bullshark、Shoal) | 同 Narwhal | 不需要单独的 leader 提议,直接用锚点规则从 DAG 读出全序(第 17 节) |
16.1 Narwhal 与 Quorum Store 有什么区别?
Quorum Store 是 Narwhal 的简化版:保留"批次加 2f+1 可用性签名",去掉了 DAG 的轮次和父链接。 Aptos 源码里的 Quorum Store 说明把它描述为基于 Narwhal 的数据传播层(AIP-26)。
Narwhal(Danezis 等,2022 年发表于 EuroSys)按轮次推进:
- 每个验证者分成一个主节点(primary)和若干工作节点(worker)。worker 负责收交易、打批次、把批次发给其他验证者的 worker;这部分可以横向扩到多台机器。
- 每一轮,主节点生成一个顶点,里面放本轮自己 worker 产出的批次摘要,以及对上一轮至少 2f+1 个已认证顶点的链接。
- 顶点可靠广播给所有验证者,收到者检查数据与链接都在本地后签名;凑够 2f+1 个签名,顶点成为已认证顶点,再广播出去。
- 某个验证者手里有了本轮 2f+1 个已认证顶点,就进入下一轮。
Quorum Store 没有轮次:
- 每个验证者按自己的节奏打批次,直接发给所有验证者。
- 收到者存下批次、签名回给作者;凑够 2f+1 个签名形成可用性证明,广播出去。
- 批次之间没有任何链接,谁先谁后完全由 leader 在提议里决定。
| 维度 | Narwhal | Quorum Store |
|---|---|---|
| 结构 | 按轮次组织的 DAG:每个顶点链接上一轮 2f+1 个顶点 | 彼此独立的批次,没有轮次与链接 |
| 可用性证明 | 已认证顶点:证明顶点本身及其全部因果历史都可取 | 可用性证明:只证明这一个批次可取 |
| 公平性 | 每轮至少 2f+1 个验证者的顶点进入 DAG,排序时会一并带上 | 取决于 leader 选哪些证明;一个 leader 跳过某个作者,后面的 leader 仍会带上 |
| 与排序的关系 | 可以直接在 DAG 上做排序(Tusk、Bullshark),也可以交给 HotStuff 类协议 | 只能配合 leader 制共识(Aptos 配合 Jolteon) |
| 回收 | 按轮次回收旧顶点 | 按批次过期时间回收 |
| 扩展 | 主节点与 worker 分离,worker 可以跨机器扩展 | 在验证者进程内完成,不做跨机器拆分 |
| 复杂度与开销 | 高:每轮每个验证者都要可靠广播并收集签名,维护 DAG | 低:只有批次与证明,没有轮次同步 |
Aptos 选 Quorum Store 的理由是:数据传播的收益(均摊 leader 带宽、可用性先于排序)已经拿到,而不必付 DAG 的轮次同步与存储成本;排序仍交给成熟的 Jolteon。Aptos 另外也实现了 DAG 模式,两者的对比与例子见设计文档 3.3 节。
17. DAG 类共识为什么提出?它怎么给交易排序?有哪些协议?
DAG 类共识让所有验证者同时出块。每个验证者每一轮都发出一个"顶点",顶点里写着自己本轮打包的交易批次,以及自己已经收到了上一轮的哪些顶点。顶点之间互相引用,连成一张所有节点共享的图;每个节点再按同一条规则,从图里读出交易的先后顺序。它解决的是 leader 制共识的两个问题:交易数据都要经过 leader 发出去;leader 一慢,全网都要等。
17.1 为什么要提出 DAG:leader 制共识的两个问题
设想 4 个验证者 A、B、C、D,每一轮由一个 leader 出块,每块 10 MB 交易(数字只用来说明量级):
- 带宽都压在 leader 身上。 本轮 leader 是 A,A 要把 10 MB 分别发给 B、C、D,上行 30 MB;这段时间 B、C、D 的上行带宽基本闲着。验证者越多,leader 要发的越多,整条链的吞吐由一台机器的网卡决定。
- leader 一慢,全网等。 如果 A 这一轮卡住了(垃圾回收停顿、网络抖动、宕机),就没有提议,其他人只能等超时(Aptos 的默认初始值是 500 ms,见第 7 节),这段时间整条链一笔交易都不处理。
Quorum Store 与 Narwhal 解决第 1 个问题:交易数据由每个验证者各自提前分发,leader 的提议里只放数据的摘要(第 16 节)。DAG 类共识进一步解决第 2 个问题:不再有"本轮必须等某个 leader 出块"这件事,每个验证者每轮都出自己的顶点,谁慢了就不等谁。
17.2 四个基本概念:批次、顶点、链接、已认证
批次:一包交易。 验证者不断收到用户的交易,攒一小段时间就打成一包,这一包叫批次。例如 A 在 50 ms 内收到 500 笔下单交易,打成批次 a1,发给其他所有验证者。批次用摘要来指代:对批次内容算一个 32 字节的哈希,内容改动一个字节,摘要就完全不同,所以摘要相当于批次的指纹。之后大家只需要说"摘要为某某的那个批次",不必再传一遍 500 笔交易。
顶点:一张签了名的小纸条。 每个验证者每一轮写一张,上面有四样东西:
| 字段 | 例子:A 在第 2 轮的顶点 A2 |
|---|---|
| 作者和轮次 | A,第 2 轮 |
| 本轮批次的摘要 | 批次 a2 的摘要 |
| 链接:上一轮若干顶点的摘要 | A1、B1、C1 的摘要 |
| 作者签名 | A 对以上内容的签名 |
顶点里只放摘要,不放交易本身,所以很小(量级是几百字节到几 KB);交易数据已经通过批次单独发过了。
链接:我已经收到并保存了这些顶点。 A2 里写上 B1 的摘要,意思是"A 写 A2 时,已经拿到了 B1,以及 B1 里的批次"。链接就像论文的参考文献:引用的是确定的内容(摘要保证内容不能被替换),而且只能引用更早的东西。顺着链接往回走,从 A2 能走到 A1、B1、C1,再往回能走到它们链接的顶点;一路能走到的全部顶点,叫 A2 的因果历史,也就是 A2 的作者写下它时已经看到的全部内容。
链接只指向上一轮,图里的箭头都指向过去,不会绕成一个圈,这就是"有向无环图"(Directed Acyclic Graph,DAG)这个名字的由来。
已认证:至少 3 个节点确认保存了它。 A 写好 A2 后发给所有人。B 收到后检查三件事:A 的签名对不对;批次 a2 自己收到了没有;A2 链接的 A1、B1、C1 自己有没有。都满足,就回给 A 一个签名,表示"我已保存 A2 及它引用的全部数据"。A 收齐 3 个签名(算上自己的,即 2f+1 个),把它们附在 A2 上,A2 就成了已认证顶点,A 再把它广播出去。
sequenceDiagram participant A participant B participant C participant D A->>B: 顶点 A2(批次 a2 的摘要,链接 A1、B1、C1) A->>C: 顶点 A2 A->>D: 顶点 A2 Note over B,C: 检查签名、批次 a2、A1/B1/C1 是否都在本地 B-->>A: 签名:已保存 C-->>A: 签名:已保存 Note over A: 加上自己的签名,凑够 3 个 A->>B: 已认证的 A2(附 3 个签名) A->>C: 同上 A->>D: 同上 Note over D: D 的签名还没回来也不影响,3 个已经够了
认证带来两个保证:
- 数据一定取得到。 3 个签名里至少 2 个来自诚实节点,它们都保存了 A2 及它引用的全部数据。之后谁缺数据,都能从它们那里取到。
- 作者不能两面说。 诚实节点在同一轮只给同一个作者签一次。假如 A 想给 B 看一个版本的 A2、给 C 看另一个版本,两个版本各需要 3 个签名,4 个节点里至少有 2 个节点两个版本都签了,其中至少 1 个是诚实节点,这不可能发生。所以每个作者每一轮最多只有一个已认证顶点。
进入下一轮:收齐上一轮 3 个已认证顶点。 验证者手里一有第 r 轮的 3 个已认证顶点,就可以写第 r+1 轮的顶点,并链接这 3 个。不必等第 4 个,所以最慢的那个节点拖不住其他人。
17.3 例子:DAG 怎么一轮一轮长出来
4 个验证者 A、B、C、D,f = 1,法定人数 3。每个验证者每轮打一个批次(A 的是 a1、a2、a3……),写一个顶点:
| 轮次 | A | B | C | D |
|---|---|---|---|---|
| 1 | A1:批次 a1 | B1:批次 b1 | C1:批次 c1 | D1:批次 d1 |
| 2 | A2:批次 a2,链接 A1、B1、C1 | B2:批次 b2,链接 A1、B1、D1 | C2:批次 c2,链接 B1、C1、D1 | D2:批次 d2,链接 A1、C1、D1 |
| 3 | A3:链接 A2、B2、C2 | B3:链接 A2、B2、D2 | C3:链接 B2、C2、D2 | D 这一轮慢了,还没发出顶点 |
画成图,箭头从顶点指向它链接的上一轮顶点:
flowchart RL
subgraph R3 [第 3 轮]
A3[A3]
B3[B3]
C3[C3]
end
subgraph R2 [第 2 轮]
A2["A2 = 锚点"]
B2[B2]
C2[C2]
D2[D2]
end
subgraph R1 [第 1 轮]
A1[A1]
B1[B1]
C1[C1]
D1[D1]
end
A2 --> A1 & B1 & C1
B2 --> A1 & B1 & D1
C2 --> B1 & C1 & D1
D2 --> A1 & C1 & D1
A3 --> A2 & B2 & C2
B3 --> A2 & B2 & D2
C3 --> B2 & C2 & D2
几点说明:
- 为什么 A2 没链接 D1。 A 准备写 A2 时,手里已经有 A1、B1、C1 三个已认证顶点,够 3 个就动手了;D1 的证书晚到了一点,没赶上。D1 并没有丢,B2、C2、D2 都链接了它。
- D 慢了,其他人照常前进。 第 3 轮 D 没发出顶点,A、B、C 各自收齐第 2 轮的 3 个已认证顶点就写了第 3 轮。在 leader 制共识里,如果这一轮的 leader 恰好是 D,全网就要等超时。
- 所有节点拼出的是同一张图。 每个节点都在本地拼这张图,收到顶点的先后可能不同;但每个作者每轮最多一个已认证顶点,顶点的链接又由摘要固定,所以某个顶点一旦被收到,它的内容和它的因果历史在所有节点那里都一样。
17.4 从图里读出顺序:锚点规则
图只记录了"谁在什么时候看到了什么",还没有给交易排出先后。排序靠一条所有节点事先都知道的确定性规则,每个节点在本地自己算,不需要再为排序单独发投票。以 Bullshark 为例:
- 每两轮指定一个锚点。 锚点是事先约定好的某个验证者在该轮的顶点(按轮换或按信誉决定,所有节点算出的一样),例子里第 2 轮的锚点是 A2。锚点的作用像"这一段的结账人":它被提交时,它看到的全部内容一起排序。
- 链接就是投票。 第 3 轮的顶点如果链接了 A2,就算给 A2 投了一票。例子里 A3、B3 链接了 A2,C3 没有,A2 得到 2 票。这些票不需要额外发送,图里本来就有。
- 凑够 f+1 票就提交锚点。 f+1 = 2,A2 被提交。为什么 2 票就够:以后的锚点在第 4 轮,它必须链接第 3 轮的 3 个顶点;第 3 轮最多只有 4 个顶点,其中没链接 A2 的最多 2 个(C3,以及可能晚到的 D3),所以任何 3 个里必然有 A3 或 B3,顺着它就能走到 A2。一般地,f+1 个链接者与任意 2f+1 个顶点至少有一个重合,所以以后的每个锚点都能追溯到 A2,所有节点对"A2 已提交"不会产生分歧。
- 把锚点的因果历史排好。 从 A2 出发能走到的、还没排过序的顶点是 A1、B1、C1 和 A2 自己。按"轮次从小到大,同一轮按验证者编号"排成 A1、B1、C1、A2,再把它们的批次依次展开:批次 a1 的 500 笔交易、b1 的交易、c1 的交易、a2 的交易,这就是这一段的交易顺序。同一笔交易如果出现在两个批次里,执行时跳过后一次。
- D1 在下一段排进来。 假设第 4 轮的锚点是 C4,它链接了 A3、B3、C3,并在第 5 轮拿够了票而提交。从 C4 能走到的、还没排过序的顶点是 D1、B2、C2、D2、A3、B3、C3 和 C4,按同样的规则排成 D1、B2、C2、D2、A3、B3、C3、C4。数据不会丢,只是晚一段排序。
- 锚点没凑够票怎么办。 比如 A 所在的节点很慢,第 3 轮没人链接 A2,A2 就暂不提交。下一个锚点提交时,如果从它能走到 A2,就先把 A2 这一段按顺序补上,再排自己的;走不到就跳过 A2。所有节点按同一规则判断,结果一致。
为什么所有节点排出同一个顺序。 三件事同时成立:大家拼出的是同一张图(17.3 节);锚点是谁、提交的条件、历史怎么排序,都是事先写死的规则;一个锚点的因果历史由链接的摘要唯一确定。每个节点自己算,结果必然一样。
17.5 DAG 解决了什么:为什么网络抖动时吞吐不会掉到零
| 情况 | leader 制共识 | DAG 类共识 |
|---|---|---|
| 交易数据怎么发出去 | 全部经由 leader 发给所有人,吞吐受 leader 一台机器的上行带宽限制 | 每个验证者发自己的批次,带宽由所有验证者分摊 |
| 本轮 leader(DAG 里是锚点的作者)很慢或宕机 | 这一轮没有提议,所有人等超时,这段时间吞吐为零 | 其他验证者照常广播批次、产生顶点,DAG 继续增长;这个锚点不提交,下一个锚点提交时一次把积累的顶点全部排进去 |
| 个别验证者网络差 | leader 恰好是它时整轮变慢 | 只是它的顶点晚到或缺席,每轮只需 3 个顶点就能推进 |
| 恢复后 | 从超时后的新一轮重新开始 | 已经传播并认证的数据直接排序,没有浪费 |
直观地说:leader 制是"一个人拿着话筒念交易,他卡住全场就停";DAG 是"所有人各自往一张公共板上贴便签,并标明看到了哪些别人的便签"。没有专门的排序员:每个人手里都有同一份规则,自己把板上的便签排好;规则指定每隔一段由某个人的便签当"结账点"(锚点),结账点一到,之前的便签就按规则排定。某个人卡住,板上照样在增加便签。
锚点卡住会怎样。 DAG 里最接近"排序员"的是锚点,但锚点只是图里的一个顶点,排序由每个节点在本地完成:
| 卡住的是谁 | 后果 | 恢复 |
|---|---|---|
| 锚点的作者(慢或宕机) | 锚点拿不到 f+1 个链接,或者根本没发出来,这一段暂不提交;批次照常分发,DAG 照常增长 | 下一个锚点提交时,把积累的顶点一次排进去;被跳过的锚点如果能从新锚点走到,就先补上它那一段(17.4 节第 6 步)。代价是这一段的排序延迟变长:Bullshark 每两轮一个锚点,至少多等两轮 |
| 某个节点自己(本地卡住) | 只是它自己落后,别人照常排序、提交 | 恢复后向别人拉取缺的顶点和批次,按同一规则重算,得到与别人相同的顺序 |
| 超过 f 个节点同时卡住 | 每轮凑不齐 2f+1 个已认证顶点,DAG 不再增长,链停 | 与 leader 制一样,这是 BFT 的容错上限;节点恢复到 2f+1 个以上后继续 |
锚点作者卡住也不是完全没有代价。Bullshark 的部分同步版本为了让诚实锚点有机会拿够链接,节点在锚点所在轮会等锚点一段时间,等不到才超时进入下一轮(按论文描述,未实测),所以锚点作者宕机时,DAG 的增长会慢一个超时;批次的分发不受影响。后续协议从两个方向减少这个代价:Shoal 按信誉选锚点,少选慢节点,并让每一轮都有锚点;Mysticeti 等协议缩短从顶点到提交的路径(17.7 节)。
17.6 代价在哪
- 消息更多。 每一轮,每个验证者都要可靠广播一个顶点、收 2f+1 个签名、再广播证书,一轮约 3n² 条消息;leader 制每块主要是一次提议广播加 n 张票(投票广播时约 n²)。节点多时差别明显。
- 排序更慢。 一个顶点从产生到被排序,至少要经过它所在轮次、锚点轮、投票轮,而每一轮都包含一次可靠广播(约 2 到 3 次消息延迟);不是锚点的顶点还可能要等下一个锚点。Bullshark 的延迟明显高于两链 HotStuff。
- 实现复杂。 要存储和回收 DAG、拉取缺失的顶点、处理异步到达的顶点、保证各节点的排序规则完全一致。
17.7 主要协议
| 协议 | 要点 |
|---|---|
| Narwhal + Tusk | Narwhal 负责按轮次构建已认证的 DAG;Tusk 在其上做异步排序,用公共随机数事后选锚点,不依赖超时 |
| Bullshark | 部分同步网络下的排序规则:每两轮一个锚点,下一轮 f+1 个顶点链接它即提交(17.4 节的例子) |
| Shoal | 在 Bullshark 上做流水线,让每一轮都有锚点,并按信誉选锚点,降低排序延迟;Aptos 的 DAG 模式基于这一思路 |
| Mysticeti(Sui) | 不再为每个顶点收集签名证书,直接把链接当作隐含投票,提交只需约 3 次消息延迟,消息量也大幅减少 |
从 Bullshark 到 Mysticeti 的演进方向,就是在保留"没有单一 leader 瓶颈、网络抖动不停摆"的前提下,把排序延迟和消息量压下来。
18. 这几种共识怎么对比?
| 协议 | 正常路径延迟(消息延迟 δ 的量级) | 正常路径通信 | 换主通信 | 流水线 | 适用 |
|---|---|---|---|---|---|
| PBFT | 约 3δ | O(n²) | O(n³)(朴素) | 否 | 小规模许可链 |
| Tendermint | 约 3δ,逐高度 | O(n²) | O(n²) | 否 | 应用链、需要即时最终性;Rust 实现 Malachite 用于 Arc |
| HotStuff | 约 7δ | O(n) | O(n) | 是 | 大规模验证者 |
| Jolteon(两链) | 约 5δ | O(n) | O(n²) | 是 | Aptos、Diem |
| Fast HotStuff(两链) | 约 5δ | O(n) | O(n²) 条消息,每个节点只验 2 个聚合签名 | 是 | Plasma 的 PlasmaBFT |
| MonadBFT | 推测最终约 3δ,最终约 5δ | O(n) | O(n²)(超时消息全员广播) | 是 | Monad |
| 加顺序票 | 约 3δ 定序 | O(n²)(投票广播) | O(n²) | 是 | Aptos 当前配置 |
| Bullshark / Shoal | 约 4 到 6 次可靠广播 | O(n²) 每轮 | 无单一 leader | 是 | 高吞吐、网络不稳定 |
| Mysticeti | 约 3δ | O(n²) 每轮 | 无单一 leader | 是 | Sui |
延迟一列只是消息轮数的量级,实际还要加上签名与验签、持久化和打包时间。
19. 怎样设计一个延迟低于 200 ms 的共识?
先算延迟预算:共识延迟约等于"消息轮数 × 单向网络延迟 + 每轮的计算与持久化",然后从这几项分别压缩。
- 网络延迟决定下限。 跨大洲单向延迟 50 到 150 ms,走 3 次消息延迟就可能超过 200 ms;同一地区单向约 1 到 10 ms,同机房亚毫秒。验证者的地理分布是第一决定因素,Hyperliquid、Crustle 的集中部署都是为此。
- 减少消息轮数。 用两链规则、顺序票或快速路径,把定序压到约 3δ;乐观提议让下一个 leader 不等 QC 就出块。
- 排序与执行解耦。 共识只对交易顺序投票,执行结果在后面单独确认(Aptos 的提交票、Monad 的延迟执行),执行时间不在共识的关键路径上。
- 数据传播与排序解耦。 用 Quorum Store 或 DAG 提前把交易数据分发出去,提议块只有几 KB,不受块大小影响。
- 压缩每轮的计算。 BLS 聚合签名批量验证;投票的持久化用顺序写的 WAL;签名验证并行化。
- 超时与换主。 超时按实际网络设置,而不是按最坏情况;leader 按信誉选择,慢节点不当 leader。
20. 从零实现一个 BFT 共识,要注意哪些工程问题?
- 安全规则独立并持久化:记录最后投票的轮次与锁定的块,投票前先落盘,崩溃重启后绝不对同一轮投两次不同的票。Aptos 把它放在单独的 SafetyRules 组件里,可以跑在独立进程或安全硬件中。
- 轮次推进(pacemaker):收到 QC 或超时证书就进入下一轮;超时指数退避,防止网络异常时轮次空转。
- 同步:落后的节点要能向别人拉取缺失的块与证书,再追上最新轮次。
- 消息校验:所有签名、轮次、父块链接都要校验;不合法的消息丢弃,不能让它阻塞主循环。
- 测试:用确定性模拟器注入延迟、丢包、分区和拜占庭行为,检查安全性(永不提交冲突的块)和活性(GST 之后一定出块)。Aptos 的 twins 测试就是让同一身份的两个节点同时运行来模拟双签。
暂无评论,欢迎留下第一条评论。