mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-24 21:56:43 +00:00
core: impelement state database
This commit is contained in:
parent
b62c0c67fa
commit
006ad0b23c
9 changed files with 176 additions and 60 deletions
|
|
@ -288,7 +288,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 *stateDatabase // State database to reuse between imports (contains state cache)
|
||||
txIndexer *txIndexer // Transaction indexer, might be nil if not enabled
|
||||
|
||||
hc *HeaderChain
|
||||
|
|
@ -338,19 +338,18 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine,
|
|||
if cfg == nil {
|
||||
cfg = DefaultConfig()
|
||||
}
|
||||
|
||||
// Open trie database with provided config
|
||||
// Open state database with provided config
|
||||
enableVerkle, err := EnableVerkleAtGenesis(db, genesis)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
triedb := triedb.NewDatabase(db, cfg.triedbConfig(enableVerkle))
|
||||
stateDB := newStateDatabase(db, cfg)
|
||||
|
||||
// Write the supplied genesis to the database if it has not been initialized
|
||||
// yet. The corresponding chain config will be returned, either from the
|
||||
// provided genesis or from the locally stored configuration if the genesis
|
||||
// has already been initialized.
|
||||
chainConfig, genesisHash, compatErr, err := SetupGenesisBlockWithOverride(db, triedb, genesis, cfg.Overrides)
|
||||
chainConfig, genesisHash, compatErr, err := SetupGenesisBlockWithOverride(db, stateDB.triedb(enableVerkle), genesis, cfg.Overrides)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -366,7 +365,8 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine,
|
|||
chainConfig: chainConfig,
|
||||
cfg: cfg,
|
||||
db: db,
|
||||
triedb: triedb,
|
||||
stateDB: stateDB,
|
||||
triedb: stateDB.triedb(false), // TODO(rjl493456442) support verkle
|
||||
triegc: prque.New[int64, common.Hash](nil),
|
||||
chainmu: syncx.NewClosableMutex(),
|
||||
bodyCache: lru.NewCache[common.Hash, *types.Body](bodyCacheLimit),
|
||||
|
|
@ -382,7 +382,6 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine,
|
|||
return nil, err
|
||||
}
|
||||
bc.flushInterval.Store(int64(cfg.TrieTimeLimit))
|
||||
bc.statedb = state.NewDatabase(bc.triedb, nil)
|
||||
bc.validator = NewBlockValidator(chainConfig, bc)
|
||||
bc.prefetcher = newStatePrefetcher(chainConfig, bc.hc)
|
||||
bc.processor = NewStateProcessor(chainConfig, bc.hc)
|
||||
|
|
@ -548,9 +547,7 @@ func (bc *BlockChain) setupSnapshot() {
|
|||
AsyncBuild: !bc.cfg.SnapshotWait,
|
||||
}
|
||||
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.setSnapshot(bc.snaps)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -941,7 +938,7 @@ func (bc *BlockChain) rewindPathHead(head *types.Header, root common.Hash) (*typ
|
|||
// then block number zero is returned, indicating that snapshot recovery is disabled
|
||||
// and the whole snapshot should be auto-generated in case of head mismatch.
|
||||
func (bc *BlockChain) rewindHead(head *types.Header, root common.Hash) (*types.Header, uint64) {
|
||||
if bc.triedb.Scheme() == rawdb.PathScheme {
|
||||
if bc.cfg.StateScheme == rawdb.PathScheme {
|
||||
return bc.rewindPathHead(head, root)
|
||||
}
|
||||
return bc.rewindHashHead(head, root)
|
||||
|
|
@ -1097,7 +1094,7 @@ func (bc *BlockChain) SnapSyncCommitHead(hash common.Hash) error {
|
|||
}
|
||||
// Reset the trie database with the fresh snap synced state.
|
||||
root := block.Root()
|
||||
if bc.triedb.Scheme() == rawdb.PathScheme {
|
||||
if bc.cfg.StateScheme == rawdb.PathScheme {
|
||||
if err := bc.triedb.Enable(root); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -1269,7 +1266,7 @@ func (bc *BlockChain) Stop() {
|
|||
}
|
||||
bc.snaps.Release()
|
||||
}
|
||||
if bc.triedb.Scheme() == rawdb.PathScheme {
|
||||
if bc.cfg.StateScheme == rawdb.PathScheme {
|
||||
// Ensure that the in-memory trie nodes are journaled to disk properly.
|
||||
if err := bc.triedb.Journal(bc.CurrentBlock().Root); err != nil {
|
||||
log.Info("Failed to journal in-memory trie nodes", "err", err)
|
||||
|
|
@ -1312,8 +1309,8 @@ func (bc *BlockChain) Stop() {
|
|||
bc.logger.OnClose()
|
||||
}
|
||||
// Close the trie database, release all the held resources as the last step.
|
||||
if err := bc.triedb.Close(); err != nil {
|
||||
log.Error("Failed to close trie database", "err", err)
|
||||
if err := bc.stateDB.close(); err != nil {
|
||||
log.Error("Failed to close state database", "err", err)
|
||||
}
|
||||
log.Info("Blockchain stopped")
|
||||
}
|
||||
|
|
@ -1586,7 +1583,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
|
|||
}
|
||||
// If node is running in path mode, skip explicit gc operation
|
||||
// which is unnecessary in this mode.
|
||||
if bc.triedb.Scheme() == rawdb.PathScheme {
|
||||
if bc.cfg.StateScheme == rawdb.PathScheme {
|
||||
return nil
|
||||
}
|
||||
// If we're running an archive node, always flush
|
||||
|
|
@ -1956,7 +1953,8 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
|
|||
defer interrupt.Store(true) // terminate the prefetch at the end
|
||||
|
||||
if bc.cfg.NoPrefetch {
|
||||
statedb, err = state.New(parentRoot, bc.statedb)
|
||||
sdb := bc.stateDB.stateDB(bc.chainConfig.IsVerkle(block.Number(), block.Time()))
|
||||
statedb, err = state.New(parentRoot, sdb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1966,15 +1964,16 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
|
|||
//
|
||||
// Note: the main processor and prefetcher share the same reader with a local
|
||||
// cache for mitigating the overhead of state access.
|
||||
prefetch, process, err := bc.statedb.ReadersWithCacheStats(parentRoot)
|
||||
sdb := bc.stateDB.stateDB(bc.chainConfig.IsVerkle(block.Number(), block.Time()))
|
||||
prefetch, process, err := sdb.ReadersWithCacheStats(parentRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
throwaway, err := state.NewWithReader(parentRoot, bc.statedb, prefetch)
|
||||
throwaway, err := state.NewWithReader(parentRoot, sdb, prefetch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
statedb, err = state.NewWithReader(parentRoot, bc.statedb, process)
|
||||
statedb, err = state.NewWithReader(parentRoot, sdb, process)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -325,8 +325,7 @@ func (bc *BlockChain) TxIndexDone() bool {
|
|||
|
||||
// HasState checks if state trie is fully present in the database or not.
|
||||
func (bc *BlockChain) HasState(hash common.Hash) bool {
|
||||
_, err := bc.statedb.OpenTrie(hash)
|
||||
return err == nil
|
||||
return bc.stateDB.hasState(hash)
|
||||
}
|
||||
|
||||
// HasBlockAndState checks if a block and associated state trie is fully present
|
||||
|
|
@ -345,7 +344,7 @@ func (bc *BlockChain) HasBlockAndState(hash common.Hash, number uint64) bool {
|
|||
// state is not treated as recoverable if it's available, thus
|
||||
// false will be returned in this case.
|
||||
func (bc *BlockChain) stateRecoverable(root common.Hash) bool {
|
||||
if bc.triedb.Scheme() == rawdb.HashScheme {
|
||||
if bc.cfg.StateScheme == rawdb.HashScheme {
|
||||
return false
|
||||
}
|
||||
result, _ := bc.triedb.Recoverable(root)
|
||||
|
|
@ -357,7 +356,7 @@ func (bc *BlockChain) stateRecoverable(root common.Hash) bool {
|
|||
func (bc *BlockChain) ContractCodeWithPrefix(hash common.Hash) []byte {
|
||||
// TODO(rjl493456442) The associated account address is also required
|
||||
// in Verkle scheme. Fix it once snap-sync is supported for Verkle.
|
||||
return bc.statedb.ContractCodeWithPrefix(common.Address{}, hash)
|
||||
return bc.stateDB.contractCode(common.Address{}, hash)
|
||||
}
|
||||
|
||||
// State returns a new mutable state based on the current HEAD block.
|
||||
|
|
@ -367,7 +366,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, bc.statedb)
|
||||
// TODO(rjl493456442) support verkle state accessing
|
||||
return state.New(root, bc.stateDB.stateDB(false))
|
||||
}
|
||||
|
||||
// Config retrieves the chain's fork configuration.
|
||||
|
|
@ -393,7 +393,8 @@ func (bc *BlockChain) Processor() Processor {
|
|||
|
||||
// StateCache returns the caching database underpinning the blockchain instance.
|
||||
func (bc *BlockChain) StateCache() state.Database {
|
||||
return bc.statedb
|
||||
// TODO(rjl493456442) support verkle database
|
||||
return bc.stateDB.stateDB(false)
|
||||
}
|
||||
|
||||
// GasLimit returns the gas limit of the current HEAD block.
|
||||
|
|
|
|||
|
|
@ -1845,7 +1845,7 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s
|
|||
rawdb.WriteLastPivotNumber(db, *tt.pivotBlock)
|
||||
}
|
||||
// Pull the plug on the database, simulating a hard crash
|
||||
chain.triedb.Close()
|
||||
chain.stateDB.close()
|
||||
db.Close()
|
||||
chain.stopWithoutSaving()
|
||||
|
||||
|
|
@ -1971,7 +1971,7 @@ func testIssue23496(t *testing.T, scheme string) {
|
|||
}
|
||||
|
||||
// Pull the plug on the database, simulating a hard crash
|
||||
chain.triedb.Close()
|
||||
chain.stateDB.close()
|
||||
db.Close()
|
||||
chain.stopWithoutSaving()
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethdb/pebble"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
|
|
@ -2033,15 +2032,14 @@ func testSetHeadWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme
|
|||
t.Fatalf("Failed to import canonical chain tail: %v", err)
|
||||
}
|
||||
// Reopen the trie database without persisting in-memory dirty nodes.
|
||||
chain.triedb.Close()
|
||||
chain.stateDB.close()
|
||||
dbconfig := &triedb.Config{}
|
||||
if scheme == rawdb.PathScheme {
|
||||
dbconfig.PathDB = pathdb.Defaults
|
||||
} else {
|
||||
dbconfig.HashDB = hashdb.Defaults
|
||||
}
|
||||
chain.triedb = triedb.NewDatabase(chain.db, dbconfig)
|
||||
chain.statedb = state.NewDatabase(chain.triedb, chain.snaps)
|
||||
chain.stateDB = newStateDatabase(chain.db, chain.cfg)
|
||||
|
||||
// Force run a freeze cycle
|
||||
type freezer interface {
|
||||
|
|
|
|||
|
|
@ -257,7 +257,7 @@ func (snaptest *crashSnapshotTest) test(t *testing.T) {
|
|||
db := chain.db
|
||||
db.Close()
|
||||
chain.stopWithoutSaving()
|
||||
chain.triedb.Close()
|
||||
chain.stateDB.close()
|
||||
|
||||
// Start a new blockchain back up and see where the repair leads us
|
||||
pdb, err := pebble.New(snaptest.datadir, 0, 0, "", false)
|
||||
|
|
@ -416,7 +416,7 @@ func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) {
|
|||
}
|
||||
|
||||
// Simulate the blockchain crash.
|
||||
tmp.triedb.Close()
|
||||
tmp.stateDB.close()
|
||||
tmp.stopWithoutSaving()
|
||||
|
||||
newchain, err = NewBlockChain(snaptest.db, snaptest.gspec, snaptest.engine, DefaultConfig().WithStateScheme(snaptest.scheme))
|
||||
|
|
|
|||
|
|
@ -153,7 +153,8 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
|
|||
}
|
||||
return err
|
||||
}
|
||||
statedb, err := state.New(blockchain.GetBlockByHash(block.ParentHash()).Root(), blockchain.statedb)
|
||||
sdb := blockchain.stateDB.stateDB(false)
|
||||
statedb, err := state.New(blockchain.GetBlockByHash(block.ParentHash()).Root(), sdb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,14 +33,14 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
// Number of codehash->size associations to keep.
|
||||
codeSizeCacheSize = 1_000_000 // 4 megabytes in total
|
||||
// CodeSizeCacheSize is the number of codehash->size associations to keep.
|
||||
CodeSizeCacheSize = 1_000_000 // 4 megabytes in total
|
||||
|
||||
// Cache size granted for caching clean code.
|
||||
codeCacheSize = 256 * 1024 * 1024
|
||||
// CodeCacheSize is the cache size granted for caching clean code.
|
||||
CodeCacheSize = 256 * 1024 * 1024
|
||||
|
||||
// Number of address->curve point associations to keep.
|
||||
pointCacheSize = 4096
|
||||
// PointCacheSize is the number of address->curve point associations to keep.
|
||||
PointCacheSize = 4096
|
||||
)
|
||||
|
||||
// Database wraps access to tries and contract code.
|
||||
|
|
@ -159,9 +159,22 @@ func NewDatabase(triedb *triedb.Database, snap *snapshot.Tree) *CachingDB {
|
|||
disk: triedb.Disk(),
|
||||
triedb: triedb,
|
||||
snap: snap,
|
||||
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
|
||||
codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
|
||||
pointCache: utils.NewPointCache(pointCacheSize),
|
||||
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](CodeCacheSize),
|
||||
codeSizeCache: lru.NewCache[common.Hash, int](CodeSizeCacheSize),
|
||||
pointCache: utils.NewPointCache(PointCacheSize),
|
||||
}
|
||||
}
|
||||
|
||||
// NewDatabaseWithCache creates a state database with the provided database
|
||||
// and relative caches.
|
||||
func NewDatabaseWithCache(disk ethdb.KeyValueStore, triedb *triedb.Database, snap *snapshot.Tree, codeCache *lru.SizeConstrainedCache[common.Hash, []byte], codeSizeCache *lru.Cache[common.Hash, int], pointCache *utils.PointCache) *CachingDB {
|
||||
return &CachingDB{
|
||||
disk: disk,
|
||||
snap: snap,
|
||||
triedb: triedb,
|
||||
codeCache: codeCache,
|
||||
codeSizeCache: codeSizeCache,
|
||||
pointCache: pointCache,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -248,22 +261,6 @@ func (db *CachingDB) OpenStorageTrie(stateRoot common.Hash, address common.Addre
|
|||
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
|
||||
|
|
|
|||
117
core/state_database.go
Normal file
117
core/state_database.go
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
// 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 core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"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"
|
||||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/trie/utils"
|
||||
"github.com/ethereum/go-ethereum/triedb"
|
||||
)
|
||||
|
||||
// stateDatabase is the central structure for managing state data,
|
||||
// including both the Merkle-Patricia Trie and the Verkle tree (post-transition).
|
||||
type stateDatabase struct {
|
||||
disk ethdb.Database
|
||||
merkle *triedb.Database
|
||||
verkle *triedb.Database
|
||||
snap *snapshot.Tree
|
||||
|
||||
// Various caches
|
||||
codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
|
||||
codeSizeCache *lru.Cache[common.Hash, int]
|
||||
pointCache *utils.PointCache
|
||||
}
|
||||
|
||||
// newStateDatabase initializes a new stateDatabase instance, creating
|
||||
// both Merkle and Verkle trie databases.
|
||||
func newStateDatabase(disk ethdb.Database, cfg *BlockChainConfig) *stateDatabase {
|
||||
return &stateDatabase{
|
||||
disk: disk,
|
||||
merkle: triedb.NewDatabase(disk, cfg.triedbConfig(false)),
|
||||
verkle: triedb.NewDatabase(disk, cfg.triedbConfig(true)),
|
||||
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](state.CodeCacheSize),
|
||||
codeSizeCache: lru.NewCache[common.Hash, int](state.CodeSizeCacheSize),
|
||||
pointCache: utils.NewPointCache(state.PointCacheSize),
|
||||
}
|
||||
}
|
||||
|
||||
// setSnapshot assigns a snapshot tree to the state database.
|
||||
func (s *stateDatabase) setSnapshot(snap *snapshot.Tree) {
|
||||
s.snap = snap
|
||||
}
|
||||
|
||||
// triedb returns the appropriate trie database based on the given flag.
|
||||
func (s *stateDatabase) triedb(isVerkle bool) *triedb.Database {
|
||||
if isVerkle {
|
||||
return s.verkle
|
||||
}
|
||||
return s.merkle
|
||||
}
|
||||
|
||||
// stateDB returns the appropriate state database based on the given flag.
|
||||
func (s *stateDatabase) stateDB(isVerkle bool) *state.CachingDB {
|
||||
if isVerkle {
|
||||
// Verkle is compatible only with path mode; snapshot is integrated natively.
|
||||
return state.NewDatabaseWithCache(s.disk, s.verkle, nil, s.codeCache, s.codeSizeCache, s.pointCache)
|
||||
}
|
||||
return state.NewDatabaseWithCache(s.disk, s.merkle, s.snap, s.codeCache, s.codeSizeCache, s.pointCache)
|
||||
}
|
||||
|
||||
// contractCode retrieves the contract code by its hash. If not present in the
|
||||
// cache, it falls back to reading from disk using the contract database prefix.
|
||||
func (s *stateDatabase) contractCode(address common.Address, codeHash common.Hash) []byte {
|
||||
code, _ := s.codeCache.Get(codeHash)
|
||||
if len(code) > 0 {
|
||||
return code
|
||||
}
|
||||
code = rawdb.ReadCodeWithPrefix(s.disk, codeHash)
|
||||
if len(code) > 0 {
|
||||
s.codeCache.Add(codeHash, code)
|
||||
s.codeSizeCache.Add(codeHash, len(code))
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
// hasState checks whether a trie state exists for the given root hash
|
||||
// in either the Merkle or Verkle database.
|
||||
func (s *stateDatabase) hasState(root common.Hash) bool {
|
||||
_, err := s.merkle.NodeReader(root)
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
_, err = s.verkle.NodeReader(root)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// close terminates the state database and closes two internal trie databases.
|
||||
func (s *stateDatabase) close() error {
|
||||
var errs []error
|
||||
if err := s.merkle.Close(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if err := s.verkle.Close(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
|
@ -612,6 +612,9 @@ func (db *Database) Close() error {
|
|||
// NodeReader returns a reader for accessing trie nodes within the specified state.
|
||||
// An error will be returned if the specified state is not available.
|
||||
func (db *Database) NodeReader(root common.Hash) (database.NodeReader, error) {
|
||||
if root == types.EmptyRootHash {
|
||||
return &reader{db: db}, nil
|
||||
}
|
||||
if _, err := db.node(root); err != nil {
|
||||
return nil, fmt.Errorf("state %#x is not available, %v", root, err)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue