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..28c57c5f5a12 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 ) @@ -63,6 +59,11 @@ type Database interface { // 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 +151,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 +176,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 +222,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 +267,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 @@ -295,6 +282,40 @@ 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()) +} + // mustCopyTrie returns a deep-copied trie. func mustCopyTrie(t Trie) Trie { switch t := t.(type) { diff --git a/core/state/database_history.go b/core/state/database_history.go index 314c56c4708a..65585771b515 100644 --- a/core/state/database_history.go +++ b/core/state/database_history.go @@ -20,10 +20,9 @@ import ( "errors" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/lru" + "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/ethdb" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie/utils" "github.com/ethereum/go-ethereum/triedb" @@ -101,21 +100,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 +120,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. @@ -153,3 +148,10 @@ func (db *HistoricDB) TrieDB() *triedb.Database { 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") +} 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/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..9f468ad174b7 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -27,7 +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" @@ -148,7 +147,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 @@ -1155,7 +1154,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 +1208,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 +1306,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 +1333,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 +1343,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..4ec613f54a31 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,9 +1290,10 @@ 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 @@ -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) }