From 58518ea2b3b8b31fe602a60bdfd94d8cabe68109 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Wed, 29 Apr 2026 16:46:48 +0200 Subject: [PATCH] core, miner: bootstrap the binary transition registry on the first UBT block Add `core/transition_registry.go` with two helpers: - `InitializeBinaryTransitionRegistry` deploys the system contract at `params.BinaryTransitionRegistryAddress`. The contract's bytecode returns the storage slot indexed by the call's CALLDATA, exposing the transition state to off-chain readers. - `WriteBinaryTransitionBaseRoot` writes the frozen MPT base root into slot 5. The slot constant is kept private so callers go through this helper. Wire both calls into the three places that build state for a new block: - `core/state_processor.go`: in `Process`, after the EIP-2935 system call, when the current block is on UBT and the parent is not. - `miner/worker.go`: at the end of `prepareWork`, with the same fork-boundary check, so locally-built payloads also seed the registry. - `core/chain_makers.go`: in `GenerateChain`, between the EIP-2935 handling and the user-supplied `gen` callback, so generated test chains see the registry deployed identically to a live chain. --- core/chain_makers.go | 7 +++++ core/state_processor.go | 11 +++++++ core/transition_registry.go | 62 +++++++++++++++++++++++++++++++++++++ miner/worker.go | 7 +++++ 4 files changed, 87 insertions(+) create mode 100644 core/transition_registry.go diff --git a/core/chain_makers.go b/core/chain_makers.go index 46cd98de61..b45f3f03af 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -399,6 +399,13 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse ProcessParentBlockHash(b.header.ParentHash, evm) } + // On the first UBT block, deploy the binary transition registry and + // record the parent's MPT root as the frozen base root. + if config.IsUBT(b.header.Number, b.header.Time) && !config.IsUBT(parent.Number(), parent.Time()) { + InitializeBinaryTransitionRegistry(statedb) + WriteBinaryTransitionBaseRoot(statedb, parent.Root()) + } + // Execute any user modifications to the block if gen != nil { gen(i, b) diff --git a/core/state_processor.go b/core/state_processor.go index fda3bf8fe7..71d9a599a2 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -95,6 +95,17 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated if config.IsPrague(block.Number(), block.Time()) || config.IsUBT(block.Number(), block.Time()) { ProcessParentBlockHash(block.ParentHash(), evm) } + // On the first block after the UBT activation, deploy the binary + // transition registry system contract and capture the frozen MPT base + // root in slot 5. The registry is what every subsequent block reads to + // reconstruct the transition state. + if config.IsUBT(block.Number(), block.Time()) { + parent := p.chain.GetHeaderByHash(block.ParentHash()) + if parent != nil && !config.IsUBT(parent.Number, parent.Time) { + InitializeBinaryTransitionRegistry(statedb) + WriteBinaryTransitionBaseRoot(statedb, parent.Root) + } + } // Iterate over and process the individual transactions for i, tx := range block.Transactions() { diff --git a/core/transition_registry.go b/core/transition_registry.go new file mode 100644 index 0000000000..5dff348f00 --- /dev/null +++ b/core/transition_registry.go @@ -0,0 +1,62 @@ +// Copyright 2026 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package core + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/params" +) + +// transitionStatusByteCode is a minimal contract that returns a single 32-byte +// storage slot to the caller. CALLDATALOAD picks the slot index from the call +// input and SLOAD reads the value, which is then returned as a 32-byte word. +var transitionStatusByteCode = []byte{ + 0x60, 0x00, // PUSH1 0 + 0x35, // CALLDATALOAD (slot index) + 0x54, // SLOAD + 0x60, 0x00, // PUSH1 0 + 0x52, // MSTORE + 0x60, 0x20, // PUSH1 32 + 0x60, 0x00, // PUSH1 0 + 0xf3, // RETURN +} + +// transitionRegistryBaseRootSlot is slot 5 of the transition registry, where +// the frozen MPT base root is stored. The slot indices match those decoded by +// overlay.LoadTransitionState; the layout is intentionally kept private to +// the core package so external callers go through these helpers. +var transitionRegistryBaseRootSlot = common.BytesToHash([]byte{5}) + +// InitializeBinaryTransitionRegistry deploys the binary transition registry +// system contract and marks the transition as started by writing 1 into slot +// 0. It must be called exactly once, on the first block after the UBT +// activation. +func InitializeBinaryTransitionRegistry(statedb *state.StateDB) { + statedb.SetCode(params.BinaryTransitionRegistryAddress, transitionStatusByteCode, tracing.CodeChangeUnspecified) + statedb.SetNonce(params.BinaryTransitionRegistryAddress, 1, tracing.NonceChangeUnspecified) + statedb.SetState(params.BinaryTransitionRegistryAddress, common.Hash{}, common.Hash{1}) +} + +// WriteBinaryTransitionBaseRoot records the frozen MPT base root in slot 5 of +// the transition registry. This must be called on the first UBT block, right +// after InitializeBinaryTransitionRegistry, with the parent block's state +// root. +func WriteBinaryTransitionBaseRoot(statedb *state.StateDB, baseRoot common.Hash) { + statedb.SetState(params.BinaryTransitionRegistryAddress, transitionRegistryBaseRootSlot, baseRoot) +} diff --git a/miner/worker.go b/miner/worker.go index ae5d6c306f..4598083151 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -332,6 +332,13 @@ func (miner *Miner) prepareWork(ctx context.Context, genParams *generateParams, if miner.chainConfig.IsPrague(header.Number, header.Time) { core.ProcessParentBlockHash(header.ParentHash, env.evm) } + // Deploy the binary transition registry on the first UBT block and seed + // it with the parent's MPT root. Subsequent blocks read the registry to + // reconstruct the transition state. + if miner.chainConfig.IsUBT(header.Number, header.Time) && !miner.chainConfig.IsUBT(parent.Number, parent.Time) { + core.InitializeBinaryTransitionRegistry(env.state) + core.WriteBinaryTransitionBaseRoot(env.state, parent.Root) + } return env, nil }