这篇文章深入探讨了 leanConsensus 协议中的 LMD-GHOST 分叉选择算法,详细阐述了其工作原理、优势、具体实现步骤及与以太坊信标链的设计差异。它还提及了 LMD-GHOST 如何与 3SF-mini 最终性机制协同工作,以确保区块链的稳定性和永久性。
深入探讨 leanConsensus 核心的两种共识机制之一:客户端如何选择正确的链并决定区块何时是永久的。
这是我们关于介绍 ethlambda 和 ethlambda 的架构 系列文章的后续。在这里,我们解释了 ethlambda 实现的两个核心共识机制:用于分叉选择的 LMD-GHOST 和用于最终性的 3SF-mini。
本文中的许多概念框架都受到了 Ben Edgington 的
Eth2 Book 的启发,特别是 LMD GHOST 章节。
强烈推荐所有对以太坊共识感兴趣的人阅读。
共识客户端每隔几秒钟就必须回答两个问题:我现在应该跟随哪个链尖? 以及 哪些区块是永久的且永远不会被回滚?
第一个问题之所以存在,是因为区块链可能会分叉:两个有效的区块可能共享同一个父区块,在同一高度创建竞争链:
┌──────────┐
┌────▶│ 区块 C │ ← 链尖 1
│ │ Slot 4 │
┌──────────┐ │ └──────────┘
│ 区块 A │─┤
│ Slot 3 │ │ ┌──────────┐
└──────────┘ └────▶│ 区块 D │ ← 链尖 2
│ Slot 5 │
└──────────┘
验证者应该跟随哪个链尖?
网络中的每个节点都必须仅使用其对区块和证明的本地视图独立得出相同的答案。分叉选择规则正是实现这一点的关键:一个从节点的观察状态到单个链尖的确定性函数。第二个问题本质上是不同的:它需要一个保守的机制,只有在有压倒性共识时才提交。
在 ethlambda 中,第一个问题由 LMD-GHOST 回答,第二个问题由 3SF-mini 回答。它们共同构成了 leanConsensus 协议的共识算法。
GHOST 协议由 Sompolinsky 和 Zohar 在 2013 年的一篇论文中提出。它的核心思想是:不选择最长或最重的链,而是选择最重的子树。对任何区块的投票都隐式地是对其所有祖先的投票:
验证者证明:head = F
A ── B ── C ── D ── E ── F
▲ ▲ ▲ ▲ ▲ ▲
│ │ │ │ │ │
└────┴────┴────┴────┴────┘
所有祖先都被隐式支持
"LMD" 代表最新消息驱动(Latest Message Driven):只有每个验证者最新的证明才算数。这可以防止投票放大:如果所有消息都算数,一个验证者可以投出许多证明并倍增其影响力。有了 LMD,每个验证者只有一个活跃投票,无论他们广播了多少证明。
验证者 7 的证明历史:
Slot 10:证明 head = B ← 丢弃
Slot 11:证明 head = C ← 丢弃
Slot 12:证明 head = E ← 这是活跃投票
只有Slot 12 的证明才计入分叉选择。
分叉选择存储维护一个简单的映射:每个验证者一行,新的证明会覆盖旧的。
┌──────────────┬──────────────────────────────┐
│ 验证者 │ 最新证明 │
├──────────────┼──────────────────────────────┤
│ 0 │ head=E, target=C, source=A │
│ 1 │ head=D, target=C, source=A │
│ 2 │ head=E, target=C, source=A │
│ 3 │ head=F, target=D, source=A │
│ ... │ ... │
└──────────────┴──────────────────────────────┘
最简单的分叉选择规则是“最重链”:跟随投票最多的链尖。这在分叉率低时有效,但在对抗条件下会失效:
最重链 vs 最重子树
──────────────────────────────────
一个拥有 40% 股权的攻击者在 A 处分叉。
诚实的大多数 (60%) 在 B 上构建,但分叉到 C 和 D:
┌───B──┬──C V0, V1, V2 投票给 C (30%)
A ────┤ └──D V3, V4, V5 投票给 D (30%)
│
└───X──Y──Z V6, V7, V8, V9 投票给 Z (40%)
最重链:
Z 有 40% 的投票,C 和 D 各有 30%。
攻击者获胜!✗
最重子树 (LMD-GHOST):
在 A:B 子树有 60% (C + D),X 子树有 40%。
选择 B。然后在 B:C 有 30%,D 有 30%(平局决胜)。
诚实的大多数获胜。✓
当诚实验证者在一个共同的子树内分叉时,LMD-GHOST 明显更好。它不是要求所有诚实验证者就单个链尖达成一致(这在网络延迟下是不可能的),而是在树的每个级别聚合它们的支持。
该算法有四个输入:
| 输入 | 目的 |
|---|---|
| 起始根 | 要搜索的子树的根(已证明检查点或创世区块) |
| 区块树 | 已知区块的集合:根 → (Slot, 父区块) |
| 证明 | 每个验证者的最新消息:validator_index → attestation |
| 最小得分 | 分支被考虑的最小权重 (0 = 任何分支;⌈2V/3⌉ = 保守) |
步骤 1:累积权重。 每个证明都会“涂色”从其证明的头部到起始根的路径,为沿途的每个区块增加 +1。在 leanConsensus 中,所有验证者目前具有相同的权重(这是概念验证的简化),这与信标链根据有效余额加权投票不同。
验证者 0 证明 head = F
R ─ A ─ B ─ C ─ D ─ E ─ F (R = 子树的根)
+1 +1 +1 +1 +1 +1 R 在 start_slot,不计数
验证者 1 证明 head = D
R ─ A ─ B ─ C ─ D
+1 +1 +1 +1
累积权重:
区块: R A B C D E F
权重: ─ 2 2 2 2 1 1
│
└ start_root (不加权,用作下降起点)
步骤 2:贪婪下降。 从起始根开始,在每个分叉处选择权重最大的子区块。重复此过程直到到达一个叶子区块。该叶子区块就是头部。
R ──┬── B (5) ← 选择 B (权重更高)
└── G (2)
B ──┬── C (3) ← 选择 C (权重更高)
└── H (2)
C ──── D (3) ← 唯一的子区块,继续
D ── (没有子区块) → HEAD = D!
低于 min_score 的子区块在下降过程中会被忽略。
决胜机制: 当两个子区块的权重完全相等时,具有字典序更高的根哈希的区块获胜。具体的选择不重要;重要的是所有节点都应用相同的规则。
同等权重场景:
父区块
│
┌───┴───┐
B (3) C (3) ← 权重相等!
根哈希: 根哈希:
0x3a.. 0x7f.. ← 0x7f > 0x3a,所以选择 C
在 ethlambda 中,核心函数是 crates/blockchain/fork_choice/src/lib.rs 中的 compute_lmd_ghost_head()。
考虑 5 个验证者(索引 0-4)以及在Slot 10 处以 R 为根的区块树:
Slot 10 ┌──────┐
│ R │ ← start_root
└──┬───┘
│
Slot 11 ┌──┴───┐
│ A │
└──┬───┘
┌──┴────────┐
│ │
Slot 12 ┌──┴───┐ ┌──┴───┐
│ B │ │ C │
└──┬───┘ └──┬───┘
│ │
Slot 13 ┌──┴───┐ ┌──┴───┐
│ D │ │ E │
└──────┘ └──────┘
最新证明:
| 验证者 | 证明头部 | 返回到 R 的路径 |
|---|---|---|
| 0 | D | D → B → A |
| 1 | D | D → B → A |
| 2 | E | E → C → A |
| 3 | E | E → C → A |
| 4 | E | E → C → A |
累积权重:
| 区块 | 权重 | 解释 |
|---|---|---|
| A | 5 | 在所有 5 个验证者的路径上 |
| B | 2 | 在 V0, V1 的路径上 |
| C | 3 | 在 V2, V3, V4 的路径上 |
| D | 2 | V0, V1 的头部 |
| E | 3 | V2, V3, V4 的头部 |
贪婪下降:
从 R 开始
└─▶ A (唯一子区块,权重 5)
├── B (权重 2)
└── C (权重 3) ← 选择 C (3 > 2)
└─▶ E (唯一子区块,权重 3)
└─▶ 没有子区块 → HEAD = E ✓
规范头部是 区块 E。尽管两个分支的深度相同,但 C→E 分支有 3 票,而 B→D 有 2 票。
如果投票发生变化怎么办? 如果两个验证者从 E 切换到 D,头部会重组:
之前: V0=D, V1=D, V2=E, V3=E, V4=E → 头部 = E (3 vs 2)
之后: V0=D, V1=D, V2=D, V3=D, V4=E → 头部 = D (4 vs 1) ← 重组
重组在瞬态网络条件下是正常的,但在稳定运行中应该很少见。它们不能跨越最终性边界:一旦区块被最终化,它就永久地成为规范链的一部分。由于你无法修复你看不见的东西,ethlambda 通过 Prometheus 指标 (lean_fork_choice_reorgs_total) 跟踪重组,并通过检查新旧头部是否共享共同前缀来检测它们。
LMD-GHOST 自身需要考虑从创世区块开始的每一个区块,并且区块树会无限增长。3SF-mini 通过生成已证明检查点解决了这个问题:这些区块已经获得了足够的证明支持,被认为是分叉选择的稳定根。
LMD-GHOST 使用最新的已证明检查点作为其起始根。它从不查看之前的区块。一旦检查点被最终化(被 3SF-mini 从已证明提升到最终化),所有在该检查点或之前的区块都可以从分叉选择工作集中完全丢弃。
┌─────────┐ ┌─────────┐ ┌──── ...
│ 最终化区块 │────────▶│ 已证明区块 │────────▶│ 分叉选择
│ Slot 50 │ │ Slot 55 │ │ 在此运行
└─────────┘ └─────────┘ └──── ...
│ │
│ └── LMD-GHOST 的起始根
│
└── 此之前的一切都是永久的;
LiveChain 在此处之前被修剪
这就是使 ethlambda 的 LiveChain(内存中的区块树)保持有界的原因:每次最终性推进时都会对其进行修剪,只保留链中未最终化的部分。
除了计算头部之外,同样的 LMD-GHOST 算法也用于计算安全目标:通过设置 min_score = ⌈2V/3⌉ 获得的保守头部,因此只跟随具有超多数支持的分支。安全目标用于 3SF-mini 的证明和最终化决策。我们将在下一篇文章中详细介绍这一点。
在一个朴素的实现中,每个证明都会在其到达的瞬间影响分叉选择。这会带来问题:网络连接更快的验证者会看到与较慢的验证者不同的头部,并且提案者的视图可能会在区块构建中途发生变化。
leanConsensus 通过一个分阶段提升流水线解决了这个问题:
证明生命周期
─────────────────────
┌──────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ 网络 │ │ 待处理 │ │ 活跃 │
│ (gossip) │──────▶│ ("new") │──────▶│ ("known") │
│ │ │ 证明 │ │ 证明 │
└──────────────┘ └──────────────────┘ └──────────────────┘
│ │
不用于 用于分叉选择
分叉选择 权重计算
│ │
在指定时间点 ─────────────▶ 提升
固定时间点
这种分阶段的设计有两个目的:一致性(所有验证者在相同时刻提升证明,减少头部选择的分歧)和提案者公平性(提案者根据已知、固定的证明集计算区块)。
证明根据其来源以不同的方式进入流水线:
| 来源 | 进入方式 | 原因 |
|---|---|---|
| 网络 gossip | 待处理 | 必须等待提升窗口 |
| 区块体 (链上) | 活跃 | 已通过共识验证 |
| 提案者自己的证明 | 待处理 | 防止提案者权重优势 |
提案者自己的证明作为待处理状态进入是故意的:如果它立即活跃,提案者将为其自己的区块获得不公平的权重优势,这是一种循环依赖,即提案一个区块会让你额外获得一票,使其成为规范区块。
ethlambda(根据 devnet-3)将每个 4 秒的Slot分为 5 个间隔,每个 800 毫秒:
head = 当前分叉选择头部,target = 从安全目标导出(用于 3SF-mini),source = 最新已证明检查点。
leanConsensus 的设计选择与信标链不同。这些并非为简化而简化;它们源于对协议的刻意承诺,即保持其简单易懂、易于分析和实现。只有在证明必要时才增加复杂性。
| 方面 | leanConsensus | 以太坊信标链 | 理由 |
|---|---|---|---|
| 投票权重 | 相等:每个验证者 1 票(目前) | 与有效余额成比例(最高 32 ETH) | 概念验证的简化;在协议成熟时保持分析和实现简单 |
| 提案者加速 | 无 | 新提案区块的临时权重奖励 | 采用不同方式解决:分阶段证明流水线处理提案者公平性,不增加分叉选择的复杂性 |
| 证明频率 | 每个Slot | 每个时期一次 | 以更高的消息量为代价,实现更灵敏的分叉选择 |
| 委员会结构 | 所有验证者每个Slot都证明 | 验证者被分成每个Slot的委员会 | 更简单;无需委员会选择逻辑 |
| Slot持续时间 | 4 秒 | 12 秒 | 更快的最终化 |
朴素的分叉选择方法在每次调用时都会从头开始重建整个区块树:遍历每个证明,在整个树上累积权重,然后下降。这可行,但很浪费:大部分树自上次运行以来并未改变。
有一系列日益复杂的实现,由信标链客户端首创:
| 方法 | 缓存内容 | 每次分叉选择运行的成本 | 扩展性取决于 |
|---|---|---|---|
| 朴素 | 无;每次都完全重建 | O(A × D) | 证明数量 × 链深度 |
| 缓存树 | 修剪后的区块树,在新区块和最终化时更新 | 避免树重建;仍然重新计算权重 | 区块到达率 |
| 缓存权重 | 权重存储在树节点中,每次投票时增量更新 | O(1) 摊销(Proto-array) | 验证者数量(优雅地) |
ethlambda 目前实现了第二种方法:LiveChain 将修剪后的树保存在内存中,并在区块到达和最终性推进时更新它。我们尚未在分叉树中缓存权重(第三种方法),但它已在我们的考虑范围内。在拥有大量验证者集合的网络中,增量权重更新对于保持分叉选择快速至关重要。
LMD-GHOST 告诉我们应该跟随哪个链尖,但仅凭分叉选择不能保证区块是永久的。在下一篇文章中,我们将介绍 3SF-mini,这个最终性小工具使区块不可逆:它将涵盖证明和最终化如何在Slot级别工作,作为内置回退机制的可证明性调度,以及这一切与信标链的 Casper FFG 有何不同。
我们最近增加了对 devnet-3 的支持,并且我们持续关注 leanSpec 的最新变化。我们每天在 Telegram 提供更新。
ethlambda 在 github.com/lambdaclass/ethlambda 上开源。请关注我们或在 Telegram 或 X 上加入讨论。
- 原文链接: blog.lambdaclass.com/for...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!