mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-24 08:49:29 +00:00
core, core/state: address slow-block review feedback
- Pre-BAL path keeps StateDB synchronous read-time accumulators (AccountReads, StorageReads, CodeReads, CodeLoaded, CodeLoadBytes) so Execution = ptime - reads stays well-formed under single-thread execution. - BAL path drops aggregate reader read-times; under parallel workers they sum across goroutines and aren't a wall-clock proxy. - Delete dead PrefetchReadTimes/WaitPrefetch forwarders on *reader and the now-unused ReadTimer/ReadDurations scaffolding. - Add regression test for EIP-7702 delegation clear: empty []byte code in the BAL must reset CodeHash to EmptyCodeHash.
This commit is contained in:
parent
fd76921afa
commit
4183a70bd6
7 changed files with 138 additions and 77 deletions
|
|
@ -680,10 +680,6 @@ func (bc *BlockChain) processBlockWithAccessList(parentRoot common.Hash, block *
|
|||
stats.DatabaseCommit = m.TrieDBCommits
|
||||
stats.Prefetch = m.StatePrefetch
|
||||
}
|
||||
readerReads := prefetchReader.(state.ReadTimer).ReadTimes()
|
||||
stats.AccountReads = readerReads.Account
|
||||
stats.StorageReads = readerReads.Storage
|
||||
stats.CodeReads = readerReads.Code
|
||||
stats.Prefetch = prefetchReader.(state.PrefetcherMetricer).Metrics().Elapsed
|
||||
|
||||
stats.StateReadCacheStats = prefetchReader.(state.ReaderStater).GetStats()
|
||||
|
|
@ -2450,14 +2446,13 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
|||
proctime = time.Since(startTime) // processing + validation + cross validation
|
||||
stats = &ExecuteStats{}
|
||||
)
|
||||
reads := statedb.Reader().(state.ReadTimer).ReadTimes()
|
||||
codeLoaded, codeLoadBytes := statedb.Reader().(state.CodeLoadTracker).CodeLoads()
|
||||
stats.AccountReads = reads.Account
|
||||
stats.StorageReads = reads.Storage
|
||||
stats.CodeReads = reads.Code
|
||||
stats.AccountUpdates = statedb.AccountUpdates
|
||||
stats.StorageUpdates = statedb.StorageUpdates
|
||||
stats.AccountHashes = statedb.AccountHashes
|
||||
// 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
|
||||
|
|
@ -2465,13 +2460,14 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
|||
stats.StorageLoaded = statedb.StorageLoaded
|
||||
stats.StorageUpdated = int(statedb.StorageUpdated.Load())
|
||||
stats.StorageDeleted = int(statedb.StorageDeleted.Load())
|
||||
stats.CodeLoaded = codeLoaded
|
||||
stats.CodeLoadBytes = codeLoadBytes
|
||||
|
||||
stats.CodeLoaded = statedb.CodeLoaded
|
||||
stats.CodeLoadBytes = statedb.CodeLoadBytes
|
||||
stats.CodeUpdated = statedb.CodeUpdated
|
||||
stats.CodeUpdateBytes = statedb.CodeUpdateBytes
|
||||
|
||||
stats.Execution = ptime - (reads.Account + reads.Storage + reads.Code)
|
||||
stats.Validation = vtime - (statedb.AccountHashes + statedb.AccountUpdates + statedb.StorageUpdates)
|
||||
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.
|
||||
|
|
|
|||
91
core/state/bal_state_transition_test.go
Normal file
91
core/state/bal_state_transition_test.go
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
// 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 (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// TestBALStateTransition_EIP7702DelegationClear locks in the fix for the
|
||||
// `if len(code) > 0` regression that skipped legitimate EIP-7702 delegation
|
||||
// clears (encoded as a non-nil empty byte slice in the BAL). After the fix
|
||||
// the gate is `if code != nil`, so a delegation clear correctly resets
|
||||
// `acct.CodeHash` to `EmptyCodeHash` and is NOT counted as a deletion.
|
||||
func TestBALStateTransition_EIP7702DelegationClear(t *testing.T) {
|
||||
addr := common.HexToAddress("0x000000000000000000000000000000000000aaaa")
|
||||
|
||||
// Pre-state: account already holds a 7702 delegation (non-empty code).
|
||||
sdb := NewDatabaseForTesting()
|
||||
prestate, _ := New(types.EmptyRootHash, sdb)
|
||||
delegationCode := append([]byte{0xef, 0x01, 0x00}, common.HexToAddress("0xbeef").Bytes()...)
|
||||
prestate.SetBalance(addr, uint256.NewInt(1e18), tracing.BalanceChangeUnspecified)
|
||||
prestate.SetNonce(addr, 1, tracing.NonceChangeUnspecified)
|
||||
prestate.SetCode(addr, delegationCode, tracing.CodeChangeUnspecified)
|
||||
parentRoot, err := prestate.Commit(0, false, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Commit prestate: %v", err)
|
||||
}
|
||||
if err := sdb.TrieDB().Commit(parentRoot, false); err != nil {
|
||||
t.Fatalf("TrieDB Commit: %v", err)
|
||||
}
|
||||
|
||||
// Build a BAL whose only mutation is a 7702 delegation clear.
|
||||
// Code is non-nil but length zero — the canonical encoding.
|
||||
construction := make(bal.ConstructionBlockAccessList)
|
||||
construction.AccumulateMutations(bal.StateMutations{
|
||||
addr: bal.AccountMutations{Code: bal.ContractCode{}},
|
||||
}, 0)
|
||||
accessList := construction.ToEncodingObj()
|
||||
|
||||
// Synthesize a zero-tx block carrying the access list.
|
||||
block := types.NewBlockWithHeader(&types.Header{Number: common.Big1}).WithAccessList(accessList)
|
||||
|
||||
// Run the BAL state transition.
|
||||
reader, err := sdb.Reader(parentRoot)
|
||||
if err != nil {
|
||||
t.Fatalf("Reader: %v", err)
|
||||
}
|
||||
bst, err := NewBALStateTransition(block, reader, sdb, parentRoot)
|
||||
if err != nil {
|
||||
t.Fatalf("NewBALStateTransition: %v", err)
|
||||
}
|
||||
bst.IntermediateRoot(false)
|
||||
if err := bst.Error(); err != nil {
|
||||
t.Fatalf("IntermediateRoot: %v", err)
|
||||
}
|
||||
|
||||
post, ok := bst.postStates[addr]
|
||||
if !ok {
|
||||
t.Fatal("post-state must exist for the address (account is updated, not deleted)")
|
||||
}
|
||||
if !bytes.Equal(post.CodeHash, types.EmptyCodeHash.Bytes()) {
|
||||
t.Fatalf("CodeHash: got %x, want %x", post.CodeHash, types.EmptyCodeHash.Bytes())
|
||||
}
|
||||
if d := bst.Deletions().Accounts; d != 0 {
|
||||
t.Fatalf("Deletions.Accounts: got %d, want 0 (delegation clear is not a deletion)", d)
|
||||
}
|
||||
if _, deleted := bst.deletions[addr]; deleted {
|
||||
t.Fatal("address must not be in s.deletions after a 7702 clear")
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,6 @@ import (
|
|||
"errors"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/overlay"
|
||||
|
|
@ -532,10 +531,6 @@ type reader struct {
|
|||
StateReader
|
||||
PrefetcherMetricer
|
||||
|
||||
accountReadNS atomic.Int64
|
||||
storageReadNS atomic.Int64
|
||||
codeReadNS atomic.Int64
|
||||
|
||||
codeLoaded sync.Map // common.Address → int (first-seen len(code))
|
||||
}
|
||||
|
||||
|
|
@ -555,17 +550,14 @@ func newReaderWithPrefetch(codeReader ContractCodeReader, stateReader StateReade
|
|||
}
|
||||
|
||||
func (r *reader) Account(addr common.Address) (*types.StateAccount, error) {
|
||||
defer func(start time.Time) { r.accountReadNS.Add(int64(time.Since(start))) }(time.Now())
|
||||
return r.StateReader.Account(addr)
|
||||
}
|
||||
|
||||
func (r *reader) Storage(addr common.Address, slot common.Hash) (common.Hash, error) {
|
||||
defer func(start time.Time) { r.storageReadNS.Add(int64(time.Since(start))) }(time.Now())
|
||||
return r.StateReader.Storage(addr, slot)
|
||||
}
|
||||
|
||||
func (r *reader) Code(addr common.Address, codeHash common.Hash) []byte {
|
||||
defer func(start time.Time) { r.codeReadNS.Add(int64(time.Since(start))) }(time.Now())
|
||||
code := r.ContractCodeReader.Code(addr, codeHash)
|
||||
if len(code) > 0 {
|
||||
r.codeLoaded.LoadOrStore(addr, len(code))
|
||||
|
|
@ -574,7 +566,6 @@ func (r *reader) Code(addr common.Address, codeHash common.Hash) []byte {
|
|||
}
|
||||
|
||||
func (r *reader) CodeSize(addr common.Address, codeHash common.Hash) (int, error) {
|
||||
defer func(start time.Time) { r.codeReadNS.Add(int64(time.Since(start))) }(time.Now())
|
||||
size, err := r.ContractCodeReader.CodeSize(addr, codeHash)
|
||||
if err == nil && size > 0 {
|
||||
r.codeLoaded.LoadOrStore(addr, size)
|
||||
|
|
@ -582,14 +573,6 @@ func (r *reader) CodeSize(addr common.Address, codeHash common.Hash) (int, error
|
|||
return size, err
|
||||
}
|
||||
|
||||
func (r *reader) ReadTimes() ReadDurations {
|
||||
return ReadDurations{
|
||||
Account: time.Duration(r.accountReadNS.Load()),
|
||||
Storage: time.Duration(r.storageReadNS.Load()),
|
||||
Code: time.Duration(r.codeReadNS.Load()),
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
|
@ -625,19 +608,3 @@ func (r *reader) GetStats() ReaderStats {
|
|||
}
|
||||
}
|
||||
|
||||
// PrefetchReadTimes forwards to the wrapped prefetcher, or returns zero.
|
||||
func (r *reader) PrefetchReadTimes() (account, storage time.Duration) {
|
||||
if pr, ok := r.StateReader.(interface {
|
||||
PrefetchReadTimes() (time.Duration, time.Duration)
|
||||
}); ok {
|
||||
return pr.PrefetchReadTimes()
|
||||
}
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
// WaitPrefetch blocks until the wrapped prefetcher drains; no-op otherwise.
|
||||
func (r *reader) WaitPrefetch() {
|
||||
if pr, ok := r.StateReader.(interface{ Wait() error }); ok {
|
||||
_ = pr.Wait()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -383,10 +383,6 @@ func (r *readerTracker) TouchStorage(addr common.Address, slot common.Hash) {
|
|||
list[slot] = struct{}{}
|
||||
}
|
||||
|
||||
func (r *readerTracker) ReadTimes() ReadDurations {
|
||||
return r.Reader.(ReadTimer).ReadTimes()
|
||||
}
|
||||
|
||||
func (r *readerTracker) CodeLoads() (count, bytes int) {
|
||||
return r.Reader.(CodeLoadTracker).CodeLoads()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,20 +16,6 @@
|
|||
|
||||
package state
|
||||
|
||||
import "time"
|
||||
|
||||
// ReadDurations groups cumulative read durations by category.
|
||||
type ReadDurations struct {
|
||||
Account time.Duration
|
||||
Storage time.Duration
|
||||
Code time.Duration
|
||||
}
|
||||
|
||||
// ReadTimer exposes a Reader's cumulative read durations.
|
||||
type ReadTimer interface {
|
||||
ReadTimes() ReadDurations
|
||||
}
|
||||
|
||||
// CodeLoadTracker exposes a Reader's deduplicated code-load count and bytes.
|
||||
type CodeLoadTracker interface {
|
||||
CodeLoads() (count, bytes int)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
|
|
@ -225,11 +226,13 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
|
|||
}
|
||||
s.db.StorageLoaded++
|
||||
|
||||
start := time.Now()
|
||||
value, err := s.db.reader.Storage(s.address, key)
|
||||
if err != nil {
|
||||
s.db.setError(err)
|
||||
return common.Hash{}
|
||||
}
|
||||
s.db.StorageReads += time.Since(start)
|
||||
|
||||
// Schedule the resolved storage slots for prefetching if it's enabled.
|
||||
if s.db.prefetcher != nil && s.data.Root != types.EmptyRootHash {
|
||||
|
|
@ -641,6 +644,12 @@ func (s *stateObject) Code() []byte {
|
|||
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {
|
||||
return nil
|
||||
}
|
||||
defer func(start time.Time) {
|
||||
s.db.CodeLoaded += 1
|
||||
s.db.CodeReads += time.Since(start)
|
||||
s.db.CodeLoadBytes += len(s.code)
|
||||
}(time.Now())
|
||||
|
||||
code := s.db.reader.Code(s.address, common.BytesToHash(s.CodeHash()))
|
||||
if len(code) == 0 {
|
||||
s.db.setError(fmt.Errorf("code is not found %x", s.CodeHash()))
|
||||
|
|
@ -659,6 +668,11 @@ func (s *stateObject) CodeSize() int {
|
|||
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {
|
||||
return 0
|
||||
}
|
||||
defer func(start time.Time) {
|
||||
s.db.CodeLoaded += 1
|
||||
s.db.CodeReads += time.Since(start)
|
||||
}(time.Now())
|
||||
|
||||
size, err := s.db.reader.CodeSize(s.address, common.BytesToHash(s.CodeHash()))
|
||||
if err != nil {
|
||||
s.db.setError(err)
|
||||
|
|
|
|||
|
|
@ -156,24 +156,33 @@ type StateDB struct {
|
|||
// State witness if cross validation is needed
|
||||
witness *stateless.Witness
|
||||
|
||||
// Per-block counters surfaced in ExecuteStats; read durations and code-loads
|
||||
// are tracked on the reader (see ReadTimer, CodeLoadTracker).
|
||||
// Measurements gathered during execution for debugging purposes
|
||||
AccountReads time.Duration
|
||||
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
|
||||
AccountUpdated int
|
||||
AccountDeleted int
|
||||
StorageLoaded int
|
||||
StorageUpdated atomic.Int64
|
||||
StorageDeleted atomic.Int64
|
||||
CodeUpdated int
|
||||
CodeUpdateBytes int
|
||||
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
|
||||
}
|
||||
|
||||
// New creates a new state from a given trie.
|
||||
|
|
@ -626,11 +635,13 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject {
|
|||
|
||||
s.AccountLoaded++
|
||||
|
||||
start := time.Now()
|
||||
acct, err := s.reader.Account(addr)
|
||||
if err != nil {
|
||||
s.setError(fmt.Errorf("getStateObject (%x) error: %w", addr.Bytes(), err))
|
||||
return nil
|
||||
}
|
||||
s.AccountReads += time.Since(start)
|
||||
|
||||
// Short circuit if the account is not found
|
||||
if acct == nil {
|
||||
|
|
|
|||
Loading…
Reference in a new issue