From 29905d86aef336bb6a12ba61403008e068226a10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 24 Jun 2021 12:46:26 +0300 Subject: [PATCH] eth/tracers: expose contextual infos (block hash, tx hash, tx index) --- eth/tracers/api.go | 39 +++++++++++++++---------------------- eth/tracers/tracer.go | 20 +++++++++++++++++-- eth/tracers/tracer_test.go | 27 +++++++++++++------------ eth/tracers/tracers_test.go | 4 ++-- 4 files changed, 49 insertions(+), 41 deletions(-) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 65bf84dc430f..c6cd64688116 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -178,13 +178,6 @@ type StdTraceConfig struct { TxHash common.Hash } -// txTraceContext is the contextual infos about a transaction before it gets run. -type txTraceContext struct { - index int // Index of the transaction within the block - hash common.Hash // Hash of the transaction - block common.Hash // Hash of the block containing the transaction -} - // txTraceResult is the result of a single transaction trace. type txTraceResult struct { Result interface{} `json:"result,omitempty"` // Trace results produced by the tracer @@ -272,10 +265,10 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config // Trace all the transactions contained within for i, tx := range task.block.Transactions() { msg, _ := tx.AsMessage(signer, task.block.BaseFee()) - txctx := &txTraceContext{ - index: i, - hash: tx.Hash(), - block: task.block.Hash(), + txctx := &Context{ + BlockHash: task.block.Hash(), + TxIndex: i, + TxHash: tx.Hash(), } res, err := api.traceTx(localctx, msg, txctx, blockCtx, task.statedb, config) if err != nil { @@ -524,10 +517,10 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac // Fetch and execute the next transaction trace tasks for task := range jobs { msg, _ := txs[task.index].AsMessage(signer, block.BaseFee()) - txctx := &txTraceContext{ - index: task.index, - hash: txs[task.index].Hash(), - block: blockHash, + txctx := &Context{ + BlockHash: blockHash, + TxIndex: task.index, + TxHash: txs[task.index].Hash(), } res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config) if err != nil { @@ -718,10 +711,10 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config * if err != nil { return nil, err } - txctx := &txTraceContext{ - index: int(index), - hash: hash, - block: blockHash, + txctx := &Context{ + BlockHash: blockHash, + TxIndex: int(index), + TxHash: hash, } return api.traceTx(ctx, msg, txctx, vmctx, statedb, config) } @@ -777,13 +770,13 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc Reexec: config.Reexec, } } - return api.traceTx(ctx, msg, new(txTraceContext), vmctx, statedb, traceConfig) + return api.traceTx(ctx, msg, new(Context), vmctx, statedb, traceConfig) } // 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, message core.Message, txctx *txTraceContext, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { +func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { // Assemble the structured logger or the JavaScript tracer var ( tracer vm.Tracer @@ -800,7 +793,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *txTrac } } // Constuct the JavaScript tracer to execute with - if tracer, err = New(*config.Tracer, txContext); err != nil { + if tracer, err = New(*config.Tracer, txctx); err != nil { return nil, err } // Handle timeouts and RPC cancellations @@ -823,7 +816,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *txTrac vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vm.Config{Debug: true, Tracer: tracer, NoBaseFee: true}) // Call Prepare to clear out the statedb access list - statedb.Prepare(txctx.hash, txctx.block, txctx.index) + statedb.Prepare(txctx.TxHash, txctx.BlockHash, txctx.TxIndex) result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas())) if err != nil { diff --git a/eth/tracers/tracer.go b/eth/tracers/tracer.go index 218903dd92a0..0b216e362824 100644 --- a/eth/tracers/tracer.go +++ b/eth/tracers/tracer.go @@ -312,10 +312,18 @@ type Tracer struct { reason error // Textual reason for the interruption } +// Context contains some contextual infos for a transaction execution that is not +// available from within the EVM object. +type Context struct { + BlockHash common.Hash // Hash of the block the tx is contained within (zero if dangling tx or call) + TxIndex int // Index of the transaction within a block (zero if dangling tx or call) + TxHash common.Hash // Hash of the transaction being traced (zero if dangling call) +} + // New instantiates a new tracer instance. code specifies a Javascript snippet, // which must evaluate to an expression returning an object with 'step', 'fault' // and 'result' functions. -func New(code string, txCtx vm.TxContext) (*Tracer, error) { +func New(code string, ctx *Context) (*Tracer, error) { // Resolve any tracers by name and assemble the tracer object if tracer, ok := tracer(code); ok { code = tracer @@ -334,8 +342,14 @@ func New(code string, txCtx vm.TxContext) (*Tracer, error) { depthValue: new(uint), refundValue: new(uint), } - tracer.ctx["gasPrice"] = txCtx.GasPrice + if ctx.BlockHash != (common.Hash{}) { + tracer.ctx["blockHash"] = ctx.BlockHash + if ctx.TxHash != (common.Hash{}) { + tracer.ctx["txIndex"] = ctx.TxIndex + tracer.ctx["txHash"] = ctx.TxHash + } + } // Set up builtins for this environment tracer.vm.PushGlobalGoFunction("toHex", func(ctx *duktape.Context) int { ctx.PushString(hexutil.Encode(popSlice(ctx))) @@ -550,11 +564,13 @@ func (jst *Tracer) CaptureStart(env *vm.EVM, from common.Address, to common.Addr jst.ctx["to"] = to jst.ctx["input"] = input jst.ctx["gas"] = gas + jst.ctx["gasPrice"] = env.TxContext.GasPrice jst.ctx["value"] = value // Initialize the context jst.ctx["block"] = env.Context.BlockNumber.Uint64() jst.dbWrapper.db = env.StateDB + // Compute intrinsic gas isHomestead := env.ChainConfig().IsHomestead(env.Context.BlockNumber) isIstanbul := env.ChainConfig().IsIstanbul(env.Context.BlockNumber) diff --git a/eth/tracers/tracer_test.go b/eth/tracers/tracer_test.go index 033824474d26..038b3944c31b 100644 --- a/eth/tracers/tracer_test.go +++ b/eth/tracers/tracer_test.go @@ -80,12 +80,14 @@ func runTrace(tracer *Tracer, vmctx *vmContext) (json.RawMessage, error) { func TestTracer(t *testing.T) { execTracer := func(code string) ([]byte, string) { t.Helper() - ctx := &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}} - tracer, err := New(code, ctx.txCtx) + tracer, err := New(code, new(Context)) if err != nil { t.Fatal(err) } - ret, err := runTrace(tracer, ctx) + ret, err := runTrace(tracer, &vmContext{ + blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, + txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}, + }) if err != nil { return nil, err.Error() // Stringify to allow comparison without nil checks } @@ -132,25 +134,21 @@ func TestHalt(t *testing.T) { t.Skip("duktape doesn't support abortion") timeout := errors.New("stahp") - vmctx := testCtx() - tracer, err := New("{step: function() { while(1); }, result: function() { return null; }}", vmctx.txCtx) + tracer, err := New("{step: function() { while(1); }, result: function() { return null; }}", new(Context)) if err != nil { t.Fatal(err) } - go func() { time.Sleep(1 * time.Second) tracer.Stop(timeout) }() - - if _, err = runTrace(tracer, vmctx); err.Error() != "stahp in server-side tracer function 'step'" { + if _, err = runTrace(tracer, testCtx()); err.Error() != "stahp in server-side tracer function 'step'" { t.Errorf("Expected timeout error, got %v", err) } } func TestHaltBetweenSteps(t *testing.T) { - vmctx := testCtx() - tracer, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }}", vmctx.txCtx) + tracer, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }}", new(Context)) if err != nil { t.Fatal(err) } @@ -158,7 +156,6 @@ func TestHaltBetweenSteps(t *testing.T) { scope := &vm.ScopeContext{ Contract: vm.NewContract(&account{}, &account{}, big.NewInt(0), 0), } - tracer.CaptureState(env, 0, 0, 0, 0, scope, nil, 0, nil) timeout := errors.New("stahp") tracer.Stop(timeout) @@ -182,12 +179,14 @@ func TestNoStepExec(t *testing.T) { } execTracer := func(code string) []byte { t.Helper() - ctx := &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}} - tracer, err := New(code, ctx.txCtx) + tracer, err := New(code, new(Context)) if err != nil { t.Fatal(err) } - ret, err := runEmptyTrace(tracer, ctx) + ret, err := runEmptyTrace(tracer, &vmContext{ + blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, + txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}, + }) if err != nil { t.Fatal(err) } diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index 8b01edd7b4cc..4f4f7c1edec1 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -173,7 +173,7 @@ func TestPrestateTracerCreate2(t *testing.T) { _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false) // Create the tracer, the EVM environment and run it - tracer, err := New("prestateTracer", txContext) + tracer, err := New("prestateTracer", new(Context)) if err != nil { t.Fatalf("failed to create call tracer: %v", err) } @@ -248,7 +248,7 @@ func TestCallTracer(t *testing.T) { _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) // Create the tracer, the EVM environment and run it - tracer, err := New("callTracer", txContext) + tracer, err := New("callTracer", new(Context)) if err != nil { t.Fatalf("failed to create call tracer: %v", err) }