以太坊的 initcode 与运行码:49152 与 24576 两道字节关卡各防什么 图 1
以太坊的 initcode 与运行码:49152 与 24576 两道字节关卡各防什么 · 图 1

一句话先说清

以太坊上部署一份合约,实际上跑两段代码:第一段叫 initcode(初始化码),执行构造逻辑、写初始状态、最后返回真正要长期驻留的运行码;第二段是运行码(runtime code),进入世界状态被所有人反复执行。协议给两者各设一道字节关卡:EIP-3860 把 initcode 上限定为 49152 字节(正好是运行码上限的两倍),EIP-170 则早在 2016 年就把运行码钉死在 24576 字节。两道关卡防的不是同一件事。

以太坊的 initcode 与运行码:49152 与 24576 两道字节关卡各防什么 图 2
以太坊的 initcode 与运行码:49152 与 24576 两道字节关卡各防什么 · 图 2

initcode 的“体检费”问题

节点在验证一笔部署交易时,要先对 initcode 做 jumpdest 分析——把所有可能是跳转目标的字节标记出来。这项工作的成本与代码长度成正比,却在 2021 年之前既没有上限也没有计费:攻击者可以在一笔交易里塞入海量 initcode,让每个节点为几毛钱的 gas 干几毫秒的解析活;更糟的是用 CREATE 反复生成变种 initcode,把整条链的验证时间拖垮。历史上这不是理论威胁——EIP 文本明确记载:2017 年 geth 1.6.5 就曾为一段精心构造的巨型 initcode 引发的漏洞发布过紧急修复。EIP-3860(2021 年提出,随 2023 年 4 月 12 日的上海升级在主网激活,编号与 EIP-3651、EIP-3855、EIP-4895 同车)给出了“限制加计费”的组合拳:超过 49152 字节的 initcode 直接拒绝;不超的,每 32 字节加收 2 gas,把解析成本按字数摊进交易账单。附带的好处是代码长度、程序计数器和跳转偏移全部能塞进 16 位整数,客户端实现更干净。

运行码关卡防的是另一件事

EIP-170 的 24576 字节约束管的是长期成本:运行码进世界状态,每个节点永远存着它、每次调用都可能分析它,还直接影响区块验证的摊销成本。两道关卡是“一次性的入场体检”与“永久的居民登记”的关系,合起来构成对“巨型合约”的完整防线。规范细节也别漏:initcode 本身按 calldata 规则收费(零字节 4 gas、非零 16 gas),部署成功还要为驻留的运行码每字节付 200 gas;CREATE2 另按每 32 字 6 gas 收哈希地址的钱——同一份字节码,四条收费通道各管一段账。

对开发者的日常意义

2023 年之后,用新版编译器默认开启 PUSH0 等上海方言部署合约,遇到“无效操作码”报错,说明目标链尚未跟上上海升级的参数表;遇到“超过大小限制”的部署失败,通常的处方是:把可复用逻辑拆库(钻石模式、代理模式)、开启优化器压缩、或把常量数据挪进不可执行的数据区。需要部署超长字节码(例如内嵌查找表)的团队,49152 的 initcode 天花板意味着必须走“多笔交易分段写入”的设计路线。

一条直觉线

把部署合约想成搬进一个新办公室:initcode 是搬家那天的施工队——按人头收一次性工钱,人太多还得按名册额外交管理费(体检费),超过限定的施工人数直接从小区门口劝返;运行码则是日后常驻的员工,编制上限严得多,因为他们的工资(存储与每次执行的解析摊销)是全体业主永远在付的。收两道费、设两道闸,正是因为“一时的算力消耗”和“永久的状态占用”本质是两种成本。

快速问答

问:为什么 initcode 上限恰好是运行码的两倍?规范直接写明 MAX_INITCODE_SIZE 等于两倍的 MAX_CODE_SIZE——经验依据是 initcode 除了运行码还要携带构造逻辑,给双倍余量既宽松又可审计。 问:旧合约会不会被新规影响?EIP-3860 只作用于部署路径,已部署合约的执行规则不变;超限交易仍可被打包,但会立即异常回滚。 问:EIP-170 之前有下限吗?没有——正因没有上下限,恶意超大代码与巨小“粉尘合约”的账都要靠后来这些提案补齐。

常见误区

一是把 49152 与 24576 记反:前者管入场通道,后者管长期状态,2 倍关系本身在规范里写明。二是以为 initcode 只在交易里收费:通过 CREATE/CREATE2 由合约生成的 initcode 同样落入这套限制与计费。三是把“跳点分析”当成安全扫描:它只是枚举合法跳转位置的机械劳动,正因为便宜且必需,才更需要被准确计费。

风险提示:本文为以太坊协议机制科普,不构成任何投资建议。