EIP-6110:修复信标链技术债务

本文深入探讨了以太坊信标链(Beacon Chain)中EIP-6110提案的实施,旨在优化验证者存款请求的处理机制。文章分析了当前Eth1-Eth2桥存在的安全和效率问题,并论述了去除此桥和Eth1Data投票的必要性,以实现更安全、更高效的存款处理。更新后的机制保证了存款请求的实时处理,减少了节点操作复杂性,提高了验证者的参与体验,同时确保了以太坊网络的安全性。

The Beacon Chain 自 Merge 以来经历了很多变化:这是我在上一篇文章中描述的一个观念的证明。对于像 Beacon Chain 这样复杂且不断发展的协议,工作永无止境,开发者总有事情要修复。为此,上述 EIPs For Nerds 系列已涵盖了两个解决关键问题的提案:委托质押的安全性 (EIP-7002) 和未检查的验证者集增长 (EIP-7251)。

为了保持围绕讨论以太坊共识层升级的主题,EIPs For Nerds 系列的下一篇文章将重点讨论 EIP-6110。EIP-6110: 供应验证者存款链上 是一个以太坊改进提案(EIP),改革了 Beacon Chain 的验证者存款请求机制,并修复了当前影响以太坊权益证明协议的安全性和效率的一系列问题。这些问题,我将在后文中详细讨论,源于存款请求处理依赖于 Eth1-Eth2 桥接。

Eth1-Eth2 桥接是一个“一次性”桥接,允许将 ETH 从以太坊的执行层发送到共识层。Eth1-Eth2 桥接通常被描述为世界上最安全的桥接,它在 Beacon Chain 的操作中发挥着关键作用,并作为想要参与以太坊共识的任何人的入口点。更具体地说,它在以太坊的执行层和共识层之间创建了一个链接,并允许处理来自未来的以太坊验证者的抵押存款请求。

尽管它有着高尚的描述,Eth1-Eth2 桥接仍然容易受到许多桥接普遍存在的问题的影响:安全性差、效率低和工程复杂性高。这些问题源于 Eth1-Eth2 桥接在验证和处理存款方面的方法,我将在本文章的后续部分进行探讨。

EIP-6110 通过弃用 Eth1-Eth2 桥接并引入一种新的将验证者存款传送到 Beacon Chain 的方式来解决这一问题。本文提供了 EIP-6110 的全面概述,并为拟议的 Beacon Chain 存款处理系统的彻底改革奠定了背景。我还将深入探讨该规范,并讨论实施 EIP-6110 的链上存款请求功能的各种优势和潜在缺点。

让我们开始吧!

Eth1-Eth2 桥接的简要历史

Eth1-Eth2 桥接已被证明是一个安全的手段,用于在以太坊的层之间传递存款信息,但是面临几个问题,比如存款处理需要延迟和操作桥接的验证者的复杂性增加。但在我们开始更详细地分析这些问题之前,有必要先理解 Eth1-Eth2 桥接存在的原因。

在 Merge 之前,将资产(存入存款合约的 ETH)从执行层转移到共识层的单向桥接是必要的,因为执行层(即“以太坊 1.0”)和共识层(即“以太坊 2.0”)是独立运作的区块链。Eth1 链是基于工作量证明(Proof of Work)的链,由矿工保护,而 Eth2 是基于权益证明(Proof of Stake)的链,由验证者保护。为每条链拥有一组不同的共识节点有许多影响;例如,Eth2 链无法根据 Eth1 链的状态信息进行操作,除非引入信任假设。

正如我在EIPs For Nerds #2: EIP-7002中提到的,“无信任”意味着“你不需要相信人们能避免不诚实行为,因为有强大的(加密经济)激励去诚实行事。”如 Beacon Chain 这样基于权益证明的协议通过要求每个验证者在加入共识协议之前进行抵押来激励诚实行为。如果一些验证者在参与共识时不诚实,协议可以识别并销毁违规验证者的抵押。

这是可能的,因为(a)Beacon Chain 控制参与协议的验证者余额,并可以根据违规行为惩罚(在某些条件下)减少该余额,以及(b)Beacon Chain 存储了验证者公钥的映射,这创建了验证者在协议中行为的加密链接,并能够高效识别违规者。所有这些都意味着,Beacon Chain 上的验证者可以在参与协议时(大部分)被信任为提供正确的信息。

相比之下,运行 Eth1 链的矿工不能被信任为向运行 Eth2 链的验证者提供有关 Eth1 链状态的正确信息。例如,Eth2(Beacon)链所需的“Eth1 状态”信息——但又无法无信任地访问——包括发送到 Eth1 链上部署的存款合约的交易信息。对于未了解情况的人,存款合约 是一个在以太坊执行层上部署的系统级智能合约,并包括两个功能:

  • 接收并验证任何想要在 Beacon Chain 上激活的新验证者或为现有验证者充值的存款。
  • 使用 EVM 的事件记录机制发送存款收据。存款收据提供了存款发生的记录。

存款合约在一个称为增量 Merkle 树的 Merkle 树结构中存储与存款相关的信息。增量 Merkle 树表现得像一个常规的 Merkle 树(Verkle Trees For The Rest Of Us 提供了有关 Merkle 树的易读介绍),除了某些优化使其对存储和更新信息更有用。例如,存款合约只存储计算根所需的一条 branch(32 个叶节点的集合),并避免在链上存储所有叶节点,从而降低了存储和更新树中元素的成本。(对以太坊的更新提供了一个关于存款合约树结构的很好的概述供感兴趣的人参考)。

一个“普通”二进制 Merkle 树,其中数据元素被哈希以创建叶节点,两个叶节点(子节点)被哈希以创建父节点(branch)节点。没有兄弟节点的最后一个叶节点是 Merkle 树的根节点。 (source)

Merkle 树具有多个优良特性,包括能够(加密地)证明某个特定值是大量数据集的一部分。存款合约为每个存款在 Merkle 树中创建一个叶节点,这允许验证者创建一个 Merkle 证明,证明在存款树中特定位置的存款的存在。Merkle 证明(也称为 Merkle branch)是计算从叶节点到 Merkle 树根节点路径所需的叶哈希集合。

正如我在 Verkle 树的文章中解释的那样,根节点是对树数据的简洁承诺,任何拥有根节点的人都可以验证断言包含在 Merkle 树中元素的 Merkle 证明,而无需存储整个树。在原始 Eth1-Eth2 桥设计中,提议者监视 Eth1 链上的事务,并通过在 Beacon 块中包含存款操作将存款转交到 Eth2 链。提议者包括 Merkle 证明,以证明存款操作对应于存款树中的叶节点——验证者在处理存款之前进行验证。

存款 Merkle 树的根作为 Eth1 链上的存款的“真实来源”,这意味着 Eth1-Eth2 桥的安全性很大程度上取决于验证存款树根的有效性。如果验证者得到错误的存款树根,一个恶意提议者可以为不存在的存款创建 Merkle 证明,并激活未存入所需抵押的新的验证者。

为了确保从 Eth1 链到 Eth2 链间存款的桥接,创建了一个名为 Eth1Data 投票的过程,以便 Beacon Chain 就存款树根达成共识。Eth1Data 代表了 Beacon Chain 对执行层的视图,或更具体地说,是在特定区块高度上存款合约的状态。Eth1Data 投票,我将在后文中进行探讨,为 Eth2 链提供了一种在不必过于信任验证 Eth1 链的矿工诚实性的情况下,推导有关存款的信息的方法。

我们不能信任 Eth1 链的矿工验证存款交易,因为矿工没有质押在 Beacon Chain 上,并且缺乏诚实行事的强烈激励。此外,与权益证明不同,工作量证明对不诚实行为(例如退回过去区块)施加了隐含的成本,并且无法可靠识别和惩罚不诚实行使共识的节点。换句话说,矿工没有任何风险,因此可以在不承受风险的情况下危害 Eth1-Eth2 桥接。

以下是一个例子来说明:

  1. 假设一个 Beacon 块提议者从矿工那里接收了区块 #10000 的头部,显示 Alice 在存款合约中存入 32 ETH(验证者从区块的状态根中提取存款树根)。区块提议者担保 Alice 的存款安全锁定在存款合约中,将存款交易包括在提议的 Beacon 块中。大多数验证者批准包含 Alice 存款的区块,之后协议为 Alice 创建了一个新验证者(余额为 32 ETH)。
  2. Alice 注意到她的验证者存款已创建,并贿赂一个拥有网络 51% 算力 的矿池,以创建一个丢弃包含原始存款交易的区块的另一条链。现在,工作量证明链遵循最长链的规则,节点应遵循具有最多“工作量”(即解决找到有效区块所需取得有效区块的计算能力专用的)的链。
  3. 对于矿池来说,创建一个更长的链是微不足道的,因为它控制着 51% 的计算能力,并且赢得找到下一个延伸链区块的比赛的几率非常大。打掉旧链后,新链将 (不包括 Alice 的存款交易)替换旧链(包括 Alice 的存款),并从链的历史中抹去 Alice 向存款合约发送 32 ETH 的记录。
  4. Alice 现在可以盘刮剩蛋糕且能拥有它。她的验证者已经在 Beacon Chain 上激活(可提取余额为 32 ETH),但 PoW 链显示她拥有她存入以资助验证的那 32 ETH。然后,Alice 可以通过创建一笔新的存款交易在 Beacon Chain 上激活另一个余额为 32 ETH 的验证者来“重复消费”她的资金。
  5. 下一位在 Beacon Chain 上提议的提议者请求来自矿工的最新区块头,并接收包含 Alice 第二次存款交易的区块。提议者(实际上,整个网络)无法发现双重消费攻击,因为它不处理来自 PoW 链的区块。具体来说,该验证者是 Eth1 链的“轻节点”:它可以通过创建一个 Merkle 证明 来证明 Alice 的交易是区块的一部分,但不能知道该区块本身是否有效(即,区块是否正确更新了区块链的状态)。
  6. 一旦协议(无可避免地)为 Alice 创建一个新的验证者,她将在 Beacon Chain 上有 64 ETH 的可提现余额。只要贿赂矿池支付的金额低于 Alice 可以提现的余额,该攻击就是盈利的。

请注意,在这个例子中,矿工不会因为建立了一个反转交易的链而受到任何惩罚。如果大多数矿工诚实并跟随一个保持 Alice 的交易完整性少数链,矿池将浪费用于挖掘替代链的计算资源。竞争行为的成本是隐含的;相比之下,Beacon Chain 上的验证者在犯规时有显性的财务成本:他们的币将被协议销毁。我在EIPs For Nerds #3: EIP-7251 (增加最大有效余额) 的一节中描述了这一特性(称为可追责安全)。

这个例子展示了为什么“信任矿工为你提供关于存款交易的可信信息”对操作 Eth1-Eth2 桥的验证者而言是非理想的。这也凸显了安全机制的重要性,用于安全地传输有关存款交易的信息,从执行层到共识层。该机制的理想实现将由 Beacon Chain 本身确保,并避免依赖于协议外部资产方的诚实性(例如矿工),这就是今天 Eth1Data 投票存在的原因。

什么是 Eth1Data 投票,它是如何运作的?

Eth1Data 投票是一个过程,其中 Beacon Chain 提议者委员会就执行层的状态达成共识,特别是关于验证者存款合约的状态。Eth1Data 投票在 2048 个时隙(SLOTS_PER_ETH1_VOTING_PERIOD)中进行,要求参与 Eth1Data 投票的提议者在提议的区块中包括一个 eth1_data 对象,表示其对执行链状态的视图。

包含在 Beacon 块中的 Eth1Data 包括 block_hash(Eth1 区块的哈希)、deposit_root(存款 Merkle 树的根)和 deposit_count(存款合约中的存款总数),并且是对特定区块高度的执行层状态的“投票”。 Beacon Chain 使用简单多数规则来决定 Eth1Data 投票的赢家:一个 eth1_data 必须在投票期间获得超过一半的提议者投票(1024 或更多)才能赢得投票:

  • 如果大多数提议者在 Eth1Data 投票期间结束时都赞成一种 Eth1 状态(即,1/2 的提议者在提议的区块中包括相同的 deposit_rootdeposit_countblock_hash),则存款树被最终确定,并且 Beacon Chain 更新 state.eth1_data 的值。在此期间包含在存款树中的待处理存款便成为可处理的(提议者创建 Merkle 证明以验证将存款包括在存款树中的有效性,验证者在 Beacon Chain 的状态转换期间进行验证)。
  • 如果没有任何 Eth1Data 获取足够的投票以满足所需门槛,则新的 Eth1Data 投票期将在下一个纪元开始时开启。待处理的存款无法处理,必须等待直到大多数提议者倾向于一种 Eth1 状态。

Eth1Data 投票的安全性基于假设在投票期间的大多数提议者(> ½)是诚实的——也就是说,提议者在区块中包括有效的 Eth1Data 并投票正确的 Eth1Data。如果提议者在 Beacon 块中包含错误的 Eth1Data,将不受惩罚,但至少一半“投票委员会”在提议期间必须在提议的区块中包含相同的 block_hashdeposit_rootdeposit_count,这为其安全性提供了强有力的保证。

拥有一个大型委员会(2048 个提议者)减少了单个恶意参与者在 Eth1Data 投票期间获得大多数提议者控制的几率。基于大量验证者,敌对控制者被选中在 1024 个时隙中提议区块(出现在 Eth1Data 投票期间的总 2048 个时隙中)的概率取决于敌对者获取超级多数(⅔)验证者抵押的能力。例如,一个人拥有 ⅔ 质押 ETH 的不可能性是基于诚实超级多数假设(即,⅔-n 质押的验证者是非敌对的),这决定了 Eth1Data 投票的安全属性以及 Eth1-Eth2 桥的安全性。

⅔-n 诚实假设也支持 Beacon Chain 的安全模型;例如,试图恢复最终区块将导致至少 ⅓ 的验证者被销毁,前提是其他 ⅔ 的验证者是诚实的。基于这一分析,Eth1-Eth2 桥似乎与核心协议一样安全。但事实并非如此,正如我们将在下一部分中分析 Eth1-Eth2 桥的安全弱点时所见。

分析 Eth1-Eth2 桥的安全性

对 Eth1Data 投票的回顾性分析揭示了可以让遭受敌对者控制的所占比例不足 ⅔(或 66%)的活跃抵押的验证者妥协 Eth1Data 投票并利用 Eth1-Eth2 桥的弱点。我们将在本部分文章中分析一些潜在的攻击向量:

通过 Eth1-Eth2 桥制裁存款

一个被敌对者控制的少数验证者可以通过故意投票错误的 Eth1Data,或者根本不投票,来阻止 Beacon Chain 在 Eth1Data 投票期间达成共识。其他(诚实的)提议者会忽略无效 Eth1Data,但敌对者可以稀释委员会投票,以至于没有任何一组合的 Eth1Data 达到所需阀值(1024 位提议者的投票)。

这是一个活跃性失败:Beacon Chain 无法处理待处理的存款,直到另一轮 Eth1Data 投票(持续 2048 个时隙或 ~8 小时)结束并产生明显的赢家。如果敌对者的验证者继续错误投票并防止 Beacon Chain 达成共识,则可以制裁存款处理并随机叫停新验证者加入尽可能长时间。请注意,从验证者的角度看,制裁存款是经济合理的,因为随着验证者设定的增长,每个验证者可获得的总奖励将下降。

另一个需要考虑的影响 GRR [总奖励率] 波动的因素是验证者稀释。越多验证者为网络做贡献,获得的质押奖励在他们之间越会稀释,因为他们被选择为块提议者的可能性会降低。目前,Beacon Chain 上活动的验证者几乎达到 50 万,当前共识层的质押收益为 4.16%。如果有 80 万验证者,这个数字会降到 3.29%。—— Emmanuel Nalepa (PoS Ethereum Staking Rewards: A Deep Dive)。

利用 Eth1-Eth2 桥

一个拥有接近一半(47%)总验证者质押的敌对者可以通过最终确定无效的 Eth1Data 来利用 Eth1-Eth2 桥。Eth1Data 的最终确定要求更新 state.eth1data 的区块(包含 block_hashdeposit_rootdeposit_count)必须得到超级多数验证者在两个纪元内的确认。这是提供安全性的存款处理机制的一个关键部分:验证者应保持与执行(Eth1)节点的连接,并验证 deposit_rootdeposit_countblock_hash 属于包含在规范 Eth1 链中的区块。

如果一个无效的存款树根被最终确定,恶意提议者可以创建(虚假的)存款证明,这些证明可以验证(虚假的)存款树根。结果,如我在 Alice 重复消费验证者存款的例子中所描述的那样,是我们最终创建了 Beacon Chain 上的新验证者,而这些验证者并不需要在存款合约中存入 32 ETH。

这样的攻击对 Beacon Chain 的安全性有负面影响。权益证明的关键是让加入验证者集尽可能昂贵,因此当协议摧毁验证者的质押并将他们从共识协议中驱逐出局时,受到很大的伤害。正因如此,我们要求每个验证者在开始验证之前将最少数量的抵押(32 ETH)锁定在存款合约中。

但这一安全属性在某些验证者能够在未经处理存款的情况下加入验证者集时失效。例如,如果 Alice 能够多次重复消费她的 32 ETH 存款,那么她在 Beacon Chain 上就会拥有不成比例的存款,相比于仅存入过一次的验证者。这意味着 Alice 可以冒险犯下可处罚的罪行(例如,投票无效区块或恢复区块),因为协议正损失她从未支付的资金。

这项攻击还会对以太坊的执行层产生负面影响:如果 Alice 开始从 Beacon Chain 撤回验证者余额到执行层撤回地址,她可以绕过协议强制的发行政策,从而比联邦储备更多地凭空打印出资金。这最终将崩溃 ETH 的市场价值,这与稀缺性和供给有关,降低了持有、花费和使用 ETH 的吸引力,并产生了对以太坊上运行的金融应用的影响。

理论上,对 Eth1-Eth2 桥的安全攻击需要 ⅔ 的验证者(66% 的质押)进行串通,并最终确定一个错误更新 state.eth1data 的 Beacon 块。但一个控制 47% 总有效余额的敌对者可以实现类似目标:

  1. 如果 47% 的验证者投票给不同的块而不是另 43% 的验证者,则 Beacon Chain 需要至少 66% 的验证者社会共识以认可有效的同一块,因此无法最终确定。因此,Beacon Chain 将会分裂成两条竞争链,因为几乎有一半的验证者集在相同高度为不同块做出背书。
  2. 如果没有哪个链进行超过一个固定数目的 epoch 的首个块最终确定,非活动泄漏将会启动,减少未在每条分叉上做评估的活跃验证者的余额。降低非投票验证者的质押确保在每条分叉上的剩余验证者集可以达到最终确定块所需的阈值(活跃质押的 ⅔)。
  3. 非活跃泄漏可以确保少数验证者可以最终确定正确的 Eth1Data 链。然而,攻击者占据上风,只需要 21% 的验证者进行双重投票(47%(敌对者的验证者) + 21%(双重投票的验证者)= 66% 超级多数)即可完成攻击。在正常情况下,双重投票(在同一时隙为两个相悖区块投票)是受到抑制的;但在存在竞争的分叉过程中,验证者为其可用的主要信任支出双重投票,现象会变得容易甚至变得重要。

请记住,质押在验证者身上的 ETH 的价值取决于哪个分叉在最后被认为是规范性。如果 Bob 的 32 ETH 在分叉 A 上,但是分叉 B 得到交易所、应用程序和其他所有方承认作为有效分叉,则 Bob 的 ETH 贬值(除非分叉 A 链成为新的以太经典)。这使得双重投票(在两个链上为区块投票)成为像 Bob 这样的验证者的主导策略——这种情况正好符合攻击者的意图。

一旦分叉 B(我们视为攻击者的分叉)成为规范性链,它可以最终确定无效的 deposit_root,并开始处理虚假存款。攻击者每个区块最多可以处理 16 个存款(MAX_DEPOSITS_PER_BLOCK),每个历史纪元最多 512 个存款,每个历史纪元为 16,384 ETH(16 * 32 ETH 每验证者)。但是,它可以继续处理无效的存款,直到补回其贿赂验证者所消耗的所有资金并从攻击中获得健康的利润。

分析 Eth1-Eth2 桥的性能和效率

除了安全问题,Eth1Data 桥接的设计还引入了性能和效率领域的其他几个问题。这些问题也与 Merge 之前执行(Eth1)链和共识(Eth2)链之间的区别有关。

假设我们相信大多数提议者在 Eth1Data 投票中诚实行事,我们仍然无法对运行 Eth1 PoW 链的矿工做出相同的假设。参与 Eth1Data 投票的提议者可能包括有效的 Eth1 区块根,但正如 Alice 的存款交易的例子所示,一组串通的矿工可以反转 PoW 链,并使恶意验证者重复消费他们的存款。由于随着 Be一个eacon 提高验证者集的正常限度的安全,允许对应的 Eth2 段凌乱是失去经济安全协议 ETH和堡垒。有必要使 Eth2 链远离 Eth1 链中的故障(例如,链重组),从而提高安全性。

解决方案是什么?对存款支付施加延迟,以使存款处理抵御重组。被称为“Eth1 跟随距离”的此延迟确定了 Eth2 链在多大程度上愿意考虑在存款合约中的一组挂起存款。Eth1 跟随距离(ETH_FOLLOW_DISTANCE)最初设置为 1024 笔块,后来增加到 2048 笔块——并且预期提议者只会考虑当考虑为 Eth1Data 投票中的选定者小于该值 کفرت的块。

例如,如果 Beacon Chain 在区块 #6144,则负责提议 Beacon 块 #6145 的验证者则需要考虑 Eth1 块 #4096 以后的存款合约数据才能与正在提议的 Eth1Data 投票兼容。这确保存款在 Eth2 链上处理后的状态一次都没有出现在反转过的远足处,除非 Eth1 链重复数近八小时内重拨远足的时间цепет。我们将该八小时窗口用 ETH_FOLLOW_DISTANCE(2048 笔块)乘以 SECONDS_PER_ETH1_BLOCK(14 秒)得出。

除了使 Eth1-Eth2 桥适应重组之外,Eth1 跟随距离使得协议开发人员和社区有足够的时间对可能影响共识层存款处理的执行(Eth1)链上的重大问题作出反应(例如链分叉)。但这种安全性提高伴随着重大折中:验证者无法立即处理存款,且需要等待 ~16 小时(对 Eth1 跟随距离 8 小时和 Eth1Data 投票会议 Zeiten jr8X 8 小时的时间)。请注意,该计算未考虑延迟,如存款交易在它适合其纸质中也和时间内所花费的令记账所需的时间。

但还有更多!依赖于 Eth1Data 投票将执行层状态导入共识层以处理存款,除了延迟外,还具有其他更多负面影响,增加了新验证者的加入障碍:

低效率的存款处理

对存款冲面规定或到达等待不低于 8 小时的影响(ETH_FOLLOW_DISTANCE)已对此提出了约 处理层移动大约的时间。还有其他因素可能加大这种延误并增加通过 Eth1-Eth2 桥将存款送到 Beacon Chain 的所需时间。一个例子:提议者被限制在每个 Beacon 块中不能包含 16 个存款操作(MAX_DEPOSITS_PER_BLOCK),以限制块的大小,避免增加验证者的高资源需求。

如之前提到的,在 Beacon 块中包含的存款必须伴有每个验证者对于要归回复支流在合约中央标签的 Merkle 证明时间这个段的树返回土地的根,在函数常规块中所需运行草题操作过程中。此外,也需要承认存款过程不影响遭份即提议者产生每笔所需的 Merkle 证明(随着有效工作中浮现是反复情况下)层层数据(关于业主图的组合能够分开)会强调一个明确的限城边境装置暴力的影响。

同时,存款里单的 Merkle 与区块的体负责的一些领域附加的数据相互爱,通过这种间谍证实方式来干扰进行位置处理的验证者,高潮表示都较正;对于河段数字更是被以酸的生机。由于块数量限制专业比较者的处理没有引发 Dense 建筑构造的利用能源,这可能会迸发出必须的 One 行其中反而可能敌对取悦的字段,由于前一 pb 笔要求转生产的小心思;此时 de 验证者场停期或是空间 (– Ether required) 等促成问题向此峰。

工程复杂性增加

维护 Eth1-Eth2 桥接的复杂性来自多个地区:

检索和同步 Eth1Data

共识客户端维护一个 Eth1Data 提取器组件,定期查询执行客户端(使用 JSON-RPC API)以获取与存款合约相关的信息。例如,提议者依赖 eth_call 来执行 get_deposit_root() 运算,该运算计算存款合约 Merkle 树的根,同时使用 eth_getLogs 从交易收据中提取存款事件(稍后将对此进行解释),以及 eth_getBlockByNumber 检索适合在 Eth1Data 投票中的选定块哈希的列表。

验证者还需要持有存款 Merkle 树的本地副本,并验证根是否匹配 Eth1Data 投票赢家的 deposit_root。这要求运行 eth_getLogs 操作下载历史日志中的事件,然后从每个块(进行存款的块)中提取存款事件,并构建一个由提取的存款列表构成的 Merkle 树。不同于提议者,验证者无需与存款合约直接交互,而可以与本地执行客户端接口,利用 JSON-RPC API 完成该任务。

不过,Eth1Data 提取器组件是需要维护和运行的复杂软件。这样的复杂性可能解释了为什么相当数量的节点操作者在主动提取存款合约日志、或返回不正确存款收据并在最坏情况下损坏存款合约数据等方面遭遇了问题。

一些客户端开发者建议,JSON-RPC API 的实现不一致可能会导致这一问题。不同的客户端使用不同的方法从块中检索存款收据,如果一个共识客户端通过执行客户端时所依赖的接口和格式无法获取存款合约数据,则可能会产生矛盾。

“发现损坏的存款历史记录”循环(与 besu)。(source)

Eth1Data 同步过程的复杂性还影响着新验证者节点操作的加入体验。通常,Beacon 节点需要从创世开始与执行客户端同步,作为正常工作流程的一部分,然后才能开始验证和处理区块。但它还需要同步存款合约日志(提取和处理历史存款),以便在完全追赶其他网络之后开始提议/确认块。

从头重建存款合约的状态增加了新验证者的工作负担。对于那些只想运行 Beacon 节点以跟踪以太坊共识层而不参与确认或提议区块的人来说,问题更加复杂——出于这个原因,有人呼吁共识客户端针对这一需求实现禁用历史存款日志的同步选项。Lighthouse 已经为非质押的 Beacon 节点提供了跳过历史存款收据同步的选项,但这在其他客户端团队中似乎还未达成标准化。

(source)

存储 Eth1Data

共识客户端需要存储一系列历史存款收据的快照,从 Beacon Chain 的创世时刻开始,以验证新区块(以重建存款 Merkle 树并验证存款的 Merkle 证明)。虽然存款合约理论上可以将所有存款数据存储为 Merkle 树的叶节点,但在某些情况下,将存款存储在日志中显得更为优惠,显著地降低了链上占用的印记。

然而,存储历史存款在日志中意味着限制验证者删除从存款合约实施部署以来链历史的一部分,增大执行节点的磁盘要求,同时对如 EIP-4444 等旨在减少执行客户端所需处理的归档数据量的历史过期提案构成障碍。(为了理解原标题和状态的区别,我鼓励阅读以太坊网站关于无状态性、州过期和历史过期的页面)。

投票 Eth1Data

Eth1Data 投票在纸面上似乎简单明了:

  1. 查询附属的执行节点以检索在 ETH1_DATA_VOTING_PERIOD 开始时落在批准的时间窗内的执行层块列表。每个块列表中的深度应较出最近的 Beacon Chain 落后 2048 笔块(ETH1_FOLLOW_DISTANCE),且块的深度不能超过 SLOTS_PER_ETH1_VOTING_PERIOD
  2. 通过检查 Beacon Chain 状态中的 votes_to_consider 来汇集到目前为止看到的(有效)投票 Eth1 块哈希。通过在 Beacon 块中的 eth1_data 字段中包含该块高度的存款 Merkle 树根、deposit_count(待处理的存款数)和 block_hash(块头哈希)来为投票获得最大投票数的 Eth1 块哈希。
  3. 如果没有任何 Eth1 块哈希符合步骤 #1 的要求,像跟前投票所需的最佳 ETH jed- 进行投票当前的 Eth1 块哈希和存款根 参@盛renderer存储在 state.eth1data 中(上一个投票周期的赢家), 如果你是 Eth1Data 投票周期中的首位提议者,请为第一个满足条件作品与当前数据生命周期提高索引创建。

我在这里大大简化了一些事情,但这是因为 Eth1Data 投票机制原则上被设计得简单。即便如此,实施 Eth1Data 投票对于许多客户端团队来说实证上变得棘手。由于提议者不能迅速取得关于存款合约的共同视图并最终化待处理存款, так беҙ__7466也随之导致 Eth1Data 投票的有效性降低。

Ben Edgington 对 Medalla 上的 Eth1Data 投票的分析 提供了关于客户端团队在正确实现 Eth1Data 架构中所遇到问题的一个不错的概览。这一分析使用了在 Medalla 测试网中收集的72 个 Eth1Data 投票期间的数据(2,304 个 epoch 或 ~10 天),他们指出了一个惊人的指标:在 22% 的 Eth1Data 投票期间,链未能达成一致的 Eth1 块哈希有效性。

有趣的是,在整个同一期间,存款 Merkle 树的根并没有未能达成 50% + 1 的多数意见。根据作者,这一差异可能是因为存款树根变化的不频繁(在同一时段内的投票块哈希在 3 到 140 之间)。

由于 deposit_root 是(可疑地)更重要,提出建议让提议者分别对 deposit_rootblock_hash 达成共识。数据提示提议者更容易就存款根达成一致,而不是块哈希,因此消除块哈希和存款树根之间的依赖关系可能显著改善追踪存款的效率。以下是提议设计中的代码片段:

(source)提议的设计修改了 Eth1Data 投票策略,以加速存款根的最终确认,并消除处理存款和引导新验证者的障碍。但它也在规范中引入了一些不必要的复杂性——之所以不必要,是因为诚实验证者规范已经提供了一种简单的机制,使提议者能够在没有问题的情况下就 Eth1Data 达成共识。

理论上,提议者可以包含不同的 Eth1Data,因为 deposit_rootdeposit_count 根据前一个区块的存款数量而变化,这将使大多数提议者难以就相同的 Eth1Data 达成一致。然而,如果每个提议者遵循诚实验证者规范(以多数票投票),我们应该能够在投票期结束之前,至少有一半的提议者将相同的 Eth1Data 包含在区块中。

以下是对以太坊基金会核心开发者 Danny Ryan 的建议的回复,供参考:

“这似乎是实施错误而不是规范错误。在第一轮后,对于区块哈希/存款根组合无法达成一致的任何失败(假设最大 1 轮延迟)只是在于未能遵循规范。考虑的投票应该在实现之间 100% 一致,如果我们假设以太坊的链上没有 1000 个区块深的分叉 [这一点在 Goerli 测试网络上是 100% 正确的]。因此,如果待考虑的投票是一致的,而我查看的是一小部分区块,那么我的投票完全是确定性的,无论客户端实现如何,随着投票期内链深度的加深,它应迅速巩固。我想在对规范进行更改之前更好地理解符合性问题的原因。”

现实世界的数据表明,我所引用的 Danny 观点是有一定道理的。例如,Prysm 共识客户端合并了一个请求(在 Ben 发表帖子不到一个月后),以对齐客户端对 Eth1Data 多数投票算法的实现与官方规范,因为用户指出了实现代码和官方规范之间的差异。Teku 也在发现与诚实验证者规范有细微偏差后,对其共识客户端进行了类似的修补,以执行 Eth1Data 投票。

我们能否改进 Eth1Data 投票并修复 Eth1-Eth2 桥?

到目前为止,我们已经确定了与 Eth1-Eth2 桥和存款处理机制相关的两大类问题:安全性和性能。为了纠正部分问题,已经提出了不同的解决方案:

调整 Eth1Data 投票的胜者阈值和投票策略

在强调拥有 47% 的总活跃股份的攻击者可能利用该桥的可能性后,Mikhail Kalinin建议将胜者阈值提高 ,从 50% 提高到 60%。如果攻击者控制了总活跃股份的超多数(⅔),则仅在 Eth1Data 投票期间(1,228 个提议者)才能获得提议者的超多数(⅗),从而使 Eth1-Eth2 桥的安全性与 Beacon 链的安全性保持一致。

也就是说,这个解决方案在生存性方面牺牲了某种程度的安全性。我是什么意思呢?新的阈值要求 Eth1Data 投票期间 60% 的提议者对执行链状态的同一视图投票,这增加了少数拒绝投票或故意投票错误 Eth1Data 的影响。

另一个建议(来自同一篇帖子)是让见证人和提议者共同承担将 Eth1Data 导入 Beacon 链的责任。投票验证者包括 eth1_datadeposit_rootdeposit_countblock_hash)在声明中,而 state.eth1data 随着从 ⅔ 的验证者获得投票的 Eth1Data 的集合而更新。该解决方案允许链更快地确定存款,而重要的是将 Eth1-Eth2 桥的安全性与 Beacon 链的安全性结合在一起(只要有 ⅔ 的验证者正确投票,链就不能被欺骗接受无效的 Eth1Data)。

然而,当时对这种方法的主要关注是网络层的负荷增加。随着验证者每个轮次都对 Eth1Data 进行投票,每个投票声明需要额外的 64 字节用于 Eth1Data,使得投票消息的大小增加了近 50%。

减少 Eth1Data 投票期和 Eth1 跟随距离

减少 SLOTS_PER_ETH1_VOTING_PERIOD 是解决存款处理延迟问题的相对简单的解决方案。但 Eth1-Eth2 桥的效率提升是以付出存款处理安全性为代价的:如果投票期减少,攻击者控制委员会中提议 Eth1Data 的多数提议者的概率也会增加。

下面的表格显示了在不同的 SLOTS_PER_ETH1_VOTING_PERIOD 参数下,攻击者接管 Eth1Data 投票的概率。注意,当 Eth1Data 投票期减少到 128 个分类和 64 个分类时,概率的差异:

(来源)

另一个建议(来自同一篇帖子)是将 ETH1_FOLLOW_DISTANCE 从 2048 个区块减少到一个更小的数字。在当时,这是一个风险,原因如我之前所解释(例如,矿工可能叛变,执行短期重组)。在 Beacon 链已经确定执行层的情况下,这已不再是风险,但问题仍然是:“如果已存在冗余,我们为什么还要通过调整参数尝试修复 Eth1-Eth2 桥?”将存款收据与存款合约快照同步的EIP-4881:存款合约快照接口解决了当前存款处理机制的一些问题:

  • 新的共识节点必须从历史区块下载存款收据,以处理同步 Beacon 链时的区块(增加了同步时间并为引导验证者造成摩擦)。
  • 活跃的共识节点还需要维护历史存款收据的缓存,以便在提议新块时为存款交易创建有效的 Merkle 证明。

EIP-4881 通过创建一个接口,使共识客户端的实现能够通过传递特定区块高度存款合约状态的“快照”来引导同步。这些快照包含已确定的存款,并可用于在不与历史存款合约日志同步的情况下重建存款 Merkle 树(并验证新存款的 Merkle 证明)。这使得同步新的 Beacon 节点变得更加容易,并为希望从本地数据库中修剪历史数据的执行客户端提供了一个解决方案。

虽然 EIP-4881 是对节点操作员用户体验的改进,但这只是一个变通办法,而不是可持续的长期解决方案。一些理由表明,分发 EIP-4881 风格的存款合约快照可能会被证明是不可持续的:

  • 这增加了构建共识客户端的团队的负担,要求其创建和维护额外的功能。理想情况下,客户端团队只需考虑在设计和维护共识客户端的最关键组件上倾注(昂贵)的工程时间。
  • 客户端开发团队被隐含信任,负责提供正确的存款合约快照。验证者可以选择验证存款合约快照与其他来源的比较,但这程度上削弱了减少同步新节点难度的目的。

EIP-6110 和重塑 Beacon 链的存款请求过程

像 Beacon 链设计的许多方面一样,当前处理存款请求的机制是从前合并(Pre-Merge)时代积累的技术债务。如果执行层与共识层连接起来,我们就不需要桥接机制来传递关于存款的信息,因为两个协议会由同一组共识节点验证。

但是,自从合并以来,情况迅速发生变化:

  • 以太坊的执行层和共识层是同步操作的:每个 Beacon 链块在块体中包括执行层的数据(ExecutionPayload)。这意味着要求处理 Beacon 链上存款交易的存款合约数据(例如存款收据)现在默认在链上可用。共识节点不再需要 Eth1Data 获取组件,因为存款合约数据在线上可见,并且可以被验证者附加的执行客户端访问。
  • 重组存款的问题也不复存在:以太坊的分叉选择规则通过要求 CL 块包括最新执行块的头和体,强制执行了执行层与共识层之间的紧密耦合。这消除了重组包含存款的 EL 块,而不重组(已确定的)CL 块的可能性,并意味着存款处理延迟(ETH_1_FOLLOW_DISTANCEETH1_DATA_VOTING_PERIOD 的后果)是多余的。
  • 执行层现在由共识层最终确定和保护。每个验证者都期望验证 Beacon 块的执行 payload(其中存储有关 EL 状态的信息,包括存款合约 Merkle 树的根),这意味着所有诚实节点始终对存款合约有相同的视图。这使得将块哈希导入 Beacon 状态的 Eth1-Eth2 桥和用于确保整个过程的 Eth1Data 投票都变得不必要。

这些观察支持了 EIP-6110,它提出了迄今为止 Beacon 链存款处理系统的最全面改革。EIP-6110 否定了 Eth1Data 投票和 Eth1-Eth2 桥,介绍了一种更新、更高效的处理存款和引导验证者到 Beacon 链的机制。我将深入探讨 EIP-6110 对改革存款过程的方法——但首先,让我们稍作休息,看看在讨论 EIP-6110 时会经常出现的一个概念:事件和交易收据。

过渡:关于智能合约事件和交易收据的快照课程

事件

智能合约在执行过程中触发或发出事件;例如,如果转移代币的所有权,则代币合约可能触发一个事件——事件在语义上等同于“发生在链上的某种事情”。日志与事件用同义词表示,但含义不同:在合约发出事件时生成一个日志,并提供有关发出事件的详细信息。智能合约通过触发事件生成日志,日志描述与事件相关的结果。

每当处理交易时,我们可以通过向完整节点发出 eth_getLogs 请求来查看该交易的事件日志,并根据交易结果采取进一步行动。例如,如果买方用 DAI 付一杯咖啡,我们可以在 DAI ERC-20 代币合约上调用 eth_getLogs 来确认买方的付款,然后再处理订单。

日志有一个主题和数据字段:日志主题是描述事件内部工作原理的 32 字节字;EVM(以太坊虚拟机)有五个操作码可供开发者用于发出事件并创建日志记录(LOG1LOG2LOG3LOG4LOG5)。这些操作码描述可以包含在日志中的主题数,存款合约使用 LOG1 操作码,因为它只有一个主题:

1. 日志的第一部分(主题)包含描述事件的 (日志) 主题数组,最大可以容纳五个主题,具体取决于使用的 LOG 操作码。第一个主题通常存储事件签名,是事件名称和函数参数的哈希值,执行该函数将发出事件。存款合约的事件日志使用 LOG1 操作码,仅有一个主题:DepositEvent 签名:0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5DepositEvent(bytes,bytes,bytes,bytes,bytes) 的 Keccak-256 哈希)。

DepositEvent 是存款合约在处理存款时发出的事件名称,字节表示 deposit() 函数的输入的十六进制编码(还有其他用途):

  • pubkey: 48 字节的值,表示验证者的公钥。公钥是从从验证者的种子词或助记符生成的私钥派生的,表示验证者在共识协议中的加密身份。
  • amount:以 Gwei(以太币的一个子单位)为单位表示的存款金额。存款人可以将完整的存款(32 ETH)发送以注册一个新的验证者,或为现有的验证者增加余额。也可以通过多次存款来累计激活验证者所需的 32 ETH 存款。
  • withdrawal_credentials:在 Beacon 链上接收验证者全额/部分取款的地址。取款凭证以 0x00x1 开头,具体取决于验证者使用的是 BLS (Bonneh-Lynn-Sachman) 取款地址还是执行层地址。有关不同类型验证者取款凭证的很好的介绍,请参见EIPs For Nerds #2: EIP-7002
  • signature:通过用验证者的签名密钥对前面三个字段进行签名生成的签名。签名是保护抵御可能使攻击者创建无效验证者的前提下执行的攻击所必需的。
  • index:索引是存款操作的输出,并分配给由存款合约处理的每一笔存款。一个数值,每次存款合约中存储的新存款都会增加。

存款合约事件模式 (来源)

2. 日志的第二部分(数据)包含与事件相关的编码数据。这些信息可以在主题字段中进行索引,但日志主题具有 32 字节的限制,而数据字段是无限存储的。数据字段可以支持存储复杂类型,如字符串和数组,这使其非常适合我们的使用情景:存储存款合约 deposit() 函数的输入数组,以便我们稍后可以通过解析存款合约事件日志提取并在首次处理验证者存款时将其记录在 Beacon 链的状态中。

交易收据

当智能合约发出事件时,相关的日志数据会被写入并记录在交易收据中。收据是与特定交易相关结果的记录,其中包括交易执行期间生成的日志和交易状态码(例如,失败或成功)的详细信息。交易收据会存储在收据树中,它不是以太坊全局状态的一部分,且收据数据对智能合约是不可访问的(节点可以通过以存档模式运行 ,或请求存档节点中的数据来提取历史区块的收据)。

相比之下,账户存储可被智能合约访问,并保持在以太坊的全球状态中,但使用的成本更高(因为每个节点必须保持状态,而且状态不断增长,这增加了存储磁盘的要求。我们需要确保存储和访问状态数据的成本足以激励节点,并且足够高以防止滥用)。

这使得事件日志比将信息保存在智能合约存储中更便宜。日志数据的成本为每个字节 8 气(gas),而账户存储的成本为每 32 个字节 20,000 气(gas)。存款合约的事件数据是 pubkeywithdrawal_credentialsamountsignatureindex 的 576 字节 ABI 编码。下面是存款合约在成功存款后发出的事件数据示例:

成功存款交易后发出的事件数据示例 (来源)

应用程序二进制接口(ABI)规定如何与智能合约(如存款合约)交互,无论是从另一个合约还是通过离线服务。智能合约的 ABI 包括合约的函数名称、输入参数、常数、事件类型(日志)和数据结构。ABI 编码的数据人类不可读,但我们可以实现 ABI 解码(使用 ABI 中的信息)来将合约执行的结果转换成可读的语法。

存款合约的 ABI (来源)

这对合约事件和收据的概述显然深入到细节,实际上,我在最初理解这些概念时也遇到了麻烦。但基本理解将使理解 EIP-6110 更容易,因为 EIP-6110 在规范的许多部分使用“存款事件”和“存款收据”这两个术语。在澄清了这一点之后,我们可以看到 EIP-6110 的高层运作方式:

EIP-6110 概述:在线供给验证者存款请求

从高层次来看,EIP-6110 的工作方式如下:

1. 执行客户端解析存款请求合约事件日志,通过在 Beacon 块的执行负载中包含存款请求数据,明确将存款请求暴露给 Beacon 链。在构建执行负载时,验证者的执行客户端扫描发出存款请求事件的存款请求交易,提取相应日志中的存款请求数据(pubkeyamountwithdrawal credentialssignatureindex)。

从区块中提取存款请求的操作是顺序敏感的:它必须严格遵循存款请求在区块执行过程中生成事件日志和收据的顺序。在从交易收据中提取存款请求之后,执行节点的区块生产组件将使用存款请求数据填充区块的存款请求字段。区块生成器预计会在将负载传递给 Beacon 节点之前,包含执行负载中的所有待处理存款(直到最大数量)。

2. 在接收到包含存款请求列表的执行请求后,验证者将请求传递给其执行客户端以验证存款。验证者的执行客户端将提取在区块中包含的存款请求发出的事件中的存款请求,并将两组存款请求进行比较。从区块中提取存款请求的操作是顺序敏感的:它必须严格遵循存款请求在区块执行过程中生成事件日志和收据的顺序。

验证者的执行客户端必须验证提取的存款请求列表是否与提议的 Beacon 块负载中的存款请求相等。除了提取的存款请求数量和顺序必须相同外,列表中每个存款请求操作的值也必须相同。识别到提取的存款请求操作与提议者提供的存款请求之间存在冲突的诚实验证者,预计会拒绝整个区块。

3. 在验证者的执行客户端验证了执行请求中包含的存款请求后,共识客户端可以安全地从 Beacon 块中读取存款数据,并按照正常工作流程处理每个验证者的存款。提议者不需要在降低标准上包含 Merkle 证明,以验证存款请求交易包含在指定根中,因为执行层已经验证了存款的存在。

你将注意到这个方案中,验证者的存款请求在出现的同一块中处理。这种“即时存款包含”之所以能够实现,得益于 EIP-6110 的取消 Eth1Data 投票,并将存款直接展示在链上,供验证者立即处理。EIP-6110 还修复我之前突出的一些问题,例如依赖 JSON-RPC API 来获取历史区块哈希和同步存款请求缓存。由于执行客户端的预期下载执行负载,它们提供了对交易收据的实时访问,因此不再需要 eth_getLogs

我们将在查看 EIP-6110 根据规范引入的变化之后,再进一步讨论 EIP-6110 的诸多好处:

执行层变更

1. 基于 EIP-7685 定义新的存款请求结构,并修改 Engine API 以在执行请求中包含存款请求

EIP-6110 定义了一种新的存款操作,并修改了 Engine API,以通过新的存款字段在执行请求中包括存款请求操作。存款对象与验证者在进行存款交易时签名的 deposit_data 相同(存款合约发出存款数据作为 DepositEvent)。新的存款请求操作具有以下结构:

class Deposit(object): pubkey: Bytes48 withdrawal_credentials: Bytes32 amount: uint64 signature: Bytes96 index: uint64

存款对象构成了 EIP-6110 “在链上供给验证者存款请求”目标的基础。Beacon 节点已经通过执行引擎访问到存款请求交易,但共识客户端无法解析存款请求合约日志并解码 ABI 编码的存款数据。执行客户端则支持 ABI 解码,并通过在 ExecutionRequests 中以共识客户端可以翻译的格式包含存款请求数据。

EIP-6110 的存款对象中包括一个索引:一个分配给存款请求合约接受的每个存款的无符号整数值(如前所述)。该索引的功能像交易 nonce,并确保存款请求只能处理一次;该索引也存储在 Beacon 链的状态中,确保存款请求按顺序处理,提议者无法跳过待处理的存款请求(稍后将详细介绍)。

2. 修改执行请求头,以接受存款请求操作的日志事件列表,并更新执行引擎以验证存款请求操作

在从存款请求合约日志中提取存款请求(顺序)后,执行引擎创建存款请求操作的日志事件列表。该存款请求对象的列表作为哈希包含在执行请求头 (ExecutionRequestHeader) 中,并充当对该块中存款请求操作列表的加密承诺。

验证者的执行节点会执行类似的例程(计算存款请求操作的列表哈希,作为哈希承诺进行编码),并将该哈希与 Beacon 块执行负载中存款请求的列表进行比较。如果承诺发生冲突,诚实的验证者预计会拒绝提议的区块。

从存款请求事件中提取存款的代码如下:

def event_data_to_deposit_request(deposit_event_data) -> bytes:
    deposit_data = parse_deposit_data(deposit_event_data)
    pubkey = Bytes48(deposit_data[0])
    withdrawal_credentials = Bytes32(deposit_data[1])
    amount = deposit_data[2]  # 8 字节 uint64 LE
    signature = Bytes96(deposit_data[3])
    index = deposit_data[4]  # 8 字节 uint64 LE
    return pubkey + withdrawal_credentials + amount + signature + index

3. 将存款请求合约地址添加至 Engine API 的配置文件

一个交易可以执行多个操作并生成不同的日志,因此我们需要一个机制来识别区块中指向存款事件的日志。当日志地址(生成该日志的智能合约地址)是存款合约的地址0x00000000219ab540356cbb839cbe05303d7705Fa 时,日志与存款事件相关。EIP-6110 向 Engine API 的配置文件引入了一个 depositContractAddress 对象,以确保执行客户端从正确的事件日志中提取存款请求。

共识层变更

1. 修改 Beacon 块结构以包含存款请求

Beacon 块的结构被修改,包含新的 Deposit 对象。Deposit 对象与 ExecutionRequest 中包含的存款对象相同。EIP 6110 风格的存款操作在 Beacon 链上使用不同的名称,以避免与现有的存款操作冲突。Deposit 对象具有以下结构:

class Deposit(Container): pubkey: Bytes48 withdrawal_credentials: Bytes32 amount: uint64 signature: Bytes96 index: uint64

Beacon 块可以包含高达 MAX_DEPOSITS_PER_30M_GAS_BLOCK 的存款请求数量。请注意,MAX_DEPOSITS_PER_30M_GAS_BLOCK 是存款操作的隐含限制,其数量等于可以打包在执行块中的存款数量,考虑到气(gas)限制(3000 万气)。相比之下,当前的 MAXIMUM_DEPOSITS_PER_30M_GAS_BLOCK = 1,271,明确限制存款请求操作:验证者预计在处理 Beacon 块时确认提议块中不包含超过 1,271 个存款请求。

Beacon 块还接受新的存款请求(从区块日志解析的存款请求列表),类似于 depositsHash。存款请求字段是 Beacon 块存款请求列表的承诺,应该与验证者的执行引擎在提取存款请求后计算的 depositsHash 相等。

注意:“存款请求”一词可能是一个误称,因为我们包含了来自区块日志的事件记录数据。一些人会将存款请求合约描述为“为接受的存款发出存款请求”,这略显不准确——一个交易的日志可以包含多个日志,并且并非所有日志都需要包含与存款请求事件相关的主题和数据。因此,EIP-6110 在此它规定存款请求合约地址集,以帮助从区块的日志中识别存款请求事件的记录。

2. 扩展 process_operations() 方法以添加 process_deposit_request()

process_operations() 函数涵盖了验证者必须执行的任务,以处理新提议的 Beacon 块并更新 Beacon 链状态。待办事项包括处理提议者的惩罚和投票、志愿去除操作、存款请求和余额补充。EIP-6110 修改 process_operations() 以包括 process_deposit_request 操作,以容纳新的存款请求操作。

(来源)

该函数中发生了一些事情:

  • process_deposit_requests 首先在 Beacon 状态中初始化 deposit_request_start_index (deposit_request_start_index 在分叉区块时为“未设置”),设置 deposit_request_start_index 为首次根据新机制处理的存款请求中的 deposit_request_index。这跟踪到目前为止已经处理的存款请求数量(稍后将详细介绍)。
  • process_deposit_request 通过 apply_deposit 将存款应用于 Beacon 状态(即在 Beacon 状态中创建一个新验证者或为现有验证者补充余额)。apply_deposit 函数已在共识客户端中实现,便于代码重用。
  • process_deposit_request 操作是首次检查验证者签名的地方——存款合约不会在 deposit_data 中验证签名,因为 EVM 当前不支持验证 BLS12-381 签名(EIP-2537是一个增加 BLS12-381 签名验证预编译的提案,这可能改变现状)。一个意义在于,攻击者可以发送伪造的存款请求交易,这些交易肯定会在共识层上失败,并通过使共识节点在验证无效存款时浪费资源来进行 DoS(拒绝服务)攻击。

3. 扩展 BeaconState 添加 deposit_requests_start_index

共识客户端在 Beacon 链的状态中保存一个 eth1_deposit_index 值,以跟踪协议所处理存款的总数量。eth1_deposit_index 每处理完请求一次,就增加一次,等于在执行层提交存款请求时被存款请求合约分配给最近验证者存款的索引(回想下索引是存款请求合约发出的事件数据的一部分)。

通过在 Beacon 状态中保存 eth1_deposit_index,我们可以防止提议者意外/故意地排除待处理的存款请求。例如,如果当前的 eth1_deposit_index 为 1000,则下一份验证者存款请求应该有一个索引为 1001。如果存款请求的索引却为 1002,则验证者知道至少有一个待处理存款请求被跳过,应拒绝该块。

EIP-6110 引入了一个 deposits_requests_index,以满足与 eth1_deposit_index 相似的目的。deposits_requests_index 跟踪在 EIP-6110 下由 Beacon 链处理的存款请求操作数量,并确保存款请求按顺序处理。如果提议块跳过一个或多个来自存款请求合约的存款请求,最终状态将与诚实节点按正确顺序应用存款请求时得到的状态不一致。

4. 通过添加 deposit_requests_start_index 和 eth1_deposit_index 限制弃用 Eth1Data 投票

Eth1 跟随距离的长度意味着,在 Eth1Data 投票批准但尚未处理的存款请求期间,会有一个过渡期,但 Beacon 链上的验证者已升级共识客户端,开始使用 EIP-6110 更改中的存款请求处理机制。在此过渡期内,验证者必须维护不同集的存款请求操作:使用旧的 process_deposit() 操作处理的存款和使用新的 process_deposit_request() 操作处理的存款请求。

EIP-6110 进行了一些修改,以确保过渡过程顺利进行,并防止旧存款与新存款请求发生冲突:

  1. BeaconState 对象被修改,以包含新的 deposit_requests_start_index 字段,用于存储根据 EIP-6110 规范在链上包含的第一个存款请求的索引。deposit_requests_start_index 的值由激活 EIP-6110 的块初始化,配合 eth1_deposit_index_limit来促进 Eth1Data 投票和 Eth1-Eth2 桥机制的弃用。
  2. process_operations() 函数添加了一个新值(eth1_deposit_index_limit),以在所有待处理存款请求处理完后禁用以前的存款机制。eth1_deposit_index_limit 将是 eth1_deposit_request_count(存款请求合约中待处理的存款数量)与 state.deposit_requests_start_index(第一个链上存款请求的索引)中较小的一个。

过渡期的运作方式如下:

  1. 在处理新的存款请求时,检查 eth1_deposit_index_limit 是否大于 eth1_deposit_index。如果当前存款的索引低于 eth1_deposit_index_limit,我们知道过渡期仍然在进行中,并且来自 Eth1Data 投票仍有待处理存款请求。
  2. 确认过渡期状态后,检查存款请求数量是不是 MAX_DEPOSITS_PER_30M_GAS_BLOCK(1,024)和 eth1_deposit_index - state.eth1_deposit_index 的较小值。这项检查对于确认提议者是否包括所有待处理的 Eth1-Eth2 桥存款请求,直至最大限制(每个块 1,024 个存款请求)至关重要。
  3. 当新的块到来,某一特定存款请求的 eth1_deposit_index 等于 eth1_deposit_index_limit 时,我们便知该存款请求是来自 Eth1Data 投票的最后一次存款请求。接下来,我们将在此暂停对 Eth1-Eth2 桥机制下的存款处理,并弃用 Eth1Data 投票。这意味着在应用存款请求时执行的is_valid_merkle_branch检查(验证梅克尔证明与存储在 state.eth1_data 中的 deposit_root 之间)将不再必要。

旧存款处理工作流(注意梅克尔证明的验证)(来源)

5. 修改 process_execution_request 以使用存款请求

如前所述,执行请求(以前的 Eth1 块和执行负载)现在成为提议的 Beacon 块的一部分。Beacon 节点对执行请求进行最少的处理,大多数验证留给执行客户端。然而,Beacon 节点必须检查新块的 ExecutionRequest 在其执行节点的视图中是有效的。

这里的 process_execution_request 便应运而生:process_execution_request 对请求进行合理性检查,包括检查执行区块的头部是否存储了(正确的)前一个区块头的哈希(该哈希存储在 Beacon 链的状态中作为 LatestExecutionRequestHeader),并且执行请求的时间戳是否与当前时隙的时间戳匹配。

process_execution_request 还在 BeaconState 中存储来自当前执行请求的头部数据。根据 EIP-6110,process_execution_request 被修改以存储一个额外的头部数据:deposit_requests。这使得一个正在验证新区块的共识节点可以确认新提议的块在前个块上是否正确(例如,它是否包含前个块的 deposit_requests 的正确值)。这在存款请求处理的区块之间形成了链接,并防止上一节中描述的一些边缘案例,例如旧存款的双重支付。

旧存款处理工作流。 (来源)

为什么选择 EIP-6110?铸造验证者存款请求的链上案例

从 EIP-6110 规范的讨论中,实施链上验证者存款请求的好处可能已经显而易见。然而,本节将更深入地解释改革当前存款处理流程的优势:

1. 提高存款处理的安全性Eth1-Eth2 桥的安全性基于一个单一行为者控制在 Eth1Data 投票期间被选择投票的验证者数量 ≥ ½ 的计算概率。如果大多数提议者对无效的 Eth1Data 投票,Eth1-Eth2 桥面临处理虚假存款或允许验证者双重花费存款的风险。

之前的分析 显示对手控制大多数提议者投票在 Eth1Data 投票中的几率很低,表明 Eth1-Eth2 桥在其当前形式下(大部分)是安全的。但我们也注意到,如果活跃验证者集中的稍小比例存在不诚实行为,桥的安全性可能会受到影响。换句话说,Eth1-Eth2 桥并未受到核心协议的保障,也不从Beacon Chain的终局机制(Casper FFG)中导出安全属性。

EIP-6110 引入了一种协议内的存款请求处理机制,该机制要求所有验证者验证来自执行层的存款请求交易。这将对手必须腐蚀的验证者阈值从 47% 提高到 66%(按照股份加权的 ⅔ 验证者),并确保存款请求处理与 Beacon Chain 本身一样安全。

如果 ⅔ 的验证者表现正常(即验证来自执行请求的存款请求),则包含无效存款操作的区块将被协议拒绝。只需一名诚实的举报者发布包括无效存款操作的区块证据,即可斩首那些对该区块作出证明的 ⅓-n 验证者。

诚实多数安全模型还改善了存款请求处理的活性:在当前的 Eth1-Eth2 桥中,一小部分不诚实的提议者可以投票错误或拒绝投票 Eth1Data 以阻止存款请求的处理。协议内的存款处理消除了对 Eth1Data 投票的需求,因此存款处理只能在 ⅔ 的验证者共谋确定未包括存款的区块时中止。

2. 降低上车摩擦,提高节点运营商的存款用户体验

存款包含的延迟更少

自合并以来,执行层和共识层同步运行(Beacon 块包含执行请求且 Beacon 块的有效性与 EL 请求的有效性相关),防止攻击者重组区块以擦除发送到存款请求合约的存款请求的记录。因此,为了保护存款处理防止在执行链上发生长时间重组的可能性,ETH1_FOLLOW_DISTANCE 延迟不再必要,我们可以即时处理存款,而不必等待大约 16 个小时(或更多)来引入新的验证者。

从理论上讲,更长的重组跨越几个区块是可能的,这样,已经活跃的验证者有机会使用在历史区块中发送的 ETH 重新提交存款请求交易。然而,实际上这是非常困难的,因为这需要在一个或多个已确定的检查点后回退 Beacon 块—这种边际情况只能在以下情况下发生:

  • 攻击者控制全体质押 ETH 的 ⅔。 一大票不诚实的验证者可以共谋最终确定一个回退先前区块的区块(这些区块可能包含来自执行层的存款)并阻止大规模斩首事件。斩首需要 ⅔ 的验证者(按照股份加权)批准斩首操作。
  • 攻击者愿意燃烧 ETH 进行安全攻击。重组(已确定的)存款需要构建一个回退典型块的替代链,并说服新节点加入非典型链。由于质押在其他链上的验证者将处于非活动状态,非活动泄漏将泄漏这些验证者的余额,直到两个链分别最终确定。

以上分析意味着,在协议内存款处理的背景下,存款请求处理机制的安全性现在与 Beacon Chain 本身的安全性紧密相关。这使得可以在不引入前所设计的安全问题的情况下,减少存款请求处理的延迟。

我们可以缩短存款交易处理时间的另一个原因(除了去除 Eth1 跟随距离和 Eth1Data 投票期)是,协议内的存款请求处理消除了共识层上验证者对存款数据进行复杂验证的需求。目前,每个验证者都需要维护存款 Merkle 树的副本,并根据 Merkle 树的根验证证明其包含存款的 Merkle 证明。

根据 EIP-6110,验证存款请求包括在存款请求合约中的责任转移给执行客户端,且验证者不需要验证 Merkle 证明便可以处理存款请求。这为每个区块(安全地)增加可以处理的存款请求数量创造了机会。

EIP-6110 废弃了 MAX_DEPOSITS_PER_BLOCK = 16 的预设,并规定使用区块的 gas 限制来限制区块中的存款请求操作数量。根据规范文档的分析,在 EIP-6110 下可以处理的最大存款请求数量约为 1271,相较于之前每区块 16 个存款操作的限制,增加幅度巨大。

更快的同步时间

EIP-6110 通过让 Beacon 链责任的参与独立于执行层历史数据的同步,减少了新验证者的上车摩擦。如前所述,区块提议者需要包括所有待处理的存款请求。证明者也需要将请求连接到存款请求列表,以便处理区块并验证包含在提议区块中的存款操作请求。

EIP-6110 消除了验证者在提议 Beacon 链上的区块之前处理历史存款的需求。共识层对存款请求不需要 Merkle 证明验证,因此,一旦验证者成功同步 Beacon 状态,它便可以开始提议区块并向网络的其余部分提供存款请求,而无需等待附属的执行客户端同步存款请求合约的历史。

(来源)

消除存储和分发存款请求合约快照的要求

EIP-4881: 存款快照接口 是解决在处理 Beacon 块之前下载历史存款交易回执并从头重建存款请求合约的 Merkle 树问题的解决方案。EIP-4881 引入了一种机制,使客户端能够存储和传输重建特定(已确定)区块高度的存款树根所需的最少量 Merkle 树哈希。这减少了新验证者的同步时间,并让执行客户端能够修剪与处理区块和交易无关的链历史的部分。

然而,EIP-4881 为节点运营商引入了额外的复杂性,因为它强制要求存储和更新存款合约快照。这本身可能变成一项相当复杂的任务,并且许多节点运营商在正确同步存款合约快照缓存时遇到问题—有时导致创建无效区块(如果提议者为存款创建无效的 Merkle 证明,则区块可能无效)。此外,并非每个客户端都使用 EIP-4881—一些仍在使用 JSON-RPC API 重新构建历史状态,这个过程非常缓慢且容易出错。

通过消除提议者为存款创建 Merkle 证明的需求,协议内的存款处理(通过 EIP-6110)允许完整节点避免存储存款请求 Merkle 树数据;作证验证者也不需要保留存款树的本地副本来验证区块。最后,新注册的共识节点只需考虑导入执行层的状态,并在同步过程中可以安全地跳过下载存款合约事件日志。

3. 降低共识客户端的工程复杂性

随着 Eth1-Eth2 桥成为处理存款请求的事实机制,共识客户端不得不花时间维护以下组件:

  • Eth1Data 获取器(查询执行客户端的存款合约事件日志)
  • 存款合约日志缓存(减少重建存款树的难度)
  • Eth1Data 投票算法(为提议者实施 Eth1Data 投票)
  • 存款合约快照缓存(加快新节点的同步)

EIP-6110 废弃 Eth1-Eth2 桥和 Eth1Data 投票,消除了客户端团队维护上述组件的负担。除了节省调试问题的珍贵工程时间外,客户端还可以通过减少代码量和降低实现复杂性来减少软件漏洞的风险。

实施 EIP-6110 是否存在潜在缺陷?

1. 破坏 ValidatorIndex 不变量

Beacon Chain 的验证者注册表存储从验证者公钥(pubkey)到验证者索引(ValidatorIndex)的映射。ValidatorIndex 与前面描述的索引不同:

  • 索引与存款请求相关联,并分配给每个发送到存款请求合约的存款。来自同一验证者的两个存款请求因此在索引字段中有不同的值。
  • ValidatorIndex 与验证者(或,更确切地说,验证者的公钥)相关联,并在 Beacon Chain 处理存款请求并创建一个验证者记录时分配,该记录在 Beacon 状态中保存有关验证者的重要信息。每个验证者记录存储的信息集包括 pubkeywithdrawal_credentials 和一个表示验证者是否被斩首的布尔值(以及其他)。

(来源)

验证者索引在每个验证者的存款生命周期中保持不变,在链重组发生时也不会变化。由于我们在处理任何存款之前对待处理的存款进行最终确定(在 Eth1Data 投票期间),因此,在 区块树 的不同分支中,验证者不太可能具有不同的 ValidatorIndex 值。

然而,EIP-6110 移除了 Eth1 跟随距离并废弃 Eth1Data 投票。这意味着待处理的存款在 ValidatorIndex 第一次创建时不再具备最终性概念;因此,同一验证者可以在两个竞争区块中将不同的 ValidatorIndex 映射到其公钥。这可能会为在共识中参与的客户端引发问题,这些客户端依赖公钥索引缓存以快速查找验证者索引。

公钥索引缓存有很多用处,例如在处理新块时确认提议者是否属于活动验证者集。 (我建议阅读 Navie Chan 的 公钥缓存分析 以全面了解公钥索引映射的使用情况。)然而,EIP-6110 破坏了单一公钥索引缓存覆盖 Beacon Chain 状态转移中每一个时刻对所有验证者索引的查找的不变量。

例如,当我们想要处理之前存款的验证者的新存款时,会发生验证者索引查找。在这里,我们无法依赖单例公钥索引缓存,因为它只捕捉了一条区块树分支中的验证者索引。如果处理初始存款的区块重组并被分配不同索引的区块替换,则验证者的索引可能会变化(使旧的 ValidatorIndex 变得冗余)。

EIP-6110 的作者提议的解决方案是,客户端团队应维护两个公钥索引映射:一个 “finalizedPubkey2Index 缓存”,其中包含已最终确定的验证者初始存款的索引,另一个是“unfinalizedPubkey2Index”缓存,包含尚未最终确定的验证者初始存款请求的索引。unfinalizedPubkeyIndex 缓存为区块树中的不同区块而维护,所以,无论哪个区块首先最终确定,客户端都有每个验证者的 pubkey-index 映射(它可以在之后用来更新 finalizedPubkey2Index 缓存)。

另一种方案是建立一个 pending_deposit_requests 队列,累积来自未最终确定的区块的存款请求,并要求 Beacon 节点在处理来自 pending_deposit_requests 队列的存款请求操作之前最终确定区块。这种策略增加了一些复杂性,因为同一验证者的每个存款请求交易将在 pending_deposit_requests 队列中创建一个新的存款并携带不同的 deposit_requests_index。相比之下,对同一验证者的所有存款请求在验证者有资格加入激活队列之前积累到验证者的索引中—从而消除了维护同一验证者的每个存款请求单独记录的需求。

2. 链数据增长

目前,由存款请求合约提供的存款数据存储在历史日志中,并不引起额外的状态增长。然而,EIP-6110 要求验证者在执行层(EL)区块中包括存款请求数据,这可能引发担忧,即执行客户端在 EIP-6110 生效后需要更多的磁盘空间来存储区块链数据。

EIP-6110 有一节专门解决链数据增长的担忧,并淡化引入链上存款数据的长期影响。这是基于对每个存款操作大小的分析(每个存款请求大约 212 字节:pubkey 48 字节,withdrawal_credentials 32 字节,amount 20 字节,signature 92 字节,index 20 字节)。因此,估计包含存款请求数据的执行请求造成的链数据增加约为每年 60MiB—以下是规范中的相关引用:

在此文档最新更新时,提交的存款总数为 824598,相当于 164MB 的存款数据。假设存款交易的频率保持不变,则该 EIP 引起的历史链数据复杂性可估计为每年 60MB,相较于其他历史数据,这是微不足道的。

同样重要的是,去除存储存款请求的需求将抵消因链上存储存款请求数据造成的一部分状态增长。虽然 EIP-4881 已经允许完整节点修剪执行链历史的一部分,EIP-6110 更进一步,完全消除了验证者以存档模式运行执行节点并保留档案数据的要求。

3. 拒绝服务(DoS)攻击向量

共识层

EIP-6110 通过去除 MAX_DEPOSITS_PER_BLOCK = 16 的硬限制,增加了每个区块处理的存款请求数量。每个区块的存款请求操作的最大数量现在完全受到区块大小限制的管控。理论上,去除存款请求操作的限制可能会扩大对共识层的拒绝服务(DoS)攻击的面;例如,恶意验证者可以发送带有无效签名的存款请求交易(共识节点在处理存款请求之前必须验证每个签名,因为存款请求合约无法验证 BLS12-381 签名(这将在 EIP-2537 中发生变化,该优化将包含在 Pectra 升级中)),从而减缓区块处理速度。

尽管如此,EIP-6110 的作者指出,利用存款请求操作高限制进行的 DoS 攻击是不可持续的,原因很简单:存款请求合约仅接受存款 1 ETH(或更多)的交易,作为反垃圾邮件措施。如果攻击者希望通过将 1,271 个无效签名的存款交易打包到一个区块中,减缓区块处理 1.2 秒,则需要在存款交易中为 1271 ETH 花费。(注意这并不包括在执行层区块中包含每个存款请求交易的 gas 费用。)

执行层

“每个存款请求交易 1 ETH” 的规则保护共识层免受 EIP-6110 启用后的 DoS 攻击。但是,它没有保护执行层免受攻击者通过无效交易对存款请求合约进行垃圾邮件攻击。这是说 DoS 攻击在执行层可行吗?并非如此。正如 EIP-6110 规范所解释的,任何与存款请求合约进行交互的人将承担一定的固定成本(例如,基础费用为 21,000 gas),这应当能够阻止潜在的滥用。

批量处理存款请求交易(大型质押池目前已经这样做)可能会降低与存款请求合约进行交互的成本—例如,写入“热”存储槽(即在交易执行期间被修改的存储槽)要比写入“冷”存储槽(即在交易执行期间未触摸的存储槽)便宜。这些“规模经济”可以被一个精明的攻击者利用,并创造一个利用执行层上存款请求交易更高限制的 DoS 向量。

尽管如此,我们可以通过评估诸多因素(如 msg.data 和 calldata 每个字节的 gas 成本,以及存款请求交易的 DepositData 消耗的总字节数)几乎确定存款请求交易的最低成本,确保提供对 DoS 向量的安全性。

4. 对区块验证和处理的影响

EIP-6110 允许执行请求最多包含 1,271 个存款请求操作,这可能对验证和处理区块所需的时间产生一些影响。然而,早期尝试通过模拟大量存款请求的区块对执行客户端进行压力测试的数据揭示了对区块处理和验证的影响程度的适度。

具体而言,在处理包含超过 700 个存款的区块时,执行客户端如 Lodestar 和 Besu 的性能与现有基线的偏差并不显著,正如在存款改革原型文档(由 EIP-6110 团队编撰)中讨论的那样。以下是文档中的相关引用:

与当前主网分析相比,与在区块体验证中发现的 validateDepositRequests 方法相关联的一些开销被注意到。然而,这种增加并不令人担忧。相信处理 EIP-6110 存款请求的区块体验证,比当前主网要长约 2-3 倍,但仍然是 Besu 中整体区块处理的一个小部分。

EIP-6110 生效后,共识客户端可能需要处理更高的存款请求处理工作负载,因为 MAX_DEPOSITS_PER_BLOCK 常量。特别是 EIP-6110 的作者建议客户端开发者应力求支持每区块最多 1,900 个存款请求,以确保稳健性。

结论

EIP-6110 是整体推动减少从 Beacon 链设计早期积累的技术债务并为以太坊构建一个健全、安全和简单的权益证明共识铺平道路的一部分。该提案引入了协议内的存款请求处理,并废弃了Eth1Data投票与Eth1-Eth2桥,这承诺将提高对于独立质押者和专业节点运营商的存款请求用户体验——除此之外,还简化了维护共识客户端的团队的工作。

更重要的是,EIP-6110 提高了存款请求处理机制的安全阈值,改善了 Beacon Chain 的经济安全。验证者的存款请求现已由核心协议保障,并从 Beacon Chain 的终点装置中获取活性和安全属性,这解决了关于 Eth1-Eth2 桥弱点的长期争论。正如一个智者(就是我)曾经说过的:最佳的桥梁就是根本不需要桥梁。

如果你喜欢阅读此文章,请考虑将其分享给可能觉得它有用的人,并订阅 Ethereum 2077。EIPs For Nerds 系列将于下周继续,深入探讨 EIP-7503(零知识虫洞):试图将匿名交易引入以太坊的基础层,修复 Tornado Cash 和其他应用层金融隐私的方法的缺陷。

致谢:感谢 Lido (LEGO) 资助计划Ethstaker 提供资助,以支持我在 EIPs For Nerds 项目中的工作。我还要感谢 Mikhail Kalinin,他回答了我的问题并在研究 EIP-6110 过程中的协助。

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

0 条评论

请先 登录 后评论
2077 Research
2077 Research
https://research.2077.xyz