Skip to content

Commit ab2caae

Browse files
authored
eth/tracers: implement debug.intermediateRoots (#23594)
This PR implements a new debug method, which I've talked briefly about to some other client developers. It allows the caller to obtain the intermediate state roots for a block (which might be either a canon block or a 'bad' block).
1 parent 443afc9 commit ab2caae

File tree

2 files changed

+71
-10
lines changed

2 files changed

+71
-10
lines changed

eth/tracers/api.go

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -445,12 +445,11 @@ func (api *API) TraceBlockFromFile(ctx context.Context, file string, config *Tra
445445
// EVM against a block pulled from the pool of bad ones and returns them as a JSON
446446
// object.
447447
func (api *API) TraceBadBlock(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) {
448-
for _, block := range rawdb.ReadAllBadBlocks(api.backend.ChainDb()) {
449-
if block.Hash() == hash {
450-
return api.traceBlock(ctx, block, config)
451-
}
448+
block := rawdb.ReadBadBlock(api.backend.ChainDb(), hash)
449+
if block == nil {
450+
return nil, fmt.Errorf("bad block %#x not found", hash)
452451
}
453-
return nil, fmt.Errorf("bad block %#x not found", hash)
452+
return api.traceBlock(ctx, block, config)
454453
}
455454

456455
// StandardTraceBlockToFile dumps the structured logs created during the
@@ -464,16 +463,72 @@ func (api *API) StandardTraceBlockToFile(ctx context.Context, hash common.Hash,
464463
return api.standardTraceBlockToFile(ctx, block, config)
465464
}
466465

466+
// IntermediateRoots executes a block (bad- or canon- or side-), and returns a list
467+
// of intermediate roots: the stateroot after each transaction.
468+
func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config *TraceConfig) ([]common.Hash, error) {
469+
block, _ := api.blockByHash(ctx, hash)
470+
if block == nil {
471+
// Check in the bad blocks
472+
block = rawdb.ReadBadBlock(api.backend.ChainDb(), hash)
473+
}
474+
if block == nil {
475+
return nil, fmt.Errorf("block %#x not found", hash)
476+
}
477+
if block.NumberU64() == 0 {
478+
return nil, errors.New("genesis is not traceable")
479+
}
480+
parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(block.NumberU64()-1), block.ParentHash())
481+
if err != nil {
482+
return nil, err
483+
}
484+
reexec := defaultTraceReexec
485+
if config != nil && config.Reexec != nil {
486+
reexec = *config.Reexec
487+
}
488+
statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true)
489+
if err != nil {
490+
return nil, err
491+
}
492+
var (
493+
roots []common.Hash
494+
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number())
495+
chainConfig = api.backend.ChainConfig()
496+
vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
497+
deleteEmptyObjects = chainConfig.IsEIP158(block.Number())
498+
)
499+
for i, tx := range block.Transactions() {
500+
var (
501+
msg, _ = tx.AsMessage(signer, block.BaseFee())
502+
txContext = core.NewEVMTxContext(msg)
503+
vmenv = vm.NewEVM(vmctx, txContext, statedb, chainConfig, vm.Config{})
504+
)
505+
statedb.Prepare(tx.Hash(), i)
506+
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil {
507+
log.Warn("Tracing intermediate roots did not complete", "txindex", i, "txhash", tx.Hash(), "err", err)
508+
// We intentionally don't return the error here: if we do, then the RPC server will not
509+
// return the roots. Most likely, the caller already knows that a certain transaction fails to
510+
// be included, but still want the intermediate roots that led to that point.
511+
// It may happen the tx_N causes an erroneous state, which in turn causes tx_N+M to not be
512+
// executable.
513+
// N.B: This should never happen while tracing canon blocks, only when tracing bad blocks.
514+
return roots, nil
515+
}
516+
// calling IntermediateRoot will internally call Finalize on the state
517+
// so any modifications are written to the trie
518+
roots = append(roots, statedb.IntermediateRoot(deleteEmptyObjects))
519+
}
520+
return roots, nil
521+
}
522+
467523
// StandardTraceBadBlockToFile dumps the structured logs created during the
468524
// execution of EVM against a block pulled from the pool of bad ones to the
469525
// local file system and returns a list of files to the caller.
470526
func (api *API) StandardTraceBadBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) {
471-
for _, block := range rawdb.ReadAllBadBlocks(api.backend.ChainDb()) {
472-
if block.Hash() == hash {
473-
return api.standardTraceBlockToFile(ctx, block, config)
474-
}
527+
block := rawdb.ReadBadBlock(api.backend.ChainDb(), hash)
528+
if block == nil {
529+
return nil, fmt.Errorf("bad block %#x not found", hash)
475530
}
476-
return nil, fmt.Errorf("bad block %#x not found", hash)
531+
return api.standardTraceBlockToFile(ctx, block, config)
477532
}
478533

479534
// traceBlock configures a new tracer according to the provided configuration, and

internal/web3ext/web3ext.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,12 @@ web3._extend({
391391
params: 2,
392392
inputFormatter: [null, null]
393393
}),
394+
new web3._extend.Method({
395+
name: 'intermediateRoots',
396+
call: 'debug_intermediateRoots',
397+
params: 2,
398+
inputFormatter: [null, null]
399+
}),
394400
new web3._extend.Method({
395401
name: 'standardTraceBlockToFile',
396402
call: 'debug_standardTraceBlockToFile',

0 commit comments

Comments
 (0)