什么是以太坊中的 “Finalized”?

  • potuz_
  • 发布于 2026-04-07 20:27
  • 阅读 13

本文探讨了以太坊 Gloas (EIP-7732) 提案对“最终性”定义的深层影响。文章指出,由于信标块与执行负载的可用性可能分离,协议最终确认的是信标块根而非完整的负载状态。作者详细分析了这一变化对 Beacon API、引擎 API 以及证明验证逻辑的技术挑战,并提出了相应的处理规则。

Finalization 的含义

目录

以太坊提供了一个强有力的保证,即诚实节点绝不会 重组 (Reorg) 一个已 FinalizedCheckpoint。此外,如果两个相互冲突的 CheckpointFinalized,FFG(以太坊的最终性组件)意味着至少 1/3 的总质押是 可被罚没的 (Slashable)。这是通常的 加密经济 安全陈述。

本文讨论的是一个更窄的问题:当以太坊说一个 CheckpointFinalized 时,该保证实际上保护了哪些状态?

在纪元边界区块缺失的情况下,这个问题在今天已经具有一定的微妙性。Gloas 使其变得更加复杂,因为 信标区块可用性 (Beacon-block availability)Payload 可用性 (Payload availability) 现在可能会出现分歧。特别是,EIP-7732 迫使我们要明确一个 FinalizedCheckpoint 到底承诺了什么,以及没有承诺什么。

术语

下文讨论使用以下术语:

  • 目标 Checkpoint (Target checkpoint)Attestation 在 FFG 中投票支持的 Checkpoint
  • 一旦有足够的 Attestation 支持,一个 Checkpoint 就会变成 Justified
  • 一旦后续的 Justified Checkpoint 通过 FFG 规则将其 Finalized,该 Checkpoint 就会变成 Finalized
  • 区块的 后置状态 (Post-state) 是应用该区块的信标链转换后的信标状态。
  • 在 Gloas 中,一个 Slot 可能是 fullemptymissedfull 意味着信标区块加 Payload 都可用;empty 意味着信标区块可用但没有包含 Payloadmissed 意味着没有信标区块可用。

Gloas 之前的 Attestation 目标

在运行良好的链中,Slot 92 的验证者在该 Slot 的区块到达后进行 Attestation

92

...

65

64

...

32

...

Slot 92 的绿色区块进行的 Attestation 唯一确定了 Slot 64 的目标区块。如果在第 2 个 Epoch 期间收集到足够的此类投票,那么当链在 Slot 96 进入第 3 个 Epoch 时,区块 64 变为 Justified。此时,之前 JustifiedCheckpoint,即区块 32,变为 Finalized

Slot 96 之后、Slot 128 之前,如果诚实节点通过标准的 Beacon API 终端点 被询问其 Finalized Checkpoint,它将返回区块 32 的 后置状态 (Post-state)。该保证适用于该 Finalized Checkpoint 的内容:

诚实节点永远不会考虑不包含区块 32 的链。即使后来有另一条链被 Finalized,或者即使 100% 的验证者集都是恶意的,诚实节点也永远不会接受一条排除区块 32 的链。

如果另一条已 Finalized 的链确实排除了区块 32,那么至少 1/3 的总质押是 可被罚没的 (Slashable)

缺失纪元边界区块

现在考虑 Slot 64 的纪元边界区块缺失或被 重组 (Reorg) 掉的情况:

92

...

65

63

64

...

32

...

在这里,对区块 92 的投票隐式地将区块 63 设置为目标,因为 63 是上一个 Epoch 中的最后一个区块。如果收集到足够的此类投票,当链越过 Slot 96 时,区块 63 变为 Justified,当链随后在 Slot 128 进入第 4 个 Epoch 时,它变为 Finalized

但这种情况包含比正常路径更多的信息。验证者不仅在为区块 63 投票。他们 在发出 Slot 64 为空的信号。所以有效的保证更强:

诚实节点永远不会考虑不包含区块 63 且 Slot 64 为空的链。他们将拒绝以下两种情况:

  • 不包含区块 63 的链,以及
  • Slot 64 包含区块的链。

如果另一条已 Finalized 的链要么排除了区块 63,要么在 Slot 64 包含了区块,那么至少 1/3 的总质押是 可被罚没的 (Slashable)

同样的逻辑延伸到多个缺失的 Slot。如果最后一个可用的区块是 62,那么验证者将同时 Finalized 区块 62 以及 Slot 63 和 64 处区块的缺失。

Gloas 之前的 Finalized 终端点

在上述边界缺失的情况下,节点应该返回什么作为 Finalized 状态?

仅返回最后一个可用区块(区块 63)的 后置状态 (Post-state) 会丢失信息:它没有反映出 Slot 64 已知为空的事实。历史解决方案是返回区块 63 的 后置状态 (Post-state),但将其推进到 Slot 64。该状态捕获了这两个事实:

  • Epoch 中的最后一个区块是 63,且
  • Slot 64 被错过或被 重组 (Reorg) 掉了。

这就是为什么 Checkpoint 同步标准化为纪元边界状态,而不是最后一个区块的原始 后置状态 (Post-state)。参见 此 Beacon API 讨论

Gloas 改变了问题

到目前为止,微妙之处在于纪元边界处是否存在区块。Gloas 引入了一种不同的歧义:信标区块执行 Payload (Execution payload) 现在可以分离。

在 Gloas 中,分叉选择 (Forkchoice) 节点可能会将一个 Slot 视为:

  • missed,和今天一样,当没有信标区块可用时;
  • full,当信标区块及其 Payload 都可用时;或者
  • empty,当信标区块可用但未包含 Payload 时。

这很重要,因为 Attester 不会对当前 SlotPayload 进行 Attestation,但他们确实间接地对早期 SlotPayload 可用性 进行 Attestation。有关完整理由,请参阅 注释版分叉选择规范。在这里,我们只需要理解 Finalization 所需的最低限度知识。

Gloas 下的正常路径

再次考虑同样的正常路径图示:

92

...

65

64

...

32

...

对区块 92 的 Attestation 仍然唯一标识了 Slot 64 的目标区块。在 Gloas 下,它还有效地标识了 Slot 64 的 Payload 状态,除了在 Slot 64 本身期间进行 Attestation 的委员会。那些验证者投票支持 64 同时作为 HeadTarget,但他们 尚未 对 64 的 Payload 做出声明。该 Epoch 中的 每一个后续委员会 都会做出这样的声明。

因此,到链达到 Slot 96 时,Attestation 包含足够的信息来同时使以下内容 Justified

  • Slot 64 处的信标区块,以及
  • Slot 64 关联的 Payload 状态。

然而,信标状态并不存储这些完整信息。为了直接在链上保留它,规范需要扩展 Checkpoint 本身,例如:

class Checkpoint(Container):
    epoch: Epoch
    root: Root
    block_hash: Hash32

这种方法被认为对客户端太具侵入性,因此 Checkpoint 结构保持不变。结果,只有 Justification/Finalization 信息中的 信标区块 (Beacon-block) 部分存储在信标状态中。

Slot 96 进入第 3 个 Epoch 后,Justified Checkpoint 包含足够的信息来使 Slot 64 处的信标区块 Justified,但 不足以 提供协议状态信息来使其 Payload Justified

这是关键区别:

在 Gloas 下,Attestation 可能包含足够的信息来重建 Payload 可用性,但 协议 Finalized 的对象仍然是存储在信标状态中的 Checkpoint 根。换句话说,即使 Payload 派生状态 尚未 Finalized信标区块 也可能已经 Finalized

这导致了一些尴尬但必要的 API 决策:

  • 在 Engine API 中,safe 区块哈希应引用 Slot 63 的 Payload,而不是 Slot 64。
  • 在 Beacon API 状态终端点中,Finalized 状态应为应用 Slot 64 处的信标区块之后、但在应用其 Payload 之前的 后置状态 (Post-state)

该行为正确地编码了协议保证。Finality 适用于 Slot 64 处的 信标区块,而不是 Slot 64 处由 Payload 派生的状态

Slot 64 被 Finalized 之后,诚实节点将永远不会接受排除 Slot 64 处信标区块的链。但他们仍可能接受在 Slot 64 的 Payload 上有所不同的链。

Gloas 下缺失边界区块的情况

现在回到 Slot 64 没有信标区块的情况:

92

...

65

63

64

...

32

...

在这种情况下,所有较早的 Payload 可能都可用,但 Slot 64 处的信标区块(以及因此 Slot 64 的任何 Payload)都缺失了。

在这里,Attestation 实际上说明了很多。即使是 Slot 64 处的委员会,也通过设置 attestation.data.index = 1 投票支持区块 63 及其 Payload。后续委员会通过在区块 65 之上构建来支持同样的 Payload。因此,仅从 Attestation 中,人们就可以恢复关于区块 63 的 Payload 状态的强有力证据。

但再一次,这与协议 Finality 不是 一回事。存储在信标状态中的 Checkpoint 仍然只承诺 Checkpoint 根。它 承诺节点可能从 Attestation 离线重建的 Payload 状态。

因此,即使在这种情况下,实际上 1/3 的 Attester 揭示了 Payload 信息,区块 63 的 Payload 仍然 不是 协议 Finalized 的内容。

Gloas 下的 Finalized 终端点

这就是 Gloas 之前的规则失效的地方。

在 Gloas 之前,将区块 63 的 后置状态 (Post-state) 推进到 Slot 64 是安全的,因为推进后的状态捕获了 Slot 64 为空这一被 Finalized 的事实。在 Gloas 下,推进状态现在可能隐式地在 相互竞争的 Payload 解释 之间做出选择。

出现了两个糟糕的选项:

  • 将区块 63 的信标区块后置状态推进到 Slot 64 可能会产生一个与建立在区块 63 的 Payload 之上的规范链相竞争的状态。
  • 返回区块 63 完整的 Payload 后置状态将断言区块 63 的 PayloadFinalized,而节点实际上并未在 Checkpoint 中跟踪这一点。

所以现在的正确规则要严格得多:

唯一可以安全返回的 Finalized 状态是根为 Justified/Finalized Checkpoint 根的区块的 信标区块后置状态 (Post-beacon-block state)。在 Gloas 下,该状态 不应 被推进到纪元边界。

这是本文核心的操作性结论。

为什么不使用本地视图?

节点可能仍会问:如果我能很好地在本地跟踪 Payload 信息,为什么不公开更强的状态呢?

原则上,记录了足够多来自链上 Attestation 的离线信息的节点可以重建一个更强的感知 Payload 的视图。在正常情况下,它可以返回 Slot 64 完整的 Payload 后置状态。在边界缺失的情况下,它可以返回推进到 Slot 64 的区块 63 的 Payload 后置状态。

问题不在于该重建是否通常正确。问题在于它比信标状态本身 Finalized 的内容 更强

如果不同的节点对同一个已 FinalizedCheckpoint 根在本地重建了不同的 Payload 状态,那么它们可能会提供不兼容的答案,同时仍然在协议 Finalized 的内容上达成一致。这在 Engine API 的任何地方都是不可接受的,甚至在 Checkpoint 同步中也是不可取的。

Engine API 的后果是重要的一个:

对于 Engine API 调用,safe 哈希应 始终 是包含在 Justified 区块中的竞价 (Bid) 的父哈希。

对于 Checkpoint 同步或调试终端点,影响较小,但并非为零。不同的 Payload 解释可能会改变执行请求,从而改变某些证明,或导致同步节点最初遵循错误的分支。

对 Attestation 验证的相关影响

on_attestation 也有一个规范影响。

今天,store_target_checkpoint_state 返回推进到纪元边界的后置 CL 状态,然后该状态在验证 Attestation 签名时被使用。但是,一旦 FFG/LMD 一致性已经确保了相同的信标区块根,剩下的歧义正是上面讨论的 Gloas Payload 歧义。

该状态稍后用于计算委员会:

def get_indexed_attestation(state: BeaconState, attestation: Attestation) -> IndexedAttestation:
    """
    返回对应于 ``attestation`` 的索引 Attestation。
    """
    attesting_indices = get_attesting_indices(state, attestation)

    return IndexedAttestation(
        attesting_indices=sorted(attesting_indices),
        data=attestation.data,
        signature=attestation.signature,
    )

这最终取决于 get_beacon_committee,它取决于 Epoch Seed 和活跃验证者索引。Seed 不受目标 Payload 是否存在的影响,但如果 Payload 中的执行请求触发了存款、退出、取款或合并,活跃验证者集可能会发生变化。

只有当目标 SlotAttestation Slot 之间缺失足够多的 Slot,使得这些变化变得重要时,这一点才会显现出来,但在这种情况下,区别是真实的:带有 Payload 的目标状态和不带 Payload 的目标状态可能意味着不同的活跃验证者索引。

因此,规范可能应该在两个方面进行更改:

  • 用于完整 Attestation 验证的状态应该是该 Attestationbeacon_block_root 的目标状态,而不是存储在 Store 中缓存的 Checkpoint 状态。
  • 应该有测试用例涵盖至少一个 Epoch 缺失区块后的 Attestation,其中被 Attestation 的区块在一个分支中是 full,在另一个分支中是 empty,并且目标状态相应地有所不同。

依赖根 (Dependent root) 分析

信标节点通常使用 依赖根 (Dependent root) 的概念来决定跨两个不同分支的两个信标状态是否共享某些不变量,如提议者预测 (Proposer lookahead) 或信标委员会等。这些依赖根被选为该 Epoch最后一个信标区块根。在 Gloas 之前,共享最后一个信标区块根将保证任何两个分支在下一个 Epoch 具有相同的预状态 (Pre-state),但现在情况不再如此,因为最后一个信标区块的 Payload 实际上可能会更改下一个区块(在下一个 Epoch 中)的预状态。因此,我们要分析从 Epoch $e$ 到 Epoch $e+1$ 到底会发生什么变化。

BEpoch $e$ 上最新的信标区块,令 P 为其 Payload。令 EF 分别是通过处理来自 BP 的后置状态到 $e+1$ 的 Slot 而获得的状态。我们想了解 EF 之间的主要可能差异。我们有明显的更改,如 latest_block_hash 等,但我们主要对可以更改信标委员会、提议者预测等的操作感兴趣。这些更改将来自处理 P 上的 执行请求 (Execution requests),因为这些更改将包含在 F 中,但不会包含在 E 中。

存款请求 (Deposit requests)

Builder 存款会立即处理并添加到注册表中,因此 F 可能会有更多的 Builder 及其余额。但这通常不会影响委员会/提议者和验证者职责。

验证者存款在处理 P 时作为 PendingDeposit 附加到状态,它们的 SlotBSlot

取款请求 (Withdrawal requests)

全额退出将调用 initiate_validator_index,后者又使用退出余额调用 compute_exit_epoch_and_update_churn。最早可能的退出 Epoch 是 $e+5$,状态的最早退出 Epoch 更新为此。退出会将验证者的退出 Epoch 设置为此退出 Epoch(至少为 $e+5$),将其可取款 Epoch 设置为至少 $e+261$。

待处理的部分取款会被附加。同样,这些可取款 Epoch 至少为 $e+261$。

合并请求 (Consolidation requests)

在切换到复利请求时,会为所有高于最低激活余额的余额添加一个带有 GENESIS_SLOT 的待处理存款。

对于成功的合并请求,源验证者的退出 Epoch 设置为至少 $e+5$,状态的最早合并 Epoch 设置为至少 $e+5$,验证者的可取款 Epoch 设置为至少 $e+261$。状态中会添加一个待处理合并。

纪元处理 (Epoch processing)

到目前为止,我们分析了由于在 B 之上应用或不应用来自 P 的请求而导致的状态主要差异。两个最终状态将通过对这两个不同的状态执行纪元转换而产生差异。纪元转换将在以下方面有所不同:

注册表更新

如果 P 包含了退出,并且有一些验证者跌破了驱逐余额,这些验证者可能会被设置为不同的退出 Epoch,无论如何,这个退出 Epoch 至少会是 $e+5$。

待处理存款

任何 Slot > 0 的已应用待处理存款都不会被应用,因为 Slot 无法被 Finalized

如果在 P 中将验证者切换为复利验证者而产生的 GENESIS_SLOT 存款,如果 Churn 尚未耗尽,则将被应用。在这种情况下,验证者的余额将再次增加,因此与 B 的状态没有区别,除了验证者已切换为复利验证者。如果已达到 Churn,则 P 之后该验证者的余额将减少到 32ETH,并且待处理存款将保留。

待处理合并

由于尚未达到可取款 Epoch,因此不会处理来自 P 的待处理合并。

有效余额更新

这是可能出现问题的地方!来自实际上持有超过 32ETH 的验证者的合并转换请求,将导致该验证者的有效余额变得不同,因为有效余额会从之前的最高 32ETH 发生变化。然而,这要求验证者起初拥有超过 32.25ETH。

因此,我们看到,除去验证者持有超过 32.25ETH 并在 P 上切换到复利这一潜在问题,从 $e$ 到 $e+1$ 的转换中,有效余额和活跃验证者集不可能有任何差异,因此 EF 状态共享相同的活跃验证者索引,并共享相同的有效余额。

由于上述相同的原因,这至少会持续 5 个以上的 Epoch。因此,只要没有超过 4 个 Epoch 缺失区块,这些状态就会保持相同的活跃验证者和有效余额(请注意,除非包含区块,否则从 P 中包含的切换到复利验证者实际上无法增加有效余额)。

对验证的影响

需要提议者预测的验证不会改变,来自 FE 的提议者预测完全相同。需要活跃验证者和活跃验证者余额的验证也无法从 F 改变到 E

唯一 的变化可能是,如果 FE 随后都在未包含任何区块的情况下被推进到未来的 Epoch。我们看到,这至少需要 5 个 Epoch 才能使这些状态产生差异。因此,当出现 5 个 Epoch 缺失区块,然后链突然恢复并立即使作为目标的区块 B 变为 Justified 时,可能会出现问题。未来的链如果使用推进后的 F 或推进后的 E,将具有不同的 Justified 余额集。

  • 原文链接: potuz.net/posts/gloas-fi...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
potuz_
potuz_
江湖只有他的大名,没有他的介绍。