From b0ea85ca065cbc00d27e2f2277995f71d9337587 Mon Sep 17 00:00:00 2001 From: Pratik Patil Date: Mon, 3 Aug 2026 16:35:45 +0530 Subject: [PATCH] core/state, trie, triedb: split CachingDB into MPT and UBT databases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adopts the type split from geth ba215fd92 (#34700), deferred out of batch 22 where it produced 31 of the batch's 34 conflicts. Scheduled rather than dropped: #34763 applies the same split to core/state/reader.go inside batch 23, and #34843 follows in batch 30, so batch 23 should not land against a non-split tree. Declining the family would fork core/state on the name of its primary type for the rest of the sync. CachingDB becomes MPTDatabase and UBTDatabase, selected by DatabaseType, with Type() on the Database interface. IsVerkle becomes IsUBT on the trie interface, on triedb.Database and in triedb.Config, VerkleDefaults becomes UBTDefaults, and the core/state call sites stop reaching through to the trie database: they ask the state database its type instead. types.EmptyVerkleHash folds into EmptyBinaryHash, which held the same value. Every Bor divergence lands on MPTDatabase, because Bor only ever runs MPT and UBTDatabase is dormant code Bor never constructs outside tooling and tests: the snap-in-reader guard, ReaderTrieOnly, the two ReadersWithCacheStats variants returning Bor's ReaderWithStats, ContractCodeWithPrefix, and Snapshot(), which Bor carries on the Database interface where upstream does not. UBTDatabase implements Snapshot() as nil since a unified binary trie has no snapshot layer. Bor has no CodeDB, so the constructors take the snapshot and own the inline code caches instead, and NewDatabaseForTesting returns the concrete MPT database because reader_test.go needs a Bor-only method. Only the type split is taken, not the runtime fork-boundary selection also in that commit: StateAt growing a header parameter, StateAtForkBoundary, and ProcessBlock choosing per block from chainConfig.IsUBT(number, time). That half is keyed on the timestamp fork fields Bor deleted in favour of block-based ones, it ripples StateAt's signature through every caller across eth, internal, miner and the tracers, and it buys nothing while VerkleBlock is nil on every preset. The params rename and the operator-facing --override.verkle flag stay for the same reason: params is where the fork schedule lives, and the sequels touch core/state. So the vocabulary is deliberately split — core, trie and triedb say UBT, params says Verkle — and both halves are recorded in needs-wiring.md. The dropped transition-state check in OpenTrie is not a behaviour change on the dispatcher path: overlay.LoadTransitionState returns Ended set to the verkle flag when nothing is stored, so a UBT trie database already took the binary branch. The one place it is real, core/blockchain.go now building the MPT database unconditionally, needs the declined fork-boundary plumbing to fix properly and is recorded there rather than papered over with invented dispatch. CachingDB.TransitionStatePerRoot was declared, initialised and never read; the only two references in the tree were those two lines. Removed rather than carried into a newly written file. No fork gate was flipped. Amsterdam, Verkle/UBT and the binary trie remain dormant. --- cmd/evm/internal/t8ntool/execution.go | 2 +- cmd/evm/internal/t8ntool/transition.go | 4 +- cmd/geth/bintrie_convert.go | 2 +- cmd/geth/bintrie_convert_test.go | 8 +- cmd/geth/chaincmd.go | 2 +- cmd/utils/flags.go | 4 +- core/blockchain.go | 12 +- core/blockchain_sethead_test.go | 2 +- core/chain_makers.go | 6 +- core/genesis.go | 26 +-- core/genesis_test.go | 6 +- core/state/database.go | 227 ++++--------------------- core/state/database_history.go | 6 + core/state/database_mpt.go | 206 ++++++++++++++++++++++ core/state/database_ubt.go | 110 ++++++++++++ core/state/reader.go | 6 +- core/state/reader_test.go | 6 +- core/state/state_object.go | 4 +- core/state/statedb.go | 14 +- core/state/statedb_test.go | 6 +- core/state/trie_prefetcher.go | 16 +- core/state/trie_prefetcher_test.go | 2 +- core/state_processor.go | 2 +- core/types/hashes.go | 3 - tests/block_test_util.go | 4 +- trie/bintrie/trie.go | 4 +- trie/secure_trie.go | 2 +- trie/transitiontrie/transition.go | 4 +- triedb/database.go | 18 +- triedb/pathdb/database.go | 20 +-- triedb/pathdb/database_test.go | 4 +- triedb/pathdb/history.go | 6 +- triedb/pathdb/layertree_test.go | 4 +- triedb/pathdb/lookup.go | 2 +- triedb/pathdb/reader.go | 2 +- 35 files changed, 453 insertions(+), 299 deletions(-) create mode 100644 core/state/database_mpt.go create mode 100644 core/state/database_ubt.go diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index cbf0dac55f..9625bf6203 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -390,7 +390,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, } func MakePreState(db ethdb.Database, accounts types.GenesisAlloc, isBintrie bool) *state.StateDB { - tdb := triedb.NewDatabase(db, &triedb.Config{Preimages: true, IsVerkle: isBintrie}) + tdb := triedb.NewDatabase(db, &triedb.Config{Preimages: true, IsUBT: isBintrie}) sdb := state.NewDatabase(tdb, nil) root := types.EmptyRootHash diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index 5f0903b4f8..331dc5ff9f 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -473,7 +473,7 @@ func BinKeys(ctx *cli.Context) error { return err } } - db := triedb.NewDatabase(rawdb.NewMemoryDatabase(), triedb.VerkleDefaults) + db := triedb.NewDatabase(rawdb.NewMemoryDatabase(), triedb.UBTDefaults) defer db.Close() bt, err := genBinTrieFromAlloc(alloc, db) @@ -517,7 +517,7 @@ func BinTrieRoot(ctx *cli.Context) error { return err } } - db := triedb.NewDatabase(rawdb.NewMemoryDatabase(), triedb.VerkleDefaults) + db := triedb.NewDatabase(rawdb.NewMemoryDatabase(), triedb.UBTDefaults) defer db.Close() bt, err := genBinTrieFromAlloc(alloc, db) diff --git a/cmd/geth/bintrie_convert.go b/cmd/geth/bintrie_convert.go index b87ecea129..c1917ecad6 100644 --- a/cmd/geth/bintrie_convert.go +++ b/cmd/geth/bintrie_convert.go @@ -144,7 +144,7 @@ func convertToBinaryTrie(ctx *cli.Context) error { defer srcTriedb.Close() destTriedb := triedb.NewDatabase(chaindb, &triedb.Config{ - IsVerkle: true, + IsUBT: true, PathDB: &pathdb.Config{ JournalDirectory: stack.ResolvePath("triedb-bintrie"), }, diff --git a/cmd/geth/bintrie_convert_test.go b/cmd/geth/bintrie_convert_test.go index 9b95f6a70f..50ae752358 100644 --- a/cmd/geth/bintrie_convert_test.go +++ b/cmd/geth/bintrie_convert_test.go @@ -82,8 +82,8 @@ func TestBintrieConvert(t *testing.T) { defer srcTriedb2.Close() destTriedb := triedb.NewDatabase(chaindb, &triedb.Config{ - IsVerkle: true, - PathDB: pathdb.Defaults, + IsUBT: true, + PathDB: pathdb.Defaults, }) defer destTriedb.Close() @@ -190,8 +190,8 @@ func TestBintrieConvertDeleteSource(t *testing.T) { }) destTriedb := triedb.NewDatabase(chaindb, &triedb.Config{ - IsVerkle: true, - PathDB: pathdb.Defaults, + IsUBT: true, + PathDB: pathdb.Defaults, }) bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 08a187d812..78a5e71319 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -298,7 +298,7 @@ func initGenesis(ctx *cli.Context) error { chaindb := utils.MakeChainDatabase(ctx, stack, false, false) defer chaindb.Close() - triedb := utils.MakeTrieDatabase(ctx, stack, chaindb, ctx.Bool(utils.CachePreimagesFlag.Name), false, genesis.IsVerkle()) + triedb := utils.MakeTrieDatabase(ctx, stack, chaindb, ctx.Bool(utils.CachePreimagesFlag.Name), false, genesis.IsUBT()) defer triedb.Close() _, hash, compatErr, err := core.SetupGenesisBlockWithOverride(chaindb, triedb, genesis, &overrides) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 4cf7034c1f..f374e695e1 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -2604,10 +2604,10 @@ func MakeConsolePreloads(ctx *cli.Context) []string { } // MakeTrieDatabase constructs a trie database based on the configured scheme. -func MakeTrieDatabase(ctx *cli.Context, stack *node.Node, disk ethdb.Database, preimage bool, readOnly bool, isVerkle bool) *triedb.Database { +func MakeTrieDatabase(ctx *cli.Context, stack *node.Node, disk ethdb.Database, preimage bool, readOnly bool, isUBT bool) *triedb.Database { config := &triedb.Config{ Preimages: preimage, - IsVerkle: isVerkle, + IsUBT: isUBT, } scheme, err := rawdb.ParseStateScheme(ctx.String(StateSchemeFlag.Name), disk) if err != nil { diff --git a/core/blockchain.go b/core/blockchain.go index e7da9d4e74..2983b1fcd2 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -318,10 +318,10 @@ func (cfg BlockChainConfig) GetTriesInMemory() uint64 { } // triedbConfig derives the configures for trie database. -func (cfg *BlockChainConfig) triedbConfig(isVerkle bool) *triedb.Config { +func (cfg *BlockChainConfig) triedbConfig(isUBT bool) *triedb.Config { config := &triedb.Config{ Preimages: cfg.Preimages, - IsVerkle: isVerkle, + IsUBT: isUBT, } if cfg.StateScheme == rawdb.HashScheme { config.HashDB = &hashdb.Config{ @@ -383,7 +383,7 @@ type BlockChain struct { lastWrite uint64 // Last block when the state was flushed flushInterval atomic.Int64 // Time interval (processing time) after which to flush a state triedb *triedb.Database // The database handler for maintaining trie nodes. - statedb *state.CachingDB // State database to reuse between imports (contains state cache) + statedb *state.MPTDatabase // State database to reuse between imports (contains state cache) txIndexer *txIndexer // Transaction indexer, might be nil if not enabled hc *HeaderChain @@ -454,7 +454,7 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine, } // Open trie database with provided config - enableVerkle, err := EnableVerkleAtGenesis(db, genesis) + enableVerkle, err := EnableUBTAtGenesis(db, genesis) if err != nil { return nil, err } @@ -508,7 +508,7 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine, bc.flushInterval.Store(int64(cfg.TrieTimeLimit)) bc.forker = NewForkChoice(bc, cfg.ShouldPreserve, cfg.Checker) - bc.statedb = state.NewDatabase(bc.triedb, nil) + bc.statedb = state.NewMPTDatabase(bc.triedb, nil) bc.validator = NewBlockValidator(chainConfig, bc) bc.prefetcher = NewStatePrefetcher(chainConfig, bc.hc) bc.processor = NewStateProcessor(bc.hc) @@ -1032,7 +1032,7 @@ func (bc *BlockChain) setupSnapshot() { bc.snaps, _ = snapshot.New(snapconfig, bc.db, bc.triedb, head.Root) // Re-initialize the state database with snapshot - bc.statedb = state.NewDatabase(bc.triedb, bc.snaps) + bc.statedb = state.NewMPTDatabase(bc.triedb, bc.snaps) } } diff --git a/core/blockchain_sethead_test.go b/core/blockchain_sethead_test.go index d04d4f353d..0ee8ed8f00 100644 --- a/core/blockchain_sethead_test.go +++ b/core/blockchain_sethead_test.go @@ -2041,7 +2041,7 @@ func testSetHeadWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme dbconfig.HashDB = hashdb.Defaults } chain.triedb = triedb.NewDatabase(chain.db, dbconfig) - chain.statedb = state.NewDatabase(chain.triedb, chain.snaps) + chain.statedb = state.NewMPTDatabase(chain.triedb, chain.snaps) // Force run a freeze cycle type freezer interface { diff --git a/core/chain_makers.go b/core/chain_makers.go index 51eac3c144..16f6e33ecd 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -128,7 +128,7 @@ func (b *BlockGen) addTx(bc *BlockChain, vmConfig vm.Config, tx *types.Transacti // Merge the tx-local access event into the "block-local" one, in order to collect // all values, so that the witness can be built. - if b.statedb.Database().TrieDB().IsVerkle() { + if b.statedb.Database().Type().Is(state.TypeUBT) { b.statedb.AccessEvents().Merge(evm.AccessEvents) } b.txs = append(b.txs, tx) @@ -445,7 +445,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse // Forcibly use hash-based state scheme for retaining all nodes in disk. var triedbConfig *triedb.Config = triedb.HashDefaults if config.IsVerkle(config.ChainID) { - triedbConfig = triedb.VerkleDefaults + triedbConfig = triedb.UBTDefaults } triedb := triedb.NewDatabase(db, triedbConfig) defer triedb.Close() @@ -494,7 +494,7 @@ func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int, db := rawdb.NewMemoryDatabase() var triedbConfig *triedb.Config = triedb.HashDefaults if genesis.Config != nil && genesis.Config.IsVerkle(genesis.Config.ChainID) { - triedbConfig = triedb.VerkleDefaults + triedbConfig = triedb.UBTDefaults } genesisTriedb := triedb.NewDatabase(db, triedbConfig) block, err := genesis.Commit(db, genesisTriedb) diff --git a/core/genesis.go b/core/genesis.go index a2dd89ef07..263772e5e1 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -144,22 +144,22 @@ func ReadGenesis(db ethdb.Database) (*Genesis, error) { } // hashAlloc computes the state root according to the genesis specification. -func hashAlloc(ga *types.GenesisAlloc, isVerkle bool) (common.Hash, error) { +func hashAlloc(ga *types.GenesisAlloc, isUBT bool) (common.Hash, error) { // If a genesis-time verkle trie is requested, create a trie config // with the verkle trie enabled so that the tree can be initialized // as such. var config *triedb.Config - if isVerkle { + if isUBT { config = &triedb.Config{ - PathDB: pathdb.Defaults, - IsVerkle: true, + PathDB: pathdb.Defaults, + IsUBT: true, } } // Create an ephemeral in-memory database for computing hash, // all the derived states will be discarded to not pollute disk. emptyRoot := types.EmptyRootHash - if isVerkle { - emptyRoot = types.EmptyVerkleHash + if isUBT { + emptyRoot = types.EmptyBinaryHash } db := rawdb.NewMemoryDatabase() statedb, err := state.New(emptyRoot, state.NewDatabase(triedb.NewDatabase(db, config), nil)) @@ -184,8 +184,8 @@ func hashAlloc(ga *types.GenesisAlloc, isVerkle bool) (common.Hash, error) { // generated states will be persisted into the given database. func flushAlloc(ga *types.GenesisAlloc, triedb *triedb.Database) (common.Hash, error) { emptyRoot := types.EmptyRootHash - if triedb.IsVerkle() { - emptyRoot = types.EmptyVerkleHash + if triedb.IsUBT() { + emptyRoot = types.EmptyBinaryHash } statedb, err := state.New(emptyRoot, state.NewDatabase(triedb, nil)) if err != nil { @@ -474,15 +474,15 @@ func (g *Genesis) chainConfigOrDefault(ghash common.Hash, stored *params.ChainCo } } -// IsVerkle indicates whether the state is already stored in a verkle +// IsUBT indicates whether the state is already stored in a unified binary // tree at genesis time. -func (g *Genesis) IsVerkle() bool { +func (g *Genesis) IsUBT() bool { return false } // ToBlock returns the genesis block according to genesis specification. func (g *Genesis) ToBlock() *types.Block { - root, err := hashAlloc(&g.Alloc, g.IsVerkle()) + root, err := hashAlloc(&g.Alloc, g.IsUBT()) if err != nil { panic(err) } @@ -631,14 +631,14 @@ func GenesisBlockForTesting(db ethdb.Database, addr common.Address, balance *big return g.MustCommit(db, triedb.NewDatabase(db, triedb.HashDefaults)) } -// EnableVerkleAtGenesis indicates whether the verkle fork should be activated +// EnableUBTAtGenesis indicates whether the verkle fork should be activated // at genesis. This is a temporary solution only for verkle devnet testing, where // verkle fork is activated at genesis, and the configured activation date has // already passed. // // In production networks (mainnet and public testnets), verkle activation always // occurs after the genesis block, making this function irrelevant in those cases. -func EnableVerkleAtGenesis(db ethdb.Database, genesis *Genesis) (bool, error) { +func EnableUBTAtGenesis(db ethdb.Database, genesis *Genesis) (bool, error) { if genesis != nil { if genesis.Config == nil { return false, errGenesisNoConfig diff --git a/core/genesis_test.go b/core/genesis_test.go index 0754fa8a16..aa45cffa04 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -343,8 +343,8 @@ func TestVerkleGenesisCommit(t *testing.T) { config.NoAsyncFlush = true triedb := triedb.NewDatabase(db, &triedb.Config{ - IsVerkle: true, - PathDB: &config, + IsUBT: true, + PathDB: &config, }) block := genesis.MustCommit(db, triedb) if !bytes.Equal(block.Root().Bytes(), expected) { @@ -352,7 +352,7 @@ func TestVerkleGenesisCommit(t *testing.T) { } // Test that the trie is verkle - if !triedb.IsVerkle() { + if !triedb.IsUBT() { t.Fatalf("expected trie to be verkle") } vdb := rawdb.NewTable(db, string(rawdb.VerklePrefix)) diff --git a/core/state/database.go b/core/state/database.go index b120b6b187..3777ee291b 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -18,15 +18,11 @@ package state import ( "fmt" - "sync" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/lru" - "github.com/ethereum/go-ethereum/core/overlay" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie/bintrie" @@ -43,8 +39,27 @@ const ( codeCacheSize = 256 * 1024 * 1024 ) +// DatabaseType represents the type of trie backing the state database. +type DatabaseType int + +const ( + // TypeMPT indicates a Merkle Patricia Trie (MPT) backed database. + TypeMPT DatabaseType = iota + + // TypeUBT indicates a Unified Binary Trie (UBT) backed database. + TypeUBT +) + +// Is returns the flag indicating the database type equals to the given one. +func (typ DatabaseType) Is(t DatabaseType) bool { + return typ == t +} + // Database wraps access to tries and contract code. type Database interface { + // Type returns the trie type backing this database (MPT or UBT). + Type() DatabaseType + // Reader returns a state reader associated with the specified state root. Reader(root common.Hash) (Reader, error) @@ -146,204 +161,26 @@ type Trie interface { // with the node that proves the absence of the key. Prove(key []byte, proofDb ethdb.KeyValueWriter) error - // IsVerkle returns true if the trie is verkle-tree based - IsVerkle() bool -} - -// CachingDB is an implementation of Database interface. It leverages both trie and -// state snapshot to provide functionalities for state access. It's meant to be a -// long-live object and has a few caches inside for sharing between blocks. -type CachingDB struct { - disk ethdb.KeyValueStore - triedb *triedb.Database - snap *snapshot.Tree - codeCache *lru.SizeConstrainedCache[common.Hash, []byte] - codeSizeCache *lru.Cache[common.Hash, int] - snapMu sync.RWMutex // Protects useSnapInReader - useSnapInReader bool - - // Transition-specific fields - TransitionStatePerRoot *lru.Cache[common.Hash, *overlay.TransitionState] + // IsUBT returns true if the trie is unified binary trie based. + IsUBT() bool } -// NewDatabase creates a state database with the provided data sources. -func NewDatabase(triedb *triedb.Database, snap *snapshot.Tree) *CachingDB { - return &CachingDB{ - disk: triedb.Disk(), - triedb: triedb, - snap: snap, - codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), - codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), - TransitionStatePerRoot: lru.NewCache[common.Hash, *overlay.TransitionState](1000), - useSnapInReader: true, +// NewDatabase creates a state database with the provided data sources. The +// snapshot is only consulted by the merkle database; unified binary tries +// have no snapshot layer. +// +// Deprecated, please use NewMPTDatabase or NewUBTDatabase directly. +func NewDatabase(tdb *triedb.Database, snap *snapshot.Tree) Database { + if tdb.IsUBT() { + return NewUBTDatabase(tdb) } -} - -func (db *CachingDB) DisableSnapInReader() { - db.snapMu.Lock() - db.useSnapInReader = false - db.snapMu.Unlock() -} - -func (db *CachingDB) EnableSnapInReader() { - db.snapMu.Lock() - db.useSnapInReader = true - db.snapMu.Unlock() + return NewMPTDatabase(tdb, snap) } // NewDatabaseForTesting is similar to NewDatabase, but it initializes the caching // db by using an ephemeral memory db with default config for testing. -func NewDatabaseForTesting() *CachingDB { - return NewDatabase(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil), nil) -} - -// Reader returns a state reader associated with the specified state root. -func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) { - var readers []StateReader - - // Configure the state reader using the standalone snapshot in hash mode. - // This reader offers improved performance but is optional and only - // partially useful if the snapshot is not fully generated. - db.snapMu.RLock() - useSnap := db.useSnapInReader - db.snapMu.RUnlock() - if db.TrieDB().Scheme() == rawdb.HashScheme && db.snap != nil && useSnap { - snap := db.snap.Snapshot(stateRoot) - if snap != nil { - readers = append(readers, newFlatReader(snap)) - } - } - // Configure the state reader using the path database in path mode. - // This reader offers improved performance but is optional and only - // partially useful if the snapshot data in path database is not - // fully generated. - if db.TrieDB().Scheme() == rawdb.PathScheme && useSnap { - reader, err := db.triedb.StateReader(stateRoot) - if err == nil { - readers = append(readers, newFlatReader(reader)) - } - } - // Configure the trie reader, which is expected to be available as the - // gatekeeper unless the state is corrupted. - tr, err := newTrieReader(stateRoot, db.triedb) - if err != nil { - return nil, err - } - readers = append(readers, tr) - - combined, err := newMultiStateReader(readers...) - if err != nil { - return nil, err - } - return newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), combined), nil -} - -// ReaderTrieOnly creates a state reader that only uses the trie, skipping -// snapshot layers. Useful for V2 parallel execution where the snapshot reader -// may have thread-safety issues under concurrent access from multiple workers. -func (db *CachingDB) ReaderTrieOnly(stateRoot common.Hash) (Reader, error) { - tr, err := newTrieReader(stateRoot, db.triedb) - if err != nil { - return nil, err - } - combined, err := newMultiStateReader(tr) - if err != nil { - return nil, err - } - return newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), combined), nil -} - -// ReadersWithCacheStats creates a pair of state readers sharing the same internal cache and -// same backing Reader, but exposing separate statistics. -func (db *CachingDB) ReadersWithCacheStats(stateRoot common.Hash) (ReaderWithStats, ReaderWithStats, error) { - reader, err := db.Reader(stateRoot) - if err != nil { - return nil, nil, err - } - shared := newReaderWithCache(reader) - return newReaderWithCacheStats(shared, rolePrefetch), newReaderWithCacheStats(shared, roleProcess), nil -} - -// ReadersWithCacheStatsTriple creates three state readers sharing the same -// internal cache: prefetch, process (serial), and parallel (V2). -// The shared cache means prefetcher warms data that V2 reads for free. -func (db *CachingDB) ReadersWithCacheStatsTriple(stateRoot common.Hash) (ReaderWithStats, ReaderWithStats, ReaderWithStats, error) { - reader, err := db.Reader(stateRoot) - if err != nil { - return nil, nil, nil, err - } - shared := newReaderWithCache(reader) - return newReaderWithCacheStats(shared, rolePrefetch), - newReaderWithCacheStats(shared, roleProcess), - newReaderWithCacheStats(shared, roleProcess), // V2 shares same cache - nil -} - -// OpenTrie opens the main account trie at a specific root hash. -func (db *CachingDB) OpenTrie(root common.Hash) (Trie, error) { - if db.triedb.IsVerkle() { - ts := overlay.LoadTransitionState(db.TrieDB().Disk(), root, db.triedb.IsVerkle()) - if ts.InTransition() { - panic("state tree transition isn't supported yet") - } - if ts.Transitioned() { - // Use BinaryTrie instead of VerkleTrie when IsVerkle is set - // (IsVerkle actually means Binary Trie mode in this codebase) - return bintrie.NewBinaryTrie(root, db.triedb) - } - } - tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb) - if err != nil { - return nil, err - } - - return tr, nil -} - -// OpenStorageTrie opens the storage trie of an account. -func (db *CachingDB) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, self Trie) (Trie, error) { - if db.triedb.IsVerkle() { - return self, nil - } - tr, err := trie.NewStateTrie(trie.StorageTrieID(stateRoot, crypto.Keccak256Hash(address.Bytes()), root), db.triedb) - if err != nil { - return nil, err - } - - return tr, nil -} - -// ContractCodeWithPrefix retrieves a particular contract's code. If the -// code can't be found in the cache, then check the existence with **new** -// db scheme. -func (db *CachingDB) ContractCodeWithPrefix(address common.Address, codeHash common.Hash) []byte { - code, _ := db.codeCache.Get(codeHash) - if len(code) > 0 { - return code - } - - code = rawdb.ReadCodeWithPrefix(db.disk, codeHash) - - if len(code) > 0 { - db.codeCache.Add(codeHash, code) - db.codeSizeCache.Add(codeHash, len(code)) - } - return code -} - -// TrieDB retrieves any intermediate trie-node caching layer. -func (db *CachingDB) TrieDB() *triedb.Database { - return db.triedb -} - -// Snapshot returns the underlying state snapshot. -func (db *CachingDB) Snapshot() *snapshot.Tree { - return db.snap -} - -// Iteratee returns a state iteratee associated with the specified state root. -func (db *CachingDB) Iteratee(root common.Hash) (Iteratee, error) { - return newStateIteratee(!db.triedb.IsVerkle(), root, db.triedb, db.snap) +func NewDatabaseForTesting() *MPTDatabase { + return NewMPTDatabase(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil), nil) } // mustCopyTrie returns a deep-copied trie. diff --git a/core/state/database_history.go b/core/state/database_history.go index fed8b6530d..4e299f37fd 100644 --- a/core/state/database_history.go +++ b/core/state/database_history.go @@ -228,6 +228,12 @@ type HistoricDB struct { codeSizeCache *lru.Cache[common.Hash, int] } +// Type returns the trie type of the underlying database. +func (db *HistoricDB) Type() DatabaseType { + // TODO(rjl493456442) support UBT in the future + return TypeMPT +} + // NewHistoricDatabase creates a historic state database. func NewHistoricDatabase(disk ethdb.KeyValueStore, triedb *triedb.Database) *HistoricDB { return &HistoricDB{ diff --git a/core/state/database_mpt.go b/core/state/database_mpt.go new file mode 100644 index 0000000000..715a93d788 --- /dev/null +++ b/core/state/database_mpt.go @@ -0,0 +1,206 @@ +// 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 state + +import ( + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/lru" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state/snapshot" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/triedb" +) + +// MPTDatabase is an implementation of Database interface for Merkle Patricia Tries. +// It leverages both trie and state snapshot to provide functionalities for state +// access. It's meant to be a long-live object and has a few caches inside for +// sharing between blocks. +type MPTDatabase struct { + disk ethdb.KeyValueStore + triedb *triedb.Database + snap *snapshot.Tree + codeCache *lru.SizeConstrainedCache[common.Hash, []byte] + codeSizeCache *lru.Cache[common.Hash, int] + snapMu sync.RWMutex // Protects useSnapInReader + useSnapInReader bool +} + +// Type returns TypeMPT, indicating this database is backed by a Merkle Patricia Trie. +func (db *MPTDatabase) Type() DatabaseType { return TypeMPT } + +// NewMPTDatabase creates a state database with the Merkle Patricia Trie manner. +func NewMPTDatabase(tdb *triedb.Database, snap *snapshot.Tree) *MPTDatabase { + return &MPTDatabase{ + disk: tdb.Disk(), + triedb: tdb, + snap: snap, + codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), + codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), + useSnapInReader: true, + } +} + +func (db *MPTDatabase) DisableSnapInReader() { + db.snapMu.Lock() + db.useSnapInReader = false + db.snapMu.Unlock() +} + +func (db *MPTDatabase) EnableSnapInReader() { + db.snapMu.Lock() + db.useSnapInReader = true + db.snapMu.Unlock() +} + +// Reader returns a state reader associated with the specified state root. +func (db *MPTDatabase) Reader(stateRoot common.Hash) (Reader, error) { + var readers []StateReader + + // Configure the state reader using the standalone snapshot in hash mode. + // This reader offers improved performance but is optional and only + // partially useful if the snapshot is not fully generated. + db.snapMu.RLock() + useSnap := db.useSnapInReader + db.snapMu.RUnlock() + if db.TrieDB().Scheme() == rawdb.HashScheme && db.snap != nil && useSnap { + snap := db.snap.Snapshot(stateRoot) + if snap != nil { + readers = append(readers, newFlatReader(snap)) + } + } + // Configure the state reader using the path database in path mode. + // This reader offers improved performance but is optional and only + // partially useful if the snapshot data in path database is not + // fully generated. + if db.TrieDB().Scheme() == rawdb.PathScheme && useSnap { + reader, err := db.triedb.StateReader(stateRoot) + if err == nil { + readers = append(readers, newFlatReader(reader)) + } + } + // Configure the trie reader, which is expected to be available as the + // gatekeeper unless the state is corrupted. + tr, err := newTrieReader(stateRoot, db.triedb) + if err != nil { + return nil, err + } + readers = append(readers, tr) + + combined, err := newMultiStateReader(readers...) + if err != nil { + return nil, err + } + return newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), combined), nil +} + +// ReaderTrieOnly creates a state reader that only uses the trie, skipping +// snapshot layers. Useful for V2 parallel execution where the snapshot reader +// may have thread-safety issues under concurrent access from multiple workers. +func (db *MPTDatabase) ReaderTrieOnly(stateRoot common.Hash) (Reader, error) { + tr, err := newTrieReader(stateRoot, db.triedb) + if err != nil { + return nil, err + } + combined, err := newMultiStateReader(tr) + if err != nil { + return nil, err + } + return newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), combined), nil +} + +// ReadersWithCacheStats creates a pair of state readers sharing the same internal cache and +// same backing Reader, but exposing separate statistics. +func (db *MPTDatabase) ReadersWithCacheStats(stateRoot common.Hash) (ReaderWithStats, ReaderWithStats, error) { + reader, err := db.Reader(stateRoot) + if err != nil { + return nil, nil, err + } + shared := newReaderWithCache(reader) + return newReaderWithCacheStats(shared, rolePrefetch), newReaderWithCacheStats(shared, roleProcess), nil +} + +// ReadersWithCacheStatsTriple creates three state readers sharing the same +// internal cache: prefetch, process (serial), and parallel (V2). +// The shared cache means prefetcher warms data that V2 reads for free. +func (db *MPTDatabase) ReadersWithCacheStatsTriple(stateRoot common.Hash) (ReaderWithStats, ReaderWithStats, ReaderWithStats, error) { + reader, err := db.Reader(stateRoot) + if err != nil { + return nil, nil, nil, err + } + shared := newReaderWithCache(reader) + return newReaderWithCacheStats(shared, rolePrefetch), + newReaderWithCacheStats(shared, roleProcess), + newReaderWithCacheStats(shared, roleProcess), // V2 shares same cache + nil +} + +// OpenTrie opens the main account trie at a specific root hash. +func (db *MPTDatabase) OpenTrie(root common.Hash) (Trie, error) { + tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb) + if err != nil { + return nil, err + } + + return tr, nil +} + +// OpenStorageTrie opens the storage trie of an account. +func (db *MPTDatabase) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, self Trie) (Trie, error) { + tr, err := trie.NewStateTrie(trie.StorageTrieID(stateRoot, crypto.Keccak256Hash(address.Bytes()), root), db.triedb) + if err != nil { + return nil, err + } + + return tr, nil +} + +// ContractCodeWithPrefix retrieves a particular contract's code. If the +// code can't be found in the cache, then check the existence with **new** +// db scheme. +func (db *MPTDatabase) ContractCodeWithPrefix(address common.Address, codeHash common.Hash) []byte { + code, _ := db.codeCache.Get(codeHash) + if len(code) > 0 { + return code + } + + code = rawdb.ReadCodeWithPrefix(db.disk, codeHash) + + if len(code) > 0 { + db.codeCache.Add(codeHash, code) + db.codeSizeCache.Add(codeHash, len(code)) + } + return code +} + +// TrieDB retrieves any intermediate trie-node caching layer. +func (db *MPTDatabase) TrieDB() *triedb.Database { + return db.triedb +} + +// Snapshot returns the underlying state snapshot. +func (db *MPTDatabase) Snapshot() *snapshot.Tree { + return db.snap +} + +// Iteratee returns a state iteratee associated with the specified state root. +func (db *MPTDatabase) Iteratee(root common.Hash) (Iteratee, error) { + return newStateIteratee(true, root, db.triedb, db.snap) +} diff --git a/core/state/database_ubt.go b/core/state/database_ubt.go new file mode 100644 index 0000000000..5737c7350a --- /dev/null +++ b/core/state/database_ubt.go @@ -0,0 +1,110 @@ +// 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 state + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/lru" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state/snapshot" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/trie/bintrie" + "github.com/ethereum/go-ethereum/triedb" +) + +// 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. +// +// Bor never constructs this outside of tooling and tests: the Verkle/UBT fork is +// dormant on every preset, so NewDatabase only reaches it when a triedb is +// explicitly opened with triedb.UBTDefaults. +type UBTDatabase struct { + disk ethdb.KeyValueStore + triedb *triedb.Database + codeCache *lru.SizeConstrainedCache[common.Hash, []byte] + codeSizeCache *lru.Cache[common.Hash, int] +} + +// Type returns TypeUBT, indicating this database is backed by a Unified Binary Trie. +func (db *UBTDatabase) Type() DatabaseType { return TypeUBT } + +// NewUBTDatabase creates a state database with the Unified binary trie manner. +func NewUBTDatabase(tdb *triedb.Database) *UBTDatabase { + return &UBTDatabase{ + disk: tdb.Disk(), + triedb: tdb, + codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), + codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), + } +} + +// Reader returns a state reader associated with the specified state root. +func (db *UBTDatabase) Reader(stateRoot common.Hash) (Reader, error) { + var readers []StateReader + + // Configure the state reader using the path database in path mode. + // This reader offers improved performance but is optional and only + // partially useful if the snapshot data in path database is not + // fully generated. + if db.TrieDB().Scheme() == rawdb.PathScheme { + reader, err := db.triedb.StateReader(stateRoot) + if err == nil { + readers = append(readers, newFlatReader(reader)) + } + } + // Configure the trie reader, which is expected to be available as the + // gatekeeper unless the state is corrupted. + tr, err := newTrieReader(stateRoot, db.triedb) + if err != nil { + return nil, err + } + readers = append(readers, tr) + + combined, err := newMultiStateReader(readers...) + if err != nil { + return nil, err + } + return newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), combined), nil +} + +// OpenTrie opens the main account trie at a specific root hash. +func (db *UBTDatabase) OpenTrie(root common.Hash) (Trie, error) { + return bintrie.NewBinaryTrie(root, db.triedb) +} + +// 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. +func (db *UBTDatabase) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, self Trie) (Trie, error) { + return self, nil +} + +// TrieDB retrieves any intermediate trie-node caching layer. +func (db *UBTDatabase) TrieDB() *triedb.Database { + return db.triedb +} + +// Snapshot implements Database. A unified binary trie has no snapshot layer. +func (db *UBTDatabase) Snapshot() *snapshot.Tree { + return nil +} + +// Iteratee returns a state iteratee associated with the specified state root, +// through which the account iterator and storage iterator can be created. +func (db *UBTDatabase) Iteratee(root common.Hash) (Iteratee, error) { + return newStateIteratee(false, root, db.triedb, nil) +} diff --git a/core/state/reader.go b/core/state/reader.go index 2b7cc7be4c..b19967cd0d 100644 --- a/core/state/reader.go +++ b/core/state/reader.go @@ -270,10 +270,10 @@ func newTrieReader(root common.Hash, db *triedb.Database) (*trieReader, error) { tr Trie err error ) - if !db.IsVerkle() { + if !db.IsUBT() { tr, err = trie.NewStateTrie(trie.StateTrieID(root), db) } else { - // When IsVerkle() is true, create a BinaryTrie wrapped in TransitionTrie + // When IsUBT() is true, create a BinaryTrie wrapped in TransitionTrie binTrie, binErr := bintrie.NewBinaryTrie(root, db) if binErr != nil { return nil, binErr @@ -459,7 +459,7 @@ func (r *trieReader) subTrieConcurrent(addr common.Address) (Trie, error) { // subTrieLocked is the legacy mutex-protected path. Verkle uses the // merged main trie; MPT uses per-address sub tries cached in subTries. func (r *trieReader) subTrieLocked(addr common.Address) (Trie, error) { - if r.db.IsVerkle() { + if r.db.IsUBT() { return r.mainTrie, nil } if v, ok := r.subTries.Load(addr); ok { diff --git a/core/state/reader_test.go b/core/state/reader_test.go index d8163d1b59..f6339ed1c2 100644 --- a/core/state/reader_test.go +++ b/core/state/reader_test.go @@ -35,7 +35,7 @@ func TestCacheAttribution_PrefetchToProcess(t *testing.T) { // Setup: Create a state database with some accounts db := rawdb.NewMemoryDatabase() triedb := triedb.NewDatabase(db, nil) - statedb := NewDatabase(triedb, nil) + statedb := NewMPTDatabase(triedb, nil) // Create initial state with some accounts state, err := New(types.EmptyRootHash, statedb) @@ -180,7 +180,7 @@ func TestCacheAttribution_UniqueUsageTracking(t *testing.T) { // Setup: Create a state database with an account db := rawdb.NewMemoryDatabase() triedb := triedb.NewDatabase(db, nil) - statedb := NewDatabase(triedb, nil) + statedb := NewMPTDatabase(triedb, nil) // Create initial state state, err := New(types.EmptyRootHash, statedb) @@ -285,7 +285,7 @@ func TestReaderWithCache_ConcurrentAccess(t *testing.T) { // Setup: Create a state database with many accounts db := rawdb.NewMemoryDatabase() triedb := triedb.NewDatabase(db, nil) - statedb := NewDatabase(triedb, nil) + statedb := NewMPTDatabase(triedb, nil) // Create initial state with 100 accounts state, err := New(types.EmptyRootHash, statedb) diff --git a/core/state/state_object.go b/core/state/state_object.go index d3a86e69c8..6933322d38 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -159,7 +159,7 @@ func (s *stateObject) getTrie() (Trie, error) { func (s *stateObject) getPrefetchedTrie() Trie { // If there's nothing to meaningfully return, let the user figure it out by // pulling the trie from disk. - if (s.data.Root == types.EmptyRootHash && !s.db.db.TrieDB().IsVerkle()) || s.db.prefetcher == nil { + if (s.data.Root == types.EmptyRootHash && s.db.db.Type().Is(TypeMPT)) || s.db.prefetcher == nil { return nil } // Attempt to retrieve the trie from the prefetcher @@ -483,7 +483,7 @@ func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) { // The main account trie commit in stateDB.commit() already calls // CollectNodes on this trie, so calling Commit here again would // redundantly traverse and serialize the entire tree per dirty account. - if s.db.GetTrie().IsVerkle() { + if s.db.GetTrie().IsUBT() { s.origin = s.data.Copy() return op, nil, nil } diff --git a/core/state/statedb.go b/core/state/statedb.go index 5589ba5b6c..1d3b382552 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -206,7 +206,7 @@ func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, erro accessList: newAccessList(), transientStorage: newTransientStorage(), } - if db.TrieDB().IsVerkle() { + if db.Type().Is(TypeUBT) { sdb.accessEvents = NewAccessEvents() } @@ -1482,7 +1482,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { if !s.skipTimers { start = time.Now() } - if s.db.TrieDB().IsVerkle() { + if s.db.Type().Is(TypeUBT) { // Bypass per-account updateTrie() for binary trie. In binary trie mode // there is only one unified trie (OpenStorageTrie returns self), so the // per-account trie setup in updateTrie() (getPrefetchedTrie, getTrie, @@ -1546,9 +1546,9 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { } } // If witness building is enabled, gather all the read-only accesses. - // Skip witness collection in Verkle mode, they will be gathered - // together at the end. - if s.witness != nil && !s.db.TrieDB().IsVerkle() { + // Skip witness collection in Unified-binary-trie mode, they will be + // gathered together at the end. + if s.witness != nil && s.db.Type().Is(TypeMPT) { witStart := time.Now() // Pull in anything that has been accessed before destruction for _, obj := range s.stateObjectsDestruct { @@ -1595,7 +1595,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { if !s.skipTimers { start = time.Now() } - if s.prefetcher != nil && !s.db.TrieDB().IsVerkle() { + if s.prefetcher != nil && s.db.Type().Is(TypeMPT) { if trie := s.prefetcher.trie(common.Hash{}, s.originalRoot); trie == nil { log.Error("Failed to retrieve account pre-fetcher trie") } else { @@ -1755,7 +1755,7 @@ func (s *StateDB) handleDestruction(noStorageWiping bool) (map[common.Hash]*acco deletes[addrHash] = op // Short circuit if the origin storage was empty. - if prev.Root == types.EmptyRootHash || s.db.TrieDB().IsVerkle() { + if prev.Root == types.EmptyRootHash || s.db.Type().Is(TypeUBT) { continue } if noStorageWiping { diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index f13fd01ae8..dd29d0feb4 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -2387,12 +2387,10 @@ func BenchmarkMVReadOverhead(b *testing.B) { // type *bintrie.BinaryTrie", because the type switch only covered *trie.StateTrie // and *transitiontrie.TransitionTrie. // -// Upstream names this TestStateDBCopyUBT and reaches the binary tree via -// triedb.UBTDefaults; Bor kept the pre-rename triedb.VerkleDefaults, which sets -// IsVerkle and so yields the same binary trie now that go-verkle is gone. +// Upstream names this TestStateDBCopyUBT. func TestStateDBCopyBinaryTrie(t *testing.T) { disk := rawdb.NewMemoryDatabase() - tdb := triedb.NewDatabase(disk, triedb.VerkleDefaults) + tdb := triedb.NewDatabase(disk, triedb.UBTDefaults) sdb := NewDatabase(tdb, nil) orig, err := New(types.EmptyRootHash, sdb) diff --git a/core/state/trie_prefetcher.go b/core/state/trie_prefetcher.go index 062b5c5196..4ffdaedf1b 100644 --- a/core/state/trie_prefetcher.go +++ b/core/state/trie_prefetcher.go @@ -41,7 +41,7 @@ var ( // // Note, the prefetcher's API is not thread safe. type triePrefetcher struct { - verkle bool // Flag whether the prefetcher is in verkle mode + isUBT bool // Flag whether the prefetcher is in UBT mode db Database // Database to fetch trie nodes through root common.Hash // Root hash of the account trie for metrics fetchers map[string]*subfetcher // Subfetchers for each trie @@ -74,7 +74,7 @@ type triePrefetcher struct { func newTriePrefetcher(db Database, root common.Hash, namespace string, noreads bool) *triePrefetcher { prefix := triePrefetchMetricsPrefix + namespace return &triePrefetcher{ - verkle: db.TrieDB().IsVerkle(), + isUBT: db.Type().Is(TypeUBT), db: db, root: root, fetchers: make(map[string]*subfetcher), // Active prefetchers use the fetchers map @@ -243,8 +243,8 @@ func (p *triePrefetcher) used(owner common.Hash, root common.Hash, usedAddr []co // trieID returns an unique trie identifier consists the trie owner and root hash. func (p *triePrefetcher) trieID(owner common.Hash, root common.Hash) string { - // The trie in verkle is only identified by state root - if p.verkle { + // The trie in ubt is only identified by state root + if p.isUBT { return p.root.Hex() } // The trie in merkle is either identified by state root (account trie), @@ -410,12 +410,12 @@ func (sf *subfetcher) terminate(async bool) { // openTrie resolves the target trie from database for prefetching. func (sf *subfetcher) openTrie() error { - // Open the verkle tree if the sub-fetcher is in verkle mode. Note, there is - // only a single fetcher for verkle. - if sf.db.TrieDB().IsVerkle() { + // Open the ubt tree if the sub-fetcher is in ubt mode. Note, there is + // only a single fetcher for ubt. + if sf.db.Type().Is(TypeUBT) { tr, err := sf.db.OpenTrie(sf.state) if err != nil { - log.Warn("Trie prefetcher failed opening verkle trie", "root", sf.root, "err", err) + log.Warn("Trie prefetcher failed opening UBT trie", "root", sf.root, "err", err) return err } sf.trie = tr diff --git a/core/state/trie_prefetcher_test.go b/core/state/trie_prefetcher_test.go index da4b898a90..012abd5f3b 100644 --- a/core/state/trie_prefetcher_test.go +++ b/core/state/trie_prefetcher_test.go @@ -73,7 +73,7 @@ func TestUseAfterTerminate(t *testing.T) { func TestVerklePrefetcher(t *testing.T) { disk := rawdb.NewMemoryDatabase() - db := triedb.NewDatabase(disk, triedb.VerkleDefaults) + db := triedb.NewDatabase(disk, triedb.UBTDefaults) sdb := NewDatabase(db, nil) state, err := New(types.EmptyRootHash, sdb) diff --git a/core/state_processor.go b/core/state_processor.go index 2b9699fb78..95d35238ef 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -286,7 +286,7 @@ func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB, } // Merge the tx-local access event into the "block-local" one, in order to collect // all values, so that the witness can be built. - if statedb.Database().TrieDB().IsVerkle() { + if statedb.Database().Type().Is(state.TypeUBT) { statedb.AccessEvents().Merge(evm.AccessEvents) } return MakeReceipt(evm, result, statedb, blockNumber, blockHash, blockTime, tx, gp.CumulativeUsed(), root), nil diff --git a/core/types/hashes.go b/core/types/hashes.go index 22f1f946dc..db8912a66f 100644 --- a/core/types/hashes.go +++ b/core/types/hashes.go @@ -43,9 +43,6 @@ var ( // EmptyRequestsHash is the known hash of an empty request set, sha256(""). EmptyRequestsHash = common.HexToHash("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") - // EmptyVerkleHash is the known hash of an empty verkle trie. - EmptyVerkleHash = common.Hash{} - // EmptyBinaryHash is the known hash of an empty binary trie. EmptyBinaryHash = common.Hash{} ) diff --git a/tests/block_test_util.go b/tests/block_test_util.go index 289b5f1007..c3e1a321d9 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -145,10 +145,10 @@ func (t *BlockTest) run(snapshotter bool, scheme string, witness bool, tracer *t db = rawdb.NewMemoryDatabase() tconf = &triedb.Config{ Preimages: true, - IsVerkle: gspec.Config.IsVerkleGenesis(), + IsUBT: gspec.Config.IsVerkleGenesis(), } ) - if scheme == rawdb.PathScheme || tconf.IsVerkle { + if scheme == rawdb.PathScheme || tconf.IsUBT { tconf.PathDB = pathdb.Defaults } else { tconf.HashDB = hashdb.Defaults diff --git a/trie/bintrie/trie.go b/trie/bintrie/trie.go index b1e3c991c0..23d014eb33 100644 --- a/trie/bintrie/trie.go +++ b/trie/bintrie/trie.go @@ -377,8 +377,8 @@ func (t *BinaryTrie) Copy() *BinaryTrie { } } -// IsVerkle returns true if the trie is a Verkle tree. -func (t *BinaryTrie) IsVerkle() bool { +// IsUBT returns true if the trie is a Verkle tree. +func (t *BinaryTrie) IsUBT() bool { // TODO @gballet This is technically NOT a verkle tree, but it has the same // behavior and basic structure, so for all intents and purposes, it can be // treated as such. Rename this when verkle gets removed. diff --git a/trie/secure_trie.go b/trie/secure_trie.go index ee010dd267..bf4fcd2afc 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -330,6 +330,6 @@ func (t *StateTrie) MustNodeIterator(start []byte) NodeIterator { return t.trie.MustNodeIterator(start) } -func (t *StateTrie) IsVerkle() bool { +func (t *StateTrie) IsUBT() bool { return false } diff --git a/trie/transitiontrie/transition.go b/trie/transitiontrie/transition.go index 4c73022082..3e5511be9e 100644 --- a/trie/transitiontrie/transition.go +++ b/trie/transitiontrie/transition.go @@ -202,8 +202,8 @@ func (t *TransitionTrie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error { panic("not implemented") // TODO: Implement } -// IsVerkle returns true if the trie is verkle-tree based -func (t *TransitionTrie) IsVerkle() bool { +// IsUBT returns true if the trie is verkle-tree based +func (t *TransitionTrie) IsUBT() bool { // For all intents and purposes, the calling code should treat this as a verkle trie return true } diff --git a/triedb/database.go b/triedb/database.go index 6531a6808f..315e37edca 100644 --- a/triedb/database.go +++ b/triedb/database.go @@ -33,7 +33,7 @@ import ( // Config defines all necessary options for database. type Config struct { Preimages bool // Flag whether the preimage of node key is recorded - IsVerkle bool // Flag whether the db is holding a verkle tree + IsUBT bool // Flag whether the db is holding a verkle tree HashDB *hashdb.Config // Configs for hash-based scheme PathDB *pathdb.Config // Configs for experimental path-based scheme } @@ -42,15 +42,15 @@ type Config struct { // default settings. var HashDefaults = &Config{ Preimages: false, - IsVerkle: false, + IsUBT: false, HashDB: hashdb.Defaults, } -// VerkleDefaults represents a config for holding verkle trie data +// UBTDefaults represents a config for holding verkle trie data // using path-based scheme with default settings. -var VerkleDefaults = &Config{ +var UBTDefaults = &Config{ Preimages: false, - IsVerkle: true, + IsUBT: true, PathDB: pathdb.Defaults, } @@ -111,7 +111,7 @@ func NewDatabase(diskdb ethdb.Database, config *Config) *Database { log.Crit("Both 'hash' and 'path' mode are configured") } if config.PathDB != nil { - db.backend = pathdb.New(diskdb, config.PathDB, config.IsVerkle) + db.backend = pathdb.New(diskdb, config.PathDB, config.IsUBT) } else { db.backend = hashdb.New(diskdb, config.HashDB) } @@ -384,9 +384,9 @@ func (db *Database) IndexProgress() (uint64, uint64, error) { return pdb.IndexProgress() } -// IsVerkle returns the indicator if the database is holding a verkle tree. -func (db *Database) IsVerkle() bool { - return db.config.IsVerkle +// IsUBT returns the indicator if the database is holding a verkle tree. +func (db *Database) IsUBT() bool { + return db.config.IsUBT } // Disk returns the underlying disk database. diff --git a/triedb/pathdb/database.go b/triedb/pathdb/database.go index e9c763309d..22395ed13f 100644 --- a/triedb/pathdb/database.go +++ b/triedb/pathdb/database.go @@ -100,7 +100,7 @@ func merkleNodeHasher(blob []byte) (common.Hash, error) { // binaryNodeHasher computes the hash of the given verkle node. func binaryNodeHasher(blob []byte) (common.Hash, error) { if len(blob) == 0 { - return types.EmptyVerkleHash, nil + return types.EmptyBinaryHash, nil } n, err := bintrie.DeserializeNode(blob, 0) if err != nil { @@ -127,7 +127,7 @@ type Database struct { // the shutdown to reject all following unexpected mutations. readOnly bool // Flag if database is opened in read only mode waitSync bool // Flag if database is deactivated due to initial state sync - isVerkle bool // Flag if database is used for verkle tree + isUBT bool // Flag if database is used for verkle tree hasher nodeHasher // Trie node hasher config *Config // Configuration for database @@ -146,7 +146,7 @@ type Database struct { // New attempts to load an already existing layer from a persistent key-value // store (with a number of memory layers from a journal). If the journal is not // matched with the base persistent layer, all the recorded diff layers are discarded. -func New(diskdb ethdb.Database, config *Config, isVerkle bool) *Database { +func New(diskdb ethdb.Database, config *Config, isUBT bool) *Database { if config == nil { config = Defaults } @@ -154,7 +154,7 @@ func New(diskdb ethdb.Database, config *Config, isVerkle bool) *Database { db := &Database{ readOnly: config.ReadOnly, - isVerkle: isVerkle, + isUBT: isUBT, config: config, diskdb: diskdb, hasher: merkleNodeHasher, @@ -164,7 +164,7 @@ func New(diskdb ethdb.Database, config *Config, isVerkle bool) *Database { // important to note that the introduction of a prefix won't lead to // substantial storage overhead, as the underlying database will efficiently // compress the shared key prefix. - if isVerkle { + if isUBT { db.diskdb = rawdb.NewTable(diskdb, string(rawdb.VerklePrefix)) db.hasher = binaryNodeHasher } @@ -174,7 +174,7 @@ func New(diskdb ethdb.Database, config *Config, isVerkle bool) *Database { // Repair the history, which might not be aligned with the persistent // state in the key-value store due to an unclean shutdown. - states, trienodes, err := repairHistory(db.diskdb, isVerkle, db.config.ReadOnly, db.tree.bottom().stateID(), db.config.TrienodeHistory >= 0) + states, trienodes, err := repairHistory(db.diskdb, isUBT, db.config.ReadOnly, db.tree.bottom().stateID(), db.config.TrienodeHistory >= 0) if err != nil { log.Crit("Failed to repair history", "err", err) } @@ -196,7 +196,7 @@ func New(diskdb ethdb.Database, config *Config, isVerkle bool) *Database { db.setHistoryIndexer() fields := config.fields() - if db.isVerkle { + if db.isUBT { fields = append(fields, "verkle", true) } log.Info("Initialized path database", fields...) @@ -265,7 +265,7 @@ func (db *Database) setStateGenerator() error { // - the database is opened in read only mode // - the snapshot build is explicitly disabled // - the database is opened in verkle tree mode - noBuild := db.readOnly || db.config.SnapshotNoBuild || db.isVerkle + noBuild := db.readOnly || db.config.SnapshotNoBuild || db.isUBT // Construct the generator and link it to the disk layer, ensuring that the // generation progress is resolved to prevent accessing uncovered states @@ -412,7 +412,7 @@ func (db *Database) Enable(root common.Hash) error { // Re-construct a new disk layer backed by persistent state // and schedule the state snapshot generation if it's permitted. - db.tree.init(generateSnapshot(db, root, db.isVerkle || db.config.SnapshotNoBuild)) + db.tree.init(generateSnapshot(db, root, db.isUBT || db.config.SnapshotNoBuild)) // After snap sync, the state of the database may have changed completely. // To ensure the history indexer always matches the current state, we must: @@ -590,7 +590,7 @@ func (db *Database) journalPath() string { return "" } var fname string - if db.isVerkle { + if db.isUBT { fname = fmt.Sprintf("verkle.journal") } else { fname = fmt.Sprintf("merkle.journal") diff --git a/triedb/pathdb/database_test.go b/triedb/pathdb/database_test.go index 2de5847124..5b20ea6c63 100644 --- a/triedb/pathdb/database_test.go +++ b/triedb/pathdb/database_test.go @@ -144,7 +144,7 @@ type testerConfig struct { layers int // Number of state transitions to generate for enableIndex bool // Enable state history indexing or not journalDir string // Directory path for persisting journal files - isVerkle bool // Enables Verkle trie mode if true + isUBT bool // Enables UBT mode if true writeBuffer *int // Optional, the size of memory allocated for write buffer trieCache *int // Optional, the size of memory allocated for trie cache @@ -184,7 +184,7 @@ func newTester(t *testing.T, config *testerConfig) *tester { NoAsyncFlush: true, JournalDirectory: config.journalDir, NoHistoryIndexDelay: true, - }, config.isVerkle) + }, config.isUBT) obj = &tester{ db: db, diff --git a/triedb/pathdb/history.go b/triedb/pathdb/history.go index 0a9f7091fa..7f5b0e35ba 100644 --- a/triedb/pathdb/history.go +++ b/triedb/pathdb/history.go @@ -376,7 +376,7 @@ func syncHistory(stores ...ethdb.AncientWriter) error { // persistent state may appear if the trienode history was disabled during the // previous run. This process detects and resolves such gaps, preventing // unexpected panics. -func repairHistory(db ethdb.Database, isVerkle bool, readOnly bool, stateID uint64, enableTrienode bool) (ethdb.ResettableAncientStore, ethdb.ResettableAncientStore, error) { +func repairHistory(db ethdb.Database, isUBT bool, readOnly bool, stateID uint64, enableTrienode bool) (ethdb.ResettableAncientStore, ethdb.ResettableAncientStore, error) { ancient, err := db.AncientDatadir() if err != nil { // TODO error out if ancient store is disabled. A tons of unit tests @@ -386,7 +386,7 @@ func repairHistory(db ethdb.Database, isVerkle bool, readOnly bool, stateID uint } // State history is mandatory as it is the key component that ensures // resilience to deep reorgs. - states, err := rawdb.NewStateFreezer(ancient, isVerkle, readOnly) + states, err := rawdb.NewStateFreezer(ancient, isUBT, readOnly) if err != nil { log.Crit("Failed to open state history freezer", "err", err) } @@ -395,7 +395,7 @@ func repairHistory(db ethdb.Database, isVerkle bool, readOnly bool, stateID uint // node with state proofs. var trienodes ethdb.ResettableAncientStore if enableTrienode { - trienodes, err = rawdb.NewTrienodeFreezer(ancient, isVerkle, readOnly) + trienodes, err = rawdb.NewTrienodeFreezer(ancient, isUBT, readOnly) if err != nil { log.Crit("Failed to open trienode history freezer", "err", err) } diff --git a/triedb/pathdb/layertree_test.go b/triedb/pathdb/layertree_test.go index a85d6704dc..c75575e8c1 100644 --- a/triedb/pathdb/layertree_test.go +++ b/triedb/pathdb/layertree_test.go @@ -928,7 +928,7 @@ func TestStorageLookup(t *testing.T) { // keccak), so the disk layer's root was never the zero hash in // practice. The bug only surfaces once the disk layer root can // legitimately be zero (for example a fresh verkle/bintrie database -// where the empty binary trie hashes to EmptyVerkleHash == +// where the empty binary trie hashes to EmptyBinaryHash == // common.Hash{}). // // The test constructs a layer tree whose base layer's root IS the zero @@ -949,7 +949,7 @@ func TestStorageLookup(t *testing.T) { func TestLookupZeroBaseRootFallback(t *testing.T) { // Build a layer tree whose disk-layer root is common.Hash{} — // mirrors the bintrie/verkle configuration where the empty trie - // hashes to EmptyVerkleHash. newTestLayerTree can't be reused + // hashes to EmptyBinaryHash. newTestLayerTree can't be reused // because it hard-codes common.Hash{0x1}. db := New(rawdb.NewMemoryDatabase(), nil, false) base := newDiskLayer(common.Hash{}, 0, db, nil, nil, newBuffer(0, 0, nil, nil, 0), nil) diff --git a/triedb/pathdb/lookup.go b/triedb/pathdb/lookup.go index f774dd341f..bd83f58131 100644 --- a/triedb/pathdb/lookup.go +++ b/triedb/pathdb/lookup.go @@ -99,7 +99,7 @@ func newLookup(head layer, descendant func(state common.Hash, ancestor common.Ha // // Note the returned hash may itself be common.Hash{} when the disk layer's // root is zero — as is the case for a fresh verkle/bintrie database whose -// empty trie hashes to EmptyVerkleHash. Callers must therefore consult the +// empty trie hashes to EmptyBinaryHash. Callers must therefore consult the // boolean rather than comparing the returned hash against common.Hash{} // directly. func (l *lookup) accountTip(accountHash common.Hash, stateID common.Hash, base common.Hash) (common.Hash, bool) { diff --git a/triedb/pathdb/reader.go b/triedb/pathdb/reader.go index 0fd7a96d57..fe9c10ebf0 100644 --- a/triedb/pathdb/reader.go +++ b/triedb/pathdb/reader.go @@ -208,7 +208,7 @@ func (db *Database) NodeReader(root common.Hash) (database.NodeReader, error) { return &reader{ db: db, state: root, - noHashCheck: db.isVerkle, + noHashCheck: db.isUBT, layer: layer, }, nil }