cmd, core, eth, tests: prevent state flushing in RPC (#33931)

Fixes https://github.com/ethereum/go-ethereum/issues/33572
This commit is contained in:
rjl493456442 2026-03-04 14:40:45 +08:00 committed by GitHub
parent fe3a74e610
commit 6d99759f01
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 103 additions and 73 deletions

View file

@ -2475,8 +2475,6 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
} }
vmcfg := vm.Config{ vmcfg := vm.Config{
EnablePreimageRecording: ctx.Bool(VMEnableDebugFlag.Name), EnablePreimageRecording: ctx.Bool(VMEnableDebugFlag.Name),
EnableWitnessStats: ctx.Bool(VMWitnessStatsFlag.Name),
StatelessSelfValidation: ctx.Bool(VMStatelessSelfValidationFlag.Name) || ctx.Bool(VMWitnessStatsFlag.Name),
} }
if ctx.IsSet(VMTraceFlag.Name) { if ctx.IsSet(VMTraceFlag.Name) {
if name := ctx.String(VMTraceFlag.Name); name != "" { if name := ctx.String(VMTraceFlag.Name); name != "" {
@ -2490,6 +2488,9 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
} }
options.VmConfig = vmcfg options.VmConfig = vmcfg
options.StatelessSelfValidation = ctx.Bool(VMStatelessSelfValidationFlag.Name) || ctx.Bool(VMWitnessStatsFlag.Name)
options.EnableWitnessStats = ctx.Bool(VMWitnessStatsFlag.Name)
chain, err := core.NewBlockChain(chainDb, gspec, engine, options) chain, err := core.NewBlockChain(chainDb, gspec, engine, options)
if err != nil { if err != nil {
Fatalf("Can't create BlockChain: %v", err) Fatalf("Can't create BlockChain: %v", err)

View file

@ -219,6 +219,10 @@ type BlockChainConfig struct {
// detailed statistics will be logged. Negative value means disabled (default), // detailed statistics will be logged. Negative value means disabled (default),
// zero logs all blocks, positive value filters blocks by execution time. // zero logs all blocks, positive value filters blocks by execution time.
SlowBlockThreshold time.Duration SlowBlockThreshold time.Duration
// Execution configs
StatelessSelfValidation bool // Generate execution witnesses and self-check against them (testing purpose)
EnableWitnessStats bool // Whether trie access statistics collection is enabled
} }
// DefaultConfig returns the default config. // DefaultConfig returns the default config.
@ -1990,7 +1994,15 @@ func (bc *BlockChain) insertChain(ctx context.Context, chain types.Blocks, setHe
} }
// The traced section of block import. // The traced section of block import.
start := time.Now() start := time.Now()
res, err := bc.ProcessBlock(ctx, parent.Root, block, setHead, makeWitness && len(chain) == 1) config := ExecuteConfig{
WriteState: true,
WriteHead: setHead,
EnableTracer: true,
MakeWitness: makeWitness && len(chain) == 1,
StatelessSelfValidation: bc.cfg.StatelessSelfValidation,
EnableWitnessStats: bc.cfg.EnableWitnessStats,
}
res, err := bc.ProcessBlock(ctx, parent.Root, block, config)
if err != nil { if err != nil {
return nil, it.index, err return nil, it.index, err
} }
@ -2073,9 +2085,36 @@ func (bpr *blockProcessingResult) Stats() *ExecuteStats {
return bpr.stats return bpr.stats
} }
// ExecuteConfig defines optional behaviors during execution.
type ExecuteConfig struct {
// WriteState controls whether the computed state changes are persisted to
// the underlying storage. If false, execution is performed in-memory only.
WriteState bool
// WriteHead indicates whether the execution result should update the canonical
// chain head. It's only relevant with WriteState == True.
WriteHead bool
// EnableTracer enables execution tracing. This is typically used for debugging
// or analysis and may significantly impact performance.
EnableTracer bool
// MakeWitness indicates whether to generate execution witness data during
// execution. Enabling this may introduce additional memory and CPU overhead.
MakeWitness bool
// StatelessSelfValidation indicates whether the execution witnesses generation
// and self-validation (testing purpose) is enabled.
StatelessSelfValidation bool
// EnableWitnessStats indicates whether to enable collection of witness trie
// access statistics
EnableWitnessStats bool
}
// ProcessBlock executes and validates the given block. If there was no error // ProcessBlock executes and validates the given block. If there was no error
// it writes the block and associated state to database. // it writes the block and associated state to database.
func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash, block *types.Block, setHead bool, makeWitness bool) (result *blockProcessingResult, blockEndErr error) { func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash, block *types.Block, config ExecuteConfig) (result *blockProcessingResult, blockEndErr error) {
var ( var (
err error err error
startTime = time.Now() startTime = time.Now()
@ -2138,12 +2177,12 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
// Generate witnesses either if we're self-testing, or if it's the // Generate witnesses either if we're self-testing, or if it's the
// only block being inserted. A bit crude, but witnesses are huge, // only block being inserted. A bit crude, but witnesses are huge,
// so we refuse to make an entire chain of them. // so we refuse to make an entire chain of them.
if bc.cfg.VmConfig.StatelessSelfValidation || makeWitness { if config.StatelessSelfValidation || config.MakeWitness {
witness, err = stateless.NewWitness(block.Header(), bc) witness, err = stateless.NewWitness(block.Header(), bc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if bc.cfg.VmConfig.EnableWitnessStats { if config.EnableWitnessStats {
witnessStats = stateless.NewWitnessStats() witnessStats = stateless.NewWitnessStats()
} }
} }
@ -2151,17 +2190,20 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
defer statedb.StopPrefetcher() defer statedb.StopPrefetcher()
} }
if bc.logger != nil && bc.logger.OnBlockStart != nil { // Instrument the blockchain tracing
bc.logger.OnBlockStart(tracing.BlockEvent{ if config.EnableTracer {
Block: block, if bc.logger != nil && bc.logger.OnBlockStart != nil {
Finalized: bc.CurrentFinalBlock(), bc.logger.OnBlockStart(tracing.BlockEvent{
Safe: bc.CurrentSafeBlock(), Block: block,
}) Finalized: bc.CurrentFinalBlock(),
} Safe: bc.CurrentSafeBlock(),
if bc.logger != nil && bc.logger.OnBlockEnd != nil { })
defer func() { }
bc.logger.OnBlockEnd(blockEndErr) if bc.logger != nil && bc.logger.OnBlockEnd != nil {
}() defer func() {
bc.logger.OnBlockEnd(blockEndErr)
}()
}
} }
// Process block using the parent state as reference point // Process block using the parent state as reference point
@ -2191,7 +2233,7 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
// witness builder/runner, which would otherwise be impossible due to the // witness builder/runner, which would otherwise be impossible due to the
// various invalid chain states/behaviors being contained in those tests. // various invalid chain states/behaviors being contained in those tests.
xvstart := time.Now() xvstart := time.Now()
if witness := statedb.Witness(); witness != nil && bc.cfg.VmConfig.StatelessSelfValidation { if witness := statedb.Witness(); witness != nil && config.StatelessSelfValidation {
log.Warn("Running stateless self-validation", "block", block.Number(), "hash", block.Hash()) log.Warn("Running stateless self-validation", "block", block.Number(), "hash", block.Hash())
// Remove critical computed fields from the block to force true recalculation // Remove critical computed fields from the block to force true recalculation
@ -2244,31 +2286,29 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
stats.CrossValidation = xvtime // The time spent on stateless cross validation stats.CrossValidation = xvtime // The time spent on stateless cross validation
// Write the block to the chain and get the status. // Write the block to the chain and get the status.
var ( var status WriteStatus
wstart = time.Now() if config.WriteState {
status WriteStatus wstart := time.Now()
) if !config.WriteHead {
if !setHead { // Don't set the head, only insert the block
// Don't set the head, only insert the block err = bc.writeBlockWithState(block, res.Receipts, statedb)
err = bc.writeBlockWithState(block, res.Receipts, statedb) } else {
} else { status, err = bc.writeBlockAndSetHead(block, res.Receipts, res.Logs, statedb, false)
status, err = bc.writeBlockAndSetHead(block, res.Receipts, res.Logs, statedb, false) }
} if err != nil {
if err != nil { return nil, err
return nil, err }
// Update the metrics touched during block commit
stats.AccountCommits = statedb.AccountCommits // Account commits are complete, we can mark them
stats.StorageCommits = statedb.StorageCommits // Storage commits are complete, we can mark them
stats.SnapshotCommit = statedb.SnapshotCommits // Snapshot commits are complete, we can mark them
stats.TrieDBCommit = statedb.TrieDBCommits // Trie database commits are complete, we can mark them
stats.BlockWrite = time.Since(wstart) - max(statedb.AccountCommits, statedb.StorageCommits) /* concurrent */ - statedb.SnapshotCommits - statedb.TrieDBCommits
} }
// Report the collected witness statistics // Report the collected witness statistics
if witnessStats != nil { if witnessStats != nil {
witnessStats.ReportMetrics(block.NumberU64()) witnessStats.ReportMetrics(block.NumberU64())
} }
// Update the metrics touched during block commit
stats.AccountCommits = statedb.AccountCommits // Account commits are complete, we can mark them
stats.StorageCommits = statedb.StorageCommits // Storage commits are complete, we can mark them
stats.SnapshotCommit = statedb.SnapshotCommits // Snapshot commits are complete, we can mark them
stats.TrieDBCommit = statedb.TrieDBCommits // Trie database commits are complete, we can mark them
stats.BlockWrite = time.Since(wstart) - max(statedb.AccountCommits, statedb.StorageCommits) /* concurrent */ - statedb.SnapshotCommits - statedb.TrieDBCommits
elapsed := time.Since(startTime) + 1 // prevent zero division elapsed := time.Since(startTime) + 1 // prevent zero division
stats.TotalTime = elapsed stats.TotalTime = elapsed
stats.MgasPerSecond = float64(res.GasUsed) * 1000 / float64(elapsed) stats.MgasPerSecond = float64(res.GasUsed) * 1000 / float64(elapsed)

View file

@ -27,13 +27,11 @@ import (
// Config are the configuration options for the Interpreter // Config are the configuration options for the Interpreter
type Config struct { type Config struct {
Tracer *tracing.Hooks Tracer *tracing.Hooks
NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls) NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls)
EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages
ExtraEips []int // Additional EIPS that are to be enabled ExtraEips []int // Additional EIPS that are to be enabled
StatelessSelfValidation bool // Generate execution witnesses and self-check against them (testing purpose)
EnableWitnessStats bool // Whether trie access statistics collection is enabled
} }
// ScopeContext contains the things that are per-call, such as stack and memory, // ScopeContext contains the things that are per-call, such as stack and memory,

View file

@ -24,6 +24,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/stateless" "github.com/ethereum/go-ethereum/core/stateless"
@ -493,34 +494,22 @@ func (api *DebugAPI) StateSize(blockHashOrNumber *rpc.BlockNumberOrHash) (interf
}, nil }, nil
} }
func (api *DebugAPI) ExecutionWitness(bn rpc.BlockNumber) (*stateless.ExtWitness, error) { func (api *DebugAPI) ExecutionWitness(bn rpc.BlockNumberOrHash) (*stateless.ExtWitness, error) {
bc := api.eth.blockchain bc := api.eth.blockchain
block, err := api.eth.APIBackend.BlockByNumber(context.Background(), bn) block, err := api.eth.APIBackend.BlockByNumberOrHash(context.Background(), bn)
if err != nil { if err != nil {
return &stateless.ExtWitness{}, fmt.Errorf("block number %v not found", bn) return &stateless.ExtWitness{}, fmt.Errorf("block %v not found", bn)
} }
parent := bc.GetHeader(block.ParentHash(), block.NumberU64()-1) parent := bc.GetHeader(block.ParentHash(), block.NumberU64()-1)
if parent == nil { if parent == nil {
return &stateless.ExtWitness{}, fmt.Errorf("block number %v found, but parent missing", bn) return &stateless.ExtWitness{}, fmt.Errorf("block %v found, but parent missing", bn)
} }
result, err := bc.ProcessBlock(context.Background(), parent.Root, block, false, true) config := core.ExecuteConfig{
if err != nil { WriteState: false,
return nil, err EnableTracer: false,
} MakeWitness: true,
return result.Witness().ToExtWitness(), nil }
} result, err := bc.ProcessBlock(context.Background(), parent.Root, block, config)
func (api *DebugAPI) ExecutionWitnessByHash(hash common.Hash) (*stateless.ExtWitness, error) {
bc := api.eth.blockchain
block := bc.GetBlockByHash(hash)
if block == nil {
return &stateless.ExtWitness{}, fmt.Errorf("block hash %x not found", hash)
}
parent := bc.GetHeader(block.ParentHash(), block.NumberU64()-1)
if parent == nil {
return &stateless.ExtWitness{}, fmt.Errorf("block number %x found, but parent missing", hash)
}
result, err := bc.ProcessBlock(context.Background(), parent.Root, block, false, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -237,8 +237,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
TxLookupLimit: int64(min(config.TransactionHistory, math.MaxInt64)), TxLookupLimit: int64(min(config.TransactionHistory, math.MaxInt64)),
VmConfig: vm.Config{ VmConfig: vm.Config{
EnablePreimageRecording: config.EnablePreimageRecording, EnablePreimageRecording: config.EnablePreimageRecording,
EnableWitnessStats: config.EnableWitnessStats,
StatelessSelfValidation: config.StatelessSelfValidation,
}, },
// Enables file journaling for the trie database. The journal files will be stored // Enables file journaling for the trie database. The journal files will be stored
// within the data directory. The corresponding paths will be either: // within the data directory. The corresponding paths will be either:
@ -247,6 +245,9 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
TrieJournalDirectory: stack.ResolvePath("triedb"), TrieJournalDirectory: stack.ResolvePath("triedb"),
StateSizeTracking: config.EnableStateSizeTracking, StateSizeTracking: config.EnableStateSizeTracking,
SlowBlockThreshold: config.SlowBlockThreshold, SlowBlockThreshold: config.SlowBlockThreshold,
StatelessSelfValidation: config.StatelessSelfValidation,
EnableWitnessStats: config.EnableWitnessStats,
} }
) )
if config.VMTrace != "" { if config.VMTrace != "" {

View file

@ -427,11 +427,6 @@ web3._extend({
params: 2, params: 2,
inputFormatter:[null, null], inputFormatter:[null, null],
}), }),
new web3._extend.Method({
name: 'freezeClient',
call: 'debug_freezeClient',
params: 1,
}),
new web3._extend.Method({ new web3._extend.Method({
name: 'getAccessibleState', name: 'getAccessibleState',
call: 'debug_getAccessibleState', call: 'debug_getAccessibleState',
@ -474,6 +469,12 @@ web3._extend({
params: 1, params: 1,
inputFormatter: [null], inputFormatter: [null],
}), }),
new web3._extend.Method({
name: 'executionWitness',
call: 'debug_executionWitness',
params: 1,
inputFormatter: [null],
}),
], ],
properties: [] properties: []
}); });

View file

@ -161,9 +161,9 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, witness bool, tracer *t
Preimages: true, Preimages: true,
TxLookupLimit: -1, // disable tx indexing TxLookupLimit: -1, // disable tx indexing
VmConfig: vm.Config{ VmConfig: vm.Config{
Tracer: tracer, Tracer: tracer,
StatelessSelfValidation: witness,
}, },
StatelessSelfValidation: witness,
} }
if snapshotter { if snapshotter {
options.SnapshotLimit = 1 options.SnapshotLimit = 1