cmd,core,tests: introduce new BAL execution flags, log BAL slow blocks, surface more metrics including prefetcher time (#34892)

Adapts some of the changes from
https://github.com/ethereum/go-ethereum/pull/34861 . Some other metrics
which are recorded manually during execution in that PR, but can be
deduced from the BAL are TBD.

I've added two bal feature flags:

* `--bal.prefetchworkers <uint>`: this tunes the number of concurrent
go-routines that will be used to perform state fetching tasks by the BAL
prefetcher. Default is `runtime.NumCPUs`, the current behavior in
`bal-devnet-3`.
* `--bal.blockingprefetch`: If set, state prefetching will block the
execution of transactions and state root update.

---------

Co-authored-by: CPerezz <cperezz19@pm.me>
This commit is contained in:
jwasinger 2026-05-06 15:39:52 -04:00 committed by GitHub
parent 2bb95a19a4
commit 697fe91750
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 166 additions and 107 deletions

View file

@ -159,6 +159,8 @@ var (
utils.BeaconCheckpointFlag, utils.BeaconCheckpointFlag,
utils.BeaconCheckpointFileFlag, utils.BeaconCheckpointFileFlag,
utils.LogSlowBlockFlag, utils.LogSlowBlockFlag,
utils.PrefetchWorkersFlag,
utils.BlockingPrefetch,
}, utils.NetworkFlags, utils.DatabaseFlags) }, utils.NetworkFlags, utils.DatabaseFlags)
rpcFlags = []cli.Flag{ rpcFlags = []cli.Flag{

View file

@ -28,6 +28,7 @@ import (
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
godebug "runtime/debug" godebug "runtime/debug"
"strconv" "strconv"
"strings" "strings"
@ -713,6 +714,19 @@ var (
Category: flags.MiscCategory, Category: flags.MiscCategory,
} }
PrefetchWorkersFlag = &cli.UintFlag{
Name: "bal.prefetchworkers",
Usage: "The number of concurrent state loading tasks to perform when prefetching BAL state. Default to the number of cpus",
Value: uint(runtime.NumCPU()),
Category: flags.MiscCategory,
}
BlockingPrefetch = &cli.BoolFlag{
Name: "bal.blockingprefetch",
Usage: "only relevant when executing in parallel with a BAL: if true, the prefetcher will block tx/state-root calculation until all scheduled fetching tasks have completed.",
Category: flags.MiscCategory,
}
// RPC settings // RPC settings
IPCDisabledFlag = &cli.BoolFlag{ IPCDisabledFlag = &cli.BoolFlag{
Name: "ipcdisable", Name: "ipcdisable",
@ -2459,6 +2473,8 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
TrienodeHistory: ctx.Int64(TrienodeHistoryFlag.Name), TrienodeHistory: ctx.Int64(TrienodeHistoryFlag.Name),
NodeFullValueCheckpoint: uint32(ctx.Uint(TrienodeHistoryFullValueCheckpointFlag.Name)), NodeFullValueCheckpoint: uint32(ctx.Uint(TrienodeHistoryFullValueCheckpointFlag.Name)),
PrefetchWorkers: int(ctx.Uint(PrefetchWorkersFlag.Name)),
BlockingPrefetch: ctx.Bool(BlockingPrefetch.Name),
// Disable transaction indexing/unindexing. // Disable transaction indexing/unindexing.
TxLookupLimit: -1, TxLookupLimit: -1,

View file

@ -211,6 +211,10 @@ type BlockChainConfig struct {
Overrides *ChainOverrides // Optional chain config overrides Overrides *ChainOverrides // Optional chain config overrides
VmConfig vm.Config // Config options for the EVM Interpreter VmConfig vm.Config // Config options for the EVM Interpreter
// BAL-related
PrefetchWorkers int // number of concurrent go-routines for BAL state prefetching
BlockingPrefetch bool // whether the prefetch should block further execution until it finishes
// TxLookupLimit specifies the maximum number of blocks from head for which // TxLookupLimit specifies the maximum number of blocks from head for which
// transaction hashes will be indexed. // transaction hashes will be indexed.
// //
@ -597,7 +601,7 @@ func (bc *BlockChain) processBlockWithAccessList(parentRoot common.Hash, block *
useAsyncReads := bc.cfg.BALExecutionMode != bal.BALExecutionNoBatchIO useAsyncReads := bc.cfg.BALExecutionMode != bal.BALExecutionNoBatchIO
al := block.AccessList() // TODO: make the return of this method not be a pointer al := block.AccessList() // TODO: make the return of this method not be a pointer
accessListReader := bal.NewAccessListReader(*al) accessListReader := bal.NewAccessListReader(*al)
prefetchReader, err := sdb.ReaderEIP7928(parentRoot, accessListReader.StorageKeys(useAsyncReads), runtime.NumCPU()) prefetchReader, err := sdb.ReaderEIP7928(parentRoot, accessListReader.StorageKeys(useAsyncReads), bc.cfg.PrefetchWorkers, bc.cfg.BlockingPrefetch)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -652,34 +656,22 @@ func (bc *BlockChain) processBlockWithAccessList(parentRoot common.Hash, block *
writeTime := time.Since(writeStart) writeTime := time.Since(writeStart)
var stats ExecuteStats var stats ExecuteStats
/* stats.ExecWall = res.ExecTime
// TODO: implement the gathering of this data stats.PostProcess = res.PostProcessTime
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 if m := res.StateTransitionMetrics; m != nil {
stats.AccountUpdated = statedb.AccountUpdated stats.AccountHashes = m.AccountUpdate + m.StateUpdate + m.StateHash
stats.AccountDeleted = statedb.AccountDeleted stats.AccountCommits = m.AccountCommits
stats.StorageLoaded = statedb.StorageLoaded stats.StorageCommits = m.StorageCommits
stats.StorageUpdated = int(statedb.StorageUpdated.Load()) stats.DatabaseCommit = m.TrieDBCommits
stats.StorageDeleted = int(statedb.StorageDeleted.Load()) stats.Prefetch = m.StatePrefetch
stats.CodeLoaded = statedb.CodeLoaded }
stats.CodeLoadBytes = statedb.CodeLoadBytes
stats.Execution = ptime - (statedb.AccountReads + statedb.StorageReads + statedb.CodeReads) // The time spent on EVM processing stats.Prefetch = prefetchReader.(state.PrefetcherMetricer).Metrics().Elapsed
stats.Validation = vtime - (statedb.AccountHashes + statedb.AccountUpdates + statedb.StorageUpdates) // The time spent on block validation
*/
// Update the metrics touched during block commit if r, ok := prefetchReader.(state.ReaderStater); ok {
stats.AccountCommits = stateTransition.Metrics().AccountCommits stats.StateReadCacheStats = r.GetStats()
stats.StorageCommits = stateTransition.Metrics().StorageCommits }
// stats.StateReadCacheStats = whichReader.GetStats()
// ^ TODO fix this
elapsed := time.Since(startTime) + 1 // prevent zero division elapsed := time.Since(startTime) + 1 // prevent zero division
stats.TotalTime = elapsed stats.TotalTime = elapsed

View file

@ -38,6 +38,7 @@ 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
// TODO: code bytes loaded
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
@ -59,6 +60,11 @@ type ExecuteStats struct {
TotalTime time.Duration // The total time spent on block execution TotalTime time.Duration // The total time spent on block execution
MgasPerSecond float64 // The million gas processed per second MgasPerSecond float64 // The million gas processed per second
// BAL parallel-path durations, surfaced under slowBlockLog.BAL.
ExecWall time.Duration // Wall-clock parallel transaction execution
PostProcess time.Duration // Post-tx finalization (system contracts, requests)
Prefetch time.Duration // BAL state prefetching
// Cache hit rates // Cache hit rates
StateReadCacheStats state.ReaderStats StateReadCacheStats state.ReaderStats
StatePrefetchCacheStats state.ReaderStats StatePrefetchCacheStats state.ReaderStats
@ -120,6 +126,8 @@ type slowBlockLog struct {
StateReads slowBlockReads `json:"state_reads"` StateReads slowBlockReads `json:"state_reads"`
StateWrites slowBlockWrites `json:"state_writes"` StateWrites slowBlockWrites `json:"state_writes"`
Cache slowBlockCache `json:"cache"` Cache slowBlockCache `json:"cache"`
// BAL is set only for blocks processed via the parallel BAL path.
BAL *slowBlockBAL `json:"bal,omitempty"`
} }
type slowBlockInfo struct { type slowBlockInfo struct {
@ -180,24 +188,30 @@ type slowBlockCodeCacheEntry struct {
MissBytes int64 `json:"miss_bytes"` MissBytes int64 `json:"miss_bytes"`
} }
// slowBlockBAL holds parallel-execution timings that don't fit the sequential schema.
type slowBlockBAL struct {
ExecWallMs float64 `json:"exec_wall_ms"`
PostProcessMs float64 `json:"post_process_ms"`
PrefetchMs float64 `json:"prefetch_ms"`
StatePrefetchMs float64 `json:"state_prefetch_ms"`
AccountUpdateMs float64 `json:"account_update_ms"`
StateUpdateMs float64 `json:"state_update_ms"`
StateHashMs float64 `json:"state_hash_ms"`
AccountCommitMs float64 `json:"account_commit_ms"`
StorageCommitMs float64 `json:"storage_commit_ms"`
TrieDBCommitMs float64 `json:"triedb_commit_ms"`
SnapshotCommitMs float64 `json:"snapshot_commit_ms"`
}
// durationToMs converts a time.Duration to milliseconds as a float64 // durationToMs converts a time.Duration to milliseconds as a float64
// with sub-millisecond precision for accurate cross-client metrics. // with sub-millisecond precision for accurate cross-client metrics.
func durationToMs(d time.Duration) float64 { func durationToMs(d time.Duration) float64 {
return float64(d.Nanoseconds()) / 1e6 return float64(d.Nanoseconds()) / 1e6
} }
// logSlow prints the detailed execution statistics in JSON format if the block // buildSlowBlockLog builds the slow-block JSON payload. Split out from logSlow
// is regarded as slow. The JSON format is designed for cross-client compatibility // so the JSON shape is directly testable.
// with other Ethereum execution clients. func buildSlowBlockLog(s *ExecuteStats, block *types.Block) slowBlockLog {
func (s *ExecuteStats) logSlow(block *types.Block, slowBlockThreshold time.Duration) {
// Negative threshold means disabled (default when flag not set)
if slowBlockThreshold < 0 {
return
}
// Threshold of 0 logs all blocks; positive threshold filters
if slowBlockThreshold > 0 && s.TotalTime < slowBlockThreshold {
return
}
logEntry := slowBlockLog{ logEntry := slowBlockLog{
Level: "warn", Level: "warn",
Msg: "Slow block", Msg: "Slow block",
@ -226,8 +240,8 @@ func (s *ExecuteStats) logSlow(block *types.Block, slowBlockThreshold time.Durat
StateWrites: slowBlockWrites{ StateWrites: slowBlockWrites{
Accounts: s.AccountUpdated, Accounts: s.AccountUpdated,
AccountsDeleted: s.AccountDeleted, AccountsDeleted: s.AccountDeleted,
StorageSlots: s.StorageUpdated, StorageSlots: int(s.StorageUpdated),
StorageSlotsDeleted: s.StorageDeleted, StorageSlotsDeleted: int(s.StorageDeleted),
Code: s.CodeUpdated, Code: s.CodeUpdated,
CodeBytes: s.CodeUpdateBytes, CodeBytes: s.CodeUpdateBytes,
}, },
@ -251,7 +265,37 @@ func (s *ExecuteStats) logSlow(block *types.Block, slowBlockThreshold time.Durat
}, },
}, },
} }
jsonBytes, err := json.Marshal(logEntry) if m := s.balTransitionStats; m != nil {
logEntry.BAL = &slowBlockBAL{
ExecWallMs: durationToMs(s.ExecWall),
PostProcessMs: durationToMs(s.PostProcess),
PrefetchMs: durationToMs(s.Prefetch),
StatePrefetchMs: durationToMs(m.StatePrefetch),
AccountUpdateMs: durationToMs(m.AccountUpdate),
StateUpdateMs: durationToMs(m.StateUpdate),
StateHashMs: durationToMs(m.StateHash),
AccountCommitMs: durationToMs(m.AccountCommits),
StorageCommitMs: durationToMs(m.StorageCommits),
TrieDBCommitMs: durationToMs(m.TrieDBCommits),
SnapshotCommitMs: durationToMs(m.SnapshotCommits),
}
}
return logEntry
}
// logSlow prints the detailed execution statistics in JSON format if the block
// is regarded as slow. The JSON format is designed for cross-client compatibility
// with other Ethereum execution clients.
func (s *ExecuteStats) logSlow(block *types.Block, slowBlockThreshold time.Duration) {
// Negative threshold means disabled (default when flag not set)
if slowBlockThreshold < 0 {
return
}
// Threshold of 0 logs all blocks; positive threshold filters
if slowBlockThreshold > 0 && s.TotalTime < slowBlockThreshold {
return
}
jsonBytes, err := json.Marshal(buildSlowBlockLog(s, block))
if err != nil { if err != nil {
log.Error("Failed to marshal slow block log", "error", err) log.Error("Failed to marshal slow block log", "error", err)
return return
@ -260,40 +304,16 @@ func (s *ExecuteStats) logSlow(block *types.Block, slowBlockThreshold time.Durat
} }
func (s *ExecuteStats) reportBALMetrics() { func (s *ExecuteStats) reportBALMetrics() {
/*
if s.AccountLoaded != 0 {
accountReadTimer.Update(s.AccountReads)
accountReadSingleTimer.Update(s.AccountReads / time.Duration(s.AccountLoaded))
}
if s.StorageLoaded != 0 {
storageReadTimer.Update(s.StorageReads)
storageReadSingleTimer.Update(s.StorageReads / time.Duration(s.StorageLoaded))
}
if s.CodeLoaded != 0 {
codeReadTimer.Update(s.CodeReads)
codeReadSingleTimer.Update(s.CodeReads / time.Duration(s.CodeLoaded))
codeReadBytesTimer.Update(time.Duration(s.CodeLoadBytes))
}
// TODO: implement these ^
*/
//accountUpdateTimer.Update(s.AccountUpdates) // Account updates are complete(in validation)
//storageUpdateTimer.Update(s.StorageUpdates) // Storage updates are complete(in validation)
//accountHashTimer.Update(s.AccountHashes) // Account hashes are complete(in validation)
accountCommitTimer.Update(s.AccountCommits) // Account commits are complete, we can mark them accountCommitTimer.Update(s.AccountCommits) // Account commits are complete, we can mark them
storageCommitTimer.Update(s.StorageCommits) // Storage commits are complete, we can mark them storageCommitTimer.Update(s.StorageCommits) // Storage commits are complete, we can mark them
stateTriePrefetchTimer.Update(s.balTransitionStats.StatePrefetch) if m := s.balTransitionStats; m != nil {
accountTriesUpdateTimer.Update(s.balTransitionStats.AccountUpdate) stateTriePrefetchTimer.Update(m.StatePrefetch)
stateTrieUpdateTimer.Update(s.balTransitionStats.StateUpdate) accountTriesUpdateTimer.Update(m.AccountUpdate)
stateTrieHashTimer.Update(s.balTransitionStats.StateHash) stateTrieUpdateTimer.Update(m.StateUpdate)
stateRootComputeTimer.Update(s.balTransitionStats.AccountUpdate + s.balTransitionStats.StateUpdate + s.balTransitionStats.StateHash) stateTrieHashTimer.Update(m.StateHash)
stateRootComputeTimer.Update(m.AccountUpdate + m.StateUpdate + m.StateHash)
//blockExecutionTimer.Update(s.Execution) // The time spent on EVM processing }
// ^basically impossible to get this metric with parallel execution
//blockValidationTimer.Update(s.Validation) // The time spent on block validation
//blockCrossValidationTimer.Update(s.CrossValidation) // The time spent on stateless cross validation
blockWriteTimer.Update(s.BlockWrite) // The time spent on block write blockWriteTimer.Update(s.BlockWrite) // The time spent on block write
blockInsertTimer.Update(s.TotalTime) // The total time spent on block execution blockInsertTimer.Update(s.TotalTime) // The total time spent on block execution

View file

@ -23,6 +23,7 @@ type ProcessResultWithMetrics struct {
// the time it took to execute all txs in the block // the time it took to execute all txs in the block
ExecTime time.Duration ExecTime time.Duration
PostProcessTime time.Duration PostProcessTime time.Duration
// TODO: have the prefetch metric in here as well?
} }
// ParallelStateProcessor is used to execute and verify blocks containing // ParallelStateProcessor is used to execute and verify blocks containing
@ -198,15 +199,14 @@ type txExecResult struct {
// resultHandler polls until all transactions have finished executing and the // resultHandler polls until all transactions have finished executing and the
// state root calculation is complete. The result is emitted on resCh. // state root calculation is complete. The result is emitted on resCh.
func (p *ParallelStateProcessor) resultHandler(block *types.Block, preTxReads bal.StateAccesses, statedb *state.StateDB, prefetchReader state.Reader, tExecStart time.Time, txResCh <-chan txExecResult, stateRootCalcResCh <-chan stateRootCalculationResult, resCh chan *ProcessResultWithMetrics) { func (p *ParallelStateProcessor) resultHandler(block *types.Block, preTxAccesses bal.StateAccesses, statedb *state.StateDB, prefetchReader state.Reader, tExecStart time.Time, txResCh <-chan txExecResult, stateRootCalcResCh <-chan stateRootCalculationResult, resCh chan *ProcessResultWithMetrics) {
// 1. if the block has transactions, receive the execution results from all of them and return an error on resCh if any txs err'd // 1. if the block has transactions, receive the execution results from all of them and return an error on resCh if any txs err'd
// 2. once all txs are executed, compute the post-tx state transition and produce the ProcessResult sending it on resCh (or an error if the post-tx state didn't match what is reported in the BAL) // 2. once all txs are executed, compute the post-tx state transition and produce the ProcessResult sending it on resCh (or an error if the post-tx state didn't match what is reported in the BAL)
var results []txExecResult var results []txExecResult
var cumulativeStateGas, cumulativeRegularGas uint64 var cumulativeStateGas, cumulativeRegularGas uint64
var execErr error var execErr error
var numTxComplete int var numTxComplete int
accesses := preTxAccesses
accesses := preTxReads
if len(block.Transactions()) > 0 { if len(block.Transactions()) > 0 {
loop: loop:
@ -361,7 +361,7 @@ func (p *ParallelStateProcessor) Process(block *types.Block, stateTransition *st
) )
startingState := statedb.Copy() startingState := statedb.Copy()
preReads, err := p.processBlockPreTx(block, statedb, balReader, cfg) preTxReads, err := p.processBlockPreTx(block, statedb, balReader, cfg)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -371,7 +371,7 @@ func (p *ParallelStateProcessor) Process(block *types.Block, stateTransition *st
// execute transactions and state root calculation in parallel // execute transactions and state root calculation in parallel
tExecStart = time.Now() tExecStart = time.Now()
go p.resultHandler(block, preReads, statedb, balReader, tExecStart, txResCh, rootCalcResultCh, resCh) go p.resultHandler(block, preTxReads, statedb, balReader, tExecStart, txResCh, rootCalcResultCh, resCh)
var workers errgroup.Group var workers errgroup.Group
workers.SetLimit(runtime.NumCPU()) workers.SetLimit(runtime.NumCPU())
for i, t := range block.Transactions() { for i, t := range block.Transactions() {

View file

@ -3,7 +3,6 @@ package state
import ( import (
"maps" "maps"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -41,11 +40,6 @@ type BALStateTransition struct {
tries sync.Map //map[common.Address]Trie tries sync.Map //map[common.Address]Trie
deletions map[common.Address]struct{} deletions map[common.Address]struct{}
accountDeleted int64
accountUpdated int64
storageDeleted atomic.Int64
storageUpdated atomic.Int64
stateUpdate *stateUpdate stateUpdate *stateUpdate
metrics BALStateTransitionMetrics metrics BALStateTransitionMetrics
@ -60,11 +54,10 @@ func (s *BALStateTransition) Metrics() *BALStateTransitionMetrics {
type BALStateTransitionMetrics struct { type BALStateTransitionMetrics struct {
// trie hashing metrics // trie hashing metrics
AccountUpdate time.Duration AccountUpdate time.Duration
StatePrefetch time.Duration StatePrefetch time.Duration
StateUpdate time.Duration StateUpdate time.Duration
StateHash time.Duration StateHash time.Duration
OriginStorageLoadTime time.Duration
// commit metrics // commit metrics
AccountCommits time.Duration AccountCommits time.Duration
@ -341,10 +334,15 @@ func (s *BALStateTransition) CommitWithUpdate(block uint64, deleteEmptyObjects b
return common.Hash{}, nil, err return common.Hash{}, nil, err
} }
accountUpdatedMeter.Mark(s.accountUpdated) /*
storageUpdatedMeter.Mark(s.storageUpdated.Load()) TODO: derive these from the BAL
accountDeletedMeter.Mark(s.accountDeleted) ^ I think even then, there is a semantic difference with how these metrics were calculated previously
storageDeletedMeter.Mark(s.storageDeleted.Load()) 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())
*/
accountTrieUpdatedMeter.Mark(int64(accountTrieNodesUpdated)) accountTrieUpdatedMeter.Mark(int64(accountTrieNodesUpdated))
accountTrieDeletedMeter.Mark(int64(accountTrieNodesDeleted)) accountTrieDeletedMeter.Mark(int64(accountTrieNodesDeleted))
storageTriesUpdatedMeter.Mark(int64(storageTrieNodesUpdated)) storageTriesUpdatedMeter.Mark(int64(storageTrieNodesUpdated))
@ -424,12 +422,8 @@ func (s *BALStateTransition) IntermediateRoot(_ bool) common.Hash {
if val != (common.Hash{}) { if val != (common.Hash{}) {
updateKeys = append(updateKeys, key[:]) updateKeys = append(updateKeys, key[:])
updateValues = append(updateValues, common.TrimLeftZeroes(val[:])) updateValues = append(updateValues, common.TrimLeftZeroes(val[:]))
s.storageUpdated.Add(1)
} else { } else {
deleteKeys = append(deleteKeys, key[:]) deleteKeys = append(deleteKeys, key[:])
s.storageDeleted.Add(1)
} }
} }
if err := tr.UpdateStorageBatch(address, updateKeys, updateValues); err != nil { if err := tr.UpdateStorageBatch(address, updateKeys, updateValues); err != nil {

View file

@ -18,7 +18,6 @@ package state
import ( import (
"fmt" "fmt"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/overlay" "github.com/ethereum/go-ethereum/core/overlay"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
@ -241,7 +240,7 @@ func (db *CachingDB) ReadersWithCacheStats(stateRoot common.Hash) (Reader, Reade
} }
// ReaderEIP7928 creates a state reader with the manner of Block-level accessList. // ReaderEIP7928 creates a state reader with the manner of Block-level accessList.
func (db *CachingDB) ReaderEIP7928(stateRoot common.Hash, accessList map[common.Address][]common.Hash, threads int) (Reader, error) { func (db *CachingDB) ReaderEIP7928(stateRoot common.Hash, accessList map[common.Address][]common.Hash, threads int, block bool) (Reader, error) {
base, err := db.StateReader(stateRoot) base, err := db.StateReader(stateRoot)
if err != nil { if err != nil {
return nil, err return nil, err
@ -251,8 +250,13 @@ func (db *CachingDB) ReaderEIP7928(stateRoot common.Hash, accessList map[common.
// Construct the state reader with background prefetching // Construct the state reader with background prefetching
pr := newPrefetchStateReader(r, accessList, threads) pr := newPrefetchStateReader(r, accessList, threads)
if block {
if err := pr.Wait(); err != nil {
panic("unreachable")
}
}
return newReader(db.codedb.Reader(), pr), nil return newReaderWithPrefetch(db.codedb.Reader(), pr, pr), nil
} }
// OpenTrie opens the main account trie at a specific root hash. // OpenTrie opens the main account trie at a specific root hash.

View file

@ -18,9 +18,6 @@ package state
import ( import (
"errors" "errors"
"sync"
"sync/atomic"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/overlay" "github.com/ethereum/go-ethereum/core/overlay"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
@ -31,6 +28,8 @@ import (
"github.com/ethereum/go-ethereum/trie/transitiontrie" "github.com/ethereum/go-ethereum/trie/transitiontrie"
"github.com/ethereum/go-ethereum/triedb" "github.com/ethereum/go-ethereum/triedb"
"github.com/ethereum/go-ethereum/triedb/database" "github.com/ethereum/go-ethereum/triedb/database"
"sync"
"sync/atomic"
) )
// ContractCodeReader defines the interface for accessing contract code. // ContractCodeReader defines the interface for accessing contract code.
@ -530,6 +529,7 @@ func (r *stateReaderWithStats) GetStateStats() StateReaderStats {
type reader struct { type reader struct {
ContractCodeReader ContractCodeReader
StateReader StateReader
PrefetcherMetricer
} }
// newReader constructs a reader with the supplied code reader and state reader. // newReader constructs a reader with the supplied code reader and state reader.
@ -540,6 +540,14 @@ func newReader(codeReader ContractCodeReader, stateReader StateReader) *reader {
} }
} }
func newReaderWithPrefetch(codeReader ContractCodeReader, stateReader StateReader, metricer PrefetcherMetricer) *reader {
return &reader{
ContractCodeReader: codeReader,
StateReader: stateReader,
PrefetcherMetricer: metricer,
}
}
// GetCodeStats returns the statistics of code access. // GetCodeStats returns the statistics of code access.
func (r *reader) GetCodeStats() ContractCodeReaderStats { func (r *reader) GetCodeStats() ContractCodeReaderStats {
if stater, ok := r.ContractCodeReader.(ContractCodeReaderStater); ok { if stater, ok := r.ContractCodeReader.(ContractCodeReaderStater); ok {

View file

@ -64,6 +64,7 @@ package state
import ( import (
"sync" "sync"
"time"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
@ -86,6 +87,17 @@ type prefetchStateReader struct {
done chan struct{} done chan struct{}
term chan struct{} term chan struct{}
closeOnce sync.Once closeOnce sync.Once
start time.Time
metrics PrefetchMetrics
}
type PrefetchMetrics struct {
// the total amount of time it took to complete the scheduled workload
Elapsed time.Duration
}
type PrefetcherMetricer interface {
Metrics() PrefetchMetrics
} }
func newPrefetchStateReader(reader StateReader, accessList bal.StorageKeys, nThreads int) *prefetchStateReader { func newPrefetchStateReader(reader StateReader, accessList bal.StorageKeys, nThreads int) *prefetchStateReader {
@ -106,11 +118,17 @@ func newPrefetchStateReaderInternal(reader StateReader, tasks []*fetchTask, nThr
nThreads: nThreads, nThreads: nThreads,
done: make(chan struct{}), done: make(chan struct{}),
term: make(chan struct{}), term: make(chan struct{}),
start: time.Now(),
} }
go r.prefetch() go r.prefetch()
return r return r
} }
func (r *prefetchStateReader) Metrics() PrefetchMetrics {
// TODO (jwasinger) actually implement this
return PrefetchMetrics{}
}
func (r *prefetchStateReader) Close() { func (r *prefetchStateReader) Close() {
r.closeOnce.Do(func() { r.closeOnce.Do(func() {
close(r.term) close(r.term)
@ -128,7 +146,10 @@ func (r *prefetchStateReader) Wait() error {
} }
func (r *prefetchStateReader) prefetch() { func (r *prefetchStateReader) prefetch() {
defer close(r.done) defer func() {
r.metrics = PrefetchMetrics{time.Since(r.start)}
close(r.done)
}()
if len(r.tasks) == 0 { if len(r.tasks) == 0 {
return return

View file

@ -209,10 +209,10 @@ func TestReaderWithTracker(t *testing.T) {
// transactions read without hitting the reader, causing the BAL to be incomplete. // transactions read without hitting the reader, causing the BAL to be incomplete.
func TestTrackerSurvivesStateDBCache(t *testing.T) { func TestTrackerSurvivesStateDBCache(t *testing.T) {
var ( var (
sdb = NewDatabaseForTesting() sdb = NewDatabaseForTesting()
statedb, _ = New(types.EmptyRootHash, sdb) statedb, _ = New(types.EmptyRootHash, sdb)
addr = common.HexToAddress("0xaaaa") addr = common.HexToAddress("0xaaaa")
slot = common.HexToHash("0x01") slot = common.HexToHash("0x01")
) )
// Set up committed state with one account that has a storage slot. // Set up committed state with one account that has a storage slot.
statedb.SetBalance(addr, uint256.NewInt(1e18), tracing.BalanceChangeUnspecified) statedb.SetBalance(addr, uint256.NewInt(1e18), tracing.BalanceChangeUnspecified)

View file

@ -162,6 +162,8 @@ func (t *BlockTest) createTestBlockChain(config *params.ChainConfig, snapshotter
}, },
StatelessSelfValidation: witness, StatelessSelfValidation: witness,
NoPrefetch: true, NoPrefetch: true,
BlockingPrefetch: true,
PrefetchWorkers: 100, // note: this is totally unrelated to NoPrefetch, just for BAL execution
} }
if snapshotter { if snapshotter {
options.SnapshotLimit = 1 options.SnapshotLimit = 1