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.
This commit is contained in:
Guillaume Ballet 2026-04-29 16:46:48 +02:00
parent 40c29ad53a
commit 58518ea2b3
No known key found for this signature in database
4 changed files with 87 additions and 0 deletions

View file

@ -399,6 +399,13 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
ProcessParentBlockHash(b.header.ParentHash, evm) 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 // Execute any user modifications to the block
if gen != nil { if gen != nil {
gen(i, b) gen(i, b)

View file

@ -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()) { if config.IsPrague(block.Number(), block.Time()) || config.IsUBT(block.Number(), block.Time()) {
ProcessParentBlockHash(block.ParentHash(), evm) 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 // Iterate over and process the individual transactions
for i, tx := range block.Transactions() { for i, tx := range block.Transactions() {

View file

@ -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 <http://www.gnu.org/licenses/>.
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)
}

View file

@ -332,6 +332,13 @@ func (miner *Miner) prepareWork(ctx context.Context, genParams *generateParams,
if miner.chainConfig.IsPrague(header.Number, header.Time) { if miner.chainConfig.IsPrague(header.Number, header.Time) {
core.ProcessParentBlockHash(header.ParentHash, env.evm) 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 return env, nil
} }