BLOCKHASH 挪进存储之后:EIP-7709 改的是价格和读法,不是窗口 图 1
BLOCKHASH 挪进存储之后:EIP-7709 改的是价格和读法,不是窗口 · 图 1

BLOCKHASH(0x40)是 EVM 里最像历史遗留的操作码:合约给它一个区块号,只有落在当前区块之前 256 个的窗口内才能换回哈希,超出范围一律返回 0。更特殊的是实现方式——这些区块哈希从来不进世界状态,客户端靠一条专门路径在内存里维护最近的区块头来应答。EIP-7709(2024 年 5 月起草,仓库状态 Draft)要给这条特例收尾:语义不动,读法和价格动。

两步走的架构

第一步是 EIP-2935:在状态里放置一个系统合约(地址 0x0000F90827F1C53a10cb7A02335B175320002935),把父块哈希持续写进它的存储,规范参数给这份历史底仓的窗口是 8191 个区块;这一步已随 2025 年 5 月的布拉格升级落地。第二步才是 EIP-7709:BLOCKHASH 的解析改为读 2935 合约的存储槽,同时把 gas 从过去的统一优惠定价改成与普通状态读取一致——规范原话是按 SLOAD 同款的冷热收费,在现行参数下就是热读 100、冷读 2100。两个关键数字要分清:BLOCKHASH_SERVE_WINDOW 等于 256,是操作码继续只对外敞开的窗口,规范明确超出窗口照旧返回 0,哪怕合约底仓里存着更多;HISTORY_SERVE_WINDOW 等于 8191,才是 2935 合约真正存下的长度。窗口是策略,底仓是事实,合约接口本来就能查到比操作码更久远的哈希。还有一条容易被忽略的解析细节:无论哪条路径,指向未来区块或当前区块自身的参数一律返回 0——这个 0 不是错误码,而是一种语义,合约若拿返回值直接当随机数素材,会把”查不到”错当成”哈希恰好为零”。

BLOCKHASH 挪进存储之后:EIP-7709 改的是价格和读法,不是窗口 图 2
BLOCKHASH 挪进存储之后:EIP-7709 改的是价格和读法,不是窗口 · 图 2

从合约到操作码的接力

2935 的合约接口解决的是”历史哈希去哪查”:它把每个父块哈希写进自己存储的第 N 个槽位,滚动维护一段可证明的历史,甚至把过去返回 0 的边界场景改成能返回真值。7709 解决的是”操作码走哪条路”:让 BLOCKHASH 不再享有独立于状态机的特殊通道。两件事一先一后,合起来才把”读历史哈希”这件小事完整搬进状态世界。

为什么要多花这道 gas

Rationale 部分给出的理由不在钱,在结构。只要 BLOCKHASH 走特例路径,给执行过程做可验证证明(witness)时就得为它单独发明一条证明规则——比如用父头链现场推哈希;改走状态之后,区块哈希的存取与任何一次 SLOAD 共用同一套状态访问机器,证明生成、无用状态清理这些围绕状态转的机制从此对 BLOCKHASH 零特判。换句话说,这个 EIP 是无状态化路线图里对齐边角的一块:先让”读历史哈希”变成一次普通的读状态,后面所有的状态证明与清理工具才不用为它留暗门。

落地状态与用户影响

截至本文写作时,7709 在 EIP 仓库仍是 Draft,未进入已激活升级的清单,价格变化以最终规范为准;已经落地的一半(2935)自布拉格升级生效。对普通用户与合约开发者的现实影响:依赖 BLOCKHASH 做随机源或时锚的合约,窗口内的可用性不变,但冷读一次的价格从平价的几百升到 2100 档,循环哈希的 gas 账要重算;用 RPC 查历史区块哈希的路径本来就不受这个操作码约束,该查多久查多久。它解决的不是”BLOCKHASH 能不能查更远”——操作码层面的答案依旧是 256——而是让”更远”这件事从此有了一条状态里的正道:想知道更久的历史,走合约接口,别指望操作码开天窗。对做依赖审计的工具方,这也提示一个新的检查项:合约读历史哈希走的是操作码还是 2935 合约,两条路的 gas 画像与失败模式完全不同,报告里值得分开列。本文只讨论机制,不构成任何投资建议。