两条路线竞争一个需求
智能合约经常需要知道自己在哪条链上。跨链重放防护、按链分支的合约逻辑,都依赖这个读数。以太坊社区为这个需求走过两条路线:一条是加操作码,合约执行到 CHAINID(0x46)时直接把链号压上栈,这是 EIP-1344,状态 Final、早已随升级生效;另一条是 EIP-2014,2019 年 5 月 10 日由 Alex Beregszaszi 提交,主张根本不加热操作码,而是设一个公共查询窗口。两份提案解决同一个问题,命运相反:一个进了每份钱包签名弹窗背后的链号校验,一个躺在仓库里停在 Stagnant(停滞)。

ESO 的设计:一个会答话的预编译
EIP-2014 把系统合约 ESO(Extended State Oracle,扩展状态神谕)放在地址 0x0000000000000000000000000000000000000009,合约用普通 CALL 或 STATICCALL 调用它,接口遵守合约 ABI 编码——对调用方来说它就像一个普通合约,不需要任何语言层新语法。第一个提供的功能是 getCurrentChainId,无参数、返回 uint64、非 payable:往它身上转任何以太都会按 EIP-140 的 REVERT 规则回滚。提案原文给出了完整的 ABI JSON,并算出这笔调用的样子:向 ESO 发送字节 5cf0e8a4,在主网会返回一个左补零到 32 字节、值为 1 的响应。这份细节在提案页面至今可逐字核对。
动机:操作码位贵,预编译接口难产
2014 的动机章节把两种既有办法的痛点说得很整齐。加操作码的问题:EVM 的操作码空间有限,为一堆低频查询功能烧Permanent的编号不划算,此前 EIP-210 给区块哈希扩容、EIP-1344 给链号加指令,都是各占一个位置的先例。加预编译的问题:每个新预编译都要单独定义并议定接口,case by case 太摩擦。ESO 的方案是把两者折中:位置只占一个预编译地址,功能通过 ABI 接口无限扩展,以后的新数据(提案展望了带状态读取信标根这类需求)只需给 ESO 加函数,不再惊动操作码表和分叉清单。这个思路在当年相当超前,它本质上是想给未来的节点数据查询设计一份标准 API。
0x09 后来另有了主人
有意思的是地址本身。2014 想用的 0x09,在真实世界后来被 EIP-152 占用——BLAKE2 压缩函数 F 的预编译,状态 Final。也就是说 ESO 落选多年之后,它圈的那块地址按另一份提案的规格上了场。而链号查询的赢家也毫无悬念:EIP-1344 的操作码路线定稿生效,今天任何一份写死链号的签名数据、每一次签名前的链号核对,背后都是 0x46 那次压栈。链号本身的历史与改号风险可参考 签过的消息会因链号改变作废吗:EIP-1959 与 1965 的链ID历史之问——链号不是装饰数字,写错或被改会让签名在另一条链上照样”合法”,这正是这类查询功能存在的根因。
为什么单点操作码赢了万能窗口
复盘这轮竞争,赢面在确定性。操作码路线零接口协商:一个字节的指令,语义写死在黄皮书级别的文档里,所有客户端照着同一份字节码实现。ESO 路线要把 ABI 协商变成全网共识的一部分——函数名、选择器、返回编码,任何一次扩展都要所有人再谈一轮;而且合约调用预编译要多付一层 CALL 的开销与出错处理。等到需要给合约读第二种扩展数据(比如信标区块根)时,社区给出的仍是操作码或专用预编译,ESO 模式始终没有被采用第二次,这基本宣判了它的路线之争失败。
从落选设计里带走什么
对钱包与链上工具的使用者,2014 提醒一件小事:合约”知道”的事情远比感觉上少——它默认只看得见区块头里的十个字段和当前执行环境,链号要靠专门指令,别的链的数据要靠桥或预言机。对读提案的人,它示范了一种常见的死法:设计漂亮、抽象干净,但每一项具体的功能都被更笨直的分头方案抢先实现,等到窗口打开时已无处安放。风险提示:本文为提案历史分析,不构成投资建议。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。