一个操作码数出三种空:EXTCODEHASH 怎么区分不存在、空壳与EOA 图 1
一个操作码数出三种空:EXTCODEHASH 怎么区分不存在、空壳与EOA · 图 1

以太坊的状态里,“什么都没有”其实分好几种。区分它们最直接的工具是 EXTCODEHASH:0x3f 操作码,2018 年由 EIP-1052 提出,2019 年 2 月底随君士坦丁堡分叉(以圣彼得堡之名在主网区块 7280000 执行)上线。它从栈里取一个参数,只认低 160 位当作地址,返回该地址账户代码的 keccak256 哈希。

三种返回值的含义

第一种:返回 0。规范写明,账户不存在、或者属于 EIP-161 定义的空账户(无余额、无 nonce、无代码的空壳)时,直接压 0 上栈——换句话说,状态里还没有这个地址的正式记录。第二种:返回 c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470。这是空字符串的 keccak256 哈希,即 EIP-1052 原文所称”账户存在但没有代码”的情形,日常对应一个持有地址记录的普通钱包地址。第三种:返回某个非零哈希,说明地址上挂着合约代码,值本身就是代码指纹。一次操作把存在性、身份类型、代码完整性三件事全说了,这也是 EIP 作者强调它与 EXTCODESIZE 的本质区别:长度分不出”没代码的合约”和”根本没有账户”,哈希分得出。顺带一提,那个空代码哈希并非随机值——它就是空字符串的 keccak256 结果,任何客户端都算出同一个数,正因如此它才能当规则比对基准。日常用法里它还是省 gas 的惯用件:合约想知道”对面还是不是我上次见过的那份代码”,一次 EXTCODEHASH 对比缓存值,比把整段代码用 EXTCODECOPY 拉回内存便宜得多;工厂模式核对某个反事实地址上有没有东西,也只看一次哈希。两个规则直接吃它定义:EIP-3607 规定发送方账户的代码哈希只要不等于那个空代码哈希,交易就必须判为无效,堵住同一个地址既归私钥又挂合约的歧义;EIP-7702 清除委托时同样是把账户代码哈希恢复成空哈希。但它是把双刃尺,要避开一个误会:哈希只覆盖代码,不覆盖存储——同一份代码可以配着面目全非的存储,反之亦然。

一个操作码数出三种空:EXTCODEHASH 怎么区分不存在、空壳与EOA 图 2
一个操作码数出三种空:EXTCODEHASH 怎么区分不存在、空壳与EOA · 图 2

一部价格上调史

这个操作码的价格变化浓缩了状态访问计价的整体转向。初始定价 400 gas,与 BALANCE 同档,理由是一样贵的一次账户查找。转折在 2019 年:研究者实测发现,用一笔 10M gas 的循环反复敲 EXTCODEHASH 这类触发状态树查找的操作码,能把部分客户端的区块处理拖进几十秒甚至分钟的量级——Martin Swende 与 Péter Szilágyi 在 2021 年 5 月的官方披露文章里公开了这条攻击线。价格于是步步上调:伊斯坦布尔升级(区块 9069000)把 EXTCODEHASH 和 BALANCE 从 400 抬到 700;2021 年柏林升级的 EIP-2929(自区块 12244000 起)彻底换范式,状态访问按冷热分层,同一笔交易里首次触碰的目标按冷访问计 2600 gas,之后再碰只收 100。更早的 2016 年,同族的 EXTCODESIZE 与 EXTCODECOPY 已在 Tangerine Whistle 分叉里从 20 涨到 70——那是以太坊第一次用定价回应状态攻击。同样一笔探测,价格从早期的几十到六七倍的起伏,反映的是把 DoS 成本从协议兜底转回调用方付费的思路。

用它对什么、不对什么

合约用它做部署探测、比对代码是否被换过,工具用它批量筛查地址类型,这些都便宜且可靠。但普通用户借 RPC 读代码哈希时要清楚语义边界:拿到 0 说明该地址在状态里还没有账户——它可能是没充过值的新公钥地址,也可能是从未被使用的预测地址;拿到空代码哈希才说明确实有人握着这把私钥的正式记录。两种空都不危险,混淆它们才会:把”尚无账户”误读成”地址无效”,或者反过来把可预测地址当成别人已占用的证据。对开发者,还有一条实操提醒:冷热定价意味着在循环里对外部地址反复做代码哈希检查是昂贵模式,先批量收集再统一访问、或者用访问清单预热,是柏林之后降低 gas 的常规手法。本文只讨论机制,不构成任何投资建议。