eth/tracers: rework the model a bit

This commit is contained in:
Martin Holst Swende
2021-10-26 14:34:59 +02:00
parent 0946623acc
commit 5db19c5922
8 changed files with 105 additions and 129 deletions

View File

@ -494,7 +494,7 @@ func BenchmarkSimpleLoop(b *testing.B) {
//Execute(loopingCode, nil, &Config{ //Execute(loopingCode, nil, &Config{
// EVMConfig: vm.Config{ // EVMConfig: vm.Config{
// Debug: true, // Debug: true,
// Tracer: tracer, // JSTracer: tracer,
// }}) // }})
// 100M gas // 100M gas
benchmarkNonModifyingCode(100000000, staticCallIdentity, "staticcall-identity-100M", "", b) benchmarkNonModifyingCode(100000000, staticCallIdentity, "staticcall-identity-100M", "", b)

View File

@ -36,7 +36,6 @@ import (
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers/native"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
@ -860,22 +859,17 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
} }
} }
// Native tracers take precedence // Native tracers take precedence
var ok bool if tracer, err = New(*config.Tracer, txctx); err != nil {
if tracer, ok = native.New(*config.Tracer); !ok { return nil, err
if tracer, err = New(*config.Tracer, txctx); err != nil {
return nil, err
}
// TODO(s1na): do we need timeout for native tracers?
// Handle timeouts and RPC cancellations
deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
go func() {
<-deadlineCtx.Done()
if deadlineCtx.Err() == context.DeadlineExceeded {
tracer.(*Tracer).Stop(errors.New("execution timeout"))
}
}()
defer cancel()
} }
deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
go func() {
<-deadlineCtx.Done()
if deadlineCtx.Err() == context.DeadlineExceeded {
tracer.(*JSTracer).Stop(errors.New("execution timeout"))
}
}()
defer cancel()
case config == nil: case config == nil:
tracer = vm.NewStructLogger(nil) tracer = vm.NewStructLogger(nil)
@ -909,7 +903,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
StructLogs: ethapi.FormatLogs(tracer.StructLogs()), StructLogs: ethapi.FormatLogs(tracer.StructLogs()),
}, nil }, nil
case native.Tracer: case Tracer:
return tracer.GetResult() return tracer.GetResult()
default: default:

View File

@ -26,10 +26,11 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers"
) )
func init() { func init() {
register("callTracerNative", NewCallTracer) tracers.RegisterNativeTracer("callTracerNative", NewCallTracer)
} }
type callFrame struct { type callFrame struct {
@ -45,20 +46,20 @@ type callFrame struct {
Calls []callFrame `json:"calls,omitempty"` Calls []callFrame `json:"calls,omitempty"`
} }
type CallTracer struct { type callTracer struct {
callstack []callFrame callstack []callFrame
} }
// NewCallTracer returns a native go tracer which tracks // NewCallTracer returns a native go tracer which tracks
// call frames of a tx, and implements vm.Tracer. // call frames of a tx, and implements vm.Tracer.
func NewCallTracer() Tracer { func NewCallTracer() tracers.Tracer {
// First callframe contains tx context info // First callframe contains tx context info
// and is populated on start and end. // and is populated on start and end.
t := &CallTracer{callstack: make([]callFrame, 1)} t := &callTracer{callstack: make([]callFrame, 1)}
return t return t
} }
func (t *CallTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { func (t *callTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
t.callstack[0] = callFrame{ t.callstack[0] = callFrame{
Type: "CALL", Type: "CALL",
From: addrToHex(from), From: addrToHex(from),
@ -72,7 +73,7 @@ func (t *CallTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Ad
} }
} }
func (t *CallTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) { func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) {
t.callstack[0].Output = bytesToHex(output) t.callstack[0].Output = bytesToHex(output)
t.callstack[0].GasUsed = uintToHex(gasUsed) t.callstack[0].GasUsed = uintToHex(gasUsed)
if err != nil { if err != nil {
@ -80,13 +81,13 @@ func (t *CallTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration,
} }
} }
func (t *CallTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { func (t *callTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
} }
func (t *CallTracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) { func (t *callTracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) {
} }
func (t *CallTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
call := callFrame{ call := callFrame{
Type: typ.String(), Type: typ.String(),
From: addrToHex(from), From: addrToHex(from),
@ -98,7 +99,7 @@ func (t *CallTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.
t.callstack = append(t.callstack, call) t.callstack = append(t.callstack, call)
} }
func (t *CallTracer) CaptureExit(output []byte, gasUsed uint64, err error) { func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
size := len(t.callstack) size := len(t.callstack)
if size <= 1 { if size <= 1 {
return return
@ -120,7 +121,7 @@ func (t *CallTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call) t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call)
} }
func (t *CallTracer) GetResult() (json.RawMessage, error) { func (t *callTracer) GetResult() (json.RawMessage, error) {
if len(t.callstack) != 1 { if len(t.callstack) != 1 {
return nil, errors.New("incorrect number of top-level calls") return nil, errors.New("incorrect number of top-level calls")
} }
@ -131,6 +132,10 @@ func (t *CallTracer) GetResult() (json.RawMessage, error) {
return json.RawMessage(res), nil return json.RawMessage(res), nil
} }
func (t *callTracer) Stop(err error) {
// TODO
}
func bytesToHex(s []byte) string { func bytesToHex(s []byte) string {
return "0x" + common.Bytes2Hex(s) return "0x" + common.Bytes2Hex(s)
} }

View File

@ -1,51 +0,0 @@
// Copyright 2021 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package native
import (
"encoding/json"
"github.com/ethereum/go-ethereum/core/vm"
)
// Tracer interface extends vm.Tracer and additionally
// allows collecting the tracing result.
type Tracer interface {
vm.Tracer
GetResult() (json.RawMessage, error)
}
// constructor creates a new instance of a Tracer.
type constructor func() Tracer
var tracers map[string]constructor = make(map[string]constructor)
// register makes native tracers in this directory which adhere
// to the `Tracer` interface available to the rest of the codebase.
// It is typically invoked in the `init()` function.
func register(name string, fn constructor) {
tracers[name] = fn
}
// New returns a new instance of a tracer, if one was
// registered under the given name.
func New(name string) (Tracer, bool) {
if fn, ok := tracers[name]; ok {
return fn(), true
}
return nil, false
}

View File

@ -100,13 +100,13 @@ func (mw *memoryWrapper) slice(begin, end int64) []byte {
if end < begin || begin < 0 { if end < begin || begin < 0 {
// TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go
// runtime goes belly up https://github.com/golang/go/issues/15639. // runtime goes belly up https://github.com/golang/go/issues/15639.
log.Warn("Tracer accessed out of bound memory", "offset", begin, "end", end) log.Warn("JSTracer accessed out of bound memory", "offset", begin, "end", end)
return nil return nil
} }
if mw.memory.Len() < int(end) { if mw.memory.Len() < int(end) {
// TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go
// runtime goes belly up https://github.com/golang/go/issues/15639. // runtime goes belly up https://github.com/golang/go/issues/15639.
log.Warn("Tracer accessed out of bound memory", "available", mw.memory.Len(), "offset", begin, "size", end-begin) log.Warn("JSTracer accessed out of bound memory", "available", mw.memory.Len(), "offset", begin, "size", end-begin)
return nil return nil
} }
return mw.memory.GetCopy(begin, end-begin) return mw.memory.GetCopy(begin, end-begin)
@ -117,7 +117,7 @@ func (mw *memoryWrapper) getUint(addr int64) *big.Int {
if mw.memory.Len() < int(addr)+32 || addr < 0 { if mw.memory.Len() < int(addr)+32 || addr < 0 {
// TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go
// runtime goes belly up https://github.com/golang/go/issues/15639. // runtime goes belly up https://github.com/golang/go/issues/15639.
log.Warn("Tracer accessed out of bound memory", "available", mw.memory.Len(), "offset", addr, "size", 32) log.Warn("JSTracer accessed out of bound memory", "available", mw.memory.Len(), "offset", addr, "size", 32)
return new(big.Int) return new(big.Int)
} }
return new(big.Int).SetBytes(mw.memory.GetPtr(addr, 32)) return new(big.Int).SetBytes(mw.memory.GetPtr(addr, 32))
@ -160,7 +160,7 @@ func (sw *stackWrapper) peek(idx int) *big.Int {
if len(sw.stack.Data()) <= idx || idx < 0 { if len(sw.stack.Data()) <= idx || idx < 0 {
// TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go
// runtime goes belly up https://github.com/golang/go/issues/15639. // runtime goes belly up https://github.com/golang/go/issues/15639.
log.Warn("Tracer accessed out of bound stack", "size", len(sw.stack.Data()), "index", idx) log.Warn("JSTracer accessed out of bound stack", "size", len(sw.stack.Data()), "index", idx)
return new(big.Int) return new(big.Int)
} }
return sw.stack.Back(idx).ToBig() return sw.stack.Back(idx).ToBig()
@ -365,7 +365,7 @@ func (r *frameResult) pushObject(vm *duktape.Context) {
// Tracer provides an implementation of Tracer that evaluates a Javascript // Tracer provides an implementation of Tracer that evaluates a Javascript
// function for each VM execution step. // function for each VM execution step.
type Tracer struct { type JSTracer struct {
vm *duktape.Context // Javascript VM instance vm *duktape.Context // Javascript VM instance
tracerObject int // Stack index of the tracer JavaScript object tracerObject int // Stack index of the tracer JavaScript object
@ -409,12 +409,8 @@ type Context struct {
// 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, ctx *Context) (*Tracer, error) { func newJsTracer(code string, ctx *Context) (*JSTracer, error) {
// Resolve any tracers by name and assemble the tracer object tracer := &JSTracer{
if tracer, ok := tracer(code); ok {
code = tracer
}
tracer := &Tracer{
vm: duktape.New(), vm: duktape.New(),
ctx: make(map[string]interface{}), ctx: make(map[string]interface{}),
opWrapper: new(opWrapper), opWrapper: new(opWrapper),
@ -522,7 +518,7 @@ func New(code string, ctx *Context) (*Tracer, error) {
if start < 0 || start > end || end > len(blob) { if start < 0 || start > end || end > len(blob) {
// TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go
// runtime goes belly up https://github.com/golang/go/issues/15639. // runtime goes belly up https://github.com/golang/go/issues/15639.
log.Warn("Tracer accessed out of bound memory", "available", len(blob), "offset", start, "size", size) log.Warn("JSTracer accessed out of bound memory", "available", len(blob), "offset", start, "size", size)
ctx.PushFixedBuffer(0) ctx.PushFixedBuffer(0)
return 1 return 1
} }
@ -566,7 +562,7 @@ func New(code string, ctx *Context) (*Tracer, error) {
tracer.traceCallFrames = hasEnter tracer.traceCallFrames = hasEnter
tracer.traceSteps = hasStep tracer.traceSteps = hasStep
// Tracer is valid, inject the big int library to access large numbers // JSTracer is valid, inject the big int library to access large numbers
tracer.vm.EvalString(bigIntegerJS) tracer.vm.EvalString(bigIntegerJS)
tracer.vm.PutGlobalString("bigInt") tracer.vm.PutGlobalString("bigInt")
@ -627,14 +623,14 @@ func New(code string, ctx *Context) (*Tracer, error) {
} }
// Stop terminates execution of the tracer at the first opportune moment. // Stop terminates execution of the tracer at the first opportune moment.
func (jst *Tracer) Stop(err error) { func (jst *JSTracer) Stop(err error) {
jst.reason = err jst.reason = err
atomic.StoreUint32(&jst.interrupt, 1) atomic.StoreUint32(&jst.interrupt, 1)
} }
// call executes a method on a JS object, catching any errors, formatting and // call executes a method on a JS object, catching any errors, formatting and
// returning them as error objects. // returning them as error objects.
func (jst *Tracer) call(noret bool, method string, args ...string) (json.RawMessage, error) { func (jst *JSTracer) call(noret bool, method string, args ...string) (json.RawMessage, error) {
// Execute the JavaScript call and return any error // Execute the JavaScript call and return any error
jst.vm.PushString(method) jst.vm.PushString(method)
for _, arg := range args { for _, arg := range args {
@ -670,7 +666,7 @@ func wrapError(context string, err error) error {
} }
// CaptureStart implements the Tracer interface to initialize the tracing operation. // CaptureStart implements the Tracer interface to initialize the tracing operation.
func (jst *Tracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { func (jst *JSTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
jst.ctx["type"] = "CALL" jst.ctx["type"] = "CALL"
if create { if create {
jst.ctx["type"] = "CREATE" jst.ctx["type"] = "CREATE"
@ -700,7 +696,7 @@ func (jst *Tracer) CaptureStart(env *vm.EVM, from common.Address, to common.Addr
} }
// CaptureState implements the Tracer interface to trace a single step of VM execution. // CaptureState implements the Tracer interface to trace a single step of VM execution.
func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { func (jst *JSTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
if !jst.traceSteps { if !jst.traceSteps {
return return
} }
@ -736,7 +732,7 @@ func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost
} }
// CaptureFault implements the Tracer interface to trace an execution fault // CaptureFault implements the Tracer interface to trace an execution fault
func (jst *Tracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { func (jst *JSTracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
if jst.err != nil { if jst.err != nil {
return return
} }
@ -750,7 +746,7 @@ func (jst *Tracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost
} }
// CaptureEnd is called after the call finishes to finalize the tracing. // CaptureEnd is called after the call finishes to finalize the tracing.
func (jst *Tracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) { func (jst *JSTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {
jst.ctx["output"] = output jst.ctx["output"] = output
jst.ctx["time"] = t.String() jst.ctx["time"] = t.String()
jst.ctx["gasUsed"] = gasUsed jst.ctx["gasUsed"] = gasUsed
@ -761,7 +757,7 @@ func (jst *Tracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, er
} }
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
func (jst *Tracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { func (jst *JSTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
if !jst.traceCallFrames { if !jst.traceCallFrames {
return return
} }
@ -791,7 +787,7 @@ func (jst *Tracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Ad
// CaptureExit is called when EVM exits a scope, even if the scope didn't // CaptureExit is called when EVM exits a scope, even if the scope didn't
// execute any code. // execute any code.
func (jst *Tracer) CaptureExit(output []byte, gasUsed uint64, err error) { func (jst *JSTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
if !jst.traceCallFrames { if !jst.traceCallFrames {
return return
} }
@ -815,7 +811,7 @@ func (jst *Tracer) CaptureExit(output []byte, gasUsed uint64, err error) {
} }
// GetResult calls the Javascript 'result' function and returns its value, or any accumulated error // GetResult calls the Javascript 'result' function and returns its value, or any accumulated error
func (jst *Tracer) GetResult() (json.RawMessage, error) { func (jst *JSTracer) GetResult() (json.RawMessage, error) {
// Transform the context into a JavaScript object and inject into the state // Transform the context into a JavaScript object and inject into the state
obj := jst.vm.PushObject() obj := jst.vm.PushObject()
@ -837,7 +833,7 @@ func (jst *Tracer) GetResult() (json.RawMessage, error) {
} }
// addToObj pushes a field to a JS object. // addToObj pushes a field to a JS object.
func (jst *Tracer) addToObj(obj int, key string, val interface{}) { func (jst *JSTracer) addToObj(obj int, key string, val interface{}) {
pushValue(jst.vm, val) pushValue(jst.vm, val)
jst.vm.PutPropString(obj, key) jst.vm.PutPropString(obj, key)
} }

View File

@ -58,7 +58,7 @@ func testCtx() *vmContext {
return &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}} return &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}}
} }
func runTrace(tracer *Tracer, vmctx *vmContext, chaincfg *params.ChainConfig) (json.RawMessage, error) { func runTrace(tracer Tracer, vmctx *vmContext, chaincfg *params.ChainConfig) (json.RawMessage, error) {
env := vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, chaincfg, vm.Config{Debug: true, Tracer: tracer}) env := vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, chaincfg, vm.Config{Debug: true, Tracer: tracer})
var ( var (
startGas uint64 = 10000 startGas uint64 = 10000
@ -168,7 +168,7 @@ func TestHaltBetweenSteps(t *testing.T) {
// TestNoStepExec tests a regular value transfer (no exec), and accessing the statedb // TestNoStepExec tests a regular value transfer (no exec), and accessing the statedb
// in 'result' // in 'result'
func TestNoStepExec(t *testing.T) { func TestNoStepExec(t *testing.T) {
runEmptyTrace := func(tracer *Tracer, vmctx *vmContext) (json.RawMessage, error) { runEmptyTrace := func(tracer Tracer, vmctx *vmContext) (json.RawMessage, error) {
env := vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) env := vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer})
startGas := uint64(10000) startGas := uint64(10000)
contract := vm.NewContract(account{}, account{}, big.NewInt(0), startGas) contract := vm.NewContract(account{}, account{}, big.NewInt(0), startGas)
@ -223,7 +223,7 @@ func TestIsPrecompile(t *testing.T) {
t.Error(err) t.Error(err)
} }
if string(res) != "false" { if string(res) != "false" {
t.Errorf("Tracer should not consider blake2f as precompile in byzantium") t.Errorf("JSTracer should not consider blake2f as precompile in byzantium")
} }
tracer, _ = New("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", new(Context)) tracer, _ = New("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", new(Context))
@ -233,7 +233,7 @@ func TestIsPrecompile(t *testing.T) {
t.Error(err) t.Error(err)
} }
if string(res) != "true" { if string(res) != "true" {
t.Errorf("Tracer should consider blake2f as precompile in istanbul") t.Errorf("JSTracer should consider blake2f as precompile in istanbul")
} }
} }

View File

@ -18,14 +18,55 @@
package tracers package tracers
import ( import (
"encoding/json"
"fmt"
"strings" "strings"
"unicode" "unicode"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers/internal/tracers" "github.com/ethereum/go-ethereum/eth/tracers/internal/tracers"
) )
// all contains all the built in JavaScript tracers by name. // Tracer interface extends vm.JSTracer and additionally
var all = make(map[string]string) // allows collecting the tracing result.
type Tracer interface {
vm.Tracer
GetResult() (json.RawMessage, error)
// Stop terminates execution of the tracer at the first opportune moment.
Stop(err error)
}
var (
nativeTracers map[string]func() Tracer = make(map[string]func() Tracer)
jsTracers = make(map[string]string)
)
// RegisterNativeTracer makes native tracers in this directory which adhere
// to the `JSTracer` interface available to the rest of the codebase.
// It is typically invoked in the `init()` function.
func RegisterNativeTracer(name string, ctor func() Tracer) {
nativeTracers[name] = ctor
}
// New returns a new instance of a tracer,
// 1. If 'code' is the name of a registered native tracer, then that tracer
// is instantiated and returned
// 2. If 'code' is the name of a registered js-tracer, then that tracer is
// instantiated and returned
// 3. Otherwise, the code is interpreted as the js code of a js-tracer, and
// is evaluated and returned.
func New(code string, ctx *Context) (Tracer, error) {
// Resolve native tracer
if fn, ok := nativeTracers[code]; ok {
return fn(), nil
}
panic(fmt.Sprintf("no native tracer %v found", code))
// Resolve js-tracers by name and assemble the tracer object
if tracer, ok := jsTracers[code]; ok {
code = tracer
}
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.
func camel(str string) string { func camel(str string) string {
@ -40,14 +81,6 @@ func camel(str string) string {
func init() { func init() {
for _, file := range tracers.AssetNames() { for _, file := range tracers.AssetNames() {
name := camel(strings.TrimSuffix(file, ".js")) name := camel(strings.TrimSuffix(file, ".js"))
all[name] = string(tracers.MustAsset(file)) jsTracers[name] = string(tracers.MustAsset(file))
} }
} }
// tracer retrieves a specific JavaScript tracer by name.
func tracer(name string) (string, bool) {
if tracer, ok := all[name]; ok {
return tracer, true
}
return "", false
}

View File

@ -35,7 +35,7 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/tracers/native" //"github.com/ethereum/go-ethereum/eth/tracers/native"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/tests" "github.com/ethereum/go-ethereum/tests"
@ -205,7 +205,7 @@ func TestPrestateTracerCreate2(t *testing.T) {
// Iterates over all the input-output datasets in the tracer test harness and // Iterates over all the input-output datasets in the tracer test harness and
// runs the JavaScript tracers against them. // runs the JavaScript tracers against them.
func TestCallTracerLegacy(t *testing.T) { func TestCallTracerLegacy(t *testing.T) {
newTracer := func() native.Tracer { newTracer := func() Tracer {
tracer, err := New("callTracerLegacy", new(Context)) tracer, err := New("callTracerLegacy", 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)
@ -217,7 +217,7 @@ func TestCallTracerLegacy(t *testing.T) {
} }
func TestCallTracer(t *testing.T) { func TestCallTracer(t *testing.T) {
newTracer := func() native.Tracer { newTracer := func() Tracer {
tracer, err := New("callTracer", new(Context)) 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)
@ -229,18 +229,17 @@ func TestCallTracer(t *testing.T) {
} }
func TestCallTracerNative(t *testing.T) { func TestCallTracerNative(t *testing.T) {
newTracer := func() native.Tracer { newTracer := func() Tracer {
tracer, ok := native.New("callTracerNative") tracer, err := New("callTracerNative", nil /* TODO? */)
if !ok { if err != nil {
t.Fatal("failed to create native call tracer") t.Fatalf("failed to create native call tracer: %v", err)
} }
return tracer return tracer
} }
testCallTracer(newTracer, "call_tracer", t) testCallTracer(newTracer, "call_tracer", t)
} }
func testCallTracer(newTracer func() native.Tracer, dirPath string, t *testing.T) { func testCallTracer(newTracer func() Tracer, dirPath string, t *testing.T) {
files, err := ioutil.ReadDir(filepath.Join("testdata", dirPath)) files, err := ioutil.ReadDir(filepath.Join("testdata", dirPath))
if err != nil { if err != nil {
t.Fatalf("failed to retrieve tracer test suite: %v", err) t.Fatalf("failed to retrieve tracer test suite: %v", err)
@ -431,7 +430,7 @@ func BenchmarkTracers(b *testing.B) {
if err := json.Unmarshal(blob, test); err != nil { if err := json.Unmarshal(blob, test); err != nil {
b.Fatalf("failed to parse testcase: %v", err) b.Fatalf("failed to parse testcase: %v", err)
} }
newTracer := func() native.Tracer { newTracer := func() Tracer {
tracer, err := New("callTracer", new(Context)) tracer, err := New("callTracer", new(Context))
if err != nil { if err != nil {
b.Fatalf("failed to create call tracer: %v", err) b.Fatalf("failed to create call tracer: %v", err)
@ -443,7 +442,7 @@ func BenchmarkTracers(b *testing.B) {
} }
} }
func benchTracer(newTracer func() native.Tracer, test *callTracerTest, b *testing.B) { func benchTracer(newTracer func() Tracer, test *callTracerTest, b *testing.B) {
// Configure a blockchain with the given prestate // Configure a blockchain with the given prestate
tx := new(types.Transaction) tx := new(types.Transaction)
if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil { if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {