Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ var (
storageCacheHitPrefetchMeter = metrics.NewRegisteredMeter("chain/storage/reads/cache/prefetch/hit", nil)
storageCacheMissPrefetchMeter = metrics.NewRegisteredMeter("chain/storage/reads/cache/prefetch/miss", nil)

codeCacheHitMeter = metrics.NewRegisteredMeter("chain/code/reads/cache/hit", nil)
codeCacheMissMeter = metrics.NewRegisteredMeter("chain/code/reads/cache/miss", nil)

accountReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/account/single/reads", nil)
storageReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/storage/single/reads", nil)
codeReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/code/single/reads", nil)
Expand Down Expand Up @@ -2060,6 +2063,8 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
accountCacheMissMeter.Mark(rStat.AccountCacheMiss)
storageCacheHitMeter.Mark(rStat.StorageCacheHit)
storageCacheMissMeter.Mark(rStat.StorageCacheMiss)
codeCacheHitMeter.Mark(rStat.CodeCacheHit)
codeCacheMissMeter.Mark(rStat.CodeCacheMiss)

if result != nil {
result.stats.StatePrefetchCacheStats = pStat
Expand Down Expand Up @@ -2182,6 +2187,12 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
stats.StorageUpdated = int(statedb.StorageUpdated.Load())
stats.StorageDeleted = int(statedb.StorageDeleted.Load())
stats.CodeLoaded = statedb.CodeLoaded
stats.CodeBytesRead = statedb.CodeBytesRead

stats.UniqueAccountsAccessed = statedb.UniqueAccountsAccessed()
stats.UniqueStorageAccessed = statedb.UniqueStorageAccessed()
stats.UniqueCodeExecuted = statedb.UniqueCodeExecuted()
stats.SystemCodeExecuted = statedb.SystemCodeExecuted()

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
Expand Down
37 changes: 28 additions & 9 deletions core/blockchain_stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,20 @@ type ExecuteStats struct {
StorageCommits time.Duration // Time spent on the storage trie commit
CodeReads time.Duration // Time spent on the contract code read

AccountLoaded int // Number of accounts loaded
AccountUpdated int // Number of accounts updated
AccountDeleted int // Number of accounts deleted
StorageLoaded int // Number of storage slots loaded
StorageUpdated int // Number of storage slots updated
StorageDeleted int // Number of storage slots deleted
CodeLoaded int // Number of contract code loaded
AccountLoaded int // Number of accounts loaded
AccountUpdated int // Number of accounts updated
AccountDeleted int // Number of accounts deleted
StorageLoaded int // Number of storage slots loaded
StorageUpdated int // Number of storage slots updated
StorageDeleted int // Number of storage slots deleted
CodeLoaded int // Number of contract code loaded
CodeBytesRead int64 // Total bytes of contract code read

// Unique access metrics
UniqueAccountsAccessed int // Number of unique accounts accessed
UniqueStorageAccessed int // Number of unique storage slots accessed
UniqueCodeExecuted int // Number of unique contracts executed
SystemCodeExecuted int // Number of unique system contracts executed (subset of UniqueCodeExecuted)

Execution time.Duration // Time spent on the EVM execution
Validation time.Duration // Time spent on the block validation
Expand Down Expand Up @@ -100,6 +107,8 @@ func (s *ExecuteStats) reportMetrics() {
accountCacheMissMeter.Mark(s.StateReadCacheStats.AccountCacheMiss)
storageCacheHitMeter.Mark(s.StateReadCacheStats.StorageCacheHit)
storageCacheMissMeter.Mark(s.StateReadCacheStats.StorageCacheMiss)
codeCacheHitMeter.Mark(s.StateReadCacheStats.CodeCacheHit)
codeCacheMissMeter.Mark(s.StateReadCacheStats.CodeCacheMiss)
}

// logSlow prints the detailed execution statistics if the block is regarded as slow.
Expand All @@ -119,7 +128,12 @@ Validation: %v
State read: %v
Account read: %v(%d)
Storage read: %v(%d)
Code read: %v(%d)
Code read: %v(%d, %s)

Unique state access:
Accounts: %d
Storage slots: %d
Contracts executed: %d (%d system)

State hash: %v
Account hash: %v
Expand All @@ -140,7 +154,12 @@ DB write: %v
common.PrettyDuration(s.AccountReads+s.StorageReads+s.CodeReads),
common.PrettyDuration(s.AccountReads), s.AccountLoaded,
common.PrettyDuration(s.StorageReads), s.StorageLoaded,
common.PrettyDuration(s.CodeReads), s.CodeLoaded,
common.PrettyDuration(s.CodeReads), s.CodeLoaded, common.StorageSize(s.CodeBytesRead),

// Unique state access
s.UniqueAccountsAccessed,
s.UniqueStorageAccessed,
s.UniqueCodeExecuted, s.SystemCodeExecuted,

// State hash
common.PrettyDuration(s.AccountHashes+s.AccountUpdates+s.StorageUpdates+max(s.AccountCommits, s.StorageCommits)),
Expand Down
1 change: 0 additions & 1 deletion core/state/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,6 @@ func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {

// ReadersWithCacheStats creates a pair of state readers sharing the same internal cache and
// same backing Reader, but exposing separate statistics.
// and statistics.
func (db *CachingDB) ReadersWithCacheStats(stateRoot common.Hash) (ReaderWithStats, ReaderWithStats, error) {
reader, err := db.Reader(stateRoot)
if err != nil {
Expand Down
119 changes: 106 additions & 13 deletions core/state/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,23 +98,30 @@ type ReaderStats struct {
AccountCacheMiss int64
StorageCacheHit int64
StorageCacheMiss int64
CodeCacheHit int64
CodeCacheMiss int64
}

// String implements fmt.Stringer, returning string format statistics.
func (s ReaderStats) String() string {
var (
accountCacheHitRate float64
storageCacheHitRate float64
codeCacheHitRate float64
)
if s.AccountCacheHit > 0 {
accountCacheHitRate = float64(s.AccountCacheHit) / float64(s.AccountCacheHit+s.AccountCacheMiss) * 100
}
if s.StorageCacheHit > 0 {
storageCacheHitRate = float64(s.StorageCacheHit) / float64(s.StorageCacheHit+s.StorageCacheMiss) * 100
}
if s.CodeCacheHit > 0 {
codeCacheHitRate = float64(s.CodeCacheHit) / float64(s.CodeCacheHit+s.CodeCacheMiss) * 100
}
msg := fmt.Sprintf("Reader statistics\n")
msg += fmt.Sprintf("account: hit: %d, miss: %d, rate: %.2f\n", s.AccountCacheHit, s.AccountCacheMiss, accountCacheHitRate)
msg += fmt.Sprintf("storage: hit: %d, miss: %d, rate: %.2f\n", s.StorageCacheHit, s.StorageCacheMiss, storageCacheHitRate)
msg += fmt.Sprintf("code: hit: %d, miss: %d, rate: %.2f\n", s.CodeCacheHit, s.CodeCacheMiss, codeCacheHitRate)
return msg
}

Expand Down Expand Up @@ -146,32 +153,46 @@ func newCachingCodeReader(db ethdb.KeyValueReader, codeCache *lru.SizeConstraine
}
}

// Code implements ContractCodeReader, retrieving a particular contract's code.
// If the contract code doesn't exist, no error will be returned.
func (r *cachingCodeReader) Code(addr common.Address, codeHash common.Hash) ([]byte, error) {
code, _ := r.codeCache.Get(codeHash)
if len(code) > 0 {
return code, nil
// code retrieves a particular contract's code along with a flag indicating
// whether it was found in the cache.
func (r *cachingCodeReader) code(addr common.Address, codeHash common.Hash) ([]byte, bool, error) {
code, found := r.codeCache.Get(codeHash)
if found && len(code) > 0 {
return code, true, nil
}
code = rawdb.ReadCode(r.db, codeHash)
if len(code) > 0 {
r.codeCache.Add(codeHash, code)
r.codeSizeCache.Add(codeHash, len(code))
}
return code, nil
return code, false, nil
}

// CodeSize implements ContractCodeReader, retrieving a particular contracts code's size.
// Code implements ContractCodeReader, retrieving a particular contract's code.
// If the contract code doesn't exist, no error will be returned.
func (r *cachingCodeReader) CodeSize(addr common.Address, codeHash common.Hash) (int, error) {
func (r *cachingCodeReader) Code(addr common.Address, codeHash common.Hash) ([]byte, error) {
code, _, err := r.code(addr, codeHash)
return code, err
}

// codeSize retrieves a particular contract's code size along with a flag indicating
// whether it was found in the cache.
func (r *cachingCodeReader) codeSize(addr common.Address, codeHash common.Hash) (int, bool, error) {
if cached, ok := r.codeSizeCache.Get(codeHash); ok {
return cached, nil
return cached, true, nil
}
code, err := r.Code(addr, codeHash)
code, _, err := r.code(addr, codeHash)
if err != nil {
return 0, err
return 0, false, err
}
return len(code), nil
return len(code), false, nil
}

// CodeSize implements ContractCodeReader, retrieving a particular contracts code's size.
// If the contract code doesn't exist, no error will be returned.
func (r *cachingCodeReader) CodeSize(addr common.Address, codeHash common.Hash) (int, error) {
size, _, err := r.codeSize(addr, codeHash)
return size, err
}

// Has returns the flag indicating whether the contract code with
Expand Down Expand Up @@ -463,6 +484,28 @@ func newReader(codeReader ContractCodeReader, stateReader StateReader) *reader {
}
}

// code retrieves contract code with a flag indicating cache hit status.
// Returns (code, incache, error). If the underlying code reader doesn't
// support cache tracking, incache is always false.
func (r *reader) code(addr common.Address, codeHash common.Hash) ([]byte, bool, error) {
if cr, ok := r.ContractCodeReader.(*cachingCodeReader); ok {
return cr.code(addr, codeHash)
}
code, err := r.ContractCodeReader.Code(addr, codeHash)
return code, false, err
}

// codeSize retrieves contract code size with a flag indicating cache hit status.
// Returns (size, incache, error). If the underlying code reader doesn't
// support cache tracking, incache is always false.
func (r *reader) codeSize(addr common.Address, codeHash common.Hash) (int, bool, error) {
if cr, ok := r.ContractCodeReader.(*cachingCodeReader); ok {
return cr.codeSize(addr, codeHash)
}
size, err := r.ContractCodeReader.CodeSize(addr, codeHash)
return size, false, err
}

// readerWithCache is a wrapper around Reader that maintains additional state caches
// to support concurrent state access.
type readerWithCache struct {
Expand Down Expand Up @@ -573,13 +616,33 @@ func (r *readerWithCache) Storage(addr common.Address, slot common.Hash) (common
return value, err
}

// code retrieves contract code with a flag indicating cache hit status.
func (r *readerWithCache) code(addr common.Address, codeHash common.Hash) ([]byte, bool, error) {
if rr, ok := r.Reader.(*reader); ok {
return rr.code(addr, codeHash)
}
code, err := r.Reader.Code(addr, codeHash)
return code, false, err
}

// codeSize retrieves contract code size with a flag indicating cache hit status.
func (r *readerWithCache) codeSize(addr common.Address, codeHash common.Hash) (int, bool, error) {
if rr, ok := r.Reader.(*reader); ok {
return rr.codeSize(addr, codeHash)
}
size, err := r.Reader.CodeSize(addr, codeHash)
return size, false, err
}

type readerWithCacheStats struct {
*readerWithCache

accountCacheHit atomic.Int64
accountCacheMiss atomic.Int64
storageCacheHit atomic.Int64
storageCacheMiss atomic.Int64
codeCacheHit atomic.Int64
codeCacheMiss atomic.Int64
}

// newReaderWithCacheStats constructs the reader with additional statistics tracked.
Expand Down Expand Up @@ -624,12 +687,42 @@ func (r *readerWithCacheStats) Storage(addr common.Address, slot common.Hash) (c
return value, nil
}

// Code implements ContractCodeReader, retrieving a particular contract's code.
func (r *readerWithCacheStats) Code(addr common.Address, codeHash common.Hash) ([]byte, error) {
code, incache, err := r.readerWithCache.code(addr, codeHash)
if err != nil {
return nil, err
}
if incache {
r.codeCacheHit.Add(1)
} else {
r.codeCacheMiss.Add(1)
}
return code, nil
}

// CodeSize implements ContractCodeReader, retrieving a particular contract's code size.
func (r *readerWithCacheStats) CodeSize(addr common.Address, codeHash common.Hash) (int, error) {
size, incache, err := r.readerWithCache.codeSize(addr, codeHash)
if err != nil {
return 0, err
}
if incache {
r.codeCacheHit.Add(1)
} else {
r.codeCacheMiss.Add(1)
}
return size, nil
}

// GetStats implements ReaderWithStats, returning the statistics of state reader.
func (r *readerWithCacheStats) GetStats() ReaderStats {
return ReaderStats{
AccountCacheHit: r.accountCacheHit.Load(),
AccountCacheMiss: r.accountCacheMiss.Load(),
StorageCacheHit: r.storageCacheHit.Load(),
StorageCacheMiss: r.storageCacheMiss.Load(),
CodeCacheHit: r.codeCacheHit.Load(),
CodeCacheMiss: r.codeCacheMiss.Load(),
}
}
1 change: 1 addition & 0 deletions core/state/state_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,7 @@ func (s *stateObject) Code() []byte {
if len(code) == 0 {
s.db.setError(fmt.Errorf("code is not found %x", s.CodeHash()))
}
s.db.CodeBytesRead += int64(len(code))
s.code = code
return code
}
Expand Down
48 changes: 48 additions & 0 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,11 @@ type StateDB struct {
StorageUpdated atomic.Int64 // Number of storage slots updated during the state transition
StorageDeleted atomic.Int64 // Number of storage slots deleted during the state transition
CodeLoaded int // Number of contract code loaded during the state transition
CodeBytesRead int64 // Total bytes of contract code read during the state transition

// Unique contracts executed tracking (set of code hashes executed during the state transition)
executedCodes map[common.Hash]struct{}
systemExecutedCodes map[common.Hash]struct{} // Subset of executedCodes that are system calls
}

// New creates a new state from a given trie.
Expand All @@ -186,6 +191,8 @@ func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, erro
journal: newJournal(),
accessList: newAccessList(),
transientStorage: newTransientStorage(),
executedCodes: make(map[common.Hash]struct{}),
systemExecutedCodes: make(map[common.Hash]struct{}),
}
if db.TrieDB().IsVerkle() {
sdb.accessEvents = NewAccessEvents(db.PointCache())
Expand Down Expand Up @@ -379,6 +386,47 @@ func (s *StateDB) GetCodeHash(addr common.Address) common.Hash {
return common.Hash{}
}

// MarkCodeExecuted records that a contract's code was executed.
// This is used for metrics tracking to count unique contracts executed.
// The isSystem parameter indicates if this is a system call (e.g., EIP-4788 beacon root).
func (s *StateDB) MarkCodeExecuted(codeHash common.Hash, isSystem bool) {
if codeHash == types.EmptyCodeHash || codeHash == (common.Hash{}) {
return
}
if s.executedCodes == nil {
return // Skip tracking for state copies (e.g., prefetcher)
}
s.executedCodes[codeHash] = struct{}{}
if isSystem {
s.systemExecutedCodes[codeHash] = struct{}{}
}
}

// UniqueCodeExecuted returns the number of unique contract codes executed.
func (s *StateDB) UniqueCodeExecuted() int {
return len(s.executedCodes)
}

// SystemCodeExecuted returns the number of unique system contract codes executed
// (e.g., EIP-4788 beacon root, EIP-2935 history storage, etc.).
func (s *StateDB) SystemCodeExecuted() int {
return len(s.systemExecutedCodes)
}

// UniqueAccountsAccessed returns the number of unique accounts accessed during the state transition.
func (s *StateDB) UniqueAccountsAccessed() int {
return len(s.stateObjects)
}

// UniqueStorageAccessed returns the number of unique storage slots accessed during the state transition.
func (s *StateDB) UniqueStorageAccessed() int {
count := 0
for _, obj := range s.stateObjects {
count += len(obj.originStorage)
}
return count
}

// GetState retrieves the value associated with the specific key.
func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
stateObject := s.getStateObject(addr)
Expand Down
10 changes: 10 additions & 0 deletions core/state/statedb_hooked.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,3 +291,13 @@ func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) {
}
}
}

// Inner returns the underlying StateDB.
func (s *hookedStateDB) Inner() *StateDB {
return s.inner
}

// MarkCodeExecuted records that a contract's code was executed.
func (s *hookedStateDB) MarkCodeExecuted(codeHash common.Hash, isSystem bool) {
s.inner.MarkCodeExecuted(codeHash, isSystem)
}
Loading
Loading