mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-26 06:36:43 +00:00
eth/tracers: initiate rework of tracer code to deduplicate block/tx tracer paths
This commit is contained in:
parent
51b34efebc
commit
0808452e66
1 changed files with 328 additions and 191 deletions
|
|
@ -244,10 +244,6 @@ func (api *API) TraceChain(ctx context.Context, start, end rpc.BlockNumber, conf
|
||||||
// transaction, dependent on the requested tracer.
|
// transaction, dependent on the requested tracer.
|
||||||
// The tracing procedure should be aborted in case the closed signal is received.
|
// The tracing procedure should be aborted in case the closed signal is received.
|
||||||
func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed <-chan error) chan *blockTraceResult {
|
func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed <-chan error) chan *blockTraceResult {
|
||||||
reexec := defaultTraceReexec
|
|
||||||
if config != nil && config.Reexec != nil {
|
|
||||||
reexec = *config.Reexec
|
|
||||||
}
|
|
||||||
blocks := int(end.NumberU64() - start.NumberU64())
|
blocks := int(end.NumberU64() - start.NumberU64())
|
||||||
threads := runtime.NumCPU()
|
threads := runtime.NumCPU()
|
||||||
if threads > blocks {
|
if threads > blocks {
|
||||||
|
|
@ -259,7 +255,19 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
|
||||||
taskCh = make(chan *blockTraceTask, threads)
|
taskCh = make(chan *blockTraceTask, threads)
|
||||||
resCh = make(chan *blockTraceTask, threads)
|
resCh = make(chan *blockTraceTask, threads)
|
||||||
tracker = newStateTracker(maximumPendingTraceStates, start.NumberU64())
|
tracker = newStateTracker(maximumPendingTraceStates, start.NumberU64())
|
||||||
|
err error
|
||||||
)
|
)
|
||||||
|
opt, err := traceExecOpt(config)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("invalid trace configuration", "err", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
env := envFromTraceConfig(api, config)
|
||||||
|
|
||||||
|
instantiateTracer := func(ctx *Context) (*Tracer, error) {
|
||||||
|
return api.instantiateTracer(config, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
for th := 0; th < threads; th++ {
|
for th := 0; th < threads; th++ {
|
||||||
pend.Add(1)
|
pend.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
|
|
@ -267,26 +275,10 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
|
||||||
|
|
||||||
// Fetch and execute the block trace taskCh
|
// Fetch and execute the block trace taskCh
|
||||||
for task := range taskCh {
|
for task := range taskCh {
|
||||||
var (
|
task.results, err = api.traceBlock(ctx, task.block, env, opt, instantiateTracer)
|
||||||
signer = types.MakeSigner(api.backend.ChainConfig(), task.block.Number(), task.block.Time())
|
if err != nil {
|
||||||
blockCtx = core.NewEVMBlockContext(task.block.Header(), api.chainContext(ctx), nil)
|
log.Warn("Tracing failed", "err", err)
|
||||||
)
|
break
|
||||||
// Trace all the transactions contained within
|
|
||||||
for i, tx := range task.block.Transactions() {
|
|
||||||
msg, _ := core.TransactionToMessage(tx, signer, task.block.BaseFee())
|
|
||||||
txctx := &Context{
|
|
||||||
BlockHash: task.block.Hash(),
|
|
||||||
BlockNumber: task.block.Number(),
|
|
||||||
TxIndex: i,
|
|
||||||
TxHash: tx.Hash(),
|
|
||||||
}
|
|
||||||
res, err := api.traceTx(ctx, tx, msg, txctx, blockCtx, task.statedb, config, nil)
|
|
||||||
if err != nil {
|
|
||||||
task.results[i] = &txTraceResult{TxHash: tx.Hash(), Error: err.Error()}
|
|
||||||
log.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
task.results[i] = &txTraceResult{TxHash: tx.Hash(), Result: res}
|
|
||||||
}
|
}
|
||||||
// Tracing state is used up, queue it for de-referencing. Note the
|
// Tracing state is used up, queue it for de-referencing. Note the
|
||||||
// state is the parent state of trace block, use block.number-1 as
|
// state is the parent state of trace block, use block.number-1 as
|
||||||
|
|
@ -373,22 +365,11 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
|
||||||
s1, s2, s3 := statedb.Database().TrieDB().Size()
|
s1, s2, s3 := statedb.Database().TrieDB().Size()
|
||||||
preferDisk = s1+s2+s3 > defaultTracechainMemLimit
|
preferDisk = s1+s2+s3 > defaultTracechainMemLimit
|
||||||
}
|
}
|
||||||
statedb, release, err = api.backend.StateAtBlock(ctx, block, reexec, statedb, false, preferDisk)
|
statedb, _, release, err = api.retrieveBlockState(ctx, block, opt, false, preferDisk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
failed = err
|
failed = err
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
// Insert block's parent beacon block root in the state
|
|
||||||
// as per EIP-4788.
|
|
||||||
context := core.NewEVMBlockContext(next.Header(), api.chainContext(ctx), nil)
|
|
||||||
evm := vm.NewEVM(context, statedb, api.backend.ChainConfig(), vm.Config{})
|
|
||||||
if beaconRoot := next.BeaconRoot(); beaconRoot != nil {
|
|
||||||
core.ProcessBeaconBlockRoot(*beaconRoot, evm)
|
|
||||||
}
|
|
||||||
// Insert parent hash in history contract.
|
|
||||||
if api.backend.ChainConfig().IsPrague(next.Number(), next.Time()) {
|
|
||||||
core.ProcessParentBlockHash(next.ParentHash(), evm)
|
|
||||||
}
|
|
||||||
// Clean out any pending release functions of trace state. Note this
|
// Clean out any pending release functions of trace state. Note this
|
||||||
// step must be done after constructing tracing state, because the
|
// step must be done after constructing tracing state, because the
|
||||||
// tracing state of block next depends on the parent state and construction
|
// tracing state of block next depends on the parent state and construction
|
||||||
|
|
@ -441,6 +422,18 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
|
||||||
return retCh
|
return retCh
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (api *API) traceBlockCustomTracer(ctx context.Context, block *types.Block, config *TraceConfig) ([]*txTraceResult, error) {
|
||||||
|
instantiateTracer := func(ctx *Context) (*Tracer, error) {
|
||||||
|
return api.instantiateTracer(config, ctx)
|
||||||
|
}
|
||||||
|
env := envFromTraceConfig(api, config)
|
||||||
|
opt, err := traceExecOpt(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return api.traceBlock(ctx, block, env, opt, instantiateTracer)
|
||||||
|
}
|
||||||
|
|
||||||
// TraceBlockByNumber returns the structured logs created during the execution of
|
// TraceBlockByNumber returns the structured logs created during the execution of
|
||||||
// EVM and returns them as a JSON object.
|
// EVM and returns them as a JSON object.
|
||||||
func (api *API) TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber, config *TraceConfig) ([]*txTraceResult, error) {
|
func (api *API) TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber, config *TraceConfig) ([]*txTraceResult, error) {
|
||||||
|
|
@ -448,7 +441,7 @@ func (api *API) TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return api.traceBlock(ctx, block, config)
|
return api.traceBlockCustomTracer(ctx, block, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TraceBlockByHash returns the structured logs created during the execution of
|
// TraceBlockByHash returns the structured logs created during the execution of
|
||||||
|
|
@ -458,7 +451,7 @@ func (api *API) TraceBlockByHash(ctx context.Context, hash common.Hash, config *
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return api.traceBlock(ctx, block, config)
|
return api.traceBlockCustomTracer(ctx, block, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TraceBlock returns the structured logs created during the execution of EVM
|
// TraceBlock returns the structured logs created during the execution of EVM
|
||||||
|
|
@ -468,7 +461,7 @@ func (api *API) TraceBlock(ctx context.Context, blob hexutil.Bytes, config *Trac
|
||||||
if err := rlp.DecodeBytes(blob, block); err != nil {
|
if err := rlp.DecodeBytes(blob, block); err != nil {
|
||||||
return nil, fmt.Errorf("could not decode block: %v", err)
|
return nil, fmt.Errorf("could not decode block: %v", err)
|
||||||
}
|
}
|
||||||
return api.traceBlock(ctx, block, config)
|
return api.traceBlockCustomTracer(ctx, block, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TraceBlockFromFile returns the structured logs created during the execution of
|
// TraceBlockFromFile returns the structured logs created during the execution of
|
||||||
|
|
@ -489,10 +482,20 @@ func (api *API) TraceBadBlock(ctx context.Context, hash common.Hash, config *Tra
|
||||||
if block == nil {
|
if block == nil {
|
||||||
return nil, fmt.Errorf("bad block %#x not found", hash)
|
return nil, fmt.Errorf("bad block %#x not found", hash)
|
||||||
}
|
}
|
||||||
return api.traceBlock(ctx, block, config)
|
instantiateTracer := func(ctx *Context) (*Tracer, error) {
|
||||||
|
return api.instantiateTracer(config, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: investigate whether the canon field has different treatment when tracing bad blocks
|
||||||
|
env := envFromTraceConfig(api, config)
|
||||||
|
opt, err := traceExecOpt(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return api.traceBlock(ctx, block, env, opt, instantiateTracer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// StandardTraceBlockToFile dumps the structured logs created during the
|
// sstandardTraceBlockToFile dumps the structured logs created during the
|
||||||
// execution of EVM to the local file system and returns a list of files
|
// execution of EVM to the local file system and returns a list of files
|
||||||
// to the caller.
|
// to the caller.
|
||||||
func (api *API) StandardTraceBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) {
|
func (api *API) StandardTraceBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) {
|
||||||
|
|
@ -578,30 +581,73 @@ func (api *API) StandardTraceBadBlockToFile(ctx context.Context, hash common.Has
|
||||||
return api.standardTraceBlockToFile(ctx, block, config)
|
return api.standardTraceBlockToFile(ctx, block, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
// traceBlock configures a new tracer according to the provided configuration, and
|
type traceExecOptions struct {
|
||||||
// executes all the transactions contained within. The return value will be one item
|
txTimeout time.Duration
|
||||||
// per transaction, dependent on the requested tracer.
|
reexec uint64
|
||||||
func (api *API) traceBlock(ctx context.Context, block *types.Block, config *TraceConfig) ([]*txTraceResult, error) {
|
parallel bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// chain configuration and flag for whether the config is canonical
|
||||||
|
type traceEnv struct {
|
||||||
|
canon bool
|
||||||
|
chainConfig *params.ChainConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func envFromStdConfig(api *API, config *StdTraceConfig) *traceEnv {
|
||||||
|
if config == nil {
|
||||||
|
return &traceEnv{
|
||||||
|
canon: true,
|
||||||
|
chainConfig: api.backend.ChainConfig(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return envFromLoggerConfig(api, &config.Config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func envFromTraceConfig(api *API, config *TraceConfig) *traceEnv {
|
||||||
|
if config == nil {
|
||||||
|
return &traceEnv{
|
||||||
|
canon: true,
|
||||||
|
chainConfig: api.backend.ChainConfig(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return envFromLoggerConfig(api, config.Config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func envFromLoggerConfig(api *API, config *logger.Config) *traceEnv {
|
||||||
|
if config == nil {
|
||||||
|
return &traceEnv{
|
||||||
|
canon: true,
|
||||||
|
chainConfig: api.backend.ChainConfig(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chainConfig := api.backend.ChainConfig()
|
||||||
|
var canon bool
|
||||||
|
if config.Overrides != nil {
|
||||||
|
chainConfig, canon = overrideConfig(chainConfig, config.Overrides)
|
||||||
|
}
|
||||||
|
return &traceEnv{
|
||||||
|
canon,
|
||||||
|
chainConfig,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) retrieveBlockState(ctx context.Context, block *types.Block, execOpt *traceExecOptions, readOnly, preferDisk bool) (db *state.StateDB, blockCtx *vm.BlockContext, stateRelease StateReleaseFunc, err error) {
|
||||||
if block.NumberU64() == 0 {
|
if block.NumberU64() == 0 {
|
||||||
return nil, errors.New("genesis is not traceable")
|
return nil, nil, nil, errors.New("genesis is not traceable")
|
||||||
}
|
}
|
||||||
// Prepare base state
|
// Prepare base state
|
||||||
parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(block.NumberU64()-1), block.ParentHash())
|
parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(block.NumberU64()-1), block.ParentHash())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
reexec := defaultTraceReexec
|
statedb, release, err := api.backend.StateAtBlock(ctx, parent, execOpt.reexec, nil, readOnly, preferDisk)
|
||||||
if config != nil && config.Reexec != nil {
|
|
||||||
reexec = *config.Reexec
|
|
||||||
}
|
|
||||||
statedb, release, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
defer release()
|
|
||||||
|
|
||||||
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
// TODO: we can move these into execBlockTrace? it changes the internal semantics of traceChain but it shouldn't matter?
|
||||||
evm := vm.NewEVM(blockCtx, statedb, api.backend.ChainConfig(), vm.Config{})
|
bCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||||
|
evm := vm.NewEVM(bCtx, statedb, api.backend.ChainConfig(), vm.Config{})
|
||||||
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
||||||
core.ProcessBeaconBlockRoot(*beaconRoot, evm)
|
core.ProcessBeaconBlockRoot(*beaconRoot, evm)
|
||||||
}
|
}
|
||||||
|
|
@ -609,13 +655,12 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
|
||||||
core.ProcessParentBlockHash(block.ParentHash(), evm)
|
core.ProcessParentBlockHash(block.ParentHash(), evm)
|
||||||
}
|
}
|
||||||
|
|
||||||
// JS tracers have high overhead. In this case run a parallel
|
return statedb, &bCtx, release, nil
|
||||||
// process that generates states in one thread and traces txes
|
}
|
||||||
// in separate worker threads.
|
|
||||||
if config != nil && config.Tracer != nil && *config.Tracer != "" {
|
func (api *API) execBlockTrace(ctx context.Context, block *types.Block, blockCtx *vm.BlockContext, statedb *state.StateDB, execOpt *traceExecOptions, instantiateTracer func(*Context) (*Tracer, error)) ([]*txTraceResult, error) {
|
||||||
if isJS := DefaultDirectory.IsJS(*config.Tracer); isJS {
|
if execOpt.parallel {
|
||||||
return api.traceBlockParallel(ctx, block, statedb, config)
|
return api.traceBlockParallel(ctx, block, statedb, execOpt.txTimeout, instantiateTracer)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Native tracers have low overhead
|
// Native tracers have low overhead
|
||||||
var (
|
var (
|
||||||
|
|
@ -633,7 +678,11 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
|
||||||
TxIndex: i,
|
TxIndex: i,
|
||||||
TxHash: tx.Hash(),
|
TxHash: tx.Hash(),
|
||||||
}
|
}
|
||||||
res, err := api.traceTx(ctx, tx, msg, txctx, blockCtx, statedb, config, nil)
|
tracer, err := instantiateTracer(txctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res, err := api.traceTx(ctx, tx, msg, txctx, *blockCtx, statedb, tracer, nil, execOpt.txTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -642,10 +691,22 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
|
||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// traceBlock configures a new tracer according to the provided configuration, and
|
||||||
|
// executes all the transactions contained within. The return value will be one item
|
||||||
|
// per transaction, dependent on the requested tracer.
|
||||||
|
func (api *API) traceBlock(ctx context.Context, block *types.Block, env *traceEnv, execOpt *traceExecOptions, instantiateTracer func(*Context) (*Tracer, error)) ([]*txTraceResult, error) {
|
||||||
|
statedb, blockCtx, release, err := api.retrieveBlockState(ctx, block, execOpt, true, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer release()
|
||||||
|
return api.execBlockTrace(ctx, block, blockCtx, statedb, execOpt, instantiateTracer)
|
||||||
|
}
|
||||||
|
|
||||||
// traceBlockParallel is for tracers that have a high overhead (read JS tracers). One thread
|
// traceBlockParallel is for tracers that have a high overhead (read JS tracers). One thread
|
||||||
// runs along and executes txes without tracing enabled to generate their prestate.
|
// runs along and executes txes without tracing enabled to generate their prestate.
|
||||||
// Worker threads take the tasks and the prestate and trace them.
|
// Worker threads take the tasks and the prestate and trace them.
|
||||||
func (api *API) traceBlockParallel(ctx context.Context, block *types.Block, statedb *state.StateDB, config *TraceConfig) ([]*txTraceResult, error) {
|
func (api *API) traceBlockParallel(ctx context.Context, block *types.Block, statedb *state.StateDB, txTimeout time.Duration, instantiateTracer func(*Context) (*Tracer, error)) ([]*txTraceResult, error) {
|
||||||
// Execute all the transaction contained within the block concurrently
|
// Execute all the transaction contained within the block concurrently
|
||||||
var (
|
var (
|
||||||
txs = block.Transactions()
|
txs = block.Transactions()
|
||||||
|
|
@ -658,6 +719,7 @@ func (api *API) traceBlockParallel(ctx context.Context, block *types.Block, stat
|
||||||
if threads > len(txs) {
|
if threads > len(txs) {
|
||||||
threads = len(txs)
|
threads = len(txs)
|
||||||
}
|
}
|
||||||
|
|
||||||
jobs := make(chan *txTraceTask, threads)
|
jobs := make(chan *txTraceTask, threads)
|
||||||
for th := 0; th < threads; th++ {
|
for th := 0; th < threads; th++ {
|
||||||
pend.Add(1)
|
pend.Add(1)
|
||||||
|
|
@ -672,12 +734,17 @@ func (api *API) traceBlockParallel(ctx context.Context, block *types.Block, stat
|
||||||
TxIndex: task.index,
|
TxIndex: task.index,
|
||||||
TxHash: txs[task.index].Hash(),
|
TxHash: txs[task.index].Hash(),
|
||||||
}
|
}
|
||||||
|
tracer, err := instantiateTracer(txctx)
|
||||||
|
if err != nil {
|
||||||
|
results[task.index] = &txTraceResult{TxHash: txs[task.index].Hash(), Error: err.Error()}
|
||||||
|
continue
|
||||||
|
}
|
||||||
// Reconstruct the block context for each transaction
|
// Reconstruct the block context for each transaction
|
||||||
// as the GetHash function of BlockContext is not safe for
|
// as the GetHash function of BlockContext is not safe fs
|
||||||
// concurrent use.
|
// concurrent use.
|
||||||
// See: https://github.com/ethereum/go-ethereum/issues/29114
|
// See: https://github.com/ethereum/go-ethereum/issues/29114
|
||||||
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||||
res, err := api.traceTx(ctx, txs[task.index], msg, txctx, blockCtx, task.statedb, config, nil)
|
res, err := api.traceTx(ctx, txs[task.index], msg, txctx, blockCtx, task.statedb, tracer, nil, txTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
results[task.index] = &txTraceResult{TxHash: txs[task.index].Hash(), Error: err.Error()}
|
results[task.index] = &txTraceResult{TxHash: txs[task.index].Hash(), Error: err.Error()}
|
||||||
continue
|
continue
|
||||||
|
|
@ -725,108 +792,150 @@ txloop:
|
||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// standardTraceBlockToFile configures a new tracer which uses standard JSON output,
|
func createTraceDumpFile(blockHash, txHash common.Hash, txIdx int, canon bool) (*os.File, error) {
|
||||||
// and traces either a full block or an individual transaction. The return value will
|
// Generate a unique temporary file to dump it into
|
||||||
// be one filename per transaction traced.
|
prefix := fmt.Sprintf("block_%#x-%d-%#x-", blockHash.Bytes()[:4], txIdx, txHash.Bytes()[:4])
|
||||||
func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block, config *StdTraceConfig) ([]string, error) {
|
if !canon {
|
||||||
// If we're tracing a single transaction, make sure it's present
|
prefix = fmt.Sprintf("%valt-", prefix)
|
||||||
if config != nil && config.TxHash != (common.Hash{}) {
|
|
||||||
if !containsTx(block, config.TxHash) {
|
|
||||||
return nil, fmt.Errorf("transaction %#x not found in block", config.TxHash)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if block.NumberU64() == 0 {
|
dump, err := os.CreateTemp(os.TempDir(), prefix)
|
||||||
return nil, errors.New("genesis is not traceable")
|
|
||||||
}
|
|
||||||
parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(block.NumberU64()-1), block.ParentHash())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
reexec := defaultTraceReexec
|
return dump, nil
|
||||||
if config != nil && config.Reexec != nil {
|
}
|
||||||
reexec = *config.Reexec
|
|
||||||
|
func (api *API) standardTraceTxToFile(ctx context.Context, block *types.Block, tx *types.Transaction, txIdx int, config *logger.Config, signer types.Signer, canon bool) ([]string, error) {
|
||||||
|
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
|
||||||
|
txctx := &Context{
|
||||||
|
BlockHash: block.Hash(),
|
||||||
|
BlockNumber: block.Number(),
|
||||||
|
TxIndex: txIdx,
|
||||||
|
TxHash: tx.Hash(),
|
||||||
}
|
}
|
||||||
statedb, release, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false)
|
_, blockCtx, statedb, release, err := api.backend.StateAtTransaction(ctx, block, txIdx, defaultTraceReexec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer release()
|
defer release()
|
||||||
|
|
||||||
|
var dumps []string
|
||||||
|
var dump *os.File
|
||||||
|
|
||||||
|
dump, err = createTraceDumpFile(block.Hash(), tx.Hash(), txIdx, canon)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dumps = append(dumps, dump.Name())
|
||||||
|
|
||||||
|
// Swap out the noop logger to the standard traces
|
||||||
|
writer := bufio.NewWriter(dump)
|
||||||
|
|
||||||
|
var logConfig *logger.Config
|
||||||
|
if config != nil {
|
||||||
|
logConfig = config
|
||||||
|
}
|
||||||
|
|
||||||
|
logger := logger.NewJSONLogger(logConfig, writer)
|
||||||
|
tracer := Tracer{
|
||||||
|
Hooks: logger,
|
||||||
|
Stop: func(err error) {},
|
||||||
|
GetResult: func() (json.RawMessage, error) { return nil, nil },
|
||||||
|
}
|
||||||
|
traceTimeout := new(time.Duration)
|
||||||
|
*traceTimeout = 1 * time.Hour
|
||||||
|
_, err = api.traceTx(ctx, tx, msg, txctx, blockCtx, statedb, &tracer, nil, *traceTimeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dumps, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func traceCallExecOpt(config *TraceCallConfig) (*traceExecOptions, error) {
|
||||||
|
if config == nil {
|
||||||
|
return traceExecOpt(nil)
|
||||||
|
}
|
||||||
|
return traceExecOpt(&config.TraceConfig)
|
||||||
|
}
|
||||||
|
func traceExecOpt(config *TraceConfig) (*traceExecOptions, error) {
|
||||||
|
opt := &traceExecOptions{
|
||||||
|
defaultTraceTimeout,
|
||||||
|
defaultTraceReexec,
|
||||||
|
false,
|
||||||
|
}
|
||||||
|
if config != nil {
|
||||||
|
if config.Reexec != nil {
|
||||||
|
opt.reexec = *config.Reexec
|
||||||
|
}
|
||||||
|
if config.Timeout != nil {
|
||||||
|
timeout, err := time.ParseDuration(*config.Timeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
opt.txTimeout = timeout
|
||||||
|
}
|
||||||
|
// JS tracers have high overhead. In this case run a parallel
|
||||||
|
// process that generates states in one thread and traces txes
|
||||||
|
// in separate worker threads.
|
||||||
|
opt.parallel = config.Tracer != nil && *config.Tracer != "" && DefaultDirectory.IsJS(*config.Tracer)
|
||||||
|
}
|
||||||
|
return opt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func standardTraceExecOpt(config *StdTraceConfig) *traceExecOptions {
|
||||||
|
opt := traceExecOptions{
|
||||||
|
defaultTraceTimeout,
|
||||||
|
defaultTraceReexec,
|
||||||
|
false,
|
||||||
|
}
|
||||||
|
if config != nil {
|
||||||
|
if config.Reexec != nil {
|
||||||
|
opt.reexec = *config.Reexec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &opt
|
||||||
|
}
|
||||||
|
|
||||||
|
// standardTraceBlockToFile configures a new tracer which uses standard JSON output,
|
||||||
|
// and traces either a full block or an individual transaction. The return value will
|
||||||
|
// be one filename per transaction traced.
|
||||||
|
func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block, config *StdTraceConfig) ([]string, error) {
|
||||||
|
if block.NumberU64() == 0 {
|
||||||
|
return nil, errors.New("genesis is not traceable")
|
||||||
|
}
|
||||||
|
|
||||||
|
env := envFromStdConfig(api, config)
|
||||||
|
|
||||||
|
var signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
|
||||||
|
if config != nil && config.TxHash != (common.Hash{}) {
|
||||||
|
idx, ok := indexOf(block, config.TxHash)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("transaction %#x not found in block", config.TxHash)
|
||||||
|
}
|
||||||
|
return api.standardTraceTxToFile(ctx, block, block.Transactions()[idx], idx, &config.Config, signer, env.canon)
|
||||||
|
}
|
||||||
// Retrieve the tracing configurations, or use default values
|
// Retrieve the tracing configurations, or use default values
|
||||||
var (
|
var (
|
||||||
logConfig logger.Config
|
logConfig logger.Config
|
||||||
txHash common.Hash
|
|
||||||
)
|
)
|
||||||
if config != nil {
|
if config != nil {
|
||||||
logConfig = config.Config
|
logConfig = config.Config
|
||||||
txHash = config.TxHash
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute transaction, either tracing all or just the requested one
|
var dumps []string
|
||||||
var (
|
// TODO: move this comment somewhere more relevant
|
||||||
dumps []string
|
|
||||||
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
|
|
||||||
chainConfig = api.backend.ChainConfig()
|
|
||||||
vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
|
||||||
canon = true
|
|
||||||
)
|
|
||||||
// Check if there are any overrides: the caller may wish to enable a future
|
// Check if there are any overrides: the caller may wish to enable a future
|
||||||
// fork when executing this block. Note, such overrides are only applicable to the
|
// fork when executing this block. Note, such overrides are only applicable to the
|
||||||
// actual specified block, not any preceding blocks that we have to go through
|
// actual specified block, not any preceding blocks that we have to go through
|
||||||
// in order to obtain the state.
|
// in order to obtain the state.
|
||||||
// Therefore, it's perfectly valid to specify `"futureForkBlock": 0`, to enable `futureFork`
|
// Therefore, it's perfectly valid to specify `"futureForkBlock": 0`, to enable `futureFork`
|
||||||
if config != nil && config.Overrides != nil {
|
|
||||||
// Note: This copies the config, to not screw up the main config
|
|
||||||
chainConfig, canon = overrideConfig(chainConfig, config.Overrides)
|
|
||||||
}
|
|
||||||
|
|
||||||
evm := vm.NewEVM(vmctx, statedb, chainConfig, vm.Config{})
|
var (
|
||||||
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
dump *os.File
|
||||||
core.ProcessBeaconBlockRoot(*beaconRoot, evm)
|
writer *bufio.Writer
|
||||||
}
|
)
|
||||||
if chainConfig.IsPrague(block.Number(), block.Time()) {
|
instantiateTracer := func(ctx *Context) (*Tracer, error) {
|
||||||
core.ProcessParentBlockHash(block.ParentHash(), evm)
|
// cleanup previous tx trace if one just ran
|
||||||
}
|
|
||||||
for i, tx := range block.Transactions() {
|
|
||||||
// Prepare the transaction for un-traced execution
|
|
||||||
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
|
|
||||||
if txHash != (common.Hash{}) && tx.Hash() != txHash {
|
|
||||||
// Process the tx to update state, but don't trace it.
|
|
||||||
_, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(msg.GasLimit))
|
|
||||||
if err != nil {
|
|
||||||
return dumps, err
|
|
||||||
}
|
|
||||||
// Finalize the state so any modifications are written to the trie
|
|
||||||
// Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
|
|
||||||
statedb.Finalise(evm.ChainConfig().IsEIP158(block.Number()))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// The transaction should be traced.
|
|
||||||
// Generate a unique temporary file to dump it into.
|
|
||||||
prefix := fmt.Sprintf("block_%#x-%d-%#x-", block.Hash().Bytes()[:4], i, tx.Hash().Bytes()[:4])
|
|
||||||
if !canon {
|
|
||||||
prefix = fmt.Sprintf("%valt-", prefix)
|
|
||||||
}
|
|
||||||
var dump *os.File
|
|
||||||
dump, err := os.CreateTemp(os.TempDir(), prefix)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
dumps = append(dumps, dump.Name())
|
|
||||||
// Set up the tracer and EVM for the transaction.
|
|
||||||
var (
|
|
||||||
writer = bufio.NewWriter(dump)
|
|
||||||
tracer = logger.NewJSONLogger(&logConfig, writer)
|
|
||||||
evm = vm.NewEVM(vmctx, statedb, chainConfig, vm.Config{
|
|
||||||
Tracer: tracer,
|
|
||||||
NoBaseFee: true,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
// Execute the transaction and flush any traces to disk
|
|
||||||
statedb.SetTxContext(tx.Hash(), i)
|
|
||||||
if tracer.OnTxStart != nil {
|
|
||||||
tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
|
|
||||||
}
|
|
||||||
_, err = core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(msg.GasLimit))
|
|
||||||
if writer != nil {
|
if writer != nil {
|
||||||
writer.Flush()
|
writer.Flush()
|
||||||
}
|
}
|
||||||
|
|
@ -834,30 +943,52 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
|
||||||
dump.Close()
|
dump.Close()
|
||||||
log.Info("Wrote standard trace", "file", dump.Name())
|
log.Info("Wrote standard trace", "file", dump.Name())
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return dumps, err
|
|
||||||
}
|
|
||||||
// Finalize the state so any modifications are written to the trie
|
|
||||||
// Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
|
|
||||||
statedb.Finalise(evm.ChainConfig().IsEIP158(block.Number()))
|
|
||||||
|
|
||||||
// If we've traced the transaction we were looking for, abort
|
// instantiate a new logger for this tx which will dump the trace to a temp file
|
||||||
if tx.Hash() == txHash {
|
dump, err := createTraceDumpFile(ctx.BlockHash, ctx.TxHash, ctx.TxIndex, env.canon)
|
||||||
break
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
dumps = append(dumps, dump.Name())
|
||||||
|
|
||||||
|
// Swap out the noop logger to the standard tracer
|
||||||
|
writer = bufio.NewWriter(dump)
|
||||||
|
tracer := Tracer{
|
||||||
|
logger.NewJSONLogger(&logConfig, writer),
|
||||||
|
func() (json.RawMessage, error) {
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
func(err error) {},
|
||||||
|
}
|
||||||
|
return &tracer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
execOpt := standardTraceExecOpt(config)
|
||||||
|
execOpt.txTimeout = 100 * time.Hour
|
||||||
|
if _, err := api.traceBlock(ctx, block, env, execOpt, instantiateTracer); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanup dumping of the last trace in the block
|
||||||
|
if writer != nil {
|
||||||
|
writer.Flush()
|
||||||
|
}
|
||||||
|
if dump != nil {
|
||||||
|
dump.Close()
|
||||||
|
log.Info("Wrote standard trace", "file", dump.Name())
|
||||||
}
|
}
|
||||||
return dumps, nil
|
return dumps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// containsTx reports whether the transaction with a certain hash
|
// containsTx reports whether the transaction with a certain hash
|
||||||
// is contained within the specified block.
|
// is contained within the specified block.
|
||||||
func containsTx(block *types.Block, hash common.Hash) bool {
|
func indexOf(block *types.Block, hash common.Hash) (int, bool) {
|
||||||
for _, tx := range block.Transactions() {
|
for i, tx := range block.Transactions() {
|
||||||
if tx.Hash() == hash {
|
if tx.Hash() == hash {
|
||||||
return true
|
return i, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// TraceTransaction returns the structured logs created during the execution of EVM
|
// TraceTransaction returns the structured logs created during the execution of EVM
|
||||||
|
|
@ -876,15 +1007,15 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *
|
||||||
if blockNumber == 0 {
|
if blockNumber == 0 {
|
||||||
return nil, errors.New("genesis is not traceable")
|
return nil, errors.New("genesis is not traceable")
|
||||||
}
|
}
|
||||||
reexec := defaultTraceReexec
|
opt, err := traceExecOpt(config)
|
||||||
if config != nil && config.Reexec != nil {
|
if err != nil {
|
||||||
reexec = *config.Reexec
|
return nil, err
|
||||||
}
|
}
|
||||||
block, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(blockNumber), blockHash)
|
block, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(blockNumber), blockHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tx, vmctx, statedb, release, err := api.backend.StateAtTransaction(ctx, block, int(index), reexec)
|
tx, vmctx, statedb, release, err := api.backend.StateAtTransaction(ctx, block, int(index), opt.reexec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -900,7 +1031,11 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *
|
||||||
TxIndex: int(index),
|
TxIndex: int(index),
|
||||||
TxHash: hash,
|
TxHash: hash,
|
||||||
}
|
}
|
||||||
return api.traceTx(ctx, tx, msg, txctx, vmctx, statedb, config, nil)
|
tracer, err := api.instantiateTracer(config, txctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return api.traceTx(ctx, tx, msg, txctx, vmctx, statedb, tracer, nil, opt.txTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TraceCall lets you trace a given eth_call. It collects the structured logs
|
// TraceCall lets you trace a given eth_call. It collects the structured logs
|
||||||
|
|
@ -938,15 +1073,16 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// try to recompute the state
|
// try to recompute the state
|
||||||
reexec := defaultTraceReexec
|
|
||||||
if config != nil && config.Reexec != nil {
|
opt, err := traceCallExecOpt(config)
|
||||||
reexec = *config.Reexec
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if config != nil && config.TxIndex != nil {
|
if config != nil && config.TxIndex != nil {
|
||||||
_, _, statedb, release, err = api.backend.StateAtTransaction(ctx, block, int(*config.TxIndex), reexec)
|
_, _, statedb, release, err = api.backend.StateAtTransaction(ctx, block, int(*config.TxIndex), opt.reexec)
|
||||||
} else {
|
} else {
|
||||||
statedb, release, err = api.backend.StateAtBlock(ctx, block, reexec, nil, true, false)
|
statedb, release, err = api.backend.StateAtBlock(ctx, block, opt.reexec, nil, true, false)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -983,22 +1119,16 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
|
||||||
if msg.BlobGasFeeCap != nil && msg.BlobGasFeeCap.BitLen() == 0 {
|
if msg.BlobGasFeeCap != nil && msg.BlobGasFeeCap.BitLen() == 0 {
|
||||||
vmctx.BlobBaseFee = new(big.Int)
|
vmctx.BlobBaseFee = new(big.Int)
|
||||||
}
|
}
|
||||||
if config != nil {
|
tracer, err := api.instantiateTracer(traceConfig, new(Context))
|
||||||
traceConfig = &config.TraceConfig
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
return api.traceTx(ctx, tx, msg, new(Context), vmctx, statedb, traceConfig, precompiles)
|
return api.traceTx(ctx, tx, msg, new(Context), vmctx, statedb, tracer, precompiles, opt.txTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
// traceTx configures a new tracer according to the provided configuration, and
|
func (api *API) instantiateTracer(config *TraceConfig, txctx *Context) (*Tracer, error) {
|
||||||
// executes the given message in the provided environment. The return value will
|
var tracer *Tracer
|
||||||
// be tracer dependent.
|
var err error
|
||||||
func (api *API) traceTx(ctx context.Context, tx *types.Transaction, message *core.Message, txctx *Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig, precompiles vm.PrecompiledContracts) (interface{}, error) {
|
|
||||||
var (
|
|
||||||
tracer *Tracer
|
|
||||||
err error
|
|
||||||
timeout = defaultTraceTimeout
|
|
||||||
usedGas uint64
|
|
||||||
)
|
|
||||||
if config == nil {
|
if config == nil {
|
||||||
config = &TraceConfig{}
|
config = &TraceConfig{}
|
||||||
}
|
}
|
||||||
|
|
@ -1011,23 +1141,30 @@ func (api *API) traceTx(ctx context.Context, tx *types.Transaction, message *cor
|
||||||
Stop: logger.Stop,
|
Stop: logger.Stop,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tracer, err = DefaultDirectory.New(*config.Tracer, txctx, config.TracerConfig, api.backend.ChainConfig())
|
chainConfig := api.backend.ChainConfig()
|
||||||
if err != nil {
|
if config.Config.Overrides != nil {
|
||||||
return nil, err
|
chainConfig = config.Config.Overrides
|
||||||
}
|
}
|
||||||
|
tracer, err = DefaultDirectory.New(*config.Tracer, txctx, config.TracerConfig, chainConfig)
|
||||||
}
|
}
|
||||||
|
return tracer, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: modify traceTx to take a tracer directly. move logic for instantiation from TraceConfig to a separate function.
|
||||||
|
// traceTx configures a new tracer according to the provided configuration, and
|
||||||
|
// executes the given message in the provided environment. The return value will
|
||||||
|
// be tracer dependent.
|
||||||
|
func (api *API) traceTx(ctx context.Context, tx *types.Transaction, message *core.Message, txctx *Context, vmctx vm.BlockContext, statedb *state.StateDB, tracer *Tracer, precompiles vm.PrecompiledContracts, timeout time.Duration) (interface{}, error) {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
usedGas uint64
|
||||||
|
)
|
||||||
tracingStateDB := state.NewHookedState(statedb, tracer.Hooks)
|
tracingStateDB := state.NewHookedState(statedb, tracer.Hooks)
|
||||||
evm := vm.NewEVM(vmctx, tracingStateDB, api.backend.ChainConfig(), vm.Config{Tracer: tracer.Hooks, NoBaseFee: true})
|
evm := vm.NewEVM(vmctx, tracingStateDB, api.backend.ChainConfig(), vm.Config{Tracer: tracer.Hooks, NoBaseFee: true})
|
||||||
if precompiles != nil {
|
if precompiles != nil {
|
||||||
evm.SetPrecompiles(precompiles)
|
evm.SetPrecompiles(precompiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define a meaningful timeout of a single transaction trace
|
|
||||||
if config.Timeout != nil {
|
|
||||||
if timeout, err = time.ParseDuration(*config.Timeout); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
|
deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
|
||||||
go func() {
|
go func() {
|
||||||
<-deadlineCtx.Done()
|
<-deadlineCtx.Done()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue