BFT / CometBFT:轮次、锁、安全性与活性¶
30 秒版(开场)¶
CometBFT 在每个高度运行一个或多个 round,每轮经历 Propose、Prevote、 Precommit;看到某个块在同一高度与轮次获得超过三分之二 voting power 的 precommit 才进入 commit。本地验证者在 Precommit 步骤观察到同轮某块的
+2/3 prevote(PoLC)后才锁定/重锁并 precommit;更高轮的合法 PoLC 可支持安全 换锁,不能说成“网络一出现 prevote 就让所有人同时锁定”。假设 Byzantine voting power 小于三分之一并最终 进入同步期,可以同时讨论安全性和活性;三分之一及以上即使不双签,也能通过不投票 阻止链前进。
3 分钟版(精讲深度)¶
- 高度与轮次:同一个 height 可能经历多个 round;proposer 离线、块无效或消息 延迟都会触发超时并进入下一轮。
- Prevote:验证者对有效 proposal、已锁定块或
nil投 prevote。 - Precommit:同轮某块获得
+2/3 prevote后验证者锁定该块并 precommit; 未形成相应证明时通常 precommitnil。 - Commit:某块获得同一
(height, round)的+2/3 precommit。 - 换锁:只在观察到更高轮次的合法 PoLC 时换锁,兼顾安全与跨轮活性。
这里的 +2/3 是 严格超过三分之二 voting power,不是“节点数量大概三分之二”。
10 分钟版(原理 + 图示)¶
stateDiagram-v2
[*] --> Propose
Propose --> Prevote: proposal / timeout
Prevote --> Precommit: +2/3 prevote or timeout
Precommit --> Commit: +2/3 precommit for block
Precommit --> Propose: nil / timeout, round + 1
Commit --> [*]: persist block and advance height
投票类型不能混¶
| 观察 | 能推出什么 | 不能推出什么 |
|---|---|---|
+2/3 prevote(B, H, R) |
形成 PoLC,验证者可锁定/换锁并 precommit B | 所有人已经 commit B |
+2/3 prevote(nil) |
本轮没有块形成可锁定多数,推进 precommit/下一轮 | 链永久停止 |
+2/3 precommit(B, H, R) |
可构造该高度的 commit | 下一高度应用状态已经落盘 |
| 本地看到少量 precommit | 只是局部网络观察 | “多数验证者已经同意” |
Lock 为什么保证安全¶
如果块 B 已获得 +2/3 precommit,其中至少有 1/3+ voting power 在小于三分之一
Byzantine 的假设下是诚实且受锁规则约束的。它们不会无证明地改投冲突块;要在更高轮
换锁,需要看到更高轮次的 +2/3 prevote。若没有足够验证者双签或违反锁规则,冲突块
无法再取得 +2/3 precommit。
讲解时不要把 lock 说成“节点永远不能改票”。它是带 round 条件的协议状态;安全换锁 正是网络从不同轮锁定状态恢复活性的关键。
Safety 与 Liveness 分开回答¶
- Safety:在 Byzantine voting power 小于三分之一、签名和状态机规则正确等假设下, 不会 commit 两个冲突块。
- Liveness:还需要网络在未知时间后进入足够同步的阶段、正确 proposer 最终出现、 timeout 能覆盖消息传播和足够 voting power 在线。
- 1/3+ 阻断:恶意或离线联盟不需要制造另一个 commit,只要拒绝投票就可以使链停止。
- 超过阈值作恶:出现冲突 commit 后,问题已超出自动 BFT 协议的正常恢复边界,需要 证据、治理和外部协调。
和 Ethereum PoS 的区别¶
CometBFT 通常在每个高度通过多轮投票形成单块 commit,提供快速确定性;Ethereum 则持续运行链式 fork choice,并用 epoch checkpoint 做 FFG finality。两者都使用权益 权重和 Byzantine 阈值,但状态机、消息和 finality 水位不能互换。
生产场景¶
- remote signer/slashing protection 必须单调持久化 chain 与最后签名的
(height, round, signed message type/step)。该元组是拒绝倒退/同类型重复签名的 顺序护栏;为支持同请求幂等返回并检测同 H/R/type 的冲突,还应持久化 canonical sign bytes(含 BlockID/nil 等语义)及原签名,不能仅保存一个 tuple 后重新签。 - validator key 的主备切换不能只依赖进程锁;需要 fencing、单活租约和签名历史。
- 监控 round 增长、proposal/prevote/precommit timeout、voting power participation、 peer 拓扑与共识 reactor backlog。
- 应用
FinalizeBlock/Commit超时、磁盘 fsync 或确定性 bug 也会让诚实节点无法参与投票。
排查与工具¶
链停在同一 height 时,比较多节点的 (H,R,S)、proposal block ID、prevote/precommit
分布和 validator power。若不断升 round,判断是 proposer、块传播、应用验证还是网络
延迟。只看“节点在线数量”不够,必须看在线 voting power。
架构取舍¶
更短 timeout 可降低健康网络延迟,但在跨地域和抖动下增加空轮;更长 timeout 提高稳定性 却拉长故障恢复。validator set 越集中,网络通信和运维更简单,但故障域、审查和密钥风险 更集中。
深挖问答¶
- 为什么有 prevote 还要 precommit? → 前者形成锁定证明,后者才表达在锁规则下的提交承诺。
- 锁住后为何还能换锁? → 观察到更高轮合法 PoLC 时换锁,避免不同轮锁导致永久停滞。
- 三分之一恶意能分叉吗? → 在假设内通常不能形成两个 commit,但能阻止
+2/3、造成停链。 - 节点数 70% 在线是否足够? → 取决于在线节点对应的 voting power,不按节点个数。
- commit 后应用一定完成了吗? → 还要区分本地观察 commit、收到块、执行状态机和持久化完成。
反模式与事故¶
- 说“收到 2/3 投票就最终”,不说明 prevote/precommit、height、round。
- 双机同时加载同一 validator key,认为共识层会自动去重。
- 把网络异步阶段的超时换轮误判成安全性失效。
- 只统计 validator 数量,不统计 voting power。
- 为追求低延迟把 timeout 固定得过小,导致跨地域持续空轮。