两次硬分叉相隔一周:EIP-1716 圣彼得堡为什么只拆不装 图 1
两次硬分叉相隔一周:EIP-1716 圣彼得堡为什么只拆不装 · 图 1

一份只写删除线的升级说明书

以太坊的升级历史上大多数元提案都在往里装东西:某次硬分叉打包了哪几个 EIP、在哪个块号生效。EIP-1716 是个反例——它的正式标题是 Hardfork Meta: Petersburg,规范正文的核心只有两行:代号 Petersburg(别名里最直白的是 Constantinople Fix 和 St. Petersfork),主网生效条件是区块高度大于等于 7280000;被移除的 EIP 恰好一个,就是 EIP-1283。换句话说,这份文件不做加法,只做一道删除线:前一次升级想装进去的净计量规则,从这次开始不许再装。

两次硬分叉相隔一周:EIP-1716 圣彼得堡为什么只拆不装 图 2
两次硬分叉相隔一周:EIP-1716 圣彼得堡为什么只拆不装 · 图 2

被撤掉的 EIP-1283 到底改了哪笔账

EIP-1283 想改的是 SSTORE 写入存储时的计费方式。旧规则按整笔操作粗算,同一存储槽先改回去再改回来,前后照样各收一大笔;净计量路线的主张是按最终状态差异收钱——写完之后和写之前比,状态没变就只收基础访问费。这个方向本身后来被 EIP-2200 用更细的价目表实现过,不算坏主意。坏在当时的实现口径:节点在事务执行过程中用一个”脏标记”临时记账,而重入式合约会在一次调用的嵌套里多次进出 SSTORE。审计者在君士坦丁堡上线前的测试环节发现,按这套脏标记口径,被调用合约可以借着嵌套上下文把本应付出的 Gas 记成对方头上,等于给重入攻击开了一条用 Gas 记账差套利的通道。改动没经过完整重审就被塞进已经定档的升级包,这正是后来争议的火种。

同一块号怎么办:规范里的优先级规则

元提案文本里有一条容易被忽略的实现规则:如果客户端把 Petersburg 和 Constantinople 配在同一个块号,以 Petersburg 为准,净效果是 1283 处于关闭状态;如果 Petersburg 配置得更早,它上线时没有任何即时效果,只是提前立好规矩——等 Constantinople 日后激活时 1283 自动失效。这个细节解释了为什么社区宁肯多跑一次分叉:与其把已验证过的君士坦丁堡改动重新拆开再审,不如追加一次只关不开的分叉,把风险面压到最小。主网最终按第一种剧本走:君士坦丁堡在第 7280000 块前夜因事故推迟,Petersburg 在同一高度把整包安全改动(含匿名密钥、SSTORE 退款上限等其余条目)单独推上线,只把 1283 留在门外。

元提案这种文件体裁为什么存在

以太坊的硬分叉不是单条规则的开关,而是一批 EIP 的打包发货单。EIP-1716 与 EIP-1679(Istanbul)属于同一体裁:正文不定义新操作码、不改任何常数,只列名单、写块号、声明依赖。读这类提案的正确姿势是把它当装箱清单看——清单本身不生产零件,但缺了它,各客户端对”这次到底装了什么”就会产生分歧。历史上因为装箱清单写漏而对不齐的升级极少,这反而说明体裁的价值:分歧在合并清单时被提前吵完,而不是在分叉之夜的节点日志里。

对今天的读者还有什么用

第一,它是”升级可以撤功能”的判例。协议层共识从来不是单向棘轮,装错的零件可以用第二次分叉拆掉,代价只是多一次协调成本。第二,它是审计时点的判例:君士坦丁堡那次教训促成后来的惯例——进升级包前每条 EIP 至少独立过一轮外部审计,装箱阶段的新改动必须重走窗口期。第三,读旧硬分叉资料时,如果某份文档说君士坦丁堡包含净计量 SSTORE,那描述在时间线上只存在了很短一段,之后主网行为以 Petersburg 为准,别拿它推演今天的 Gas 账单。

快速问答

问:那次延迟对普通用户有什么可感知的损失? 答:账户、交易、地址都不受影响,唯一的变化是升级窗口整体后移,测试网上多跑了一轮双清单验证;对持有者和开发者来说没有需要动手的操作。

问:怎么确认历史资料里的行为口径? 答:查 EIP-1716 与 EIP-1679 的正式文件,两者的块号清单与收录名单就是权威分界;判断任意一笔 2019 年 2、3 月的 SSTORE 账单属于哪套规则,先比块号和 7280000。

风险提示:本文是协议历史与技术机制介绍,不构成任何投资建议;涉及具体 Gas 计费与升级行为请以对应 EIP 原文和当期客户端文档为准。