github.com/ethereum/go-ethereum@v1.16.1/eth/tracers/logger/logger.go (about)

     1  // Copyright 2021 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package logger
    18  
    19  import (
    20  	"encoding/hex"
    21  	"encoding/json"
    22  	"errors"
    23  	"fmt"
    24  	"io"
    25  	"maps"
    26  	"math/big"
    27  	"strings"
    28  	"sync/atomic"
    29  
    30  	"github.com/ethereum/go-ethereum/common"
    31  	"github.com/ethereum/go-ethereum/common/hexutil"
    32  	"github.com/ethereum/go-ethereum/common/math"
    33  	"github.com/ethereum/go-ethereum/core/tracing"
    34  	"github.com/ethereum/go-ethereum/core/types"
    35  	"github.com/ethereum/go-ethereum/core/vm"
    36  	"github.com/ethereum/go-ethereum/params"
    37  	"github.com/holiman/uint256"
    38  )
    39  
    40  // Storage represents a contract's storage.
    41  type Storage map[common.Hash]common.Hash
    42  
    43  // Config are the configuration options for structured logger the EVM
    44  type Config struct {
    45  	EnableMemory     bool // enable memory capture
    46  	DisableStack     bool // disable stack capture
    47  	DisableStorage   bool // disable storage capture
    48  	EnableReturnData bool // enable return data capture
    49  	Limit            int  // maximum size of output, but zero means unlimited
    50  	// Chain overrides, can be used to execute a trace using future fork rules
    51  	Overrides *params.ChainConfig `json:"overrides,omitempty"`
    52  }
    53  
    54  //go:generate go run github.com/fjl/gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go
    55  
    56  // StructLog is emitted to the EVM each cycle and lists information about the
    57  // current internal state prior to the execution of the statement.
    58  type StructLog struct {
    59  	Pc            uint64                      `json:"pc"`
    60  	Op            vm.OpCode                   `json:"op"`
    61  	Gas           uint64                      `json:"gas"`
    62  	GasCost       uint64                      `json:"gasCost"`
    63  	Memory        []byte                      `json:"memory,omitempty"`
    64  	MemorySize    int                         `json:"memSize"`
    65  	Stack         []uint256.Int               `json:"stack"`
    66  	ReturnData    []byte                      `json:"returnData,omitempty"`
    67  	Storage       map[common.Hash]common.Hash `json:"-"`
    68  	Depth         int                         `json:"depth"`
    69  	RefundCounter uint64                      `json:"refund"`
    70  	Err           error                       `json:"-"`
    71  }
    72  
    73  // overrides for gencodec
    74  type structLogMarshaling struct {
    75  	Gas         math.HexOrDecimal64
    76  	GasCost     math.HexOrDecimal64
    77  	Memory      hexutil.Bytes
    78  	ReturnData  hexutil.Bytes
    79  	Stack       []hexutil.U256
    80  	OpName      string `json:"opName"`          // adds call to OpName() in MarshalJSON
    81  	ErrorString string `json:"error,omitempty"` // adds call to ErrorString() in MarshalJSON
    82  }
    83  
    84  // OpName formats the operand name in a human-readable format.
    85  func (s *StructLog) OpName() string {
    86  	return s.Op.String()
    87  }
    88  
    89  // ErrorString formats the log's error as a string.
    90  func (s *StructLog) ErrorString() string {
    91  	if s.Err != nil {
    92  		return s.Err.Error()
    93  	}
    94  	return ""
    95  }
    96  
    97  // WriteTo writes the human-readable log data into the supplied writer.
    98  func (s *StructLog) WriteTo(writer io.Writer) {
    99  	fmt.Fprintf(writer, "%-16spc=%08d gas=%v cost=%v", s.Op, s.Pc, s.Gas, s.GasCost)
   100  	if s.Err != nil {
   101  		fmt.Fprintf(writer, " ERROR: %v", s.Err)
   102  	}
   103  	fmt.Fprintln(writer)
   104  
   105  	if len(s.Stack) > 0 {
   106  		fmt.Fprintln(writer, "Stack:")
   107  		for i := len(s.Stack) - 1; i >= 0; i-- {
   108  			fmt.Fprintf(writer, "%08d  %s\n", len(s.Stack)-i-1, s.Stack[i].Hex())
   109  		}
   110  	}
   111  	if len(s.Memory) > 0 {
   112  		fmt.Fprintln(writer, "Memory:")
   113  		fmt.Fprint(writer, hex.Dump(s.Memory))
   114  	}
   115  	if len(s.Storage) > 0 {
   116  		fmt.Fprintln(writer, "Storage:")
   117  		for h, item := range s.Storage {
   118  			fmt.Fprintf(writer, "%x: %x\n", h, item)
   119  		}
   120  	}
   121  	if len(s.ReturnData) > 0 {
   122  		fmt.Fprintln(writer, "ReturnData:")
   123  		fmt.Fprint(writer, hex.Dump(s.ReturnData))
   124  	}
   125  	fmt.Fprintln(writer)
   126  }
   127  
   128  // structLogLegacy stores a structured log emitted by the EVM while replaying a
   129  // transaction in debug mode. It's the legacy format used in tracer. The differences
   130  // between the structLog json and the 'legacy' json are:
   131  //
   132  // op:
   133  // Legacy uses string (e.g. "SSTORE"), non-legacy uses a byte.
   134  // non-legacy has an 'opName' field containing the op name.
   135  //
   136  // gas, gasCost:
   137  // Legacy uses integers, non-legacy hex-strings
   138  //
   139  // memory:
   140  // Legacy uses a list of 64-char strings, each representing 32-byte chunks
   141  // of evm memory. Non-legacy just uses a string of hexdata, no chunking.
   142  //
   143  // storage:
   144  // Legacy has a storage field while non-legacy doesn't.
   145  type structLogLegacy struct {
   146  	Pc            uint64             `json:"pc"`
   147  	Op            string             `json:"op"`
   148  	Gas           uint64             `json:"gas"`
   149  	GasCost       uint64             `json:"gasCost"`
   150  	Depth         int                `json:"depth"`
   151  	Error         string             `json:"error,omitempty"`
   152  	Stack         *[]string          `json:"stack,omitempty"`
   153  	ReturnData    string             `json:"returnData,omitempty"`
   154  	Memory        *[]string          `json:"memory,omitempty"`
   155  	Storage       *map[string]string `json:"storage,omitempty"`
   156  	RefundCounter uint64             `json:"refund,omitempty"`
   157  }
   158  
   159  // toLegacyJSON converts the structLog to legacy json-encoded legacy form.
   160  func (s *StructLog) toLegacyJSON() json.RawMessage {
   161  	msg := structLogLegacy{
   162  		Pc:            s.Pc,
   163  		Op:            s.Op.String(),
   164  		Gas:           s.Gas,
   165  		GasCost:       s.GasCost,
   166  		Depth:         s.Depth,
   167  		Error:         s.ErrorString(),
   168  		RefundCounter: s.RefundCounter,
   169  	}
   170  	if s.Stack != nil {
   171  		stack := make([]string, len(s.Stack))
   172  		for i, stackValue := range s.Stack {
   173  			stack[i] = stackValue.Hex()
   174  		}
   175  		msg.Stack = &stack
   176  	}
   177  	if len(s.ReturnData) > 0 {
   178  		msg.ReturnData = hexutil.Bytes(s.ReturnData).String()
   179  	}
   180  	if len(s.Memory) > 0 {
   181  		memory := make([]string, 0, (len(s.Memory)+31)/32)
   182  		for i := 0; i < len(s.Memory); i += 32 {
   183  			end := i + 32
   184  			if end > len(s.Memory) {
   185  				end = len(s.Memory)
   186  			}
   187  			memory = append(memory, fmt.Sprintf("%x", s.Memory[i:end]))
   188  		}
   189  		msg.Memory = &memory
   190  	}
   191  	if len(s.Storage) > 0 {
   192  		storage := make(map[string]string)
   193  		for i, storageValue := range s.Storage {
   194  			storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue)
   195  		}
   196  		msg.Storage = &storage
   197  	}
   198  	element, _ := json.Marshal(msg)
   199  	return element
   200  }
   201  
   202  // StructLogger is an EVM state logger and implements EVMLogger.
   203  //
   204  // StructLogger can capture state based on the given Log configuration and also keeps
   205  // a track record of modified storage which is used in reporting snapshots of the
   206  // contract their storage.
   207  //
   208  // A StructLogger can either yield it's output immediately (streaming) or store for
   209  // later output.
   210  type StructLogger struct {
   211  	cfg Config
   212  	env *tracing.VMContext
   213  
   214  	storage map[common.Address]Storage
   215  	output  []byte
   216  	err     error
   217  	usedGas uint64
   218  
   219  	writer     io.Writer         // If set, the logger will stream instead of store logs
   220  	logs       []json.RawMessage // buffer of json-encoded logs
   221  	resultSize int
   222  
   223  	interrupt atomic.Bool // Atomic flag to signal execution interruption
   224  	reason    error       // Textual reason for the interruption
   225  	skip      bool        // skip processing hooks.
   226  }
   227  
   228  // NewStreamingStructLogger returns a new streaming logger.
   229  func NewStreamingStructLogger(cfg *Config, writer io.Writer) *StructLogger {
   230  	l := NewStructLogger(cfg)
   231  	l.writer = writer
   232  	return l
   233  }
   234  
   235  // NewStructLogger construct a new (non-streaming) struct logger.
   236  func NewStructLogger(cfg *Config) *StructLogger {
   237  	logger := &StructLogger{
   238  		storage: make(map[common.Address]Storage),
   239  		logs:    make([]json.RawMessage, 0),
   240  	}
   241  	if cfg != nil {
   242  		logger.cfg = *cfg
   243  	}
   244  	return logger
   245  }
   246  
   247  func (l *StructLogger) Hooks() *tracing.Hooks {
   248  	return &tracing.Hooks{
   249  		OnTxStart:           l.OnTxStart,
   250  		OnTxEnd:             l.OnTxEnd,
   251  		OnSystemCallStartV2: l.OnSystemCallStart,
   252  		OnSystemCallEnd:     l.OnSystemCallEnd,
   253  		OnExit:              l.OnExit,
   254  		OnOpcode:            l.OnOpcode,
   255  	}
   256  }
   257  
   258  // OnOpcode logs a new structured log message and pushes it out to the environment
   259  //
   260  // OnOpcode also tracks SLOAD/SSTORE ops to track storage change.
   261  func (l *StructLogger) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
   262  	// If tracing was interrupted, exit
   263  	if l.interrupt.Load() {
   264  		return
   265  	}
   266  	// Processing a system call.
   267  	if l.skip {
   268  		return
   269  	}
   270  	// check if already accumulated the size of the response.
   271  	if l.cfg.Limit != 0 && l.resultSize > l.cfg.Limit {
   272  		return
   273  	}
   274  	var (
   275  		op           = vm.OpCode(opcode)
   276  		memory       = scope.MemoryData()
   277  		contractAddr = scope.Address()
   278  		stack        = scope.StackData()
   279  		stackLen     = len(stack)
   280  	)
   281  	log := StructLog{pc, op, gas, cost, nil, len(memory), nil, nil, nil, depth, l.env.StateDB.GetRefund(), err}
   282  	if l.cfg.EnableMemory {
   283  		log.Memory = memory
   284  	}
   285  	if !l.cfg.DisableStack {
   286  		log.Stack = scope.StackData()
   287  	}
   288  	if l.cfg.EnableReturnData {
   289  		log.ReturnData = rData
   290  	}
   291  
   292  	// Copy a snapshot of the current storage to a new container
   293  	var storage Storage
   294  	if !l.cfg.DisableStorage && (op == vm.SLOAD || op == vm.SSTORE) {
   295  		// initialise new changed values storage container for this contract
   296  		// if not present.
   297  		if l.storage[contractAddr] == nil {
   298  			l.storage[contractAddr] = make(Storage)
   299  		}
   300  		// capture SLOAD opcodes and record the read entry in the local storage
   301  		if op == vm.SLOAD && stackLen >= 1 {
   302  			var (
   303  				address = common.Hash(stack[stackLen-1].Bytes32())
   304  				value   = l.env.StateDB.GetState(contractAddr, address)
   305  			)
   306  			l.storage[contractAddr][address] = value
   307  			storage = maps.Clone(l.storage[contractAddr])
   308  		} else if op == vm.SSTORE && stackLen >= 2 {
   309  			// capture SSTORE opcodes and record the written entry in the local storage.
   310  			var (
   311  				value   = common.Hash(stack[stackLen-2].Bytes32())
   312  				address = common.Hash(stack[stackLen-1].Bytes32())
   313  			)
   314  			l.storage[contractAddr][address] = value
   315  			storage = maps.Clone(l.storage[contractAddr])
   316  		}
   317  	}
   318  	log.Storage = storage
   319  
   320  	// create a log
   321  	if l.writer == nil {
   322  		entry := log.toLegacyJSON()
   323  		l.resultSize += len(entry)
   324  		l.logs = append(l.logs, entry)
   325  		return
   326  	}
   327  	log.WriteTo(l.writer)
   328  }
   329  
   330  // OnExit is called a call frame finishes processing.
   331  func (l *StructLogger) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
   332  	if depth != 0 {
   333  		return
   334  	}
   335  	if l.skip {
   336  		return
   337  	}
   338  	l.output = output
   339  	l.err = err
   340  	// TODO @holiman, should we output the per-scope output?
   341  	//if l.cfg.Debug {
   342  	//	fmt.Printf("%#x\n", output)
   343  	//	if err != nil {
   344  	//		fmt.Printf(" error: %v\n", err)
   345  	//	}
   346  	//}
   347  }
   348  
   349  func (l *StructLogger) GetResult() (json.RawMessage, error) {
   350  	// Tracing aborted
   351  	if l.reason != nil {
   352  		return nil, l.reason
   353  	}
   354  	failed := l.err != nil
   355  	returnData := common.CopyBytes(l.output)
   356  	// Return data when successful and revert reason when reverted, otherwise empty.
   357  	if failed && !errors.Is(l.err, vm.ErrExecutionReverted) {
   358  		returnData = []byte{}
   359  	}
   360  	return json.Marshal(&ExecutionResult{
   361  		Gas:         l.usedGas,
   362  		Failed:      failed,
   363  		ReturnValue: returnData,
   364  		StructLogs:  l.logs,
   365  	})
   366  }
   367  
   368  // Stop terminates execution of the tracer at the first opportune moment.
   369  func (l *StructLogger) Stop(err error) {
   370  	l.reason = err
   371  	l.interrupt.Store(true)
   372  }
   373  
   374  func (l *StructLogger) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
   375  	l.env = env
   376  }
   377  func (l *StructLogger) OnSystemCallStart(env *tracing.VMContext) {
   378  	l.skip = true
   379  }
   380  
   381  func (l *StructLogger) OnSystemCallEnd() {
   382  	l.skip = false
   383  }
   384  
   385  func (l *StructLogger) OnTxEnd(receipt *types.Receipt, err error) {
   386  	if err != nil {
   387  		// Don't override vm error
   388  		if l.err == nil {
   389  			l.err = err
   390  		}
   391  		return
   392  	}
   393  	if receipt != nil {
   394  		l.usedGas = receipt.GasUsed
   395  	}
   396  }
   397  
   398  // Error returns the VM error captured by the trace.
   399  func (l *StructLogger) Error() error { return l.err }
   400  
   401  // Output returns the VM return value captured by the trace.
   402  func (l *StructLogger) Output() []byte { return l.output }
   403  
   404  // WriteTrace writes a formatted trace to the given writer
   405  // @deprecated
   406  func WriteTrace(writer io.Writer, logs []StructLog) {
   407  	for _, log := range logs {
   408  		log.WriteTo(writer)
   409  	}
   410  }
   411  
   412  type mdLogger struct {
   413  	out  io.Writer
   414  	cfg  *Config
   415  	env  *tracing.VMContext
   416  	skip bool
   417  }
   418  
   419  // NewMarkdownLogger creates a logger which outputs information in a format adapted
   420  // for human readability, and is also a valid markdown table
   421  func NewMarkdownLogger(cfg *Config, writer io.Writer) *mdLogger {
   422  	l := &mdLogger{out: writer, cfg: cfg}
   423  	if l.cfg == nil {
   424  		l.cfg = &Config{}
   425  	}
   426  	return l
   427  }
   428  
   429  func (t *mdLogger) Hooks() *tracing.Hooks {
   430  	return &tracing.Hooks{
   431  		OnTxStart:           t.OnTxStart,
   432  		OnSystemCallStartV2: t.OnSystemCallStart,
   433  		OnSystemCallEnd:     t.OnSystemCallEnd,
   434  		OnEnter:             t.OnEnter,
   435  		OnExit:              t.OnExit,
   436  		OnOpcode:            t.OnOpcode,
   437  		OnFault:             t.OnFault,
   438  	}
   439  }
   440  
   441  func (t *mdLogger) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
   442  	t.env = env
   443  }
   444  
   445  func (t *mdLogger) OnSystemCallStart(env *tracing.VMContext) {
   446  	t.skip = true
   447  }
   448  
   449  func (t *mdLogger) OnSystemCallEnd() {
   450  	t.skip = false
   451  }
   452  
   453  func (t *mdLogger) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
   454  	if t.skip {
   455  		return
   456  	}
   457  	if depth != 0 {
   458  		return
   459  	}
   460  	if create := vm.OpCode(typ) == vm.CREATE; !create {
   461  		fmt.Fprintf(t.out, "Pre-execution info:\n"+
   462  			"  - from: `%v`\n"+
   463  			"  - to: `%v`\n"+
   464  			"  - data: `%#x`\n"+
   465  			"  - gas: `%d`\n"+
   466  			"  - value: `%v` wei\n",
   467  			from.String(), to.String(), input, gas, value)
   468  	} else {
   469  		fmt.Fprintf(t.out, "Pre-execution info:\n"+
   470  			"  - from: `%v`\n"+
   471  			"  - create: `%v`\n"+
   472  			"  - data: `%#x`\n"+
   473  			"  - gas: `%d`\n"+
   474  			"  - value: `%v` wei\n",
   475  			from.String(), to.String(), input, gas, value)
   476  	}
   477  	fmt.Fprintf(t.out, `
   478  |  Pc   |      Op     | Cost |   Refund  |   Stack   |
   479  |-------|-------------|------|-----------|-----------|
   480  `)
   481  }
   482  
   483  func (t *mdLogger) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
   484  	if t.skip {
   485  		return
   486  	}
   487  	if depth == 0 {
   488  		fmt.Fprintf(t.out, "\nPost-execution info:\n"+
   489  			"  - output: `%#x`\n"+
   490  			"  - consumed gas: `%d`\n"+
   491  			"  - error: `%v`\n",
   492  			output, gasUsed, err)
   493  	}
   494  }
   495  
   496  // OnOpcode also tracks SLOAD/SSTORE ops to track storage change.
   497  func (t *mdLogger) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
   498  	if t.skip {
   499  		return
   500  	}
   501  	stack := scope.StackData()
   502  	fmt.Fprintf(t.out, "| %4d  | %10v  |  %3d |%10v |", pc, vm.OpCode(op).String(),
   503  		cost, t.env.StateDB.GetRefund())
   504  
   505  	if !t.cfg.DisableStack {
   506  		// format stack
   507  		var a []string
   508  		for _, elem := range stack {
   509  			a = append(a, elem.Hex())
   510  		}
   511  		b := fmt.Sprintf("[%v]", strings.Join(a, ","))
   512  		fmt.Fprintf(t.out, "%10v |", b)
   513  	}
   514  	fmt.Fprintln(t.out, "")
   515  	if err != nil {
   516  		fmt.Fprintf(t.out, "Error: %v\n", err)
   517  	}
   518  }
   519  
   520  func (t *mdLogger) OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error) {
   521  	if t.skip {
   522  		return
   523  	}
   524  	fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err)
   525  }
   526  
   527  // ExecutionResult groups all structured logs emitted by the EVM
   528  // while replaying a transaction in debug mode as well as transaction
   529  // execution status, the amount of gas used and the return value
   530  type ExecutionResult struct {
   531  	Gas         uint64            `json:"gas"`
   532  	Failed      bool              `json:"failed"`
   533  	ReturnValue hexutil.Bytes     `json:"returnValue"`
   534  	StructLogs  []json.RawMessage `json:"structLogs"`
   535  }