Skip to content
Merged
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
35 changes: 23 additions & 12 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,20 @@ func (bc *BlockChain) processBlockWithAccessList(parentRoot common.Hash, block *
writeTime := time.Since(writeStart)
var stats ExecuteStats

wc := stateTransition.WrittenCounts()
d := stateTransition.Deletions()
codeLoaded, codeLoadBytes := prefetchReader.(state.CodeLoadTracker).CodeLoads()
stats.AccountLoaded = al.UniqueAccountCount()
stats.AccountUpdated = wc.Accounts - d.Accounts
stats.AccountDeleted = d.Accounts
stats.StorageLoaded = al.UniqueStorageSlotCount()
stats.StorageUpdated = wc.StorageSlots - d.Storage
stats.StorageDeleted = d.Storage
stats.CodeLoaded = codeLoaded
stats.CodeLoadBytes = codeLoadBytes
stats.CodeUpdated = wc.Codes
stats.CodeUpdateBytes = wc.CodeBytes

stats.ExecWall = res.ExecTime
stats.PostProcess = res.PostProcessTime

Expand All @@ -666,12 +680,9 @@ func (bc *BlockChain) processBlockWithAccessList(parentRoot common.Hash, block *
stats.DatabaseCommit = m.TrieDBCommits
stats.Prefetch = m.StatePrefetch
}

stats.Prefetch = prefetchReader.(state.PrefetcherMetricer).Metrics().Elapsed

if r, ok := prefetchReader.(state.ReaderStater); ok {
stats.StateReadCacheStats = r.GetStats()
}
stats.StateReadCacheStats = prefetchReader.(state.ReaderStater).GetStats()

elapsed := time.Since(startTime) + 1 // prevent zero division
stats.TotalTime = elapsed
Expand Down Expand Up @@ -2436,11 +2447,11 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
stats = &ExecuteStats{}
)
// 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.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
Expand All @@ -2455,9 +2466,9 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
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
stats.Execution = ptime - (statedb.AccountReads + statedb.StorageReads + statedb.CodeReads) // EVM processing time
stats.Validation = vtime - (statedb.AccountHashes + statedb.AccountUpdates + statedb.StorageUpdates) // Block validation time
stats.CrossValidation = xvtime

// Write the block to the chain and get the status.
var status WriteStatus
Expand Down
27 changes: 13 additions & 14 deletions core/blockchain_stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,16 @@ type ExecuteStats struct {
StorageCommits time.Duration // Time spent on the storage trie commit
CodeReads time.Duration // Time spent on the contract code read

// TODO: code bytes 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
CodeLoadBytes int // Number of bytes read from contract code
CodeUpdated int // Number of contract code written (CREATE/CREATE2 + EIP-7702)
CodeUpdateBytes int // Total bytes of code written
AccountLoaded int
AccountUpdated int
AccountDeleted int
StorageLoaded int
StorageUpdated int
StorageDeleted int
CodeLoaded int
CodeLoadBytes int
CodeUpdated int
CodeUpdateBytes int

Execution time.Duration // Time spent on the EVM execution
Validation time.Duration // Time spent on the block validation
Expand Down Expand Up @@ -223,7 +222,7 @@ func buildSlowBlockLog(s *ExecuteStats, block *types.Block) slowBlockLog {
},
Timing: slowBlockTime{
ExecutionMs: durationToMs(s.Execution),
StateReadMs: durationToMs(s.AccountReads + s.StorageReads + s.CodeReads),
StateReadMs: durationToMs(s.AccountReads + s.StorageReads + s.CodeReads + s.Prefetch),
StateHashMs: durationToMs(s.AccountHashes + s.AccountUpdates + s.StorageUpdates),
CommitMs: durationToMs(max(s.AccountCommits, s.StorageCommits) + s.DatabaseCommit + s.BlockWrite),
TotalMs: durationToMs(s.TotalTime),
Expand Down Expand Up @@ -304,8 +303,8 @@ func (s *ExecuteStats) logSlow(block *types.Block, slowBlockThreshold time.Durat
}

func (s *ExecuteStats) reportBALMetrics() {
accountCommitTimer.Update(s.AccountCommits) // Account commits are complete, we can mark them
storageCommitTimer.Update(s.StorageCommits) // Storage commits are complete, we can mark them
accountCommitTimer.Update(s.AccountCommits)
storageCommitTimer.Update(s.StorageCommits)

if m := s.balTransitionStats; m != nil {
stateTriePrefetchTimer.Update(m.StatePrefetch)
Expand Down
9 changes: 3 additions & 6 deletions core/parallel_state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,13 @@ import (
"golang.org/x/sync/errgroup"
)

// ProcessResultWithMetrics wraps ProcessResult with some metrics that are
// emitted when executing blocks containing access lists.
// ProcessResultWithMetrics wraps ProcessResult with timing breakdown for BAL block processing.
type ProcessResultWithMetrics struct {
ProcessResult *ProcessResult
PreProcessTime time.Duration
StateTransitionMetrics *state.BALStateTransitionMetrics
// the time it took to execute all txs in the block
ExecTime time.Duration
PostProcessTime time.Duration
// TODO: have the prefetch metric in here as well?
ExecTime time.Duration
PostProcessTime time.Duration
}

// ParallelStateProcessor is used to execute and verify blocks containing
Expand Down
41 changes: 32 additions & 9 deletions core/state/bal_state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package state
import (
"maps"
"sync"
"sync/atomic"
"time"

"github.com/ethereum/go-ethereum/common"
Expand All @@ -20,6 +21,7 @@ import (
// this object is only used for a single block.
type BALStateTransition struct {
accessList bal.AccessListReader
written bal.WrittenCounts
db Database
reader Reader
stateTrie Trie
Expand All @@ -40,6 +42,11 @@ type BALStateTransition struct {
tries sync.Map //map[common.Address]Trie
deletions map[common.Address]struct{}

// Deletion counters; not derivable from the BAL alone (selfdestruct vs
// balance/nonce reset is indistinguishable without prestate).
accountDeleted int
storageDeleted atomic.Int64

stateUpdate *stateUpdate

metrics BALStateTransitionMetrics
Expand All @@ -52,6 +59,19 @@ func (s *BALStateTransition) Metrics() *BALStateTransitionMetrics {
return &s.metrics
}

// DeletionCounts holds per-block deletion counters from the parallel root-pass.
type DeletionCounts struct {
Accounts int
Storage int
}

func (s *BALStateTransition) Deletions() DeletionCounts {
return DeletionCounts{
Accounts: s.accountDeleted,
Storage: int(s.storageDeleted.Load()),
}
}

type BALStateTransitionMetrics struct {
// trie hashing metrics
AccountUpdate time.Duration
Expand All @@ -75,6 +95,7 @@ func NewBALStateTransition(block *types.Block, prefetchReader Reader, db Databas

return &BALStateTransition{
accessList: bal.NewAccessListReader(*block.AccessList()),
written: block.AccessList().WrittenCounts(),
db: db,
reader: prefetchReader,
stateTrie: stateTrie,
Expand All @@ -90,6 +111,11 @@ func NewBALStateTransition(block *types.Block, prefetchReader Reader, db Databas
}, nil
}

// WrittenCounts returns the cached BAL write counts (computed once per block).
func (s *BALStateTransition) WrittenCounts() bal.WrittenCounts {
return s.written
}

func (s *BALStateTransition) Error() error {
return s.err
}
Expand Down Expand Up @@ -334,15 +360,11 @@ func (s *BALStateTransition) CommitWithUpdate(block uint64, deleteEmptyObjects b
return common.Hash{}, nil, err
}

/*
TODO: derive these from the BAL
^ I think even then, there is a semantic difference with how these metrics were calculated previously
I don't know if it makes sense to recompute those, or just derive new ones from the BAL
accountUpdatedMeter.Mark(int64(s.accountUpdated))
storageUpdatedMeter.Mark(s.storageUpdated.Load())
accountDeletedMeter.Mark(int64(s.accountDeleted))
storageDeletedMeter.Mark(s.storageDeleted.Load())
*/
storageDeleted := s.storageDeleted.Load()
accountUpdatedMeter.Mark(int64(s.written.Accounts - s.accountDeleted))
storageUpdatedMeter.Mark(int64(s.written.StorageSlots) - storageDeleted)
accountDeletedMeter.Mark(int64(s.accountDeleted))
storageDeletedMeter.Mark(storageDeleted)
accountTrieUpdatedMeter.Mark(int64(accountTrieNodesUpdated))
accountTrieDeletedMeter.Mark(int64(accountTrieNodesDeleted))
storageTriesUpdatedMeter.Mark(int64(storageTrieNodesUpdated))
Expand Down Expand Up @@ -477,6 +499,7 @@ func (s *BALStateTransition) IntermediateRoot(_ bool) common.Hash {
return common.Hash{}
}
s.deletions[mutatedAddr] = struct{}{}
s.accountDeleted++
} else {
acct, code := s.updateAccount(mutatedAddr)

Expand Down
34 changes: 32 additions & 2 deletions core/state/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ package state

import (
"errors"
"sync"
"sync/atomic"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/overlay"
"github.com/ethereum/go-ethereum/core/types"
Expand All @@ -28,8 +31,6 @@ import (
"github.com/ethereum/go-ethereum/trie/transitiontrie"
"github.com/ethereum/go-ethereum/triedb"
"github.com/ethereum/go-ethereum/triedb/database"
"sync"
"sync/atomic"
)

// ContractCodeReader defines the interface for accessing contract code.
Expand Down Expand Up @@ -530,6 +531,8 @@ type reader struct {
ContractCodeReader
StateReader
PrefetcherMetricer

codeLoaded sync.Map // common.Address → int (first-seen len(code))
}

// newReader constructs a reader with the supplied code reader and state reader.
Expand All @@ -548,6 +551,33 @@ func newReaderWithPrefetch(codeReader ContractCodeReader, stateReader StateReade
}
}

func (r *reader) Code(addr common.Address, codeHash common.Hash) []byte {
code := r.ContractCodeReader.Code(addr, codeHash)
if len(code) > 0 {
r.codeLoaded.LoadOrStore(addr, len(code))
}
return code
}

func (r *reader) CodeSize(addr common.Address, codeHash common.Hash) (int, error) {
size, err := r.ContractCodeReader.CodeSize(addr, codeHash)
if err == nil && size > 0 {
r.codeLoaded.LoadOrStore(addr, size)
}
return size, err
}

// CodeLoads returns the count of unique contracts whose code was fetched and
// the sum of their first-seen byte lengths. Call after Reader use has quiesced.
func (r *reader) CodeLoads() (count, bytes int) {
r.codeLoaded.Range(func(_, v any) bool {
count++
bytes += v.(int)
return true
})
return
}
Comment thread
jwasinger marked this conversation as resolved.

// GetCodeStats returns the statistics of code access.
func (r *reader) GetCodeStats() ContractCodeReaderStats {
if stater, ok := r.ContractCodeReader.(ContractCodeReaderStater); ok {
Expand Down
13 changes: 13 additions & 0 deletions core/state/reader_eip_7928.go
Original file line number Diff line number Diff line change
Expand Up @@ -382,3 +382,16 @@ func (r *readerTracker) TouchStorage(addr common.Address, slot common.Hash) {
}
list[slot] = struct{}{}
}

func (r *readerTracker) CodeLoads() (count, bytes int) {
return r.Reader.(CodeLoadTracker).CodeLoads()
}

// GetStateStats forwards stats from the wrapped reader; without this, BAL
// blocks would emit zero cache hit/miss counts.
func (r *prefetchStateReader) GetStateStats() StateReaderStats {
if stater, ok := r.StateReader.(StateReaderStater); ok {
return stater.GetStateStats()
}
return StateReaderStats{}
}
28 changes: 28 additions & 0 deletions core/state/reader_eip_7928_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,3 +263,31 @@ func TestTrackerSurvivesStateDBCache(t *testing.T) {
t.Fatal("slot must be tracked on cache hit (storage)")
}
}

// TestPrefetchStateReaderForwardsStats locks down that prefetchStateReader
// exposes the underlying stateReaderWithStats counters via GetStateStats.
func TestPrefetchStateReaderForwardsStats(t *testing.T) {
stub := newRefStateReader()
addr := testrand.Address()

cached := newStateReaderWithCache(stub)
withStats := newStateReaderWithStats(cached)
prefetch := newPrefetchStateReaderInternal(withStats, nil, 1)

if _, err := prefetch.Account(addr); err != nil {
t.Fatalf("Account: %v", err)
}
if _, err := prefetch.Account(addr); err != nil {
t.Fatalf("Account (second): %v", err)
}

stats := withStats.GetStateStats()
if stats.AccountCacheHit == 0 || stats.AccountCacheMiss == 0 {
t.Fatalf("inner stats not populated: %+v", stats)
}
gotStats := prefetch.GetStateStats()
if gotStats != stats {
t.Fatalf("forward mismatch: got %+v, want %+v", gotStats, stats)
}
}

5 changes: 5 additions & 0 deletions core/state/reader_stater.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@

package state

// CodeLoadTracker exposes a Reader's deduplicated code-load count and bytes.
type CodeLoadTracker interface {
CodeLoads() (count, bytes int)
}

// ContractCodeReaderStats aggregates statistics for the contract code reader.
type ContractCodeReaderStats struct {
CacheHit int64 // Number of cache hits
Expand Down
Loading
Loading