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 }