core: extend code read statistics with cache and unique metrics

This extends the execution statistics infrastructure from #33442 with:

- Code cache hit/miss tracking (completing parity with account/storage)
- Code bytes read metric for I/O volume analysis
- Unique state access metrics (accounts, storage slots, contracts)

The slow block log now shows:

    Code read: 626ns(4, 1.07 KiB)

    Unique state access:
        Accounts: 6
        Storage slots: 11
        Contracts executed: 4

    Reader statistics
    account: hit: 4, miss: 6, rate: 40.00
    storage: hit: 0, miss: 11, rate: 0.00
    code: hit: 4, miss: 0, rate: 100.00
This commit is contained in:
CPerezz 2026-01-03 22:52:58 +01:00
parent ffe9dc97e5
commit be2d43090c
No known key found for this signature in database
GPG key ID: 62045F34B97177DD
9 changed files with 212 additions and 29 deletions

View file

@ -87,6 +87,9 @@ var (
storageCacheHitPrefetchMeter = metrics.NewRegisteredMeter("chain/storage/reads/cache/prefetch/hit", nil) storageCacheHitPrefetchMeter = metrics.NewRegisteredMeter("chain/storage/reads/cache/prefetch/hit", nil)
storageCacheMissPrefetchMeter = metrics.NewRegisteredMeter("chain/storage/reads/cache/prefetch/miss", 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) accountReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/account/single/reads", nil)
storageReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/storage/single/reads", nil) storageReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/storage/single/reads", nil)
codeReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/code/single/reads", nil) codeReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/code/single/reads", nil)
@ -2060,6 +2063,8 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
accountCacheMissMeter.Mark(rStat.AccountCacheMiss) accountCacheMissMeter.Mark(rStat.AccountCacheMiss)
storageCacheHitMeter.Mark(rStat.StorageCacheHit) storageCacheHitMeter.Mark(rStat.StorageCacheHit)
storageCacheMissMeter.Mark(rStat.StorageCacheMiss) storageCacheMissMeter.Mark(rStat.StorageCacheMiss)
codeCacheHitMeter.Mark(rStat.CodeCacheHit)
codeCacheMissMeter.Mark(rStat.CodeCacheMiss)
if result != nil { if result != nil {
result.stats.StatePrefetchCacheStats = pStat result.stats.StatePrefetchCacheStats = pStat
@ -2182,6 +2187,11 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
stats.StorageUpdated = int(statedb.StorageUpdated.Load()) stats.StorageUpdated = int(statedb.StorageUpdated.Load())
stats.StorageDeleted = int(statedb.StorageDeleted.Load()) stats.StorageDeleted = int(statedb.StorageDeleted.Load())
stats.CodeLoaded = statedb.CodeLoaded stats.CodeLoaded = statedb.CodeLoaded
stats.CodeBytesRead = statedb.CodeBytesRead
stats.UniqueAccountsAccessed = statedb.UniqueAccountsAccessed()
stats.UniqueStorageAccessed = statedb.UniqueStorageAccessed()
stats.UniqueCodeExecuted = statedb.UniqueCodeExecuted()
stats.Execution = ptime - (statedb.AccountReads + statedb.StorageReads + statedb.CodeReads) // The time spent on EVM processing 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.Validation = vtime - (statedb.AccountHashes + statedb.AccountUpdates + statedb.StorageUpdates) // The time spent on block validation

View file

@ -39,13 +39,19 @@ type ExecuteStats struct {
StorageCommits time.Duration // Time spent on the storage trie commit StorageCommits time.Duration // Time spent on the storage trie commit
CodeReads time.Duration // Time spent on the contract code read CodeReads time.Duration // Time spent on the contract code read
AccountLoaded int // Number of accounts loaded AccountLoaded int // Number of accounts loaded
AccountUpdated int // Number of accounts updated AccountUpdated int // Number of accounts updated
AccountDeleted int // Number of accounts deleted AccountDeleted int // Number of accounts deleted
StorageLoaded int // Number of storage slots loaded StorageLoaded int // Number of storage slots loaded
StorageUpdated int // Number of storage slots updated StorageUpdated int // Number of storage slots updated
StorageDeleted int // Number of storage slots deleted StorageDeleted int // Number of storage slots deleted
CodeLoaded int // Number of contract code loaded 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
Execution time.Duration // Time spent on the EVM execution Execution time.Duration // Time spent on the EVM execution
Validation time.Duration // Time spent on the block validation Validation time.Duration // Time spent on the block validation
@ -100,6 +106,8 @@ func (s *ExecuteStats) reportMetrics() {
accountCacheMissMeter.Mark(s.StateReadCacheStats.AccountCacheMiss) accountCacheMissMeter.Mark(s.StateReadCacheStats.AccountCacheMiss)
storageCacheHitMeter.Mark(s.StateReadCacheStats.StorageCacheHit) storageCacheHitMeter.Mark(s.StateReadCacheStats.StorageCacheHit)
storageCacheMissMeter.Mark(s.StateReadCacheStats.StorageCacheMiss) 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. // logSlow prints the detailed execution statistics if the block is regarded as slow.
@ -119,7 +127,12 @@ Validation: %v
State read: %v State read: %v
Account read: %v(%d) Account read: %v(%d)
Storage 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
State hash: %v State hash: %v
Account hash: %v Account hash: %v
@ -140,7 +153,12 @@ DB write: %v
common.PrettyDuration(s.AccountReads+s.StorageReads+s.CodeReads), common.PrettyDuration(s.AccountReads+s.StorageReads+s.CodeReads),
common.PrettyDuration(s.AccountReads), s.AccountLoaded, common.PrettyDuration(s.AccountReads), s.AccountLoaded,
common.PrettyDuration(s.StorageReads), s.StorageLoaded, 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,
// State hash // State hash
common.PrettyDuration(s.AccountHashes+s.AccountUpdates+s.StorageUpdates+max(s.AccountCommits, s.StorageCommits)), common.PrettyDuration(s.AccountHashes+s.AccountUpdates+s.StorageUpdates+max(s.AccountCommits, s.StorageCommits)),

View file

@ -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 // ReadersWithCacheStats creates a pair of state readers sharing the same internal cache and
// same backing Reader, but exposing separate statistics. // same backing Reader, but exposing separate statistics.
// and statistics.
func (db *CachingDB) ReadersWithCacheStats(stateRoot common.Hash) (ReaderWithStats, ReaderWithStats, error) { func (db *CachingDB) ReadersWithCacheStats(stateRoot common.Hash) (ReaderWithStats, ReaderWithStats, error) {
reader, err := db.Reader(stateRoot) reader, err := db.Reader(stateRoot)
if err != nil { if err != nil {

View file

@ -98,6 +98,8 @@ type ReaderStats struct {
AccountCacheMiss int64 AccountCacheMiss int64
StorageCacheHit int64 StorageCacheHit int64
StorageCacheMiss int64 StorageCacheMiss int64
CodeCacheHit int64
CodeCacheMiss int64
} }
// String implements fmt.Stringer, returning string format statistics. // String implements fmt.Stringer, returning string format statistics.
@ -105,6 +107,7 @@ func (s ReaderStats) String() string {
var ( var (
accountCacheHitRate float64 accountCacheHitRate float64
storageCacheHitRate float64 storageCacheHitRate float64
codeCacheHitRate float64
) )
if s.AccountCacheHit > 0 { if s.AccountCacheHit > 0 {
accountCacheHitRate = float64(s.AccountCacheHit) / float64(s.AccountCacheHit+s.AccountCacheMiss) * 100 accountCacheHitRate = float64(s.AccountCacheHit) / float64(s.AccountCacheHit+s.AccountCacheMiss) * 100
@ -112,9 +115,13 @@ func (s ReaderStats) String() string {
if s.StorageCacheHit > 0 { if s.StorageCacheHit > 0 {
storageCacheHitRate = float64(s.StorageCacheHit) / float64(s.StorageCacheHit+s.StorageCacheMiss) * 100 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("Reader statistics\n")
msg += fmt.Sprintf("account: hit: %d, miss: %d, rate: %.2f\n", s.AccountCacheHit, s.AccountCacheMiss, accountCacheHitRate) 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("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 return msg
} }
@ -146,32 +153,46 @@ func newCachingCodeReader(db ethdb.KeyValueReader, codeCache *lru.SizeConstraine
} }
} }
// Code implements ContractCodeReader, retrieving a particular contract's code. // code retrieves a particular contract's code along with a flag indicating
// If the contract code doesn't exist, no error will be returned. // whether it was found in the cache.
func (r *cachingCodeReader) Code(addr common.Address, codeHash common.Hash) ([]byte, error) { func (r *cachingCodeReader) code(addr common.Address, codeHash common.Hash) ([]byte, bool, error) {
code, _ := r.codeCache.Get(codeHash) code, found := r.codeCache.Get(codeHash)
if len(code) > 0 { if found && len(code) > 0 {
return code, nil return code, true, nil
} }
code = rawdb.ReadCode(r.db, codeHash) code = rawdb.ReadCode(r.db, codeHash)
if len(code) > 0 { if len(code) > 0 {
r.codeCache.Add(codeHash, code) r.codeCache.Add(codeHash, code)
r.codeSizeCache.Add(codeHash, len(code)) r.codeSizeCache.Add(codeHash, len(code))
} }
return code, nil return code, false, nil
}
// 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, _, 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, true, nil
}
code, _, err := r.code(addr, codeHash)
if err != nil {
return 0, false, err
}
return len(code), false, nil
} }
// CodeSize implements ContractCodeReader, retrieving a particular contracts code's size. // CodeSize implements ContractCodeReader, retrieving a particular contracts code's size.
// If the contract code doesn't exist, no error will be returned. // 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) CodeSize(addr common.Address, codeHash common.Hash) (int, error) {
if cached, ok := r.codeSizeCache.Get(codeHash); ok { size, _, err := r.codeSize(addr, codeHash)
return cached, nil return size, err
}
code, err := r.Code(addr, codeHash)
if err != nil {
return 0, err
}
return len(code), nil
} }
// Has returns the flag indicating whether the contract code with // Has returns the flag indicating whether the contract code with
@ -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 // readerWithCache is a wrapper around Reader that maintains additional state caches
// to support concurrent state access. // to support concurrent state access.
type readerWithCache struct { type readerWithCache struct {
@ -573,6 +616,24 @@ func (r *readerWithCache) Storage(addr common.Address, slot common.Hash) (common
return value, err 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 { type readerWithCacheStats struct {
*readerWithCache *readerWithCache
@ -580,6 +641,8 @@ type readerWithCacheStats struct {
accountCacheMiss atomic.Int64 accountCacheMiss atomic.Int64
storageCacheHit atomic.Int64 storageCacheHit atomic.Int64
storageCacheMiss atomic.Int64 storageCacheMiss atomic.Int64
codeCacheHit atomic.Int64
codeCacheMiss atomic.Int64
} }
// newReaderWithCacheStats constructs the reader with additional statistics tracked. // newReaderWithCacheStats constructs the reader with additional statistics tracked.
@ -624,6 +687,34 @@ func (r *readerWithCacheStats) Storage(addr common.Address, slot common.Hash) (c
return value, nil 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. // GetStats implements ReaderWithStats, returning the statistics of state reader.
func (r *readerWithCacheStats) GetStats() ReaderStats { func (r *readerWithCacheStats) GetStats() ReaderStats {
return ReaderStats{ return ReaderStats{
@ -631,5 +722,7 @@ func (r *readerWithCacheStats) GetStats() ReaderStats {
AccountCacheMiss: r.accountCacheMiss.Load(), AccountCacheMiss: r.accountCacheMiss.Load(),
StorageCacheHit: r.storageCacheHit.Load(), StorageCacheHit: r.storageCacheHit.Load(),
StorageCacheMiss: r.storageCacheMiss.Load(), StorageCacheMiss: r.storageCacheMiss.Load(),
CodeCacheHit: r.codeCacheHit.Load(),
CodeCacheMiss: r.codeCacheMiss.Load(),
} }
} }

View file

@ -543,6 +543,7 @@ func (s *stateObject) Code() []byte {
if len(code) == 0 { if len(code) == 0 {
s.db.setError(fmt.Errorf("code is not found %x", s.CodeHash())) s.db.setError(fmt.Errorf("code is not found %x", s.CodeHash()))
} }
s.db.CodeBytesRead += int64(len(code))
s.code = code s.code = code
return code return code
} }

View file

@ -160,6 +160,10 @@ type StateDB struct {
StorageUpdated atomic.Int64 // Number of storage slots updated 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 StorageDeleted atomic.Int64 // Number of storage slots deleted during the state transition
CodeLoaded int // Number of contract code loaded 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{}
} }
// New creates a new state from a given trie. // New creates a new state from a given trie.
@ -186,6 +190,7 @@ func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, erro
journal: newJournal(), journal: newJournal(),
accessList: newAccessList(), accessList: newAccessList(),
transientStorage: newTransientStorage(), transientStorage: newTransientStorage(),
executedCodes: make(map[common.Hash]struct{}),
} }
if db.TrieDB().IsVerkle() { if db.TrieDB().IsVerkle() {
sdb.accessEvents = NewAccessEvents(db.PointCache()) sdb.accessEvents = NewAccessEvents(db.PointCache())
@ -379,6 +384,37 @@ func (s *StateDB) GetCodeHash(addr common.Address) common.Hash {
return 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.
func (s *StateDB) MarkCodeExecuted(codeHash common.Hash) {
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{}{}
}
// UniqueCodeExecuted returns the number of unique contract codes executed.
func (s *StateDB) UniqueCodeExecuted() int {
return len(s.executedCodes)
}
// 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. // GetState retrieves the value associated with the specific key.
func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash { func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
stateObject := s.getStateObject(addr) stateObject := s.getStateObject(addr)

View file

@ -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) {
s.inner.MarkCodeExecuted(codeHash)
}

View file

@ -293,7 +293,10 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
// The contract is a scoped environment for this execution context only. // The contract is a scoped environment for this execution context only.
contract := NewContract(caller, addr, value, gas, evm.jumpDests) contract := NewContract(caller, addr, value, gas, evm.jumpDests)
contract.IsSystemCall = isSystemCall(caller) contract.IsSystemCall = isSystemCall(caller)
contract.SetCallCode(evm.resolveCodeHash(addr), code) codeHash := evm.resolveCodeHash(addr)
contract.SetCallCode(codeHash, code)
// Track unique contract execution for metrics
evm.StateDB.MarkCodeExecuted(codeHash)
ret, err = evm.Run(contract, input, false) ret, err = evm.Run(contract, input, false)
gas = contract.Gas gas = contract.Gas
} }
@ -352,7 +355,10 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
// Initialise a new contract and set the code that is to be used by the EVM. // Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only. // The contract is a scoped environment for this execution context only.
contract := NewContract(caller, caller, value, gas, evm.jumpDests) contract := NewContract(caller, caller, value, gas, evm.jumpDests)
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr)) codeHash := evm.resolveCodeHash(addr)
contract.SetCallCode(codeHash, evm.resolveCode(addr))
// Track unique contract execution for metrics
evm.StateDB.MarkCodeExecuted(codeHash)
ret, err = evm.Run(contract, input, false) ret, err = evm.Run(contract, input, false)
gas = contract.Gas gas = contract.Gas
} }
@ -396,7 +402,10 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address,
// //
// Note: The value refers to the original value from the parent call. // Note: The value refers to the original value from the parent call.
contract := NewContract(originCaller, caller, value, gas, evm.jumpDests) contract := NewContract(originCaller, caller, value, gas, evm.jumpDests)
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr)) codeHash := evm.resolveCodeHash(addr)
contract.SetCallCode(codeHash, evm.resolveCode(addr))
// Track unique contract execution for metrics
evm.StateDB.MarkCodeExecuted(codeHash)
ret, err = evm.Run(contract, input, false) ret, err = evm.Run(contract, input, false)
gas = contract.Gas gas = contract.Gas
} }
@ -447,7 +456,10 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b
// Initialise a new contract and set the code that is to be used by the EVM. // Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only. // The contract is a scoped environment for this execution context only.
contract := NewContract(caller, addr, new(uint256.Int), gas, evm.jumpDests) contract := NewContract(caller, addr, new(uint256.Int), gas, evm.jumpDests)
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr)) codeHash := evm.resolveCodeHash(addr)
contract.SetCallCode(codeHash, evm.resolveCode(addr))
// Track unique contract execution for metrics
evm.StateDB.MarkCodeExecuted(codeHash)
// When an error was returned by the EVM or when setting the creation code // When an error was returned by the EVM or when setting the creation code
// above we revert to the snapshot and consume any gas remaining. Additionally // above we revert to the snapshot and consume any gas remaining. Additionally

View file

@ -101,4 +101,8 @@ type StateDB interface {
// Finalise must be invoked at the end of a transaction // Finalise must be invoked at the end of a transaction
Finalise(bool) Finalise(bool)
// MarkCodeExecuted records that a contract's code was executed.
// Used for metrics tracking to count unique contracts executed.
MarkCodeExecuted(codeHash common.Hash)
} }