mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-12 01:41:36 +00:00
core/state: integrate witness collector
This commit is contained in:
parent
5e23a29b73
commit
d57dca07b1
14 changed files with 424 additions and 203 deletions
|
|
@ -72,11 +72,10 @@ var (
|
|||
accountReadTimer = metrics.NewRegisteredResettingTimer("chain/account/reads", nil)
|
||||
accountHashTimer = metrics.NewRegisteredResettingTimer("chain/account/hashes", nil)
|
||||
accountUpdateTimer = metrics.NewRegisteredResettingTimer("chain/account/updates", nil)
|
||||
accountCommitTimer = metrics.NewRegisteredResettingTimer("chain/account/commits", nil)
|
||||
hasherCommitTimer = metrics.NewRegisteredResettingTimer("chain/trie/commits", nil)
|
||||
|
||||
storageReadTimer = metrics.NewRegisteredResettingTimer("chain/storage/reads", nil)
|
||||
storageUpdateTimer = metrics.NewRegisteredResettingTimer("chain/storage/updates", nil)
|
||||
storageCommitTimer = metrics.NewRegisteredResettingTimer("chain/storage/commits", nil)
|
||||
codeReadTimer = metrics.NewRegisteredResettingTimer("chain/code/reads", nil)
|
||||
codeReadBytesTimer = metrics.NewRegisteredResettingTimer("chain/code/readbytes", nil)
|
||||
|
||||
|
|
@ -2112,12 +2111,16 @@ type ExecuteConfig struct {
|
|||
// it writes the block and associated state to database.
|
||||
func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash, block *types.Block, config ExecuteConfig) (result *blockProcessingResult, blockEndErr error) {
|
||||
var (
|
||||
err error
|
||||
startTime = time.Now()
|
||||
statedb *state.StateDB
|
||||
interrupt atomic.Bool
|
||||
sdb = state.NewDatabase(bc.triedb, bc.codedb).WithSnapshot(bc.snaps)
|
||||
err error
|
||||
startTime = time.Now()
|
||||
statedb *state.StateDB
|
||||
interrupt atomic.Bool
|
||||
sdb = state.NewDatabase(bc.triedb, bc.codedb).WithSnapshot(bc.snaps)
|
||||
makeWitness bool
|
||||
)
|
||||
if bc.chainConfig.IsByzantium(block.Number()) && (config.StatelessSelfValidation || config.MakeWitness) {
|
||||
makeWitness = true
|
||||
}
|
||||
defer interrupt.Store(true) // terminate the prefetch at the end
|
||||
|
||||
if bc.cfg.NoPrefetch {
|
||||
|
|
@ -2126,6 +2129,10 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
|||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// Enable trie node prewarming. The read-only state should also
|
||||
// be prewarmed for constructing a comprehensive execution witness.
|
||||
sdb = sdb.EnablePrefetch(makeWitness)
|
||||
|
||||
// If prefetching is enabled, run that against the current state to pre-cache
|
||||
// transactions and probabilistically some of the account/storage trie nodes.
|
||||
//
|
||||
|
|
@ -2171,20 +2178,16 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
|||
// while processing transactions. Before Byzantium the prefetcher is mostly
|
||||
// useless due to the intermediate root hashing after each transaction.
|
||||
var witness *stateless.Witness
|
||||
if bc.chainConfig.IsByzantium(block.Number()) {
|
||||
if makeWitness {
|
||||
// Generate witnesses either if we're self-testing, or if it's the
|
||||
// only block being inserted. A bit crude, but witnesses are huge,
|
||||
// so we refuse to make an entire chain of them.
|
||||
if config.StatelessSelfValidation || config.MakeWitness {
|
||||
witness, err = stateless.NewWitness(block.Header(), bc, config.EnableWitnessStats)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
witness, err = stateless.NewWitness(block.Header(), bc, config.EnableWitnessStats)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
statedb.StartPrefetcher("chain", witness)
|
||||
defer statedb.StopPrefetcher()
|
||||
statedb.TraceWitness(witness)
|
||||
}
|
||||
|
||||
// Instrument the blockchain tracing
|
||||
if config.EnableTracer {
|
||||
if bc.logger != nil && bc.logger.OnBlockStart != nil {
|
||||
|
|
@ -2252,34 +2255,9 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
|||
}
|
||||
|
||||
var (
|
||||
xvtime = time.Since(xvstart)
|
||||
proctime = time.Since(startTime) // processing + validation + cross validation
|
||||
stats = &ExecuteStats{}
|
||||
stats = NewExecuteStats(statedb, ptime, vtime, time.Since(xvstart))
|
||||
)
|
||||
// Update the metrics touched during block processing and validation
|
||||
stats.AccountReads = statedb.AccountReads // Account reads are complete(in processing)
|
||||
stats.StorageReads = statedb.StorageReads // Storage reads are complete(in processing)
|
||||
stats.AccountUpdates = statedb.AccountUpdates // Account updates are complete(in validation)
|
||||
stats.StorageUpdates = statedb.StorageUpdates // Storage updates are complete(in validation)
|
||||
stats.AccountHashes = statedb.AccountHashes // Account hashes are complete(in validation)
|
||||
stats.CodeReads = statedb.CodeReads
|
||||
|
||||
stats.AccountLoaded = statedb.AccountLoaded
|
||||
//stats.AccountUpdated = statedb.AccountUpdated
|
||||
stats.AccountDeleted = statedb.AccountDeleted
|
||||
stats.StorageLoaded = statedb.StorageLoaded
|
||||
stats.StorageUpdated = int(statedb.StorageUpdated.Load())
|
||||
stats.StorageDeleted = int(statedb.StorageDeleted.Load())
|
||||
|
||||
stats.CodeLoaded = statedb.CodeLoaded
|
||||
stats.CodeLoadBytes = statedb.CodeLoadBytes
|
||||
//stats.CodeUpdated = statedb.CodeUpdated
|
||||
//stats.CodeUpdateBytes = statedb.CodeUpdateBytes
|
||||
|
||||
stats.Execution = ptime - (statedb.AccountReads + statedb.StorageReads + statedb.CodeReads) // The time spent on EVM processing
|
||||
stats.Validation = vtime - (statedb.AccountHashes + statedb.AccountUpdates + statedb.StorageUpdates) // The time spent on block validation
|
||||
stats.CrossValidation = xvtime // The time spent on stateless cross validation
|
||||
|
||||
// Write the block to the chain and get the status.
|
||||
var status WriteStatus
|
||||
if config.WriteState {
|
||||
|
|
@ -2294,10 +2272,9 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
|||
return nil, err
|
||||
}
|
||||
// Update the metrics touched during block commit
|
||||
stats.AccountCommits = statedb.AccountCommits // Account commits are complete, we can mark them
|
||||
stats.StorageCommits = statedb.StorageCommits // Storage commits are complete, we can mark them
|
||||
stats.HasherCommit = statedb.HasherCommits // Storage commits are complete, we can mark them
|
||||
stats.DatabaseCommit = statedb.DatabaseCommits // Database commits are complete, we can mark them
|
||||
stats.BlockWrite = time.Since(wstart) - max(statedb.AccountCommits, statedb.StorageCommits) /* concurrent */ - statedb.DatabaseCommits
|
||||
stats.BlockWrite = time.Since(wstart) - statedb.HasherCommits - statedb.DatabaseCommits
|
||||
}
|
||||
// Report the collected witness statistics
|
||||
if witness != nil {
|
||||
|
|
|
|||
|
|
@ -424,6 +424,11 @@ func (bc *BlockChain) StateAt(root common.Hash) (*state.StateDB, error) {
|
|||
return state.New(root, state.NewDatabase(bc.triedb, bc.codedb).WithSnapshot(bc.snaps))
|
||||
}
|
||||
|
||||
// StateWithPrefetching returns a new mutable state based on a particular point in time.
|
||||
func (bc *BlockChain) StateWithPrefetching(root common.Hash) (*state.StateDB, error) {
|
||||
return state.New(root, state.NewDatabase(bc.triedb, bc.codedb).WithSnapshot(bc.snaps).EnablePrefetch(false))
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
|
|
|||
|
|
@ -29,14 +29,30 @@ import (
|
|||
// ExecuteStats includes all the statistics of a block execution in details.
|
||||
type ExecuteStats struct {
|
||||
// State read times
|
||||
AccountReads time.Duration // Time spent on the account reads
|
||||
StorageReads time.Duration // Time spent on the storage reads
|
||||
AccountReads time.Duration // Time spent on the account reads
|
||||
StorageReads time.Duration // Time spent on the storage reads
|
||||
CodeReads time.Duration // Time spent on the contract code read
|
||||
|
||||
// State hash times
|
||||
AccountHashes time.Duration // Time spent on the account trie hash
|
||||
AccountUpdates time.Duration // Time spent on the account trie update
|
||||
AccountCommits time.Duration // Time spent on the account trie commit
|
||||
StorageUpdates time.Duration // Time spent on the storage trie update
|
||||
StorageCommits time.Duration // Time spent on the storage trie commit
|
||||
CodeReads time.Duration // Time spent on the contract code read
|
||||
|
||||
// EVM execution time
|
||||
Execution time.Duration // Time spent on the EVM execution
|
||||
|
||||
// Validation times
|
||||
Validation time.Duration // Time spent on the block validation
|
||||
CrossValidation time.Duration // Optional, time spent on the block cross validation
|
||||
|
||||
// Commit times
|
||||
HasherCommit time.Duration // Time spent on trie commit
|
||||
DatabaseCommit time.Duration // Time spent on database commit
|
||||
BlockWrite time.Duration // Time spent on block write
|
||||
|
||||
// Others
|
||||
TotalTime time.Duration // The total time spent on block execution
|
||||
MgasPerSecond float64 // The million gas processed per second
|
||||
|
||||
AccountLoaded int // Number of accounts loaded
|
||||
AccountUpdated int // Number of accounts updated
|
||||
|
|
@ -49,19 +65,40 @@ type ExecuteStats struct {
|
|||
CodeUpdated int // Number of contract code written (CREATE/CREATE2 + EIP-7702)
|
||||
CodeUpdateBytes int // Total bytes of code written
|
||||
|
||||
Execution time.Duration // Time spent on the EVM execution
|
||||
Validation time.Duration // Time spent on the block validation
|
||||
CrossValidation time.Duration // Optional, time spent on the block cross validation
|
||||
DatabaseCommit time.Duration // Time spent on database commit
|
||||
BlockWrite time.Duration // Time spent on block write
|
||||
TotalTime time.Duration // The total time spent on block execution
|
||||
MgasPerSecond float64 // The million gas processed per second
|
||||
|
||||
// Cache hit rates
|
||||
StateReadCacheStats state.ReaderStats
|
||||
StatePrefetchCacheStats state.ReaderStats
|
||||
}
|
||||
|
||||
func NewExecuteStats(stateDB *state.StateDB, process time.Duration, validation time.Duration, crossValidation time.Duration) *ExecuteStats {
|
||||
return &ExecuteStats{
|
||||
// State read times
|
||||
AccountReads: stateDB.AccountReads,
|
||||
StorageReads: stateDB.StorageReads,
|
||||
CodeReads: stateDB.CodeReads,
|
||||
|
||||
// State hash times
|
||||
AccountHashes: stateDB.AccountHashes,
|
||||
AccountUpdates: stateDB.AccountUpdates,
|
||||
StorageUpdates: stateDB.StorageUpdates,
|
||||
|
||||
Execution: process - stateDB.StateReadTime(),
|
||||
Validation: validation - stateDB.StateHashTime(),
|
||||
CrossValidation: crossValidation,
|
||||
|
||||
AccountLoaded: stateDB.AccountLoaded,
|
||||
AccountUpdated: stateDB.AccountUpdated,
|
||||
AccountDeleted: stateDB.AccountDeleted,
|
||||
StorageLoaded: stateDB.StorageLoaded,
|
||||
StorageUpdated: int(stateDB.StorageUpdated.Load()),
|
||||
StorageDeleted: int(stateDB.StorageDeleted.Load()),
|
||||
CodeLoaded: stateDB.CodeLoaded,
|
||||
CodeLoadBytes: stateDB.CodeLoadBytes,
|
||||
CodeUpdated: stateDB.CodeUpdated,
|
||||
CodeUpdateBytes: stateDB.CodeUpdateBytes,
|
||||
}
|
||||
}
|
||||
|
||||
// reportMetrics uploads execution statistics to the metrics system.
|
||||
func (s *ExecuteStats) reportMetrics() {
|
||||
if s.AccountLoaded != 0 {
|
||||
|
|
@ -80,8 +117,7 @@ func (s *ExecuteStats) reportMetrics() {
|
|||
accountUpdateTimer.Update(s.AccountUpdates) // Account updates are complete(in validation)
|
||||
storageUpdateTimer.Update(s.StorageUpdates) // Storage updates are complete(in validation)
|
||||
accountHashTimer.Update(s.AccountHashes) // Account hashes are complete(in validation)
|
||||
accountCommitTimer.Update(s.AccountCommits) // Account commits are complete, we can mark them
|
||||
storageCommitTimer.Update(s.StorageCommits) // Storage commits are complete, we can mark them
|
||||
hasherCommitTimer.Update(s.HasherCommit) // Trie commits are complete, we can mark them
|
||||
|
||||
blockExecutionTimer.Update(s.Execution) // The time spent on EVM processing
|
||||
blockValidationTimer.Update(s.Validation) // The time spent on block validation
|
||||
|
|
@ -206,7 +242,7 @@ func (s *ExecuteStats) logSlow(block *types.Block, slowBlockThreshold time.Durat
|
|||
ExecutionMs: durationToMs(s.Execution),
|
||||
StateReadMs: durationToMs(s.AccountReads + s.StorageReads + s.CodeReads),
|
||||
StateHashMs: durationToMs(s.AccountHashes + s.AccountUpdates + s.StorageUpdates),
|
||||
CommitMs: durationToMs(max(s.AccountCommits, s.StorageCommits) + s.DatabaseCommit + s.BlockWrite),
|
||||
CommitMs: durationToMs(s.HasherCommit + s.DatabaseCommit + s.BlockWrite),
|
||||
TotalMs: durationToMs(s.TotalTime),
|
||||
},
|
||||
Throughput: slowBlockThru{
|
||||
|
|
|
|||
|
|
@ -150,6 +150,9 @@ type CachingDB struct {
|
|||
triedb *triedb.Database
|
||||
codedb *CodeDB
|
||||
snap *snapshot.Tree
|
||||
|
||||
prefetch bool
|
||||
prefetchRead bool
|
||||
}
|
||||
|
||||
// NewDatabase creates a state database with the provided data sources.
|
||||
|
|
@ -177,6 +180,13 @@ func (db *CachingDB) WithSnapshot(snapshot *snapshot.Tree) *CachingDB {
|
|||
return db
|
||||
}
|
||||
|
||||
// EnablePrefetch enables the hasher prefetching feature.
|
||||
func (db *CachingDB) EnablePrefetch(prefetchRead bool) *CachingDB {
|
||||
db.prefetch = true
|
||||
db.prefetchRead = prefetchRead
|
||||
return db
|
||||
}
|
||||
|
||||
// StateReader returns a state reader associated with the specified state root.
|
||||
func (db *CachingDB) StateReader(stateRoot common.Hash) (StateReader, error) {
|
||||
var readers []StateReader
|
||||
|
|
@ -224,7 +234,7 @@ func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
|
|||
// Hasher implements Database, returning a hasher associated with the specified
|
||||
// state root.
|
||||
func (db *CachingDB) Hasher(stateRoot common.Hash) (Hasher, error) {
|
||||
return newMerkleHasher(stateRoot, db.triedb, true, true)
|
||||
return newMerkleHasher(stateRoot, db.triedb, db.prefetch, db.prefetchRead)
|
||||
}
|
||||
|
||||
// ReadersWithCacheStats creates a pair of state readers that share the same
|
||||
|
|
|
|||
|
|
@ -93,9 +93,9 @@ type Prefetcher interface {
|
|||
// WitnessCollector is an optional extension implemented by hashers that can
|
||||
// construct a state witness for the most recent committed state transition.
|
||||
type WitnessCollector interface {
|
||||
// Witness returns the state witness corresponding to the most recent
|
||||
// CollectWitness returns the state witness corresponding to the most recent
|
||||
// committed state transition.
|
||||
Witness() (*stateless.Witness, error)
|
||||
CollectWitness(*stateless.Witness)
|
||||
}
|
||||
|
||||
// Prover is an optional extension implemented by hashers that can construct
|
||||
|
|
|
|||
|
|
@ -21,12 +21,14 @@ import (
|
|||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/stateless"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||
"github.com/ethereum/go-ethereum/triedb"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// wrapTrie pairs a StateTrie with an optional background prefetcher that
|
||||
|
|
@ -36,6 +38,7 @@ type wrapTrie struct {
|
|||
prefetcher *prefetcher
|
||||
}
|
||||
|
||||
// newWrapTrie creates a merkle trie with the optional prefetcher enabled.
|
||||
func newWrapTrie(id *trie.ID, db *triedb.Database, prefetch bool, prefetchRead bool) (*wrapTrie, error) {
|
||||
t, err := trie.NewStateTrie(id, db)
|
||||
if err != nil {
|
||||
|
|
@ -59,7 +62,7 @@ func (tr *wrapTrie) term() {
|
|||
tr.prefetcher = nil
|
||||
}
|
||||
|
||||
// The methods below shadow the embedded StateTrie so that any direct trie
|
||||
// The methods below shadow the embedded trie.StateTrie so that any direct trie
|
||||
// access auto-terminates the prefetcher first. This makes data-race freedom
|
||||
// structural: callers never need to remember to call term() manually.
|
||||
|
||||
|
|
@ -98,9 +101,9 @@ func (tr *wrapTrie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error {
|
|||
return tr.StateTrie.Prove(key, proofDb)
|
||||
}
|
||||
|
||||
func (tr *wrapTrie) copy() *wrapTrie {
|
||||
func (tr *wrapTrie) Witness() map[string][]byte {
|
||||
tr.term()
|
||||
return &wrapTrie{StateTrie: tr.StateTrie.Copy()}
|
||||
return tr.StateTrie.Witness()
|
||||
}
|
||||
|
||||
func (tr *wrapTrie) prefetchAccounts(addresses []common.Address, read bool) {
|
||||
|
|
@ -117,22 +120,31 @@ func (tr *wrapTrie) prefetchStorage(addr common.Address, keys []common.Hash, rea
|
|||
tr.prefetcher.scheduleSlots(addr, keys, read)
|
||||
}
|
||||
|
||||
// rootReader wraps the account trie for loading the storage root. It is
|
||||
// essential to use an independent trie to prevent potential data races
|
||||
// with the optional prefetcher.
|
||||
type rootReader struct {
|
||||
// copy returns a deep-copied state trie. Notably the prefetcher is deliberately
|
||||
// not copied, as it only belongs to the original one.
|
||||
func (tr *wrapTrie) copy() *wrapTrie {
|
||||
tr.term()
|
||||
return &wrapTrie{StateTrie: tr.StateTrie.Copy()}
|
||||
}
|
||||
|
||||
// storageRootReader wraps the account trie for loading the storage root. It is
|
||||
// essential to use an independent trie to prevent potential data races with
|
||||
// the optional prefetcher.
|
||||
//
|
||||
// TODO(rjl493456442) use the flat state for better read efficiency.
|
||||
type storageRootReader struct {
|
||||
tr *trie.StateTrie
|
||||
}
|
||||
|
||||
func newRootReader(root common.Hash, db *triedb.Database) (*rootReader, error) {
|
||||
func newStorageRootReader(root common.Hash, db *triedb.Database) (*storageRootReader, error) {
|
||||
t, err := trie.NewStateTrie(trie.StateTrieID(root), db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rootReader{tr: t}, nil
|
||||
return &storageRootReader{tr: t}, nil
|
||||
}
|
||||
|
||||
func (r *rootReader) readStorageRoot(address common.Address) (common.Hash, error) {
|
||||
func (r *storageRootReader) read(address common.Address) (common.Hash, error) {
|
||||
acct, err := r.tr.GetAccount(address)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
|
|
@ -143,8 +155,8 @@ func (r *rootReader) readStorageRoot(address common.Address) (common.Hash, error
|
|||
return acct.Root, nil
|
||||
}
|
||||
|
||||
func (r *rootReader) copy() *rootReader {
|
||||
return &rootReader{tr: r.tr.Copy()}
|
||||
func (r *storageRootReader) copy() *storageRootReader {
|
||||
return &storageRootReader{tr: r.tr.Copy()}
|
||||
}
|
||||
|
||||
// merkleHasher is a Hasher implementation backed by the traditional two-layer
|
||||
|
|
@ -152,7 +164,7 @@ func (r *rootReader) copy() *rootReader {
|
|||
type merkleHasher struct {
|
||||
db *triedb.Database
|
||||
root common.Hash
|
||||
reader *rootReader
|
||||
reader *storageRootReader
|
||||
prefetch bool
|
||||
prefetchRead bool
|
||||
|
||||
|
|
@ -160,7 +172,7 @@ type merkleHasher struct {
|
|||
storageTries map[common.Address]*wrapTrie
|
||||
|
||||
// deletedTries preserves storage tries of accounts that were deleted
|
||||
// during the block. Keyed by address; only the first deletion per
|
||||
// during the block keyed by address. Only the first deletion per
|
||||
// address is recorded (the pre-block incarnation).
|
||||
deletedTries map[common.Address]*wrapTrie
|
||||
|
||||
|
|
@ -169,7 +181,8 @@ type merkleHasher struct {
|
|||
// UpdateStorage or set to EmptyRootHash on deletion.
|
||||
storageRoots map[common.Address]Hashes
|
||||
|
||||
storageLock sync.Mutex // guards storage trie fields
|
||||
// Lock guards storage trie fields
|
||||
storageLock sync.Mutex
|
||||
}
|
||||
|
||||
func newMerkleHasher(root common.Hash, db *triedb.Database, prefetch bool, prefetchRead bool) (*merkleHasher, error) {
|
||||
|
|
@ -177,7 +190,7 @@ func newMerkleHasher(root common.Hash, db *triedb.Database, prefetch bool, prefe
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r, err := newRootReader(root, db)
|
||||
r, err := newStorageRootReader(root, db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -201,11 +214,14 @@ func (h *merkleHasher) storageRoot(addr common.Address) (common.Hash, error) {
|
|||
if hashes, ok := h.storageRoots[addr]; ok {
|
||||
return hashes.Hash, nil
|
||||
}
|
||||
root, err := h.reader.readStorageRoot(addr)
|
||||
root, err := h.reader.read(addr)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
h.storageRoots[addr] = Hashes{Prev: root, Hash: root}
|
||||
h.storageRoots[addr] = Hashes{
|
||||
Prev: root,
|
||||
Hash: root,
|
||||
}
|
||||
return root, nil
|
||||
}
|
||||
|
||||
|
|
@ -223,6 +239,7 @@ func (h *merkleHasher) openStorageTrie(address common.Address, prefetch bool) (*
|
|||
return nil, err
|
||||
}
|
||||
id := trie.StorageTrieID(h.root, crypto.Keccak256Hash(address.Bytes()), root)
|
||||
|
||||
tr, err := newWrapTrie(id, h.db, h.prefetch && prefetch, h.prefetchRead)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -231,8 +248,9 @@ func (h *merkleHasher) openStorageTrie(address common.Address, prefetch bool) (*
|
|||
return tr, nil
|
||||
}
|
||||
|
||||
// deleteAccount removes the account specified by the address from the state.
|
||||
func (h *merkleHasher) deleteAccount(addr common.Address) error {
|
||||
// Deletion: capture the original storage root before modifying the trie.
|
||||
// Capture the original storage root before modifying the trie.
|
||||
_, err := h.storageRoot(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -251,6 +269,7 @@ func (h *merkleHasher) deleteAccount(addr common.Address) error {
|
|||
return h.acctTrie.DeleteAccount(addr)
|
||||
}
|
||||
|
||||
// update writes the account specified by the address into the state.
|
||||
func (h *merkleHasher) updateAccount(addr common.Address, account AccountMut) error {
|
||||
root, err := h.storageRoot(addr)
|
||||
if err != nil {
|
||||
|
|
@ -265,10 +284,12 @@ func (h *merkleHasher) updateAccount(addr common.Address, account AccountMut) er
|
|||
return h.acctTrie.UpdateAccount(addr, data, 0)
|
||||
}
|
||||
|
||||
// UpdateAccount implements Hasher.
|
||||
// UpdateAccount implements Hasher, writing a list of account mutations
|
||||
// into the state. The assumption is held all the storage changes have
|
||||
// already been written beforehand.
|
||||
func (h *merkleHasher) UpdateAccount(addresses []common.Address, accounts []AccountMut) error {
|
||||
var err error
|
||||
for i, addr := range addresses {
|
||||
var err error
|
||||
if accounts[i].Account == nil {
|
||||
err = h.deleteAccount(addr)
|
||||
} else {
|
||||
|
|
@ -281,7 +302,9 @@ func (h *merkleHasher) UpdateAccount(addresses []common.Address, accounts []Acco
|
|||
return nil
|
||||
}
|
||||
|
||||
// UpdateStorage implements Hasher.
|
||||
// UpdateStorage implements Hasher, writing a list of storage slot mutations
|
||||
// into the state. This function must be invoked first before writing the
|
||||
// associated account metadata into the state.
|
||||
func (h *merkleHasher) UpdateStorage(address common.Address, keys []common.Hash, values []common.Hash) error {
|
||||
tr, err := h.openStorageTrie(address, false)
|
||||
if err != nil {
|
||||
|
|
@ -289,18 +312,19 @@ func (h *merkleHasher) UpdateStorage(address common.Address, keys []common.Hash,
|
|||
}
|
||||
for i, key := range keys {
|
||||
if values[i] == (common.Hash{}) {
|
||||
if err := tr.DeleteStorage(address, key[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
err = tr.DeleteStorage(address, key[:])
|
||||
} else {
|
||||
if err := tr.UpdateStorage(address, key[:], common.TrimLeftZeroes(values[i][:])); err != nil {
|
||||
return err
|
||||
}
|
||||
err = tr.UpdateStorage(address, key[:], common.TrimLeftZeroes(values[i][:]))
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Hash outside the lock to allow full parallelism across accounts.
|
||||
hash := tr.Hash()
|
||||
|
||||
// Write back the storage root back for reflecting the most recent
|
||||
// changes.
|
||||
h.storageLock.Lock()
|
||||
h.storageRoots[address] = Hashes{
|
||||
Prev: h.storageRoots[address].Prev,
|
||||
|
|
@ -310,50 +334,64 @@ func (h *merkleHasher) UpdateStorage(address common.Address, keys []common.Hash,
|
|||
return nil
|
||||
}
|
||||
|
||||
// Hash implements Hasher, computing the state root hash without committing.
|
||||
func (h *merkleHasher) Hash() common.Hash {
|
||||
return h.acctTrie.Hash()
|
||||
}
|
||||
|
||||
// Close terminates all prefetcher goroutines. Safe to call multiple times.
|
||||
func (h *merkleHasher) Close() {
|
||||
h.acctTrie.term()
|
||||
for _, tr := range h.storageTries {
|
||||
tr.term()
|
||||
}
|
||||
for _, tr := range h.deletedTries {
|
||||
tr.term()
|
||||
}
|
||||
}
|
||||
|
||||
// Commit implements Hasher, finalizing all pending changes and returning
|
||||
// the resulting state root hash, along with the set of dirty trie nodes
|
||||
// generated by the updates.
|
||||
func (h *merkleHasher) Commit() (common.Hash, *trienode.MergedNodeSet, map[common.Address]Hashes, error) {
|
||||
// Explicitly terminate all resolved tries. Some of them may not be
|
||||
// terminated due to read-only prefetching. This is essential to
|
||||
// prevent goroutine leaks.
|
||||
h.Close()
|
||||
|
||||
nodes := trienode.NewMergedNodeSet()
|
||||
var (
|
||||
eg errgroup.Group
|
||||
root common.Hash
|
||||
|
||||
lock sync.Mutex
|
||||
nodes = trienode.NewMergedNodeSet()
|
||||
merge = func(set *trienode.NodeSet) error {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
return nodes.Merge(set)
|
||||
}
|
||||
)
|
||||
eg.Go(func() error {
|
||||
r, set := h.acctTrie.Commit(true)
|
||||
root = r
|
||||
if set == nil {
|
||||
return nil
|
||||
}
|
||||
return merge(set)
|
||||
})
|
||||
for _, tr := range h.storageTries {
|
||||
if _, set := tr.Commit(false); set != nil {
|
||||
if err := nodes.Merge(set); err != nil {
|
||||
return common.Hash{}, nil, nil, err
|
||||
eg.Go(func() error {
|
||||
_, set := tr.Commit(false)
|
||||
if set == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return merge(set)
|
||||
})
|
||||
}
|
||||
root, set := h.acctTrie.Commit(true)
|
||||
if set != nil {
|
||||
if err := nodes.Merge(set); err != nil {
|
||||
return common.Hash{}, nil, nil, err
|
||||
}
|
||||
if err := eg.Wait(); err != nil {
|
||||
return common.Hash{}, nil, nil, err
|
||||
}
|
||||
return root, nodes, h.storageRoots, nil
|
||||
}
|
||||
|
||||
// Copy implements Hasher, returning a deep-copied hasher instance.
|
||||
func (h *merkleHasher) Copy() Hasher {
|
||||
cpy := &merkleHasher{
|
||||
db: h.db,
|
||||
root: h.root,
|
||||
reader: h.reader.copy(),
|
||||
prefetch: false,
|
||||
prefetchRead: false,
|
||||
acctTrie: h.acctTrie.copy(),
|
||||
storageTries: make(map[common.Address]*wrapTrie, len(h.storageTries)),
|
||||
deletedTries: make(map[common.Address]*wrapTrie, len(h.deletedTries)),
|
||||
|
|
@ -368,12 +406,24 @@ func (h *merkleHasher) Copy() Hasher {
|
|||
return cpy
|
||||
}
|
||||
|
||||
// ProveAccount implements Prover.
|
||||
// Close terminates all prefetcher goroutines. Safe to call multiple times.
|
||||
func (h *merkleHasher) Close() {
|
||||
h.acctTrie.term()
|
||||
for _, tr := range h.storageTries {
|
||||
tr.term()
|
||||
}
|
||||
for _, tr := range h.deletedTries {
|
||||
tr.term()
|
||||
}
|
||||
}
|
||||
|
||||
// ProveAccount implements Prover, constructing a proof for the given account.
|
||||
func (h *merkleHasher) ProveAccount(addr common.Address, proofDb ethdb.KeyValueWriter) error {
|
||||
return h.acctTrie.Prove(crypto.Keccak256(addr.Bytes()), proofDb)
|
||||
}
|
||||
|
||||
// ProveStorage implements Prover.
|
||||
// ProveStorage implements Prover, constructing a proof for the given storage
|
||||
// slot of the specified account.
|
||||
func (h *merkleHasher) ProveStorage(addr common.Address, key common.Hash, proofDb ethdb.KeyValueWriter) error {
|
||||
tr, err := h.openStorageTrie(addr, false)
|
||||
if err != nil {
|
||||
|
|
@ -382,6 +432,19 @@ func (h *merkleHasher) ProveStorage(addr common.Address, key common.Hash, proofD
|
|||
return tr.Prove(crypto.Keccak256(key.Bytes()), proofDb)
|
||||
}
|
||||
|
||||
// CollectWitness implements WitnessCollector. It aggregates all trie nodes
|
||||
// accessed (both read and write) across the account trie, all active storage
|
||||
// tries and deleted storage tries into a single state witness.
|
||||
func (h *merkleHasher) CollectWitness(witness *stateless.Witness) {
|
||||
witness.AddState(h.acctTrie.Witness(), common.Hash{})
|
||||
for addr, tr := range h.storageTries {
|
||||
witness.AddState(tr.Witness(), crypto.Keccak256Hash(addr.Bytes()))
|
||||
}
|
||||
for addr, tr := range h.deletedTries {
|
||||
witness.AddState(tr.Witness(), crypto.Keccak256Hash(addr.Bytes()))
|
||||
}
|
||||
}
|
||||
|
||||
// PrefetchAccount implements Prefetcher, preloading the nodes of specific accounts.
|
||||
func (h *merkleHasher) PrefetchAccount(addresses []common.Address, read bool) {
|
||||
if !h.prefetch {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/stateless"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/triedb"
|
||||
"github.com/holiman/uint256"
|
||||
|
|
@ -539,3 +540,90 @@ func TestMerkleHasherCopy(t *testing.T) {
|
|||
t.Fatal("original root changed after mutating copy")
|
||||
}
|
||||
}
|
||||
|
||||
// proofNodes collects the raw RLP-encoded trie nodes written by Prove calls.
|
||||
type proofNodes struct{ nodes [][]byte }
|
||||
|
||||
func (p *proofNodes) Put(key []byte, value []byte) error {
|
||||
p.nodes = append(p.nodes, common.CopyBytes(value))
|
||||
return nil
|
||||
}
|
||||
func (p *proofNodes) Delete([]byte) error { return nil }
|
||||
|
||||
// TestMerkleHasherWitness verifies that the witness returned by Witness()
|
||||
// contains every trie node on the Merkle proof path for each accessed account
|
||||
// and storage slot, including nodes from deleted storage tries.
|
||||
func TestMerkleHasherWitness(t *testing.T) {
|
||||
h := makeBaseState(t, hasherTestConfig{"prefetchAll", true, true})
|
||||
|
||||
// Mutate addr1 storage, then delete and recreate with different
|
||||
// storage so that both deletedTries and storageTries are populated.
|
||||
h.PrefetchStorage(hasherAddr1, []common.Hash{hasherSlot1}, false)
|
||||
if err := h.UpdateStorage(hasherAddr1, []common.Hash{hasherSlot1}, []common.Hash{hasherVal2}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := h.UpdateAccount([]common.Address{hasherAddr1}, []AccountMut{hasherDeleteAccount()}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := h.UpdateStorage(hasherAddr1, []common.Hash{hasherSlot3}, []common.Hash{hasherVal3}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := h.UpdateAccount(
|
||||
[]common.Address{hasherAddr1, hasherAddr2},
|
||||
[]AccountMut{hasherAccount(10, 500), hasherAccount(2, 300)},
|
||||
); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
witness := &stateless.Witness{
|
||||
Codes: make(map[string]struct{}),
|
||||
State: make(map[string]struct{}),
|
||||
}
|
||||
h.CollectWitness(witness)
|
||||
|
||||
if len(witness.State) == 0 {
|
||||
t.Fatal("witness should contain trie nodes")
|
||||
}
|
||||
// Open a separate prover from the same pre-state root. Proofs
|
||||
// generated here traverse the same trie paths that the mutating
|
||||
// hasher loaded, so every proof node must be in the witness.
|
||||
prover, err := newMerkleHasher(h.root, h.db, false, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer prover.Close()
|
||||
|
||||
// Collect all expected proof nodes into a single set. The union of
|
||||
// account proofs (addr1, addr2) and storage proofs (addr1/slot1)
|
||||
// should exactly equal witness.State — no missing, no extra.
|
||||
expected := make(map[string]struct{})
|
||||
|
||||
for _, addr := range []common.Address{hasherAddr1, hasherAddr2} {
|
||||
pn := &proofNodes{}
|
||||
if err := prover.ProveAccount(addr, pn); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, node := range pn.nodes {
|
||||
expected[string(node)] = struct{}{}
|
||||
}
|
||||
}
|
||||
// Storage proof for addr1/slot1 (accessed before deletion).
|
||||
// Slot2 was in the base state but never read or written during the
|
||||
// block, so its leaf node is correctly absent from the witness.
|
||||
pn := &proofNodes{}
|
||||
if err := prover.ProveStorage(hasherAddr1, hasherSlot1, pn); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, node := range pn.nodes {
|
||||
expected[string(node)] = struct{}{}
|
||||
}
|
||||
// Every expected proof node must be in the witness.
|
||||
for node := range expected {
|
||||
if _, ok := witness.State[node]; !ok {
|
||||
t.Fatal("proof node missing from witness")
|
||||
}
|
||||
}
|
||||
// The witness must not contain any extra nodes beyond the proofs.
|
||||
if len(witness.State) != len(expected) {
|
||||
t.Fatalf("witness has %d nodes, expected %d (extra junk present)", len(witness.State), len(expected))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,14 +19,14 @@ package state
|
|||
import "github.com/ethereum/go-ethereum/metrics"
|
||||
|
||||
var (
|
||||
accountReadMeters = metrics.NewRegisteredMeter("state/read/account", nil)
|
||||
storageReadMeters = metrics.NewRegisteredMeter("state/read/storage", nil)
|
||||
accountUpdatedMeter = metrics.NewRegisteredMeter("state/update/account", nil)
|
||||
storageUpdatedMeter = metrics.NewRegisteredMeter("state/update/storage", nil)
|
||||
accountDeletedMeter = metrics.NewRegisteredMeter("state/delete/account", nil)
|
||||
storageDeletedMeter = metrics.NewRegisteredMeter("state/delete/storage", nil)
|
||||
accountTrieUpdatedMeter = metrics.NewRegisteredMeter("state/update/accountnodes", nil)
|
||||
storageTriesUpdatedMeter = metrics.NewRegisteredMeter("state/update/storagenodes", nil)
|
||||
accountTrieDeletedMeter = metrics.NewRegisteredMeter("state/delete/accountnodes", nil)
|
||||
storageTriesDeletedMeter = metrics.NewRegisteredMeter("state/delete/storagenodes", nil)
|
||||
accountReadMeters = metrics.NewRegisteredMeter("state/read/account", nil)
|
||||
storageReadMeters = metrics.NewRegisteredMeter("state/read/storage", nil)
|
||||
accountUpdatedMeter = metrics.NewRegisteredMeter("state/update/account", nil)
|
||||
storageUpdatedMeter = metrics.NewRegisteredMeter("state/update/storage", nil)
|
||||
accountDeletedMeter = metrics.NewRegisteredMeter("state/delete/account", nil)
|
||||
storageDeletedMeter = metrics.NewRegisteredMeter("state/delete/storage", nil)
|
||||
//accountTrieUpdatedMeter = metrics.NewRegisteredMeter("state/update/accountnodes", nil)
|
||||
//storageTriesUpdatedMeter = metrics.NewRegisteredMeter("state/update/storagenodes", nil)
|
||||
//accountTrieDeletedMeter = metrics.NewRegisteredMeter("state/delete/accountnodes", nil)
|
||||
//storageTriesDeletedMeter = metrics.NewRegisteredMeter("state/delete/storagenodes", nil)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -63,7 +63,6 @@ type Account struct {
|
|||
}
|
||||
|
||||
// newEmptyAccount returns an empty account.
|
||||
// nolint:unused
|
||||
func newEmptyAccount() *Account {
|
||||
return &Account{
|
||||
Balance: uint256.NewInt(0),
|
||||
|
|
|
|||
|
|
@ -38,7 +38,11 @@ type mutation struct {
|
|||
}
|
||||
|
||||
func (m *mutation) copy() *mutation {
|
||||
return &mutation{typ: m.typ, applied: m.applied, precedingDelete: m.precedingDelete}
|
||||
return &mutation{
|
||||
typ: m.typ,
|
||||
applied: m.applied,
|
||||
precedingDelete: m.precedingDelete,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mutation) isDelete() bool {
|
||||
|
|
|
|||
|
|
@ -171,14 +171,13 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
|
|||
s.originStorage[key] = common.Hash{} // track the empty slot as origin value
|
||||
return common.Hash{}
|
||||
}
|
||||
s.db.StorageLoaded++
|
||||
|
||||
start := time.Now()
|
||||
value, err := s.db.reader.Storage(s.address, key)
|
||||
if err != nil {
|
||||
s.db.setError(err)
|
||||
return common.Hash{}
|
||||
}
|
||||
s.db.StorageLoaded++
|
||||
s.db.StorageReads += time.Since(start)
|
||||
|
||||
s.originStorage[key] = value
|
||||
|
|
@ -267,8 +266,10 @@ func (s *stateObject) updateTrie() error {
|
|||
return nil
|
||||
}
|
||||
var (
|
||||
keys = make([]common.Hash, 0, len(s.uncommittedStorage))
|
||||
vals = make([]common.Hash, 0, len(s.uncommittedStorage))
|
||||
updates int64
|
||||
deletes int64
|
||||
keys = make([]common.Hash, 0, len(s.uncommittedStorage))
|
||||
vals = make([]common.Hash, 0, len(s.uncommittedStorage))
|
||||
)
|
||||
for key, origin := range s.uncommittedStorage {
|
||||
// Skip noop changes, persist actual changes
|
||||
|
|
@ -281,10 +282,17 @@ func (s *stateObject) updateTrie() error {
|
|||
log.Error("Storage slot is not found in pending area", "address", s.address, "slot", key)
|
||||
continue
|
||||
}
|
||||
if value == (common.Hash{}) {
|
||||
deletes += 1
|
||||
} else {
|
||||
updates += 1
|
||||
}
|
||||
keys = append(keys, key)
|
||||
vals = append(vals, value)
|
||||
}
|
||||
s.uncommittedStorage = make(Storage) // empties the commit markers
|
||||
s.db.StorageUpdated.Add(updates)
|
||||
s.db.StorageDeleted.Add(deletes)
|
||||
|
||||
return s.db.hasher.UpdateStorage(s.address, keys, vals)
|
||||
}
|
||||
|
|
@ -337,7 +345,7 @@ func (s *stateObject) commit() (*accountUpdate, error) {
|
|||
// commit the contract code if it's modified
|
||||
if s.dirtyCode {
|
||||
s.dirtyCode = false // reset the dirty flag
|
||||
|
||||
|
||||
op.code = &contractCode{
|
||||
hash: common.BytesToHash(s.CodeHash()),
|
||||
blob: s.code,
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import (
|
|||
"maps"
|
||||
"slices"
|
||||
"sort"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
|
@ -111,30 +110,11 @@ type StateDB struct {
|
|||
// Snapshot and RevertToSnapshot.
|
||||
journal *journal
|
||||
|
||||
// State witness if cross validation is needed
|
||||
witness *stateless.Witness
|
||||
|
||||
// Measurements gathered during execution for debugging purposes
|
||||
AccountReads time.Duration
|
||||
AccountHashes time.Duration
|
||||
AccountUpdates time.Duration
|
||||
AccountCommits time.Duration
|
||||
|
||||
StorageReads time.Duration
|
||||
StorageUpdates time.Duration
|
||||
StorageCommits time.Duration
|
||||
DatabaseCommits time.Duration
|
||||
CodeReads time.Duration
|
||||
|
||||
AccountLoaded int // Number of accounts retrieved from the database during the state transition
|
||||
AccountDeleted int // Number of accounts deleted during the state transition
|
||||
StorageLoaded int // Number of storage slots retrieved from the database during the state transition
|
||||
StorageUpdated atomic.Int64 // Number of storage slots updated during the state transition
|
||||
StorageDeleted atomic.Int64 // Number of storage slots deleted during the state transition
|
||||
|
||||
// CodeLoadBytes is the total number of bytes read from contract code.
|
||||
// This value may be smaller than the actual number of bytes read, since
|
||||
// some APIs (e.g. CodeSize) may load the entire code from either the
|
||||
// cache or the database when the size is not available in the cache.
|
||||
CodeLoaded int // Number of contract code loaded during the state transition
|
||||
CodeLoadBytes int // Total bytes of resolved code
|
||||
Stats
|
||||
}
|
||||
|
||||
// New creates a new state from a given trie.
|
||||
|
|
@ -173,16 +153,11 @@ func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, erro
|
|||
return sdb, nil
|
||||
}
|
||||
|
||||
// StartPrefetcher initializes a new trie prefetcher to pull in nodes from the
|
||||
// state trie concurrently while the state is mutated so that when we reach the
|
||||
// commit phase, most of the needed data is already hot.
|
||||
func (s *StateDB) StartPrefetcher(namespace string, witness *stateless.Witness) {
|
||||
// TraceWitness enables execution witness gathering.
|
||||
func (s *StateDB) TraceWitness(witness *stateless.Witness) {
|
||||
s.witness = witness
|
||||
}
|
||||
|
||||
// StopPrefetcher terminates a running prefetcher and reports any leftover stats
|
||||
// from the gathered metrics.
|
||||
func (s *StateDB) StopPrefetcher() {}
|
||||
|
||||
// setError remembers the first non-nil error it is called with.
|
||||
func (s *StateDB) setError(err error) {
|
||||
if s.dbErr == nil {
|
||||
|
|
@ -206,7 +181,7 @@ func (s *StateDB) AddLog(log *types.Log) {
|
|||
}
|
||||
|
||||
// GetLogs returns the logs matching the specified transaction hash, and annotates
|
||||
// them with the given blockNumber and blockHash.
|
||||
// them with the given block attributes.
|
||||
func (s *StateDB) GetLogs(hash common.Hash, blockNumber uint64, blockHash common.Hash, blockTime uint64) []*types.Log {
|
||||
logs := s.logs[hash]
|
||||
for _, l := range logs {
|
||||
|
|
@ -217,6 +192,7 @@ func (s *StateDB) GetLogs(hash common.Hash, blockNumber uint64, blockHash common
|
|||
return logs
|
||||
}
|
||||
|
||||
// Logs returns the un-annotated logs in order.
|
||||
func (s *StateDB) Logs() []*types.Log {
|
||||
logs := make([]*types.Log, 0, s.logSize)
|
||||
for _, lgs := range s.logs {
|
||||
|
|
@ -296,6 +272,9 @@ func (s *StateDB) TxIndex() int {
|
|||
func (s *StateDB) GetCode(addr common.Address) []byte {
|
||||
stateObject := s.getStateObject(addr)
|
||||
if stateObject != nil {
|
||||
if s.witness != nil {
|
||||
s.witness.AddCode(stateObject.Code())
|
||||
}
|
||||
return stateObject.Code()
|
||||
}
|
||||
return nil
|
||||
|
|
@ -304,6 +283,9 @@ func (s *StateDB) GetCode(addr common.Address) []byte {
|
|||
func (s *StateDB) GetCodeSize(addr common.Address) int {
|
||||
stateObject := s.getStateObject(addr)
|
||||
if stateObject != nil {
|
||||
if s.witness != nil {
|
||||
s.witness.AddCode(stateObject.Code())
|
||||
}
|
||||
return stateObject.CodeSize()
|
||||
}
|
||||
return 0
|
||||
|
|
@ -502,14 +484,13 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject {
|
|||
if _, ok := s.stateObjectsDestruct[addr]; ok {
|
||||
return nil
|
||||
}
|
||||
s.AccountLoaded++
|
||||
|
||||
start := time.Now()
|
||||
acct, err := s.reader.Account(addr)
|
||||
if err != nil {
|
||||
s.setError(fmt.Errorf("getStateObject (%x) error: %w", addr.Bytes(), err))
|
||||
return nil
|
||||
}
|
||||
s.AccountLoaded++
|
||||
s.AccountReads += time.Since(start)
|
||||
|
||||
// Short circuit if the account is not found
|
||||
|
|
@ -611,6 +592,9 @@ func (s *StateDB) Copy() *StateDB {
|
|||
transientStorage: s.transientStorage.Copy(),
|
||||
journal: s.journal.copy(),
|
||||
}
|
||||
if s.witness != nil {
|
||||
state.witness = s.witness.Copy()
|
||||
}
|
||||
if s.accessEvents != nil {
|
||||
state.accessEvents = s.accessEvents.Copy()
|
||||
}
|
||||
|
|
@ -756,6 +740,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
|||
continue
|
||||
}
|
||||
op.precedingDelete = false
|
||||
|
||||
delAddrs = append(delAddrs, addr)
|
||||
delAccts = append(delAccts, AccountMut{Account: nil})
|
||||
}
|
||||
|
|
@ -764,6 +749,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
|||
s.setError(err)
|
||||
return common.Hash{}
|
||||
}
|
||||
s.AccountDeleted += len(delAddrs)
|
||||
}
|
||||
s.AccountUpdates += time.Since(start)
|
||||
|
||||
|
|
@ -797,14 +783,20 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
|||
|
||||
if op.isDelete() {
|
||||
accounts = append(accounts, AccountMut{Account: nil})
|
||||
s.AccountDeleted += 1
|
||||
continue
|
||||
}
|
||||
obj := s.stateObjects[addr]
|
||||
mut := AccountMut{Account: &obj.data}
|
||||
if obj.dirtyCode {
|
||||
mut.Code = &CodeMut{Code: obj.code}
|
||||
|
||||
// Count code writes post-Finalise so reverted CREATEs are excluded.
|
||||
s.CodeUpdated += 1
|
||||
s.CodeUpdateBytes += len(obj.code)
|
||||
}
|
||||
accounts = append(accounts, mut)
|
||||
s.AccountUpdated += 1
|
||||
}
|
||||
if err := s.hasher.UpdateAccount(addresses, accounts); err != nil {
|
||||
s.setError(err)
|
||||
|
|
@ -932,8 +924,7 @@ func (s *StateDB) handleDestruction(noStorageWiping bool) (map[common.Hash]*acco
|
|||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to delete storage, err: %w", err)
|
||||
}
|
||||
op.storages = storages
|
||||
op.storagesOrigin = storagesOrigin
|
||||
op.storages, op.storagesOrigin = storages, storagesOrigin
|
||||
|
||||
// Aggregate the associated trie node changes.
|
||||
if err := nodes.Merge(set); err != nil {
|
||||
|
|
@ -957,15 +948,6 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
|
|||
if s.dbErr != nil {
|
||||
return nil, fmt.Errorf("commit aborted due to database error: %v", s.dbErr)
|
||||
}
|
||||
// Commit objects to the trie, measuring the elapsed time
|
||||
var (
|
||||
accountTrieNodesUpdated int
|
||||
accountTrieNodesDeleted int
|
||||
storageTrieNodesUpdated int
|
||||
storageTrieNodesDeleted int
|
||||
|
||||
updates = make(map[common.Hash]*accountUpdate, len(s.mutations)) // aggregated account updates
|
||||
)
|
||||
// Given that some accounts could be destroyed and then recreated within
|
||||
// the same block, account deletions must be processed first. This ensures
|
||||
// that the storage trie nodes deleted during destruction and recreated
|
||||
|
|
@ -974,6 +956,8 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Aggregated account updates
|
||||
updates := make(map[common.Hash]*accountUpdate, len(s.mutations))
|
||||
for addr, op := range s.mutations {
|
||||
if op.isDelete() {
|
||||
continue
|
||||
|
|
@ -992,29 +976,16 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
|
|||
// Handle all state updates afterwards, concurrently to one another to shave
|
||||
// off some milliseconds from the commit operation. Also accumulate the code
|
||||
// writes to run in parallel with the computations.
|
||||
start := time.Now()
|
||||
root, set, secondaryHashes, err := s.hasher.Commit()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.HasherCommits = time.Since(start)
|
||||
|
||||
if err := nodes.MergeSet(set); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
accountReadMeters.Mark(int64(s.AccountLoaded))
|
||||
storageReadMeters.Mark(int64(s.StorageLoaded))
|
||||
storageUpdatedMeter.Mark(s.StorageUpdated.Load())
|
||||
accountDeletedMeter.Mark(int64(s.AccountDeleted))
|
||||
storageDeletedMeter.Mark(s.StorageDeleted.Load())
|
||||
accountTrieUpdatedMeter.Mark(int64(accountTrieNodesUpdated))
|
||||
accountTrieDeletedMeter.Mark(int64(accountTrieNodesDeleted))
|
||||
storageTriesUpdatedMeter.Mark(int64(storageTrieNodesUpdated))
|
||||
storageTriesDeletedMeter.Mark(int64(storageTrieNodesDeleted))
|
||||
|
||||
// Clear the metric markers
|
||||
s.AccountLoaded, s.AccountDeleted = 0, 0
|
||||
s.StorageLoaded = 0
|
||||
s.StorageUpdated.Store(0)
|
||||
s.StorageDeleted.Store(0)
|
||||
|
||||
// Clear all internal flags and update state root at the end.
|
||||
s.mutations = make(map[common.Address]*mutation)
|
||||
s.stateObjectsDestruct = make(map[common.Address]*stateObject)
|
||||
|
|
@ -1022,6 +993,12 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
|
|||
origin := s.originalRoot
|
||||
s.originalRoot = root
|
||||
|
||||
if s.witness != nil {
|
||||
builder, ok := s.hasher.(WitnessCollector)
|
||||
if ok {
|
||||
builder.CollectWitness(s.witness)
|
||||
}
|
||||
}
|
||||
return newStateUpdate(noStorageWiping, origin, root, blockNumber, deletes, updates, nodes, secondaryHashes), nil
|
||||
}
|
||||
|
||||
|
|
@ -1160,7 +1137,7 @@ func (s *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addre
|
|||
|
||||
// Witness retrieves the current state witness being collected.
|
||||
func (s *StateDB) Witness() *stateless.Witness {
|
||||
return nil
|
||||
return s.witness
|
||||
}
|
||||
|
||||
func (s *StateDB) AccessEvents() *AccessEvents {
|
||||
|
|
|
|||
61
core/state/statedb_stats.go
Normal file
61
core/state/statedb_stats.go
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright 2026 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package state
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Stats contains all measurements gathered during state execution for
|
||||
// debugging and metrics purposes.
|
||||
type Stats struct {
|
||||
AccountReads time.Duration // Account read time
|
||||
StorageReads time.Duration // Storage read time
|
||||
CodeReads time.Duration // Code read time
|
||||
AccountHashes time.Duration // Account trie hash time
|
||||
AccountUpdates time.Duration // Account trie update time
|
||||
StorageUpdates time.Duration // Storage trie update and hash time
|
||||
HasherCommits time.Duration // Trie commit time
|
||||
DatabaseCommits time.Duration // Database commit time
|
||||
|
||||
AccountLoaded int // Number of accounts retrieved from the database during the state transition
|
||||
AccountUpdated int // Number of accounts updated during the state transition
|
||||
AccountDeleted int // Number of accounts deleted during the state transition
|
||||
StorageLoaded int // Number of storage slots retrieved from the database during the state transition
|
||||
StorageUpdated atomic.Int64 // Number of storage slots updated during the state transition
|
||||
StorageDeleted atomic.Int64 // Number of storage slots deleted during the state transition
|
||||
|
||||
// CodeLoadBytes is the total number of bytes read from contract code.
|
||||
// This value may be smaller than the actual number of bytes read, since
|
||||
// some APIs (e.g. CodeSize) may load the entire code from either the
|
||||
// cache or the database when the size is not available in the cache.
|
||||
CodeLoaded int // Number of contract code loaded during the state transition
|
||||
CodeLoadBytes int // Total bytes of resolved code
|
||||
CodeUpdated int // Number of contracts with code changes that persisted
|
||||
CodeUpdateBytes int // Total bytes of persisted code written
|
||||
}
|
||||
|
||||
// StateReadTime returns the total time spent on the state read.
|
||||
func (s *Stats) StateReadTime() time.Duration {
|
||||
return s.AccountReads + s.StorageReads + s.CodeReads
|
||||
}
|
||||
|
||||
// StateHashTime returns the total time spent on the state hash.
|
||||
func (s *Stats) StateHashTime() time.Duration {
|
||||
return s.AccountHashes + s.AccountUpdates + s.StorageUpdates
|
||||
}
|
||||
|
|
@ -80,11 +80,6 @@ func (env *environment) txFitsSize(tx *types.Transaction) bool {
|
|||
return env.size+tx.Size() < params.MaxBlockSize-maxBlockSizeBufferZone
|
||||
}
|
||||
|
||||
// discard terminates the background threads before discarding it.
|
||||
func (env *environment) discard() {
|
||||
env.state.StopPrefetcher()
|
||||
}
|
||||
|
||||
const (
|
||||
commitInterruptNone int32 = iota
|
||||
commitInterruptNewHead
|
||||
|
|
@ -147,8 +142,6 @@ func (miner *Miner) generateWork(ctx context.Context, genParam *generateParams,
|
|||
if err != nil {
|
||||
return &newPayloadResult{err: err}
|
||||
}
|
||||
defer work.discard()
|
||||
|
||||
// Check withdrawals fit max block size.
|
||||
// Due to the cap on withdrawal count, this can actually never happen, but we still need to
|
||||
// check to ensure the CL notices there's a problem if the withdrawal cap is ever lifted.
|
||||
|
|
@ -334,8 +327,8 @@ func (miner *Miner) makeEnv(parent *types.Header, header *types.Header, coinbase
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
state.TraceWitness(bundle)
|
||||
}
|
||||
state.StartPrefetcher("miner", bundle)
|
||||
// Note the passed coinbase may be different with header.Coinbase.
|
||||
return &environment{
|
||||
signer: types.MakeSigner(miner.chainConfig, header.Number, header.Time),
|
||||
|
|
|
|||
Loading…
Reference in a new issue