# 迈向仅有效性部分无状态(VOPS)的务实之路

本文为机器翻译
展示原文
ChatGPT 图片 2025 年 4 月 22 日 上午 11_26_48
ChatGPT 图片 2025年4月22日 上午11_26_48 1024×1536 148 KB

介绍

以太坊长期以来一直追求无状态验证的目标:使参与者无需存储整个链的状态即可验证区块。无状态旨在降低硬件要求,促进验证者节点之间的去中心化,并通过允许构建和验证更大的区块而无需所有节点复制完整状态来解锁可扩展性。

实现这一愿景的一个主要方案是弱无状态,即只有区块生产者保留完整状态,其他节点使用小型状态证明来验证区块。弱无状态虽然因其简单高效而颇具吸引力,但也带来了一个关键挑战:在大多数节点无法独立验证交易的世界中,以太坊如何保持其抗审查 (CR) 特性?

在本文中,我们将探讨弱无状态性为何会削弱以太坊的抗审查性保证,并提出一个务实的解决方案:仅验证部分无状态性 (VOPS) 。VOPS要求节点存储仅够验证待处理交易的账户数据,从而将存储量减少 25 倍,同时又能保持以太坊的抗审查性。

:bulb:

我们认为:

  • 弱无国籍状态本身并不能保证强大的抵制审查能力。
  • 未来的设计必须重新审视强无状态性,并解决实际问题,例如谁生成这些证明、哪些类型的证明最有效,以及带宽和证明成本如何影响节点要求。
  • 与此同时,仅有效性部分无状态 (VOPS)提供了一种简单有效的桥梁:将本地存储减少25 倍,同时保留功能性、抗审查的公共内存池。
    AA-VOPS扩展了 VOPS 以支持完整的本机帐户抽象,通过本地缓存和增量更新最大限度地减少见证开销,提供了实现强无状态的途径。

原因

我们首先会解释,如果我们想通过FOCIL之类的机制为所有交易提供强大的抗审查保障,那么弱无状态(即仅依赖区块生产者持有状态)为何行不通。在深入探讨之前,我们先来快速回顾一下 FOCIL 在有状态的世界中运作所需的主要要素。我们将展示 FOCIL 当前的设计如何依赖于内存池能够保留有效交易并剔除无效交易的假设:

  • 用户发送交易,如果有效(即通过随机数和余额检查),则会在节点之间广播,并在公共内存池中保持待处理状态。
    注意:在本文中,我们使用术语“节点”通过其执行层客户端来指代内存池维护者。
  • 包含者每个时隙,16 个包含者观察待处理的内存池交易,将它们添加到包含列表(IL),并在 CL P2P 网络上广播这些 IL。
  • 区块生产者必须在其区块中包含所有 IL 有效交易的并集。只有当 IL 交易无效或区块已满(即条件属性)时,才能将其从区块中排除。
  • 证明者会验证所有 IL 交易是否都已包含在区块中。如果已包含,则证明者会投票支持该区块。否则,他们会评估区块是否已满,或缺失的交易是否有效,以确定排除交易是否合理,或者该区块是否存在审查行为。

现在,假设该协议只要求区块生产者持有状态。这意味着用户、维护内存池的节点包含者证明者无法自行确定交易是否根据preStateRoot有效。事实上,他们将无法访问关键验证检查所需的完整、最新的状态信息,包括确认发送者是否拥有足够的balance以及交易的nonce是否正确。需要注意的是,目前 dApp 已经提供了任何智能合约条件或状态相关逻辑(例如,Uniswap 池的当前状态),以帮助用户避免回滚。

因此,在一个弱无国籍的世界里:

  • :white_check_mark:对于证明者来说,一切都很顺利:他们根本不需要存储任何东西,因为区块生产者会生成区块级见证。这些见证可以采用聚合 Merkle/Verkle 证明(例如IPA 多重证明)甚至 SNARK(稍后将对此进行详细介绍)的形式,它们表明区块中包含的交易进行的所有状态访问对于preStateRoot都是有效的。换句话说,它们证明了为区块执行提供的数据存在于前一个区块状态根中。重要的是,区块生产者还必须为他们排除的任何 IL 交易附加见证(使用与 IL 一起提交的见证或通过重建它们),以便证明者可以重新执行区块并验证每个遗漏是否合理。
  • 对于维护内存池和包含器的节点来说,如果我们希望它们无状态,就会存在一个根本问题。当用户将交易发送到公共内存池时,节点无法知道这些交易是否符合preStateRoot有效性,因为它们无法执行常规的noncebalance检查来确定是否应该重新广播交易或将其从本地内存池中删除:
    • :x:攻击者可以利用这一漏洞,用无效交易淹没内存池和 IL ,从而有效地抵消 FOCIL 的好处,并审查那些原本可以从纳入 IL 中受益的交易。
    • :x:缺乏noncebalance检查作为第一道防线,会形成一种新的 DoS 攻击向量:它允许任何人都向内存池或 IL 提交无效交易,从而降低内存池的质量,并使有效交易更难浮出水面。这会产生与包含者对抗并用垃圾填充 IL 时相同的破坏。关键区别在于,如今只有验证者(拥有 32 ETH)才能成为包含者,而如果没有基本的过滤机制,任何参与者都可以降低内存池质量并干扰包含列表的有效性——这降低了攻击门槛,并在实践中削弱了抗审查能力。

怎么办?

在下一节中,我们将探讨克服弱无国籍方法所带来的挑战的潜在选择。

选项 1:强无国籍

一种看似简单的方法是要求用户交易与针对preStateRoot完整状态访问(例如,Merkle 或 Verkle 证明)捆绑在一起,这通常被称为强无状态。我们甚至可以放宽强无状态的严格定义,要求每个先前的区块生产者为每笔交易附加状态见证,以便节点可以按需获取它们。理论上,这将允许任何节点(包括包含者证明者)独立验证交易的状态访问是否与preStateRoot一致,而无需访问完整的当前状态。这种机制对于有效管理内存池很有用:节点不必每个 slot 都排除和包含交易,而是可以保留不受后续区块影响的交易,并修剪那些 nonce 或余额已更改的交易。

但在实践中,它需要在节点和当前区块生产者之间建立实时传输通道,从而引入强信任假设和新的审查向量:区块生产者可以延迟或选择性地保留交易级证明,将目标交易排除在每个内存池和包含列表中,悄悄地将其排除在外,同时仍然生成一个看似有效的区块。此外,对于新交易,仅依赖前一个区块的状态是不够的。用户需要访问更新的实时状态见证,其中包括交易实际触及的账户和存储槽,以及重建这些条目路径所需的状态 trie 的任何其他部分——这是由于 trie 结构连接状态的方式所致。持续获取这些证明不仅会增加带宽使用量和延迟——从而降低用户体验——而且还提出了一个关键问题:谁应该充当提供证明的节点?钱包?dApp?Portal 网络?超级节点(即质押2048 ETH的验证者)?还是以上所有?

如果我们希望见证者和包含者节点完全无状态并在智能手表上运行,强无状态可能是最终目标,但进一步的研究仍需解答这一问题,并通过评估以下两方面所需的成本和硬件要求来确定哪些参与者最适合履行这一职责: (1)存储完整状态; (2)生成并广播与用户交易相关的见证和证明。需要注意的是,这种方法只需要 N 选 1 的诚实假设——理论上,一个能够生成有效证明的诚实参与者就足够了。然而,在实践中,仅依赖一个或极少数参与者可能会导致诸如租金抽取(例如承诺攻击、审查制度和垄断定价)等问题。

选项 2:仅有效的部分无国籍状态

一种务实的短期方法是依赖部分无状态机制,仅存储验证交易有效性所需的最少数据。在 VOPS 下,每个节点每个 EOA 仅维护四个字段—— address (20 B)、 nonce (8 B)、 balance (12 B)和一个 1 位的codeFlag而不是完整的状态。

当交易到达时,节点会检查codeFlag

  • codeFlag = 0 (纯 EOA,无委托指示器 - 意味着帐户不能将执行委托给自定义代码):
    • 验证签名、随机数、余额与费用以及 gas 限制。
    • 允许每个地址进行多个正在进行的交易。
  • codeFlag = 1 (任何能够使用 23 字节EIP-7702委托指示符运行代码的地址):
    • 每个地址最多执行一项待处理交易。
    • 由于委托代码可能会不可预测地改变状态,因此可防止随机数/余额冲突。

在每个新块上,节点都会使用所有修改过的四元组更新其表,删除任何因陈旧随机数或余额不足而无效的交易,每当帐户的codeFlag0翻转为1 时,删除除最高优先级待处理交易之外的所有交易,并且当标志从1翻转回0时,提升任何排队的 EOA 交易,其随机数现在匹配。

由于每个帐户条目只有20 + 8 + 12 + 0.125 ≈ 40.125 bytes ,因此维护约 2.41 亿个帐户需要:

241\,\text{百万} \times 40.125\,\text{字节} \approx 8.4\,\text{GiB}
241百万× 40.125字节 8.4吉布

与目前~233 GiB全状态大小相比,这减少了25 倍以上 (h/t Guillaume),但仍然能让 VOPS 节点有效地维护内存池。需要注意的是, 8.4 GiB数据是未压缩的,因此这只是对 VOPS 所能节省的存储空间的保守估计。

Verkle 的 VOPS

为了使 VOPS 理念具体化,我们首先在Verkle设置中锚定该提案。

区块头字段

场地目的
preStateRoot执行块之前的状态根。
postStateRoot执行后的状态根。
区块级witness (IPA 多重证明,在区块主体中)证明执行期间读取的每个状态元素对于preStateRoot都是有效的。
transactions完整的交易清单。

让我们回顾一下在那种情况下谁做了什么:

  • 用户将交易广播到公共内存池。在 VOPS 下,部分无状态节点每个帐户仅保留四个字段—— addressnoncebalancecodeFlag ——足以决定每个待处理交易是保留还是删除。

  • 包含者每个 slot,16 个包含者会观察内存池中待处理的交易,将其添加到包含列表 (IL),并在 CL P2P 网络上广播这些 IL。如果包含者也维护内存池(就像现在的情况一样),则在将交易包含到 IL 之前无需进行额外检查,因为针对preStateRoot (例如, noncebalancecodeFlag )的有效性检查已作为内存池维护的一部分执行。但是,如果将来包含者被拆分成独立角色(例如,轻量级的“智能手表”包含者),则它们需要在将交易包含到 IL 之前独立执行交易有效性检查。

  • 区块生产者掌握着完整的以太坊状态。他们必须在其提议的区块中包含所有 IL 交易的有效交易并集。只有当 IL 交易无效或区块已满(即满足条件属性)时,才能将其排除。

    在 Verkle 的VOPS世界中,区块生产者负责生成和提交以下内容:

    • 区块级见证(例如,Verkle 树上的IPA 多重证明):这证明了区块执行所提供的数据存在于前一个区块状态根中。
    • 后状态根 (post-state root) :执行所有交易后,区块生产者计算并提交生成的postStateRoot到区块头中。这作为执行的输出,必须由证明者验证。这已经是区块生产者目前的做法,在 VOPS 世界中并非新要求。
  • 证明者通过执行以下步骤来验证区块:

    1. :white_check_mark:验证区块级见证:确认所有提供的状态(通过 IPA 多重证明证明)对preStateRoot有效。

    2. :white_check_mark:在本地重新执行区块:使用提供的交易,证明者从预状态(从见证重建)开始独立重新执行区块,以重新计算postStateRoot

    3. :white_check_mark:检查postStateRoot :确保本地重新计算的postStateRoot与块中提交的 postStateRoot 匹配。

    4. :white_check_mark:验证 IL 条件

    • 包含 IL 交易
      • 在从重建的预状态进行本地重新执行期间,可以观察到与块中存在的 IL 交易相对应的所有状态变化。
    • 排除的IL交易
      • 执行完块中包含的所有交易后,尝试执行每个排除的 IL 交易:
        • 如果交易无法通过随机数或余额检查,或者区块已满,则排除是合理的。
        • 否则,区块生产者正在进行审查,并且该区块不应该获得投票。

    如果所有检查(状态访问的有效性、执行的正确性以及 IL 条件的满足性)都通过,则证明者将投票支持该区块。通过在步骤 2中重新执行区块,并在步骤 3中同时更新其本地四元组表,VOPS 节点无需存储完整的 Verkle 树,即可保持其部分状态完美同步。

zkVM 的 VOPS

基于 Verkle 风格的 VOPS,我们可以用零知识虚拟机 ( zkVM)取代区块级状态证明和本地重新执行。每个区块都附带一个SNARK证明,允许每个验证者通过运行单次毫秒级验证来检查整个转换过程以及所有 IL 条件。

  • 区块生产者必须证明什么:
    • 状态有效性:交易读取的每个键/值都经过preStateRoot验证。
    • 执行正确性:在重建的预状态下执行有序交易会产生已提交的postStateRoot
    • 差异正确性:将正确的完整状态差异应用于preStateRoot会产生postStateRoot ,并且差异与标题中嵌入的差异匹配。

区块头字段

场地目的
preStateRoot执行前的状态根
postStateRoot执行后的状态根
stateDiff每个修改过的账户叶和存储槽的完整列表的 Merkle 根
execProof SNARK 将transactions + stateDiff绑定到转换preStateRoot → postStateRoot

两点说明:

  • 在 VOPS 和 AA-VOPS 下,节点依赖于接收每个区块的完整stateDiff来更新其本地状态。EIP -7928 区块级访问列表 (BAL)正是提供这种机制:以可验证的方式强制发布所有已修改的账户、存储密钥、余额和随机数。
  • VOPS 节点使用其更新的四元组表和收到的stateDiff在本地检查 IL 合规性——这里没有额外的证明义务(有关每个帐户证明与包含列表规则的关系,请参阅 AA-VOPS)。

VOPS 节点的逐块例程

  1. 验证execProof 确认:white_check_mark:状态有效性, :white_check_mark:执行正确性,以及:white_check_mark:差异正确性。
  2. 提取四元组。从已验证的完整stateDiff中,提取每个已修改账户的(address, nonce, balance, codeFlag)并更新本地表。
  3. 执行IL规则。使用刷新后的四元组表,在本地重新检查包含列表条件。IL的执行可以通过使用存储的VOPS四元组字段进行本地检查来实现。
  4. 修剪内存池。删除任何因新的四元组值而无效的待处理交易。

资源概况

方面节点区块生产者
磁盘四元组表约 8.4 GiB(比完整 MPT 状态小 25 倍)未改变
带宽stateDiff仅增加了几十 KB——与当前块限制相比微不足道几乎没有变化,但证人的上传带宽略有增加
中央处理器每个区块进行一次快速 SNARK 验证(笔记本电脑上以毫秒为单位)繁重的证明工作量——尽管改进很快,但仍然比构建 Merkle/Verkle 见证成本高得多(需要多个 GPU)
校样尺寸大小恒定,验证者总是下载相同的几百字节恒定大小,理想情况下每个证明为 128-256 KiB

底线。

凭借约 8.4 GiB 的本地状态、不变的内存池规则以及每个区块的单一简洁证明,zkVM VOPS 保留了以太坊的抗审查性,同时将验证器硬件要求保持在消费级限制范围内。

VOPS 同步

首先要强调一点。在目前的Verkle二叉树提案中,账户和存储槽位是交错排列的,没有明确区分。与当前的 Merkle Patricia 树不同,在 Merkle Patricia 树中,账户构成一个独立的子树,这意味着 VOPS 节点不能简单地下载快照来重建其本地账户表。相反,它必须重新执行创世区块中的所有区块。未来的统一树设计可以添加最小语义(例如,位域)来区分账户和存储,从而实现高效的快照同步。

目前,账户 trie 大约占总状态大小的1/6 。假设采用 Snap 同步方法,仅下载账户 trie 即可使同步速度比下载完整状态快五到六倍,这考虑到了修复阶段。修复阶段所需的时间仍与现在相同,并且许多下载的数据最终会被丢弃,但同步可以从任何完整节点进行,就像现在一样。

引导 VOPS 节点仅需要帐户状态(无存储尝试或代码块)以及通常的块头:

  1. 标头下载和验证

    • 获取并验证从创世点(或受信任的检查点)到最新头的块头。
  2. 逐块更新

    对于每个新块:

    • 取回:
      • Verkle-tree:标题+IPA 多重证明+完整交易列表
      • zkVM:头文件 + SNARK execProof + 紧凑的stateDiff sidecar
    • 验证并提取:
      • Verkle 树:检查多重证明,提取所有受影响的账户条目,然后根据重建的初始状态,按顺序重新执行每笔交易。记录每个更改(address, nonce, balance, codeFlag)
      • zkVM:验证 SNARK(状态读取、执行和 IL 规则)并解析更新的四元组的stateDiff sidecar 列表。
    • 应用:使用每个修改的帐户条目更新本地表。
  3. 内存池修剪

    • 每次更新表时,删除任何待处理的交易,其发送者现在具有陈旧的随机数、余额不足或codeFlag从 0→1 翻转(仅保留该地址的最高优先级待处理交易)。
    • 处理完所有块之后,表格就完全是当前的了,并且内存池会像实时操作一样强制执行有效的准入/修剪。

VOPS 和原生账户抽象 (AA-VOPS)

原生账户抽象(AA,参见EIP-7701带来了重大的范式转变:账户不再是具有固定验证规则的简单对象,而是可以运行任意代码的可编程实体。这种灵活性打破了仅检查noncebalancecodeFlag就足以验证交易的假设。因此,VOPS 需要升级: AA-VOPS

AA-VOPS 扩展了 VOPS,使其能够支持原生 AA,同时通过避免完全的全局状态复制来保持节点的轻量级。每个节点不再需要跟踪每个账户,而是只跟踪其主动关注的账户(针对其自身的 EOA 或与其交互的账户),并维护一个小型本地缓存,该缓存会随着时间的推移逐步更新。

原生 AA 释放了强大的新功能,同时也迫使生态系统更接近强无状态设计,即交易必须携带明确的见证信息。当我们扩展 VOPS 以支持 AA-VOPS 时,我们应该仔细权衡完全原生 AA 的优势是否值得增加这种复杂性,或者坚持更简单的 VOPS 模型是否更能保持去中心化和效率。

AA-VOPS 与强无状态

强无状态性要求用户在每笔交易中附加完整的状态见证,涵盖所有涉及的账户和存储槽。

相比之下, AA-VOPS允许节点仅为与其自身 EOA 绑定的特定账户维护最新的证明。这些证明在多个区块中保持有效,除非账户发生变更,并使用每个区块中包含的轻量级stateDiffs进行刷新。

这样就避免了每笔交易都需要大量的见证,将带宽和存储要求保持在最低限度,同时保持抗审查性和交易有效性。

AA-VOPS 的工作原理

本地缓存和见证维护

节点(或代表其行事的钱包或 dApp 后端)保留:

  • 它自己的帐户叶: noncebalancestorageRootcodeHash
  • 帐户的 AA 逻辑所需的任何额外存储槽。
  • Merkle 或 Verkle 路径见证根据最近的stateRoot验证这些字段。

见证一直有效,直到后续区块修改任何所涵盖的字段。

引导见证人:

增量更新:

每个区块都提供

  1. 紧凑、完整的stateDiff (如 VOPS-with-zkVM 中一样)。
  2. IL 承诺(例如,16 个 IL 签名的集合)。此承诺允许证明者验证区块生产者是否已承诺遵守其在本地观察到的所有 IL。
  3. transactionsstateDiff和 IL 合规规则绑定在一起的区块级证明(例如 SNARK)。

当一个区块到达时,节点:

  1. 验证区块级证明,以确保:

    • stateDiff正确编码了从preStateRootpostStateRoot的转换。
    • 所有包含的交易均已正确执行。
    • IL 集中的每个交易要么被包含,要么被有效排除(因为块已满,或者交易在之前执行后变得无效)。

    (可选优化:区块生产者可以为每个被排除的 IL 交易附加一个“失败提示”,指出执行中失败的索引,从而减少证明者的工作量。)

  2. 如果需要,修补其见证:

    VOPS 节点(或跟踪其自身 EOA 的钱包/dapp)检查其任何账户是否出现在stateDiff中。

    • 如果存在,它会更新缓存的叶子并重新计算受影响的 Merkle/Verkle 路径。
    • 如果不存在,缓存的见证对于新的stateRoot仍然有效。

如果节点保持在线并处理每个区块,则它在引导后无需再次进行存档查询。如果节点落后,它可以通过eth_getProof重放缺失的差异或重新播种其见证数据。

交易打包

提交交易时:

  • 对于 EOA 和EIP-7702账户,无需额外的见证。账户字段的最小值可在本地获取。
  • 对于EIP-7701智能账户,附有简洁的单账户有效性证明(例如 SNARK),显示账户叶的包含和正确的VALIDATE逻辑执行。

未来 AA 兼容性

如果未来的 AA 标准允许VALIDATE读取账户外部数据,则发送者可以扩展附加见证,以覆盖这些额外的存储槽。该模型可以自然扩展。

内存池入场

接收节点使用其缓存的stateDiffs检查更新,根据引用的stateRoot验证见证或证明:

  • 如果差异显示账户没有变化,则交易被接受。
  • 如果差异表明帐户更改,则见证已过时,并且节点会请求更新的证明。

实际上,节点还可以维护最近 stateDiffs 的滑动窗口(例如,最后 ~N 个块),以允许在不重新查询的情况下进行交易验证。

AA-VOPS 为何引人注目

  • 无全局复制:

    每个节点仅存储几千字节,而不是 8 GiB(VOPS)或约 233 GiB(完整状态)。区块生产者和 RPC 提供者仍然需要完整状态。

  • AA兼容性:

    目前可与EIP-7701配合使用,并且如果验证逻辑稍后读取外部存储,则可以进行调整。

  • 按需引导:

    为了跟踪新的 EOA,节点会获取一次单个eth_getProof并通过差异保持其最新状态。

权衡

  1. 更高的P2P带宽:

    每笔交易都带有自己的见证或简洁证明,平均交易大小不断增加(Verkle 约为 736 字节,二叉树约为 1024 字节)。

  2. 用户端证明或获取:

    节点必须通过跟踪差异或查询完整节点来保持证明的最新状态,这很容易受到集中化向量的影响。

  3. 引导依赖完整节点:

    与 VOPS 不同,AA-VOPS 依赖于全节点或 RPC 提供商来初始获取账户证明。如果缺少差异,节点必须通过eth_getProof重新播种,如果只有少数提供商占据主导地位,则可能会带来中心化风险。

结论和要点

仅验证部分无状态( VOPS ) 将节点的本地存储需求减少到未压缩的大约 8.4 GiB——仅包含(address, nonce, balance, codeFlag)四元组——同时保留了以太坊的抗审查特性。这种方法利用了账户和存储槽增长之间的不对称性:只要存储继续主导新的状态条目,节省的成本仍然可观;如果账户创建速度超过存储增长速度,相对优势就会相应减弱。

VOPS 的原始版本有两个主要优势:首先是无见证内存池:由于每个节点都存储每个 EOA (address, nonce, balance, codeFlag)的四元组,因此内存池无需每笔交易都提供 Merkle 或 Verkle 证明。区块级证明(例如 zkEVM 中的 SNARK)仍然能够保证完全正确性,同时只会在每个区块的传播过程中增加几百字节的数据。其次是真正的点对点同步:由于每个节点都维护所有 EOA 的最小状态,因此您可以从任何对等节点引导或恢复,而无需额外的证明或单独的帐户数据,从而消除了对完整节点或存档节点的依赖。

但支持原生无状态证明 (AA) 会迫使我们做出一系列不同的权衡。AA-VOPS 通过在每笔交易中附加一个小的、最新的证明,将本地存储空间缩减至几千字节,但其缺点是 P2P 负载更高,并且每当您开始追踪新账户、启动节点或离线后重新上线时,都需要偶尔调用全节点或专用服务。随着证明技术和 SNARK 的不断改进,AA-VOPS 或将成为迈向完全无状态的长期且面向未来的途径。相比之下,原始的 VOPS 则脱颖而出,成为一种务实的短期解决方案,通过避免交易级见证来保持无缝的用户体验。


来源
免责声明:以上内容仅为作者观点,不代表Followin的任何立场,不构成与Followin相关的任何投资建议。
喜欢
2
收藏
2
评论