Optimism NUT 机制深度解析:op-core/nuts/state 中 pre-fork 状态工件的设计、生成与确定性校验 📅 发布时间:2026/9/17 13:25:25 👁 浏览次数: Optimism NUT 机制深度解析op-core/nuts/state 中 pre-fork 状态工件的设计、生成与确定性校验【免费下载链接】optimismOptimism is Ethereum, scaled.项目地址: https://gitcode.com/GitHub_Trending/op/optimismOptimism 通过 Network Upgrade TransactionNUTbundle 定义每次 L2 硬分叉激活的 deposit 交易序列而op-core/nuts/state/目录下的fork_state.json工件则是这套机制的关键配套它们是链在某个分叉激活后、下一分叉激活前冻结下来的 L2 predeploy 状态快照。读完本篇你将理解这些 pre-fork 状态工件为什么存在、如何命名、如何用ops/scripts/gen-seed-state.sh生成种子状态、如何用just nut-prefork-state-for fork逐个合成后续状态以及如何用just check-nut-prefork-states在 CI 中保证状态快照可复现、无漂移并能结合 state.go 与 prefork_state_gen_test.go 源码看懂生成与消费两端的调用链。Pre-fork 状态工件NUT bundle 激活测试的启动盘按 op-core/nuts/state/README.md 的定义fork_state.json是冻结的 L2 predeploy 状态代理合约 其实现含完整 storage时间点是该分叉激活之后、下一分叉激活之前——即链刚完成fork升级那一刻的状态。这套工件直接服务于 NUT bundle 激活测试 nut_bundle_activation_test.go对于分叉F该测试不是从当前源码重新构建 genesis而是从forks.Prev(F)_state.json启动。这样做的意义在于——bundle 本身是冻结的、不可变的见 fork_lock.toml 中记录的 sha256 与 commit让测试从该 bundle 当初真正要升级的那个 predeploy 版本启动才能把 bundle 对着它设计时的目标环境做验证。在 nut_bundle_activation_test.go 中可以看到这一消费点测试调用nutsstate.PreForkState(fork)取出前置状态若缺失会直接报出generate the predecessor forks state (see op-core/nuts/state)的指引。命名规则按状态代表的分叉命名而非按消费它的 bundle原文档明确了命名与工作流的核心约束状态文件以它所代表的分叉命名而不是以消费它的 bundle 命名。原因在于未来分叉的名字无法预知设计意图是哪个分叉上线就在何时生成对应的状态。当前仓库中的对应关系是文件状态时点被谁消费jovian_state.jsonjovian 激活后的状态karst bundle 激活测试karst_state.jsonkarst 激活后的状态lagoon bundle 激活测试lagoon_state.jsonlagoon 激活后的状态下一个分叉名字确定后这些文件与 NUT bundle 锁文件配套被 CI 强制检查check-nut-locks 脚本不仅校验 bundle 哈希与 lock 条目一致还会检查每个已锁定分叉都有对应的op-core/nuts/state/fork_state.json错误信息直接指向本目录的 README。状态合成链每个状态由前一个状态叠加冻结 bundle 而来每个状态都由前一个状态合成而来karst_state jovian_state (karst bundle applied)依此类推。链的种子是jovian_state.json——jovian 没有前序 NUT bundle因此它是唯一需要从那个时代的源码构建的状态其余状态都只是重放冻结 bundle。当前目录中已提交的工件即 jovian_state.json、karst_state.json 与 lagoon_state.json。生成种子状态jovian_state.json必须用 jovian 自己的工具链ops/scripts/gen-seed-state.sh为什么不能用当前 op-deployer 来生成原文档给出的理由是当前的 op-deployer 无法消费 jovian 时代的合约。在 worktree 里构建等于把那个时代的合约与那个时代的工具配对。从 gen-seed-state.sh 的实现可以看到完整流程锁定时代脚本默认使用固定 commitd09c836f818c73ae139f60b717654c4e53712743即op-contracts/v5.0.0发布 tag——jovian 的 L2 合约发布版本支持以参数传入其他 commit。若本地克隆缺少该 tag 对应的 commit脚本会给出明确的git fetch ... tag op-contracts/v5.0.0提示而非晦涩的报错建 worktreegit worktree add --detach在临时目录检出该 commit退出时自动清理构建时代合约在 worktree 内运行just build-no-tests生成 jovian 时代的 forge-artifacts时代适配后 dump 状态把仓库中已提交的 dump 工具 prefork-state-dump/main.go 拷入 worktree并用sed将导入路径op-core/predeploys改写回op-service/predeploys——因为在 jovian 时代的 commit 上predeploys 包还位于op-service下后来 op-core 解耦时才迁到op-core/predeploys从而保证 dump 工具能对着分叉时代的包编译输出go run ./ops/scripts/prefork-state-dump jovian out生成 predeploy 作用域的 jovian 状态写入op-core/nuts/state/jovian_state.json。生成后续状态compose 工作流以 karst 为例种子之后的每个状态都是其分叉激活后的状态fork_state prev_state (冻结的 fork bundle applied)实际命令为# karst_state.json jovian_state karst bundle just nut-prefork-state-for karst流程是启动前置状态前一分叉的状态→ 在激活块应用冻结 bundle → 将激活后、predeploy 作用域的状态 dump 到fork_state.json。从根目录 justfile 可以看到其底层实现# Generates op-core/nuts/state/fork_state.json (predecessor state frozen fork bundle). _nut-prefork-state-for fork: OP_E2E_GEN_PREFORK_STATE{{fork}} go test -count1 -run TestGenerateForkState ./rust/kona/tests/proofs/也就是说它运行的是 env-gated 的TestGenerateForkState位于 prefork_state_gen_test.go该测试复用验证测试同一个activateFork流程——生成逻辑与验证逻辑因此始终保持一致in lockstep这也是它作为测试而非独立二进制的存在理由。另外compose不需要分叉时代的合约构建——它只是重放已经冻结的 bundle所以当前工具链即可胜任尽管 seed 生成时提到的 ABI 漂移对 compose 不构成障碍。两条实操约束值得注意一次只生成一个分叉且每个生成后先提交加载器在编译期 embed 状态文件所以生成fork_state.json之前prev_state.json必须已落盘测试二进制需要重新编译才能看到新状态CI 校验运行just check-nut-prefork-states可验证每个已提交的分叉状态都是最新的——它会重新生成所有非 seed 状态并在提交的 JSON 发生变化时失败实现见 justfile对每个*_state.json跳过 jovian 后调用_nut-prefork-state-for最后git diff --exit-code判定漂移。源码剖析state 包如何加载与校验 pre-fork 状态state.go 是整个工件的加载端几个设计点都有明确注释为什么单独成包op-core/nuts被 op-node 与 kona-node 导入以 embed bundle而状态 JSON 每个分叉数百 KB。把 embed 放在state包意味着状态只随导入它的测试二进制分发不会进入节点二进制加载逻辑PreForkState(fork)先取forks.Prev(fork)得到前置分叉读嵌入式//go:embed *_state.json中的prev_state.json反序列化为types.GenesisAlloc。缺失状态是硬错误前置分叉不存在或该前置分叉的工件尚未提交因为激活测试强制要求一个 pre-fork 状态守卫测试state_test.go 中TestPreForkState遍历forks.From(forks.Karst)的所有分叉断言每个消费方分叉都能解析出一个非空的 pre-fork 状态且其中存在带代码的SequencerFeeVault代理——这样在不需要 kona-host 的情况下就能快速捕获状态漏提交/漏 embed而不必等到激活测试真正运行时才暴露TestPreForkStateWithoutPreforkState则反向确认 bedrock..jovian 这些没有前置状态的分叉全部报错。消费侧的作用域由 prefork_state_gen_test.go 中的predeployScoped保证dump 出来的状态只保留每个 predeploy/preinstall 及其 EIP-1967 实现槽指向的实现合约含每账户完整 storage其余账户如预充值 EOA全部丢弃——这正是状态文件体积可控且predeploy 作用域这一名字由来的原因。确定性合成状态字节可复现seed 则是一个特定实例原文档对确定性做了明确的二分说明合成状态karst_state 及之后字节可复现在给定已提交 seed 的前提下。fork_state prev_state (frozen bundle)本身是确定性的额外的确定性措施是生成器会清零 L1Block 的 L1 属性槽整数槽 0..8——timestamp、L1 hash 等见canonicalizeL1Block其注释对应snapshots/storageLayout/L1Block.json的布局。这些槽由逐块的 L1-info deposit 写入若不处理会随测试的 wall-clock L1 genesis 时间变化而它们在状态被消费时本来就会被重新设置所以清零不影响行为。EIP-1967 的 impl/admin 槽与isFeatureEnabled映射keccak 键控则原样保留。Seedjovian_state不字节可复现其 worktree 里的 op-deployer 运行会随机化 CREATE2 salt导致烧进跨域桥 predeployotherMessenger/otherBridge中的 L1 对应地址每次生成都不同。仓库提交的是其中一个特定实例合成状态原样继承这些地址。由于这些地址是任意的、激活测试也不依赖它们这不影响测试所验证的内容。小结op-core/nuts/state/是 Optimism NUT 硬分叉体系里测试可信性的一环bundle 冻结了改什么pre-fork 状态工件冻结了从什么基线改。种子jovian_state.json用分叉时代的工具链一次性生成后续状态用just nut-prefork-state-for fork逐个合成并先提交再生成下一个just check-nut-prefork-states与 check-nut-locks 在 CI 中共同保证状态与锁文件不漂移、不缺失而PreForkState的编译期 embed 设计确保这些大 JSON 只随测试二进制分发。整条链路配合 op-core/nuts/README.md 中的两 PR 工作流contracts 变更 → snapshot bundle → 生成 pre-fork 状态构成了一套可审计、可复现的 L2 合约升级验证基础设施。【免费下载链接】optimismOptimism is Ethereum, scaled.项目地址: https://gitcode.com/GitHub_Trending/op/optimism创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考