共识层(CL)架构
许多区块链共识协议都是可分叉的。可分叉的链使用分叉选择规则,有时会发生重组。
以太坊的共识协议结合了两个独立的共识协议。LMD GHOST 本质上提供活性。Casper FFG 提供最终性。这两者共同被称为 Gasper。
在一个活性协议中,好的事情总会发生。在一个安全协议中,坏的事情永远不会发生。没有任何实用的协议能够始终保持安全性的同时又始终保持活性。
分叉选择机制
如BFT中所述,由于各种原因 - 比如网络延迟、中断、消息乱序或恶意行为 — 网络中的节点可能会对网络状态有不同的认知。最终,我们希望每个诚实节点都能就一个相同的、线性的历史记录和系统状态的共同视图达成一致。协议的分叉选择规则正是帮助实现这种共识的机制。
区块树
基于区块树和节点对网络本地视图的决策标准,分叉选择规则旨在选择最有可能成为最终规范链的分支。它选择在节点收敛到共同视图时最不可能被剪枝的分支。
分叉选择规则从候选区块中选择一个头区块。头区块标识了一条从创世区块开始的唯一线性区块链。
分叉选择规则
分叉选择规则通过选择分支末端的区块(称为头区块)来隐式地选择一个分支。对于任何正确的节点来说,任何分叉选择的首要规则是:被选中的区块必须根据协议规则是有效的,并且它的所有祖先区块也必须是有效的。任何无效区块都会被忽略,基于无效区块构建的区块也是无效的。
以下是几个不同分叉选择规则的例子:
- 工作量证明(Proof-of-work):在以太坊和比特币中,使用"最重链规则"(有时被称为"最长链",但这种说法并不严格准确)。头区块是累积"工作量"最多的链的末端区块。
需要注意的是,与普遍的认知相反,以太坊的工作量证明协议并没有使用任何形式的 GHOST 作为分叉选择规则。这个误解一直存在,可能是因为以太坊白皮书的缘故。最终当 Vitalik 被问到这个问题时,他确认虽然在 PoW 下曾计划使用 GHOST,但由于担心某些未明确的攻击而从未实施。最重链规则更简单且经过充分测试,运行良好。
- Casper FFG (权益证明):在以太坊的 PoS Casper FFG 协议中,分叉选择规则是"遵循包含最高高度已证明检查点的链",并且永远不会回滚已最终确定的区块。
- LMD GHOST (权益证明):在以太坊的 PoS LMD GHOST 协议中,分叉选择规则是选择"最贪婪最重可观察子树"。这涉及计算验证者对区块及其子孙区块的累积投票。它同样也应用 Casper FFG 的规则。
这些分叉选择规则都会为区块分配一个数值分数。得分最高的区块即为头区块。目标是让所有正确的节点在看到某个区块时,都会认同它是头区块并跟随它的分支。这样,所有正确的节点最终都会就一条从创世区块开始的规范链达成一致。
重组和回滚
当节点接收到新的投票(在权益证明中包括对区块的新投票)时,它会根据这些新信息重新评估分叉选择规则。通常情况下,新区块会是当前头区块的子区块,并成为新的头区块。
然而,有时新区块可能是区块树中另一个区块的后代。如果节点没有这个新区块的父区块,它会向其对等节点请求该区块以及其他缺失的区块。
在更新后的区块树上运行分叉选择规则可能会显示,新的头区块位于与之前头区块不同的分支上。当这种情况发生时,节点必须执行重组(reorganisation)。这意味着它将移除(回滚)之前包含的区块,并采用新头区块所在分支上的区块。
例如,如果一个节点的链上有区块 和 ,并且它将 视为头区块,它知道有区块 的存在但在其当前的链视图中并不出现;该区块位于侧分支上。
此时,节点认为区块 是最佳头区块,因此它的链是区块
当节点随后收到构建在区块 上而不是当前头区块 上的区块 时,它必须决定是否应该将 作为新的头区块。举例来说,如果分叉选择规则表明 是更好的头区块,节点将回滚区块 和 。它会将这些区块从链上移除,就像从未收到过它们一样,并回退到区块 之后的状态。
然后,节点会将区块 和 添加到它的链上并处理它们。在这次重组之后,节点的链将包含 和 。
此时,节点认为区块 是最佳头区块,因此它的链必须改变为区块
后来,可能出现一个区块 ,它构建在区块 上,分叉选择规则说 应该是新的头区块,节点将再次重组,回滚到区块 并重新播放 分支上的区块。
由于网络延迟,一两个区块的短重组是很常见的。除非链遭受攻击,或者分叉选择规则及其实现存在漏洞,否则较长的重组应该很少发生。
安全性和活性
在共识机制中,有两个关键概念:安全性和活性。
安全性意味着"坏事永远不会发生",比如防止双重支付或确认冲突的检查点。它确保一致性,这意味着所有诚实节点应该始终对区块链的状态达成一致。
活性意味着"好事最终会发生",确保区块链始终能添加新的区块,永远不会陷入死锁状态。
CAP 定理指出没有分布式系统能够同时提供一致性、可用性和分区容错性。这意味着当通信不可靠时,我们无法设计出在所有情况下都既安全又活跃的系统。
以太坊优先保证活性
以太坊的共识协议旨在在良好的网络条件下同时提供安全性和活性。然而,在网络出现问题时,它优先保证活性。在网络分区的情况下,每个分区的节点都会继续产生区块,但无法达成最终确定性(这是一个安全性属性)。如果分区持续存在,每个分区可能会确定不同的历史记录,导致形成两条无法调和的独立链。
因此,虽然以太坊努力同时实现安全性和活性,但它倾向于确保网络保持活跃并继续处理交易,即使在严重的网络中断期间可能会带来潜在的安全性问题。
机器中的幽灵
以太坊的权益证明共识协议结合了两个独立的协议:LMD GHOST 和 Casper FFG。这两者共同被称为 "Gasper" 共识协议。关于这两个协议的详细信息以及它们如何协同工作将在下一节 [Gasper] 中详细介绍。
Gasper 旨在结合 LMD GHOST 和 Casper FFG 的优势。LMD GHOST 提供活性,通过定期产生新区块确保链持续运行。然而,它容易产生分叉且在形式上并不安全。另一方面,Casper FFG 通过定期对链进行最终确定来提供安全性,防止长程回滚。
本质上,LMD GHOST 保持链向前推进,而 Casper FFG 通过确定区块来确保稳定性。这种组合使以太坊能够优先保证活性,意味着即使 Casper FFG 无法确定区块,链仍然能继续增长。尽管这种组合机制并非完美且存在一些复杂性,但它是一个在实践中运行良好的工程解决方案。
架构
以太坊是一个由节点组成的去中心化网络,这些节点通过点对点连接进行通信。这些连接由运行以太坊客户端软件的计算机形成。
节点不需要运行验证者客户端(绿色节点)就可以成为网络的一部分,但是要参与共识,则需要质押 32 ETH 并运行验证者客户端。
共识层的组件
-
信标节点:信标节点使用客户端软件来协调以太坊的权益证明共识。例如 Prysm、Teku、Lighthouse 和 Nimbus。信标节点与其他信标节点、本地执行节点以及(可选的)本地验证者进行通信。
-
验证者:验证者客户端是允许人们在以太坊共识层质押 32 ETH 的软件。验证者在权益证明系统中负责提议区块,取代了工作量证明中的矿工。验证者只与本地信标节点通信,后者为其提供指令并将其工作广播到网络中。
承载真实应用的主要以太坊网络被称为以太坊主网。以太坊主网是以太坊的实时生产环境,用于铸造和管理真实的以太币(ETH)并具有实际货币价值。
此外还有测试网络,用于铸造和管理测试用的以太币,供开发者、节点运营者和验证者在使用主网真实 ETH 之前测试新功能。每个以太坊网络都有两层:执行层(EL)和共识层(CL)。每个以太坊节点都包含这两层的软件:执行层客户端软件(如 Nethermind、Besu、Geth 和 Erigon)和共识层客户端软件(如 Prysm、Teku、Lighthouse、Nimbus 和 Lodestar)。
共识层负责维护共识链(信标链)并处理从其他对等节点接收到的共识区块(信标区块)和证明。共识客户端参与一个独立的点对点网络,其规范与执行客户端不同。它们需要参与区块传播以接收来自对等节点的新区块,并在轮到它们提议时广播区块。
执行层和共识层客户端并行运行,需要保持连接以进行通信。共识客户端向执行客户端提供指令,执行客户端则将交易包传递给共识客户端以包含在信标区块中。通信通过本地 RPC 连接使用 Engine-API 实现。它们共享一个 ENR,但每个客户端使用独立的密钥(eth1 密钥和 eth2 密钥)。
控制流程
当共识客户端不是区块生产者时:
- 通过区块 gossip 协议接收区块。
- 对区块进行预验证。
- 将区块中的交易作为执行负载发送到执行层。
- 执行层执行交易并验证区块状态。
- 执行层将验证数据发送回共识层。
- 共识层将区块添加到其区块链中并对其进行证明,将证明广播到网络中。
当共识客户端是区块生产者时:
- 收到成为下一个区块生产者的通知。
- 调用执行客户端中的创建区块方法。
- 执行层访问交易内存池。
- 执行客户端将交易打包成区块,执行它们,并生成区块哈希。
- 共识客户端将交易和区块哈希添加到信标区块中。
- 共识客户端通过区块 gossip 协议广播区块。
- 其他客户端验证区块并对其进行证明。
- 一旦得到足够验证者的证明,区块就会被添加到链头,被证明合理性并最终确定。
状态转换
状态转换函数在区块链中至关重要。每个节点维护一个反映其对世界认知的状态。
节点通过按顺序应用区块使用"状态转换函数"来更新它们的状态。这个函数是"纯函数",意味着其输出仅依赖于输入且没有副作用。因此,如果每个节点从相同的状态(创世状态)开始并应用相同的区块,它们最终都会得到相同的状态。如果不是这样,就说明出现了共识失败。
如果 是信标状态, 是信标区块,则状态转换函数 为:
这里, 是前置状态, 是后置状态。函数 随着每个新区块的到来而迭代执行以更新状态。
信标链状态转换
与区块驱动的工作量证明不同,信标链是槽驱动的。状态更新取决于槽的进展,而不依赖于区块是否存在。
信标链的状态转换函数包括:
- 每槽转换:
- 每块转换:
- 每周期转换:
每个函数都按照信标链规范中定义的特定时间更新链。
有效性条件
从前置状态和已签名区块得到的后置状态是通过 state_transition(state, signed_block)
计算的。导致未处理异常(例如,断言失败或越界访问)或 uint64 溢出/下溢的转换都是无效的。
信标链状态转换函数
从前置状态 state
和已签名区块 signed_block
得到的后置状态是通过 state_transition(state, signed_block)
定义的。触发未处理异常(例如,assert
断言失败或列表越界访问)的状态转换被视为无效。导致 uint64
溢出或下溢的状态转换也被视为无效。
def state_transition(state: BeaconState, signed_block: SignedBeaconBlock, validate_result: bool=True) -> None:
block = signed_block.message
# Process slots (including those with no blocks) since block
process_slots(state, block.slot)
# Verify signature
if validate_result:
assert verify_block_signature(state, signed_block)
# Process block
process_block(state, block)
# Verify state root
if validate_result:
assert block.state_root == hash_tree_root(state)
def verify_block_signature(state: BeaconState, signed_block: SignedBeaconBlock) -> bool:
proposer = state.validators[signed_block.message.proposer_index]
signing_root = compute_signing_root(signed_block.message, get_domain(state, DOMAIN_BEACON_PROPOSER))
return bls.Verify(proposer.pubkey, signing_root, signed_block.signature)
def process_slots(state: BeaconState, slot: Slot) -> None:
assert state.slot < slot
while state.slot < slot:
process_slot(state)
# Process epoch on the start slot of the next epoch
if (state.slot + 1) % SLOTS_PER_EPOCH == 0:
process_epoch(state)
state.slot = Slot(state.slot + 1)
Resources
- Vitalik Buterin, "Parametrizing Casper: the decentralization/finality time/overhead tradeoff"
- Engine API spec
- Vitalik's Annotated Ethereum 2.0 Spec
- Ethereum, "Eth2: Annotated Spec"
- Martin Kleppmann, Distributed Systems.
- Leslie Lamport et al., The Byzantine Generals Problem.
- Austin Griffith, Byzantine Generals - ETH.BUILD.
- Michael Sproul, "Inside Ethereum"
- Eth2 Handbook by Ben Edgington