以太坊开发者已暂停 Glamsterdam 的 Hoodi 测试网分叉,同时正在研究有关交易在 gas 不足时应用程序行为的发现。此次升级改变了网络对某些触及或扩展其状态的操作的收费方式,审计人员发现,即使在升级之前编写的合约,在拟议规则下也可能表现出不同的行为。
摘要
- 以太坊开发者原定于 10 月 26 日进行 Hoodi 分叉,随后推迟。11 月 6 日的开发者更新表示,在团队解决审查期间提出的问题期间,分叉仍处于暂停状态。
- 两项提案 EIP-8037 和 EIP-8038 影响创建状态和访问现有状态的成本。这一变化可能改变合约在执行中某一特定时点剩余的 gas 量。
- ChainSecurity 发现了某些情况:调用可能失败,而发起调用的合约仍能完成其失败处理程序。在较早的 gas 规则下,同一路径可能导致外层交易回滚。
- 开发者正在检查各客户端实现和现有合约中的假设。根据以太坊基金会的更新,ChainSecurity、Dedaub 和内部审查人员参与了此次审查。
- Hegota 的工作在并行推进。值得关注的 Glamsterdam 证据是修订后的测试网时间表以及其背后测试的结果。
以太坊开发者原定于10 月 26 日分叉 Hoodi。他们推迟了该测试,11 月 6 日,以太坊基金会开发者 Marius van der Wijden 表示该测试仍处于暂停状态,同时开发者正在解决 gas 审查中发现的问题。
担忧集中在一个大多数人只有在交易失败时才会遇到的细节上。合约可以调用其他合约,并决定如果这些调用返回不成功时该怎么做。做出该决定的代码在升级后可能看起来完全相同,但由于网络现在对操作各部分收费方式不同,可能会走不同的路径。
这就是为什么一项针对以太坊状态记账方式的升级,已成为应用开发者以及编写以太坊客户端团队共同面临的问题。两者都需要知道哪些旧假设在变化后仍然成立。
Glamsterdam 对 gas 的改变
EIP-8037 改变了协议对创建状态操作的记账方式。以太坊的状态包括合约存储等记录。一项更改可以跨交易持续存在,因此其成本会影响在原始交易完成后维护链的节点。该提案将某些状态创建的记账与普通执行 gas 分开,并设有一个可为符合条件的操作支取的水库。
EIP-8038 对访问状态的操作重新定价。这两项提案影响执行路径的不同部分。合约可能读取一个值、进行外部调用并写入结果。仅审查最终写入的拟议价格,会忽略较早的读取和调用对执行到达该写入时可用预算的影响。
因此,即使用户提供相同的交易输入,一个熟悉的应用程序也可能产生不同的结果。重要的问题是,哪些差异反映了预期的重新定价,哪些差异暴露了客户端或合约所依赖的假设。
一次失败的调用如何改变最终结果
设想一个合约调用第二个合约来执行更大操作中的一个步骤。如果第二个合约失败,调用方可以停止一切,或者处理该失败并继续。批处理器可能会记录一个不成功的项目然后继续。另一个应用可能期望,如果失败的调用消耗了足够多的 gas,整个操作就回滚。
ChainSecurity 的分析描述了新的计费方式如何影响这一区别。以太坊已经根据 EIP-150 规则限制了调用方可以向前传递多少 gas。在相关情况下,大约六十四分之一可以留在调用方手中。问题在于执行返回后调用方实际还有多少可用 gas,以及完成该路径上的指令需要多少成本。
假设一个应用将调用失败视为普通的业务结果。如果它有足够的 gas 来完成失败处理程序,它可能会记录一个结果或继续交易的其余部分。如果它在执行过程中耗尽 gas,外层交易就会回滚。尽管内层调用在两种情况下都失败了,但这些结果会留下不同的记录。
审计人员还检查了基于 gasleft() 的检查。合约可以使用该指令将其剩余执行预算与硬编码的最小值进行比较。在一种计费方案下校准的阈值在另一种方案下可能变成糟糕的预测指标,尤其是当合约遇到状态操作或向另一个地址转发 gas 时。合约没有自动方式来修改其开发者在部署时嵌入的数字。
审计人员需要确立什么
基金会的 11 月更新描述了正在审查的八项假设。至少有两个不同的问题需要测试。实现新规则的客户端必须对给定交易得出相同的结果。应用审查人员还必须找出该共享结果与现有合约预期不同的情况。
针对第一个问题的测试可以在多个客户端上执行同一笔交易,并比较状态和 gas 结果。针对第二个问题的测试需要遵循应用自身的逻辑。合约是否将失败的回调视为继续进行的信号、是否在调用前记录状态变更,以及是否能够区分业务拒绝和从未有足够 gas 的调用,这些都很重要。
这些路径在常规部署测试中很容易被遗漏。典型的交易预算可能足以让调用及其处理程序在两种计费方案下都完成。刻意收紧的预算可以暴露差异。因此,边界测试必须包含失败的调用以及接近进入或完成失败处理程序所需 gas 量的情况。
Hoodi 能展示什么
Glamsterdam 已经登陆以太坊的 Sepolia 测试网,开发者在那里测试了 2 亿 gas 上限。Hoodi 将为开发者提供另一个共享部署环境,用于演练修订后的升级。
公共测试网分叉为客户端团队和应用运营者提供了一个共同环境来运行这些检查。运营者可以按照共享时间表更新客户端、比较结果,并针对分叉规则测试工具。应用团队可以检查交易模拟和 gas 估算是否考虑了他们在执行期间看到的相同变化。
成功的 Hoodi 分叉将为开发者提供关于他们所测试的版本和交易的证据。更长远的问题是,具有代表性的应用在其 gas 预算极限下会如何表现。新的分叉日期如果能配合一份记录——说明复现了哪些情况、相应改变了什么以及团队如何测试——将更容易评估。
gas 相关工作与其他修复同时进行。在 Hoodi gas 审查进行期间,以太坊开发者另行修订了验证者存款规范。这些修复需要各自的测试,因为它们影响分叉的不同部分。
目前,最新的关于 gas 风险的开发者更新给出了暂停的理由,并表示 Hegota 的工作仍在继续。它将下一次 Glamsterdam 测试的时间留待后续决定。
Gas 限制与状态成本如何相互配合
更高的区块 Gas 限制为以太坊在其记账系统下提供了更多的工作空间。分配给单个操作的定价决定了该操作占用多少空间。因此,对限制的更改和对价格的更改可能影响同一笔交易的不同部分。
有些工作会在交易结束时终止。创建状态则不同,因为数据可以持久存在,给节点运营者留下需要存储和后续处理的内容。一个新的存储槽、账户或合约可能会增加这种负担,即使创建它的应用此后几乎没有流量。
EIP-8037 改变了某些状态创建成本的记账方式。EIP-8038 重新定价了对状态的访问,其中可能包括涉及已存在记录的操作。一笔读取某个值、调用另一个合约并创建存储条目的交易,可能在单次运行中同时遇到这两种变化。
仅凭更高的区块限制并不能说明该序列的成本会更高还是更低。它描述的是整个区块可用的空间。交易的路径以及每个操作的价格决定了它能否在发送方提供的 Gas 范围内完成。
当 Sepolia 以 2 亿 Gas 限制开始测试 Glamsterdam 时,这一区别就已经具有相关性。该测试展示了开发者在该网络上运行的内容。对 Hoodi 的暂停则涉及这些规则是否已为下一个测试网做好准备,包括依赖早期 Gas 假设的应用程序的行为。
正在审查的八项假设
以太坊基金会的审计仓库将其示例按操作价格的变化和 Gas 跟踪方式的变化进行分组。仓库中的数字来自拟议规范的固定版本,如果提案被修订,这些数字可能会发生变化。
第一组关注点涉及固定配额以及调用后完成工作的成本。开发者可能为某个操作设定了 Gas 限制,而该操作现在的成本更高了。原本用于支付最终写入的预留量可能不再足够。在另一种情况下,调用者保留的一小部分可能足以完成一条应用程序此前预期会回滚的失败路径。
记账方式的更改引入了其他可能性。如果单独的资源池仍能为符合条件的状态创建提供资金,那么调用者即使其普通执行预算紧张,也可以在调用失败后写入一条记录。调用者的最终结果随后可能取决于哪些操作使用了该单独预算。
在审查的规范中,清除同一笔交易中先前创建的槽可以立即返还部分状态 Gas。回滚的调用也可以返还状态 Gas。因此,基于“回滚前消耗的所有 Gas 都已消失”这一假设编写的代码可能会误读实际发生的情况。
同样,两次 gasleft() 测量也可能对工作成本给出不完整的视图。使用两者差值向某人收费或计算退款的合约,依赖于对两次读数之间 Gas 如何变化的假设。从单独池中扣除的费用以及执行期间返还的状态 Gas 可能会改变这种关系。
第八种情况涉及 EIP-7702,它允许账户授权将委托给合约代码。即使交易内部的某项授权因 Gas 不足而未生效,该交易仍可能被包含。仅凭交易被包含就推断委托已生效的钱包或服务,可能会将未完成的操作视为已完成。
该仓库表示,其作者收到的许多发现都涉及固定金额或预留量。它要求开发者和审计人员针对这些示例测试其应用程序并报告问题。该清单识别了需要审查的模式。它并不意味着使用其中任一模式的每个应用程序都会产生损失。
一次失败的调用如何仍能留下成功的交易
一笔以太坊交易可以包含多个调用。一个合约可以请求另一个合约执行某个步骤,然后决定如何响应结果。内部调用失败与交易失败是两种不同的结果。
批处理器提供了一个有用的例子。如果某一项失败,处理器可能会记录该失败并继续处理下一项。如果它在能够记录结果之前耗尽了 gas,外层交易可能会回滚并撤销先前的更改。因此,同样的内部失败会根据调用方之后能做什么而留下不同的最终状态。
另一个不同的应用可能会将失败的调用视为更改谁可以提交请求或是否允许再次尝试的理由。如果调用方能够完成一条应用原本预期会回滚的路径,那么产生的状态就可能持续存在。安全后果取决于所完成的路径实际记录了什么。
某个行为者可能能够选择一笔交易提供多少 gas。如果该人能够留下足够的 gas 让外层合约进入某条路径,但留下太少的 gas 使内部调用无法完成,那么该失败就可以由交易预算诱发。对失败采取行动的合约需要将这种情况与一个确实到达了被调用方并合法失败的调用区分开来。
EIP-150 转发限制是其中的一部分。按照相关规则,尽可能多地转发 gas 的调用方大约保留六十四分之一。保留的金额仍然需要支付后续指令的费用。这些指令是否完成取决于调度以及应用于它们的计费方式。
一个合约可能对很窄的预算范围存在漏洞。给内部调用足够的 gas,它就能正常工作。给整笔交易太少的 gas,外层路径就会回滚。有问题的区间是内部调用失败、而调用方仍有足够 gas 执行开发者并未针对该失败所预期的操作。
为什么更改一个固定的 gas 数值可能无法解决问题
硬编码的阈值可以为合约提供一种简单的方式,在发起调用之前检查它是否有足够的 gas。开发者可能已经用常见交易测试过该阈值,并为后续工作留出了缓冲。这个数值变成了对选择它时所依据的调度的依赖。
一项操作的成本也可能因应用是在更新现有记录还是创建新记录而不同。向已经拥有余额的人转账可能会遇到与创建首次余额的转账不同的存储路径。仅覆盖重复接收者的测试可能会遗漏成本更高的路径。
合约测量 gas 的位置很重要。在调用之前立即读取与在若干次状态访问和调用本身之后读取是不同的。从后一次读取推断失败原因的合约假设它了解其间可能被收取或返回了什么。
更改津贴额度本身也可能带来后果。它可以让先前失败的路径完成,也可以给被调用方足够的 gas 执行在旧额度下无法执行的工作。开发者需要在调整之前了解该额度原本旨在防止什么。
有些合约可以通过治理或管理流程进行更改。其他合约则是不可变的,或者依赖于无法就地更改的组件。即使合约可以被修订,估算、赞助或中继其交易的服务也可能需要更新它们自己的假设。
一个有说服力的复现需要展示什么
测试可以在旧规则和新规则下以相同的交易、状态和 gas 限制开始。比较应当识别出哪个调用返回失败、调用方接下来做什么,以及交易是否记录了任何先前未能持续存在的更改。
测试略高于和略低于阈值的预算有助于表明该影响是否占据一个狭窄的区间。它还可以识别执行在何处发生分歧。早期的余额检查、转发给被调用方的金额以及失败处理程序中的写入是更改可能显现的不同位置。
然后审查者必须询问谁能够到达该路径。在测试合约中复现的更改确立了执行上的差异。真实应用可能要求授权、检查单独的条件或使受影响的操作可重试。这些细节决定了是否有人能够强制产生不想要的结果。
客户端团队还有一项单独的测试。在相同的分叉配置下,所有执行客户端对同一笔交易都应得出相同的结果。客户端之间的分歧,与所有客户端一致地产生应用程序未曾预料的结果,是不同的问题。
测试套件既需要失败路径,也需要成功路径。强制每一次失败的调用都回滚,可能会阻止一次不想要的状态变更,但也会移除完成批次其余部分的合法能力。预期的应用程序行为决定了修复需要保留什么。
一个好的复现应当让最终状态易于检查。如果一条失败记录在一次运行后仍然存在,而在另一次运行后消失,测试就能展示确切的差异,而不是依赖关于 gas 使用量的一般性报告。这使评估风险和拟议变更都变得更加容易。
在本地复现之后,公共测试网还能补充什么
开发者可以在本地分叉或受控的客户端环境中测试一个边缘情况。公共测试网随后让多个运营者在共享条件下运行相同的升级并比较其行为。它还为钱包和基础设施团队提供了一个地方,让他们针对生效的规则测试自己的软件。
Sepolia 和 Hoodi 在这一过程中服务于不同的步骤。Sepolia 的激活表明,开发者可以在该网络上运行计划中的代码,并在配置的 gas 限制下检查其运行情况。Hoodi 则让他们在处理审查期间变得重要的发现之后进行进一步部署。
测试网上的应用程序、流量和历史状态与主网不同。一次顺利的分叉可以提供关于客户端一致性和常规交易的证据,但并未覆盖用户依赖的每一种合约模式。有针对性的复现则提供了关于某个边缘情况的证据,而该情况可能不在普通测试网流量中出现。
升级说明也是测试的一部分。运营者需要一个实现预期配置的版本以及一个精确的激活点。应用程序需要知道其模拟应在何时开始使用修订后的规则。在这些细节之外,一个新的日期也很有用。
十月的推迟和十一月的状态报告在不同时间描述了同一次暂停。前者取消了原定的 Hoodi 日期。后者说明这些问题仍需解决。下一次部署通知可以显示哪些变更已准备好供运营者测试。
钱包如何在操作失败时报告成功
钱包通常会在要求用户签名之前估算 gas。估算取决于节点在其模拟中所应用的状态和规则。使用较旧分叉配置的服务可能给出一个不再适合当前链所执行交易的估算值。
一笔回滚的交易仍然可能让发送者花费 gas。一笔在捕获内部失败后完成的外部交易可能更难解读。链可以将整体交易记录为成功,而其中的某个操作并未发生。钱包可能需要应用程序的事件或状态来向用户解释这一差异。
中继者和交易赞助方面临一个相关的问题。他们可能为别人请求的交易选择预算或支付成本。如果预算不再覆盖预期路径,赞助方可能为未能交付所请求操作的执行付费。
索引器可以在执行之后看到效果。一条现在能够完成的失败路径可能产生下游系统此前未曾见过的日志或状态变更。索引器仍然遵循链的实际状态。依赖索引器的应用程序可能需要改变其解读所产生记录的方式。
端到端测试可以比较钱包的估算、中继者提交的预算、客户端的执行结果以及界面的解释。每一步都可能各自成功,而用户仍然收到关于哪些操作生效的误导性说明。在应用程序将某一结果视为最终结果的地方,这一区别最为重要。
什么能解除 Hoodi 的暂停
基金会正在要求应用程序团队识别他们所依赖的假设,并提供开发者可以复现的案例。有些问题可能通过修改拟议的 gas 规则来解决。其他问题可能要求应用程序或基础设施提供商修订其处理新行为的方式。
如果规范发生变化,相关的测试是:一个此前出现分歧的案例现在是否能达到预期结果。如果开发者保留某项行为变更,应用程序团队需要足够的细节来判断其软件是否依赖先前的结果。两种结果都取决于描述受影响的调用以及在其之后持续存在的状态。
修订后的规范还必须传达到客户端构建和运营者运行的版本。支持早期 Glamsterdam 配置的构建可能不包含后续的更改。新的 Hoodi 通知中的发布版本号将指明哪些版本可以一起测试。
基金会的 11 月更新表示,Hegota 的工作仍在并行推进。这意味着对较早分叉的暂停并不涵盖下一个分叉的所有开发决策。对于 Glamsterdam,下一个相关里程碑是修订后的 Hoodi 时间表,其中包含开发者已准备好公开运行的配置。
一次成功的分叉将为各团队提供一份共享记录,供其在做出主网决定之前进行审查。引发延迟的那些案例仍然是检验修订后规则是否准备就绪的最清晰测试。这些测试必须展示当调用在其 gas 限制附近失败、而外部交易仍有剩余工作要做时会发生什么。
拟议的 gas 数值揭示了不同路径的哪些差异
基金会的代码库给出了一个具体示例,说明创建状态与更新状态之间的差异。在其固定的 Glamsterdam 规范下,创建新槽位的冷写入被拆分为执行成本和显著更大的状态费用。对现有槽位的写入也会重新定价,但遵循不同的计算方式。
这种差异可能出现在两个用户发出看似相同的应用请求时。一个用户可能之前使用过该服务,因此合约更新的是现有条目。另一个用户可能需要一条新记录。对第一个用户有效的 gas 限额对第二个用户可能会失败。
该代码库还表示,在其测试的配置中,只有在超过指定的交易 gas 限制时,才会有一个单独的资源池可用。在较小的交易中,状态费用则来自普通预算。这一阈值使得测试多种预算变得很重要,而不是假设该资源池对每次调用的行为都相同。
因此,读取剩余 gas 的代码在某些情况下只能观察到交易资源的一部分。它读取的数据对于该指令所暴露的预算仍然有意义。变化的是应用程序能够就已被消耗或仍然可用的总资源得出的推断。
这些 gas 示例与拟议的配置相关联。它们并未给出一个可应用于钱包现有估算的单一乘数。首次存储写入、使用固定津贴的调用以及失败的委托各自都需要单独检查,因为它们会遇到规则的不同部分。
这也是为什么修订后的 Hoodi 日期若能附带规范修订版本以及该发布所涵盖的案例列表,会更有用。这些细节将使各团队能够重新运行与其应用程序相匹配的测试,然后再从测试网的整体稳定性中得出结论。
关注要点
- Hoodi 时间表:修订后的分叉日期应明确操作者预期运行的客户端版本和分叉配置。
- 客户端一致性:跨客户端测试可以显示执行客户端在新边界附近是否达到相同的状态和 Gas 结果。
- 合约行为:复现应显示失败的外部调用何时为调用者留下足够的 Gas 以完成执行,以及外层交易记录了什么。
- 提案修订:对 EIP-8037 或 EIP-8038 的修改将有助于显示哪些审查发现需要协议变更。
- 应用工具:模拟和 Gas 估算结果可以显示钱包和基础设施提供商是否正确反映了分叉规则。
常见问题
什么是 Hoodi?
Hoodi 是一个以太坊测试网,用于在共享环境中演练网络升级。被推迟的分叉本应在该测试网上应用 Glamsterdam 规则,以便操作者和应用团队能够共同观察这些规则。
分叉为什么被推迟?
开发者正在审查与 Gas 核算以及现有应用处理失败调用的方式相关的发现。他们还在检查各客户端实现是否就拟议规则下的行为达成一致。
此次暂停是否改变了当前的以太坊主网?
11 月的更新涉及的是 Hoodi 测试分叉。它并未宣布在主网上激活拟议的 Gas 规则,也未给出新的主网日期。
合约能否在没有更新的情况下改变行为?
可以。其指令按照网络的 Gas 规则执行。如果变更后的收费为外部调用或其失败处理程序留下了不同的预算,相同的指令可能导致不同的结果。
失败的调用是否总是回滚整个交易?
调用者可以处理不成功的外部调用并继续执行。外层交易是否完成取决于调用者的指令以及执行这些指令所剩余的 Gas。
Gas 储备池有什么作用?
根据该提案,它为符合条件的状态创建操作提供了一个单独跟踪的预算。审查者必须考虑该预算与调用者可以检查或向前传递的执行 Gas 之间如何相互作用。
审计人员为什么测试低 Gas 路径?
预算充裕的调用在两种规则下可能表现相同。接近执行限制的交易可以揭示处理程序在一种规则下完成而在另一种规则下回滚的情况。
Hegota 的工作是否已停止?
基金会的 11 月更新表示 Hegota 的工作在并行继续。该工作有其自己的里程碑,而 Glamsterdam 的 Hoodi 分叉仍受 Gas 审查的约束。






