eth/tracers: rework the model a bit
This commit is contained in:
@ -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)
|
||||||
|
@ -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, ok = native.New(*config.Tracer); !ok {
|
|
||||||
if tracer, err = New(*config.Tracer, txctx); err != nil {
|
if tracer, err = New(*config.Tracer, txctx); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// TODO(s1na): do we need timeout for native tracers?
|
|
||||||
// Handle timeouts and RPC cancellations
|
|
||||||
deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
|
deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
|
||||||
go func() {
|
go func() {
|
||||||
<-deadlineCtx.Done()
|
<-deadlineCtx.Done()
|
||||||
if deadlineCtx.Err() == context.DeadlineExceeded {
|
if deadlineCtx.Err() == context.DeadlineExceeded {
|
||||||
tracer.(*Tracer).Stop(errors.New("execution timeout"))
|
tracer.(*JSTracer).Stop(errors.New("execution timeout"))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
defer cancel()
|
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:
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
}
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
|
@ -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 {
|
||||||
|
Reference in New Issue
Block a user