Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

eth/tracers: expose contextual infos (block hash, tx hash, tx index) #23104

Merged
merged 1 commit into from
Jun 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 16 additions & 23 deletions eth/tracers/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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 {
Expand Down
20 changes: 18 additions & 2 deletions eth/tracers/tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I probably missed the memo, just wondering why you exported the fields?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's part of the API (tracers.New).

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
Expand All @@ -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)))
Expand Down Expand Up @@ -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)
Expand Down
27 changes: 13 additions & 14 deletions eth/tracers/tracer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -132,33 +134,28 @@ 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)
}
env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer})
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)
Expand All @@ -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)
}
Expand Down
4 changes: 2 additions & 2 deletions eth/tracers/tracers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down