mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-08 07:58:40 +00:00
Merge b3d3dd70f5 into 68646229a0
This commit is contained in:
commit
506235e173
16 changed files with 536 additions and 169 deletions
107
core/bintrie_transition_test.go
Normal file
107
core/bintrie_transition_test.go
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
// 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 (
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/consensus/beacon"
|
||||||
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestBinaryTransitionRegistryBootstrap exercises the registry deployment on
|
||||||
|
// the first UBT block: pre-fork blocks must leave the registry uninitialised,
|
||||||
|
// the first UBT block must mark started=true and the base root must be
|
||||||
|
// captured for every subsequent UBT block.
|
||||||
|
func TestBinaryTransitionRegistryBootstrap(t *testing.T) {
|
||||||
|
var (
|
||||||
|
ubtTime uint64 = 30
|
||||||
|
coinbase = common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7")
|
||||||
|
gspec = &Genesis{
|
||||||
|
Config: ¶ms.ChainConfig{
|
||||||
|
ChainID: big.NewInt(1),
|
||||||
|
HomesteadBlock: big.NewInt(0),
|
||||||
|
EIP150Block: big.NewInt(0),
|
||||||
|
EIP155Block: big.NewInt(0),
|
||||||
|
EIP158Block: big.NewInt(0),
|
||||||
|
ByzantiumBlock: big.NewInt(0),
|
||||||
|
ConstantinopleBlock: big.NewInt(0),
|
||||||
|
PetersburgBlock: big.NewInt(0),
|
||||||
|
IstanbulBlock: big.NewInt(0),
|
||||||
|
MuirGlacierBlock: big.NewInt(0),
|
||||||
|
BerlinBlock: big.NewInt(0),
|
||||||
|
LondonBlock: big.NewInt(0),
|
||||||
|
Ethash: new(params.EthashConfig),
|
||||||
|
ShanghaiTime: u64(0),
|
||||||
|
CancunTime: u64(0),
|
||||||
|
PragueTime: u64(0),
|
||||||
|
UBTTime: &ubtTime,
|
||||||
|
TerminalTotalDifficulty: common.Big0,
|
||||||
|
EnableUBTAtGenesis: false,
|
||||||
|
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
|
||||||
|
Cancun: params.DefaultCancunBlobConfig,
|
||||||
|
Prague: params.DefaultPragueBlobConfig,
|
||||||
|
UBT: params.DefaultPragueBlobConfig,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Alloc: GenesisAlloc{
|
||||||
|
coinbase: {
|
||||||
|
Balance: big.NewInt(1000000000000000000),
|
||||||
|
},
|
||||||
|
params.BeaconRootsAddress: {Nonce: 1, Code: params.BeaconRootsCode, Balance: common.Big0},
|
||||||
|
params.HistoryStorageAddress: {Nonce: 1, Code: params.HistoryStorageCode, Balance: common.Big0},
|
||||||
|
params.WithdrawalQueueAddress: {Nonce: 1, Code: params.WithdrawalQueueCode, Balance: common.Big0},
|
||||||
|
params.ConsolidationQueueAddress: {Nonce: 1, Code: params.ConsolidationQueueCode, Balance: common.Big0},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
config := gspec.Config
|
||||||
|
engine := beacon.New(ethash.NewFaker())
|
||||||
|
|
||||||
|
registryAddr := params.BinaryTransitionRegistryAddress
|
||||||
|
slotStarted := common.Hash{}
|
||||||
|
slotBaseRoot := common.BytesToHash([]byte{5})
|
||||||
|
|
||||||
|
GenerateChainWithGenesis(gspec, engine, 6, func(i int, gen *BlockGen) {
|
||||||
|
gen.SetPoS()
|
||||||
|
|
||||||
|
blockNum := gen.Number()
|
||||||
|
blockTime := gen.Timestamp()
|
||||||
|
isUBT := config.IsUBT(blockNum, blockTime)
|
||||||
|
|
||||||
|
started := gen.GetState(registryAddr, slotStarted)
|
||||||
|
baseRoot := gen.GetState(registryAddr, slotBaseRoot)
|
||||||
|
t.Logf("block %d: num=%d time=%d isUBT=%v started=%x baseRoot=%x",
|
||||||
|
i, blockNum.Uint64(), blockTime, isUBT, started, baseRoot)
|
||||||
|
|
||||||
|
if !isUBT {
|
||||||
|
if started != (common.Hash{}) {
|
||||||
|
t.Errorf("block %d: pre-transition block should not have registry initialized", i)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if started == (common.Hash{}) {
|
||||||
|
t.Errorf("block %d: UBT block should have registry slot 0 (started) set", i)
|
||||||
|
}
|
||||||
|
if baseRoot == (common.Hash{}) {
|
||||||
|
t.Errorf("block %d: UBT block should have registry slot 5 (base root) set", i)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -38,6 +38,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
||||||
"github.com/ethereum/go-ethereum/core/history"
|
"github.com/ethereum/go-ethereum/core/history"
|
||||||
|
"github.com/ethereum/go-ethereum/core/overlay"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
||||||
|
|
@ -257,6 +258,13 @@ func (cfg BlockChainConfig) WithNoAsyncFlush(on bool) *BlockChainConfig {
|
||||||
return &cfg
|
return &cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newBinaryTrieDB opens the sibling triedb that holds binary trie nodes
|
||||||
|
// during the MPT-to-binary transition. It uses the same scheme and limits as
|
||||||
|
// the main triedb but is forced into UBT mode.
|
||||||
|
func newBinaryTrieDB(db ethdb.Database, cfg *BlockChainConfig) *triedb.Database {
|
||||||
|
return triedb.NewDatabase(db, cfg.triedbConfig(true))
|
||||||
|
}
|
||||||
|
|
||||||
// triedbConfig derives the configures for trie database.
|
// triedbConfig derives the configures for trie database.
|
||||||
func (cfg *BlockChainConfig) triedbConfig(isUBT bool) *triedb.Config {
|
func (cfg *BlockChainConfig) triedbConfig(isUBT bool) *triedb.Config {
|
||||||
config := &triedb.Config{
|
config := &triedb.Config{
|
||||||
|
|
@ -323,6 +331,7 @@ type BlockChain struct {
|
||||||
lastWrite uint64 // Last block when the state was flushed
|
lastWrite uint64 // Last block when the state was flushed
|
||||||
flushInterval atomic.Int64 // Time interval (processing time) after which to flush a state
|
flushInterval atomic.Int64 // Time interval (processing time) after which to flush a state
|
||||||
triedb *triedb.Database // The database handler for maintaining trie nodes.
|
triedb *triedb.Database // The database handler for maintaining trie nodes.
|
||||||
|
bintriedb *triedb.Database // Sibling triedb for binary trie nodes during the MPT-to-binary transition; nil if no UBT fork is configured or the chain is binary at genesis.
|
||||||
codedb *state.CodeDB // The database handler for maintaining contract codes.
|
codedb *state.CodeDB // The database handler for maintaining contract codes.
|
||||||
txIndexer *txIndexer // Transaction indexer, might be nil if not enabled
|
txIndexer *txIndexer // Transaction indexer, might be nil if not enabled
|
||||||
|
|
||||||
|
|
@ -382,16 +391,26 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
triedb := triedb.NewDatabase(db, cfg.triedbConfig(enableVerkle))
|
mainTriedb := triedb.NewDatabase(db, cfg.triedbConfig(enableVerkle))
|
||||||
|
|
||||||
|
// Open a sibling binary trie database when the chain is configured for an
|
||||||
|
// MPT-to-binary transition (i.e. UBTTime is set and the chain did not start
|
||||||
|
// binary at genesis). The two databases run in parallel during the
|
||||||
|
// transition window: writes target the binary trie, reads fall through to
|
||||||
|
// the frozen MPT base resolved from the transition registry.
|
||||||
|
var bintriedb *triedb.Database
|
||||||
|
|
||||||
// Write the supplied genesis to the database if it has not been initialized
|
// Write the supplied genesis to the database if it has not been initialized
|
||||||
// yet. The corresponding chain config will be returned, either from the
|
// yet. The corresponding chain config will be returned, either from the
|
||||||
// provided genesis or from the locally stored configuration if the genesis
|
// provided genesis or from the locally stored configuration if the genesis
|
||||||
// has already been initialized.
|
// has already been initialized.
|
||||||
chainConfig, genesisHash, compatErr, err := SetupGenesisBlockWithOverride(db, triedb, genesis, cfg.Overrides, cfg.VmConfig.Tracer)
|
chainConfig, genesisHash, compatErr, err := SetupGenesisBlockWithOverride(db, mainTriedb, genesis, cfg.Overrides, cfg.VmConfig.Tracer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if !enableVerkle && chainConfig.UBTTime != nil {
|
||||||
|
bintriedb = newBinaryTrieDB(db, cfg)
|
||||||
|
}
|
||||||
log.Info("")
|
log.Info("")
|
||||||
log.Info(strings.Repeat("-", 153))
|
log.Info(strings.Repeat("-", 153))
|
||||||
for _, line := range strings.Split(chainConfig.Description(), "\n") {
|
for _, line := range strings.Split(chainConfig.Description(), "\n") {
|
||||||
|
|
@ -404,7 +423,8 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine,
|
||||||
chainConfig: chainConfig,
|
chainConfig: chainConfig,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
db: db,
|
db: db,
|
||||||
triedb: triedb,
|
triedb: mainTriedb,
|
||||||
|
bintriedb: bintriedb,
|
||||||
codedb: state.NewCodeDB(db),
|
codedb: state.NewCodeDB(db),
|
||||||
triegc: prque.New[int64, common.Hash](nil),
|
triegc: prque.New[int64, common.Hash](nil),
|
||||||
chainmu: syncx.NewClosableMutex(),
|
chainmu: syncx.NewClosableMutex(),
|
||||||
|
|
@ -1349,6 +1369,11 @@ func (bc *BlockChain) Stop() {
|
||||||
if err := bc.triedb.Journal(bc.CurrentBlock().Root); err != nil {
|
if err := bc.triedb.Journal(bc.CurrentBlock().Root); err != nil {
|
||||||
log.Info("Failed to journal in-memory trie nodes", "err", err)
|
log.Info("Failed to journal in-memory trie nodes", "err", err)
|
||||||
}
|
}
|
||||||
|
if bc.bintriedb != nil {
|
||||||
|
if err := bc.bintriedb.Journal(bc.CurrentBlock().Root); err != nil {
|
||||||
|
log.Info("Failed to journal in-memory binary trie nodes", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Ensure the state of a recent block is also stored to disk before exiting.
|
// Ensure the state of a recent block is also stored to disk before exiting.
|
||||||
// We're writing three different states to catch different restart scenarios:
|
// We're writing three different states to catch different restart scenarios:
|
||||||
|
|
@ -1390,6 +1415,11 @@ func (bc *BlockChain) Stop() {
|
||||||
if err := bc.triedb.Close(); err != nil {
|
if err := bc.triedb.Close(); err != nil {
|
||||||
log.Error("Failed to close trie database", "err", err)
|
log.Error("Failed to close trie database", "err", err)
|
||||||
}
|
}
|
||||||
|
if bc.bintriedb != nil {
|
||||||
|
if err := bc.bintriedb.Close(); err != nil {
|
||||||
|
log.Error("Failed to close binary trie database", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
log.Info("Blockchain stopped")
|
log.Info("Blockchain stopped")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2108,6 +2138,72 @@ type ExecuteConfig struct {
|
||||||
EnableWitnessStats bool
|
EnableWitnessStats bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stateDatabase returns the appropriate state.Database for executing a block
|
||||||
|
// with the given header against the supplied parent root. The routing is:
|
||||||
|
//
|
||||||
|
// - pre-UBT-fork blocks → MPTDatabase backed by bc.triedb.
|
||||||
|
// - first UBT block (parent is pre-UBT) → transition UBTDatabase, with the
|
||||||
|
// binary trie as primary store and the parent's MPT root as the frozen
|
||||||
|
// base. The MPT triedb is provided for read-through during the transition.
|
||||||
|
// - subsequent UBT blocks → if the registry still reports an active
|
||||||
|
// transition, a transition UBTDatabase is built; otherwise (transition
|
||||||
|
// ended, or chainConfig.UBTTransitionEndTime crossed) a plain UBTDatabase
|
||||||
|
// is returned with the transition wrap disabled.
|
||||||
|
func (bc *BlockChain) stateDatabase(parentRoot common.Hash, header *types.Header) (state.Database, error) {
|
||||||
|
if !bc.chainConfig.IsUBT(header.Number, header.Time) {
|
||||||
|
return state.NewMPTDatabase(bc.triedb, bc.codedb).WithSnapshot(bc.snaps), nil
|
||||||
|
}
|
||||||
|
// Past the configured transition end: pure binary, no wrap, no MPT base.
|
||||||
|
if !bc.chainConfig.UBTTransitionActive(header.Number, header.Time) {
|
||||||
|
return state.NewUBTDatabase(bc.bintriedbOrMain(), bc.codedb).WithTransitionTreeWrap(false), nil
|
||||||
|
}
|
||||||
|
// First UBT block: parent is pre-UBT; seed the transition with parent root.
|
||||||
|
parent := bc.GetHeaderByHash(header.ParentHash)
|
||||||
|
if parent != nil && !bc.chainConfig.IsUBT(parent.Number, parent.Time) {
|
||||||
|
return state.NewTransitionUBTDatabase(bc.bintriedbOrMain(), bc.triedb, bc.codedb, parentRoot), nil
|
||||||
|
}
|
||||||
|
// Subsequent UBT block while the transition is active: probe the registry
|
||||||
|
// to see whether a base root is still recorded.
|
||||||
|
return bc.probeTransitionDatabase(parentRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bintriedbOrMain returns bc.bintriedb when present, otherwise the main triedb.
|
||||||
|
// Binary-at-genesis chains have no sibling binary triedb because the main
|
||||||
|
// triedb is already in UBT mode.
|
||||||
|
func (bc *BlockChain) bintriedbOrMain() *triedb.Database {
|
||||||
|
if bc.bintriedb != nil {
|
||||||
|
return bc.bintriedb
|
||||||
|
}
|
||||||
|
return bc.triedb
|
||||||
|
}
|
||||||
|
|
||||||
|
// probeTransitionDatabase reads the transition registry from the binary trie
|
||||||
|
// at the given root and chooses between a transition-mode UBTDatabase (if
|
||||||
|
// the registry still records a base root) and a plain UBTDatabase otherwise.
|
||||||
|
func (bc *BlockChain) probeTransitionDatabase(root common.Hash) (state.Database, error) {
|
||||||
|
bindb := bc.bintriedbOrMain()
|
||||||
|
plain := state.NewUBTDatabase(bindb, bc.codedb)
|
||||||
|
reader, err := plain.StateReader(root)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ts, err := overlay.LoadTransitionState(storageReaderFunc(reader.Storage))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if ts == nil || ts.Transitioned() || ts.BaseRoot == (common.Hash{}) {
|
||||||
|
return plain, nil
|
||||||
|
}
|
||||||
|
return state.NewTransitionUBTDatabase(bindb, bc.triedb, bc.codedb, ts.BaseRoot), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// storageReaderFunc adapts a state-reader Storage method to overlay.StorageReader.
|
||||||
|
type storageReaderFunc func(addr common.Address, slot common.Hash) (common.Hash, error)
|
||||||
|
|
||||||
|
func (f storageReaderFunc) Storage(addr common.Address, slot common.Hash) (common.Hash, error) {
|
||||||
|
return f(addr, slot)
|
||||||
|
}
|
||||||
|
|
||||||
// ProcessBlock executes and validates the given block. If there was no error
|
// ProcessBlock executes and validates the given block. If there was no error
|
||||||
// it writes the block and associated state to database.
|
// it writes the block and associated state to database.
|
||||||
func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash, block *types.Block, config ExecuteConfig) (result *blockProcessingResult, blockEndErr error) {
|
func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash, block *types.Block, config ExecuteConfig) (result *blockProcessingResult, blockEndErr error) {
|
||||||
|
|
@ -2116,14 +2212,12 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
||||||
startTime = time.Now()
|
startTime = time.Now()
|
||||||
statedb *state.StateDB
|
statedb *state.StateDB
|
||||||
interrupt atomic.Bool
|
interrupt atomic.Bool
|
||||||
sdb state.Database
|
|
||||||
)
|
)
|
||||||
defer interrupt.Store(true) // terminate the prefetch at the end
|
defer interrupt.Store(true) // terminate the prefetch at the end
|
||||||
|
|
||||||
if bc.chainConfig.IsUBT(block.Number(), block.Time()) {
|
sdb, err := bc.stateDatabase(parentRoot, block.Header())
|
||||||
sdb = state.NewUBTDatabase(bc.triedb, bc.codedb)
|
if err != nil {
|
||||||
} else {
|
return nil, err
|
||||||
sdb = state.NewMPTDatabase(bc.triedb, bc.codedb).WithSnapshot(bc.snaps)
|
|
||||||
}
|
}
|
||||||
// If prefetching is enabled, run that against the current state to pre-cache
|
// If prefetching is enabled, run that against the current state to pre-cache
|
||||||
// transactions and probabilistically some of the account/storage trie nodes.
|
// transactions and probabilistically some of the account/storage trie nodes.
|
||||||
|
|
|
||||||
|
|
@ -421,27 +421,21 @@ func (bc *BlockChain) State() (*state.StateDB, error) {
|
||||||
|
|
||||||
// StateAt returns a new mutable state based on a particular point in time.
|
// StateAt returns a new mutable state based on a particular point in time.
|
||||||
func (bc *BlockChain) StateAt(header *types.Header) (*state.StateDB, error) {
|
func (bc *BlockChain) StateAt(header *types.Header) (*state.StateDB, error) {
|
||||||
if bc.chainConfig.IsUBT(header.Number, header.Time) {
|
db, err := bc.stateDatabase(header.Root, header)
|
||||||
return state.New(header.Root, state.NewUBTDatabase(bc.triedb, bc.codedb))
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
return state.New(header.Root, state.NewMPTDatabase(bc.triedb, bc.codedb).WithSnapshot(bc.snaps))
|
return state.New(header.Root, db)
|
||||||
}
|
}
|
||||||
|
|
||||||
// StateAtForkBoundary returns a new mutable state based on the parent state
|
// StateAtForkBoundary returns a new mutable state based on the parent state
|
||||||
// and the given header, handling the transition across the UBT fork.
|
// and the given header, handling the transition across the UBT fork.
|
||||||
func (bc *BlockChain) StateAtForkBoundary(parent *types.Header, header *types.Header) (*state.StateDB, error) {
|
func (bc *BlockChain) StateAtForkBoundary(parent *types.Header, header *types.Header) (*state.StateDB, error) {
|
||||||
// The parent is already in the UBT fork.
|
db, err := bc.stateDatabase(parent.Root, header)
|
||||||
if bc.chainConfig.IsUBT(parent.Number, parent.Time) {
|
if err != nil {
|
||||||
return state.New(parent.Root, state.NewUBTDatabase(bc.triedb, bc.codedb))
|
return nil, err
|
||||||
}
|
}
|
||||||
// The current block is the first block in the UBT fork
|
return state.New(parent.Root, db)
|
||||||
// (i.e., the parent is the last MPT block).
|
|
||||||
if bc.chainConfig.IsUBT(header.Number, header.Time) {
|
|
||||||
// TODO(gballet): register chain context if needed
|
|
||||||
return state.New(parent.Root, state.NewUBTDatabase(bc.triedb, bc.codedb))
|
|
||||||
}
|
|
||||||
// Both the parent and current block are in the MPT fork.
|
|
||||||
return state.New(parent.Root, state.NewMPTDatabase(bc.triedb, bc.codedb).WithSnapshot(bc.snaps))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HistoricState returns a historic state specified by the given header.
|
// HistoricState returns a historic state specified by the given header.
|
||||||
|
|
|
||||||
|
|
@ -172,6 +172,14 @@ func (b *BlockGen) GetBalance(addr common.Address) *uint256.Int {
|
||||||
return b.statedb.GetBalance(addr)
|
return b.statedb.GetBalance(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetState returns the storage slot value for the given address and key as
|
||||||
|
// observed by the in-progress block generator. Useful in tests that need to
|
||||||
|
// inspect system-contract slots written during block production (e.g. the
|
||||||
|
// binary transition registry).
|
||||||
|
func (b *BlockGen) GetState(addr common.Address, key common.Hash) common.Hash {
|
||||||
|
return b.statedb.GetState(addr, key)
|
||||||
|
}
|
||||||
|
|
||||||
// AddUncheckedTx forcefully adds a transaction to the block without any validation.
|
// AddUncheckedTx forcefully adds a transaction to the block without any validation.
|
||||||
//
|
//
|
||||||
// AddUncheckedTx will cause consensus failures when used during real
|
// AddUncheckedTx will cause consensus failures when used during real
|
||||||
|
|
@ -399,6 +407,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)
|
||||||
|
|
|
||||||
|
|
@ -276,10 +276,11 @@ func (e *GenesisMismatchError) Error() string {
|
||||||
|
|
||||||
// ChainOverrides contains the changes to chain config.
|
// ChainOverrides contains the changes to chain config.
|
||||||
type ChainOverrides struct {
|
type ChainOverrides struct {
|
||||||
OverrideOsaka *uint64
|
OverrideOsaka *uint64
|
||||||
OverrideBPO1 *uint64
|
OverrideBPO1 *uint64
|
||||||
OverrideBPO2 *uint64
|
OverrideBPO2 *uint64
|
||||||
OverrideUBT *uint64
|
OverrideUBT *uint64
|
||||||
|
OverrideUBTTransitionEnd *uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply applies the chain overrides on the supplied chain config.
|
// apply applies the chain overrides on the supplied chain config.
|
||||||
|
|
@ -299,6 +300,9 @@ func (o *ChainOverrides) apply(cfg *params.ChainConfig) error {
|
||||||
if o.OverrideUBT != nil {
|
if o.OverrideUBT != nil {
|
||||||
cfg.UBTTime = o.OverrideUBT
|
cfg.UBTTime = o.OverrideUBT
|
||||||
}
|
}
|
||||||
|
if o.OverrideUBTTransitionEnd != nil {
|
||||||
|
cfg.UBTTransitionEndTime = o.OverrideUBTTransitionEnd
|
||||||
|
}
|
||||||
return cfg.CheckConfigForkOrder()
|
return cfg.CheckConfigForkOrder()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,29 +17,39 @@
|
||||||
package overlay
|
package overlay
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/gob"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Storage slots used by the binary transition registry system contract at
|
||||||
|
// params.BinaryTransitionRegistryAddress.
|
||||||
|
var (
|
||||||
|
transitionStartedKey = common.Hash{}
|
||||||
|
conversionProgressAddressKey = common.BytesToHash([]byte{1})
|
||||||
|
conversionProgressSlotKey = common.BytesToHash([]byte{2})
|
||||||
|
conversionProgressStorageProcessed = common.BytesToHash([]byte{3})
|
||||||
|
transitionEndedKey = common.BytesToHash([]byte{4})
|
||||||
|
baseRootKey = common.BytesToHash([]byte{5})
|
||||||
|
)
|
||||||
|
|
||||||
|
// StorageReader is a minimal interface for reading contract storage slots.
|
||||||
|
type StorageReader interface {
|
||||||
|
Storage(addr common.Address, slot common.Hash) (common.Hash, error)
|
||||||
|
}
|
||||||
|
|
||||||
// TransitionState is a structure that holds the progress markers of the
|
// TransitionState is a structure that holds the progress markers of the
|
||||||
// translation process.
|
// translation process.
|
||||||
type TransitionState struct {
|
type TransitionState struct {
|
||||||
CurrentAccountAddress *common.Address // addresss of the last translated account
|
CurrentAccountAddress *common.Address // address of the last translated account
|
||||||
CurrentSlotHash common.Hash // hash of the last translated storage slot
|
CurrentSlotHash common.Hash // hash of the last translated storage slot
|
||||||
CurrentPreimageOffset int64 // next byte to read from the preimage file
|
|
||||||
Started, Ended bool
|
Started, Ended bool
|
||||||
|
|
||||||
// Mark whether the storage for an account has been processed. This is useful if the
|
// StorageProcessed marks whether the storage of the current account has
|
||||||
// maximum number of leaves of the conversion is reached before the whole storage is
|
// been fully processed. Useful when the maximum number of leaves of the
|
||||||
// processed.
|
// conversion is reached before the storage is exhausted.
|
||||||
StorageProcessed bool
|
StorageProcessed bool
|
||||||
|
|
||||||
BaseRoot common.Hash // hash of the last read-only MPT base tree
|
BaseRoot common.Hash // frozen MPT base root captured at the fork block
|
||||||
}
|
}
|
||||||
|
|
||||||
// InTransition returns true if the translation process is in progress.
|
// InTransition returns true if the translation process is in progress.
|
||||||
|
|
@ -55,12 +65,11 @@ func (ts *TransitionState) Transitioned() bool {
|
||||||
// Copy returns a deep copy of the TransitionState object.
|
// Copy returns a deep copy of the TransitionState object.
|
||||||
func (ts *TransitionState) Copy() *TransitionState {
|
func (ts *TransitionState) Copy() *TransitionState {
|
||||||
ret := &TransitionState{
|
ret := &TransitionState{
|
||||||
Started: ts.Started,
|
Started: ts.Started,
|
||||||
Ended: ts.Ended,
|
Ended: ts.Ended,
|
||||||
CurrentSlotHash: ts.CurrentSlotHash,
|
CurrentSlotHash: ts.CurrentSlotHash,
|
||||||
CurrentPreimageOffset: ts.CurrentPreimageOffset,
|
StorageProcessed: ts.StorageProcessed,
|
||||||
StorageProcessed: ts.StorageProcessed,
|
BaseRoot: ts.BaseRoot,
|
||||||
BaseRoot: ts.BaseRoot,
|
|
||||||
}
|
}
|
||||||
if ts.CurrentAccountAddress != nil {
|
if ts.CurrentAccountAddress != nil {
|
||||||
addr := *ts.CurrentAccountAddress
|
addr := *ts.CurrentAccountAddress
|
||||||
|
|
@ -69,38 +78,54 @@ func (ts *TransitionState) Copy() *TransitionState {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadTransitionState retrieves the Verkle transition state associated with
|
// IsTransitionActive checks whether the binary transition registry has been
|
||||||
// the given state root hash from the database.
|
// initialised by reading slot 0 (started) from the system contract.
|
||||||
func LoadTransitionState(db ethdb.KeyValueReader, root common.Hash, isUBT bool) *TransitionState {
|
func IsTransitionActive(reader StorageReader) bool {
|
||||||
var ts *TransitionState
|
val, err := reader.Storage(params.BinaryTransitionRegistryAddress, transitionStartedKey)
|
||||||
|
if err != nil {
|
||||||
data, _ := rawdb.ReadVerkleTransitionState(db, root)
|
return false
|
||||||
|
|
||||||
// if a state could be read from the db, attempt to decode it
|
|
||||||
if len(data) > 0 {
|
|
||||||
var (
|
|
||||||
newts TransitionState
|
|
||||||
buf = bytes.NewBuffer(data[:])
|
|
||||||
dec = gob.NewDecoder(buf)
|
|
||||||
)
|
|
||||||
// Decode transition state
|
|
||||||
err := dec.Decode(&newts)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("failed to decode transition state", "err", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
ts = &newts
|
|
||||||
}
|
}
|
||||||
|
return val != (common.Hash{})
|
||||||
// Fallback that should only happen before the transition
|
}
|
||||||
if ts == nil {
|
|
||||||
// Initialize the first transition state, with the "ended"
|
// LoadTransitionState reads the full transition state from the binary
|
||||||
// field set to true if the database was created
|
// transition registry system contract storage. Returns nil when the
|
||||||
// as a verkle database.
|
// registry has not been initialised (i.e. the chain has not yet reached the
|
||||||
log.Debug("no transition state found, starting fresh", "verkle", isUBT)
|
// UBT fork block).
|
||||||
|
func LoadTransitionState(reader StorageReader) (*TransitionState, error) {
|
||||||
// Start with a fresh state
|
started, err := reader.Storage(params.BinaryTransitionRegistryAddress, transitionStartedKey)
|
||||||
ts = &TransitionState{Ended: isUBT}
|
if err != nil {
|
||||||
}
|
return nil, err
|
||||||
return ts
|
}
|
||||||
|
if started == (common.Hash{}) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ended, _ := reader.Storage(params.BinaryTransitionRegistryAddress, transitionEndedKey)
|
||||||
|
baseRoot, _ := reader.Storage(params.BinaryTransitionRegistryAddress, baseRootKey)
|
||||||
|
|
||||||
|
var currentAddr *common.Address
|
||||||
|
addrVal, _ := reader.Storage(params.BinaryTransitionRegistryAddress, conversionProgressAddressKey)
|
||||||
|
if addrVal != (common.Hash{}) {
|
||||||
|
addr := common.BytesToAddress(addrVal.Bytes())
|
||||||
|
currentAddr = &addr
|
||||||
|
}
|
||||||
|
|
||||||
|
slotHash, err := reader.Storage(params.BinaryTransitionRegistryAddress, conversionProgressSlotKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
storageProcessed, err := reader.Storage(params.BinaryTransitionRegistryAddress, conversionProgressStorageProcessed)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TransitionState{
|
||||||
|
Started: true,
|
||||||
|
Ended: ended != (common.Hash{}),
|
||||||
|
BaseRoot: baseRoot,
|
||||||
|
CurrentAccountAddress: currentAddr,
|
||||||
|
CurrentSlotHash: slotHash,
|
||||||
|
StorageProcessed: storageProcessed != (common.Hash{}),
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
// Copyright 2025 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 rawdb
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ReadVerkleTransitionState(db ethdb.KeyValueReader, hash common.Hash) ([]byte, error) {
|
|
||||||
return db.Get(transitionStateKey(hash))
|
|
||||||
}
|
|
||||||
|
|
||||||
func WriteVerkleTransitionState(db ethdb.KeyValueWriter, hash common.Hash, state []byte) error {
|
|
||||||
return db.Put(transitionStateKey(hash), state)
|
|
||||||
}
|
|
||||||
|
|
@ -691,7 +691,7 @@ var knownMetadataKeys = [][]byte{
|
||||||
snapshotGeneratorKey, snapshotRecoveryKey, txIndexTailKey, fastTxLookupLimitKey,
|
snapshotGeneratorKey, snapshotRecoveryKey, txIndexTailKey, fastTxLookupLimitKey,
|
||||||
uncleanShutdownKey, badBlockKey, transitionStatusKey, skeletonSyncStatusKey,
|
uncleanShutdownKey, badBlockKey, transitionStatusKey, skeletonSyncStatusKey,
|
||||||
persistentStateIDKey, trieJournalKey, snapshotSyncStatusKey, snapSyncStatusFlagKey,
|
persistentStateIDKey, trieJournalKey, snapshotSyncStatusKey, snapSyncStatusFlagKey,
|
||||||
filterMapsRangeKey, headStateHistoryIndexKey, headTrienodeHistoryIndexKey, VerkleTransitionStatePrefix,
|
filterMapsRangeKey, headStateHistoryIndexKey, headTrienodeHistoryIndexKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
// printChainMetadata prints out chain metadata to stderr.
|
// printChainMetadata prints out chain metadata to stderr.
|
||||||
|
|
|
||||||
|
|
@ -165,9 +165,6 @@ var (
|
||||||
preimageCounter = metrics.NewRegisteredCounter("db/preimage/total", nil)
|
preimageCounter = metrics.NewRegisteredCounter("db/preimage/total", nil)
|
||||||
preimageHitsCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil)
|
preimageHitsCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil)
|
||||||
preimageMissCounter = metrics.NewRegisteredCounter("db/preimage/miss", nil)
|
preimageMissCounter = metrics.NewRegisteredCounter("db/preimage/miss", nil)
|
||||||
|
|
||||||
// Verkle transition information
|
|
||||||
VerkleTransitionStatePrefix = []byte("verkle-transition-state-")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// LegacyTxLookupEntry is the legacy TxLookupEntry definition with some unnecessary
|
// LegacyTxLookupEntry is the legacy TxLookupEntry definition with some unnecessary
|
||||||
|
|
@ -460,8 +457,3 @@ func trienodeHistoryIndexBlockKey(addressHash common.Hash, path []byte, blockID
|
||||||
|
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// transitionStateKey = transitionStatusKey + hash
|
|
||||||
func transitionStateKey(hash common.Hash) []byte {
|
|
||||||
return append(VerkleTransitionStatePrefix, hash.Bytes()...)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -17,18 +17,25 @@
|
||||||
package state
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
"github.com/ethereum/go-ethereum/trie/bintrie"
|
"github.com/ethereum/go-ethereum/trie/bintrie"
|
||||||
|
"github.com/ethereum/go-ethereum/trie/transitiontrie"
|
||||||
"github.com/ethereum/go-ethereum/triedb"
|
"github.com/ethereum/go-ethereum/triedb"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UBTDatabase is an implementation of Database interface for Unified Binary Trie.
|
// UBTDatabase is an implementation of Database interface for Unified Binary Trie.
|
||||||
// It provides the same functionality as MPTDatabase but uses unified binary
|
|
||||||
// trie for state hashing instead of Merkle Patricia Tries.
|
|
||||||
type UBTDatabase struct {
|
type UBTDatabase struct {
|
||||||
triedb *triedb.Database
|
triedb *triedb.Database
|
||||||
codedb *CodeDB
|
mpttriedb *triedb.Database
|
||||||
|
codedb *CodeDB
|
||||||
|
baseRoot common.Hash
|
||||||
|
wrapInTransitionTrie bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type returns Binary, indicating this database is backed by a Universal Binary Trie.
|
// Type returns Binary, indicating this database is backed by a Universal Binary Trie.
|
||||||
|
|
@ -40,11 +47,38 @@ func NewUBTDatabase(triedb *triedb.Database, codedb *CodeDB) *UBTDatabase {
|
||||||
codedb = NewCodeDB(triedb.Disk())
|
codedb = NewCodeDB(triedb.Disk())
|
||||||
}
|
}
|
||||||
return &UBTDatabase{
|
return &UBTDatabase{
|
||||||
triedb: triedb,
|
triedb: triedb,
|
||||||
codedb: codedb,
|
codedb: codedb,
|
||||||
|
wrapInTransitionTrie: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewTransitionUBTDatabase creates a UBTDatabase for the active MPT-to-binary
|
||||||
|
// transition window. The binary trie is the primary store; reads fall through
|
||||||
|
// to the frozen MPT at baseRoot via the supplied mpttriedb, and writes go
|
||||||
|
// only to the binary trie.
|
||||||
|
func NewTransitionUBTDatabase(bintriedb, mpttriedb *triedb.Database, codedb *CodeDB, baseRoot common.Hash) *UBTDatabase {
|
||||||
|
if codedb == nil {
|
||||||
|
codedb = NewCodeDB(bintriedb.Disk())
|
||||||
|
}
|
||||||
|
return &UBTDatabase{
|
||||||
|
triedb: bintriedb,
|
||||||
|
mpttriedb: mpttriedb,
|
||||||
|
codedb: codedb,
|
||||||
|
baseRoot: baseRoot,
|
||||||
|
wrapInTransitionTrie: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTransitionTreeWrap toggles whether reads at this database are wrapped
|
||||||
|
// in a TransitionTrie. Setting it to false disables the wrap regardless of
|
||||||
|
// the registry state and is intended for callers that have already crossed
|
||||||
|
// the configured UBTTransitionEndTime.
|
||||||
|
func (db *UBTDatabase) WithTransitionTreeWrap(wrap bool) *UBTDatabase {
|
||||||
|
db.wrapInTransitionTrie = wrap
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
// StateReader returns a state reader associated with the specified state root.
|
// StateReader returns a state reader associated with the specified state root.
|
||||||
func (db *UBTDatabase) StateReader(stateRoot common.Hash) (StateReader, error) {
|
func (db *UBTDatabase) StateReader(stateRoot common.Hash) (StateReader, error) {
|
||||||
var readers []StateReader
|
var readers []StateReader
|
||||||
|
|
@ -61,7 +95,7 @@ func (db *UBTDatabase) StateReader(stateRoot common.Hash) (StateReader, error) {
|
||||||
}
|
}
|
||||||
// Configure the trie reader, which is expected to be available as the
|
// Configure the trie reader, which is expected to be available as the
|
||||||
// gatekeeper unless the state is corrupted.
|
// gatekeeper unless the state is corrupted.
|
||||||
tr, err := newUBTTrieReader(stateRoot, db.triedb)
|
tr, err := newUBTTrieReader(stateRoot, db.triedb, db.mpttriedb, db.wrapInTransitionTrie)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -96,13 +130,30 @@ func (db *UBTDatabase) ReadersWithCacheStats(stateRoot common.Hash) (Reader, Rea
|
||||||
|
|
||||||
// OpenTrie opens the main account trie at a specific root hash.
|
// OpenTrie opens the main account trie at a specific root hash.
|
||||||
func (db *UBTDatabase) OpenTrie(root common.Hash) (Trie, error) {
|
func (db *UBTDatabase) OpenTrie(root common.Hash) (Trie, error) {
|
||||||
return bintrie.NewBinaryTrie(root, db.triedb)
|
bt, err := bintrie.NewBinaryTrie(root, db.triedb)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if db.mpttriedb == nil || db.baseRoot == (common.Hash{}) {
|
||||||
|
return transitiontrie.NewTransitionTrie(nil, bt, false), nil
|
||||||
|
}
|
||||||
|
base, err := trie.NewStateTrie(trie.StateTrieID(db.baseRoot), db.mpttriedb)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return transitiontrie.NewTransitionTrie(base, bt, false), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenStorageTrie opens the storage trie of an account. In binary trie mode,
|
// OpenStorageTrie opens the storage trie of an account. In binary trie mode
|
||||||
// all state objects share one unified trie, so the main trie is returned.
|
// the unified trie carries all state, so the main trie is reused.
|
||||||
func (db *UBTDatabase) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, self Trie) (Trie, error) {
|
func (db *UBTDatabase) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, self Trie) (Trie, error) {
|
||||||
return self, nil
|
if self != nil && self.IsUBT() {
|
||||||
|
return self, nil
|
||||||
|
}
|
||||||
|
if db.mpttriedb == nil {
|
||||||
|
return nil, errors.New("no MPT trie database available for storage trie outside the transition window")
|
||||||
|
}
|
||||||
|
return trie.NewStateTrie(trie.StorageTrieID(stateRoot, crypto.Keccak256Hash(address.Bytes()), root), db.mpttriedb)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrieDB retrieves any intermediate trie-node caching layer.
|
// TrieDB retrieves any intermediate trie-node caching layer.
|
||||||
|
|
@ -128,10 +179,17 @@ func (db *UBTDatabase) Commit(update *StateUpdate) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// On the first transition block, the originRoot is the MPT base root,
|
||||||
|
// but the binary trie's parent state is empty. Substitute the empty
|
||||||
|
// binary hash so triedb.Update doesn't reject the mismatch.
|
||||||
|
originRoot := update.OriginRoot
|
||||||
|
if db.mpttriedb != nil && originRoot == db.baseRoot {
|
||||||
|
originRoot = types.EmptyBinaryHash
|
||||||
|
}
|
||||||
// Encode the state mutations in the UBT format
|
// Encode the state mutations in the UBT format
|
||||||
accounts, accountOrigin, storages, storageOrigin := update.EncodeUBTState()
|
accounts, accountOrigin, storages, storageOrigin := update.EncodeUBTState()
|
||||||
|
|
||||||
return db.triedb.Update(update.Root, update.OriginRoot, update.BlockNumber, update.Nodes, &triedb.StateSet{
|
return db.triedb.Update(update.Root, originRoot, update.BlockNumber, update.Nodes, &triedb.StateSet{
|
||||||
Accounts: accounts,
|
Accounts: accounts,
|
||||||
AccountsOrigin: accountOrigin,
|
AccountsOrigin: accountOrigin,
|
||||||
Storages: storages,
|
Storages: storages,
|
||||||
|
|
|
||||||
|
|
@ -252,46 +252,57 @@ type ubtTrieReader struct {
|
||||||
lock sync.Mutex // Lock for protecting concurrent read
|
lock sync.Mutex // Lock for protecting concurrent read
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// binTrieStorageReader adapts a *bintrie.BinaryTrie to overlay.StorageReader
|
||||||
|
// so the transition state can be loaded directly from the binary trie at a
|
||||||
|
// specific state root.
|
||||||
|
type binTrieStorageReader struct {
|
||||||
|
tr *bintrie.BinaryTrie
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *binTrieStorageReader) Storage(addr common.Address, slot common.Hash) (common.Hash, error) {
|
||||||
|
val, err := r.tr.GetStorage(addr, slot.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
var out common.Hash
|
||||||
|
out.SetBytes(val)
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
// newUBTTrieReader constructs a Unified-binary-trie reader of the specific state.
|
// newUBTTrieReader constructs a Unified-binary-trie reader of the specific state.
|
||||||
// An error will be returned if the associated trie specified by root is not existent.
|
// An error will be returned if the associated trie specified by root is not existent.
|
||||||
func newUBTTrieReader(root common.Hash, db *triedb.Database) (*ubtTrieReader, error) {
|
//
|
||||||
binTrie, binErr := bintrie.NewBinaryTrie(root, db)
|
// When wrapInTransitionTrie is true, reads fall through to the frozen MPT
|
||||||
if binErr != nil {
|
// base whenever the transition registry has captured a non-zero base root
|
||||||
return nil, binErr
|
// in slot 5 and an MPT triedb is available. When wrapInTransitionTrie is
|
||||||
|
// false, the MPT base is never consulted regardless of registry state.
|
||||||
|
//
|
||||||
|
// Both branches still build a TransitionTrie because *bintrie.BinaryTrie
|
||||||
|
// cannot satisfy the state.Trie interface directly (import cycle between
|
||||||
|
// trie and trie/bintrie). When wrap is false the base argument is nil and
|
||||||
|
// the wrapper degenerates to a passthrough.
|
||||||
|
func newUBTTrieReader(root common.Hash, bindb *triedb.Database, mptdb *triedb.Database, wrapInTransitionTrie bool) (*ubtTrieReader, error) {
|
||||||
|
binTrie, err := bintrie.NewBinaryTrie(root, bindb)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
// Based on the transition status, determine if the overlay
|
var base *trie.StateTrie
|
||||||
// tree needs to be created, or if a single, target tree is
|
if wrapInTransitionTrie && mptdb != nil {
|
||||||
// to be picked.
|
ts, err := overlay.LoadTransitionState(&binTrieStorageReader{tr: binTrie})
|
||||||
var (
|
|
||||||
tr Trie
|
|
||||||
ts = overlay.LoadTransitionState(db.Disk(), root, true)
|
|
||||||
)
|
|
||||||
if ts.InTransition() {
|
|
||||||
mpt, err := trie.NewStateTrie(trie.StateTrieID(ts.BaseRoot), db)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tr = transitiontrie.NewTransitionTrie(mpt, binTrie, false)
|
if ts != nil && ts.BaseRoot != (common.Hash{}) {
|
||||||
} else {
|
base, err = trie.NewStateTrie(trie.StateTrieID(ts.BaseRoot), mptdb)
|
||||||
// HACK: Use TransitionTrie with nil base as a wrapper to make BinaryTrie
|
if err != nil {
|
||||||
// satisfy the Trie interface. This works around the import cycle between
|
return nil, err
|
||||||
// trie and trie/bintrie packages.
|
}
|
||||||
//
|
}
|
||||||
// TODO: In future PRs, refactor the package structure to avoid this hack:
|
|
||||||
// - Option 1: Move common interfaces (Trie, NodeIterator) to a separate
|
|
||||||
// package that both trie and trie/bintrie can import
|
|
||||||
// - Option 2: Create a factory function in the trie package that returns
|
|
||||||
// BinaryTrie as a Trie interface without direct import
|
|
||||||
// - Option 3: Move BinaryTrie to the main trie package
|
|
||||||
//
|
|
||||||
// The current approach works but adds unnecessary overhead and complexity
|
|
||||||
// by using TransitionTrie when there's no actual transition happening.
|
|
||||||
tr = transitiontrie.NewTransitionTrie(nil, binTrie, false)
|
|
||||||
}
|
}
|
||||||
return &ubtTrieReader{
|
return &ubtTrieReader{
|
||||||
root: root,
|
root: root,
|
||||||
db: db,
|
db: bindb,
|
||||||
tr: tr,
|
tr: transitiontrie.NewTransitionTrie(base, binTrie, false),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,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() {
|
||||||
|
|
|
||||||
62
core/transition_registry.go
Normal file
62
core/transition_registry.go
Normal 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)
|
||||||
|
}
|
||||||
|
|
@ -335,6 +335,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -468,6 +468,11 @@ type ChainConfig struct {
|
||||||
AmsterdamTime *uint64 `json:"amsterdamTime,omitempty"` // Amsterdam switch time (nil = no fork, 0 = already on amsterdam)
|
AmsterdamTime *uint64 `json:"amsterdamTime,omitempty"` // Amsterdam switch time (nil = no fork, 0 = already on amsterdam)
|
||||||
UBTTime *uint64 `json:"ubtTime,omitempty"` // UBT switch time (nil = no fork, 0 = already on UBT)
|
UBTTime *uint64 `json:"ubtTime,omitempty"` // UBT switch time (nil = no fork, 0 = already on UBT)
|
||||||
|
|
||||||
|
// UBTTransitionEndTime is the timestamp at which the MPT-to-binary
|
||||||
|
// transition tree is no longer applied. Mirrors the threshold semantics
|
||||||
|
// of TerminalTotalDifficulty.
|
||||||
|
UBTTransitionEndTime *uint64 `json:"ubtTransitionEndTime,omitempty"`
|
||||||
|
|
||||||
// TerminalTotalDifficulty is the amount of total difficulty reached by
|
// TerminalTotalDifficulty is the amount of total difficulty reached by
|
||||||
// the network that triggers the consensus upgrade.
|
// the network that triggers the consensus upgrade.
|
||||||
TerminalTotalDifficulty *big.Int `json:"terminalTotalDifficulty,omitempty"`
|
TerminalTotalDifficulty *big.Int `json:"terminalTotalDifficulty,omitempty"`
|
||||||
|
|
@ -598,6 +603,9 @@ func (c *ChainConfig) String() string {
|
||||||
if c.UBTTime != nil {
|
if c.UBTTime != nil {
|
||||||
result += fmt.Sprintf(", UBTTime: %v", *c.UBTTime)
|
result += fmt.Sprintf(", UBTTime: %v", *c.UBTTime)
|
||||||
}
|
}
|
||||||
|
if c.UBTTransitionEndTime != nil {
|
||||||
|
result += fmt.Sprintf(", UBTTransitionEndTime: %v", *c.UBTTransitionEndTime)
|
||||||
|
}
|
||||||
result += "}"
|
result += "}"
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
@ -693,6 +701,9 @@ func (c *ChainConfig) Description() string {
|
||||||
if c.UBTTime != nil {
|
if c.UBTTime != nil {
|
||||||
banner += fmt.Sprintf(" - UBT: @%-10v blob: (%s)\n", *c.UBTTime, c.BlobScheduleConfig.UBT)
|
banner += fmt.Sprintf(" - UBT: @%-10v blob: (%s)\n", *c.UBTTime, c.BlobScheduleConfig.UBT)
|
||||||
}
|
}
|
||||||
|
if c.UBTTransitionEndTime != nil {
|
||||||
|
banner += fmt.Sprintf(" - UBT transition tree ends: @%-10v\n", *c.UBTTransitionEndTime)
|
||||||
|
}
|
||||||
banner += fmt.Sprintf("\nAll fork specifications can be found at https://ethereum.github.io/execution-specs/src/ethereum/forks/\n")
|
banner += fmt.Sprintf("\nAll fork specifications can be found at https://ethereum.github.io/execution-specs/src/ethereum/forks/\n")
|
||||||
return banner
|
return banner
|
||||||
}
|
}
|
||||||
|
|
@ -871,16 +882,18 @@ func (c *ChainConfig) IsUBT(num *big.Int, time uint64) bool {
|
||||||
return c.IsLondon(num) && isTimestampForked(c.UBTTime, time)
|
return c.IsLondon(num) && isTimestampForked(c.UBTTime, time)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsUBTGenesis checks whether the verkle fork is activated at the genesis block.
|
// UBTTransitionActive reports whether state access at the given block number
|
||||||
//
|
// and time should still be wrapped in a TransitionTrie (binary overlay on top
|
||||||
// Verkle mode is considered enabled if the verkle fork time is configured,
|
// of the frozen MPT base). It is true when UBT is active and either no end
|
||||||
// regardless of whether the local time has surpassed the fork activation time.
|
// time is configured or the block is still before the configured end time.
|
||||||
// This is a temporary workaround for verkle devnet testing, where verkle is
|
func (c *ChainConfig) UBTTransitionActive(num *big.Int, time uint64) bool {
|
||||||
// activated at genesis, and the configured activation date has already passed.
|
if !c.IsUBT(num, time) {
|
||||||
//
|
return false
|
||||||
// In production networks (mainnet and public testnets), verkle activation
|
}
|
||||||
// always occurs after the genesis block, making this function irrelevant in
|
return c.UBTTransitionEndTime == nil || time < *c.UBTTransitionEndTime
|
||||||
// those cases.
|
}
|
||||||
|
|
||||||
|
// IsUBTGenesis checks whether the UBT fork is activated at the genesis block.
|
||||||
func (c *ChainConfig) IsUBTGenesis() bool {
|
func (c *ChainConfig) IsUBTGenesis() bool {
|
||||||
return c.EnableUBTAtGenesis
|
return c.EnableUBTAtGenesis
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -222,6 +222,10 @@ var (
|
||||||
// EIP-7251 - Increase the MAX_EFFECTIVE_BALANCE
|
// EIP-7251 - Increase the MAX_EFFECTIVE_BALANCE
|
||||||
ConsolidationQueueAddress = common.HexToAddress("0x0000BBdDc7CE488642fb579F8B00f3a590007251")
|
ConsolidationQueueAddress = common.HexToAddress("0x0000BBdDc7CE488642fb579F8B00f3a590007251")
|
||||||
ConsolidationQueueCode = common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe1460d35760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461019a57600182026001905f5b5f82111560685781019083028483029004916001019190604d565b9093900492505050366060146088573661019a573461019a575f5260205ff35b341061019a57600154600101600155600354806004026004013381556001015f358155600101602035815560010160403590553360601b5f5260605f60143760745fa0600101600355005b6003546002548082038060021160e7575060025b5f5b8181146101295782810160040260040181607402815460601b815260140181600101548152602001816002015481526020019060030154905260010160e9565b910180921461013b5790600255610146565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561017357505f5b6001546001828201116101885750505f61018e565b01600190035b5f555f6001556074025ff35b5f5ffd")
|
ConsolidationQueueCode = common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe1460d35760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461019a57600182026001905f5b5f82111560685781019083028483029004916001019190604d565b9093900492505050366060146088573661019a573461019a575f5260205ff35b341061019a57600154600101600155600354806004026004013381556001015f358155600101602035815560010160403590553360601b5f5260605f60143760745fa0600101600355005b6003546002548082038060021160e7575060025b5f5b8181146101295782810160040260040181607402815460601b815260140181600101548152602001816002015481526020019060030154905260010160e9565b910180921461013b5790600255610146565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561017357505f5b6001546001828201116101885750505f61018e565b01600190035b5f555f6001556074025ff35b5f5ffd")
|
||||||
|
|
||||||
|
// BinaryTransitionRegistryAddress is the system contract that exposes the
|
||||||
|
// MPT-to-binary transition state via storage slots.
|
||||||
|
BinaryTransitionRegistryAddress = common.HexToAddress("0x1622162216221622162216221622162216221622")
|
||||||
)
|
)
|
||||||
|
|
||||||
// System log events.
|
// System log events.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue