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)
|
accountReadTimer = metrics.NewRegisteredResettingTimer("chain/account/reads", nil)
|
||||||
accountHashTimer = metrics.NewRegisteredResettingTimer("chain/account/hashes", nil)
|
accountHashTimer = metrics.NewRegisteredResettingTimer("chain/account/hashes", nil)
|
||||||
accountUpdateTimer = metrics.NewRegisteredResettingTimer("chain/account/updates", 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)
|
storageReadTimer = metrics.NewRegisteredResettingTimer("chain/storage/reads", nil)
|
||||||
storageUpdateTimer = metrics.NewRegisteredResettingTimer("chain/storage/updates", nil)
|
storageUpdateTimer = metrics.NewRegisteredResettingTimer("chain/storage/updates", nil)
|
||||||
storageCommitTimer = metrics.NewRegisteredResettingTimer("chain/storage/commits", nil)
|
|
||||||
codeReadTimer = metrics.NewRegisteredResettingTimer("chain/code/reads", nil)
|
codeReadTimer = metrics.NewRegisteredResettingTimer("chain/code/reads", nil)
|
||||||
codeReadBytesTimer = metrics.NewRegisteredResettingTimer("chain/code/readbytes", nil)
|
codeReadBytesTimer = metrics.NewRegisteredResettingTimer("chain/code/readbytes", nil)
|
||||||
|
|
||||||
|
|
@ -2112,12 +2111,16 @@ type ExecuteConfig struct {
|
||||||
// it writes the block and associated state to database.
|
// 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) {
|
func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash, block *types.Block, config ExecuteConfig) (result *blockProcessingResult, blockEndErr error) {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
startTime = time.Now()
|
startTime = time.Now()
|
||||||
statedb *state.StateDB
|
statedb *state.StateDB
|
||||||
interrupt atomic.Bool
|
interrupt atomic.Bool
|
||||||
sdb = state.NewDatabase(bc.triedb, bc.codedb).WithSnapshot(bc.snaps)
|
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
|
defer interrupt.Store(true) // terminate the prefetch at the end
|
||||||
|
|
||||||
if bc.cfg.NoPrefetch {
|
if bc.cfg.NoPrefetch {
|
||||||
|
|
@ -2126,6 +2129,10 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
// If prefetching is enabled, run that against the current state to pre-cache
|
||||||
// transactions and probabilistically some of the account/storage trie nodes.
|
// 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
|
// while processing transactions. Before Byzantium the prefetcher is mostly
|
||||||
// useless due to the intermediate root hashing after each transaction.
|
// useless due to the intermediate root hashing after each transaction.
|
||||||
var witness *stateless.Witness
|
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
|
// Generate witnesses either if we're self-testing, or if it's the
|
||||||
// only block being inserted. A bit crude, but witnesses are huge,
|
// only block being inserted. A bit crude, but witnesses are huge,
|
||||||
// so we refuse to make an entire chain of them.
|
// so we refuse to make an entire chain of them.
|
||||||
if config.StatelessSelfValidation || config.MakeWitness {
|
witness, err = stateless.NewWitness(block.Header(), bc, config.EnableWitnessStats)
|
||||||
witness, err = stateless.NewWitness(block.Header(), bc, config.EnableWitnessStats)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
statedb.StartPrefetcher("chain", witness)
|
statedb.TraceWitness(witness)
|
||||||
defer statedb.StopPrefetcher()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instrument the blockchain tracing
|
// Instrument the blockchain tracing
|
||||||
if config.EnableTracer {
|
if config.EnableTracer {
|
||||||
if bc.logger != nil && bc.logger.OnBlockStart != nil {
|
if bc.logger != nil && bc.logger.OnBlockStart != nil {
|
||||||
|
|
@ -2252,34 +2255,9 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
xvtime = time.Since(xvstart)
|
|
||||||
proctime = time.Since(startTime) // processing + validation + cross validation
|
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.
|
// Write the block to the chain and get the status.
|
||||||
var status WriteStatus
|
var status WriteStatus
|
||||||
if config.WriteState {
|
if config.WriteState {
|
||||||
|
|
@ -2294,10 +2272,9 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Update the metrics touched during block commit
|
// Update the metrics touched during block commit
|
||||||
stats.AccountCommits = statedb.AccountCommits // Account commits are complete, we can mark them
|
stats.HasherCommit = statedb.HasherCommits // Storage commits are complete, we can mark them
|
||||||
stats.StorageCommits = statedb.StorageCommits // Storage commits are complete, we can mark them
|
|
||||||
stats.DatabaseCommit = statedb.DatabaseCommits // Database 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
|
// Report the collected witness statistics
|
||||||
if witness != nil {
|
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))
|
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.
|
// HistoricState returns a historic state specified by the given root.
|
||||||
// Live states are not available and won't be served, please use `State`
|
// Live states are not available and won't be served, please use `State`
|
||||||
// or `StateAt` instead.
|
// or `StateAt` instead.
|
||||||
|
|
|
||||||
|
|
@ -29,14 +29,30 @@ import (
|
||||||
// ExecuteStats includes all the statistics of a block execution in details.
|
// ExecuteStats includes all the statistics of a block execution in details.
|
||||||
type ExecuteStats struct {
|
type ExecuteStats struct {
|
||||||
// State read times
|
// State read times
|
||||||
AccountReads time.Duration // Time spent on the account reads
|
AccountReads time.Duration // Time spent on the account reads
|
||||||
StorageReads time.Duration // Time spent on the storage 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
|
AccountHashes time.Duration // Time spent on the account trie hash
|
||||||
AccountUpdates time.Duration // Time spent on the account trie update
|
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
|
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
|
AccountLoaded int // Number of accounts loaded
|
||||||
AccountUpdated int // Number of accounts updated
|
AccountUpdated int // Number of accounts updated
|
||||||
|
|
@ -49,19 +65,40 @@ type ExecuteStats struct {
|
||||||
CodeUpdated int // Number of contract code written (CREATE/CREATE2 + EIP-7702)
|
CodeUpdated int // Number of contract code written (CREATE/CREATE2 + EIP-7702)
|
||||||
CodeUpdateBytes int // Total bytes of code written
|
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
|
// Cache hit rates
|
||||||
StateReadCacheStats state.ReaderStats
|
StateReadCacheStats state.ReaderStats
|
||||||
StatePrefetchCacheStats 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.
|
// reportMetrics uploads execution statistics to the metrics system.
|
||||||
func (s *ExecuteStats) reportMetrics() {
|
func (s *ExecuteStats) reportMetrics() {
|
||||||
if s.AccountLoaded != 0 {
|
if s.AccountLoaded != 0 {
|
||||||
|
|
@ -80,8 +117,7 @@ func (s *ExecuteStats) reportMetrics() {
|
||||||
accountUpdateTimer.Update(s.AccountUpdates) // Account updates are complete(in validation)
|
accountUpdateTimer.Update(s.AccountUpdates) // Account updates are complete(in validation)
|
||||||
storageUpdateTimer.Update(s.StorageUpdates) // Storage 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)
|
accountHashTimer.Update(s.AccountHashes) // Account hashes are complete(in validation)
|
||||||
accountCommitTimer.Update(s.AccountCommits) // Account commits are complete, we can mark them
|
hasherCommitTimer.Update(s.HasherCommit) // Trie commits are complete, we can mark them
|
||||||
storageCommitTimer.Update(s.StorageCommits) // Storage commits are complete, we can mark them
|
|
||||||
|
|
||||||
blockExecutionTimer.Update(s.Execution) // The time spent on EVM processing
|
blockExecutionTimer.Update(s.Execution) // The time spent on EVM processing
|
||||||
blockValidationTimer.Update(s.Validation) // The time spent on block validation
|
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),
|
ExecutionMs: durationToMs(s.Execution),
|
||||||
StateReadMs: durationToMs(s.AccountReads + s.StorageReads + s.CodeReads),
|
StateReadMs: durationToMs(s.AccountReads + s.StorageReads + s.CodeReads),
|
||||||
StateHashMs: durationToMs(s.AccountHashes + s.AccountUpdates + s.StorageUpdates),
|
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),
|
TotalMs: durationToMs(s.TotalTime),
|
||||||
},
|
},
|
||||||
Throughput: slowBlockThru{
|
Throughput: slowBlockThru{
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,9 @@ type CachingDB struct {
|
||||||
triedb *triedb.Database
|
triedb *triedb.Database
|
||||||
codedb *CodeDB
|
codedb *CodeDB
|
||||||
snap *snapshot.Tree
|
snap *snapshot.Tree
|
||||||
|
|
||||||
|
prefetch bool
|
||||||
|
prefetchRead bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDatabase creates a state database with the provided data sources.
|
// NewDatabase creates a state database with the provided data sources.
|
||||||
|
|
@ -177,6 +180,13 @@ func (db *CachingDB) WithSnapshot(snapshot *snapshot.Tree) *CachingDB {
|
||||||
return db
|
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.
|
// StateReader returns a state reader associated with the specified state root.
|
||||||
func (db *CachingDB) StateReader(stateRoot common.Hash) (StateReader, error) {
|
func (db *CachingDB) StateReader(stateRoot common.Hash) (StateReader, error) {
|
||||||
var readers []StateReader
|
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
|
// Hasher implements Database, returning a hasher associated with the specified
|
||||||
// state root.
|
// state root.
|
||||||
func (db *CachingDB) Hasher(stateRoot common.Hash) (Hasher, error) {
|
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
|
// 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
|
// WitnessCollector is an optional extension implemented by hashers that can
|
||||||
// construct a state witness for the most recent committed state transition.
|
// construct a state witness for the most recent committed state transition.
|
||||||
type WitnessCollector interface {
|
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.
|
// committed state transition.
|
||||||
Witness() (*stateless.Witness, error)
|
CollectWitness(*stateless.Witness)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prover is an optional extension implemented by hashers that can construct
|
// Prover is an optional extension implemented by hashers that can construct
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,14 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"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/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||||
"github.com/ethereum/go-ethereum/triedb"
|
"github.com/ethereum/go-ethereum/triedb"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
// wrapTrie pairs a StateTrie with an optional background prefetcher that
|
// wrapTrie pairs a StateTrie with an optional background prefetcher that
|
||||||
|
|
@ -36,6 +38,7 @@ type wrapTrie struct {
|
||||||
prefetcher *prefetcher
|
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) {
|
func newWrapTrie(id *trie.ID, db *triedb.Database, prefetch bool, prefetchRead bool) (*wrapTrie, error) {
|
||||||
t, err := trie.NewStateTrie(id, db)
|
t, err := trie.NewStateTrie(id, db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -59,7 +62,7 @@ func (tr *wrapTrie) term() {
|
||||||
tr.prefetcher = nil
|
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
|
// access auto-terminates the prefetcher first. This makes data-race freedom
|
||||||
// structural: callers never need to remember to call term() manually.
|
// 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)
|
return tr.StateTrie.Prove(key, proofDb)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tr *wrapTrie) copy() *wrapTrie {
|
func (tr *wrapTrie) Witness() map[string][]byte {
|
||||||
tr.term()
|
tr.term()
|
||||||
return &wrapTrie{StateTrie: tr.StateTrie.Copy()}
|
return tr.StateTrie.Witness()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tr *wrapTrie) prefetchAccounts(addresses []common.Address, read bool) {
|
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)
|
tr.prefetcher.scheduleSlots(addr, keys, read)
|
||||||
}
|
}
|
||||||
|
|
||||||
// rootReader wraps the account trie for loading the storage root. It is
|
// copy returns a deep-copied state trie. Notably the prefetcher is deliberately
|
||||||
// essential to use an independent trie to prevent potential data races
|
// not copied, as it only belongs to the original one.
|
||||||
// with the optional prefetcher.
|
func (tr *wrapTrie) copy() *wrapTrie {
|
||||||
type rootReader struct {
|
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
|
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)
|
t, err := trie.NewStateTrie(trie.StateTrieID(root), db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
acct, err := r.tr.GetAccount(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Hash{}, err
|
return common.Hash{}, err
|
||||||
|
|
@ -143,8 +155,8 @@ func (r *rootReader) readStorageRoot(address common.Address) (common.Hash, error
|
||||||
return acct.Root, nil
|
return acct.Root, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rootReader) copy() *rootReader {
|
func (r *storageRootReader) copy() *storageRootReader {
|
||||||
return &rootReader{tr: r.tr.Copy()}
|
return &storageRootReader{tr: r.tr.Copy()}
|
||||||
}
|
}
|
||||||
|
|
||||||
// merkleHasher is a Hasher implementation backed by the traditional two-layer
|
// merkleHasher is a Hasher implementation backed by the traditional two-layer
|
||||||
|
|
@ -152,7 +164,7 @@ func (r *rootReader) copy() *rootReader {
|
||||||
type merkleHasher struct {
|
type merkleHasher struct {
|
||||||
db *triedb.Database
|
db *triedb.Database
|
||||||
root common.Hash
|
root common.Hash
|
||||||
reader *rootReader
|
reader *storageRootReader
|
||||||
prefetch bool
|
prefetch bool
|
||||||
prefetchRead bool
|
prefetchRead bool
|
||||||
|
|
||||||
|
|
@ -160,7 +172,7 @@ type merkleHasher struct {
|
||||||
storageTries map[common.Address]*wrapTrie
|
storageTries map[common.Address]*wrapTrie
|
||||||
|
|
||||||
// deletedTries preserves storage tries of accounts that were deleted
|
// 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).
|
// address is recorded (the pre-block incarnation).
|
||||||
deletedTries map[common.Address]*wrapTrie
|
deletedTries map[common.Address]*wrapTrie
|
||||||
|
|
||||||
|
|
@ -169,7 +181,8 @@ type merkleHasher struct {
|
||||||
// UpdateStorage or set to EmptyRootHash on deletion.
|
// UpdateStorage or set to EmptyRootHash on deletion.
|
||||||
storageRoots map[common.Address]Hashes
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
r, err := newRootReader(root, db)
|
r, err := newStorageRootReader(root, db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -201,11 +214,14 @@ func (h *merkleHasher) storageRoot(addr common.Address) (common.Hash, error) {
|
||||||
if hashes, ok := h.storageRoots[addr]; ok {
|
if hashes, ok := h.storageRoots[addr]; ok {
|
||||||
return hashes.Hash, nil
|
return hashes.Hash, nil
|
||||||
}
|
}
|
||||||
root, err := h.reader.readStorageRoot(addr)
|
root, err := h.reader.read(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Hash{}, err
|
return common.Hash{}, err
|
||||||
}
|
}
|
||||||
h.storageRoots[addr] = Hashes{Prev: root, Hash: root}
|
h.storageRoots[addr] = Hashes{
|
||||||
|
Prev: root,
|
||||||
|
Hash: root,
|
||||||
|
}
|
||||||
return root, nil
|
return root, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -223,6 +239,7 @@ func (h *merkleHasher) openStorageTrie(address common.Address, prefetch bool) (*
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
id := trie.StorageTrieID(h.root, crypto.Keccak256Hash(address.Bytes()), root)
|
id := trie.StorageTrieID(h.root, crypto.Keccak256Hash(address.Bytes()), root)
|
||||||
|
|
||||||
tr, err := newWrapTrie(id, h.db, h.prefetch && prefetch, h.prefetchRead)
|
tr, err := newWrapTrie(id, h.db, h.prefetch && prefetch, h.prefetchRead)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -231,8 +248,9 @@ func (h *merkleHasher) openStorageTrie(address common.Address, prefetch bool) (*
|
||||||
return tr, nil
|
return tr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deleteAccount removes the account specified by the address from the state.
|
||||||
func (h *merkleHasher) deleteAccount(addr common.Address) error {
|
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)
|
_, err := h.storageRoot(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -251,6 +269,7 @@ func (h *merkleHasher) deleteAccount(addr common.Address) error {
|
||||||
return h.acctTrie.DeleteAccount(addr)
|
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 {
|
func (h *merkleHasher) updateAccount(addr common.Address, account AccountMut) error {
|
||||||
root, err := h.storageRoot(addr)
|
root, err := h.storageRoot(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -265,10 +284,12 @@ func (h *merkleHasher) updateAccount(addr common.Address, account AccountMut) er
|
||||||
return h.acctTrie.UpdateAccount(addr, data, 0)
|
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 {
|
func (h *merkleHasher) UpdateAccount(addresses []common.Address, accounts []AccountMut) error {
|
||||||
|
var err error
|
||||||
for i, addr := range addresses {
|
for i, addr := range addresses {
|
||||||
var err error
|
|
||||||
if accounts[i].Account == nil {
|
if accounts[i].Account == nil {
|
||||||
err = h.deleteAccount(addr)
|
err = h.deleteAccount(addr)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -281,7 +302,9 @@ func (h *merkleHasher) UpdateAccount(addresses []common.Address, accounts []Acco
|
||||||
return nil
|
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 {
|
func (h *merkleHasher) UpdateStorage(address common.Address, keys []common.Hash, values []common.Hash) error {
|
||||||
tr, err := h.openStorageTrie(address, false)
|
tr, err := h.openStorageTrie(address, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -289,18 +312,19 @@ func (h *merkleHasher) UpdateStorage(address common.Address, keys []common.Hash,
|
||||||
}
|
}
|
||||||
for i, key := range keys {
|
for i, key := range keys {
|
||||||
if values[i] == (common.Hash{}) {
|
if values[i] == (common.Hash{}) {
|
||||||
if err := tr.DeleteStorage(address, key[:]); err != nil {
|
err = tr.DeleteStorage(address, key[:])
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if err := tr.UpdateStorage(address, key[:], common.TrimLeftZeroes(values[i][:])); err != nil {
|
err = tr.UpdateStorage(address, key[:], common.TrimLeftZeroes(values[i][:]))
|
||||||
return err
|
}
|
||||||
}
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Hash outside the lock to allow full parallelism across accounts.
|
// Hash outside the lock to allow full parallelism across accounts.
|
||||||
hash := tr.Hash()
|
hash := tr.Hash()
|
||||||
|
|
||||||
|
// Write back the storage root back for reflecting the most recent
|
||||||
|
// changes.
|
||||||
h.storageLock.Lock()
|
h.storageLock.Lock()
|
||||||
h.storageRoots[address] = Hashes{
|
h.storageRoots[address] = Hashes{
|
||||||
Prev: h.storageRoots[address].Prev,
|
Prev: h.storageRoots[address].Prev,
|
||||||
|
|
@ -310,50 +334,64 @@ func (h *merkleHasher) UpdateStorage(address common.Address, keys []common.Hash,
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hash implements Hasher, computing the state root hash without committing.
|
||||||
func (h *merkleHasher) Hash() common.Hash {
|
func (h *merkleHasher) Hash() common.Hash {
|
||||||
return h.acctTrie.Hash()
|
return h.acctTrie.Hash()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close terminates all prefetcher goroutines. Safe to call multiple times.
|
// Commit implements Hasher, finalizing all pending changes and returning
|
||||||
func (h *merkleHasher) Close() {
|
// the resulting state root hash, along with the set of dirty trie nodes
|
||||||
h.acctTrie.term()
|
// generated by the updates.
|
||||||
for _, tr := range h.storageTries {
|
|
||||||
tr.term()
|
|
||||||
}
|
|
||||||
for _, tr := range h.deletedTries {
|
|
||||||
tr.term()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *merkleHasher) Commit() (common.Hash, *trienode.MergedNodeSet, map[common.Address]Hashes, error) {
|
func (h *merkleHasher) Commit() (common.Hash, *trienode.MergedNodeSet, map[common.Address]Hashes, error) {
|
||||||
// Explicitly terminate all resolved tries. Some of them may not be
|
// Explicitly terminate all resolved tries. Some of them may not be
|
||||||
// terminated due to read-only prefetching. This is essential to
|
// terminated due to read-only prefetching. This is essential to
|
||||||
// prevent goroutine leaks.
|
// prevent goroutine leaks.
|
||||||
h.Close()
|
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 {
|
for _, tr := range h.storageTries {
|
||||||
if _, set := tr.Commit(false); set != nil {
|
eg.Go(func() error {
|
||||||
if err := nodes.Merge(set); err != nil {
|
_, set := tr.Commit(false)
|
||||||
return common.Hash{}, nil, nil, err
|
if set == nil {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
return merge(set)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
root, set := h.acctTrie.Commit(true)
|
if err := eg.Wait(); err != nil {
|
||||||
if set != nil {
|
return common.Hash{}, nil, nil, err
|
||||||
if err := nodes.Merge(set); err != nil {
|
|
||||||
return common.Hash{}, nil, nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return root, nodes, h.storageRoots, nil
|
return root, nodes, h.storageRoots, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Copy implements Hasher, returning a deep-copied hasher instance.
|
||||||
func (h *merkleHasher) Copy() Hasher {
|
func (h *merkleHasher) Copy() Hasher {
|
||||||
cpy := &merkleHasher{
|
cpy := &merkleHasher{
|
||||||
db: h.db,
|
db: h.db,
|
||||||
root: h.root,
|
root: h.root,
|
||||||
reader: h.reader.copy(),
|
reader: h.reader.copy(),
|
||||||
prefetch: false,
|
prefetch: false,
|
||||||
|
prefetchRead: false,
|
||||||
acctTrie: h.acctTrie.copy(),
|
acctTrie: h.acctTrie.copy(),
|
||||||
storageTries: make(map[common.Address]*wrapTrie, len(h.storageTries)),
|
storageTries: make(map[common.Address]*wrapTrie, len(h.storageTries)),
|
||||||
deletedTries: make(map[common.Address]*wrapTrie, len(h.deletedTries)),
|
deletedTries: make(map[common.Address]*wrapTrie, len(h.deletedTries)),
|
||||||
|
|
@ -368,12 +406,24 @@ func (h *merkleHasher) Copy() Hasher {
|
||||||
return cpy
|
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 {
|
func (h *merkleHasher) ProveAccount(addr common.Address, proofDb ethdb.KeyValueWriter) error {
|
||||||
return h.acctTrie.Prove(crypto.Keccak256(addr.Bytes()), proofDb)
|
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 {
|
func (h *merkleHasher) ProveStorage(addr common.Address, key common.Hash, proofDb ethdb.KeyValueWriter) error {
|
||||||
tr, err := h.openStorageTrie(addr, false)
|
tr, err := h.openStorageTrie(addr, false)
|
||||||
if err != nil {
|
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)
|
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.
|
// PrefetchAccount implements Prefetcher, preloading the nodes of specific accounts.
|
||||||
func (h *merkleHasher) PrefetchAccount(addresses []common.Address, read bool) {
|
func (h *merkleHasher) PrefetchAccount(addresses []common.Address, read bool) {
|
||||||
if !h.prefetch {
|
if !h.prefetch {
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"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/core/types"
|
||||||
"github.com/ethereum/go-ethereum/triedb"
|
"github.com/ethereum/go-ethereum/triedb"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
|
|
@ -539,3 +540,90 @@ func TestMerkleHasherCopy(t *testing.T) {
|
||||||
t.Fatal("original root changed after mutating copy")
|
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"
|
import "github.com/ethereum/go-ethereum/metrics"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
accountReadMeters = metrics.NewRegisteredMeter("state/read/account", nil)
|
accountReadMeters = metrics.NewRegisteredMeter("state/read/account", nil)
|
||||||
storageReadMeters = metrics.NewRegisteredMeter("state/read/storage", nil)
|
storageReadMeters = metrics.NewRegisteredMeter("state/read/storage", nil)
|
||||||
accountUpdatedMeter = metrics.NewRegisteredMeter("state/update/account", nil)
|
accountUpdatedMeter = metrics.NewRegisteredMeter("state/update/account", nil)
|
||||||
storageUpdatedMeter = metrics.NewRegisteredMeter("state/update/storage", nil)
|
storageUpdatedMeter = metrics.NewRegisteredMeter("state/update/storage", nil)
|
||||||
accountDeletedMeter = metrics.NewRegisteredMeter("state/delete/account", nil)
|
accountDeletedMeter = metrics.NewRegisteredMeter("state/delete/account", nil)
|
||||||
storageDeletedMeter = metrics.NewRegisteredMeter("state/delete/storage", nil)
|
storageDeletedMeter = metrics.NewRegisteredMeter("state/delete/storage", nil)
|
||||||
accountTrieUpdatedMeter = metrics.NewRegisteredMeter("state/update/accountnodes", nil)
|
//accountTrieUpdatedMeter = metrics.NewRegisteredMeter("state/update/accountnodes", nil)
|
||||||
storageTriesUpdatedMeter = metrics.NewRegisteredMeter("state/update/storagenodes", nil)
|
//storageTriesUpdatedMeter = metrics.NewRegisteredMeter("state/update/storagenodes", nil)
|
||||||
accountTrieDeletedMeter = metrics.NewRegisteredMeter("state/delete/accountnodes", nil)
|
//accountTrieDeletedMeter = metrics.NewRegisteredMeter("state/delete/accountnodes", nil)
|
||||||
storageTriesDeletedMeter = metrics.NewRegisteredMeter("state/delete/storagenodes", nil)
|
//storageTriesDeletedMeter = metrics.NewRegisteredMeter("state/delete/storagenodes", nil)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,6 @@ type Account struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// newEmptyAccount returns an empty account.
|
// newEmptyAccount returns an empty account.
|
||||||
// nolint:unused
|
|
||||||
func newEmptyAccount() *Account {
|
func newEmptyAccount() *Account {
|
||||||
return &Account{
|
return &Account{
|
||||||
Balance: uint256.NewInt(0),
|
Balance: uint256.NewInt(0),
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,11 @@ type mutation struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mutation) copy() *mutation {
|
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 {
|
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
|
s.originStorage[key] = common.Hash{} // track the empty slot as origin value
|
||||||
return common.Hash{}
|
return common.Hash{}
|
||||||
}
|
}
|
||||||
s.db.StorageLoaded++
|
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
value, err := s.db.reader.Storage(s.address, key)
|
value, err := s.db.reader.Storage(s.address, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.db.setError(err)
|
s.db.setError(err)
|
||||||
return common.Hash{}
|
return common.Hash{}
|
||||||
}
|
}
|
||||||
|
s.db.StorageLoaded++
|
||||||
s.db.StorageReads += time.Since(start)
|
s.db.StorageReads += time.Since(start)
|
||||||
|
|
||||||
s.originStorage[key] = value
|
s.originStorage[key] = value
|
||||||
|
|
@ -267,8 +266,10 @@ func (s *stateObject) updateTrie() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
keys = make([]common.Hash, 0, len(s.uncommittedStorage))
|
updates int64
|
||||||
vals = make([]common.Hash, 0, len(s.uncommittedStorage))
|
deletes int64
|
||||||
|
keys = make([]common.Hash, 0, len(s.uncommittedStorage))
|
||||||
|
vals = make([]common.Hash, 0, len(s.uncommittedStorage))
|
||||||
)
|
)
|
||||||
for key, origin := range s.uncommittedStorage {
|
for key, origin := range s.uncommittedStorage {
|
||||||
// Skip noop changes, persist actual changes
|
// 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)
|
log.Error("Storage slot is not found in pending area", "address", s.address, "slot", key)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if value == (common.Hash{}) {
|
||||||
|
deletes += 1
|
||||||
|
} else {
|
||||||
|
updates += 1
|
||||||
|
}
|
||||||
keys = append(keys, key)
|
keys = append(keys, key)
|
||||||
vals = append(vals, value)
|
vals = append(vals, value)
|
||||||
}
|
}
|
||||||
s.uncommittedStorage = make(Storage) // empties the commit markers
|
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)
|
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
|
// commit the contract code if it's modified
|
||||||
if s.dirtyCode {
|
if s.dirtyCode {
|
||||||
s.dirtyCode = false // reset the dirty flag
|
s.dirtyCode = false // reset the dirty flag
|
||||||
|
|
||||||
op.code = &contractCode{
|
op.code = &contractCode{
|
||||||
hash: common.BytesToHash(s.CodeHash()),
|
hash: common.BytesToHash(s.CodeHash()),
|
||||||
blob: s.code,
|
blob: s.code,
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ import (
|
||||||
"maps"
|
"maps"
|
||||||
"slices"
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -111,30 +110,11 @@ type StateDB struct {
|
||||||
// Snapshot and RevertToSnapshot.
|
// Snapshot and RevertToSnapshot.
|
||||||
journal *journal
|
journal *journal
|
||||||
|
|
||||||
|
// State witness if cross validation is needed
|
||||||
|
witness *stateless.Witness
|
||||||
|
|
||||||
// Measurements gathered during execution for debugging purposes
|
// Measurements gathered during execution for debugging purposes
|
||||||
AccountReads time.Duration
|
Stats
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new state from a given trie.
|
// 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
|
return sdb, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartPrefetcher initializes a new trie prefetcher to pull in nodes from the
|
// TraceWitness enables execution witness gathering.
|
||||||
// state trie concurrently while the state is mutated so that when we reach the
|
func (s *StateDB) TraceWitness(witness *stateless.Witness) {
|
||||||
// commit phase, most of the needed data is already hot.
|
s.witness = witness
|
||||||
func (s *StateDB) StartPrefetcher(namespace string, witness *stateless.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.
|
// setError remembers the first non-nil error it is called with.
|
||||||
func (s *StateDB) setError(err error) {
|
func (s *StateDB) setError(err error) {
|
||||||
if s.dbErr == nil {
|
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
|
// 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 {
|
func (s *StateDB) GetLogs(hash common.Hash, blockNumber uint64, blockHash common.Hash, blockTime uint64) []*types.Log {
|
||||||
logs := s.logs[hash]
|
logs := s.logs[hash]
|
||||||
for _, l := range logs {
|
for _, l := range logs {
|
||||||
|
|
@ -217,6 +192,7 @@ func (s *StateDB) GetLogs(hash common.Hash, blockNumber uint64, blockHash common
|
||||||
return logs
|
return logs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Logs returns the un-annotated logs in order.
|
||||||
func (s *StateDB) Logs() []*types.Log {
|
func (s *StateDB) Logs() []*types.Log {
|
||||||
logs := make([]*types.Log, 0, s.logSize)
|
logs := make([]*types.Log, 0, s.logSize)
|
||||||
for _, lgs := range s.logs {
|
for _, lgs := range s.logs {
|
||||||
|
|
@ -296,6 +272,9 @@ func (s *StateDB) TxIndex() int {
|
||||||
func (s *StateDB) GetCode(addr common.Address) []byte {
|
func (s *StateDB) GetCode(addr common.Address) []byte {
|
||||||
stateObject := s.getStateObject(addr)
|
stateObject := s.getStateObject(addr)
|
||||||
if stateObject != nil {
|
if stateObject != nil {
|
||||||
|
if s.witness != nil {
|
||||||
|
s.witness.AddCode(stateObject.Code())
|
||||||
|
}
|
||||||
return stateObject.Code()
|
return stateObject.Code()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -304,6 +283,9 @@ func (s *StateDB) GetCode(addr common.Address) []byte {
|
||||||
func (s *StateDB) GetCodeSize(addr common.Address) int {
|
func (s *StateDB) GetCodeSize(addr common.Address) int {
|
||||||
stateObject := s.getStateObject(addr)
|
stateObject := s.getStateObject(addr)
|
||||||
if stateObject != nil {
|
if stateObject != nil {
|
||||||
|
if s.witness != nil {
|
||||||
|
s.witness.AddCode(stateObject.Code())
|
||||||
|
}
|
||||||
return stateObject.CodeSize()
|
return stateObject.CodeSize()
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
|
|
@ -502,14 +484,13 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject {
|
||||||
if _, ok := s.stateObjectsDestruct[addr]; ok {
|
if _, ok := s.stateObjectsDestruct[addr]; ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
s.AccountLoaded++
|
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
acct, err := s.reader.Account(addr)
|
acct, err := s.reader.Account(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.setError(fmt.Errorf("getStateObject (%x) error: %w", addr.Bytes(), err))
|
s.setError(fmt.Errorf("getStateObject (%x) error: %w", addr.Bytes(), err))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
s.AccountLoaded++
|
||||||
s.AccountReads += time.Since(start)
|
s.AccountReads += time.Since(start)
|
||||||
|
|
||||||
// Short circuit if the account is not found
|
// Short circuit if the account is not found
|
||||||
|
|
@ -611,6 +592,9 @@ func (s *StateDB) Copy() *StateDB {
|
||||||
transientStorage: s.transientStorage.Copy(),
|
transientStorage: s.transientStorage.Copy(),
|
||||||
journal: s.journal.copy(),
|
journal: s.journal.copy(),
|
||||||
}
|
}
|
||||||
|
if s.witness != nil {
|
||||||
|
state.witness = s.witness.Copy()
|
||||||
|
}
|
||||||
if s.accessEvents != nil {
|
if s.accessEvents != nil {
|
||||||
state.accessEvents = s.accessEvents.Copy()
|
state.accessEvents = s.accessEvents.Copy()
|
||||||
}
|
}
|
||||||
|
|
@ -756,6 +740,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
op.precedingDelete = false
|
op.precedingDelete = false
|
||||||
|
|
||||||
delAddrs = append(delAddrs, addr)
|
delAddrs = append(delAddrs, addr)
|
||||||
delAccts = append(delAccts, AccountMut{Account: nil})
|
delAccts = append(delAccts, AccountMut{Account: nil})
|
||||||
}
|
}
|
||||||
|
|
@ -764,6 +749,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
||||||
s.setError(err)
|
s.setError(err)
|
||||||
return common.Hash{}
|
return common.Hash{}
|
||||||
}
|
}
|
||||||
|
s.AccountDeleted += len(delAddrs)
|
||||||
}
|
}
|
||||||
s.AccountUpdates += time.Since(start)
|
s.AccountUpdates += time.Since(start)
|
||||||
|
|
||||||
|
|
@ -797,14 +783,20 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
||||||
|
|
||||||
if op.isDelete() {
|
if op.isDelete() {
|
||||||
accounts = append(accounts, AccountMut{Account: nil})
|
accounts = append(accounts, AccountMut{Account: nil})
|
||||||
|
s.AccountDeleted += 1
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
obj := s.stateObjects[addr]
|
obj := s.stateObjects[addr]
|
||||||
mut := AccountMut{Account: &obj.data}
|
mut := AccountMut{Account: &obj.data}
|
||||||
if obj.dirtyCode {
|
if obj.dirtyCode {
|
||||||
mut.Code = &CodeMut{Code: obj.code}
|
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)
|
accounts = append(accounts, mut)
|
||||||
|
s.AccountUpdated += 1
|
||||||
}
|
}
|
||||||
if err := s.hasher.UpdateAccount(addresses, accounts); err != nil {
|
if err := s.hasher.UpdateAccount(addresses, accounts); err != nil {
|
||||||
s.setError(err)
|
s.setError(err)
|
||||||
|
|
@ -932,8 +924,7 @@ func (s *StateDB) handleDestruction(noStorageWiping bool) (map[common.Hash]*acco
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to delete storage, err: %w", err)
|
return nil, nil, fmt.Errorf("failed to delete storage, err: %w", err)
|
||||||
}
|
}
|
||||||
op.storages = storages
|
op.storages, op.storagesOrigin = storages, storagesOrigin
|
||||||
op.storagesOrigin = storagesOrigin
|
|
||||||
|
|
||||||
// Aggregate the associated trie node changes.
|
// Aggregate the associated trie node changes.
|
||||||
if err := nodes.Merge(set); err != nil {
|
if err := nodes.Merge(set); err != nil {
|
||||||
|
|
@ -957,15 +948,6 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
|
||||||
if s.dbErr != nil {
|
if s.dbErr != nil {
|
||||||
return nil, fmt.Errorf("commit aborted due to database error: %v", s.dbErr)
|
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
|
// Given that some accounts could be destroyed and then recreated within
|
||||||
// the same block, account deletions must be processed first. This ensures
|
// the same block, account deletions must be processed first. This ensures
|
||||||
// that the storage trie nodes deleted during destruction and recreated
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// Aggregated account updates
|
||||||
|
updates := make(map[common.Hash]*accountUpdate, len(s.mutations))
|
||||||
for addr, op := range s.mutations {
|
for addr, op := range s.mutations {
|
||||||
if op.isDelete() {
|
if op.isDelete() {
|
||||||
continue
|
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
|
// Handle all state updates afterwards, concurrently to one another to shave
|
||||||
// off some milliseconds from the commit operation. Also accumulate the code
|
// off some milliseconds from the commit operation. Also accumulate the code
|
||||||
// writes to run in parallel with the computations.
|
// writes to run in parallel with the computations.
|
||||||
|
start := time.Now()
|
||||||
root, set, secondaryHashes, err := s.hasher.Commit()
|
root, set, secondaryHashes, err := s.hasher.Commit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
s.HasherCommits = time.Since(start)
|
||||||
|
|
||||||
if err := nodes.MergeSet(set); err != nil {
|
if err := nodes.MergeSet(set); err != nil {
|
||||||
return nil, err
|
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.
|
// Clear all internal flags and update state root at the end.
|
||||||
s.mutations = make(map[common.Address]*mutation)
|
s.mutations = make(map[common.Address]*mutation)
|
||||||
s.stateObjectsDestruct = make(map[common.Address]*stateObject)
|
s.stateObjectsDestruct = make(map[common.Address]*stateObject)
|
||||||
|
|
@ -1022,6 +993,12 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
|
||||||
origin := s.originalRoot
|
origin := s.originalRoot
|
||||||
s.originalRoot = root
|
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
|
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.
|
// Witness retrieves the current state witness being collected.
|
||||||
func (s *StateDB) Witness() *stateless.Witness {
|
func (s *StateDB) Witness() *stateless.Witness {
|
||||||
return nil
|
return s.witness
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StateDB) AccessEvents() *AccessEvents {
|
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
|
return env.size+tx.Size() < params.MaxBlockSize-maxBlockSizeBufferZone
|
||||||
}
|
}
|
||||||
|
|
||||||
// discard terminates the background threads before discarding it.
|
|
||||||
func (env *environment) discard() {
|
|
||||||
env.state.StopPrefetcher()
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
commitInterruptNone int32 = iota
|
commitInterruptNone int32 = iota
|
||||||
commitInterruptNewHead
|
commitInterruptNewHead
|
||||||
|
|
@ -147,8 +142,6 @@ func (miner *Miner) generateWork(ctx context.Context, genParam *generateParams,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &newPayloadResult{err: err}
|
return &newPayloadResult{err: err}
|
||||||
}
|
}
|
||||||
defer work.discard()
|
|
||||||
|
|
||||||
// Check withdrawals fit max block size.
|
// Check withdrawals fit max block size.
|
||||||
// Due to the cap on withdrawal count, this can actually never happen, but we still need to
|
// 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.
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
state.TraceWitness(bundle)
|
||||||
}
|
}
|
||||||
state.StartPrefetcher("miner", bundle)
|
|
||||||
// Note the passed coinbase may be different with header.Coinbase.
|
// Note the passed coinbase may be different with header.Coinbase.
|
||||||
return &environment{
|
return &environment{
|
||||||
signer: types.MakeSigner(miner.chainConfig, header.Number, header.Time),
|
signer: types.MakeSigner(miner.chainConfig, header.Number, header.Time),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue