github.com/klaytn/klaytn@v1.10.2/blockchain/vm/internaltx_tracer.go (about)

     1  // Modifications Copyright 2020 The klaytn Authors
     2  // Copyright 2017 The go-ethereum Authors
     3  // This file is part of the go-ethereum library.
     4  //
     5  // The go-ethereum library is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Lesser General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // The go-ethereum library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  // GNU Lesser General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public License
    16  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    17  //
    18  // InternalTxTracer is a full blown transaction tracer that extracts and reports all
    19  // the internal calls made by a transaction, along with any useful information.
    20  //
    21  // This file is derived from eth/tracers/internal/tracers/call_tracer.js (2018/06/04).
    22  // Modified and improved for the klaytn development.
    23  
    24  package vm
    25  
    26  import (
    27  	"errors"
    28  	"fmt"
    29  	"math/big"
    30  	"sync/atomic"
    31  	"time"
    32  
    33  	"github.com/klaytn/klaytn/accounts/abi"
    34  	"github.com/klaytn/klaytn/common"
    35  	"github.com/klaytn/klaytn/common/hexutil"
    36  )
    37  
    38  var (
    39  	errEvmExecutionReverted = errors.New("evm: execution reverted")
    40  	errExecutionReverted    = errors.New("execution reverted")
    41  	errInternalFailure      = errors.New("internal failure")
    42  	emptyAddr               = common.Address{}
    43  )
    44  
    45  // InternalTxTracer is a full blown transaction tracer that extracts and reports all
    46  // the internal calls made by a transaction, along with any useful information.
    47  // It is ported to golang from JS, specifically call_tracer.js
    48  type InternalTxTracer struct {
    49  	callStack []*InternalCall
    50  	output    []byte
    51  	err       error
    52  	errValue  string
    53  
    54  	// Below are newly added fields to support call_tracer.js
    55  	descended        bool
    56  	revertedContract common.Address
    57  	ctx              map[string]interface{} // Transaction context gathered throughout execution
    58  	initialized      bool
    59  	revertString     string
    60  
    61  	interrupt uint32 // Atomic flag to signal execution interruption
    62  	reason    error  // Textual reason for the interruption
    63  }
    64  
    65  // NewInternalTxTracer returns a new InternalTxTracer.
    66  func NewInternalTxTracer() *InternalTxTracer {
    67  	logger := &InternalTxTracer{
    68  		callStack: []*InternalCall{{}},
    69  		ctx:       map[string]interface{}{},
    70  	}
    71  	return logger
    72  }
    73  
    74  // InternalCall is emitted to the EVM each cycle and
    75  // lists information about the current internal state
    76  // prior to the execution of the statement.
    77  type InternalCall struct {
    78  	Type  string          `json:"type"`
    79  	From  *common.Address `json:"from"`
    80  	To    *common.Address `json:"to"`
    81  	Value string          `json:"value"`
    82  
    83  	Gas     uint64 `json:"gas"`
    84  	GasIn   uint64 `json:"gasIn"`
    85  	GasUsed uint64 `json:"gasUsed"`
    86  	GasCost uint64 `json:"gasCost"`
    87  
    88  	Input  string `json:"input"`  // hex string
    89  	Output string `json:"output"` // hex string
    90  	Error  error  `json:"err"`
    91  
    92  	OutOff *big.Int `json:"outoff"`
    93  	OutLen *big.Int `json:"outlen"`
    94  
    95  	Calls []*InternalCall `json:"calls"`
    96  }
    97  
    98  // OpName formats the operand name in a human-readable format.
    99  func (s *InternalCall) OpName() string {
   100  	return s.Type
   101  }
   102  
   103  // ErrorString formats the tracerLog's error as a string.
   104  func (s *InternalCall) ErrorString() string {
   105  	if s.Error != nil {
   106  		return s.Error.Error()
   107  	}
   108  	return ""
   109  }
   110  
   111  func (s *InternalCall) ToTrace() *InternalTxTrace {
   112  	nestedCalls := []*InternalTxTrace{}
   113  	for _, call := range s.Calls {
   114  		nestedCalls = append(nestedCalls, call.ToTrace())
   115  	}
   116  
   117  	return &InternalTxTrace{
   118  		Type:  s.Type,
   119  		From:  s.From,
   120  		To:    s.To,
   121  		Value: s.Value,
   122  
   123  		Gas:     s.Gas,
   124  		GasUsed: s.GasUsed,
   125  
   126  		Input:  s.Input,
   127  		Output: s.Output,
   128  		Error:  s.Error,
   129  
   130  		Calls: nestedCalls,
   131  	}
   132  }
   133  
   134  // InternalTxTrace is returned data after the end of trace-collecting cycle.
   135  // It implements an object returned by "result" function at call_tracer.js
   136  type InternalTxTrace struct {
   137  	Type  string          `json:"type"`
   138  	From  *common.Address `json:"from,omitempty"`
   139  	To    *common.Address `json:"to,omitempty"`
   140  	Value string          `json:"value,omitempty"`
   141  
   142  	Gas     uint64 `json:"gas,omitempty"`
   143  	GasUsed uint64 `json:"gasUsed,omitempty"`
   144  
   145  	Input  string `json:"input,omitempty"`  // hex string
   146  	Output string `json:"output,omitempty"` // hex string
   147  	Error  error  `json:"error,omitempty"`
   148  
   149  	Time  time.Duration      `json:"time,omitempty"`
   150  	Calls []*InternalTxTrace `json:"calls,omitempty"`
   151  
   152  	Reverted *RevertedInfo `json:"reverted,omitempty"`
   153  }
   154  
   155  type RevertedInfo struct {
   156  	Contract *common.Address `json:"contract,omitempty"`
   157  	Message  string          `json:"message,omitempty"`
   158  }
   159  
   160  // CaptureStart implements the Tracer interface to initialize the tracing operation.
   161  func (this *InternalTxTracer) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error {
   162  	this.ctx["type"] = CALL.String()
   163  	if create {
   164  		this.ctx["type"] = CREATE.String()
   165  	}
   166  	this.ctx["from"] = from
   167  	this.ctx["to"] = to
   168  	this.ctx["input"] = hexutil.Encode(input)
   169  	this.ctx["gas"] = gas
   170  	this.ctx["value"] = value
   171  
   172  	return nil
   173  }
   174  
   175  // tracerLog is used to help comparing codes between this and call_tracer.js
   176  // by following the conventions used in call_tracer.js
   177  type tracerLog struct {
   178  	env      *EVM
   179  	pc       uint64
   180  	op       OpCode
   181  	gas      uint64
   182  	cost     uint64
   183  	memory   *Memory
   184  	stack    *Stack
   185  	contract *Contract
   186  	depth    int
   187  	err      error
   188  }
   189  
   190  func wrapError(context string, err error) error {
   191  	return fmt.Errorf("%v    in server-side tracer function '%v'", err.Error(), context)
   192  }
   193  
   194  // Stop terminates execution of the tracer at the first opportune moment.
   195  func (this *InternalTxTracer) Stop(err error) {
   196  	this.reason = err
   197  	atomic.StoreUint32(&this.interrupt, 1)
   198  }
   199  
   200  // CaptureState implements the Tracer interface to trace a single step of VM execution.
   201  func (this *InternalTxTracer) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, logStack *Stack, contract *Contract, depth int, err error) error {
   202  	if this.err == nil {
   203  		// Initialize the context if it wasn't done yet
   204  		if !this.initialized {
   205  			this.ctx["block"] = env.BlockNumber.Uint64()
   206  			this.initialized = true
   207  		}
   208  		// If tracing was interrupted, set the error and stop
   209  		if atomic.LoadUint32(&this.interrupt) > 0 {
   210  			this.err = this.reason
   211  			return nil
   212  		}
   213  
   214  		log := &tracerLog{
   215  			env, pc, op, gas, cost,
   216  			memory, logStack, contract, depth, err,
   217  		}
   218  		err := this.step(log)
   219  		if err != nil {
   220  			this.err = wrapError("step", err)
   221  		}
   222  	}
   223  	return nil
   224  }
   225  
   226  func (this *InternalTxTracer) step(log *tracerLog) error {
   227  	// Capture any errors immediately
   228  	if log.err != nil {
   229  		this.fault(log)
   230  		return nil
   231  	}
   232  
   233  	// We only care about system opcodes, faster if we pre-check once
   234  	sysCall := (log.op & 0xf0) == 0xf0
   235  	op := log.op
   236  	// If a new contract is being created, add to the call stack
   237  	if sysCall && (op == CREATE || op == CREATE2) {
   238  		inOff := log.stack.Back(1)
   239  		inEnd := big.NewInt(0).Add(inOff, log.stack.Back(2)).Int64()
   240  
   241  		// Assemble the internal call report and store for completion
   242  		fromAddr := log.contract.Address()
   243  		call := &InternalCall{
   244  			Type:    op.String(),
   245  			From:    &fromAddr,
   246  			Input:   hexutil.Encode(log.memory.Slice(inOff.Int64(), inEnd)),
   247  			GasIn:   log.gas,
   248  			GasCost: log.cost,
   249  			Value:   "0x" + log.stack.Peek().Text(16), // '0x' + tracerLog.stack.peek(0).toString(16)
   250  		}
   251  		this.callStack = append(this.callStack, call)
   252  		this.descended = true
   253  		return nil
   254  	}
   255  	// If a contract is being self destructed, gather that as a subcall too
   256  	if sysCall && op == SELFDESTRUCT {
   257  		left := this.callStackLength()
   258  		if this.callStack[left-1] == nil {
   259  			this.callStack[left-1] = &InternalCall{}
   260  		}
   261  		if this.callStack[left-1].Calls == nil {
   262  			this.callStack[left-1].Calls = []*InternalCall{}
   263  		}
   264  		contractAddr := log.contract.Address()
   265  		ret := log.stack.Peek()
   266  		toAddr := common.HexToAddress(ret.Text(16))
   267  		this.callStack[left-1].Calls = append(
   268  			this.callStack[left-1].Calls,
   269  			&InternalCall{
   270  				Type:    op.String(),
   271  				From:    &contractAddr,
   272  				To:      &toAddr,
   273  				Value:   "0x" + log.env.StateDB.GetBalance(contractAddr).Text(16),
   274  				GasIn:   log.gas,
   275  				GasCost: log.cost,
   276  			},
   277  		)
   278  		return nil
   279  	}
   280  	// If a new method invocation is being done, add to the call stack
   281  	if sysCall && (op == CALL || op == CALLCODE || op == DELEGATECALL || op == STATICCALL) {
   282  
   283  		// Skip any pre-compile invocations, those are just fancy opcodes
   284  		toAddr := common.HexToAddress(log.stack.Back(1).Text(16))
   285  		if _, ok := PrecompiledContractsByzantiumCompatible[toAddr]; ok {
   286  			return nil
   287  		}
   288  
   289  		off := 1
   290  		if op == DELEGATECALL || op == STATICCALL {
   291  			off = 0
   292  		}
   293  
   294  		inOff := log.stack.Back(2 + off)
   295  		inEnd := big.NewInt(0).Add(inOff, log.stack.Back(3+off)).Int64()
   296  
   297  		// Assemble the internal call report and store for completion
   298  		fromAddr := log.contract.Address()
   299  		call := &InternalCall{
   300  			Type:    op.String(),
   301  			From:    &fromAddr,
   302  			To:      &toAddr,
   303  			Input:   hexutil.Encode(log.memory.Slice(inOff.Int64(), inEnd)),
   304  			GasIn:   log.gas,
   305  			GasCost: log.cost,
   306  			OutOff:  big.NewInt(log.stack.Back(4 + off).Int64()),
   307  			OutLen:  big.NewInt(log.stack.Back(5 + off).Int64()),
   308  		}
   309  		if op != DELEGATECALL && op != STATICCALL {
   310  			call.Value = "0x" + log.stack.Back(2).Text(16)
   311  		}
   312  		this.callStack = append(this.callStack, call)
   313  		this.descended = true
   314  
   315  		return nil
   316  	}
   317  	// If we've just descended into an inner call, retrieve it's true allowance. We
   318  	// need to extract if from within the call as there may be funky gas dynamics
   319  	// with regard to requested and actually given gas (2300 stipend, 63/64 rule).
   320  	if this.descended {
   321  		if log.depth >= this.callStackLength() {
   322  			this.callStack[this.callStackLength()-1].Gas = log.gas
   323  		} else {
   324  			// TODO(karalabe): The call was made to a plain account. We currently don't
   325  			// have access to the true gas amount inside the call and so any amount will
   326  			// mostly be wrong since it depends on a lot of input args. Skip gas for now.
   327  		}
   328  		this.descended = false
   329  	}
   330  	// If an existing call is returning, pop off the call stack
   331  	if sysCall && op == REVERT && this.callStackLength() > 0 {
   332  		this.callStack[this.callStackLength()-1].Error = errExecutionReverted
   333  		if this.revertedContract == emptyAddr {
   334  			if this.callStack[this.callStackLength()-1].To == nil {
   335  				this.revertedContract = log.contract.Address()
   336  			} else {
   337  				this.revertedContract = *this.callStack[this.callStackLength()-1].To
   338  			}
   339  		}
   340  		return nil
   341  	}
   342  	if log.depth == this.callStackLength()-1 {
   343  		// Pop off the last call and get the execution results
   344  		call := this.callStackPop()
   345  
   346  		if call.Type == CREATE.String() || call.Type == CREATE2.String() {
   347  			// If the call was a CREATE, retrieve the contract address and output code
   348  			call.GasUsed = call.GasIn - call.GasCost - log.gas
   349  			call.GasIn, call.GasCost = uint64(0), uint64(0)
   350  
   351  			ret := log.stack.Peek()
   352  			if ret.Cmp(big.NewInt(0)) != 0 {
   353  				toAddr := common.HexToAddress(ret.Text(16))
   354  				call.To = &toAddr
   355  				call.Output = hexutil.Encode(log.env.StateDB.GetCode(common.HexToAddress(ret.Text(16))))
   356  			} else if call.Error == nil {
   357  				call.Error = errInternalFailure // TODO(karalabe): surface these faults somehow
   358  			}
   359  		} else {
   360  			// If the call was a contract call, retrieve the gas usage and output
   361  			if call.Gas != uint64(0) {
   362  				call.GasUsed = call.GasIn - call.GasCost + call.Gas - log.gas
   363  			}
   364  			ret := log.stack.Peek()
   365  			if ret == nil || ret.Cmp(big.NewInt(0)) != 0 {
   366  				callOutOff, callOutLen := call.OutOff.Int64(), call.OutLen.Int64()
   367  				call.Output = hexutil.Encode(log.memory.Slice(callOutOff, callOutOff+callOutLen))
   368  			} else if call.Error == nil {
   369  				call.Error = errInternalFailure // TODO(karalabe): surface these faults somehow
   370  			}
   371  			call.GasIn, call.GasCost = uint64(0), uint64(0)
   372  			call.OutOff, call.OutLen = nil, nil
   373  		}
   374  		if call.Gas != uint64(0) {
   375  			// TODO-ChainDataFetcher
   376  			// Below is the original code, but it is just to convert the value into the hex string, nothing to do
   377  			// call.gas = '0x' + bigInt(call.gas).toString(16);
   378  		}
   379  		// Inject the call into the previous one
   380  		left := this.callStackLength()
   381  		if left == 0 {
   382  			left = 1 // added to avoid index out of range in golang
   383  			this.callStack = []*InternalCall{{}}
   384  		}
   385  		if this.callStack[left-1] == nil {
   386  			this.callStack[left-1] = &InternalCall{}
   387  		}
   388  		if len(this.callStack[left-1].Calls) == 0 {
   389  			this.callStack[left-1].Calls = []*InternalCall{}
   390  		}
   391  		this.callStack[left-1].Calls = append(this.callStack[left-1].Calls, call)
   392  	}
   393  
   394  	return nil
   395  }
   396  
   397  // CaptureFault implements the Tracer interface to trace an execution fault
   398  // while running an opcode.
   399  func (this *InternalTxTracer) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, s *Stack, contract *Contract, depth int, err error) error {
   400  	if this.err == nil {
   401  		// Apart from the error, everything matches the previous invocation
   402  		this.errValue = err.Error()
   403  
   404  		log := &tracerLog{
   405  			env, pc, op, gas, cost,
   406  			memory, s, contract, depth, err,
   407  		}
   408  		// fault does not return an error
   409  		this.fault(log)
   410  	}
   411  	return nil
   412  }
   413  
   414  // CaptureEnd is called after the call finishes to finalize the tracing.
   415  func (this *InternalTxTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error {
   416  	this.ctx["output"] = hexutil.Encode(output)
   417  	this.ctx["gasUsed"] = gasUsed
   418  	this.ctx["time"] = t
   419  
   420  	if err != nil {
   421  		this.ctx["error"] = err
   422  	}
   423  	return nil
   424  }
   425  
   426  func (this *InternalTxTracer) GetResult() (*InternalTxTrace, error) {
   427  	result := this.result()
   428  	// Clean up the JavaScript environment
   429  	this.reset()
   430  	return result, this.err
   431  }
   432  
   433  // reset clears data collected during the previous tracing.
   434  // It should act like calling jst.vm.DestroyHeap() and jst.vm.Destroy() at tracers.Tracer
   435  func (this *InternalTxTracer) reset() {
   436  	this.callStack = []*InternalCall{}
   437  	this.output = nil
   438  
   439  	this.descended = false
   440  	this.revertedContract = common.Address{}
   441  	this.initialized = false
   442  	this.revertString = ""
   443  }
   444  
   445  // result is invoked when all the opcodes have been iterated over and returns
   446  // the final result of the tracing.
   447  func (this *InternalTxTracer) result() *InternalTxTrace {
   448  	if _, exist := this.ctx["type"]; !exist {
   449  		this.ctx["type"] = ""
   450  	}
   451  	if _, exist := this.ctx["from"]; !exist {
   452  		this.ctx["from"] = nil
   453  	}
   454  	if _, exist := this.ctx["to"]; !exist {
   455  		this.ctx["to"] = nil
   456  	}
   457  	if _, exist := this.ctx["value"]; !exist {
   458  		this.ctx["value"] = big.NewInt(0)
   459  	}
   460  	if _, exist := this.ctx["gas"]; !exist {
   461  		this.ctx["gas"] = uint64(0)
   462  	}
   463  	if _, exist := this.ctx["gasUsed"]; !exist {
   464  		this.ctx["gasUsed"] = uint64(0)
   465  	}
   466  	if _, exist := this.ctx["input"]; !exist {
   467  		this.ctx["input"] = ""
   468  	}
   469  	if _, exist := this.ctx["output"]; !exist {
   470  		this.ctx["output"] = ""
   471  	}
   472  	if _, exist := this.ctx["time"]; !exist {
   473  		this.ctx["time"] = time.Duration(0)
   474  	}
   475  	if this.callStackLength() == 0 {
   476  		this.callStack = []*InternalCall{{}}
   477  	}
   478  	var from, to *common.Address
   479  	if addr, ok := this.ctx["from"].(common.Address); ok {
   480  		from = &addr
   481  	}
   482  	if addr, ok := this.ctx["to"].(common.Address); ok {
   483  		to = &addr
   484  	}
   485  
   486  	result := &InternalTxTrace{
   487  		Type:    this.ctx["type"].(string),
   488  		From:    from,
   489  		To:      to,
   490  		Value:   "0x" + this.ctx["value"].(*big.Int).Text(16),
   491  		Gas:     this.ctx["gas"].(uint64),
   492  		GasUsed: this.ctx["gasUsed"].(uint64),
   493  		Input:   this.ctx["input"].(string),
   494  		Output:  this.ctx["output"].(string),
   495  		Time:    this.ctx["time"].(time.Duration),
   496  	}
   497  
   498  	nestedCalls := []*InternalTxTrace{}
   499  	for _, call := range this.callStack[0].Calls {
   500  		nestedCalls = append(nestedCalls, call.ToTrace())
   501  	}
   502  	result.Calls = nestedCalls
   503  
   504  	if this.callStack[0].Error != nil {
   505  		result.Error = this.callStack[0].Error
   506  	} else if ctxErr, _ := this.ctx["error"]; ctxErr != nil {
   507  		result.Error = ctxErr.(error)
   508  	}
   509  	if result.Error != nil && (result.Error.Error() != errExecutionReverted.Error() || result.Output == "0x") {
   510  		result.Output = "" // delete result.output;
   511  	}
   512  	if err := this.ctx["error"]; err != nil && err.(error).Error() == errEvmExecutionReverted.Error() {
   513  		outputHex := this.ctx["output"].(string) // it is already a hex string
   514  
   515  		if s, err := abi.UnpackRevert(common.FromHex(outputHex)); err == nil {
   516  			this.revertString = s
   517  		} else {
   518  			this.revertString = ""
   519  		}
   520  
   521  		contract := this.revertedContract
   522  		message := this.revertString
   523  		result.Reverted = &RevertedInfo{Contract: &contract, Message: message}
   524  	}
   525  	return result
   526  }
   527  
   528  // InternalTxLogs returns the captured tracerLog entries.
   529  func (this *InternalTxTracer) InternalTxLogs() []*InternalCall { return this.callStack }
   530  
   531  // fault is invoked when the actual execution of an opcode fails.
   532  func (this *InternalTxTracer) fault(log *tracerLog) {
   533  	if this.callStackLength() == 0 {
   534  		return
   535  	}
   536  	// If the topmost call already reverted, don't handle the additional fault again
   537  	if this.callStack[this.callStackLength()-1].Error != nil {
   538  		return
   539  	}
   540  	// Pop off the just failed call
   541  	call := this.callStackPop()
   542  	call.Error = log.err
   543  
   544  	// Consume all available gas and clean any leftovers
   545  	if call.Gas != uint64(0) {
   546  		call.GasUsed = call.Gas
   547  	}
   548  	call.GasIn, call.GasCost = uint64(0), uint64(0)
   549  	call.OutOff, call.OutLen = nil, nil
   550  
   551  	// Flatten the failed call into its parent
   552  	left := this.callStackLength()
   553  	if left > 0 {
   554  		if this.callStack[left-1] == nil {
   555  			this.callStack[left-1] = &InternalCall{}
   556  		}
   557  		if len(this.callStack[left-1].Calls) == 0 {
   558  			this.callStack[left-1].Calls = []*InternalCall{}
   559  		}
   560  		this.callStack[left-1].Calls = append(this.callStack[left-1].Calls, call)
   561  		return
   562  	}
   563  	// Last call failed too, leave it in the stack
   564  	this.callStack = append(this.callStack, call)
   565  }
   566  
   567  func (this *InternalTxTracer) callStackLength() int {
   568  	return len(this.callStack)
   569  }
   570  
   571  func (this *InternalTxTracer) callStackPop() *InternalCall {
   572  	if this.callStackLength() == 0 {
   573  		return &InternalCall{}
   574  	}
   575  
   576  	topItem := this.callStack[this.callStackLength()-1]
   577  	this.callStack = this.callStack[:this.callStackLength()-1]
   578  	return topItem
   579  }