资讯动态

EIP-7736 详解:基于 Verkle 树的叶子级状态过期(Leaf-Level State Expiry)方案

发布时间:2026/9/15 22:28:09 来源:尧图企业网站定制
EIP-7736 详解基于 Verkle 树的叶子级状态过期Leaf-Level State Expiry方案【免费下载链接】EIPsThe Ethereum Improvement Proposal repository项目地址: https://gitcode.com/GitHub_Trending/ei/EIPs导读EIP-7736Leaf-level state expiry in verkle trees提出了一种简单、非穷尽式的以太坊状态过期方案不再像既往提案那样引入地址空间扩展ASE、每 epoch 独立多棵树等重型结构改造而是只对 Verkle 树中扩展与后缀树extension-and-suffix tree这一叶子级单位进行过期与复活。本文以 EIPS/eip-7736.md 为骨架结合 EIPS/eip-6800.mdVerkle 树统一状态树与 EIPS/eip-4762.md无状态化 gas 成本改革展开讲解。读完本文你将掌握该方案如何通过last_epoch字段标记冷数据、如何用check_epoch_end触发整棵子树的过期、如何用复活交易resurrection transaction按 EIP-4762 的 witness 成本定价并恢复被删除的数据以及它相比既有状态过期提案的取舍与向后兼容性考量。注意本 EIP 当前状态为Stagnant停滞FORK_TIME与RESURRECT_TX_TYPE均为 TBD测试用例与参考实现部分标注 TODO。本文以仓库中该文档的既有内容为准进行解读。背景为什么需要叶子级状态过期状态无限增长的痛点以太坊全节点需要永久保存所有历史状态账户、存储、合约代码。随着使用量增长状态膨胀成为长期问题学界与社区提出了多种状态过期state expiry设想。EIP-7736 在 Motivation 中明确指出既往实现状态过期的尝试因复杂度迅速攀升而停滞——它们要求对以太坊结构做出重大改动地址空间扩展 address space extension、oil、多棵树 multiple trees 等。EIP-7736 的作者Guillaume Ballet、Wei Han Ng主张一种更简单但非穷尽式的路径只删除叶子节点让树的其余部分保持完整。这样做可以去掉那些对用户与开发者体验有害的方法。其核心收益是简单不要求 ASE、不要求每 epoch 建多棵树、复活证明更小、gas 成本清晰、只过期冷数据而让热数据集保持活跃同时保持前向兼容未来仍可叠加 ASE 或多棵树方案。与 EIP-6800 Verkle 树的承接关系该方案建立在 EIP-6800 定义的 Verkle 状态树之上。EIP-6800 用统一的 Verkle 树替代 MPT将账户头部、合约代码、存储槽全部嵌入一棵key: value树中核心单元正是extension-and-suffix treedef extension_and_suffix_tree(stem: bytes31, values: Dict[byte, bytes32]) - int: sub_leaves [0] * 512 for suffix, value in values.items(): sub_leaves[2 * suffix] int.from_bytes(value[:16], little) 2**128 sub_leaves[2 * suffix 1] int.from_bytes(value[16:], little) C1 compute_commitment_root(sub_leaves[:256]) C2 compute_commitment_root(sub_leaves[256:]) return compute_commitment_root([1, # Extension marker int.from_bytes(stem, little), group_to_scalar_field(C1), group_to_scalar_field(C2)] [0] * 252)在 EIP-6800 中256 个共享同一stem前缀的叶子构成一棵后缀树suffix tree后缀树的承诺C1、C2连同stem一起被打包进扩展节点extension node的承诺中。EIP-7736 正是以扩展节点 其后缀节点为最小过期单元——这是理解全文的关键前提。EIP-6800 文档中附带的结构示意图 assets/eip-6800/tree_structure.png 直观展示了从 Root → Stem Tree → Extension Tree → Suffix Tree → Data 的嵌套关系规范总览与常量定义EIP-7736 的规范可归纳为三大部分树结构变更给扩展节点新增last_epoch字段并引入全局current_epoch计数器过期机制在每个区块处理开始时检查是否到 epoch 边界到期则调度删除扩展与后缀树复活机制新增一种交易类型携带简单的 Verkle 证明付费后重建被删除的扩展与后缀树并更新 epoch 计数。规范遵循 RFC 2119 / RFC 8174 的 MUST / MUST NOT / REQUIRED / SHALL / SHALL NOT / SHOULD / SHOULD NOT / RECOMMENDED / NOT RECOMMENDED / MAY / OPTIONAL 语义。常量表名称描述值FORK_TIME分叉激活时间TBDEPOCH_LENGTH一个 epoch 的持续时间秒157788006 个月INITIAL_EPOCH_COUNTER在FORK_TIME时间戳处结束的那个 epoch0NUM_ACTIVE_EPOCHS同时未过期的 epoch 数量2RESURRECT_TX_TYPE复活交易的类型 IDTBD其中EPOCH_LENGTH 15778800秒即 6 个月365.25 天 / 2NUM_ACTIVE_EPOCHS 2意味着当前 epoch 与上一个 epoch 的数据保持活跃只有更早的 epoch 数据会被过期。树结构变更给扩展节点加上last_epoch全局计数器current_epoch在分叉前初始化一个整型变量current_epoch INITIAL_EPOCH_COUNTER即 0分叉后它保存当前 epoch 编号。分叉激活时刻FORK_TIME恰好是 epoch 0 的结束点之后每经过EPOCH_LENGTH秒递增一次。扩展节点新增last_epoch字段扩展节点的承诺计算被修改在原有[1, stem, C1, C2]之后追加一个last_epoch求值点原先该位置填0共 252 个零现在改为 251 个零def extension_and_suffix_tree(stem: bytes31, values: Dict[byte, bytes32], last_epoch: int) - int: sub_leaves [0] * 512 for suffix, value in values.items(): sub_leaves[2 * suffix] int.from_bytes(value[:16], little) 2**128 sub_leaves[2 * suffix 1] int.from_bytes(value[16:], little) C1 compute_commitment_root(sub_leaves[:256]) C2 compute_commitment_root(sub_leaves[256:]) return compute_commitment_root([1, # Extension marker int.from_bytes(stem, little), group_to_scalar_field(C1), group_to_scalar_field(C2), last_epoch] # Added in this EIP [0] * 251)对比 EIP-6800 中的原始版本末尾为[0] * 252EIP-7736 将第 4 个下标从 0 起求值点从0改为承载last_epoch。这正是向后兼容性的来源——详见向后兼容性一节。读写操作的规则对树的每次读或写事件都必须先检查current_epoch last_epoch NUM_ACTIVE_EPOCHS若成立则正常执行读/写否则revert回滚。同时每当该扩展节点处理一次写事件就用current_epoch刷新last_epoch。换句话说last_epoch记录的是该节点最后一次被写入所在的那个 epoch。为什么只有写才刷新last_epochRationale 一节专门解释了这一设计取舍任何对last_epoch的更新在效果上都等价于一次写操作。如果读也刷新last_epoch则要么把读的成本抬高到写成本进一步推高 EIP-4762 已经引入的 gas 成本或者实际上以读的价格执行写这既会削弱状态过期的效果还可能引入DoS 攻击向量攻击者可以廉价地持续刷新大量冷数据的 epoch 计数阻止它们过期。因此只有写事件刷新计数器读保持廉价。过期机制check_epoch_end与 keepsake区块级检查在区块处理开始时、执行交易之前运行check_epoch_enddef check_epoch_end(block): if block.timestamp FORK_TIME current_epoch * EPOCH_LENGTH: current_epoch current_epoch 1 schedule_expiry(current_epoch-NUM_ACTIVE_EPOCHS)当区块时间戳越过当前 epoch 的终点FORK_TIME current_epoch * EPOCH_LENGTH时current_epoch递增调用schedule_expiry(current_epoch - NUM_ACTIVE_EPOCHS)调度两个 epoch 之前的那个 epoch 的数据过期。schedule_expiry的具体行为留给客户端实现者决定例如懒删除、后台批量删除、与数据库压缩结合等。规范对此不做强制要求。需要保留的数据keepsake为了让过期后的子树能够被可靠地重建尤其要为兄弟节点提供插入锚点每个被删除的扩展与后缀节点必须保留两份数据合称为该节点的keepsakestem值用于定位树中的位置以便插入兄弟节点节点承诺C用于校验。重要注记实际的删除动作不应早于该 epoch 第一个区块被 finalize最终确认除非客户端有办法在重组reorg时恢复区块。换言之删除必须等到最终确定性保障之后才能物理执行否则重组可能让已删除的数据无法恢复。过期粒度删除大部分而非全部EIP-7736 删除的是值与子承诺subcommitments也就是扩展与后缀树内部的叶子数据与两层后缀承诺C1、C2同时保留stem与节点承诺C以便轻松插入兄弟节点。Rationale 强调虽然没有删除_所有_数据但它删除了_大部分_数据——即 values 与 subcommitments同时保留了轻松插入兄弟节点的能力。这也解释了为什么该方案比复活单个叶子更贵——那是为换取简化所付出的代价。复活机制复活交易Resurrection Transaction交易格式复活交易定义为RESURRECT_TX_TYPE|ssz(Vector[stem,last_epoch,values])即交易 payload 是 SSZ 序列化的三元组列表其中stem用于在树中定位以便重建节点last_epoch与values正是当初被删除的条目keepsake 之外被删掉的部分。Gas 定价基于 EIP-4762 的 witness 成本在验证开始前先按 EIP-4762 定义的常量收费def resurrect_gas_cost(values) - int: return WITNESS_BRANCH_COST SUBTREE_EDIT_COST sum(WITNESS_CHUNK_COST CHUNK_EDIT_COST CHUNK_FILL_COST for i in values)EIP-4762 中定义的相关常量及其值见 EIPS/eip-4762.md常量值WITNESS_BRANCH_COST1900WITNESS_CHUNK_COST200SUBTREE_EDIT_COST3000CHUNK_EDIT_COST500CHUNK_FILL_COST6200定价逻辑的含义复活一棵子树 读取一条 witness 分支WITNESS_BRANCH_COST 编辑一棵子树SUBTREE_EDIT_COST 对每个被恢复的叶子分别收取 chunk 见证WITNESS_CHUNK_COST、chunk 编辑CHUNK_EDIT_COST与填充成本CHUNK_FILL_COST。这与 EIP-4762 的 witness/访问事件定价模型保持一致所有重新填充的数据都要按其真实见证与编辑成本付费杜绝廉价复活导致的滥用。验证流程付费完成后进入验证。顶层函数遍历交易中的所有子树def validate_subtrees(tree, tx, current_epoch) - bool: # The tx is a SSZ payload subtrees deserialize_ssz(tx[1:]) if subtrees None: return false # Process all subtrees in the transaction for subtree in subtrees: ok validate_subtree(tree, subtree.stem, subtree.values, subtree.last_epoch, current_epoch) if not ok: return false return true其中tx[1:]去掉类型字节后反序列化出 SSZ payloadtx首个字节即RESURRECT_TX_TYPE。对每棵子树validate_subtree完成两步核心校验def validate_subtree(tree, stem, values, last_epoch, current_epoch) - bool: # Compute the commitment to the expired # tree, get the expired_C extension_and_suffix_tree(stem, values, last_epoch) expired tree.get_keepsake(stem) if keepsake.C ! expired_C: return false # Replace the keepsake with the resurrected # extension-and-suffix tree. new_C extension_and_suffix_tree(stem, values, current_epoch) return tree.resurrect_subtree(stem, new_C, values, current_epoch) None验证逻辑要点用交易提供的(stem, values, last_epoch)重新计算过期子树的承诺expired_C从树中取出该stem的 keepsake比对keepsake.C ! expired_C——承诺不匹配则直接拒绝这保证了复活者必须精确提供当初被删除的数据无法伪造或篡改通过校验后用current_epoch作为新的last_epoch重新计算承诺new_C调用tree.resurrect_subtree(stem, new_C, values, current_epoch)将子树写回树中resurrect_subtree成功时返回None失败则返回错误此时验证返回false。注意第 3 步中new_C使用了current_epoch而非旧的last_epoch——复活本身是一次写事件因此新节点立即带着当前 epoch 标记重新进入活跃生命周期再经过NUM_ACTIVE_EPOCHS个 epoch 后才会再次过期。复活证明更小的原因Rationale 指出相比其他状态过期提案本方案的复活证明更小只需要提供数据本身即可复活valueslast_epoch无需提供大量 Merkle/Verkle 路径证明——因为 keepsake 中保留的节点承诺C直接充当了完整性锚点验证者只需重算承诺并与 keepsake 比对。Rationale设计取舍一览EIP-7736 相对既往状态过期提案的核心优势原文 Rationale 总结无需 Address Space ExtensionASE不改变地址语义只使用一棵树不需要每 epoch 建多棵独立树复活证明更小仅提供数据即可复活gas 成本清晰定价直接复用 EIP-4762 的 witness 成本常量只过期冷数据热数据集保持活跃对用户体验冲击小前向兼容未来仍可叠加 ASE 或多棵树方案epoch 幂运算/加法成本摊薄current_epoch相关的指数/加法计算每个 epoch 只需支付一次很快被摊薄。同时它也坦诚代价比复活单个叶子更贵换取简化并非删除全部数据而是删除大部分values 与子承诺。向后兼容性EIP-7736 对 Verkle 树是向后兼容的。关键依据见 EIPS/eip-7736.md默认情况下EIP-6800 中第 4 个下标从 0 起求值点的值被设为0而这正是INITIAL_EPOCH_COUNTER的值。也就是说在分叉前所有既存扩展节点第 4 个求值点恰好等于INITIAL_EPOCH_COUNTER 0因此分叉后这些节点的承诺无需任何改写即与新的extension_and_suffix_tree(stem, values, last_epoch0)定义一致。既存 Verkle 树可以直接无缝接入本方案。测试用例与参考实现现状EIP-7736 文档中Test Cases标注为TODO尚无正式测试向量Reference Implementation标注为TODO尚无参考实现。这两部分有待社区后续补充。作为背景参照其依赖的 EIP-6800 在文档中列出了参考实现分支geth 的beverly-hills-just-after-pbss分支、Nethermind 的verkle/tree分支但 EIP-7736 本身尚未有对应实现落地。安全考量与开放性讨论文档的 Security Considerations 一节目前仅有一句Needs discussion.结合全文可以归纳出需要继续讨论的安全与工程问题删除时机的最终确定性规范明确实际删除不得早于该 epoch 首个区块 finalize否则重组会丢失数据schedule_expiry的具体行为留给客户端实现这本身就是需要统一的安全边界复活交易的 DoS 面复活按 witness 成本全额收费理论上限制了廉价刷复活但复活交易的大批量Vector[...]语义、以及复活后的数据再次过期与再复活的循环仍需评估其 gas 上限与区块级 DoS 风险keepsake 的存储开销每个过期子树保留stem 承诺C累积的 keepsake 集本身会增长如何压缩、何时清理是工程问题读/写检查对共识实现的要求current_epoch last_epoch NUM_ACTIVE_EPOCHS的检查必须在所有客户端严格一致否则会出现分叉。小结EIP-7736 提供了一条以扩展与后缀树为过期单元的低复杂度状态过期路径给扩展节点新增last_epoch字段用区块级check_epoch_end在 epoch 边界调度过期通过 keepsakestem 承诺C保留重建锚点并以一种携带 SSZ payload 的新交易类型、按 EIP-4762 witness 成本定价完成复活。它不追求删光所有数据而是删除大部分、保留锚点以此换取无需 ASE、单棵树、小证明、清晰定价的简洁性并与 EIP-6800 的 Verkle 树承诺结构天然向后兼容。对于研究状态过期、Verkle 树共识改造与无状态客户端路线图的开发者这是一份值得反复研读的核心设计文档其测试用例、参考实现与安全讨论仍是开放的 TODO也是社区后续可以贡献的方向。本文基于当前仓库中的 EIPS/eip-7736.md、EIPS/eip-6800.md、EIPS/eip-4762.md 编写相关结构示意图见 assets/eip-6800/tree_structure.png。【免费下载链接】EIPsThe Ethereum Improvement Proposal repository项目地址: https://gitcode.com/GitHub_Trending/ei/EIPs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价