mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-10 10:06:47 +00:00
This Pr implements some prerequisite changes for #34004 : split the `CachingDB` into a `MerkleDB` and a `UBTDB`, so that very different behaviors don't clash as much. The transition isn't handled by this PR, but after talking to Gary we agreed that `UBTDB` should receive another `triedb`, which will only be loaded if the `Ended` flag is set to false in the conversion contract. If this is too hard to achieve, it makes sense to load it regardless, and then loading can be prevented at a later stage by adding a `UBTTransitionFinalizationTime` in `ChainConfig`. --------- Co-authored-by: Gary Rong <garyrong0905@gmail.com>
178 lines
6.5 KiB
Go
178 lines
6.5 KiB
Go
// 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 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"
|
|
)
|
|
|
|
// MPTDatabase is an implementation of Database interface for Merkle Patricia Tries.
|
|
// It leverages both trie and state snapshot to provide functionalities for state
|
|
// access.
|
|
type MPTDatabase struct {
|
|
triedb *triedb.Database
|
|
codedb *CodeDB
|
|
snap *snapshot.Tree
|
|
}
|
|
|
|
// Type returns Merkle, 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, codedb *CodeDB) *MPTDatabase {
|
|
if codedb == nil {
|
|
codedb = NewCodeDB(tdb.Disk())
|
|
}
|
|
return &MPTDatabase{
|
|
triedb: tdb,
|
|
codedb: codedb,
|
|
}
|
|
}
|
|
|
|
// WithSnapshot configures the provided state snapshot. Note that this
|
|
// registration must be performed before the MPTDatabase is used.
|
|
func (db *MPTDatabase) WithSnapshot(snapshot *snapshot.Tree) Database {
|
|
db.snap = snapshot
|
|
return db
|
|
}
|
|
|
|
// StateReader returns a state reader associated with the specified state root.
|
|
func (db *MPTDatabase) 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 *MPTDatabase) 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 *MPTDatabase) 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 *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
|
|
}
|
|
|
|
// TrieDB retrieves any intermediate trie-node caching layer.
|
|
func (db *MPTDatabase) TrieDB() *triedb.Database {
|
|
return db.triedb
|
|
}
|
|
|
|
// 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 *MPTDatabase) 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 *MPTDatabase) Iteratee(root common.Hash) (Iteratee, error) {
|
|
return newStateIteratee(true, root, db.triedb, db.snap)
|
|
}
|