eth/tracers: expose contextual infos (block hash, tx hash, tx index)
This commit is contained in:
parent
10eb654f27
commit
29905d86ae
@ -178,13 +178,6 @@ type StdTraceConfig struct {
|
|||||||
TxHash common.Hash
|
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.
|
// txTraceResult is the result of a single transaction trace.
|
||||||
type txTraceResult struct {
|
type txTraceResult struct {
|
||||||
Result interface{} `json:"result,omitempty"` // Trace results produced by the tracer
|
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
|
// Trace all the transactions contained within
|
||||||
for i, tx := range task.block.Transactions() {
|
for i, tx := range task.block.Transactions() {
|
||||||
msg, _ := tx.AsMessage(signer, task.block.BaseFee())
|
msg, _ := tx.AsMessage(signer, task.block.BaseFee())
|
||||||
txctx := &txTraceContext{
|
txctx := &Context{
|
||||||
index: i,
|
BlockHash: task.block.Hash(),
|
||||||
hash: tx.Hash(),
|
TxIndex: i,
|
||||||
block: task.block.Hash(),
|
TxHash: tx.Hash(),
|
||||||
}
|
}
|
||||||
res, err := api.traceTx(localctx, msg, txctx, blockCtx, task.statedb, config)
|
res, err := api.traceTx(localctx, msg, txctx, blockCtx, task.statedb, config)
|
||||||
if err != nil {
|
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
|
// Fetch and execute the next transaction trace tasks
|
||||||
for task := range jobs {
|
for task := range jobs {
|
||||||
msg, _ := txs[task.index].AsMessage(signer, block.BaseFee())
|
msg, _ := txs[task.index].AsMessage(signer, block.BaseFee())
|
||||||
txctx := &txTraceContext{
|
txctx := &Context{
|
||||||
index: task.index,
|
BlockHash: blockHash,
|
||||||
hash: txs[task.index].Hash(),
|
TxIndex: task.index,
|
||||||
block: blockHash,
|
TxHash: txs[task.index].Hash(),
|
||||||
}
|
}
|
||||||
res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config)
|
res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -718,10 +711,10 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
txctx := &txTraceContext{
|
txctx := &Context{
|
||||||
index: int(index),
|
BlockHash: blockHash,
|
||||||
hash: hash,
|
TxIndex: int(index),
|
||||||
block: blockHash,
|
TxHash: hash,
|
||||||
}
|
}
|
||||||
return api.traceTx(ctx, msg, txctx, vmctx, statedb, config)
|
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,
|
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
|
// traceTx configures a new tracer according to the provided configuration, and
|
||||||
// executes the given message in the provided environment. The return value will
|
// executes the given message in the provided environment. The return value will
|
||||||
// be tracer dependent.
|
// 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
|
// Assemble the structured logger or the JavaScript tracer
|
||||||
var (
|
var (
|
||||||
tracer vm.Tracer
|
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
|
// 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
|
return nil, err
|
||||||
}
|
}
|
||||||
// Handle timeouts and RPC cancellations
|
// 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})
|
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
|
// 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()))
|
result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -312,10 +312,18 @@ type Tracer struct {
|
|||||||
reason error // Textual reason for the interruption
|
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,
|
// New instantiates a new tracer instance. code specifies a Javascript snippet,
|
||||||
// which must evaluate to an expression returning an object with 'step', 'fault'
|
// which must evaluate to an expression returning an object with 'step', 'fault'
|
||||||
// and 'result' functions.
|
// 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
|
// Resolve any tracers by name and assemble the tracer object
|
||||||
if tracer, ok := tracer(code); ok {
|
if tracer, ok := tracer(code); ok {
|
||||||
code = tracer
|
code = tracer
|
||||||
@ -334,8 +342,14 @@ func New(code string, txCtx vm.TxContext) (*Tracer, error) {
|
|||||||
depthValue: new(uint),
|
depthValue: new(uint),
|
||||||
refundValue: 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
|
// Set up builtins for this environment
|
||||||
tracer.vm.PushGlobalGoFunction("toHex", func(ctx *duktape.Context) int {
|
tracer.vm.PushGlobalGoFunction("toHex", func(ctx *duktape.Context) int {
|
||||||
ctx.PushString(hexutil.Encode(popSlice(ctx)))
|
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["to"] = to
|
||||||
jst.ctx["input"] = input
|
jst.ctx["input"] = input
|
||||||
jst.ctx["gas"] = gas
|
jst.ctx["gas"] = gas
|
||||||
|
jst.ctx["gasPrice"] = env.TxContext.GasPrice
|
||||||
jst.ctx["value"] = value
|
jst.ctx["value"] = value
|
||||||
|
|
||||||
// Initialize the context
|
// Initialize the context
|
||||||
jst.ctx["block"] = env.Context.BlockNumber.Uint64()
|
jst.ctx["block"] = env.Context.BlockNumber.Uint64()
|
||||||
jst.dbWrapper.db = env.StateDB
|
jst.dbWrapper.db = env.StateDB
|
||||||
|
|
||||||
// Compute intrinsic gas
|
// Compute intrinsic gas
|
||||||
isHomestead := env.ChainConfig().IsHomestead(env.Context.BlockNumber)
|
isHomestead := env.ChainConfig().IsHomestead(env.Context.BlockNumber)
|
||||||
isIstanbul := env.ChainConfig().IsIstanbul(env.Context.BlockNumber)
|
isIstanbul := env.ChainConfig().IsIstanbul(env.Context.BlockNumber)
|
||||||
|
@ -80,12 +80,14 @@ func runTrace(tracer *Tracer, vmctx *vmContext) (json.RawMessage, error) {
|
|||||||
func TestTracer(t *testing.T) {
|
func TestTracer(t *testing.T) {
|
||||||
execTracer := func(code string) ([]byte, string) {
|
execTracer := func(code string) ([]byte, string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
ctx := &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}}
|
tracer, err := New(code, new(Context))
|
||||||
tracer, err := New(code, ctx.txCtx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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 {
|
if err != nil {
|
||||||
return nil, err.Error() // Stringify to allow comparison without nil checks
|
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")
|
t.Skip("duktape doesn't support abortion")
|
||||||
|
|
||||||
timeout := errors.New("stahp")
|
timeout := errors.New("stahp")
|
||||||
vmctx := testCtx()
|
tracer, err := New("{step: function() { while(1); }, result: function() { return null; }}", new(Context))
|
||||||
tracer, err := New("{step: function() { while(1); }, result: function() { return null; }}", vmctx.txCtx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
tracer.Stop(timeout)
|
tracer.Stop(timeout)
|
||||||
}()
|
}()
|
||||||
|
if _, err = runTrace(tracer, testCtx()); err.Error() != "stahp in server-side tracer function 'step'" {
|
||||||
if _, err = runTrace(tracer, vmctx); err.Error() != "stahp in server-side tracer function 'step'" {
|
|
||||||
t.Errorf("Expected timeout error, got %v", err)
|
t.Errorf("Expected timeout error, got %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHaltBetweenSteps(t *testing.T) {
|
func TestHaltBetweenSteps(t *testing.T) {
|
||||||
vmctx := testCtx()
|
tracer, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }}", new(Context))
|
||||||
tracer, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }}", vmctx.txCtx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -158,7 +156,6 @@ func TestHaltBetweenSteps(t *testing.T) {
|
|||||||
scope := &vm.ScopeContext{
|
scope := &vm.ScopeContext{
|
||||||
Contract: vm.NewContract(&account{}, &account{}, big.NewInt(0), 0),
|
Contract: vm.NewContract(&account{}, &account{}, big.NewInt(0), 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
tracer.CaptureState(env, 0, 0, 0, 0, scope, nil, 0, nil)
|
tracer.CaptureState(env, 0, 0, 0, 0, scope, nil, 0, nil)
|
||||||
timeout := errors.New("stahp")
|
timeout := errors.New("stahp")
|
||||||
tracer.Stop(timeout)
|
tracer.Stop(timeout)
|
||||||
@ -182,12 +179,14 @@ func TestNoStepExec(t *testing.T) {
|
|||||||
}
|
}
|
||||||
execTracer := func(code string) []byte {
|
execTracer := func(code string) []byte {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
ctx := &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}}
|
tracer, err := New(code, new(Context))
|
||||||
tracer, err := New(code, ctx.txCtx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -173,7 +173,7 @@ func TestPrestateTracerCreate2(t *testing.T) {
|
|||||||
_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false)
|
_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false)
|
||||||
|
|
||||||
// Create the tracer, the EVM environment and run it
|
// Create the tracer, the EVM environment and run it
|
||||||
tracer, err := New("prestateTracer", txContext)
|
tracer, err := New("prestateTracer", new(Context))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create call tracer: %v", err)
|
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)
|
_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
|
||||||
|
|
||||||
// Create the tracer, the EVM environment and run it
|
// Create the tracer, the EVM environment and run it
|
||||||
tracer, err := New("callTracer", txContext)
|
tracer, err := New("callTracer", new(Context))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create call tracer: %v", err)
|
t.Fatalf("failed to create call tracer: %v", err)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user