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

This commit is contained in:
Daniel Liu 2025-08-22 16:40:18 +08:00 committed by GitHub
parent 516883d14c
commit ad45cb55fe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 28 additions and 29 deletions

View file

@ -646,7 +646,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
return nil, err return nil, err
} }
} }
if t, err := New(*config.Tracer, txContext, txctx, config.TracerConfig); err != nil { if t, err := New(*config.Tracer, txctx, config.TracerConfig); err != nil {
return nil, err return nil, err
} else { } else {
deadlineCtx, cancel := context.WithTimeout(ctx, timeout) deadlineCtx, cancel := context.WithTimeout(ctx, timeout)

View file

@ -312,7 +312,7 @@ func TestTraceCall(t *testing.T) {
} }
} }
func TestOverridenTraceCall(t *testing.T) { func TestOverriddenTraceCall(t *testing.T) {
t.Parallel() t.Parallel()
// Initialize test accounts // Initialize test accounts

View file

@ -110,7 +110,7 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
} }
statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc) statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc)
) )
tracer, err := tracers.New(tracerName, txContext, new(tracers.Context), test.TracerConfig) tracer, err := tracers.New(tracerName, new(tracers.Context), test.TracerConfig)
if err != nil { if err != nil {
t.Fatalf("failed to create call tracer: %v", err) t.Fatalf("failed to create call tracer: %v", err)
} }
@ -223,7 +223,7 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
tracer, err := tracers.New(tracerName, txContext, new(tracers.Context), nil) tracer, err := tracers.New(tracerName, new(tracers.Context), nil)
if err != nil { if err != nil {
b.Fatalf("failed to create call tracer: %v", err) b.Fatalf("failed to create call tracer: %v", err)
} }
@ -292,7 +292,7 @@ func testContractTracer(tracerName string, dirPath string, t *testing.T) {
} }
statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc) statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc)
) )
tracer, err := tracers.New(tracerName, txContext, new(tracers.Context), test.TracerConfig) tracer, err := tracers.New(tracerName, new(tracers.Context), test.TracerConfig)
if err != nil { if err != nil {
t.Fatalf("failed to create call tracer: %v", err) t.Fatalf("failed to create call tracer: %v", err)
} }

View file

@ -411,7 +411,7 @@ type Context struct {
// 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.
// TODO gerui rename to private func // TODO gerui rename to private func
func NewJsTracer(code string, txCtx vm.TxContext, ctx *Context) (*JsTracer, error) { func NewJsTracer(code string, ctx *Context) (*JsTracer, error) {
tracer := &JsTracer{ tracer := &JsTracer{
vm: duktape.New(), vm: duktape.New(),
ctx: make(map[string]interface{}), ctx: make(map[string]interface{}),
@ -428,7 +428,6 @@ func NewJsTracer(code string, txCtx vm.TxContext, ctx *Context) (*JsTracer, erro
frame: newFrame(), frame: newFrame(),
frameResult: newFrameResult(), frameResult: newFrameResult(),
} }
tracer.ctx["gasPrice"] = txCtx.GasPrice
if ctx.BlockHash != (common.Hash{}) { if ctx.BlockHash != (common.Hash{}) {
tracer.ctx["blockHash"] = ctx.BlockHash tracer.ctx["blockHash"] = ctx.BlockHash
@ -678,6 +677,7 @@ func (jst *JsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Ad
// 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
// Update list of precompiles based on current block // Update list of precompiles based on current block
rules := env.ChainConfig().Rules(env.Context.BlockNumber) rules := env.ChainConfig().Rules(env.Context.BlockNumber)
jst.activePrecompiles = vm.ActivePrecompiles(rules) jst.activePrecompiles = vm.ActivePrecompiles(rules)

View file

@ -80,11 +80,14 @@ func runTrace(tracer Tracer, vmctx *vmContext, chaincfg *params.ChainConfig) (js
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{ctx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txContext: vm.TxContext{GasPrice: big.NewInt(100000)}} tracer, err := New(code, new(Context), nil)
tracer, err := New(code, ctx.txContext, new(Context), nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
ctx := &vmContext{
ctx: vm.BlockContext{BlockNumber: big.NewInt(1)},
txContext: vm.TxContext{GasPrice: big.NewInt(100000)},
}
ret, err := runTrace(tracer, ctx, params.TestChainConfig) ret, err := runTrace(tracer, ctx, params.TestChainConfig)
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,8 +135,7 @@ 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), nil)
tracer, err := New("{step: function() { while(1); }, result: function() { return null; }}", vmctx.txContext, new(Context), nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -141,14 +143,13 @@ func TestHalt(t *testing.T) {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
tracer.Stop(timeout) tracer.Stop(timeout)
}() }()
if _, err = runTrace(tracer, vmctx, params.TestChainConfig); err.Error() != "stahp in server-side tracer function 'step'" { if _, err = runTrace(tracer, testCtx(), params.TestChainConfig); 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), nil)
tracer, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }}", vmctx.txContext, new(Context), nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -171,13 +172,13 @@ func TestHaltBetweenSteps(t *testing.T) {
func TestNoStepExec(t *testing.T) { func TestNoStepExec(t *testing.T) {
execTracer := func(code string) []byte { execTracer := func(code string) []byte {
t.Helper() t.Helper()
txContext := vm.TxContext{GasPrice: big.NewInt(100000)} tracer, err := New(code, new(Context), nil)
tracer, err := New(code, txContext, new(Context), nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
ctx := vm.BlockContext{BlockNumber: big.NewInt(1)} txCtx := vm.TxContext{GasPrice: big.NewInt(100000)}
env := vm.NewEVM(ctx, txContext, &dummyStatedb{}, nil, params.TestChainConfig, vm.Config{Tracer: tracer}) blockCtx := vm.BlockContext{BlockNumber: big.NewInt(1)}
env := vm.NewEVM(blockCtx, txCtx, &dummyStatedb{}, nil, params.TestChainConfig, vm.Config{Tracer: tracer})
tracer.CaptureStart(env, common.Address{}, common.Address{}, false, []byte{}, 1000, big.NewInt(0)) tracer.CaptureStart(env, common.Address{}, common.Address{}, false, []byte{}, 1000, big.NewInt(0))
tracer.CaptureEnd(nil, 0, 1, nil) tracer.CaptureEnd(nil, 0, 1, nil)
ret, err := tracer.GetResult() ret, err := tracer.GetResult()
@ -224,7 +225,7 @@ func TestIsPrecompile(t *testing.T) {
chaincfg.BerlinBlock = big.NewInt(300) chaincfg.BerlinBlock = big.NewInt(300)
txCtx := vm.TxContext{GasPrice: big.NewInt(100000)} txCtx := vm.TxContext{GasPrice: big.NewInt(100000)}
tracer, err := New("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", txCtx, new(Context), nil) tracer, err := New("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", new(Context), nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -239,7 +240,7 @@ func TestIsPrecompile(t *testing.T) {
} }
blockCtx = vm.BlockContext{BlockNumber: big.NewInt(250)} blockCtx = vm.BlockContext{BlockNumber: big.NewInt(250)}
tracer, _ = New("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", txCtx, new(Context), nil) tracer, _ = New("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", new(Context), nil)
res, err = runTrace(tracer, &vmContext{blockCtx, txCtx}, chaincfg) res, err = runTrace(tracer, &vmContext{blockCtx, txCtx}, chaincfg)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
@ -250,18 +251,16 @@ func TestIsPrecompile(t *testing.T) {
} }
func TestEnterExit(t *testing.T) { func TestEnterExit(t *testing.T) {
txCtx := vm.TxContext{GasPrice: big.NewInt(100000)}
// test that either both or none of enter() and exit() are defined // test that either both or none of enter() and exit() are defined
if _, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}}", txCtx, new(Context), nil); err == nil { if _, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}}", new(Context), nil); err == nil {
t.Fatal("tracer creation should've failed without exit() definition") t.Fatal("tracer creation should've failed without exit() definition")
} }
if _, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}, exit: function() {}}", txCtx, new(Context), nil); err != nil { if _, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}, exit: function() {}}", new(Context), nil); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// test that the enter and exit method are correctly invoked and the values passed // test that the enter and exit method are correctly invoked and the values passed
tracer, err := New("{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, step: function() {}, fault: function() {}, result: function() { return {enters: this.enters, exits: this.exits, enterGas: this.enterGas, gasUsed: this.gasUsed} }, enter: function(frame) { this.enters++; this.enterGas = frame.getGas(); }, exit: function(res) { this.exits++; this.gasUsed = res.getGasUsed(); }}", txCtx, new(Context), nil) tracer, err := New("{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, step: function() {}, fault: function() {}, result: function() { return {enters: this.enters, exits: this.exits, enterGas: this.enterGas, gasUsed: this.gasUsed} }, enter: function(frame) { this.enters++; this.enterGas = frame.getGas(); }, exit: function(res) { this.exits++; this.gasUsed = res.getGasUsed(); }}", new(Context), nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -57,7 +57,7 @@ func RegisterNativeTracer(name string, ctor ctorFn) {
// instantiated and returned // instantiated and returned
// 3. Otherwise, the code is interpreted as the js code of a js-tracer, and // 3. Otherwise, the code is interpreted as the js code of a js-tracer, and
// is evaluated and returned. // is evaluated and returned.
func New(code string, txCtx vm.TxContext, ctx *Context, cfg json.RawMessage) (Tracer, error) { func New(code string, ctx *Context, cfg json.RawMessage) (Tracer, error) {
// Resolve native tracer // Resolve native tracer
if fn, ok := nativeTracers[code]; ok { if fn, ok := nativeTracers[code]; ok {
return fn(cfg) return fn(cfg)
@ -66,7 +66,7 @@ func New(code string, txCtx vm.TxContext, ctx *Context, cfg json.RawMessage) (Tr
if tracer, ok := jsTracers[code]; ok { if tracer, ok := jsTracers[code]; ok {
code = tracer code = tracer
} }
return NewJsTracer(code, txCtx, ctx) return NewJsTracer(code, ctx)
} }
// camel converts a snake cased input string into a camel cased output. // camel converts a snake cased input string into a camel cased output.

View file

@ -148,7 +148,7 @@ func TestZeroValueToNotExitCall(t *testing.T) {
} }
statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc) statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc)
// Create the tracer, the EVM environment and run it // Create the tracer, the EVM environment and run it
tracer, err := New("callTracer", txContext, new(Context), nil) tracer, err := New("callTracer", new(Context), nil)
if err != nil { if err != nil {
t.Fatalf("failed to create call tracer: %v", err) t.Fatalf("failed to create call tracer: %v", err)
} }
@ -233,7 +233,7 @@ func TestPrestateTracerCreate2(t *testing.T) {
statedb := tests.MakePreState(db, alloc) statedb := tests.MakePreState(db, alloc)
// Create the tracer, the EVM environment and run it // Create the tracer, the EVM environment and run it
tracer, err := New("prestateTracer", txContext, new(Context), nil) tracer, err := New("prestateTracer", new(Context), nil)
if err != nil { if err != nil {
t.Fatalf("failed to create call tracer: %v", err) t.Fatalf("failed to create call tracer: %v", err)
} }