ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

深入解析 EIP-2935:将历史区块哈希写入状态存储的系统合约方案(EIPs 仓库)

深入解析 EIP-2935:将历史区块哈希写入状态存储的系统合约方案(EIPs 仓库) 深入解析 EIP-2935将历史区块哈希写入状态存储的系统合约方案EIPs 仓库【免费下载链接】EIPsThe Ethereum Improvement Proposal repository项目地址: https://gitcode.com/GitHub_Trending/ei/EIPs本文基于 EIP-2935 官方规范 编写。EIP-2935Serve historical block hashes from state是进入 Prague/ElectraPectra网络升级的执行层核心提案之一其核心思路是把最近8191个区块的哈希写入一个系统合约的环形缓冲区存储让历史区块哈希成为状态的一部分。读完本文你将掌握该提案的动机、环形缓冲区与系统调用设计、get/set操作语义、部署地址的推导过程、Gas 影响以及它与 EIP-4788信标根入 EVM、EIP-7709BLOCKHASH 从存储读取并调整成本等后续提案之间的演进关系。一、提案定位与核心动机EIP-2935 的全称是Serve historical block hashes from state状态为 Final类型为 Standards Track / Core创建于 2020-09-03作者包括 Vitalik Buterin、Guillaume Ballet、Gajinder Singhg11tech等以太坊核心开发者。其摘要给出了一句最精炼的定义在区块处理逻辑中将最近HISTORY_SERVE_WINDOW个历史区块哈希存入一个系统合约的存储中并且该 EIP不改变BLOCKHASH指令的解析机制因此也不改变其查询范围与成本。1.1 为什么需要把区块哈希放进状态传统上EVM 的BLOCKHASH指令隐式假设执行客户端手头持有最近的区块哈希。这个假设在无状态客户端stateless client的前景下并不未来可期——无状态客户端本身不保存历史区块数据。将区块哈希纳入状态之后这些哈希就可以被捆绑进提供给无状态客户端的witness见证数据中。这一点在 Merkle Patricia TrieMPT下已经可行而在 Verkle 树时代将变得更加高效。1.2 为什么不直接扩展 BLOCKHASH 的服务范围直接扩展BLOCKHASH可以服务的区块范围BLOCKHASH_SERVE_WINDOW属于语义变更会破坏大量现有合约的假设。而通过本提案的合约存储来软过渡地扩展历史访问窗口Rollup 等二层方案可以直接查询该合约从而获得更长的历史窗口。此外该方案还有一个附带好处可以直接针对当前状态构建/验证与最近HISTORY_SERVE_WINDOW个祖先相关的证明。二、规范速览四个关键参数| 参数 | 值 | | - | - | |BLOCKHASH_SERVE_WINDOW|256| |HISTORY_SERVE_WINDOW|8191| |SYSTEM_ADDRESS|0xfffffffffffffffffffffffffffffffffffffffe| |HISTORY_STORAGE_ADDRESS|0x0000F90827F1C53a10cb7A02335B175320002935|核心数据结构是一个长度为HISTORY_SERVE_WINDOW8191的环形缓冲区ring buffer用于存储最近 8191 个区块哈希。注意HISTORY_SERVE_WINDOWBLOCKHASH_SERVE_WINDOW而BLOCKHASH_SERVE_WINDOW256保持不变——这正是不改 BLOCKHASH 语义、只新增更长的存储式历史访问通道的设计意图。三、区块处理系统调用与写入流程3.1 每区块的系统调用在处理任何区块的开始阶段即处理任何交易之前以SYSTEM_ADDRESS身份调用HISTORY_STORAGE_ADDRESScalldatablock.parent.hash32 字节gas limit30_000_000value0这会触发历史合约的set()例程。这是与 EIP-4788 相同的系统操作约定因此必须满足以下条件该调用必须执行到完成该调用不计入区块的 gas 限制该调用不遵循 EIP-1559 的销毁语义——调用中不应转移任何 value如果HISTORY_STORAGE_ADDRESS处没有代码调用必须静默失败。规范同时给出了一个替代方案客户端也可以选择直接写入合约的存储但通过 EVM 调用该合约仍是首选方式详见 Rationale 部分。3.2 环形缓冲区的填充期需要注意EIP 激活后需要经过HISTORY_SERVE_WINDOW8191个区块才能完全填满环形缓冲区。合约在激活之初只包含分叉区块的父哈希不包含更早的任何哈希。3.3 EVM 变更BLOCKHASH操作码的语义与之前完全相同。本 EIP 对BLOCKHASH机制及其成本零影响。四、历史区块哈希合约get / set 双操作历史合约只有两个操作get和set。合约本身不通过 calldata 的函数选择器来区分操作而是通过调用者身份判断当caller等于SYSTEM_ADDRESSEIP-4788 引入的约定时执行set否则执行get。4.1get从 EVM 查询区块哈希get用于 EVM 内的区块哈希查询调用者以大端编码的区块号作为 calldata 传入如果 calldata 不是 32 字节revert对于任何超出[block.number - HISTORY_SERVE_WINDOW, block.number - 1]范围的请求revert。4.2set写入父哈希调用者将block.parent.hash作为 calldata 传入将存储槽block.number - 1 % HISTORY_SERVE_WINDOW的值设置为calldata[0:32]。4.3 官方 EVM 汇编字节码以下是规范给出的、可直接用于历史合约的精确 EVM 汇编源自sys-asm项目caller push20 0xfffffffffffffffffffffffffffffffffffffffe eq push1 0x46 jumpi push1 0x20 calldatasize sub push1 0x42 jumpi push0 calldataload push1 0x01 number sub dup2 gt push1 0x42 jumpi push2 0x1fff dup2 number sub gt push1 0x42 jumpi push2 0x1fff swap1 mod sload push0 mstore push1 0x20 push0 return jumpdest push0 push0 revert jumpdest push0 calldataload push2 0x1fff push1 0x01 number sub mod sstore stop可以对照阅读这段汇编与上面get/set的语义描述push2 0x1fff即 8191出现在取模运算处是环形缓冲区大小在字节码中的直接体现get路径中的两次边界检查分别对应请求不小于block.number - 1与请求不早于block.number - 8191。4.4 部署从交易反推合成地址合约通过一个特殊的合成地址synthetic address部署该地址由期望的部署交易反向推导而来。规范给出的部署交易如下{ type: 0x0, nonce: 0x0, to: null, gas: 0x3d090, gasPrice: 0xe8d4a51000, maxPriorityFeePerGas: null, maxFeePerGas: null, value: 0x0, input: 0x60538060095f395ff33373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500, v: 0x1b, r: 0x539, s: 0xaa12693182426612186309f02cfe8a80a0000, hash: 0x67139a552b0d3fffc30c0fa7d0c20d42144138c8fe07fc5691f09c1cce632e15 }关键点在于交易input中有一段简单的构造器constructor前缀用于在部署时拼接出所需的运行时字节码。该交易的发送者可以计算为0x3462413Af4609098e1E27A490f554f260213D685该账户部署的第一个合约地址为rlp([sender, 0])计算结果正是0x0000F90827F1C53a10cb7A02335B175320002935这就是HISTORY_STORAGE_ADDRESS的由来。需要强调虽然这种合约创建方式不像create2那样绑定特定 initcode但该合成地址在密码学上绑定于交易的 input 数据即 initcode。4.5 几种激活场景规范给出了三种典型激活情形帮助理解环形缓冲区的初始状态在创世genesis激活创世状态不写入任何历史在区块1开始时创世哈希作为一次正常操作写入槽0在区块1激活只有创世哈希被写入槽0在区块32激活区块31的哈希被写入槽31其余所有槽为0。五、EIP-161 处理与 Gas 成本5.1 EIP-161 豁免上述字节码将按照 EIP-4788 的方式部署。因此HISTORY_STORAGE_ADDRESS处的账户会带有代码且 nonce 为 1并豁免于 EIP-161 的空账户清理。5.2 Gas不预热、按需支付区块开始时的系统更新即process_block_hash_history或通过以SYSTEM_ADDRESS为调用者的系统调用不会按照 EIP-2929 规则预热HISTORY_STORAGE_ADDRESS账户或其存储槽。因此第一次调用该合约时需要为预热账户及其访问的存储槽付费任何对该合约的普通合约调用都遵循正常的 EVM 执行语义由于BLOCKHASH语义不变本 EIP 对BLOCKHASH机制及其成本没有影响。六、设计 Rationale为什么这样做6.1 简化去除三处不必要的复杂性此前有过非常相似的提案本 EIP 是对它们的简化砍掉了三个复杂性来源用多层树状结构而非单层列表用 EVM 代码书写 EIP为深度历史访问做无界串行存储哈希。权衡利弊后最终决定只使用有限的环形缓冲区来服务所需的HISTORY_SERVE_WINDOW——因为 EIP-4788 与信标状态累加器已经允许虽然稍复杂针对合并merge以来的任意祖先构建证明。6.2 过渡策略选择等窗口填满分叉后BLOCKHASH解析逻辑的过渡有两种方案等待HISTORY_SERVE_WINDOW个区块让整个相关历史写入完毕在分叉块一次性存储最近全部HISTORY_SERVE_WINDOW个区块哈希。最终选择了前者理由是其大幅简化逻辑合约启动大约需要一天时间考虑到这是一个全新的历史访问方式且没有既有合约依赖它这一取舍被认为是有利的。6.3 插入父哈希的两种客户端实现客户端插入父区块哈希到状态一般有两种选择系统调用HISTORY_STORAGE_ADDRESS让合约自行处理存储绕过 EVM 处理直接写入状态 trie。直接写入的实现伪代码如下def process_block_hash_history(block: Block, state: State): if block.timestamp FORK_TIMESTAMP: // FORK_TIMESTAMP should be defined outside of the EIP state.insert_slot(HISTORY_STORAGE_ADDRESS, (block.number-1) % HISTORY_SERVE_WINDOW , block.parent.hash)规范建议在 Verkle 分叉之前采用方案 1以与 EIP-4788 保持一致并规避该 EIP 已激活但历史合约尚未部署的错误配置网络问题。如果 Verkle 分叉时过滤系统合约代码块被认为过于复杂该建议可能会被重新评估。6.4 为什么缓冲区大小是 8191其他系统合约中环形缓冲区大小选用素数是为了确保在缓冲区被完全填满之前不会有值被覆盖之后每个值每轮迭代更新一次即使部分槽位缺失或出块时间变化也是如此。而本 EIP 中参与取模运算是区块号它每轮只递增 1因此可以确信环形缓冲区始终饱和。为了与其他系统合约保持一致仍然保留了 8191 的大小。以当前主网参数计算8191 个根大约覆盖一天的区块历史这给用户留出了充足的时间来发起一笔针对特定哈希的验证交易并让其上链。七、向后兼容性与安全考量7.1 向后兼容本 EIP 对区块验证规则集引入了向后不兼容的变更但这两处变更都不影响当前用户活动与体验——因为BLOCKHASH指令的对外语义原封不动。7.2 安全考量分支投毒攻击拥有热更新路径分支的合约系统合约或其他存在分支投毒branch poisoning攻击风险攻击者可能在热路径分支附近撒上微不足道的 ETH试图拖慢状态根更新。但评估结论是要让状态根更新出现有意义的减速攻击成本会急剧上升因此该风险被认为可接受。7.3 测试用例官方测试用例位于 execution-specs 仓库的tests/prague/eip2935_historical_block_hashes_from_state目录对应 commit27174ca81b09dee4d41db23b40305cd1bb70f5bf覆盖了从状态提供历史区块哈希的完整场景。八、在 Pectra 升级中的落地与生态关联8.1 随 Pectra 主网上线EIP-7600Hardfork Meta - Pectra 将 EIP-2935 列为 Prague/Electra 升级的核心执行层 EIP 之一并给出了各网络的激活时间网络激活 Epoch激活时间戳Holešky1159681740434112Sepolia2224641741159776Hoodi20481742999832Mainnet36403217466123118.2 与 EIP-4788 的姊妹关系EIP-2935 在系统调用约定SYSTEM_ADDRESS、30M gas、0 value、静默失败、部署方式合成地址 构造器前缀、环形缓冲区设计素数 8191等方面都与 EIP-4788Beacon block root in the EVM 高度同构。区别在于4788 以timestamp % HISTORY_BUFFER_LENGTH为索引、存储(timestamp, beacon_root)双缓冲以抵御跳槽攻击而 2935 以(block.number - 1) % HISTORY_SERVE_WINDOW为索引、只需单缓冲因为区块号单调递增、缓冲区必然饱和。8.3 为 EIP-7709 铺路BLOCKHASH 存储化EIP-7709Read BLOCKHASH from Storage and Update Cost 直接建立在 EIP-2935 之上它要求BLOCKHASH (0x40)操作码改为从该系统合约的存储中读取并对窗口内的查询施加冷/暖SLOAD存储成本。其伪代码清晰展示了 2935 环形缓冲区的槽位计算被复用的方式def resolve_blockhash(block: Block, state: State, arg: uint64): if arg block.number or (arg BLOCKHASH_SERVE_WINDOW) block.number: return 0 # performs an sload on arg % HISTORY_SERVE_WINDOW including gas charges, # warming effects as well as state-access recording return state.load_slot(HISTORY_STORAGE_ADDRESS, arg % HISTORY_SERVE_WINDOW)EIP-7709 明确指出BLOCKHASH指令本身仍只服务有限的 256 窗口以保持向后兼容更深层的历史访问需要直接调用 EIP-2935 系统合约这会走一次正常的合约执行并产生相应收费与状态访问记录。这也印证了 EIP-2935 的设计定位它是一套更长历史的存储式查询通道而非对BLOCKHASH的替换。8.4 在后续提案中的引用EIP-3670EOF 代码格式 指出BLOCKHASH指令已被 EIP-2935 引入的系统合约很好地取代EIP-7886区块级快照相关提案 要求客户端在任何交易执行前创建区块级快照且该步骤发生在处理 EIP-4788 与 EIP-2935 系统合约之后EIP-7928区块级访问列表相关提案 的ITEM_COST设计特意为系统合约执行如 EIP-2935、EIP-4788、EIP-7002、EIP-7251与提款收款人EIP-4895产生的、不消耗区块 gas 的 BAL 条目预留缓冲并将 EIP-2935 历史哈希合约地址0x0000F90827F1C53a10cb7A02335B175320002935明确列为在区块哈希合约存储父哈希的系统合约写入点。九、总结EIP-2935 用状态内 8191 槽环形缓冲区 系统调用写入 合成地址部署三件套把历史区块哈希变成了可被 witness 打包、可被合约直接查询的状态数据。它不改动BLOCKHASH的任何语义却为无状态客户端、Rollup 长历史访问和针对近期祖先的链上证明打开了大门并作为 Pectra 核心执行层变更与 EIP-7709 的前置依赖持续影响着以太坊状态访问模型与 Gas 定价的演进。对想要深入理解客户端实现细节的读者建议对照阅读 EIP-2935 规范原文、EIP-4788 与 EIP-7709三份文档共同构成了系统合约式历史数据服务的完整图景。版权说明本文内容基于 EIP-2935 规范 编写规范原文以 CC0 协议放弃版权。【免费下载链接】EIPsThe Ethereum Improvement Proposal repository项目地址: https://gitcode.com/GitHub_Trending/ei/EIPs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表