From c9c1dc9d5c94aceb965a2601c03fdc2360e74041 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 11 Oct 2021 18:20:45 +0200 Subject: [PATCH] eth/tracers: add native call tracer --- eth/tracers/api.go | 31 +++++---- eth/tracers/native/call.go | 125 +++++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 13 deletions(-) create mode 100644 eth/tracers/native/call.go diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 5019fb6f73..07764bc4ac 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -36,6 +36,7 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "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/internal/ethapi" "github.com/ethereum/go-ethereum/log" @@ -858,19 +859,23 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex return nil, err } } - // Constuct the JavaScript tracer to execute with - if tracer, err = New(*config.Tracer, txctx); err != nil { - return nil, err - } - // 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")) + // Native tracers take precedence + var ok bool + if tracer, ok = native.New(*config.Tracer); !ok { + if tracer, err = New(*config.Tracer, txctx); err != nil { + return nil, err } - }() - defer cancel() + // 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() + } case config == nil: tracer = vm.NewStructLogger(nil) @@ -904,7 +909,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex StructLogs: ethapi.FormatLogs(tracer.StructLogs()), }, nil - case *Tracer: + case native.Tracer: return tracer.GetResult() default: diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go new file mode 100644 index 0000000000..923fd052c1 --- /dev/null +++ b/eth/tracers/native/call.go @@ -0,0 +1,125 @@ +package native + +import ( + "encoding/json" + "math/big" + "strconv" + "strings" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" +) + +func init() { + Register("callTracerNative", NewCallTracer) +} + +type CallFrame struct { + Type string `json:"type"` + From string `json:"from"` + To string `json:"to"` + Input string `json:"input"` + Gas string `json:"gas"` + Value string `json:"value,omitempty"` + GasUsed string `json:"gasUsed"` + Output string `json:"output"` + Error string `json:"error,omitempty"` + Calls []CallFrame `json:"calls,omitempty"` +} + +type CallTracer struct { + callstack []CallFrame +} + +func NewCallTracer() Tracer { + t := &CallTracer{callstack: make([]CallFrame, 1)} + return t +} + +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{ + Type: "CALL", + From: addrToHex(from), + To: addrToHex(to), + Input: bytesToHex(input), + Gas: uintToHex(gas), + Value: bigToHex(value), + Calls: make([]CallFrame, 0), + } + if create { + t.callstack[0].Type = "CREATE" + } +} + +func (t *CallTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) { + t.callstack[0].Output = bytesToHex(output) + t.callstack[0].GasUsed = uintToHex(gasUsed) + if err != nil { + t.callstack[0].Error = 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) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + call := CallFrame{ + Type: typ.String(), + From: addrToHex(from), + To: addrToHex(to), + Input: bytesToHex(input), + Gas: uintToHex(gas), + Value: bigToHex(value), + Calls: make([]CallFrame, 0), + } + t.callstack = append(t.callstack, call) +} + +func (t *CallTracer) CaptureExit(output []byte, gasUsed uint64, err error) { + size := len(t.callstack) + if size > 1 { + // pop call + call := t.callstack[size-1] + t.callstack = t.callstack[:size-1] + size -= 1 + + call.GasUsed = uintToHex(gasUsed) + if err == nil { + call.Output = bytesToHex(output) + } else { + call.Error = err.Error() + if call.Type == "CREATE" || call.Type == "CREATE2" { + call.To = "" + } + } + t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call) + } +} + +func (t *CallTracer) GetResult() (json.RawMessage, error) { + res, err := json.Marshal(t.callstack) + if err != nil { + return nil, err + } + return json.RawMessage(res), nil +} + +func bytesToHex(s []byte) string { + return "0x" + common.Bytes2Hex(s) +} + +func bigToHex(n *big.Int) string { + return "0x" + n.Text(16) +} + +func uintToHex(n uint64) string { + return "0x" + strconv.FormatUint(n, 16) +} + +func addrToHex(a common.Address) string { + return strings.ToLower(a.Hex()) +}