eth/tracers: support for golang tracers + add golang callTracer (#23708)
* eth/tracers: add basic native loader
* eth/tracers: add GetResult to tracer interface
* eth/tracers: add native call tracer
* eth/tracers: fix call tracer json result
* eth/tracers: minor fix
* eth/tracers: fix
* eth/tracers: fix benchTracer
* eth/tracers: test native call tracer
* eth/tracers: fix
* eth/tracers: rm extra make
Co-authored-by: Martin Holst Swende <martin@swende.se>
* eth/tracers: rm extra make
* eth/tracers: make callFrame private
* eth/tracers: clean-up and comments
* eth/tracers: add license
* eth/tracers: rework the model a bit
* eth/tracers: move tracecall tests to subpackage
* cmd/geth: load native tracers
* eth/tracers: minor fix
* eth/tracers: impl stop
* eth/tracers: add native noop tracer
* renamings
Co-authored-by: Martin Holst Swende <martin@swende.se>
* eth/tracers: more renamings
* eth/tracers: make jstracer non-exported, avoid cast
* eth/tracers, core/vm: rename vm.Tracer to vm.EVMLogger for clarity
* eth/tracers: minor comment fix
* eth/tracers/testing: lint nitpicks
* core,eth: cancel evm on nativecalltracer stop
* Revert "core,eth: cancel evm on nativecalltracer stop"
This reverts commit 01bb908790.
* eth/tracers: linter nits
* eth/tracers: fix output on err
Co-authored-by: Martin Holst Swende <martin@swende.se>
			
			
This commit is contained in:
		@@ -96,7 +96,7 @@ type rejectedTx struct {
 | 
				
			|||||||
// Apply applies a set of transactions to a pre-state
 | 
					// Apply applies a set of transactions to a pre-state
 | 
				
			||||||
func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
 | 
					func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
 | 
				
			||||||
	txs types.Transactions, miningReward int64,
 | 
						txs types.Transactions, miningReward int64,
 | 
				
			||||||
	getTracerFn func(txIndex int, txHash common.Hash) (tracer vm.Tracer, err error)) (*state.StateDB, *ExecutionResult, error) {
 | 
						getTracerFn func(txIndex int, txHash common.Hash) (tracer vm.EVMLogger, err error)) (*state.StateDB, *ExecutionResult, error) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Capture errors for BLOCKHASH operation, if we haven't been supplied the
 | 
						// Capture errors for BLOCKHASH operation, if we haven't been supplied the
 | 
				
			||||||
	// required blockhashes
 | 
						// required blockhashes
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -89,10 +89,10 @@ func Transition(ctx *cli.Context) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	var (
 | 
						var (
 | 
				
			||||||
		err     error
 | 
							err     error
 | 
				
			||||||
		tracer  vm.Tracer
 | 
							tracer  vm.EVMLogger
 | 
				
			||||||
		baseDir = ""
 | 
							baseDir = ""
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
	var getTracer func(txIndex int, txHash common.Hash) (vm.Tracer, error)
 | 
						var getTracer func(txIndex int, txHash common.Hash) (vm.EVMLogger, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// If user specified a basedir, make sure it exists
 | 
						// If user specified a basedir, make sure it exists
 | 
				
			||||||
	if ctx.IsSet(OutputBasedir.Name) {
 | 
						if ctx.IsSet(OutputBasedir.Name) {
 | 
				
			||||||
@@ -119,7 +119,7 @@ func Transition(ctx *cli.Context) error {
 | 
				
			|||||||
				prevFile.Close()
 | 
									prevFile.Close()
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}()
 | 
							}()
 | 
				
			||||||
		getTracer = func(txIndex int, txHash common.Hash) (vm.Tracer, error) {
 | 
							getTracer = func(txIndex int, txHash common.Hash) (vm.EVMLogger, error) {
 | 
				
			||||||
			if prevFile != nil {
 | 
								if prevFile != nil {
 | 
				
			||||||
				prevFile.Close()
 | 
									prevFile.Close()
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@@ -131,7 +131,7 @@ func Transition(ctx *cli.Context) error {
 | 
				
			|||||||
			return vm.NewJSONLogger(logConfig, traceFile), nil
 | 
								return vm.NewJSONLogger(logConfig, traceFile), nil
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		getTracer = func(txIndex int, txHash common.Hash) (tracer vm.Tracer, err error) {
 | 
							getTracer = func(txIndex int, txHash common.Hash) (tracer vm.EVMLogger, err error) {
 | 
				
			||||||
			return nil, nil
 | 
								return nil, nil
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -116,7 +116,7 @@ func runCmd(ctx *cli.Context) error {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var (
 | 
						var (
 | 
				
			||||||
		tracer        vm.Tracer
 | 
							tracer        vm.EVMLogger
 | 
				
			||||||
		debugLogger   *vm.StructLogger
 | 
							debugLogger   *vm.StructLogger
 | 
				
			||||||
		statedb       *state.StateDB
 | 
							statedb       *state.StateDB
 | 
				
			||||||
		chainConfig   *params.ChainConfig
 | 
							chainConfig   *params.ChainConfig
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -65,7 +65,7 @@ func stateTestCmd(ctx *cli.Context) error {
 | 
				
			|||||||
		EnableReturnData: !ctx.GlobalBool(DisableReturnDataFlag.Name),
 | 
							EnableReturnData: !ctx.GlobalBool(DisableReturnDataFlag.Name),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	var (
 | 
						var (
 | 
				
			||||||
		tracer   vm.Tracer
 | 
							tracer   vm.EVMLogger
 | 
				
			||||||
		debugger *vm.StructLogger
 | 
							debugger *vm.StructLogger
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
	switch {
 | 
						switch {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -39,6 +39,10 @@ import (
 | 
				
			|||||||
	"github.com/ethereum/go-ethereum/log"
 | 
						"github.com/ethereum/go-ethereum/log"
 | 
				
			||||||
	"github.com/ethereum/go-ethereum/metrics"
 | 
						"github.com/ethereum/go-ethereum/metrics"
 | 
				
			||||||
	"github.com/ethereum/go-ethereum/node"
 | 
						"github.com/ethereum/go-ethereum/node"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Force-load the native, to trigger registration
 | 
				
			||||||
 | 
						_ "github.com/ethereum/go-ethereum/eth/tracers/native"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"gopkg.in/urfave/cli.v1"
 | 
						"gopkg.in/urfave/cli.v1"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,7 +28,7 @@ import (
 | 
				
			|||||||
// Config are the configuration options for the Interpreter
 | 
					// Config are the configuration options for the Interpreter
 | 
				
			||||||
type Config struct {
 | 
					type Config struct {
 | 
				
			||||||
	Debug                   bool      // Enables debugging
 | 
						Debug                   bool      // Enables debugging
 | 
				
			||||||
	Tracer                  Tracer // Opcode logger
 | 
						Tracer                  EVMLogger // Opcode logger
 | 
				
			||||||
	NoRecursion             bool      // Disables call, callcode, delegate call and create
 | 
						NoRecursion             bool      // Disables call, callcode, delegate call and create
 | 
				
			||||||
	NoBaseFee               bool      // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls)
 | 
						NoBaseFee               bool      // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls)
 | 
				
			||||||
	EnablePreimageRecording bool      // Enables recording of SHA3/keccak preimages
 | 
						EnablePreimageRecording bool      // Enables recording of SHA3/keccak preimages
 | 
				
			||||||
@@ -152,9 +152,9 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
 | 
				
			|||||||
		pc   = uint64(0) // program counter
 | 
							pc   = uint64(0) // program counter
 | 
				
			||||||
		cost uint64
 | 
							cost uint64
 | 
				
			||||||
		// copies used by tracer
 | 
							// copies used by tracer
 | 
				
			||||||
		pcCopy  uint64 // needed for the deferred Tracer
 | 
							pcCopy  uint64 // needed for the deferred EVMLogger
 | 
				
			||||||
		gasCopy uint64 // for Tracer to log gas remaining before execution
 | 
							gasCopy uint64 // for EVMLogger to log gas remaining before execution
 | 
				
			||||||
		logged  bool   // deferred Tracer should ignore already logged steps
 | 
							logged  bool   // deferred EVMLogger should ignore already logged steps
 | 
				
			||||||
		res     []byte // result of the opcode execution function
 | 
							res     []byte // result of the opcode execution function
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
	// Don't move this deferrred function, it's placed before the capturestate-deferred method,
 | 
						// Don't move this deferrred function, it's placed before the capturestate-deferred method,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -98,12 +98,12 @@ func (s *StructLog) ErrorString() string {
 | 
				
			|||||||
	return ""
 | 
						return ""
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Tracer is used to collect execution traces from an EVM transaction
 | 
					// EVMLogger is used to collect execution traces from an EVM transaction
 | 
				
			||||||
// execution. CaptureState is called for each step of the VM with the
 | 
					// execution. CaptureState is called for each step of the VM with the
 | 
				
			||||||
// current VM state.
 | 
					// current VM state.
 | 
				
			||||||
// Note that reference types are actual VM data structures; make copies
 | 
					// Note that reference types are actual VM data structures; make copies
 | 
				
			||||||
// if you need to retain them beyond the current call.
 | 
					// if you need to retain them beyond the current call.
 | 
				
			||||||
type Tracer interface {
 | 
					type EVMLogger interface {
 | 
				
			||||||
	CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int)
 | 
						CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int)
 | 
				
			||||||
	CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error)
 | 
						CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error)
 | 
				
			||||||
	CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int)
 | 
						CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int)
 | 
				
			||||||
@@ -112,7 +112,7 @@ type Tracer interface {
 | 
				
			|||||||
	CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error)
 | 
						CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// StructLogger is an EVM state logger and implements Tracer.
 | 
					// StructLogger is an EVM state logger and implements EVMLogger.
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
// StructLogger can capture state based on the given Log configuration and also keeps
 | 
					// StructLogger can capture state based on the given Log configuration and also keeps
 | 
				
			||||||
// a track record of modified storage which is used in reporting snapshots of the
 | 
					// a track record of modified storage which is used in reporting snapshots of the
 | 
				
			||||||
@@ -145,7 +145,7 @@ func (l *StructLogger) Reset() {
 | 
				
			|||||||
	l.err = nil
 | 
						l.err = nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CaptureStart implements the Tracer interface to initialize the tracing operation.
 | 
					// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
 | 
				
			||||||
func (l *StructLogger) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
 | 
					func (l *StructLogger) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -210,7 +210,7 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui
 | 
				
			|||||||
	l.logs = append(l.logs, log)
 | 
						l.logs = append(l.logs, log)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CaptureFault implements the Tracer interface to trace an execution fault
 | 
					// CaptureFault implements the EVMLogger interface to trace an execution fault
 | 
				
			||||||
// while running an opcode.
 | 
					// while running an opcode.
 | 
				
			||||||
func (l *StructLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) {
 | 
					func (l *StructLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -862,12 +862,14 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
 | 
				
			|||||||
func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) {
 | 
					func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) {
 | 
				
			||||||
	// Assemble the structured logger or the JavaScript tracer
 | 
						// Assemble the structured logger or the JavaScript tracer
 | 
				
			||||||
	var (
 | 
						var (
 | 
				
			||||||
		tracer    vm.Tracer
 | 
							tracer    vm.EVMLogger
 | 
				
			||||||
		err       error
 | 
							err       error
 | 
				
			||||||
		txContext = core.NewEVMTxContext(message)
 | 
							txContext = core.NewEVMTxContext(message)
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
	switch {
 | 
						switch {
 | 
				
			||||||
	case config != nil && config.Tracer != nil:
 | 
						case config == nil:
 | 
				
			||||||
 | 
							tracer = vm.NewStructLogger(nil)
 | 
				
			||||||
 | 
						case config.Tracer != nil:
 | 
				
			||||||
		// Define a meaningful timeout of a single transaction trace
 | 
							// Define a meaningful timeout of a single transaction trace
 | 
				
			||||||
		timeout := defaultTraceTimeout
 | 
							timeout := defaultTraceTimeout
 | 
				
			||||||
		if config.Timeout != nil {
 | 
							if config.Timeout != nil {
 | 
				
			||||||
@@ -875,23 +877,19 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
 | 
				
			|||||||
				return nil, err
 | 
									return nil, err
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		// Constuct the JavaScript tracer to execute with
 | 
							if t, err := New(*config.Tracer, txctx); err != nil {
 | 
				
			||||||
		if tracer, err = New(*config.Tracer, txctx); err != nil {
 | 
					 | 
				
			||||||
			return nil, err
 | 
								return nil, err
 | 
				
			||||||
		}
 | 
							} else {
 | 
				
			||||||
		// 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 errors.Is(deadlineCtx.Err(), context.DeadlineExceeded) {
 | 
				
			||||||
				tracer.(*Tracer).Stop(errors.New("execution timeout"))
 | 
										t.Stop(errors.New("execution timeout"))
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}()
 | 
								}()
 | 
				
			||||||
			defer cancel()
 | 
								defer cancel()
 | 
				
			||||||
 | 
								tracer = t
 | 
				
			||||||
	case config == nil:
 | 
							}
 | 
				
			||||||
		tracer = vm.NewStructLogger(nil)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
		tracer = vm.NewStructLogger(config.LogConfig)
 | 
							tracer = vm.NewStructLogger(config.LogConfig)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -921,7 +919,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 *Tracer:
 | 
						case Tracer:
 | 
				
			||||||
		return tracer.GetResult()
 | 
							return tracer.GetResult()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										170
									
								
								eth/tracers/native/call.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								eth/tracers/native/call.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,170 @@
 | 
				
			|||||||
 | 
					// 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"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"math/big"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"sync/atomic"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/ethereum/go-ethereum/common"
 | 
				
			||||||
 | 
						"github.com/ethereum/go-ethereum/core/vm"
 | 
				
			||||||
 | 
						"github.com/ethereum/go-ethereum/eth/tracers"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						tracers.RegisterNativeTracer("callTracerNative", NewCallTracer)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type callFrame struct {
 | 
				
			||||||
 | 
						Type    string      `json:"type"`
 | 
				
			||||||
 | 
						From    string      `json:"from"`
 | 
				
			||||||
 | 
						To      string      `json:"to,omitempty"`
 | 
				
			||||||
 | 
						Value   string      `json:"value,omitempty"`
 | 
				
			||||||
 | 
						Gas     string      `json:"gas"`
 | 
				
			||||||
 | 
						GasUsed string      `json:"gasUsed"`
 | 
				
			||||||
 | 
						Input   string      `json:"input"`
 | 
				
			||||||
 | 
						Output  string      `json:"output,omitempty"`
 | 
				
			||||||
 | 
						Error   string      `json:"error,omitempty"`
 | 
				
			||||||
 | 
						Calls   []callFrame `json:"calls,omitempty"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type callTracer struct {
 | 
				
			||||||
 | 
						callstack []callFrame
 | 
				
			||||||
 | 
						interrupt uint32 // Atomic flag to signal execution interruption
 | 
				
			||||||
 | 
						reason    error  // Textual reason for the interruption
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewCallTracer returns a native go tracer which tracks
 | 
				
			||||||
 | 
					// call frames of a tx, and implements vm.EVMLogger.
 | 
				
			||||||
 | 
					func NewCallTracer() tracers.Tracer {
 | 
				
			||||||
 | 
						// First callframe contains tx context info
 | 
				
			||||||
 | 
						// and is populated on start and end.
 | 
				
			||||||
 | 
						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),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if create {
 | 
				
			||||||
 | 
							t.callstack[0].Type = "CREATE"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) {
 | 
				
			||||||
 | 
						t.callstack[0].GasUsed = uintToHex(gasUsed)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.callstack[0].Error = err.Error()
 | 
				
			||||||
 | 
							if err.Error() == "execution reverted" && len(output) > 0 {
 | 
				
			||||||
 | 
								t.callstack[0].Output = bytesToHex(output)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							t.callstack[0].Output = bytesToHex(output)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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) {
 | 
				
			||||||
 | 
						// Skip if tracing was interrupted
 | 
				
			||||||
 | 
						if atomic.LoadUint32(&t.interrupt) > 0 {
 | 
				
			||||||
 | 
							// TODO: env.Cancel()
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						call := callFrame{
 | 
				
			||||||
 | 
							Type:  typ.String(),
 | 
				
			||||||
 | 
							From:  addrToHex(from),
 | 
				
			||||||
 | 
							To:    addrToHex(to),
 | 
				
			||||||
 | 
							Input: bytesToHex(input),
 | 
				
			||||||
 | 
							Gas:   uintToHex(gas),
 | 
				
			||||||
 | 
							Value: bigToHex(value),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						t.callstack = append(t.callstack, call)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
 | 
				
			||||||
 | 
						size := len(t.callstack)
 | 
				
			||||||
 | 
						if size <= 1 {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// 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) {
 | 
				
			||||||
 | 
						if len(t.callstack) != 1 {
 | 
				
			||||||
 | 
							return nil, errors.New("incorrect number of top-level calls")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						res, err := json.Marshal(t.callstack[0])
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return json.RawMessage(res), t.reason
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t *callTracer) Stop(err error) {
 | 
				
			||||||
 | 
						t.reason = err
 | 
				
			||||||
 | 
						atomic.StoreUint32(&t.interrupt, 1)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func bytesToHex(s []byte) string {
 | 
				
			||||||
 | 
						return "0x" + common.Bytes2Hex(s)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func bigToHex(n *big.Int) string {
 | 
				
			||||||
 | 
						if n == nil {
 | 
				
			||||||
 | 
							return ""
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						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())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										46
									
								
								eth/tracers/native/noop.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								eth/tracers/native/noop.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
				
			|||||||
 | 
					package native
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"math/big"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/ethereum/go-ethereum/common"
 | 
				
			||||||
 | 
						"github.com/ethereum/go-ethereum/core/vm"
 | 
				
			||||||
 | 
						"github.com/ethereum/go-ethereum/eth/tracers"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						tracers.RegisterNativeTracer("noopTracerNative", NewNoopTracer)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type noopTracer struct{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewNoopTracer() tracers.Tracer {
 | 
				
			||||||
 | 
						return &noopTracer{}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t *noopTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t *noopTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t *noopTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t *noopTracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t *noopTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t *noopTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t *noopTracer) GetResult() (json.RawMessage, error) {
 | 
				
			||||||
 | 
						return json.RawMessage(`{}`), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t *noopTracer) Stop(err error) {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										246
									
								
								eth/tracers/testing/calltrace_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										246
									
								
								eth/tracers/testing/calltrace_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,246 @@
 | 
				
			|||||||
 | 
					package testing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"math/big"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"reflect"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
						"unicode"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/ethereum/go-ethereum/common"
 | 
				
			||||||
 | 
						"github.com/ethereum/go-ethereum/common/hexutil"
 | 
				
			||||||
 | 
						"github.com/ethereum/go-ethereum/common/math"
 | 
				
			||||||
 | 
						"github.com/ethereum/go-ethereum/core"
 | 
				
			||||||
 | 
						"github.com/ethereum/go-ethereum/core/rawdb"
 | 
				
			||||||
 | 
						"github.com/ethereum/go-ethereum/core/types"
 | 
				
			||||||
 | 
						"github.com/ethereum/go-ethereum/core/vm"
 | 
				
			||||||
 | 
						"github.com/ethereum/go-ethereum/eth/tracers"
 | 
				
			||||||
 | 
						"github.com/ethereum/go-ethereum/rlp"
 | 
				
			||||||
 | 
						"github.com/ethereum/go-ethereum/tests"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Force-load the native, to trigger registration
 | 
				
			||||||
 | 
						_ "github.com/ethereum/go-ethereum/eth/tracers/native"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type callContext struct {
 | 
				
			||||||
 | 
						Number     math.HexOrDecimal64   `json:"number"`
 | 
				
			||||||
 | 
						Difficulty *math.HexOrDecimal256 `json:"difficulty"`
 | 
				
			||||||
 | 
						Time       math.HexOrDecimal64   `json:"timestamp"`
 | 
				
			||||||
 | 
						GasLimit   math.HexOrDecimal64   `json:"gasLimit"`
 | 
				
			||||||
 | 
						Miner      common.Address        `json:"miner"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// callTrace is the result of a callTracer run.
 | 
				
			||||||
 | 
					type callTrace struct {
 | 
				
			||||||
 | 
						Type    string          `json:"type"`
 | 
				
			||||||
 | 
						From    common.Address  `json:"from"`
 | 
				
			||||||
 | 
						To      common.Address  `json:"to"`
 | 
				
			||||||
 | 
						Input   hexutil.Bytes   `json:"input"`
 | 
				
			||||||
 | 
						Output  hexutil.Bytes   `json:"output"`
 | 
				
			||||||
 | 
						Gas     *hexutil.Uint64 `json:"gas,omitempty"`
 | 
				
			||||||
 | 
						GasUsed *hexutil.Uint64 `json:"gasUsed,omitempty"`
 | 
				
			||||||
 | 
						Value   *hexutil.Big    `json:"value,omitempty"`
 | 
				
			||||||
 | 
						Error   string          `json:"error,omitempty"`
 | 
				
			||||||
 | 
						Calls   []callTrace     `json:"calls,omitempty"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// callTracerTest defines a single test to check the call tracer against.
 | 
				
			||||||
 | 
					type callTracerTest struct {
 | 
				
			||||||
 | 
						Genesis *core.Genesis `json:"genesis"`
 | 
				
			||||||
 | 
						Context *callContext  `json:"context"`
 | 
				
			||||||
 | 
						Input   string        `json:"input"`
 | 
				
			||||||
 | 
						Result  *callTrace    `json:"result"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Iterates over all the input-output datasets in the tracer test harness and
 | 
				
			||||||
 | 
					// runs the JavaScript tracers against them.
 | 
				
			||||||
 | 
					func TestCallTracerLegacy(t *testing.T) {
 | 
				
			||||||
 | 
						testCallTracer("callTracerLegacy", "call_tracer_legacy", t)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestCallTracer(t *testing.T) {
 | 
				
			||||||
 | 
						testCallTracer("callTracer", "call_tracer", t)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestCallTracerNative(t *testing.T) {
 | 
				
			||||||
 | 
						testCallTracer("callTracerNative", "call_tracer", t)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func testCallTracer(tracerName string, dirPath string, t *testing.T) {
 | 
				
			||||||
 | 
						files, err := ioutil.ReadDir(filepath.Join("..", "testdata", dirPath))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatalf("failed to retrieve tracer test suite: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, file := range files {
 | 
				
			||||||
 | 
							if !strings.HasSuffix(file.Name(), ".json") {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							file := file // capture range variable
 | 
				
			||||||
 | 
							t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) {
 | 
				
			||||||
 | 
								t.Parallel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								var (
 | 
				
			||||||
 | 
									test = new(callTracerTest)
 | 
				
			||||||
 | 
									tx   = new(types.Transaction)
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
								// Call tracer test found, read if from disk
 | 
				
			||||||
 | 
								if blob, err := ioutil.ReadFile(filepath.Join("..", "testdata", dirPath, file.Name())); err != nil {
 | 
				
			||||||
 | 
									t.Fatalf("failed to read testcase: %v", err)
 | 
				
			||||||
 | 
								} else if err := json.Unmarshal(blob, test); err != nil {
 | 
				
			||||||
 | 
									t.Fatalf("failed to parse testcase: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
 | 
				
			||||||
 | 
									t.Fatalf("failed to parse testcase input: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								// Configure a blockchain with the given prestate
 | 
				
			||||||
 | 
								var (
 | 
				
			||||||
 | 
									signer    = types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)))
 | 
				
			||||||
 | 
									origin, _ = signer.Sender(tx)
 | 
				
			||||||
 | 
									txContext = vm.TxContext{
 | 
				
			||||||
 | 
										Origin:   origin,
 | 
				
			||||||
 | 
										GasPrice: tx.GasPrice(),
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									context = vm.BlockContext{
 | 
				
			||||||
 | 
										CanTransfer: core.CanTransfer,
 | 
				
			||||||
 | 
										Transfer:    core.Transfer,
 | 
				
			||||||
 | 
										Coinbase:    test.Context.Miner,
 | 
				
			||||||
 | 
										BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)),
 | 
				
			||||||
 | 
										Time:        new(big.Int).SetUint64(uint64(test.Context.Time)),
 | 
				
			||||||
 | 
										Difficulty:  (*big.Int)(test.Context.Difficulty),
 | 
				
			||||||
 | 
										GasLimit:    uint64(test.Context.GasLimit),
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									_, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
								tracer, err := tracers.New(tracerName, new(tracers.Context))
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									t.Fatalf("failed to create call tracer: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer})
 | 
				
			||||||
 | 
								msg, err := tx.AsMessage(signer, nil)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									t.Fatalf("failed to prepare transaction for tracing: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
 | 
				
			||||||
 | 
								if _, err = st.TransitionDb(); err != nil {
 | 
				
			||||||
 | 
									t.Fatalf("failed to execute transaction: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								// Retrieve the trace result and compare against the etalon
 | 
				
			||||||
 | 
								res, err := tracer.GetResult()
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									t.Fatalf("failed to retrieve trace result: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								ret := new(callTrace)
 | 
				
			||||||
 | 
								if err := json.Unmarshal(res, ret); err != nil {
 | 
				
			||||||
 | 
									t.Fatalf("failed to unmarshal trace result: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if !jsonEqual(ret, test.Result) {
 | 
				
			||||||
 | 
									// uncomment this for easier debugging
 | 
				
			||||||
 | 
									//have, _ := json.MarshalIndent(ret, "", " ")
 | 
				
			||||||
 | 
									//want, _ := json.MarshalIndent(test.Result, "", " ")
 | 
				
			||||||
 | 
									//t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", string(have), string(want))
 | 
				
			||||||
 | 
									t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, test.Result)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// jsonEqual is similar to reflect.DeepEqual, but does a 'bounce' via json prior to
 | 
				
			||||||
 | 
					// comparison
 | 
				
			||||||
 | 
					func jsonEqual(x, y interface{}) bool {
 | 
				
			||||||
 | 
						xTrace := new(callTrace)
 | 
				
			||||||
 | 
						yTrace := new(callTrace)
 | 
				
			||||||
 | 
						if xj, err := json.Marshal(x); err == nil {
 | 
				
			||||||
 | 
							json.Unmarshal(xj, xTrace)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if yj, err := json.Marshal(y); err == nil {
 | 
				
			||||||
 | 
							json.Unmarshal(yj, yTrace)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return reflect.DeepEqual(xTrace, yTrace)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// camel converts a snake cased input string into a camel cased output.
 | 
				
			||||||
 | 
					func camel(str string) string {
 | 
				
			||||||
 | 
						pieces := strings.Split(str, "_")
 | 
				
			||||||
 | 
						for i := 1; i < len(pieces); i++ {
 | 
				
			||||||
 | 
							pieces[i] = string(unicode.ToUpper(rune(pieces[i][0]))) + pieces[i][1:]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return strings.Join(pieces, "")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					func BenchmarkTracers(b *testing.B) {
 | 
				
			||||||
 | 
						files, err := ioutil.ReadDir(filepath.Join("..", "testdata", "call_tracer"))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							b.Fatalf("failed to retrieve tracer test suite: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, file := range files {
 | 
				
			||||||
 | 
							if !strings.HasSuffix(file.Name(), ".json") {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							file := file // capture range variable
 | 
				
			||||||
 | 
							b.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(b *testing.B) {
 | 
				
			||||||
 | 
								blob, err := ioutil.ReadFile(filepath.Join("..", "testdata", "call_tracer", file.Name()))
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									b.Fatalf("failed to read testcase: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								test := new(callTracerTest)
 | 
				
			||||||
 | 
								if err := json.Unmarshal(blob, test); err != nil {
 | 
				
			||||||
 | 
									b.Fatalf("failed to parse testcase: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								benchTracer("callTracerNative", test, b)
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
 | 
				
			||||||
 | 
						// Configure a blockchain with the given prestate
 | 
				
			||||||
 | 
						tx := new(types.Transaction)
 | 
				
			||||||
 | 
						if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
 | 
				
			||||||
 | 
							b.Fatalf("failed to parse testcase input: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)))
 | 
				
			||||||
 | 
						msg, err := tx.AsMessage(signer, nil)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							b.Fatalf("failed to prepare transaction for tracing: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						origin, _ := signer.Sender(tx)
 | 
				
			||||||
 | 
						txContext := vm.TxContext{
 | 
				
			||||||
 | 
							Origin:   origin,
 | 
				
			||||||
 | 
							GasPrice: tx.GasPrice(),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						context := vm.BlockContext{
 | 
				
			||||||
 | 
							CanTransfer: core.CanTransfer,
 | 
				
			||||||
 | 
							Transfer:    core.Transfer,
 | 
				
			||||||
 | 
							Coinbase:    test.Context.Miner,
 | 
				
			||||||
 | 
							BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)),
 | 
				
			||||||
 | 
							Time:        new(big.Int).SetUint64(uint64(test.Context.Time)),
 | 
				
			||||||
 | 
							Difficulty:  (*big.Int)(test.Context.Difficulty),
 | 
				
			||||||
 | 
							GasLimit:    uint64(test.Context.GasLimit),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						b.ReportAllocs()
 | 
				
			||||||
 | 
						b.ResetTimer()
 | 
				
			||||||
 | 
						for i := 0; i < b.N; i++ {
 | 
				
			||||||
 | 
							tracer, err := tracers.New(tracerName, new(tracers.Context))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								b.Fatalf("failed to create call tracer: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer})
 | 
				
			||||||
 | 
							snap := statedb.Snapshot()
 | 
				
			||||||
 | 
							st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
 | 
				
			||||||
 | 
							if _, err = st.TransitionDb(); err != nil {
 | 
				
			||||||
 | 
								b.Fatalf("failed to execute transaction: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if _, err = tracer.GetResult(); err != nil {
 | 
				
			||||||
 | 
								b.Fatal(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							statedb.RevertToSnapshot(snap)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -363,9 +363,9 @@ func (r *frameResult) pushObject(vm *duktape.Context) {
 | 
				
			|||||||
	vm.PutPropString(obj, "getError")
 | 
						vm.PutPropString(obj, "getError")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Tracer provides an implementation of Tracer that evaluates a Javascript
 | 
					// jsTracer 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),
 | 
				
			||||||
@@ -620,14 +616,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 {
 | 
				
			||||||
@@ -663,7 +659,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"
 | 
				
			||||||
@@ -693,7 +689,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
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -729,7 +725,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
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -743,7 +739,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
 | 
				
			||||||
@@ -754,7 +750,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
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -784,7 +780,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
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -808,7 +804,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()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -830,7 +826,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)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,14 +18,53 @@
 | 
				
			|||||||
package tracers
 | 
					package tracers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
	"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.EVMLogger and additionally
 | 
				
			||||||
var all = make(map[string]string)
 | 
					// allows collecting the tracing result.
 | 
				
			||||||
 | 
					type Tracer interface {
 | 
				
			||||||
 | 
						vm.EVMLogger
 | 
				
			||||||
 | 
						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 which adhere
 | 
				
			||||||
 | 
					// to the `Tracer` interface available to the rest of the codebase.
 | 
				
			||||||
 | 
					// It is typically invoked in the `init()` function, e.g. see the `native/call.go`.
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// 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 +79,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
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,23 +20,18 @@ import (
 | 
				
			|||||||
	"crypto/ecdsa"
 | 
						"crypto/ecdsa"
 | 
				
			||||||
	"crypto/rand"
 | 
						"crypto/rand"
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"io/ioutil"
 | 
					 | 
				
			||||||
	"math/big"
 | 
						"math/big"
 | 
				
			||||||
	"path/filepath"
 | 
					 | 
				
			||||||
	"reflect"
 | 
						"reflect"
 | 
				
			||||||
	"strings"
 | 
					 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/ethereum/go-ethereum/common"
 | 
						"github.com/ethereum/go-ethereum/common"
 | 
				
			||||||
	"github.com/ethereum/go-ethereum/common/hexutil"
 | 
						"github.com/ethereum/go-ethereum/common/hexutil"
 | 
				
			||||||
	"github.com/ethereum/go-ethereum/common/math"
 | 
					 | 
				
			||||||
	"github.com/ethereum/go-ethereum/core"
 | 
						"github.com/ethereum/go-ethereum/core"
 | 
				
			||||||
	"github.com/ethereum/go-ethereum/core/rawdb"
 | 
						"github.com/ethereum/go-ethereum/core/rawdb"
 | 
				
			||||||
	"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/params"
 | 
						"github.com/ethereum/go-ethereum/params"
 | 
				
			||||||
	"github.com/ethereum/go-ethereum/rlp"
 | 
					 | 
				
			||||||
	"github.com/ethereum/go-ethereum/tests"
 | 
						"github.com/ethereum/go-ethereum/tests"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -104,22 +99,6 @@ type callTrace struct {
 | 
				
			|||||||
	Calls   []callTrace     `json:"calls,omitempty"`
 | 
						Calls   []callTrace     `json:"calls,omitempty"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type callContext struct {
 | 
					 | 
				
			||||||
	Number     math.HexOrDecimal64   `json:"number"`
 | 
					 | 
				
			||||||
	Difficulty *math.HexOrDecimal256 `json:"difficulty"`
 | 
					 | 
				
			||||||
	Time       math.HexOrDecimal64   `json:"timestamp"`
 | 
					 | 
				
			||||||
	GasLimit   math.HexOrDecimal64   `json:"gasLimit"`
 | 
					 | 
				
			||||||
	Miner      common.Address        `json:"miner"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// callTracerTest defines a single test to check the call tracer against.
 | 
					 | 
				
			||||||
type callTracerTest struct {
 | 
					 | 
				
			||||||
	Genesis *core.Genesis `json:"genesis"`
 | 
					 | 
				
			||||||
	Context *callContext  `json:"context"`
 | 
					 | 
				
			||||||
	Input   string        `json:"input"`
 | 
					 | 
				
			||||||
	Result  *callTrace    `json:"result"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// TestZeroValueToNotExitCall tests the calltracer(s) on the following:
 | 
					// TestZeroValueToNotExitCall tests the calltracer(s) on the following:
 | 
				
			||||||
// Tx to A, A calls B with zero value. B does not already exist.
 | 
					// Tx to A, A calls B with zero value. B does not already exist.
 | 
				
			||||||
// Expected: that enter/exit is invoked and the inner call is shown in the result
 | 
					// Expected: that enter/exit is invoked and the inner call is shown in the result
 | 
				
			||||||
@@ -280,96 +259,6 @@ func TestPrestateTracerCreate2(t *testing.T) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Iterates over all the input-output datasets in the tracer test harness and
 | 
					 | 
				
			||||||
// runs the JavaScript tracers against them.
 | 
					 | 
				
			||||||
func TestCallTracerLegacy(t *testing.T) {
 | 
					 | 
				
			||||||
	testCallTracer("callTracerLegacy", "call_tracer_legacy", t)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func testCallTracer(tracer string, dirPath string, t *testing.T) {
 | 
					 | 
				
			||||||
	files, err := ioutil.ReadDir(filepath.Join("testdata", dirPath))
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		t.Fatalf("failed to retrieve tracer test suite: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	for _, file := range files {
 | 
					 | 
				
			||||||
		if !strings.HasSuffix(file.Name(), ".json") {
 | 
					 | 
				
			||||||
			continue
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		file := file // capture range variable
 | 
					 | 
				
			||||||
		t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) {
 | 
					 | 
				
			||||||
			t.Parallel()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Call tracer test found, read if from disk
 | 
					 | 
				
			||||||
			blob, err := ioutil.ReadFile(filepath.Join("testdata", dirPath, file.Name()))
 | 
					 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				t.Fatalf("failed to read testcase: %v", err)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			test := new(callTracerTest)
 | 
					 | 
				
			||||||
			if err := json.Unmarshal(blob, test); err != nil {
 | 
					 | 
				
			||||||
				t.Fatalf("failed to parse testcase: %v", err)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			// Configure a blockchain with the given prestate
 | 
					 | 
				
			||||||
			tx := new(types.Transaction)
 | 
					 | 
				
			||||||
			if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
 | 
					 | 
				
			||||||
				t.Fatalf("failed to parse testcase input: %v", err)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)))
 | 
					 | 
				
			||||||
			origin, _ := signer.Sender(tx)
 | 
					 | 
				
			||||||
			txContext := vm.TxContext{
 | 
					 | 
				
			||||||
				Origin:   origin,
 | 
					 | 
				
			||||||
				GasPrice: tx.GasPrice(),
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			context := vm.BlockContext{
 | 
					 | 
				
			||||||
				CanTransfer: core.CanTransfer,
 | 
					 | 
				
			||||||
				Transfer:    core.Transfer,
 | 
					 | 
				
			||||||
				Coinbase:    test.Context.Miner,
 | 
					 | 
				
			||||||
				BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)),
 | 
					 | 
				
			||||||
				Time:        new(big.Int).SetUint64(uint64(test.Context.Time)),
 | 
					 | 
				
			||||||
				Difficulty:  (*big.Int)(test.Context.Difficulty),
 | 
					 | 
				
			||||||
				GasLimit:    uint64(test.Context.GasLimit),
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Create the tracer, the EVM environment and run it
 | 
					 | 
				
			||||||
			tracer, err := New(tracer, new(Context))
 | 
					 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				t.Fatalf("failed to create call tracer: %v", err)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			msg, err := tx.AsMessage(signer, nil)
 | 
					 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				t.Fatalf("failed to prepare transaction for tracing: %v", err)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
 | 
					 | 
				
			||||||
			if _, err = st.TransitionDb(); err != nil {
 | 
					 | 
				
			||||||
				t.Fatalf("failed to execute transaction: %v", err)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			// Retrieve the trace result and compare against the etalon
 | 
					 | 
				
			||||||
			res, err := tracer.GetResult()
 | 
					 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				t.Fatalf("failed to retrieve trace result: %v", err)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			ret := new(callTrace)
 | 
					 | 
				
			||||||
			if err := json.Unmarshal(res, ret); err != nil {
 | 
					 | 
				
			||||||
				t.Fatalf("failed to unmarshal trace result: %v", err)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if !jsonEqual(ret, test.Result) {
 | 
					 | 
				
			||||||
				// uncomment this for easier debugging
 | 
					 | 
				
			||||||
				//have, _ := json.MarshalIndent(ret, "", " ")
 | 
					 | 
				
			||||||
				//want, _ := json.MarshalIndent(test.Result, "", " ")
 | 
					 | 
				
			||||||
				//t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", string(have), string(want))
 | 
					 | 
				
			||||||
				t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, test.Result)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func TestCallTracer(t *testing.T) {
 | 
					 | 
				
			||||||
	testCallTracer("callTracer", "call_tracer", t)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// jsonEqual is similar to reflect.DeepEqual, but does a 'bounce' via json prior to
 | 
					// jsonEqual is similar to reflect.DeepEqual, but does a 'bounce' via json prior to
 | 
				
			||||||
// comparison
 | 
					// comparison
 | 
				
			||||||
func jsonEqual(x, y interface{}) bool {
 | 
					func jsonEqual(x, y interface{}) bool {
 | 
				
			||||||
@@ -466,73 +355,3 @@ func BenchmarkTransactionTrace(b *testing.B) {
 | 
				
			|||||||
		tracer.Reset()
 | 
							tracer.Reset()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
func BenchmarkTracers(b *testing.B) {
 | 
					 | 
				
			||||||
	files, err := ioutil.ReadDir(filepath.Join("testdata", "call_tracer"))
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		b.Fatalf("failed to retrieve tracer test suite: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	for _, file := range files {
 | 
					 | 
				
			||||||
		if !strings.HasSuffix(file.Name(), ".json") {
 | 
					 | 
				
			||||||
			continue
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		file := file // capture range variable
 | 
					 | 
				
			||||||
		b.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(b *testing.B) {
 | 
					 | 
				
			||||||
			blob, err := ioutil.ReadFile(filepath.Join("testdata", "call_tracer", file.Name()))
 | 
					 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				b.Fatalf("failed to read testcase: %v", err)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			test := new(callTracerTest)
 | 
					 | 
				
			||||||
			if err := json.Unmarshal(blob, test); err != nil {
 | 
					 | 
				
			||||||
				b.Fatalf("failed to parse testcase: %v", err)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			benchTracer("callTracer", test, b)
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
 | 
					 | 
				
			||||||
	// Configure a blockchain with the given prestate
 | 
					 | 
				
			||||||
	tx := new(types.Transaction)
 | 
					 | 
				
			||||||
	if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
 | 
					 | 
				
			||||||
		b.Fatalf("failed to parse testcase input: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)))
 | 
					 | 
				
			||||||
	msg, err := tx.AsMessage(signer, nil)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		b.Fatalf("failed to prepare transaction for tracing: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	origin, _ := signer.Sender(tx)
 | 
					 | 
				
			||||||
	txContext := vm.TxContext{
 | 
					 | 
				
			||||||
		Origin:   origin,
 | 
					 | 
				
			||||||
		GasPrice: tx.GasPrice(),
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	context := vm.BlockContext{
 | 
					 | 
				
			||||||
		CanTransfer: core.CanTransfer,
 | 
					 | 
				
			||||||
		Transfer:    core.Transfer,
 | 
					 | 
				
			||||||
		Coinbase:    test.Context.Miner,
 | 
					 | 
				
			||||||
		BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)),
 | 
					 | 
				
			||||||
		Time:        new(big.Int).SetUint64(uint64(test.Context.Time)),
 | 
					 | 
				
			||||||
		Difficulty:  (*big.Int)(test.Context.Difficulty),
 | 
					 | 
				
			||||||
		GasLimit:    uint64(test.Context.GasLimit),
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Create the tracer, the EVM environment and run it
 | 
					 | 
				
			||||||
	tracer, err := New(tracerName, new(Context))
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		b.Fatalf("failed to create call tracer: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	b.ReportAllocs()
 | 
					 | 
				
			||||||
	b.ResetTimer()
 | 
					 | 
				
			||||||
	for i := 0; i < b.N; i++ {
 | 
					 | 
				
			||||||
		snap := statedb.Snapshot()
 | 
					 | 
				
			||||||
		st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
 | 
					 | 
				
			||||||
		if _, err = st.TransitionDb(); err != nil {
 | 
					 | 
				
			||||||
			b.Fatalf("failed to execute transaction: %v", err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		statedb.RevertToSnapshot(snap)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user