历史区块哈希合约:EIP-2935 怎么把 8191 个区块哈希搬进状态 图 1
历史区块哈希合约:EIP-2935 怎么把 8191 个区块哈希搬进状态 · 图 1

智能合约里有一个操作码叫 BLOCKHASH,用来查历史区块的哈希。但它有个硬边界:只服务最近 256 个区块,再早的查询一律返回零。这个 256 窗口是远古设计的遗产,改它有兼容性代价,于是 EIP-2935 选了一条不改语义的侧路——把最近 8191 个区块哈希存进一个系统合约的存储里,谁要谁直接查。这个看似低调的搬家动作,是为无状态执行铺的一块地基。本文按规范文本拆解合约的地址、写入路径和读取规则,以及为什么它和 BLOCKHASH 和平共处。

区块哈希为什么需要一个新家

BLOCKHASH 的实现前提是节点手头有最近的区块哈希。对今天的节点这不算要求,但对设想的无状态执行者来说这就断了:无状态验证者只携带一份见证数据,不随身背着一串历史区块。见证是由执行层生成的,如果区块哈希本身躺在状态存储里,它就能顺着见证一起打包递过去,无状态验证者不需要任何额外通道。这就是 EIP-2935 把哈希挪进状态的核心动机:让哈希成为状态的一部分,让它天然进入见证。对第二类读者同样有吸引力——二层系统的合约。Rollup 的桥和验证合约经常需要核对某个历史区块的哈希,256 个区块按主网节奏只有二十多分钟,稍长的挑战窗口就不够用。8191 个哈希按当前出块节奏大约覆盖一天,把这类需求稳稳罩住了。

合约怎么来的:一个反推出来的地址

历史区块哈希合约:EIP-2935 怎么把 8191 个区块哈希搬进状态的机制示意

这个合约住在地址 0x0000F90827F1C53a10cb7A02335B175320002935。地址末尾的 2935 不是巧合,是让地址肉眼可读;它不是 create2 部署的,而是从一笔特殊交易反推出来的合成地址:先写好目标字节码,再造出一笔 nonce 为零的普通创建交易,让交易发送方恰好是从协议内地址派生的账户,其首个合约地址正好落在目标值上。字节码被逐字节嵌进交易的输入数据,地址与代码因此在密码学上互相绑定,任何人都能重新推演验证,没有部署仪式,也没有可信设置。

写入路径与 EIP-4788 同一套惯例。每个区块开始处理交易之前,EVM 以系统地址向该合约发起一次调用,输入是父区块哈希的 32 字节。合约收到系统调用时执行写入:把值存进编号为区块高度减一、再对 8191 取模的存储槽。这是一个标准的环缓冲区,高度每推进 8191 个区块,写入位置就回到起点。规范明确:合约激活后需要再走满 8191 个区块,环才会被连续填满;激活初期的环里只有升级之后新产生的哈希,更老的历史不在其中。

读取规则:两个窗口各管一段

普通调用(调用者不是系统地址)走读取分支。调用方传入 32 字节大端编码的区块高度,合约先在环里定位槽位并返回存储值。规范对越界做了两道拦截:calldata 长度不是 32 字节回退;请求的高度落在区块高度减 8191 到区块高度减一这个闭区间之外同样回退——也就是说,它宁可报错也不返回零,和 BLOCKHASH 查不到就默默给零的旧脾气截然不同,这一点写合约时很容易被旧经验带偏。

同时规范用一整行强调:BLOCKHASH 操作码的语义、范围和费用原样不动。于是链上并存两个窗口——操作码管最近 256 个,合约管最近 8191 个——各查各的,费用也各走各的。为什么环是 8191 而不是更大?规范的推理很朴素:一天左右的覆盖对提交一笔带验证的交易并等到上链绰绰有余,再大就是白白加重状态。

边界与核对清单

第一,这个合约不是归档服务。它只保证环内的连续哈希,想做”任意历史区块是否存在”的证明仍然要走区块浏览器、归档节点或协议里其他承诺机制。第二,环里的哈希没有随附区块本体,知道哈希不等于能重放那个区块的数据。第三,它是无状态路线图上的中间支柱之一:见证里带上这些哈希的收益,要等无状态客户端真正铺开才会兑现,此前更像一份提前打好的地基,规范自己也把这条路线标为分阶段推进。第四,验证合约逻辑时留意分支安全:规范在安全考虑里专门讨论了热点写入路径被微量资金干扰的分支投毒问题,结论是攻击成本远不足以拖慢状态根更新,但这属于协议层自己盯的风险,不是用户层要防的事。普通用户感知不到这个合约的直接操作面,它出现的意义在于:未来轻客户端和二层桥的验证路径更短、能验证的内容更多。本文仅为协议机制说明,不构成任何投资建议。