feat: backport flatCallTracer (#895)

This commit is contained in:
Dragan Milic 2024-08-23 08:52:00 +02:00 committed by GitHub
parent 9de4980851
commit 972fe8afc4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 527 additions and 27 deletions

View file

@ -194,6 +194,7 @@ type StdTraceConfig struct {
// txTraceResult is the result of a single transaction trace. // txTraceResult is the result of a single transaction trace.
type txTraceResult struct { type txTraceResult struct {
TxHash common.Hash `json:"txHash"` // Hash of the transaction being traced
Result interface{} `json:"result,omitempty"` // Trace results produced by the tracer Result interface{} `json:"result,omitempty"` // Trace results produced by the tracer
Error string `json:"error,omitempty"` // Trace failure produced by the tracer Error string `json:"error,omitempty"` // Trace failure produced by the tracer
} }
@ -280,9 +281,10 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config
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 := &Context{ txctx := &Context{
BlockHash: task.block.Hash(), BlockNumber: task.block.NumberU64(),
TxIndex: i, BlockHash: task.block.Hash(),
TxHash: tx.Hash(), TxIndex: i,
TxHash: tx.Hash(),
} }
l1DataFee, err := fees.CalculateL1DataFee(tx, task.statedb, api.backend.ChainConfig(), task.block.Number()) l1DataFee, err := fees.CalculateL1DataFee(tx, task.statedb, api.backend.ChainConfig(), task.block.Number())
@ -613,6 +615,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
} }
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), api.backend.ChainConfig(), nil) blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), api.backend.ChainConfig(), nil)
blockHash := block.Hash() blockHash := block.Hash()
blockNumber := block.NumberU64()
for th := 0; th < threads; th++ { for th := 0; th < threads; th++ {
pend.Add(1) pend.Add(1)
go func() { go func() {
@ -621,23 +624,33 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
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 := &Context{ txctx := &Context{
BlockHash: blockHash, BlockNumber: blockNumber,
TxIndex: task.index, BlockHash: blockHash,
TxHash: txs[task.index].Hash(), TxIndex: task.index,
TxHash: txs[task.index].Hash(),
} }
l1DataFee, err := fees.CalculateL1DataFee(txs[task.index], task.statedb, api.backend.ChainConfig(), block.Number()) l1DataFee, err := fees.CalculateL1DataFee(txs[task.index], task.statedb, api.backend.ChainConfig(), block.Number())
if err != nil { if err != nil {
// though it's not a "tracing error", we still need to put it here // though it's not a "tracing error", we still need to put it here
results[task.index] = &txTraceResult{Error: err.Error()} results[task.index] = &txTraceResult{
TxHash: txs[task.index].Hash(),
Error: err.Error(),
}
continue continue
} }
res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config, l1DataFee) res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config, l1DataFee)
if err != nil { if err != nil {
results[task.index] = &txTraceResult{Error: err.Error()} results[task.index] = &txTraceResult{
TxHash: txs[task.index].Hash(),
Error: err.Error(),
}
continue continue
} }
results[task.index] = &txTraceResult{Result: res} results[task.index] = &txTraceResult{
TxHash: txs[task.index].Hash(),
Result: res,
}
} }
}() }()
} }

View file

@ -55,7 +55,7 @@ type fourByteTracer struct {
// newFourByteTracer returns a native go tracer which collects // newFourByteTracer returns a native go tracer which collects
// 4 byte-identifiers of a tx, and implements vm.EVMLogger. // 4 byte-identifiers of a tx, and implements vm.EVMLogger.
func newFourByteTracer() tracers.Tracer { func newFourByteTracer(ctx *tracers.Context) tracers.Tracer {
t := &fourByteTracer{ t := &fourByteTracer{
ids: make(map[string]int), ids: make(map[string]int),
} }

View file

@ -56,7 +56,7 @@ type callTracer struct {
// newCallTracer returns a native go tracer which tracks // newCallTracer returns a native go tracer which tracks
// call frames of a tx, and implements vm.EVMLogger. // call frames of a tx, and implements vm.EVMLogger.
func newCallTracer() tracers.Tracer { func newCallTracer(ctx *tracers.Context) 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)}

View file

@ -0,0 +1,318 @@
// Copyright 2022 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"
"errors"
"fmt"
"math/big"
"strings"
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/common/hexutil"
"github.com/scroll-tech/go-ethereum/core/vm"
"github.com/scroll-tech/go-ethereum/eth/tracers"
)
//go:generate go run github.com/fjl/gencodec -type flatCallAction -field-override flatCallActionMarshaling -out gen_flatcallaction_json.go
//go:generate go run github.com/fjl/gencodec -type flatCallResult -field-override flatCallResultMarshaling -out gen_flatcallresult_json.go
func init() {
// tracers.DefaultDirectory.Register("flatCallTracer", newFlatCallTracer, false)
register("flatCallTracer", newFlatCallTracer)
}
var parityErrorMapping = map[string]string{
"contract creation code storage out of gas": "Out of gas",
"out of gas": "Out of gas",
"gas uint64 overflow": "Out of gas",
"max code size exceeded": "Out of gas",
"invalid jump destination": "Bad jump destination",
"execution reverted": "Reverted",
"return data out of bounds": "Out of bounds",
"stack limit reached 1024 (1023)": "Out of stack",
"precompiled failed": "Built-in failed",
"invalid input length": "Built-in failed",
}
var parityErrorMappingStartingWith = map[string]string{
"invalid opcode:": "Bad instruction",
"stack underflow": "Stack underflow",
}
// flatCallFrame is a standalone callframe.
type flatCallFrame struct {
Action flatCallAction `json:"action"`
BlockHash *common.Hash `json:"blockHash"`
BlockNumber uint64 `json:"blockNumber"`
Error string `json:"error,omitempty"`
Result *flatCallResult `json:"result,omitempty"`
Subtraces int `json:"subtraces"`
TraceAddress []int `json:"traceAddress"`
TransactionHash *common.Hash `json:"transactionHash"`
TransactionPosition uint64 `json:"transactionPosition"`
Type string `json:"type"`
}
type flatCallAction struct {
Author *common.Address `json:"author,omitempty"`
RewardType string `json:"rewardType,omitempty"`
SelfDestructed *common.Address `json:"address,omitempty"`
Balance *big.Int `json:"balance,omitempty"`
CallType string `json:"callType,omitempty"`
CreationMethod string `json:"creationMethod,omitempty"`
From *common.Address `json:"from,omitempty"`
Gas *uint64 `json:"gas,omitempty"`
Init *[]byte `json:"init,omitempty"`
Input *[]byte `json:"input,omitempty"`
RefundAddress *common.Address `json:"refundAddress,omitempty"`
To *common.Address `json:"to,omitempty"`
Value *big.Int `json:"value,omitempty"`
}
type flatCallActionMarshaling struct {
Balance *hexutil.Big
Gas *hexutil.Uint64
Init *hexutil.Bytes
Input *hexutil.Bytes
Value *hexutil.Big
}
type flatCallResult struct {
Address *common.Address `json:"address,omitempty"`
Code *[]byte `json:"code,omitempty"`
GasUsed *uint64 `json:"gasUsed,omitempty"`
Output *[]byte `json:"output,omitempty"`
}
type flatCallResultMarshaling struct {
Code *hexutil.Bytes
GasUsed *hexutil.Uint64
Output *hexutil.Bytes
}
// flatCallTracer reports call frame information of a tx in a flat format, i.e.
// as opposed to the nested format of `callTracer`.
type flatCallTracer struct {
ctx *tracers.Context
*callTracer
}
// newFlatCallTracer returns a new flatCallTracer.
func newFlatCallTracer(ctx *tracers.Context) tracers.Tracer {
t := &callTracer{callstack: make([]callFrame, 1)}
return &flatCallTracer{callTracer: t, ctx: ctx}
}
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
func (t *flatCallTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
t.callTracer.CaptureEnter(typ, from, to, input, gas, value)
if len(t.callTracer.callstack) > 0 && t.callTracer.callstack[len(t.callTracer.callstack)-1].Value == "" {
t.callTracer.callstack[len(t.callTracer.callstack)-1].Value = "0x0"
}
}
// GetResult returns the json-encoded nested list of call traces, and any
// error arising from the encoding or forceful termination (via `Stop`).
func (t *flatCallTracer) GetResult() (json.RawMessage, error) {
if len(t.callTracer.callstack) < 1 {
return nil, errors.New("invalid number of calls")
}
flat, err := flatFromNested(&t.callTracer.callstack[0], []int{}, true, t.ctx)
if err != nil {
return nil, err
}
res, err := json.Marshal(flat)
if err != nil {
return nil, err
}
return res, t.reason
}
func flatFromNested(input *callFrame, traceAddress []int, convertErrs bool, ctx *tracers.Context) (output []flatCallFrame, err error) {
var frame *flatCallFrame
switch vm.StringToOp(input.Type) {
case vm.CREATE, vm.CREATE2:
frame = newFlatCreate(input)
case vm.SELFDESTRUCT:
frame = newFlatSuicide(input)
case vm.CALL, vm.STATICCALL, vm.CALLCODE, vm.DELEGATECALL:
frame = newFlatCall(input)
default:
return nil, fmt.Errorf("unrecognized call frame type: %s", input.Type)
}
frame.TraceAddress = traceAddress
frame.Error = input.Error
frame.Subtraces = len(input.Calls)
fillCallFrameFromContext(frame, ctx)
if convertErrs {
convertErrorToParity(frame)
}
// Revert output contains useful information (revert reason).
// Otherwise discard result.
if input.Error != "" && input.Error != vm.ErrExecutionReverted.Error() {
frame.Result = nil
}
output = append(output, *frame)
if len(input.Calls) > 0 {
for i, childCall := range input.Calls {
childAddr := childTraceAddress(traceAddress, i)
childCallCopy := childCall
flat, err := flatFromNested(&childCallCopy, childAddr, convertErrs, ctx)
if err != nil {
return nil, err
}
output = append(output, flat...)
}
}
return output, nil
}
func addressPointer(a string) *common.Address {
if a == "" {
return nil
}
addr := common.HexToAddress(a)
return &addr
}
func uint64Pointer(v string) *uint64 {
if v == "" {
return nil
}
val, _ := hexutil.DecodeUint64(v)
return &val
}
func bigInt(v string) *big.Int {
if v == "" {
return nil
}
val, _ := hexutil.DecodeBig(v)
return val
}
func bytesPointer(v string) *[]byte {
if v == "" {
return nil
}
val := hexutil.MustDecode(v)
return &val
}
func newFlatCreate(input *callFrame) *flatCallFrame {
var (
actionInit = bytesPointer(input.Input)
resultCode = bytesPointer(input.Output)
)
return &flatCallFrame{
Type: strings.ToLower(vm.CREATE.String()),
Action: flatCallAction{
From: addressPointer(input.From),
Gas: uint64Pointer(input.Gas),
Value: bigInt(input.Value),
Init: actionInit,
},
Result: &flatCallResult{
GasUsed: uint64Pointer(input.GasUsed),
Address: addressPointer(input.To),
Code: resultCode,
},
}
}
func newFlatCall(input *callFrame) *flatCallFrame {
var (
actionInput = bytesPointer(input.Input)
resultOutput = bytesPointer(input.Output)
)
return &flatCallFrame{
Type: strings.ToLower(vm.CALL.String()),
Action: flatCallAction{
From: addressPointer(input.From),
To: addressPointer(input.To),
Gas: uint64Pointer(input.Gas),
Value: bigInt(input.Value),
CallType: strings.ToLower(input.Type),
Input: actionInput,
},
Result: &flatCallResult{
GasUsed: uint64Pointer(input.GasUsed),
Output: resultOutput,
},
}
}
func newFlatSuicide(input *callFrame) *flatCallFrame {
return &flatCallFrame{
Type: "suicide",
Action: flatCallAction{
SelfDestructed: addressPointer(input.From),
Balance: bigInt(input.Value),
RefundAddress: addressPointer(input.To),
},
}
}
func fillCallFrameFromContext(callFrame *flatCallFrame, ctx *tracers.Context) {
if ctx.BlockHash != (common.Hash{}) {
callFrame.BlockHash = &ctx.BlockHash
}
if ctx.BlockNumber != 0 {
callFrame.BlockNumber = ctx.BlockNumber
}
if ctx.TxHash != (common.Hash{}) {
callFrame.TransactionHash = &ctx.TxHash
}
callFrame.TransactionPosition = uint64(ctx.TxIndex)
}
func convertErrorToParity(call *flatCallFrame) {
if call.Error == "" {
return
}
if parityError, ok := parityErrorMapping[call.Error]; ok {
call.Error = parityError
} else {
for gethError, parityError := range parityErrorMappingStartingWith {
if strings.HasPrefix(call.Error, gethError) {
call.Error = parityError
}
}
}
}
func childTraceAddress(a []int, i int) []int {
child := make([]int, 0, len(a)+1)
child = append(child, a...)
child = append(child, i)
return child
}

View file

@ -0,0 +1,110 @@
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package native
import (
"encoding/json"
"math/big"
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/common/hexutil"
)
var _ = (*flatCallActionMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (f flatCallAction) MarshalJSON() ([]byte, error) {
type flatCallAction struct {
Author *common.Address `json:"author,omitempty"`
RewardType string `json:"rewardType,omitempty"`
SelfDestructed *common.Address `json:"address,omitempty"`
Balance *hexutil.Big `json:"balance,omitempty"`
CallType string `json:"callType,omitempty"`
CreationMethod string `json:"creationMethod,omitempty"`
From *common.Address `json:"from,omitempty"`
Gas *hexutil.Uint64 `json:"gas,omitempty"`
Init *hexutil.Bytes `json:"init,omitempty"`
Input *hexutil.Bytes `json:"input,omitempty"`
RefundAddress *common.Address `json:"refundAddress,omitempty"`
To *common.Address `json:"to,omitempty"`
Value *hexutil.Big `json:"value,omitempty"`
}
var enc flatCallAction
enc.Author = f.Author
enc.RewardType = f.RewardType
enc.SelfDestructed = f.SelfDestructed
enc.Balance = (*hexutil.Big)(f.Balance)
enc.CallType = f.CallType
enc.CreationMethod = f.CreationMethod
enc.From = f.From
enc.Gas = (*hexutil.Uint64)(f.Gas)
enc.Init = (*hexutil.Bytes)(f.Init)
enc.Input = (*hexutil.Bytes)(f.Input)
enc.RefundAddress = f.RefundAddress
enc.To = f.To
enc.Value = (*hexutil.Big)(f.Value)
return json.Marshal(&enc)
}
// UnmarshalJSON unmarshals from JSON.
func (f *flatCallAction) UnmarshalJSON(input []byte) error {
type flatCallAction struct {
Author *common.Address `json:"author,omitempty"`
RewardType *string `json:"rewardType,omitempty"`
SelfDestructed *common.Address `json:"address,omitempty"`
Balance *hexutil.Big `json:"balance,omitempty"`
CallType *string `json:"callType,omitempty"`
CreationMethod *string `json:"creationMethod,omitempty"`
From *common.Address `json:"from,omitempty"`
Gas *hexutil.Uint64 `json:"gas,omitempty"`
Init *hexutil.Bytes `json:"init,omitempty"`
Input *hexutil.Bytes `json:"input,omitempty"`
RefundAddress *common.Address `json:"refundAddress,omitempty"`
To *common.Address `json:"to,omitempty"`
Value *hexutil.Big `json:"value,omitempty"`
}
var dec flatCallAction
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
if dec.Author != nil {
f.Author = dec.Author
}
if dec.RewardType != nil {
f.RewardType = *dec.RewardType
}
if dec.SelfDestructed != nil {
f.SelfDestructed = dec.SelfDestructed
}
if dec.Balance != nil {
f.Balance = (*big.Int)(dec.Balance)
}
if dec.CallType != nil {
f.CallType = *dec.CallType
}
if dec.CreationMethod != nil {
f.CreationMethod = *dec.CreationMethod
}
if dec.From != nil {
f.From = dec.From
}
if dec.Gas != nil {
f.Gas = (*uint64)(dec.Gas)
}
if dec.Init != nil {
f.Init = (*[]byte)(dec.Init)
}
if dec.Input != nil {
f.Input = (*[]byte)(dec.Input)
}
if dec.RefundAddress != nil {
f.RefundAddress = dec.RefundAddress
}
if dec.To != nil {
f.To = dec.To
}
if dec.Value != nil {
f.Value = (*big.Int)(dec.Value)
}
return nil
}

View file

@ -0,0 +1,55 @@
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package native
import (
"encoding/json"
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/common/hexutil"
)
var _ = (*flatCallResultMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (f flatCallResult) MarshalJSON() ([]byte, error) {
type flatCallResult struct {
Address *common.Address `json:"address,omitempty"`
Code *hexutil.Bytes `json:"code,omitempty"`
GasUsed *hexutil.Uint64 `json:"gasUsed,omitempty"`
Output *hexutil.Bytes `json:"output,omitempty"`
}
var enc flatCallResult
enc.Address = f.Address
enc.Code = (*hexutil.Bytes)(f.Code)
enc.GasUsed = (*hexutil.Uint64)(f.GasUsed)
enc.Output = (*hexutil.Bytes)(f.Output)
return json.Marshal(&enc)
}
// UnmarshalJSON unmarshals from JSON.
func (f *flatCallResult) UnmarshalJSON(input []byte) error {
type flatCallResult struct {
Address *common.Address `json:"address,omitempty"`
Code *hexutil.Bytes `json:"code,omitempty"`
GasUsed *hexutil.Uint64 `json:"gasUsed,omitempty"`
Output *hexutil.Bytes `json:"output,omitempty"`
}
var dec flatCallResult
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
if dec.Address != nil {
f.Address = dec.Address
}
if dec.Code != nil {
f.Code = (*[]byte)(dec.Code)
}
if dec.GasUsed != nil {
f.GasUsed = (*uint64)(dec.GasUsed)
}
if dec.Output != nil {
f.Output = (*[]byte)(dec.Output)
}
return nil
}

View file

@ -35,7 +35,7 @@ func init() {
type noopTracer struct{} type noopTracer struct{}
// newNoopTracer returns a new noop tracer. // newNoopTracer returns a new noop tracer.
func newNoopTracer() tracers.Tracer { func newNoopTracer(ctx *tracers.Context) tracers.Tracer {
return &noopTracer{} return &noopTracer{}
} }

View file

@ -61,7 +61,7 @@ type prestateTracer struct {
deleted map[common.Address]bool deleted map[common.Address]bool
} }
func newPrestateTracer() tracers.Tracer { func newPrestateTracer(ctx *tracers.Context) tracers.Tracer {
return &prestateTracer{ return &prestateTracer{
pre: state{}, pre: state{},
post: state{}, post: state{},

View file

@ -27,9 +27,11 @@ Aside from implementing the tracer, it also needs to register itself, using the
Example: Example:
```golang ```golang
func init() {
register("noopTracerNative", newNoopTracer) func init() {
} register("noopTracerNative", newNoopTracer)
}
``` ```
*/ */
package native package native
@ -57,12 +59,12 @@ The go spec (https://golang.org/ref/spec#Package_initialization) says
Hence, we cannot make the map in init, but must make it upon first use. Hence, we cannot make the map in init, but must make it upon first use.
*/ */
var ctors map[string]func() tracers.Tracer var ctors map[string]func(ctx *tracers.Context) tracers.Tracer
// register is used by native tracers to register their presence. // register is used by native tracers to register their presence.
func register(name string, ctor func() tracers.Tracer) { func register(name string, ctor func(ctx *tracers.Context) tracers.Tracer) {
if ctors == nil { if ctors == nil {
ctors = make(map[string]func() tracers.Tracer) ctors = make(map[string]func(ctx *tracers.Context) tracers.Tracer)
} }
ctors[name] = ctor ctors[name] = ctor
} }
@ -70,10 +72,10 @@ func register(name string, ctor func() tracers.Tracer) {
// lookup returns a tracer, if one can be matched to the given name. // lookup returns a tracer, if one can be matched to the given name.
func lookup(name string, ctx *tracers.Context) (tracers.Tracer, error) { func lookup(name string, ctx *tracers.Context) (tracers.Tracer, error) {
if ctors == nil { if ctors == nil {
ctors = make(map[string]func() tracers.Tracer) ctors = make(map[string]func(ctx *tracers.Context) tracers.Tracer)
} }
if ctor, ok := ctors[name]; ok { if ctor, ok := ctors[name]; ok {
return ctor(), nil return ctor(ctx), nil
} }
return nil, errors.New("no tracer found") return nil, errors.New("no tracer found")
} }

View file

@ -29,9 +29,10 @@ import (
// Context contains some contextual infos for a transaction execution that is not // Context contains some contextual infos for a transaction execution that is not
// available from within the EVM object. // available from within the EVM object.
type Context struct { type Context struct {
BlockHash common.Hash // Hash of the block the tx is contained within (zero if dangling tx or call) BlockNumber uint64 // Block number 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) BlockHash common.Hash // Hash of the block the tx is contained within (zero if dangling tx or call)
TxHash common.Hash // Hash of the transaction being traced (zero if dangling 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)
} }
// Tracer interface extends vm.EVMLogger and additionally // Tracer interface extends vm.EVMLogger and additionally

View file

@ -314,9 +314,10 @@ func (env *TraceEnv) getTxResult(state *state.StateDB, index int, block *types.B
txContext := core.NewEVMTxContext(msg) txContext := core.NewEVMTxContext(msg)
tracerContext := tracers.Context{ tracerContext := tracers.Context{
BlockHash: block.Hash(), BlockNumber: block.NumberU64(),
TxIndex: index, BlockHash: block.Hash(),
TxHash: tx.Hash(), TxIndex: index,
TxHash: tx.Hash(),
} }
callTracer, err := tracers.New("callTracer", &tracerContext) callTracer, err := tracers.New("callTracer", &tracerContext)
if err != nil { if err != nil {