收据里的一个字节:EIP-658 如何用状态码替代中间状态根 图 1
收据里的一个字节:EIP-658 如何用状态码替代中间状态根 · 图 1

打包成功不等于执行成功

在以太坊上,一笔交易被矿工收进块里,只说明签名和费用预检过了关;它真正想做的调用可能中途触发回退——余额不足、权限校验失败、合约主动 revert。回退的意思是:本次交易引发的状态变化整体撤销,但手续费照扣。于是链下观察者必须回答一个此前收据答不了的问题:这单到底成没成?

EIP-658 之前,回答这个问题只有两条路。全节点可以重放交易取最终状态差异,快同步节点只能重放自己同步点之后的部分,轻节点干脆做不到。更早的收据里本来有一个中间状态根字段,记录交易执行完的世界状态承诺,但 EIP-98 收据默克尔化改造后,这个字段已经名存实亡。提案作者尼克·约翰逊的点子堪称字段再生的典范:既然空着也是空着,把它改成一个比特的语义——一代表成功,零代表失败,任何可导致回退的操作一律算失败。

字段换了人,语义定了规

规范行文短得罕见:自拜占庭分叉激活块起——主网是 2017 年 10 月激活的拜占庭升级,对应区块四百三十七万——收据中的中间状态根位置改放返回状态,零为一失、一为成功。提案明确说这是对 EIP-140 引入 REVERT 操作码的配套补丁:在可回退的世界里,花光全部燃料不再是失败的可靠信号,旧判据失效,状态码补上这个洞。

顺带解决的还有返回数据的收纳问题。收据不再假装承诺状态之后,日志与布隆字段成了收据的主体,后来 EIP-2718 带类型交易信封、EIP-658 的状态码继续保留在字段布局里,一路活到今天的每一版收据结构。

今天读收据的三个姿势

第一,判断成败永远看状态码,别看气耗。失败交易照样烧完预算上限内的燃料,靠 gasUsed 猜结果在拜占庭之后就是错误方法。第二,状态码只覆盖顶层交易;合约内部调用失败被调用方吞掉的情况,状态码仍是成功,需要结合日志或追踪判断业务语义是否兑现。第三,老链数据分界清晰:拜占庭前的收据该位置是旧的哈希语义,历史浏览器与归档工具常把早期交易的该字段留空或填占位哈希,跨版本对账时先确认节点返回的收据来自哪个分叉时代。

和比特币对照一下

比特币世界的对应物一直存在:交易被写进块里,但脚本执行失败的交易早在最初版本就会被区块校验拒绝,压根进不了块,所以不存在进块却失败这种中间态。以太坊的执行模型更自由——交易先进块,内部再跑任意合约调用,失败才由状态码兜底。两个网络对交易失败的假设差别,正是各自的执行模型决定的:读懂一条链的回执,先要理解它的虚拟机怎么决定一条交易配不配进块。

快速问答

问:收据状态码和交易回执里的日志有什么关系? 答:状态码回答顶层交易成没成;日志记录合约主动发出的事件。一个讲成败,一个讲业务事件,判据互补。

问:为什么不能靠事件里有没有转账来推断成功? 答:合约可以在同一笔交易里先转再 revert,或者用不同事件体系;唯一可靠的顶层结论仍是状态码加状态读取。

一条直觉线

把收据想成快递面单:以前面单上要抄一份仓库盘点号,实际没人核对;EIP-658 把这一栏改成签收章——盖了就是送到,没盖就是退回,至于退回原因、途中细节,去看日志和追踪。一个字节换来全网络免重放的成败判定,这是字段语义演进的教科书案例。

风险提示:本文介绍协议机制,不构成投资建议;解读链上结果请结合节点查询与官方文档,谨防把打包误当成功。