diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go
index 5303d432fb6b..5a96c17b5eb8 100644
--- a/cmd/evm/internal/t8ntool/execution.go
+++ b/cmd/evm/internal/t8ntool/execution.go
@@ -29,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/state/codedb"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
@@ -373,7 +374,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
func MakePreState(db ethdb.Database, accounts types.GenesisAlloc) *state.StateDB {
tdb := triedb.NewDatabase(db, &triedb.Config{Preimages: true})
- sdb := state.NewDatabase(tdb, nil)
+ sdb := state.NewDatabase(tdb, codedb.New(db))
statedb, err := state.New(types.EmptyRootHash, sdb)
if err != nil {
panic(fmt.Errorf("failed to create initial state: %v", err))
diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go
index ebb3e044615d..eba53c00fb6e 100644
--- a/cmd/evm/runner.go
+++ b/cmd/evm/runner.go
@@ -35,6 +35,7 @@ import (
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/state/codedb"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
@@ -226,8 +227,9 @@ func runCmd(ctx *cli.Context) error {
HashDB: hashdb.Defaults,
})
defer triedb.Close()
+
genesis := genesisConfig.MustCommit(db, triedb)
- sdb := state.NewDatabase(triedb, nil)
+ sdb := state.NewDatabase(triedb, codedb.New(db))
prestate, _ = state.New(genesis.Root(), sdb)
chainConfig = genesisConfig.Config
diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go
index c5145bbfb738..a30dc6d65867 100644
--- a/cmd/geth/chaincmd.go
+++ b/cmd/geth/chaincmd.go
@@ -37,6 +37,7 @@ import (
"github.com/ethereum/go-ethereum/core/history"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/state/codedb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
@@ -649,7 +650,7 @@ func dump(ctx *cli.Context) error {
triedb := utils.MakeTrieDatabase(ctx, stack, db, true, true, false) // always enable preimage lookup
defer triedb.Close()
- state, err := state.New(root, state.NewDatabase(triedb, nil))
+ state, err := state.New(root, state.NewDatabase(triedb, codedb.New(db)))
if err != nil {
return err
}
diff --git a/core/blockchain.go b/core/blockchain.go
index b7acd12aca78..a21ddf4407c3 100644
--- a/core/blockchain.go
+++ b/core/blockchain.go
@@ -39,6 +39,7 @@ import (
"github.com/ethereum/go-ethereum/core/history"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/state/codedb"
"github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/stateless"
"github.com/ethereum/go-ethereum/core/tracing"
@@ -72,9 +73,10 @@ var (
accountUpdateTimer = metrics.NewRegisteredResettingTimer("chain/account/updates", nil)
accountCommitTimer = metrics.NewRegisteredResettingTimer("chain/account/commits", nil)
- storageReadTimer = metrics.NewRegisteredResettingTimer("chain/storage/reads", nil)
- storageUpdateTimer = metrics.NewRegisteredResettingTimer("chain/storage/updates", nil)
- storageCommitTimer = metrics.NewRegisteredResettingTimer("chain/storage/commits", nil)
+ storageReadTimer = metrics.NewRegisteredResettingTimer("chain/storage/reads", nil)
+ storageUpdateTimer = metrics.NewRegisteredResettingTimer("chain/storage/updates", nil)
+ storageCommitTimer = metrics.NewRegisteredResettingTimer("chain/storage/commits", nil)
+ databaseCommitTimer = metrics.NewRegisteredResettingTimer("chain/database/commits", nil)
accountCacheHitMeter = metrics.NewRegisteredMeter("chain/account/reads/cache/process/hit", nil)
accountCacheMissMeter = metrics.NewRegisteredMeter("chain/account/reads/cache/process/miss", nil)
@@ -89,9 +91,6 @@ var (
accountReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/account/single/reads", nil)
storageReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/storage/single/reads", nil)
- snapshotCommitTimer = metrics.NewRegisteredResettingTimer("chain/snapshot/commits", nil)
- triedbCommitTimer = metrics.NewRegisteredResettingTimer("chain/triedb/commits", nil)
-
blockInsertTimer = metrics.NewRegisteredResettingTimer("chain/inserts", nil)
blockValidationTimer = metrics.NewRegisteredResettingTimer("chain/validation", nil)
blockCrossValidationTimer = metrics.NewRegisteredResettingTimer("chain/crossvalidation", nil)
@@ -296,7 +295,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)
+ codedb *codedb.Database // The database handler for maintaining contract codes
txIndexer *txIndexer // Transaction indexer, might be nil if not enabled
hc *HeaderChain
@@ -376,6 +375,7 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine,
cfg: cfg,
db: db,
triedb: triedb,
+ codedb: codedb.New(db),
triegc: prque.New[int64, common.Hash](nil),
chainmu: syncx.NewClosableMutex(),
bodyCache: lru.NewCache[common.Hash, *types.Body](bodyCacheLimit),
@@ -391,7 +391,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(bc.hc)
@@ -568,9 +567,6 @@ 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)
}
}
@@ -1989,11 +1985,12 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
startTime = time.Now()
statedb *state.StateDB
interrupt atomic.Bool
+ sdb = state.NewDatabase(bc.triedb, bc.codedb).WithSnapshot(bc.snaps)
)
defer interrupt.Store(true) // terminate the prefetch at the end
if bc.cfg.NoPrefetch {
- statedb, err = state.New(parentRoot, bc.statedb)
+ statedb, err = state.New(parentRoot, sdb)
if err != nil {
return nil, err
}
@@ -2003,15 +2000,15 @@ 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)
+ 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
}
@@ -2167,10 +2164,9 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
// Update the metrics touched during block commit
accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them
storageCommitTimer.Update(statedb.StorageCommits) // Storage commits are complete, we can mark them
- snapshotCommitTimer.Update(statedb.SnapshotCommits) // Snapshot commits are complete, we can mark them
- triedbCommitTimer.Update(statedb.TrieDBCommits) // Trie database commits are complete, we can mark them
+ databaseCommitTimer.Update(statedb.DatabaseCommits) // Database commits are complete, we can mark them
- blockWriteTimer.Update(time.Since(wstart) - max(statedb.AccountCommits, statedb.StorageCommits) /* concurrent */ - statedb.SnapshotCommits - statedb.TrieDBCommits)
+ blockWriteTimer.Update(time.Since(wstart) - max(statedb.AccountCommits, statedb.StorageCommits) /* concurrent */ - statedb.DatabaseCommits)
elapsed := time.Since(startTime) + 1 // prevent zero division
blockInsertTimer.Update(elapsed)
diff --git a/core/blockchain_reader.go b/core/blockchain_reader.go
index 4894523b0e58..c79b5bfe1516 100644
--- a/core/blockchain_reader.go
+++ b/core/blockchain_reader.go
@@ -26,6 +26,7 @@ import (
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/state/codedb"
"github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
@@ -371,7 +372,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)
+ _, err := bc.triedb.NodeReader(hash)
return err == nil
}
@@ -403,7 +404,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.codedb.Reader().CodeWithPrefix(common.Address{}, hash)
}
// State returns a new mutable state based on the current HEAD block.
@@ -413,14 +414,14 @@ 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)
+ return state.New(root, state.NewDatabase(bc.triedb, bc.codedb).WithSnapshot(bc.snaps))
}
// HistoricState returns a historic state specified by the given root.
// Live states are not available and won't be served, please use `State`
// or `StateAt` instead.
func (bc *BlockChain) HistoricState(root common.Hash) (*state.StateDB, error) {
- return state.New(root, state.NewHistoricDatabase(bc.db, bc.triedb))
+ return state.New(root, state.NewHistoricDatabase(bc.triedb, bc.codedb))
}
// Config retrieves the chain's fork configuration.
@@ -444,11 +445,6 @@ func (bc *BlockChain) Processor() Processor {
return bc.processor
}
-// StateCache returns the caching database underpinning the blockchain instance.
-func (bc *BlockChain) StateCache() state.Database {
- return bc.statedb
-}
-
// GasLimit returns the gas limit of the current HEAD block.
func (bc *BlockChain) GasLimit() uint64 {
return bc.CurrentBlock().GasLimit
@@ -492,6 +488,11 @@ func (bc *BlockChain) TrieDB() *triedb.Database {
return bc.triedb
}
+// CodeDB retrieves the low level contract code database used for data storage.
+func (bc *BlockChain) CodeDB() *codedb.Database {
+ return bc.codedb
+}
+
// HeaderChain returns the underlying header chain.
func (bc *BlockChain) HeaderChain() *HeaderChain {
return bc.hc
diff --git a/core/blockchain_sethead_test.go b/core/blockchain_sethead_test.go
index 72ca15d7f614..f2fbc003f1cc 100644
--- a/core/blockchain_sethead_test.go
+++ b/core/blockchain_sethead_test.go
@@ -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"
@@ -2041,7 +2040,6 @@ 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)
// Force run a freeze cycle
type freezer interface {
diff --git a/core/blockchain_test.go b/core/blockchain_test.go
index b749798f9c32..8a93526f4587 100644
--- a/core/blockchain_test.go
+++ b/core/blockchain_test.go
@@ -156,7 +156,8 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
}
return err
}
- statedb, err := state.New(blockchain.GetBlockByHash(block.ParentHash()).Root(), blockchain.statedb)
+ sdb := state.NewDatabase(blockchain.triedb, blockchain.codedb)
+ statedb, err := state.New(blockchain.GetBlockByHash(block.ParentHash()).Root(), sdb)
if err != nil {
return err
}
diff --git a/core/chain_makers.go b/core/chain_makers.go
index af55716cca3e..f712a29f0f0f 100644
--- a/core/chain_makers.go
+++ b/core/chain_makers.go
@@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/state/codedb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/ethdb"
@@ -429,9 +430,10 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
// Forcibly use hash-based state scheme for retaining all nodes in disk.
triedb := triedb.NewDatabase(db, triedb.HashDefaults)
defer triedb.Close()
+ codedb := codedb.New(db)
for i := 0; i < n; i++ {
- statedb, err := state.New(parent.Root(), state.NewDatabase(triedb, nil))
+ statedb, err := state.New(parent.Root(), state.NewDatabase(triedb, codedb))
if err != nil {
panic(err)
}
@@ -540,7 +542,7 @@ func GenerateVerkleChain(config *params.ChainConfig, parent *types.Block, engine
return block, b.receipts
}
- sdb := state.NewDatabase(trdb, nil)
+ sdb := state.NewDatabase(trdb, codedb.New(db))
for i := 0; i < n; i++ {
statedb, err := state.New(parent.Root(), sdb)
diff --git a/core/genesis.go b/core/genesis.go
index d0d490874d07..e29603525a5c 100644
--- a/core/genesis.go
+++ b/core/genesis.go
@@ -29,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/state/codedb"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
@@ -145,7 +146,7 @@ func hashAlloc(ga *types.GenesisAlloc, isVerkle bool) (common.Hash, error) {
emptyRoot = types.EmptyVerkleHash
}
db := rawdb.NewMemoryDatabase()
- statedb, err := state.New(emptyRoot, state.NewDatabase(triedb.NewDatabase(db, config), nil))
+ statedb, err := state.New(emptyRoot, state.NewDatabase(triedb.NewDatabase(db, config), codedb.New(db)))
if err != nil {
return common.Hash{}, err
}
@@ -164,12 +165,12 @@ func hashAlloc(ga *types.GenesisAlloc, isVerkle bool) (common.Hash, error) {
// flushAlloc is very similar with hash, but the main difference is all the
// generated states will be persisted into the given database.
-func flushAlloc(ga *types.GenesisAlloc, triedb *triedb.Database) (common.Hash, error) {
+func flushAlloc(ga *types.GenesisAlloc, db ethdb.KeyValueStore, triedb *triedb.Database) (common.Hash, error) {
emptyRoot := types.EmptyRootHash
if triedb.IsVerkle() {
emptyRoot = types.EmptyVerkleHash
}
- statedb, err := state.New(emptyRoot, state.NewDatabase(triedb, nil))
+ statedb, err := state.New(emptyRoot, state.NewDatabase(triedb, codedb.New(db)))
if err != nil {
return common.Hash{}, err
}
@@ -552,7 +553,7 @@ func (g *Genesis) Commit(db ethdb.Database, triedb *triedb.Database) (*types.Blo
return nil, errors.New("can't start clique chain without signers")
}
// flush the data to disk and compute the state root
- root, err := flushAlloc(&g.Alloc, triedb)
+ root, err := flushAlloc(&g.Alloc, db, triedb)
if err != nil {
return nil, err
}
diff --git a/core/state/codedb/database.go b/core/state/codedb/database.go
new file mode 100644
index 000000000000..4267bd270301
--- /dev/null
+++ b/core/state/codedb/database.go
@@ -0,0 +1,183 @@
+// 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 .
+
+package codedb
+
+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/ethdb"
+)
+
+const (
+ // Number of codeHash->size associations to keep.
+ codeSizeCacheSize = 1_000_000
+
+ // Cache size granted for caching clean code.
+ codeCacheSize = 256 * 1024 * 1024
+)
+
+// Cache maintains cached contract code that is shared across blocks, enabling
+// fast access for external calls such as RPCs and state transitions.
+//
+// It is thread-safe and has a bounded size.
+type Cache struct {
+ codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
+ codeSizeCache *lru.Cache[common.Hash, int]
+}
+
+// NewCache initializes the contract code cache with the predefined capacity.
+func NewCache() *Cache {
+ return &Cache{
+ codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
+ codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
+ }
+}
+
+// Get returns the contract code associated with the provided code hash.
+func (c *Cache) Get(hash common.Hash) ([]byte, bool) {
+ return c.codeCache.Get(hash)
+}
+
+// GetSize returns the contract code size associated with the provided code hash.
+func (c *Cache) GetSize(hash common.Hash) (int, bool) {
+ return c.codeSizeCache.Get(hash)
+}
+
+// Put adds the provided contract code along with its size information into the cache.
+func (c *Cache) Put(hash common.Hash, code []byte) {
+ c.codeCache.Add(hash, code)
+ c.codeSizeCache.Add(hash, len(code))
+}
+
+// Reader implements state.ContractCodeReader, accessing contract code either in
+// local key-value store or the shared code cache.
+//
+// Reader is safe for concurrent access.
+type Reader struct {
+ db ethdb.KeyValueReader
+ cache *Cache
+}
+
+// newReader constructs the code reader with provided key value store and the cache.
+func newReader(db ethdb.KeyValueReader, cache *Cache) *Reader {
+ return &Reader{
+ db: db,
+ cache: cache,
+ }
+}
+
+// Code implements state.ContractCodeReader, retrieving a particular contract's code.
+// Null is returned if the contract code is not present.
+func (r *Reader) Code(addr common.Address, codeHash common.Hash) []byte {
+ code, _ := r.cache.Get(codeHash)
+ if len(code) > 0 {
+ return code
+ }
+ code = rawdb.ReadCode(r.db, codeHash)
+ if len(code) > 0 {
+ r.cache.Put(codeHash, code)
+ }
+ return code
+}
+
+// CodeSize implements state.ContractCodeReader, retrieving a particular contract
+// code's size. Zero is returned if the contract code is not present.
+func (r *Reader) CodeSize(addr common.Address, codeHash common.Hash) int {
+ if cached, ok := r.cache.GetSize(codeHash); ok {
+ return cached
+ }
+ return len(r.Code(addr, codeHash))
+}
+
+// CodeWithPrefix retrieves the contract code for the specified account address
+// and code hash. It is almost identical to Code, but uses rawdb.ReadCodeWithPrefix
+// for database lookups. The intention is to gradually deprecate the old
+// contract code scheme.
+func (r *Reader) CodeWithPrefix(addr common.Address, codeHash common.Hash) []byte {
+ code, _ := r.cache.Get(codeHash)
+ if len(code) > 0 {
+ return code
+ }
+ code = rawdb.ReadCodeWithPrefix(r.db, codeHash)
+ if len(code) > 0 {
+ r.cache.Put(codeHash, code)
+ }
+ return code
+}
+
+// Writer implements the state.ContractCodeWriter for committing the dirty contract
+// code into database.
+type Writer struct {
+ db *Database
+ codes [][]byte
+ codeHashes []common.Hash
+}
+
+// newWriter constructs the code writer.
+func newWriter(db *Database) *Writer {
+ return &Writer{
+ db: db,
+ }
+}
+
+// Put inserts the given contract code into the writer, waiting for commit.
+func (w *Writer) Put(codeHash common.Hash, code []byte) {
+ w.codes = append(w.codes, code)
+ w.codeHashes = append(w.codeHashes, codeHash)
+}
+
+// Commit flushes the accumulated dirty contract code into the database and
+// also place them in the cache.
+func (w *Writer) Commit() error {
+ batch := w.db.db.NewBatch()
+ for i, code := range w.codes {
+ rawdb.WriteCode(batch, w.codeHashes[i], code)
+ w.db.cache.Put(w.codeHashes[i], code)
+ }
+ if err := batch.Write(); err != nil {
+ return err
+ }
+ w.codes = w.codes[:0]
+ w.codeHashes = w.codeHashes[:0]
+ return nil
+}
+
+// Database is responsible for managing the contract code and provides the access
+// to it. It can be used as a global object, sharing it between multiple entities.
+type Database struct {
+ db ethdb.KeyValueStore
+ cache *Cache
+}
+
+// New constructs the contract code database with the provided key value store.
+func New(db ethdb.KeyValueStore) *Database {
+ return &Database{
+ db: db,
+ cache: NewCache(),
+ }
+}
+
+// Reader returns the contract code reader.
+func (s *Database) Reader() *Reader {
+ return newReader(s.db, s.cache)
+}
+
+// Writer returns the contract code writer.
+func (s *Database) Writer() *Writer {
+ return newWriter(s)
+}
diff --git a/core/state/database.go b/core/state/database.go
index 58d0ccfe8292..ab47dd72d596 100644
--- a/core/state/database.go
+++ b/core/state/database.go
@@ -23,10 +23,12 @@ import (
"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/codedb"
"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/trienode"
"github.com/ethereum/go-ethereum/trie/utils"
@@ -34,12 +36,6 @@ import (
)
const (
- // 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
-
// Number of address->curve point associations to keep.
pointCacheSize = 4096
)
@@ -49,6 +45,10 @@ type Database interface {
// Reader returns a state reader associated with the specified state root.
Reader(root common.Hash) (Reader, error)
+ // Iteratee returns a state iteratee associated with the specified state root,
+ // through which the account iterator and storage iterator can be created.
+ Iteratee(root common.Hash) (Iteratee, error)
+
// OpenTrie opens the main account trie.
OpenTrie(root common.Hash) (Trie, error)
@@ -61,8 +61,10 @@ type Database interface {
// TrieDB returns the underlying trie database for managing trie nodes.
TrieDB() *triedb.Database
- // Snapshot returns the underlying state snapshot.
- Snapshot() *snapshot.Tree
+ // 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.
+ Commit(update *stateUpdate) error
}
// Trie is a Ethereum Merkle Patricia trie.
@@ -150,29 +152,23 @@ type Trie interface {
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.
+// CachingDB is an implementation of Database interface, providing functionalities
+// for state access and update by leveraging configured data storage layers.
type CachingDB struct {
- disk ethdb.KeyValueStore
- triedb *triedb.Database
- snap *snapshot.Tree
- codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
- codeSizeCache *lru.Cache[common.Hash, int]
- pointCache *utils.PointCache
+ triedb *triedb.Database
+ codedb *codedb.Database
+ snap *snapshot.Tree
+ pointCache *utils.PointCache
// Transition-specific fields
TransitionStatePerRoot *lru.Cache[common.Hash, *overlay.TransitionState]
}
// NewDatabase creates a state database with the provided data sources.
-func NewDatabase(triedb *triedb.Database, snap *snapshot.Tree) *CachingDB {
+func NewDatabase(triedb *triedb.Database, codedb *codedb.Database) *CachingDB {
return &CachingDB{
- disk: triedb.Disk(),
triedb: triedb,
- snap: snap,
- codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
- codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
+ codedb: codedb,
pointCache: utils.NewPointCache(pointCacheSize),
TransitionStatePerRoot: lru.NewCache[common.Hash, *overlay.TransitionState](1000),
}
@@ -181,7 +177,15 @@ func NewDatabase(triedb *triedb.Database, snap *snapshot.Tree) *CachingDB {
// 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)
+ db := rawdb.NewMemoryDatabase()
+ return NewDatabase(triedb.NewDatabase(db, nil), codedb.New(db))
+}
+
+// WithSnapshot configures the provided contract code cache. Note that this
+// registration must be performed before the cachingDB is used.
+func (db *CachingDB) WithSnapshot(snapshot *snapshot.Tree) *CachingDB {
+ db.snap = snapshot
+ return db
}
// Reader returns a state reader associated with the specified state root.
@@ -219,7 +223,7 @@ func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
if err != nil {
return nil, err
}
- return newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), combined), nil
+ return newReader(db.codedb.Reader(), combined), nil
}
// ReadersWithCacheStats creates a pair of state readers sharing the same internal cache and
@@ -264,22 +268,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
@@ -290,9 +278,44 @@ func (db *CachingDB) PointCache() *utils.PointCache {
return db.pointCache
}
-// Snapshot returns the underlying state snapshot.
-func (db *CachingDB) 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 *CachingDB) 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 {
+ writer := db.codedb.Writer()
+ for _, code := range update.codes {
+ writer.Put(code.hash, code.blob)
+ }
+ if err := writer.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 *CachingDB) Iteratee(root common.Hash) (Iteratee, error) {
+ return newStateIteratee(!db.triedb.IsVerkle(), root, db.triedb, db.snap)
}
// mustCopyTrie returns a deep-copied trie.
diff --git a/core/state/database_history.go b/core/state/database_history.go
index 314c56c4708a..cc1e16a280bf 100644
--- a/core/state/database_history.go
+++ b/core/state/database_history.go
@@ -20,10 +20,8 @@ import (
"errors"
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/lru"
- "github.com/ethereum/go-ethereum/core/state/snapshot"
+ "github.com/ethereum/go-ethereum/core/state/codedb"
"github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie/utils"
"github.com/ethereum/go-ethereum/triedb"
@@ -101,21 +99,17 @@ func (r *historicReader) Storage(addr common.Address, key common.Hash) (common.H
// HistoricDB is the implementation of Database interface, with the ability to
// access historical state.
type HistoricDB struct {
- disk ethdb.KeyValueStore
- triedb *triedb.Database
- codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
- codeSizeCache *lru.Cache[common.Hash, int]
- pointCache *utils.PointCache
+ triedb *triedb.Database
+ codedb *codedb.Database
+ pointCache *utils.PointCache
}
// NewHistoricDatabase creates a historic state database.
-func NewHistoricDatabase(disk ethdb.KeyValueStore, triedb *triedb.Database) *HistoricDB {
+func NewHistoricDatabase(triedb *triedb.Database, codedb *codedb.Database) *HistoricDB {
return &HistoricDB{
- disk: disk,
- triedb: triedb,
- codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
- codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
- pointCache: utils.NewPointCache(pointCacheSize),
+ triedb: triedb,
+ codedb: codedb,
+ pointCache: utils.NewPointCache(pointCacheSize),
}
}
@@ -125,7 +119,7 @@ func (db *HistoricDB) Reader(stateRoot common.Hash) (Reader, error) {
if err != nil {
return nil, err
}
- return newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), newHistoricReader(hr)), nil
+ return newReader(db.codedb.Reader(), newHistoricReader(hr)), nil
}
// OpenTrie opens the main account trie. It's not supported by historic database.
@@ -149,7 +143,15 @@ func (db *HistoricDB) TrieDB() *triedb.Database {
return db.triedb
}
-// Snapshot returns the underlying state snapshot.
-func (db *HistoricDB) Snapshot() *snapshot.Tree {
- return nil
+// 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 *HistoricDB) Commit(update *stateUpdate) error {
+ return errors.New("not implemented")
+}
+
+// Iteratee returns a state iteratee associated with the specified state root,
+// through which the account iterator and storage iterator can be created.
+func (db *HistoricDB) Iteratee(root common.Hash) (Iteratee, error) {
+ return nil, errors.New("not implemented")
}
diff --git a/core/state/database_iterator.go b/core/state/database_iterator.go
new file mode 100644
index 000000000000..7a72ce6a6c67
--- /dev/null
+++ b/core/state/database_iterator.go
@@ -0,0 +1,408 @@
+// 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 .
+
+package state
+
+import (
+ "errors"
+ "fmt"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/state/snapshot"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/trie"
+ "github.com/ethereum/go-ethereum/triedb"
+)
+
+// Iterator is an iterator to step over all the accounts or the specific
+// storage in the specific state.
+type Iterator interface {
+ // Next steps the iterator forward one element, returning false if exhausted,
+ // or an error if iteration failed for some reason.
+ Next() bool
+
+ // Error returns any failure that occurred during iteration, which might have
+ // caused a premature iteration exit.
+ Error() error
+
+ // Hash returns the hash of the account or storage slot the iterator is
+ // currently at.
+ Hash() common.Hash
+
+ // Release releases associated resources. Release should always succeed and
+ // can be called multiple times without causing error.
+ Release()
+}
+
+// AccountIterator is an iterator to step over all the accounts in the
+// specific state.
+type AccountIterator interface {
+ Iterator
+
+ // Address returns the raw account address the iterator is currently at.
+ // An error will be returned if the preimage is not available.
+ Address() (common.Address, error)
+
+ // Account returns the RLP encoded account the iterator is currently at.
+ // An error will be retained if the iterator becomes invalid.
+ Account() []byte
+}
+
+// StorageIterator is an iterator to step over the specific storage in the
+// specific state.
+type StorageIterator interface {
+ Iterator
+
+ // Key returns the raw storage slot key the iterator is currently at.
+ // An error will be returned if the preimage is not available.
+ Key() (common.Hash, error)
+
+ // Slot returns the storage slot the iterator is currently at. An error will
+ // be retained if the iterator becomes invalid.
+ Slot() []byte
+}
+
+// Iteratee wraps the NewIterator methods for traversing the accounts and
+// storages of the specific state.
+type Iteratee interface {
+ // NewAccountIterator creates an account iterator for the state specified by
+ // the given root. It begins at a specified starting position, corresponding
+ // to a particular initial key (or the next key if the specified one does
+ // not exist).
+ //
+ // The starting position here refers to the hash of the account address.
+ NewAccountIterator(start common.Hash) (AccountIterator, error)
+
+ // NewStorageIterator creates a storage iterator for the state specified by
+ // the given root and the address hash. It begins at a specified starting
+ // position, corresponding to a particular initial key (or the next key if
+ // the specified one does not exist).
+ //
+ // The starting position here refers to the hash of the slot key.
+ NewStorageIterator(addressHash common.Hash, storageRoot common.Hash, start common.Hash) (StorageIterator, error)
+}
+
+// PreimageReader wraps the function Preimage for accessing the preimage of
+// a given hash.
+type PreimageReader interface {
+ // Preimage returns the preimage of associated hash.
+ Preimage(hash common.Hash) []byte
+}
+
+// flatAccountIterator is a wrapper around the underlying flat state iterator.
+// Before returning data from the iterator, it performs an additional conversion
+// to bridge the slim encoding with the full encoding format.
+type flatAccountIterator struct {
+ err error
+ it snapshot.AccountIterator
+ preimage PreimageReader
+}
+
+// newFlatAccountIterator constructs the account iterator with the provided
+// flat state iterator.
+func newFlatAccountIterator(it snapshot.AccountIterator, preimage PreimageReader) *flatAccountIterator {
+ return &flatAccountIterator{it: it, preimage: preimage}
+}
+
+// Next steps the iterator forward one element, returning false if exhausted,
+// or an error if iteration failed for some reason.
+func (ai *flatAccountIterator) Next() bool {
+ if ai.err != nil {
+ return false
+ }
+ return ai.it.Next()
+}
+
+// Error returns any failure that occurred during iteration, which might have
+// caused a premature iteration exit.
+func (ai *flatAccountIterator) Error() error {
+ if ai.err != nil {
+ return ai.err
+ }
+ return ai.it.Error()
+}
+
+// Hash returns the hash of the account or storage slot the iterator is
+// currently at.
+func (ai *flatAccountIterator) Hash() common.Hash {
+ return ai.it.Hash()
+}
+
+// Release releases associated resources. Release should always succeed and
+// can be called multiple times without causing error.
+func (ai *flatAccountIterator) Release() {
+ ai.it.Release()
+}
+
+// Address returns the raw account address the iterator is currently at.
+// An error will be returned if the preimage is not available.
+func (ai *flatAccountIterator) Address() (common.Address, error) {
+ if ai.preimage == nil {
+ return common.Address{}, errors.New("account address is not available")
+ }
+ preimage := ai.preimage.Preimage(ai.Hash())
+ if preimage == nil {
+ return common.Address{}, errors.New("account address is not available")
+ }
+ return common.BytesToAddress(preimage), nil
+}
+
+// Account returns the account data the iterator is currently at. The account
+// data is encoded as slim format from the underlying iterator, the conversion
+// is required.
+func (ai *flatAccountIterator) Account() []byte {
+ data, err := types.FullAccountRLP(ai.it.Account())
+ if err != nil {
+ ai.err = err
+ return nil
+ }
+ return data
+}
+
+// flatStorageIterator is a wrapper around the underlying flat state iterator.
+type flatStorageIterator struct {
+ it snapshot.StorageIterator
+ preimage PreimageReader
+}
+
+// newFlatStorageIterator constructs the storage iterator with the provided
+// flat state iterator.
+func newFlatStorageIterator(it snapshot.StorageIterator, preimage PreimageReader) *flatStorageIterator {
+ return &flatStorageIterator{it: it, preimage: preimage}
+}
+
+// Next steps the iterator forward one element, returning false if exhausted,
+// or an error if iteration failed for some reason.
+func (si *flatStorageIterator) Next() bool {
+ return si.it.Next()
+}
+
+// Error returns any failure that occurred during iteration, which might have
+// caused a premature iteration exit.
+func (si *flatStorageIterator) Error() error {
+ return si.it.Error()
+}
+
+// Hash returns the hash of the account or storage slot the iterator is
+// currently at.
+func (si *flatStorageIterator) Hash() common.Hash {
+ return si.it.Hash()
+}
+
+// Release releases associated resources. Release should always succeed and
+// can be called multiple times without causing error.
+func (si *flatStorageIterator) Release() {
+ si.it.Release()
+}
+
+// Key returns the raw storage slot key the iterator is currently at.
+// An error will be returned if the preimage is not available.
+func (si *flatStorageIterator) Key() (common.Hash, error) {
+ if si.preimage == nil {
+ return common.Hash{}, errors.New("slot key is not available")
+ }
+ preimage := si.preimage.Preimage(si.Hash())
+ if preimage == nil {
+ return common.Hash{}, errors.New("slot key is not available")
+ }
+ return common.BytesToHash(preimage), nil
+}
+
+// Slot returns the storage slot data the iterator is currently at.
+func (si *flatStorageIterator) Slot() []byte {
+ return si.it.Slot()
+}
+
+// merkleIterator implements the Iterator interface, providing functions to traverse
+// the accounts or storages with the manner of Merkle-Patricia-Trie.
+type merkleIterator struct {
+ err error
+ tr Trie
+ it *trie.Iterator
+ account bool
+}
+
+// newMerkleTrieIterator constructs the iterator with the given trie and starting position.
+func newMerkleTrieIterator(tr Trie, start common.Hash, account bool) (*merkleIterator, error) {
+ it, err := tr.NodeIterator(start.Bytes())
+ if err != nil {
+ return nil, err
+ }
+ return &merkleIterator{
+ tr: tr,
+ it: trie.NewIterator(it),
+ account: account,
+ }, nil
+}
+
+// Next steps the iterator forward one element, returning false if exhausted,
+// or an error if iteration failed for some reason.
+func (ti *merkleIterator) Next() bool {
+ if ti.err != nil {
+ return false
+ }
+ return ti.it.Next()
+}
+
+// Error returns any failure that occurred during iteration, which might have
+// caused a premature iteration exit.
+func (ti *merkleIterator) Error() error {
+ if ti.err != nil {
+ return ti.err
+ }
+ return ti.it.Err
+}
+
+// Hash returns the hash of the account or storage slot the iterator is
+// currently at.
+func (ti *merkleIterator) Hash() common.Hash {
+ return common.BytesToHash(ti.it.Key)
+}
+
+// Release releases associated resources. Release should always succeed and
+// can be called multiple times without causing error.
+func (ti *merkleIterator) Release() {}
+
+// Address returns the raw account address the iterator is currently at.
+// An error will be returned if the preimage is not available.
+func (ti *merkleIterator) Address() (common.Address, error) {
+ if !ti.account {
+ return common.Address{}, errors.New("account address is not available")
+ }
+ preimage := ti.tr.GetKey(ti.it.Key)
+ if preimage == nil {
+ return common.Address{}, errors.New("account address is not available")
+ }
+ return common.BytesToAddress(preimage), nil
+}
+
+// Account returns the account data the iterator is currently at.
+func (ti *merkleIterator) Account() []byte {
+ if !ti.account {
+ ti.err = errors.New("account data is not available")
+ return nil
+ }
+ return ti.it.Value
+}
+
+// Key returns the raw storage slot key the iterator is currently at.
+// An error will be returned if the preimage is not available.
+func (ti *merkleIterator) Key() (common.Hash, error) {
+ if ti.account {
+ return common.Hash{}, errors.New("slot key is not available")
+ }
+ preimage := ti.tr.GetKey(ti.it.Key)
+ if preimage == nil {
+ return common.Hash{}, errors.New("slot key is not available")
+ }
+ return common.BytesToHash(preimage), nil
+}
+
+// Slot returns the storage slot the iterator is currently at.
+func (ti *merkleIterator) Slot() []byte {
+ if ti.account {
+ ti.err = errors.New("storage data is not available")
+ return nil
+ }
+ return ti.it.Value
+}
+
+// stateIteratee implements Iteratee interface, providing the state traversal
+// functionalities of a specific state.
+type stateIteratee struct {
+ merkle bool
+ root common.Hash
+ triedb *triedb.Database
+ snap *snapshot.Tree
+}
+
+func newStateIteratee(merkle bool, root common.Hash, triedb *triedb.Database, snap *snapshot.Tree) (*stateIteratee, error) {
+ return &stateIteratee{
+ merkle: merkle,
+ root: root,
+ triedb: triedb,
+ snap: snap,
+ }, nil
+}
+
+// NewAccountIterator creates an account iterator for the state specified by
+// the given root. It begins at a specified starting position, corresponding
+// to a particular initial key (or the next key if the specified one does
+// not exist).
+//
+// The starting position here refers to the hash of the account address.
+func (si *stateIteratee) NewAccountIterator(start common.Hash) (AccountIterator, error) {
+ // If the external snapshot is available (hash scheme), try to initialize
+ // the account iterator from there first.
+ if si.snap != nil {
+ it, err := si.snap.AccountIterator(si.root, start)
+ if err == nil {
+ return newFlatAccountIterator(it, si.triedb), nil
+ }
+ }
+ // If the external snapshot is not available, try to initialize the
+ // account iterator from the trie database (path scheme)
+ it, err := si.triedb.AccountIterator(si.root, start)
+ if err == nil {
+ return newFlatAccountIterator(it, si.triedb), nil
+ }
+ if !si.merkle {
+ return nil, fmt.Errorf("state %x is not available for account traversal", si.root)
+ }
+ // The snapshot is not usable so far, construct the account iterator from
+ // the trie as the fallback. It's not as efficient as the flat state iterator.
+ tr, err := trie.NewStateTrie(trie.StateTrieID(si.root), si.triedb)
+ if err != nil {
+ return nil, err
+ }
+ return newMerkleTrieIterator(tr, start, true)
+}
+
+// NewStorageIterator creates a storage iterator for the state specified by
+// the given root and the address hash. It begins at a specified starting
+// position, corresponding to a particular initial key (or the next key if
+// the specified one does not exist).
+//
+// The starting position here refers to the hash of the slot key.
+func (si *stateIteratee) NewStorageIterator(addressHash common.Hash, storageRoot common.Hash, start common.Hash) (StorageIterator, error) {
+ // If the external snapshot is available (hash scheme), try to initialize
+ // the storage iterator from there first.
+ if si.snap != nil {
+ it, err := si.snap.StorageIterator(si.root, addressHash, start)
+ if err == nil {
+ return newFlatStorageIterator(it, si.triedb), nil
+ }
+ }
+ // If the external snapshot is not available, try to initialize the
+ // storage iterator from the trie database (path scheme)
+ it, err := si.triedb.StorageIterator(si.root, addressHash, start)
+ if err == nil {
+ return newFlatStorageIterator(it, si.triedb), nil
+ }
+ if !si.merkle {
+ return nil, fmt.Errorf("state %x is not available for account traversal", si.root)
+ }
+ // The snapshot is not usable so far, construct the storage iterator from
+ // the trie as the fallback. It's not as efficient as the flat state iterator.
+ //
+ // TODO(rjl493456442) the storageRoot can be resolved from the reader
+ // internally, we can probably get rid of it from the parameters.
+ tr, err := trie.NewStateTrie(trie.StorageTrieID(si.root, addressHash, storageRoot), si.triedb)
+ if err != nil {
+ return nil, err
+ }
+ return newMerkleTrieIterator(tr, start, false)
+}
diff --git a/core/state/reader.go b/core/state/database_reader.go
similarity index 87%
rename from core/state/reader.go
rename to core/state/database_reader.go
index 3e8b31b6be3d..8e18a8a616e8 100644
--- a/core/state/reader.go
+++ b/core/state/database_reader.go
@@ -22,12 +22,9 @@ import (
"sync/atomic"
"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/types"
"github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/utils"
@@ -37,19 +34,13 @@ import (
// ContractCodeReader defines the interface for accessing contract code.
type ContractCodeReader interface {
- // Code retrieves a particular contract's code.
- //
- // - Returns nil code along with nil error if the requested contract code
- // doesn't exist
- // - Returns an error only if an unexpected issue occurs
- Code(addr common.Address, codeHash common.Hash) ([]byte, error)
+ // Code retrieves a particular contract's code. Returns nil code if the
+ // requested contract code doesn't exist
+ Code(addr common.Address, codeHash common.Hash) []byte
- // CodeSize retrieves a particular contracts code's size.
- //
- // - Returns zero code size along with nil error if the requested contract code
- // doesn't exist
- // - Returns an error only if an unexpected issue occurs
- CodeSize(addr common.Address, codeHash common.Hash) (int, error)
+ // CodeSize retrieves a particular contracts code's size. Returns zero
+ // code size if the requested contract code doesn't exist
+ CodeSize(addr common.Address, codeHash common.Hash) int
}
// StateReader defines the interface for accessing accounts and storage slots
@@ -98,56 +89,6 @@ type ReaderWithStats interface {
GetStats() ReaderStats
}
-// cachingCodeReader implements ContractCodeReader, accessing contract code either in
-// local key-value store or the shared code cache.
-//
-// cachingCodeReader is safe for concurrent access.
-type cachingCodeReader struct {
- db ethdb.KeyValueReader
-
- // These caches could be shared by multiple code reader instances,
- // they are natively thread-safe.
- codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
- codeSizeCache *lru.Cache[common.Hash, int]
-}
-
-// newCachingCodeReader constructs the code reader.
-func newCachingCodeReader(db ethdb.KeyValueReader, codeCache *lru.SizeConstrainedCache[common.Hash, []byte], codeSizeCache *lru.Cache[common.Hash, int]) *cachingCodeReader {
- return &cachingCodeReader{
- db: db,
- codeCache: codeCache,
- codeSizeCache: codeSizeCache,
- }
-}
-
-// Code implements ContractCodeReader, retrieving a particular contract's code.
-// If the contract code doesn't exist, no error will be returned.
-func (r *cachingCodeReader) Code(addr common.Address, codeHash common.Hash) ([]byte, error) {
- code, _ := r.codeCache.Get(codeHash)
- if len(code) > 0 {
- return code, nil
- }
- code = rawdb.ReadCode(r.db, codeHash)
- if len(code) > 0 {
- r.codeCache.Add(codeHash, code)
- r.codeSizeCache.Add(codeHash, len(code))
- }
- return code, nil
-}
-
-// CodeSize implements ContractCodeReader, retrieving a particular contracts code's size.
-// If the contract code doesn't exist, no error will be returned.
-func (r *cachingCodeReader) CodeSize(addr common.Address, codeHash common.Hash) (int, error) {
- if cached, ok := r.codeSizeCache.Get(codeHash); ok {
- return cached, nil
- }
- code, err := r.Code(addr, codeHash)
- if err != nil {
- return 0, err
- }
- return len(code), nil
-}
-
// flatReader wraps a database state reader and is safe for concurrent access.
type flatReader struct {
reader database.StateReader
diff --git a/core/state/dump.go b/core/state/dump.go
index a4abc33733f1..1a1c667bb95b 100644
--- a/core/state/dump.go
+++ b/core/state/dump.go
@@ -26,7 +26,6 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
- "github.com/ethereum/go-ethereum/trie"
)
// DumpConfig is a set of options to control what portions of the state will be
@@ -112,9 +111,6 @@ func (d iterativeDump) OnRoot(root common.Hash) {
// DumpToCollector iterates the state according to the given options and inserts
// the items into a collector for aggregation or serialization.
-//
-// The state iterator is still trie-based and can be converted to snapshot-based
-// once the state snapshot is fully integrated into database. TODO(rjl493456442).
func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []byte) {
// Sanitize the input to allow nil configs
if conf == nil {
@@ -129,20 +125,23 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
log.Info("Trie dumping started", "root", s.originalRoot)
c.OnRoot(s.originalRoot)
- tr, err := s.db.OpenTrie(s.originalRoot)
+ iteratee, err := s.db.Iteratee(s.originalRoot)
if err != nil {
return nil
}
- trieIt, err := tr.NodeIterator(conf.Start)
+ var startHash common.Hash
+ if conf.Start != nil {
+ startHash = common.BytesToHash(conf.Start)
+ }
+ acctIt, err := iteratee.NewAccountIterator(startHash)
if err != nil {
- log.Error("Trie dumping error", "err", err)
return nil
}
- it := trie.NewIterator(trieIt)
+ defer acctIt.Release()
- for it.Next() {
+ for acctIt.Next() {
var data types.StateAccount
- if err := rlp.DecodeBytes(it.Value, &data); err != nil {
+ if err := rlp.DecodeBytes(acctIt.Account(), &data); err != nil {
panic(err)
}
var (
@@ -151,20 +150,19 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
Nonce: data.Nonce,
Root: data.Root[:],
CodeHash: data.CodeHash,
- AddressHash: it.Key,
+ AddressHash: acctIt.Hash().Bytes(),
}
- address *common.Address
- addr common.Address
- addrBytes = tr.GetKey(it.Key)
+ address *common.Address
+ addr common.Address
)
- if addrBytes == nil {
+ addrBytes, err := acctIt.Address()
+ if err != nil {
missingPreimages++
if conf.OnlyWithAddresses {
continue
}
} else {
- addr = common.BytesToAddress(addrBytes)
- address = &addr
+ address = &addrBytes
account.Address = address
}
obj := newObject(s, addr, &data)
@@ -174,40 +172,36 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
if !conf.SkipStorage {
account.Storage = make(map[common.Hash]string)
- storageTr, err := s.db.OpenStorageTrie(s.originalRoot, addr, obj.Root(), tr)
+ storageIt, err := iteratee.NewStorageIterator(acctIt.Hash(), obj.Root(), common.Hash{})
if err != nil {
log.Error("Failed to load storage trie", "err", err)
continue
}
- trieIt, err := storageTr.NodeIterator(nil)
- if err != nil {
- log.Error("Failed to create trie iterator", "err", err)
- continue
- }
- storageIt := trie.NewIterator(trieIt)
+
for storageIt.Next() {
- _, content, _, err := rlp.Split(storageIt.Value)
+ _, content, _, err := rlp.Split(storageIt.Slot())
if err != nil {
log.Error("Failed to decode the value returned by iterator", "error", err)
continue
}
- key := storageTr.GetKey(storageIt.Key)
- if key == nil {
+ key, err := storageIt.Key()
+ if err != nil {
continue
}
- account.Storage[common.BytesToHash(key)] = common.Bytes2Hex(content)
+ account.Storage[key] = common.Bytes2Hex(content)
}
+ storageIt.Release()
}
c.OnAccount(address, account)
accounts++
if time.Since(logged) > 8*time.Second {
- log.Info("Trie dumping in progress", "at", common.Bytes2Hex(it.Key), "accounts", accounts,
+ log.Info("Trie dumping in progress", "at", acctIt.Hash().Hex(), "accounts", accounts,
"elapsed", common.PrettyDuration(time.Since(start)))
logged = time.Now()
}
if conf.Max > 0 && accounts >= conf.Max {
- if it.Next() {
- nextKey = it.Key
+ if acctIt.Next() {
+ nextKey = acctIt.Hash().Bytes()
}
break
}
diff --git a/core/state/iterator.go b/core/state/iterator.go
index 0abae091d954..0050a840d802 100644
--- a/core/state/iterator.go
+++ b/core/state/iterator.go
@@ -144,10 +144,7 @@ func (it *nodeIterator) step() error {
}
if !bytes.Equal(account.CodeHash, types.EmptyCodeHash.Bytes()) {
it.codeHash = common.BytesToHash(account.CodeHash)
- it.code, err = it.state.reader.Code(address, common.BytesToHash(account.CodeHash))
- if err != nil {
- return fmt.Errorf("code %x: %v", account.CodeHash, err)
- }
+ it.code = it.state.reader.Code(address, common.BytesToHash(account.CodeHash))
if len(it.code) == 0 {
return fmt.Errorf("code is not found: %x", account.CodeHash)
}
diff --git a/core/state/state_object.go b/core/state/state_object.go
index fdeb4254c1bc..fc3f93430677 100644
--- a/core/state/state_object.go
+++ b/core/state/state_object.go
@@ -530,10 +530,7 @@ func (s *stateObject) Code() []byte {
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {
return nil
}
- code, err := s.db.reader.Code(s.address, common.BytesToHash(s.CodeHash()))
- if err != nil {
- s.db.setError(fmt.Errorf("can't load code hash %x: %v", s.CodeHash(), err))
- }
+ code := s.db.reader.Code(s.address, common.BytesToHash(s.CodeHash()))
if len(code) == 0 {
s.db.setError(fmt.Errorf("code is not found %x", s.CodeHash()))
}
@@ -551,10 +548,7 @@ func (s *stateObject) CodeSize() int {
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {
return 0
}
- size, err := s.db.reader.CodeSize(s.address, common.BytesToHash(s.CodeHash()))
- if err != nil {
- s.db.setError(fmt.Errorf("can't load code size %x: %v", s.CodeHash(), err))
- }
+ size := s.db.reader.CodeSize(s.address, common.BytesToHash(s.CodeHash()))
if size == 0 {
s.db.setError(fmt.Errorf("code is not found %x", s.CodeHash()))
}
diff --git a/core/state/state_sizer_test.go b/core/state/state_sizer_test.go
index 65f652e424c1..9362d27681ef 100644
--- a/core/state/state_sizer_test.go
+++ b/core/state/state_sizer_test.go
@@ -22,6 +22,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/core/state/codedb"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/triedb"
@@ -34,7 +35,7 @@ func TestSizeTracker(t *testing.T) {
defer db.Close()
tdb := triedb.NewDatabase(db, &triedb.Config{PathDB: pathdb.Defaults})
- sdb := NewDatabase(tdb, nil)
+ sdb := NewDatabase(tdb, codedb.New(db))
// Generate 50 blocks to establish a baseline
baselineBlockNum := uint64(50)
@@ -100,7 +101,7 @@ func TestSizeTracker(t *testing.T) {
t.Fatalf("Failed to close triedb before baseline measurement: %v", err)
}
tdb = triedb.NewDatabase(db, &triedb.Config{PathDB: pathdb.Defaults})
- sdb = NewDatabase(tdb, nil)
+ sdb = NewDatabase(tdb, codedb.New(db))
// Wait for snapshot completion
for !tdb.SnapshotCompleted() {
diff --git a/core/state/state_test.go b/core/state/state_test.go
index eeeb7fa2df87..ba26bc033ef0 100644
--- a/core/state/state_test.go
+++ b/core/state/state_test.go
@@ -23,6 +23,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/core/state/codedb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/triedb"
@@ -41,7 +42,7 @@ func newStateEnv() *stateEnv {
func TestDump(t *testing.T) {
db := rawdb.NewMemoryDatabase()
triedb := triedb.NewDatabase(db, &triedb.Config{Preimages: true})
- tdb := NewDatabase(triedb, nil)
+ tdb := NewDatabase(triedb, codedb.New(db))
sdb, _ := New(types.EmptyRootHash, tdb)
s := &stateEnv{state: sdb}
@@ -97,7 +98,7 @@ func TestDump(t *testing.T) {
func TestIterativeDump(t *testing.T) {
db := rawdb.NewMemoryDatabase()
triedb := triedb.NewDatabase(db, &triedb.Config{Preimages: true})
- tdb := NewDatabase(triedb, nil)
+ tdb := NewDatabase(triedb, codedb.New(db))
sdb, _ := New(types.EmptyRootHash, tdb)
s := &stateEnv{state: sdb}
diff --git a/core/state/statedb.go b/core/state/statedb.go
index b770698255e4..dd1ecc19fac1 100644
--- a/core/state/statedb.go
+++ b/core/state/statedb.go
@@ -27,8 +27,6 @@ import (
"time"
"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/core/stateless"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
@@ -148,7 +146,7 @@ type StateDB struct {
StorageUpdates time.Duration
StorageCommits time.Duration
SnapshotCommits time.Duration
- TrieDBCommits time.Duration
+ DatabaseCommits time.Duration
AccountLoaded int // Number of accounts retrieved from the database during the state transition
AccountUpdated int // Number of accounts updated during the state transition
@@ -982,31 +980,36 @@ func (s *StateDB) clearJournalAndRefund() {
s.refund = 0
}
-// fastDeleteStorage is the function that efficiently deletes the storage trie
-// of a specific account. It leverages the associated state snapshot for fast
-// storage iteration and constructs trie node deletion markers by creating
-// stack trie with iterated slots.
-func (s *StateDB) fastDeleteStorage(snaps *snapshot.Tree, addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, map[common.Hash][]byte, *trienode.NodeSet, error) {
- iter, err := snaps.StorageIterator(s.originalRoot, addrHash, common.Hash{})
- if err != nil {
- return nil, nil, nil, err
- }
- defer iter.Release()
-
+// deleteStorage is designed to delete the storage trie of a designated account.
+// The function will make an attempt to utilize an efficient strategy if the
+// associated state snapshot is reachable; otherwise, it will resort to a less
+// efficient approach.
+func (s *StateDB) deleteStorage(addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, map[common.Hash][]byte, *trienode.NodeSet, error) {
var (
+ err error
nodes = trienode.NewNodeSet(addrHash) // the set for trie node mutations (value is nil)
storages = make(map[common.Hash][]byte) // the set for storage mutations (value is nil)
storageOrigins = make(map[common.Hash][]byte) // the set for tracking the original value of slot
)
+ iteratee, err := s.db.Iteratee(s.originalRoot)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ it, err := iteratee.NewStorageIterator(addrHash, root, common.Hash{})
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ defer it.Release()
+
stack := trie.NewStackTrie(func(path []byte, hash common.Hash, blob []byte) {
nodes.AddNode(path, trienode.NewDeletedWithPrev(blob))
})
- for iter.Next() {
- slot := common.CopyBytes(iter.Slot())
- if err := iter.Error(); err != nil { // error might occur after Slot function
+ for it.Next() {
+ slot := common.CopyBytes(it.Slot())
+ if err := it.Error(); err != nil { // error might occur after Slot function
return nil, nil, nil, err
}
- key := iter.Hash()
+ key := it.Hash()
storages[key] = nil
storageOrigins[key] = slot
@@ -1014,7 +1017,7 @@ func (s *StateDB) fastDeleteStorage(snaps *snapshot.Tree, addrHash common.Hash,
return nil, nil, nil, err
}
}
- if err := iter.Error(); err != nil { // error might occur during iteration
+ if err := it.Error(); err != nil { // error might occur during iteration
return nil, nil, nil, err
}
if stack.Hash() != root {
@@ -1023,68 +1026,6 @@ func (s *StateDB) fastDeleteStorage(snaps *snapshot.Tree, addrHash common.Hash,
return storages, storageOrigins, nodes, nil
}
-// slowDeleteStorage serves as a less-efficient alternative to "fastDeleteStorage,"
-// employed when the associated state snapshot is not available. It iterates the
-// storage slots along with all internal trie nodes via trie directly.
-func (s *StateDB) slowDeleteStorage(addr common.Address, addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, map[common.Hash][]byte, *trienode.NodeSet, error) {
- tr, err := s.db.OpenStorageTrie(s.originalRoot, addr, root, s.trie)
- if err != nil {
- return nil, nil, nil, fmt.Errorf("failed to open storage trie, err: %w", err)
- }
- it, err := tr.NodeIterator(nil)
- if err != nil {
- return nil, nil, nil, fmt.Errorf("failed to open storage iterator, err: %w", err)
- }
- var (
- nodes = trienode.NewNodeSet(addrHash) // the set for trie node mutations (value is nil)
- storages = make(map[common.Hash][]byte) // the set for storage mutations (value is nil)
- storageOrigins = make(map[common.Hash][]byte) // the set for tracking the original value of slot
- )
- for it.Next(true) {
- if it.Leaf() {
- key := common.BytesToHash(it.LeafKey())
- storages[key] = nil
- storageOrigins[key] = common.CopyBytes(it.LeafBlob())
- continue
- }
- if it.Hash() == (common.Hash{}) {
- continue
- }
- nodes.AddNode(it.Path(), trienode.NewDeletedWithPrev(it.NodeBlob()))
- }
- if err := it.Error(); err != nil {
- return nil, nil, nil, err
- }
- return storages, storageOrigins, nodes, nil
-}
-
-// deleteStorage is designed to delete the storage trie of a designated account.
-// The function will make an attempt to utilize an efficient strategy if the
-// associated state snapshot is reachable; otherwise, it will resort to a less
-// efficient approach.
-func (s *StateDB) deleteStorage(addr common.Address, addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, map[common.Hash][]byte, *trienode.NodeSet, error) {
- var (
- err error
- nodes *trienode.NodeSet // the set for trie node mutations (value is nil)
- storages map[common.Hash][]byte // the set for storage mutations (value is nil)
- storageOrigins map[common.Hash][]byte // the set for tracking the original value of slot
- )
- // The fast approach can be failed if the snapshot is not fully
- // generated, or it's internally corrupted. Fallback to the slow
- // one just in case.
- snaps := s.db.Snapshot()
- if snaps != nil {
- storages, storageOrigins, nodes, err = s.fastDeleteStorage(snaps, addrHash, root)
- }
- if snaps == nil || err != nil {
- storages, storageOrigins, nodes, err = s.slowDeleteStorage(addr, addrHash, root)
- }
- if err != nil {
- return nil, nil, nil, err
- }
- return storages, storageOrigins, nodes, nil
-}
-
// handleDestruction processes all destruction markers and deletes the account
// and associated storage slots if necessary. There are four potential scenarios
// as following:
@@ -1135,7 +1076,7 @@ func (s *StateDB) handleDestruction(noStorageWiping bool) (map[common.Hash]*acco
return nil, nil, fmt.Errorf("unexpected storage wiping, %x", addr)
}
// Remove storage slots belonging to the account.
- storages, storagesOrigin, set, err := s.deleteStorage(addr, addrHash, prev.Root)
+ storages, storagesOrigin, set, err := s.deleteStorage(addrHash, prev.Root)
if err != nil {
return nil, nil, fmt.Errorf("failed to delete storage, err: %w", err)
}
@@ -1155,7 +1096,7 @@ func (s *StateDB) GetTrie() Trie {
// commit gathers the state mutations accumulated along with the associated
// trie changes, resetting all internal flags with the new state as the base.
-func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNumber uint64) (*stateUpdate, error) {
+func (s *StateDB) commit(deleteEmptyObjects bool, rawStorageKey bool, blockNumber uint64) (*stateUpdate, error) {
// Short circuit in case any database failure occurred earlier.
if s.dbErr != nil {
return nil, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr)
@@ -1209,7 +1150,7 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
// the same block, account deletions must be processed first. This ensures
// that the storage trie nodes deleted during destruction and recreated
// during subsequent resurrection can be combined correctly.
- deletes, delNodes, err := s.handleDestruction(noStorageWiping)
+ deletes, delNodes, err := s.handleDestruction(rawStorageKey)
if err != nil {
return nil, err
}
@@ -1307,53 +1248,17 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
origin := s.originalRoot
s.originalRoot = root
- return newStateUpdate(noStorageWiping, origin, root, blockNumber, deletes, updates, nodes), nil
-}
-
-// commitAndFlush is a wrapper of commit which also commits the state mutations
-// to the configured data stores.
-func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool, noStorageWiping bool) (*stateUpdate, error) {
- ret, err := s.commit(deleteEmptyObjects, noStorageWiping, block)
- if err != nil {
+ start = time.Now()
+ update := newStateUpdate(rawStorageKey, origin, root, blockNumber, deletes, updates, nodes)
+ if err := s.db.Commit(update); err != nil {
return nil, err
}
- // Commit dirty contract code if any exists
- if db := s.db.TrieDB().Disk(); db != nil && len(ret.codes) > 0 {
- batch := db.NewBatch()
- for _, code := range ret.codes {
- rawdb.WriteCode(batch, code.hash, code.blob)
- }
- if err := batch.Write(); err != nil {
- return nil, err
- }
- }
- if !ret.empty() {
- // If snapshotting is enabled, update the snapshot tree with this new version
- if snap := s.db.Snapshot(); snap != nil && snap.Snapshot(ret.originRoot) != nil {
- start := time.Now()
- if err := snap.Update(ret.root, ret.originRoot, ret.accounts, ret.storages); err != nil {
- log.Warn("Failed to update snapshot tree", "from", ret.originRoot, "to", ret.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 := snap.Cap(ret.root, TriesInMemory); err != nil {
- log.Warn("Failed to cap snapshot tree", "root", ret.root, "layers", TriesInMemory, "err", err)
- }
- s.SnapshotCommits += time.Since(start)
- }
- // If trie database is enabled, commit the state update as a new layer
- if db := s.db.TrieDB(); db != nil {
- start := time.Now()
- if err := db.Update(ret.root, ret.originRoot, block, ret.nodes, ret.stateSet()); err != nil {
- return nil, err
- }
- s.TrieDBCommits += time.Since(start)
- }
- }
- s.reader, _ = s.db.Reader(s.originalRoot)
- return ret, err
+ s.DatabaseCommits = time.Since(start)
+
+ // The reader update must be performed as the final step, otherwise,
+ // the new state would not be visible before db.commit.
+ s.reader, _ = s.db.Reader(root)
+ return update, nil
}
// Commit writes the state mutations into the configured data stores.
@@ -1370,8 +1275,8 @@ func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool, noStorag
// Since self-destruction was deprecated with the Cancun fork and there are
// no empty accounts left that could be deleted by EIP-158, storage wiping
// should not occur.
-func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool, noStorageWiping bool) (common.Hash, error) {
- ret, err := s.commitAndFlush(block, deleteEmptyObjects, noStorageWiping)
+func (s *StateDB) Commit(blockNumber uint64, deleteEmptyObjects bool, rawStorageKey bool) (common.Hash, error) {
+ ret, err := s.commit(deleteEmptyObjects, rawStorageKey, blockNumber)
if err != nil {
return common.Hash{}, err
}
@@ -1380,8 +1285,8 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool, noStorageWiping
// CommitWithUpdate writes the state mutations and returns both the root hash and the state update.
// This is useful for tracking state changes at the blockchain level.
-func (s *StateDB) CommitWithUpdate(block uint64, deleteEmptyObjects bool, noStorageWiping bool) (common.Hash, *stateUpdate, error) {
- ret, err := s.commitAndFlush(block, deleteEmptyObjects, noStorageWiping)
+func (s *StateDB) CommitWithUpdate(blockNumber uint64, deleteEmptyObjects bool, rawStorageKey bool) (common.Hash, *stateUpdate, error) {
+ ret, err := s.commit(deleteEmptyObjects, rawStorageKey, blockNumber)
if err != nil {
return common.Hash{}, nil, err
}
diff --git a/core/state/statedb_fuzz_test.go b/core/state/statedb_fuzz_test.go
index f4761bd10c77..f8e61ea3edfd 100644
--- a/core/state/statedb_fuzz_test.go
+++ b/core/state/statedb_fuzz_test.go
@@ -31,6 +31,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/core/state/codedb"
"github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
@@ -209,7 +210,7 @@ func (test *stateTest) run() bool {
if i != 0 {
root = roots[len(roots)-1]
}
- state, err := New(root, NewDatabase(tdb, snaps))
+ state, err := New(root, NewDatabase(tdb, codedb.New(disk)).WithSnapshot(snaps))
if err != nil {
panic(err)
}
@@ -228,7 +229,7 @@ func (test *stateTest) run() bool {
} else {
state.IntermediateRoot(true) // call intermediateRoot at the transaction boundary
}
- ret, err := state.commitAndFlush(0, true, false) // call commit at the block boundary
+ ret, err := state.commit(true, false, 0) // call commit at the block boundary
if err != nil {
panic(err)
}
diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go
index 661d17bb7beb..f55344e85ef2 100644
--- a/core/state/statedb_test.go
+++ b/core/state/statedb_test.go
@@ -32,6 +32,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/core/state/codedb"
"github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
@@ -52,7 +53,7 @@ func TestUpdateLeaks(t *testing.T) {
var (
db = rawdb.NewMemoryDatabase()
tdb = triedb.NewDatabase(db, nil)
- sdb = NewDatabase(tdb, nil)
+ sdb = NewDatabase(tdb, codedb.New(db))
)
state, _ := New(types.EmptyRootHash, sdb)
@@ -90,8 +91,8 @@ func TestIntermediateLeaks(t *testing.T) {
finalDb := rawdb.NewMemoryDatabase()
transNdb := triedb.NewDatabase(transDb, nil)
finalNdb := triedb.NewDatabase(finalDb, nil)
- transState, _ := New(types.EmptyRootHash, NewDatabase(transNdb, nil))
- finalState, _ := New(types.EmptyRootHash, NewDatabase(finalNdb, nil))
+ transState, _ := New(types.EmptyRootHash, NewDatabase(transNdb, codedb.New(transDb)))
+ finalState, _ := New(types.EmptyRootHash, NewDatabase(finalNdb, codedb.New(finalDb)))
modify := func(state *StateDB, addr common.Address, i, tweak byte) {
state.SetBalance(addr, uint256.NewInt(uint64(11*i)+uint64(tweak)), tracing.BalanceChangeUnspecified)
@@ -980,7 +981,7 @@ func testMissingTrieNodes(t *testing.T, scheme string) {
CleanCacheSize: 0,
}}) // disable caching
}
- db := NewDatabase(tdb, nil)
+ db := NewDatabase(tdb, codedb.New(memDb))
var root common.Hash
state, _ := New(types.EmptyRootHash, db)
@@ -1204,7 +1205,7 @@ func TestFlushOrderDataLoss(t *testing.T) {
var (
memdb = rawdb.NewMemoryDatabase()
tdb = triedb.NewDatabase(memdb, triedb.HashDefaults)
- statedb = NewDatabase(tdb, nil)
+ statedb = NewDatabase(tdb, codedb.New(memdb))
state, _ = New(types.EmptyRootHash, statedb)
)
for a := byte(0); a < 10; a++ {
@@ -1225,7 +1226,7 @@ func TestFlushOrderDataLoss(t *testing.T) {
t.Fatalf("failed to commit state trie: %v", err)
}
// Reopen the state trie from flushed disk and verify it
- state, err = New(root, NewDatabase(triedb.NewDatabase(memdb, triedb.HashDefaults), nil))
+ state, err = New(root, NewDatabase(triedb.NewDatabase(memdb, triedb.HashDefaults), codedb.New(memdb)))
if err != nil {
t.Fatalf("failed to reopen state trie: %v", err)
}
@@ -1276,7 +1277,7 @@ func TestDeleteStorage(t *testing.T) {
disk = rawdb.NewMemoryDatabase()
tdb = triedb.NewDatabase(disk, nil)
snaps, _ = snapshot.New(snapshot.Config{CacheSize: 10}, disk, tdb, types.EmptyRootHash)
- db = NewDatabase(tdb, snaps)
+ db = NewDatabase(tdb, codedb.New(disk)).WithSnapshot(snaps)
state, _ = New(types.EmptyRootHash, db)
addr = common.HexToAddress("0x1")
)
@@ -1289,19 +1290,20 @@ func TestDeleteStorage(t *testing.T) {
state.SetState(addr, slot, value)
}
root, _ := state.Commit(0, true, false)
+
// Init phase done, create two states, one with snap and one without
- fastState, _ := New(root, NewDatabase(tdb, snaps))
- slowState, _ := New(root, NewDatabase(tdb, nil))
+ fastState, _ := New(root, NewDatabase(tdb, codedb.New(disk)).WithSnapshot(snaps))
+ slowState, _ := New(root, NewDatabase(tdb, codedb.New(disk)))
obj := fastState.getOrNewStateObject(addr)
storageRoot := obj.data.Root
- _, _, fastNodes, err := fastState.deleteStorage(addr, crypto.Keccak256Hash(addr[:]), storageRoot)
+ _, _, fastNodes, err := fastState.deleteStorage(crypto.Keccak256Hash(addr[:]), storageRoot)
if err != nil {
t.Fatal(err)
}
- _, _, slowNodes, err := slowState.deleteStorage(addr, crypto.Keccak256Hash(addr[:]), storageRoot)
+ _, _, slowNodes, err := slowState.deleteStorage(crypto.Keccak256Hash(addr[:]), storageRoot)
if err != nil {
t.Fatal(err)
}
@@ -1329,7 +1331,7 @@ func TestStorageDirtiness(t *testing.T) {
var (
disk = rawdb.NewMemoryDatabase()
tdb = triedb.NewDatabase(disk, nil)
- db = NewDatabase(tdb, nil)
+ db = NewDatabase(tdb, codedb.New(disk))
state, _ = New(types.EmptyRootHash, db)
addr = common.HexToAddress("0x1")
checkDirty = func(key common.Hash, value common.Hash, dirty bool) {
diff --git a/core/state/sync_test.go b/core/state/sync_test.go
index cae0e0a936bc..f86c3cb6b34a 100644
--- a/core/state/sync_test.go
+++ b/core/state/sync_test.go
@@ -22,6 +22,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/core/state/codedb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
@@ -54,7 +55,7 @@ func makeTestState(scheme string) (ethdb.Database, Database, *triedb.Database, c
}
db := rawdb.NewMemoryDatabase()
nodeDb := triedb.NewDatabase(db, config)
- sdb := NewDatabase(nodeDb, nil)
+ sdb := NewDatabase(nodeDb, codedb.New(db))
state, _ := New(types.EmptyRootHash, sdb)
// Fill it with some arbitrary data
@@ -95,7 +96,7 @@ func checkStateAccounts(t *testing.T, db ethdb.Database, scheme string, root com
config.PathDB = pathdb.Defaults
}
// Check root availability and state contents
- state, err := New(root, NewDatabase(triedb.NewDatabase(db, &config), nil))
+ state, err := New(root, NewDatabase(triedb.NewDatabase(db, &config), codedb.New(db)))
if err != nil {
t.Fatalf("failed to create state trie at %x: %v", root, err)
}
@@ -121,7 +122,7 @@ func checkStateConsistency(db ethdb.Database, scheme string, root common.Hash) e
if scheme == rawdb.PathScheme {
config.PathDB = pathdb.Defaults
}
- state, err := New(root, NewDatabase(triedb.NewDatabase(db, config), nil))
+ state, err := New(root, NewDatabase(triedb.NewDatabase(db, config), codedb.New(db)))
if err != nil {
return err
}
@@ -222,8 +223,8 @@ func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool, s
codeResults = make([]trie.CodeSyncResult, len(codeElements))
)
for i, element := range codeElements {
- data, err := cReader.Code(common.Address{}, element.code)
- if err != nil || len(data) == 0 {
+ data := cReader.Code(common.Address{}, element.code)
+ if len(data) == 0 {
t.Fatalf("failed to retrieve contract bytecode for hash %x", element.code)
}
codeResults[i] = trie.CodeSyncResult{Hash: element.code, Data: data}
@@ -346,8 +347,8 @@ func testIterativeDelayedStateSync(t *testing.T, scheme string) {
if len(codeElements) > 0 {
codeResults := make([]trie.CodeSyncResult, len(codeElements)/2+1)
for i, element := range codeElements[:len(codeResults)] {
- data, err := cReader.Code(common.Address{}, element.code)
- if err != nil || len(data) == 0 {
+ data := cReader.Code(common.Address{}, element.code)
+ if len(data) == 0 {
t.Fatalf("failed to retrieve contract bytecode for %x", element.code)
}
codeResults[i] = trie.CodeSyncResult{Hash: element.code, Data: data}
@@ -452,8 +453,8 @@ func testIterativeRandomStateSync(t *testing.T, count int, scheme string) {
if len(codeQueue) > 0 {
results := make([]trie.CodeSyncResult, 0, len(codeQueue))
for hash := range codeQueue {
- data, err := cReader.Code(common.Address{}, hash)
- if err != nil || len(data) == 0 {
+ data := cReader.Code(common.Address{}, hash)
+ if len(data) == 0 {
t.Fatalf("failed to retrieve node data for %x", hash)
}
results = append(results, trie.CodeSyncResult{Hash: hash, Data: data})
@@ -551,8 +552,8 @@ func testIterativeRandomDelayedStateSync(t *testing.T, scheme string) {
for hash := range codeQueue {
delete(codeQueue, hash)
- data, err := cReader.Code(common.Address{}, hash)
- if err != nil || len(data) == 0 {
+ data := cReader.Code(common.Address{}, hash)
+ if len(data) == 0 {
t.Fatalf("failed to retrieve node data for %x", hash)
}
results = append(results, trie.CodeSyncResult{Hash: hash, Data: data})
@@ -671,8 +672,8 @@ func testIncompleteStateSync(t *testing.T, scheme string) {
if len(codeQueue) > 0 {
results := make([]trie.CodeSyncResult, 0, len(codeQueue))
for hash := range codeQueue {
- data, err := cReader.Code(common.Address{}, hash)
- if err != nil || len(data) == 0 {
+ data := cReader.Code(common.Address{}, hash)
+ if len(data) == 0 {
t.Fatalf("failed to retrieve node data for %x", hash)
}
results = append(results, trie.CodeSyncResult{Hash: hash, Data: data})
diff --git a/core/state/trie_prefetcher_test.go b/core/state/trie_prefetcher_test.go
index 41349c0c0e70..36735a8fd0fe 100644
--- a/core/state/trie_prefetcher_test.go
+++ b/core/state/trie_prefetcher_test.go
@@ -22,6 +22,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/core/state/codedb"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
@@ -69,7 +70,7 @@ func TestUseAfterTerminate(t *testing.T) {
func TestVerklePrefetcher(t *testing.T) {
disk := rawdb.NewMemoryDatabase()
db := triedb.NewDatabase(disk, triedb.VerkleDefaults)
- sdb := NewDatabase(db, nil)
+ sdb := NewDatabase(db, codedb.New(disk))
state, err := New(types.EmptyRootHash, sdb)
if err != nil {
diff --git a/core/stateless.go b/core/stateless.go
index b20c909da670..1aaf5112d8e1 100644
--- a/core/stateless.go
+++ b/core/stateless.go
@@ -22,6 +22,7 @@ import (
"github.com/ethereum/go-ethereum/consensus/beacon"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/state/codedb"
"github.com/ethereum/go-ethereum/core/stateless"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
@@ -51,7 +52,7 @@ func ExecuteStateless(config *params.ChainConfig, vmconfig vm.Config, block *typ
}
// Create and populate the state database to serve as the stateless backend
memdb := witness.MakeHashDB()
- db, err := state.New(witness.Root(), state.NewDatabase(triedb.NewDatabase(memdb, triedb.HashDefaults), nil))
+ db, err := state.New(witness.Root(), state.NewDatabase(triedb.NewDatabase(memdb, triedb.HashDefaults), codedb.New(memdb)))
if err != nil {
return common.Hash{}, common.Hash{}, err
}
diff --git a/core/verkle_witness_test.go b/core/verkle_witness_test.go
index 9495e325ca9d..a5383d8c1e18 100644
--- a/core/verkle_witness_test.go
+++ b/core/verkle_witness_test.go
@@ -30,6 +30,7 @@ import (
"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/state/codedb"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
@@ -262,7 +263,7 @@ func TestProcessParentBlockHash(t *testing.T) {
cacheConfig := DefaultConfig().WithStateScheme(rawdb.PathScheme)
cacheConfig.SnapshotLimit = 0
triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig(true))
- statedb, _ := state.New(types.EmptyVerkleHash, state.NewDatabase(triedb, nil))
+ statedb, _ := state.New(types.EmptyVerkleHash, state.NewDatabase(triedb, codedb.New(db)))
checkBlockHashes(statedb, true)
})
}
diff --git a/eth/api_debug_test.go b/eth/api_debug_test.go
index 034026b366a0..93d417d2169b 100644
--- a/eth/api_debug_test.go
+++ b/eth/api_debug_test.go
@@ -33,6 +33,7 @@ import (
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/state/codedb"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
@@ -113,7 +114,7 @@ func TestAccountRange(t *testing.T) {
var (
mdb = rawdb.NewMemoryDatabase()
- statedb = state.NewDatabase(triedb.NewDatabase(mdb, &triedb.Config{Preimages: true}), nil)
+ statedb = state.NewDatabase(triedb.NewDatabase(mdb, &triedb.Config{Preimages: true}), codedb.New(mdb))
sdb, _ = state.New(types.EmptyRootHash, statedb)
addrs = [AccountRangeMaxResults * 2]common.Address{}
m = map[common.Address]bool{}
@@ -212,7 +213,7 @@ func TestStorageRangeAt(t *testing.T) {
var (
mdb = rawdb.NewMemoryDatabase()
tdb = triedb.NewDatabase(mdb, &triedb.Config{Preimages: true})
- db = state.NewDatabase(tdb, nil)
+ db = state.NewDatabase(tdb, codedb.New(mdb))
sdb, _ = state.New(types.EmptyRootHash, db)
addr = common.Address{0x01}
keys = []common.Hash{ // hashes of Keys of storage
diff --git a/eth/state_accessor.go b/eth/state_accessor.go
index 79c91043a3c7..0cc47d775ae6 100644
--- a/eth/state_accessor.go
+++ b/eth/state_accessor.go
@@ -69,7 +69,7 @@ func (eth *Ethereum) hashState(ctx context.Context, block *types.Block, reexec u
// TODO(rjl493456442), clean cache is disabled to prevent memory leak,
// please re-enable it for better performance.
tdb := triedb.NewDatabase(eth.chainDb, triedb.HashDefaults)
- database = state.NewDatabase(tdb, nil)
+ database = state.NewDatabase(tdb, eth.blockchain.CodeDB())
if statedb, err = state.New(block.Root(), database); err == nil {
log.Info("Found disk backend for state trie", "root", block.Root(), "number", block.Number())
return statedb, noopReleaser, nil
@@ -87,7 +87,7 @@ func (eth *Ethereum) hashState(ctx context.Context, block *types.Block, reexec u
// TODO(rjl493456442), clean cache is disabled to prevent memory leak,
// please re-enable it for better performance.
tdb = triedb.NewDatabase(eth.chainDb, triedb.HashDefaults)
- database = state.NewDatabase(tdb, nil)
+ database = state.NewDatabase(tdb, eth.blockchain.CodeDB())
// If we didn't check the live database, do check state over ephemeral database,
// otherwise we would rewind past a persisted block (specific corner case is
diff --git a/internal/ethapi/override/override_test.go b/internal/ethapi/override/override_test.go
index 6feafaac756e..ba66d5bae9b0 100644
--- a/internal/ethapi/override/override_test.go
+++ b/internal/ethapi/override/override_test.go
@@ -22,11 +22,9 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
- "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/core/vm"
- "github.com/ethereum/go-ethereum/triedb"
)
type precompileContract struct{}
@@ -40,7 +38,7 @@ func (p *precompileContract) Name() string {
}
func TestStateOverrideMovePrecompile(t *testing.T) {
- db := state.NewDatabase(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil), nil)
+ db := state.NewDatabaseForTesting()
statedb, err := state.New(types.EmptyRootHash, db)
if err != nil {
t.Fatalf("failed to create statedb: %v", err)
diff --git a/miner/miner_test.go b/miner/miner_test.go
index 575ee4d0fd59..2853ac518ff0 100644
--- a/miner/miner_test.go
+++ b/miner/miner_test.go
@@ -155,7 +155,8 @@ func createMiner(t *testing.T) *Miner {
if err != nil {
t.Fatalf("can't create new chain %v", err)
}
- statedb, _ := state.New(bc.Genesis().Root(), bc.StateCache())
+ sdb := state.NewDatabase(bc.TrieDB(), bc.CodeDB())
+ statedb, _ := state.New(bc.Genesis().Root(), sdb)
blockchain := &testBlockChain{bc.Genesis().Root(), chainConfig, statedb, 10000000, new(event.Feed)}
pool := legacypool.New(testTxPoolConfig, blockchain)
diff --git a/tests/state_test_util.go b/tests/state_test_util.go
index 1d6cc8db70f9..309aadbc52b6 100644
--- a/tests/state_test_util.go
+++ b/tests/state_test_util.go
@@ -33,6 +33,7 @@ import (
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/state/codedb"
"github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
@@ -508,7 +509,7 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc, snapshotter bo
tconf.PathDB = pathdb.Defaults
}
triedb := triedb.NewDatabase(db, tconf)
- sdb := state.NewDatabase(triedb, nil)
+ sdb := state.NewDatabase(triedb, codedb.New(db))
statedb, _ := state.New(types.EmptyRootHash, sdb)
for addr, a := range accounts {
statedb.SetCode(addr, a.Code, tracing.CodeChangeUnspecified)
@@ -532,7 +533,7 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc, snapshotter bo
}
snaps, _ = snapshot.New(snapconfig, db, triedb, root)
}
- sdb = state.NewDatabase(triedb, snaps)
+ sdb = state.NewDatabase(triedb, codedb.New(db)).WithSnapshot(snaps)
statedb, _ = state.New(root, sdb)
return StateTestState{statedb, triedb, snaps}
}
diff --git a/triedb/hashdb/database.go b/triedb/hashdb/database.go
index 38392aa5199b..90d051429049 100644
--- a/triedb/hashdb/database.go
+++ b/triedb/hashdb/database.go
@@ -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)
}