core: impelement state database

This commit is contained in:
Gary Rong 2025-06-24 10:51:20 +08:00
parent b62c0c67fa
commit 006ad0b23c
9 changed files with 176 additions and 60 deletions

View file

@ -288,7 +288,7 @@ type BlockChain struct {
lastWrite uint64 // Last block when the state was flushed
flushInterval atomic.Int64 // Time interval (processing time) after which to flush a state
triedb *triedb.Database // The database handler for maintaining trie nodes.
statedb *state.CachingDB // State database to reuse between imports (contains state cache)
stateDB *stateDatabase // State database to reuse between imports (contains state cache)
txIndexer *txIndexer // Transaction indexer, might be nil if not enabled
hc *HeaderChain
@ -338,19 +338,18 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine,
if cfg == nil {
cfg = DefaultConfig()
}
// Open trie database with provided config
// Open state database with provided config
enableVerkle, err := EnableVerkleAtGenesis(db, genesis)
if err != nil {
return nil, err
}
triedb := triedb.NewDatabase(db, cfg.triedbConfig(enableVerkle))
stateDB := newStateDatabase(db, cfg)
// Write the supplied genesis to the database if it has not been initialized
// yet. The corresponding chain config will be returned, either from the
// provided genesis or from the locally stored configuration if the genesis
// has already been initialized.
chainConfig, genesisHash, compatErr, err := SetupGenesisBlockWithOverride(db, triedb, genesis, cfg.Overrides)
chainConfig, genesisHash, compatErr, err := SetupGenesisBlockWithOverride(db, stateDB.triedb(enableVerkle), genesis, cfg.Overrides)
if err != nil {
return nil, err
}
@ -366,7 +365,8 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine,
chainConfig: chainConfig,
cfg: cfg,
db: db,
triedb: triedb,
stateDB: stateDB,
triedb: stateDB.triedb(false), // TODO(rjl493456442) support verkle
triegc: prque.New[int64, common.Hash](nil),
chainmu: syncx.NewClosableMutex(),
bodyCache: lru.NewCache[common.Hash, *types.Body](bodyCacheLimit),
@ -382,7 +382,6 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine,
return nil, err
}
bc.flushInterval.Store(int64(cfg.TrieTimeLimit))
bc.statedb = state.NewDatabase(bc.triedb, nil)
bc.validator = NewBlockValidator(chainConfig, bc)
bc.prefetcher = newStatePrefetcher(chainConfig, bc.hc)
bc.processor = NewStateProcessor(chainConfig, bc.hc)
@ -548,9 +547,7 @@ func (bc *BlockChain) setupSnapshot() {
AsyncBuild: !bc.cfg.SnapshotWait,
}
bc.snaps, _ = snapshot.New(snapconfig, bc.db, bc.triedb, head.Root)
// Re-initialize the state database with snapshot
bc.statedb = state.NewDatabase(bc.triedb, bc.snaps)
bc.stateDB.setSnapshot(bc.snaps)
}
}
@ -941,7 +938,7 @@ func (bc *BlockChain) rewindPathHead(head *types.Header, root common.Hash) (*typ
// then block number zero is returned, indicating that snapshot recovery is disabled
// and the whole snapshot should be auto-generated in case of head mismatch.
func (bc *BlockChain) rewindHead(head *types.Header, root common.Hash) (*types.Header, uint64) {
if bc.triedb.Scheme() == rawdb.PathScheme {
if bc.cfg.StateScheme == rawdb.PathScheme {
return bc.rewindPathHead(head, root)
}
return bc.rewindHashHead(head, root)
@ -1097,7 +1094,7 @@ func (bc *BlockChain) SnapSyncCommitHead(hash common.Hash) error {
}
// Reset the trie database with the fresh snap synced state.
root := block.Root()
if bc.triedb.Scheme() == rawdb.PathScheme {
if bc.cfg.StateScheme == rawdb.PathScheme {
if err := bc.triedb.Enable(root); err != nil {
return err
}
@ -1269,7 +1266,7 @@ func (bc *BlockChain) Stop() {
}
bc.snaps.Release()
}
if bc.triedb.Scheme() == rawdb.PathScheme {
if bc.cfg.StateScheme == rawdb.PathScheme {
// Ensure that the in-memory trie nodes are journaled to disk properly.
if err := bc.triedb.Journal(bc.CurrentBlock().Root); err != nil {
log.Info("Failed to journal in-memory trie nodes", "err", err)
@ -1312,8 +1309,8 @@ func (bc *BlockChain) Stop() {
bc.logger.OnClose()
}
// Close the trie database, release all the held resources as the last step.
if err := bc.triedb.Close(); err != nil {
log.Error("Failed to close trie database", "err", err)
if err := bc.stateDB.close(); err != nil {
log.Error("Failed to close state database", "err", err)
}
log.Info("Blockchain stopped")
}
@ -1586,7 +1583,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
}
// If node is running in path mode, skip explicit gc operation
// which is unnecessary in this mode.
if bc.triedb.Scheme() == rawdb.PathScheme {
if bc.cfg.StateScheme == rawdb.PathScheme {
return nil
}
// If we're running an archive node, always flush
@ -1956,7 +1953,8 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
defer interrupt.Store(true) // terminate the prefetch at the end
if bc.cfg.NoPrefetch {
statedb, err = state.New(parentRoot, bc.statedb)
sdb := bc.stateDB.stateDB(bc.chainConfig.IsVerkle(block.Number(), block.Time()))
statedb, err = state.New(parentRoot, sdb)
if err != nil {
return nil, err
}
@ -1966,15 +1964,16 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
//
// Note: the main processor and prefetcher share the same reader with a local
// cache for mitigating the overhead of state access.
prefetch, process, err := bc.statedb.ReadersWithCacheStats(parentRoot)
sdb := bc.stateDB.stateDB(bc.chainConfig.IsVerkle(block.Number(), block.Time()))
prefetch, process, err := sdb.ReadersWithCacheStats(parentRoot)
if err != nil {
return nil, err
}
throwaway, err := state.NewWithReader(parentRoot, bc.statedb, prefetch)
throwaway, err := state.NewWithReader(parentRoot, sdb, prefetch)
if err != nil {
return nil, err
}
statedb, err = state.NewWithReader(parentRoot, bc.statedb, process)
statedb, err = state.NewWithReader(parentRoot, sdb, process)
if err != nil {
return nil, err
}

View file

@ -325,8 +325,7 @@ func (bc *BlockChain) TxIndexDone() bool {
// HasState checks if state trie is fully present in the database or not.
func (bc *BlockChain) HasState(hash common.Hash) bool {
_, err := bc.statedb.OpenTrie(hash)
return err == nil
return bc.stateDB.hasState(hash)
}
// HasBlockAndState checks if a block and associated state trie is fully present
@ -345,7 +344,7 @@ func (bc *BlockChain) HasBlockAndState(hash common.Hash, number uint64) bool {
// state is not treated as recoverable if it's available, thus
// false will be returned in this case.
func (bc *BlockChain) stateRecoverable(root common.Hash) bool {
if bc.triedb.Scheme() == rawdb.HashScheme {
if bc.cfg.StateScheme == rawdb.HashScheme {
return false
}
result, _ := bc.triedb.Recoverable(root)
@ -357,7 +356,7 @@ func (bc *BlockChain) stateRecoverable(root common.Hash) bool {
func (bc *BlockChain) ContractCodeWithPrefix(hash common.Hash) []byte {
// TODO(rjl493456442) The associated account address is also required
// in Verkle scheme. Fix it once snap-sync is supported for Verkle.
return bc.statedb.ContractCodeWithPrefix(common.Address{}, hash)
return bc.stateDB.contractCode(common.Address{}, hash)
}
// State returns a new mutable state based on the current HEAD block.
@ -367,7 +366,8 @@ func (bc *BlockChain) State() (*state.StateDB, error) {
// StateAt returns a new mutable state based on a particular point in time.
func (bc *BlockChain) StateAt(root common.Hash) (*state.StateDB, error) {
return state.New(root, bc.statedb)
// TODO(rjl493456442) support verkle state accessing
return state.New(root, bc.stateDB.stateDB(false))
}
// Config retrieves the chain's fork configuration.
@ -393,7 +393,8 @@ func (bc *BlockChain) Processor() Processor {
// StateCache returns the caching database underpinning the blockchain instance.
func (bc *BlockChain) StateCache() state.Database {
return bc.statedb
// TODO(rjl493456442) support verkle database
return bc.stateDB.stateDB(false)
}
// GasLimit returns the gas limit of the current HEAD block.

View file

@ -1845,7 +1845,7 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s
rawdb.WriteLastPivotNumber(db, *tt.pivotBlock)
}
// Pull the plug on the database, simulating a hard crash
chain.triedb.Close()
chain.stateDB.close()
db.Close()
chain.stopWithoutSaving()
@ -1971,7 +1971,7 @@ func testIssue23496(t *testing.T, scheme string) {
}
// Pull the plug on the database, simulating a hard crash
chain.triedb.Close()
chain.stateDB.close()
db.Close()
chain.stopWithoutSaving()

View file

@ -30,7 +30,6 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb/pebble"
"github.com/ethereum/go-ethereum/params"
@ -2033,15 +2032,14 @@ func testSetHeadWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme
t.Fatalf("Failed to import canonical chain tail: %v", err)
}
// Reopen the trie database without persisting in-memory dirty nodes.
chain.triedb.Close()
chain.stateDB.close()
dbconfig := &triedb.Config{}
if scheme == rawdb.PathScheme {
dbconfig.PathDB = pathdb.Defaults
} else {
dbconfig.HashDB = hashdb.Defaults
}
chain.triedb = triedb.NewDatabase(chain.db, dbconfig)
chain.statedb = state.NewDatabase(chain.triedb, chain.snaps)
chain.stateDB = newStateDatabase(chain.db, chain.cfg)
// Force run a freeze cycle
type freezer interface {

View file

@ -257,7 +257,7 @@ func (snaptest *crashSnapshotTest) test(t *testing.T) {
db := chain.db
db.Close()
chain.stopWithoutSaving()
chain.triedb.Close()
chain.stateDB.close()
// Start a new blockchain back up and see where the repair leads us
pdb, err := pebble.New(snaptest.datadir, 0, 0, "", false)
@ -416,7 +416,7 @@ func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) {
}
// Simulate the blockchain crash.
tmp.triedb.Close()
tmp.stateDB.close()
tmp.stopWithoutSaving()
newchain, err = NewBlockChain(snaptest.db, snaptest.gspec, snaptest.engine, DefaultConfig().WithStateScheme(snaptest.scheme))

View file

@ -153,7 +153,8 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
}
return err
}
statedb, err := state.New(blockchain.GetBlockByHash(block.ParentHash()).Root(), blockchain.statedb)
sdb := blockchain.stateDB.stateDB(false)
statedb, err := state.New(blockchain.GetBlockByHash(block.ParentHash()).Root(), sdb)
if err != nil {
return err
}

View file

@ -33,14 +33,14 @@ import (
)
const (
// Number of codehash->size associations to keep.
codeSizeCacheSize = 1_000_000 // 4 megabytes in total
// CodeSizeCacheSize is the number of codehash->size associations to keep.
CodeSizeCacheSize = 1_000_000 // 4 megabytes in total
// Cache size granted for caching clean code.
codeCacheSize = 256 * 1024 * 1024
// CodeCacheSize is the cache size granted for caching clean code.
CodeCacheSize = 256 * 1024 * 1024
// Number of address->curve point associations to keep.
pointCacheSize = 4096
// PointCacheSize is the number of address->curve point associations to keep.
PointCacheSize = 4096
)
// Database wraps access to tries and contract code.
@ -159,9 +159,22 @@ func NewDatabase(triedb *triedb.Database, snap *snapshot.Tree) *CachingDB {
disk: triedb.Disk(),
triedb: triedb,
snap: snap,
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
pointCache: utils.NewPointCache(pointCacheSize),
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](CodeCacheSize),
codeSizeCache: lru.NewCache[common.Hash, int](CodeSizeCacheSize),
pointCache: utils.NewPointCache(PointCacheSize),
}
}
// NewDatabaseWithCache creates a state database with the provided database
// and relative caches.
func NewDatabaseWithCache(disk ethdb.KeyValueStore, triedb *triedb.Database, snap *snapshot.Tree, codeCache *lru.SizeConstrainedCache[common.Hash, []byte], codeSizeCache *lru.Cache[common.Hash, int], pointCache *utils.PointCache) *CachingDB {
return &CachingDB{
disk: disk,
snap: snap,
triedb: triedb,
codeCache: codeCache,
codeSizeCache: codeSizeCache,
pointCache: pointCache,
}
}
@ -248,22 +261,6 @@ func (db *CachingDB) OpenStorageTrie(stateRoot common.Hash, address common.Addre
return tr, nil
}
// ContractCodeWithPrefix retrieves a particular contract's code. If the
// code can't be found in the cache, then check the existence with **new**
// db scheme.
func (db *CachingDB) ContractCodeWithPrefix(address common.Address, codeHash common.Hash) []byte {
code, _ := db.codeCache.Get(codeHash)
if len(code) > 0 {
return code
}
code = rawdb.ReadCodeWithPrefix(db.disk, codeHash)
if len(code) > 0 {
db.codeCache.Add(codeHash, code)
db.codeSizeCache.Add(codeHash, len(code))
}
return code
}
// TrieDB retrieves any intermediate trie-node caching layer.
func (db *CachingDB) TrieDB() *triedb.Database {
return db.triedb

117
core/state_database.go Normal file
View file

@ -0,0 +1,117 @@
// Copyright 2025 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package core
import (
"errors"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/trie/utils"
"github.com/ethereum/go-ethereum/triedb"
)
// stateDatabase is the central structure for managing state data,
// including both the Merkle-Patricia Trie and the Verkle tree (post-transition).
type stateDatabase struct {
disk ethdb.Database
merkle *triedb.Database
verkle *triedb.Database
snap *snapshot.Tree
// Various caches
codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
codeSizeCache *lru.Cache[common.Hash, int]
pointCache *utils.PointCache
}
// newStateDatabase initializes a new stateDatabase instance, creating
// both Merkle and Verkle trie databases.
func newStateDatabase(disk ethdb.Database, cfg *BlockChainConfig) *stateDatabase {
return &stateDatabase{
disk: disk,
merkle: triedb.NewDatabase(disk, cfg.triedbConfig(false)),
verkle: triedb.NewDatabase(disk, cfg.triedbConfig(true)),
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](state.CodeCacheSize),
codeSizeCache: lru.NewCache[common.Hash, int](state.CodeSizeCacheSize),
pointCache: utils.NewPointCache(state.PointCacheSize),
}
}
// setSnapshot assigns a snapshot tree to the state database.
func (s *stateDatabase) setSnapshot(snap *snapshot.Tree) {
s.snap = snap
}
// triedb returns the appropriate trie database based on the given flag.
func (s *stateDatabase) triedb(isVerkle bool) *triedb.Database {
if isVerkle {
return s.verkle
}
return s.merkle
}
// stateDB returns the appropriate state database based on the given flag.
func (s *stateDatabase) stateDB(isVerkle bool) *state.CachingDB {
if isVerkle {
// Verkle is compatible only with path mode; snapshot is integrated natively.
return state.NewDatabaseWithCache(s.disk, s.verkle, nil, s.codeCache, s.codeSizeCache, s.pointCache)
}
return state.NewDatabaseWithCache(s.disk, s.merkle, s.snap, s.codeCache, s.codeSizeCache, s.pointCache)
}
// contractCode retrieves the contract code by its hash. If not present in the
// cache, it falls back to reading from disk using the contract database prefix.
func (s *stateDatabase) contractCode(address common.Address, codeHash common.Hash) []byte {
code, _ := s.codeCache.Get(codeHash)
if len(code) > 0 {
return code
}
code = rawdb.ReadCodeWithPrefix(s.disk, codeHash)
if len(code) > 0 {
s.codeCache.Add(codeHash, code)
s.codeSizeCache.Add(codeHash, len(code))
}
return code
}
// hasState checks whether a trie state exists for the given root hash
// in either the Merkle or Verkle database.
func (s *stateDatabase) hasState(root common.Hash) bool {
_, err := s.merkle.NodeReader(root)
if err == nil {
return true
}
_, err = s.verkle.NodeReader(root)
return err == nil
}
// close terminates the state database and closes two internal trie databases.
func (s *stateDatabase) close() error {
var errs []error
if err := s.merkle.Close(); err != nil {
errs = append(errs, err)
}
if err := s.verkle.Close(); err != nil {
errs = append(errs, err)
}
return errors.Join(errs...)
}

View file

@ -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)
}