diff --git a/core/blockchain.go b/core/blockchain.go index b01d56307b..c3c7263be7 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2116,11 +2116,29 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash, startTime = time.Now() statedb *state.StateDB interrupt atomic.Bool - sdb = state.NewDatabase(bc.triedb, bc.codedb).WithSnapshot(bc.snaps) + sdb state.Database ) defer interrupt.Store(true) // terminate the prefetch at the end - if bc.cfg.NoPrefetch { + if bc.chainConfig.IsUBT(block.Number(), block.Time()) { + sdb = state.NewUBTDB(bc.triedb, bc.codedb) + } else { + sdb = state.NewMerkleDB(bc.triedb, bc.codedb).WithSnapshot(bc.snaps) + } + // If prefetching is enabled, run that against the current state to pre-cache + // transactions and probabilistically some of the account/storage trie nodes. + // + // Note: the main processor and prefetcher share the same reader with a local + // cache for mitigating the overhead of state access. + type prewarmReader interface { + // ReadersWithCacheStats creates a pair of state readers that share the + // same underlying state reader and internal state cache, while maintaining + // separate statistics respectively. + ReadersWithCacheStats(stateRoot common.Hash) (state.Reader, state.Reader, error) + } + warmer, ok := sdb.(prewarmReader) + + if bc.cfg.NoPrefetch || !ok { statedb, err = state.New(parentRoot, sdb) if err != nil { return nil, err @@ -2131,7 +2149,7 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash, // // Note: the main processor and prefetcher share the same reader with a local // cache for mitigating the overhead of state access. - prefetch, process, err := sdb.ReadersWithCacheStats(parentRoot) + prefetch, process, err := warmer.ReadersWithCacheStats(parentRoot) if err != nil { return nil, err } diff --git a/core/blockchain_reader.go b/core/blockchain_reader.go index 3614702d1a..0822ad6274 100644 --- a/core/blockchain_reader.go +++ b/core/blockchain_reader.go @@ -421,7 +421,8 @@ func (bc *BlockChain) State() (*state.StateDB, error) { // StateAt returns a new mutable state based on a particular point in time. func (bc *BlockChain) StateAt(root common.Hash) (*state.StateDB, error) { - return state.New(root, state.NewDatabase(bc.triedb, bc.codedb).WithSnapshot(bc.snaps)) + // TODO(rjl493456442) support ubt later + return state.New(root, state.NewMerkleDB(bc.triedb, bc.codedb).WithSnapshot(bc.snaps)) } // HistoricState returns a historic state specified by the given root. diff --git a/core/state/database.go b/core/state/database.go index 1107d7bd38..ec4dd3d646 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -23,9 +23,7 @@ import ( "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/log" "github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie/transitiontrie" "github.com/ethereum/go-ethereum/trie/trienode" @@ -55,20 +53,8 @@ type Database interface { // if the commit fails. Commit(update *stateUpdate) error - // WithSnapshot configures the snapshot tree. This registration must be - // performed before the database is used. - WithSnapshot(snap *snapshot.Tree) Database - // Snapshot returns the underlying state snapshot. Snapshot() *snapshot.Tree - - // StateReader returns a state reader associated with the specified state root. - StateReader(stateRoot common.Hash) (StateReader, error) - - // ReadersWithCacheStats creates a pair of state readers that share the same - // underlying state reader and internal state cache, while maintaining separate - // statistics respectively. - ReadersWithCacheStats(stateRoot common.Hash) (Reader, Reader, error) } // Trie is a Ethereum Merkle Patricia trie. @@ -156,31 +142,14 @@ type Trie interface { IsUBT() bool } -// MerkleDB 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 MerkleDB struct { - triedb *triedb.Database - codedb *CodeDB - snap *snapshot.Tree -} - // NewDatabase creates a state database with the provided data sources. +// +// Deprecated, please use NewMerkleDB or NewUBTDB directly. func NewDatabase(tdb *triedb.Database, codedb *CodeDB) Database { - if codedb == nil { - codedb = NewCodeDB(tdb.Disk()) - } if tdb.IsUBT() { - return &UBTDB{ - triedb: tdb, - codedb: codedb, - } - } - return &MerkleDB{ - triedb: tdb, - codedb: codedb, + return NewUBTDB(tdb, codedb) } + return NewMerkleDB(tdb, codedb) } // NewDatabaseForTesting is similar to NewDatabase, but it initializes the caching @@ -190,139 +159,6 @@ func NewDatabaseForTesting() Database { return NewDatabase(triedb.NewDatabase(db, nil), NewCodeDB(db)) } -// WithSnapshot configures the provided contract code cache. Note that this -// registration must be performed before the MPTDB is used. -func (db *MerkleDB) WithSnapshot(snapshot *snapshot.Tree) Database { - db.snap = snapshot - return db -} - -// StateReader returns a state reader associated with the specified state root. -func (db *MerkleDB) StateReader(stateRoot common.Hash) (StateReader, 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. - if db.TrieDB().Scheme() == rawdb.HashScheme && db.snap != nil { - 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 { - 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) - - return newMultiStateReader(readers...) -} - -// Reader implements Database, returning a reader associated with the specified -// state root. -func (db *MerkleDB) Reader(stateRoot common.Hash) (Reader, error) { - sr, err := db.StateReader(stateRoot) - if err != nil { - return nil, err - } - return newReader(db.codedb.Reader(), sr), nil -} - -// ReadersWithCacheStats creates a pair of state readers that share the same -// underlying state reader and internal state cache, while maintaining separate -// statistics respectively. -func (db *MerkleDB) ReadersWithCacheStats(stateRoot common.Hash) (Reader, Reader, error) { - r, err := db.StateReader(stateRoot) - if err != nil { - return nil, nil, err - } - sr := newStateReaderWithCache(r) - ra := newReader(db.codedb.Reader(), newStateReaderWithStats(sr)) - rb := newReader(db.codedb.Reader(), newStateReaderWithStats(sr)) - return ra, rb, nil -} - -// OpenTrie opens the main account trie at a specific root hash. -func (db *MerkleDB) 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 *MerkleDB) 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 -} - -// TrieDB retrieves any intermediate trie-node caching layer. -func (db *MerkleDB) TrieDB() *triedb.Database { - return db.triedb -} - -// Snapshot returns the underlying state snapshot. -func (db *MerkleDB) Snapshot() *snapshot.Tree { - return db.snap -} - -// Commit flushes all pending writes and finalizes the state transition, -// committing the changes to the underlying storage. It returns an error -// if the commit fails. -func (db *MerkleDB) Commit(update *stateUpdate) error { - // Short circuit if nothing to commit - if update.empty() { - return nil - } - // Commit dirty contract code if any exists - if len(update.codes) > 0 { - batch := db.codedb.NewBatchWithSize(len(update.codes)) - for _, code := range update.codes { - batch.Put(code.hash, code.blob) - } - if err := batch.Commit(); err != nil { - return err - } - } - // If snapshotting is enabled, update the snapshot tree with this new version - if db.snap != nil && db.snap.Snapshot(update.originRoot) != nil { - if err := db.snap.Update(update.root, update.originRoot, update.accounts, update.storages); err != nil { - log.Warn("Failed to update snapshot tree", "from", update.originRoot, "to", update.root, "err", err) - } - // Keep 128 diff layers in the memory, persistent layer is 129th. - // - head layer is paired with HEAD state - // - head-1 layer is paired with HEAD-1 state - // - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state - if err := db.snap.Cap(update.root, TriesInMemory); err != nil { - log.Warn("Failed to cap snapshot tree", "root", update.root, "layers", TriesInMemory, "err", err) - } - } - return db.triedb.Update(update.root, update.originRoot, update.blockNumber, update.nodes, update.stateSet()) -} - -// Iteratee returns a state iteratee associated with the specified state root, -// through which the account iterator and storage iterator can be created. -func (db *MerkleDB) Iteratee(root common.Hash) (Iteratee, error) { - return newStateIteratee(true, root, db.triedb, db.snap) -} - // mustCopyTrie returns a deep-copied trie. func mustCopyTrie(t Trie) Trie { switch t := t.(type) { diff --git a/core/state/database_history.go b/core/state/database_history.go index f1b6c29226..a78d4c304b 100644 --- a/core/state/database_history.go +++ b/core/state/database_history.go @@ -316,8 +316,3 @@ func (db *HistoricDB) Snapshot() *snapshot.Tree { func (db *HistoricDB) StateReader(stateRoot common.Hash) (StateReader, error) { return nil, errors.New("not implemented") } - -// ReadersWithCacheStats is not supported by historic database. -func (db *HistoricDB) ReadersWithCacheStats(stateRoot common.Hash) (Reader, Reader, error) { - return nil, nil, errors.New("not implemented") -} diff --git a/core/state/database_merkle.go b/core/state/database_merkle.go new file mode 100644 index 0000000000..d43adc52dd --- /dev/null +++ b/core/state/database_merkle.go @@ -0,0 +1,181 @@ +// 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/core/rawdb" + "github.com/ethereum/go-ethereum/core/state/snapshot" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/triedb" +) + +// MerkleDB 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 MerkleDB struct { + triedb *triedb.Database + codedb *CodeDB + snap *snapshot.Tree +} + +// NewMerkleDB creates a state database with the Merkle Patricia Trie manner. +func NewMerkleDB(tdb *triedb.Database, codedb *CodeDB) *MerkleDB { + if codedb == nil { + codedb = NewCodeDB(tdb.Disk()) + } + return &MerkleDB{ + triedb: tdb, + codedb: codedb, + } +} + +// WithSnapshot configures the provided contract code cache. Note that this +// registration must be performed before the MerkleDB is used. +func (db *MerkleDB) WithSnapshot(snapshot *snapshot.Tree) Database { + db.snap = snapshot + return db +} + +// StateReader returns a state reader associated with the specified state root. +func (db *MerkleDB) StateReader(stateRoot common.Hash) (StateReader, 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. + if db.TrieDB().Scheme() == rawdb.HashScheme && db.snap != nil { + 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 { + 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) + + return newMultiStateReader(readers...) +} + +// Reader implements Database, returning a reader associated with the specified +// state root. +func (db *MerkleDB) Reader(stateRoot common.Hash) (Reader, error) { + sr, err := db.StateReader(stateRoot) + if err != nil { + return nil, err + } + return newReader(db.codedb.Reader(), sr), nil +} + +// ReadersWithCacheStats creates a pair of state readers that share the same +// underlying state reader and internal state cache, while maintaining separate +// statistics respectively. +func (db *MerkleDB) ReadersWithCacheStats(stateRoot common.Hash) (Reader, Reader, error) { + r, err := db.StateReader(stateRoot) + if err != nil { + return nil, nil, err + } + sr := newStateReaderWithCache(r) + ra := newReader(db.codedb.Reader(), newStateReaderWithStats(sr)) + rb := newReader(db.codedb.Reader(), newStateReaderWithStats(sr)) + return ra, rb, nil +} + +// OpenTrie opens the main account trie at a specific root hash. +func (db *MerkleDB) 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 *MerkleDB) 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 +} + +// TrieDB retrieves any intermediate trie-node caching layer. +func (db *MerkleDB) TrieDB() *triedb.Database { + return db.triedb +} + +// Snapshot returns the underlying state snapshot. +func (db *MerkleDB) Snapshot() *snapshot.Tree { + return db.snap +} + +// Commit flushes all pending writes and finalizes the state transition, +// committing the changes to the underlying storage. It returns an error +// if the commit fails. +func (db *MerkleDB) Commit(update *stateUpdate) error { + // Short circuit if nothing to commit + if update.empty() { + return nil + } + // Commit dirty contract code if any exists + if len(update.codes) > 0 { + batch := db.codedb.NewBatchWithSize(len(update.codes)) + for _, code := range update.codes { + batch.Put(code.hash, code.blob) + } + if err := batch.Commit(); err != nil { + return err + } + } + // If snapshotting is enabled, update the snapshot tree with this new version + if db.snap != nil && db.snap.Snapshot(update.originRoot) != nil { + if err := db.snap.Update(update.root, update.originRoot, update.accounts, update.storages); err != nil { + log.Warn("Failed to update snapshot tree", "from", update.originRoot, "to", update.root, "err", err) + } + // Keep 128 diff layers in the memory, persistent layer is 129th. + // - head layer is paired with HEAD state + // - head-1 layer is paired with HEAD-1 state + // - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state + if err := db.snap.Cap(update.root, TriesInMemory); err != nil { + log.Warn("Failed to cap snapshot tree", "root", update.root, "layers", TriesInMemory, "err", err) + } + } + return db.triedb.Update(update.root, update.originRoot, update.blockNumber, update.nodes, update.stateSet()) +} + +// Iteratee returns a state iteratee associated with the specified state root, +// through which the account iterator and storage iterator can be created. +func (db *MerkleDB) 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 index 5a1e90f250..28b233479b 100644 --- a/core/state/database_ubt.go +++ b/core/state/database_ubt.go @@ -26,12 +26,25 @@ import ( ) // UBTDB is an implementation of Database interface for Universal Binary Tries. +// It provides the same functionality as MerkleDB but uses binary tries for state +// storage instead of Merkle Patricia Tries. type UBTDB struct { triedb *triedb.Database codedb *CodeDB snap *snapshot.Tree } +// NewUBTDB creates a state database with the Unified binary trie manner. +func NewUBTDB(triedb *triedb.Database, codedb *CodeDB) *UBTDB { + if codedb == nil { + codedb = NewCodeDB(triedb.Disk()) + } + return &UBTDB{ + triedb: triedb, + codedb: codedb, + } +} + // WithSnapshot configures the snapshot tree. Note that this registration must // be performed before the UBTDB is used. func (db *UBTDB) WithSnapshot(snapshot *snapshot.Tree) Database {