github.com/klaytn/klaytn@v1.12.1/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/holiman/uint256"
    34  	"github.com/klaytn/klaytn/accounts/abi"
    35  	"github.com/klaytn/klaytn/common"
    36  	"github.com/klaytn/klaytn/common/hexutil"
    37  )
    38  
    39  var (
    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  	gasLimit uint64 // Amount of gas bought for the whole tx
    65  }
    66  
    67  // NewInternalTxTracer returns a new InternalTxTracer.
    68  func NewInternalTxTracer() *InternalTxTracer {
    69  	logger := &InternalTxTracer{
    70  		callStack: []*InternalCall{{}},
    71  		ctx:       map[string]interface{}{},
    72  	}
    73  	return logger
    74  }
    75  
    76  // InternalCall is emitted to the EVM each cycle and
    77  // lists information about the current internal state
    78  // prior to the execution of the statement.
    79  type InternalCall struct {
    80  	Type  string          `json:"type"`
    81  	From  *common.Address `json:"from"`
    82  	To    *common.Address `json:"to"`
    83  	Value string          `json:"value"`
    84  
    85  	Gas     uint64 `json:"gas"`
    86  	GasIn   uint64 `json:"gasIn"`
    87  	GasUsed uint64 `json:"gasUsed"`
    88  	GasCost uint64 `json:"gasCost"`
    89  
    90  	Input  string `json:"input"`  // hex string
    91  	Output string `json:"output"` // hex string
    92  	Error  error  `json:"err"`
    93  
    94  	OutOff *big.Int `json:"outoff"`
    95  	OutLen *big.Int `json:"outlen"`
    96  
    97  	Calls []*InternalCall `json:"calls"`
    98  }
    99  
   100  // OpName formats the operand name in a human-readable format.
   101  func (s *InternalCall) OpName() string {
   102  	return s.Type
   103  }
   104  
   105  // ErrorString formats the tracerLog's error as a string.
   106  func (s *InternalCall) ErrorString() string {
   107  	if s.Error != nil {
   108  		return s.Error.Error()
   109  	}
   110  	return ""
   111  }
   112  
   113  func (s *InternalCall) ToTrace() *InternalTxTrace {
   114  	nestedCalls := []*InternalTxTrace{}
   115  	for _, call := range s.Calls {
   116  		nestedCalls = append(nestedCalls, call.ToTrace())
   117  	}
   118  
   119  	return &InternalTxTrace{
   120  		Type:  s.Type,
   121  		From:  s.From,
   122  		To:    s.To,
   123  		Value: s.Value,
   124  
   125  		Gas:     s.Gas,
   126  		GasUsed: s.GasUsed,
   127  
   128  		Input:  s.Input,
   129  		Output: s.Output,
   130  		Error:  s.Error,
   131  
   132  		Calls: nestedCalls,
   133  	}
   134  }
   135  
   136  // InternalTxTrace is returned data after the end of trace-collecting cycle.
   137  // It implements an object returned by "result" function at call_tracer.js
   138  type InternalTxTrace struct {
   139  	Type  string          `json:"type"`
   140  	From  *common.Address `json:"from,omitempty"`
   141  	To    *common.Address `json:"to,omitempty"`
   142  	Value string          `json:"value,omitempty"`
   143  
   144  	Gas     uint64 `json:"gas,omitempty"`
   145  	GasUsed uint64 `json:"gasUsed,omitempty"`
   146  
   147  	Input  string `json:"input,omitempty"`  // hex string
   148  	Output string `json:"output,omitempty"` // hex string
   149  	Error  error  `json:"error,omitempty"`
   150  
   151  	Time  time.Duration      `json:"time,omitempty"`
   152  	Calls []*InternalTxTrace `json:"calls,omitempty"`
   153  
   154  	Reverted *RevertedInfo `json:"reverted,omitempty"`
   155  }
   156  
   157  type RevertedInfo struct {
   158  	Contract *common.Address `json:"contract,omitempty"`
   159  	Message  string          `json:"message,omitempty"`
   160  }
   161  
   162  // CaptureStart implements the Tracer interface to initialize the tracing operation.
   163  func (t *InternalTxTracer) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
   164  	t.ctx["type"] = CALL.String()
   165  	if create {
   166  		t.ctx["type"] = CREATE.String()
   167  	}
   168  	t.ctx["from"] = from
   169  	t.ctx["to"] = to
   170  	t.ctx["input"] = hexutil.Encode(input)
   171  	t.ctx["gas"] = gas
   172  	t.ctx["gasPrice"] = env.TxContext.GasPrice
   173  	t.ctx["value"] = value
   174  }
   175  
   176  // tracerLog is used to help comparing codes between this and call_tracer.js
   177  // by following the conventions used in call_tracer.js
   178  type tracerLog struct {
   179  	env      *EVM
   180  	pc       uint64
   181  	op       OpCode
   182  	gas      uint64
   183  	cost     uint64
   184  	memory   *Memory
   185  	stack    *Stack
   186  	contract *Contract
   187  	depth    int
   188  	err      error
   189  }
   190  
   191  func wrapError(context string, err error) error {
   192  	return fmt.Errorf("%v    in server-side tracer function '%v'", err.Error(), context)
   193  }
   194  
   195  // Stop terminates execution of the tracer at the first opportune moment.
   196  func (t *InternalTxTracer) Stop(err error) {
   197  	t.reason = err
   198  	atomic.StoreUint32(&t.interrupt, 1)
   199  }
   200  
   201  // CaptureState implements the Tracer interface to trace a single step of VM execution.
   202  func (t *InternalTxTracer) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) {
   203  	if t.err == nil {
   204  		// Initialize the context if it wasn't done yet
   205  		if !t.initialized {
   206  			t.ctx["block"] = env.Context.BlockNumber.Uint64()
   207  			t.initialized = true
   208  		}
   209  		// If tracing was interrupted, set the error and stop
   210  		if atomic.LoadUint32(&t.interrupt) > 0 {
   211  			t.err = t.reason
   212  			return
   213  		}
   214  
   215  		log := &tracerLog{
   216  			env, pc, op, gas, cost,
   217  			scope.Memory, scope.Stack, scope.Contract, depth, err,
   218  		}
   219  		err := t.step(log)
   220  		if err != nil {
   221  			t.err = wrapError("step", err)
   222  		}
   223  	}
   224  }
   225  
   226  func (t *InternalTxTracer) step(log *tracerLog) error {
   227  	// Capture any errors immediately
   228  	if log.err != nil {
   229  		t.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.ToBig(), log.stack.Back(2).ToBig()).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(int64(inOff.Uint64()), inEnd)),
   247  			GasIn:   log.gas,
   248  			GasCost: log.cost,
   249  			Value:   log.stack.peek().Hex(), // '0x' + tracerLog.stack.peek(0).toString(16)
   250  		}
   251  		t.callStack = append(t.callStack, call)
   252  		t.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 := t.callStackLength()
   258  		if t.callStack[left-1] == nil {
   259  			t.callStack[left-1] = &InternalCall{}
   260  		}
   261  		if t.callStack[left-1].Calls == nil {
   262  			t.callStack[left-1].Calls = []*InternalCall{}
   263  		}
   264  		contractAddr := log.contract.Address()
   265  		ret := log.stack.peek()
   266  		toAddr := common.HexToAddress(ret.Hex())
   267  		t.callStack[left-1].Calls = append(
   268  			t.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).Hex())
   285  		if _, ok := PrecompiledContractsByzantium[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.ToBig(), log.stack.Back(3+off).ToBig()).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(int64(inOff.Uint64()), inEnd)),
   304  			GasIn:   log.gas,
   305  			GasCost: log.cost,
   306  			OutOff:  big.NewInt(int64(log.stack.Back(4 + off).Uint64())),
   307  			OutLen:  big.NewInt(int64(log.stack.Back(5 + off).Uint64())),
   308  		}
   309  		if op != DELEGATECALL && op != STATICCALL {
   310  			call.Value = log.stack.Back(2).Hex()
   311  		}
   312  		t.callStack = append(t.callStack, call)
   313  		t.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 t.descended {
   321  		if log.depth >= t.callStackLength() {
   322  			t.callStack[t.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  		t.descended = false
   329  	}
   330  	// If an existing call is returning, pop off the call stack
   331  	if sysCall && op == REVERT && t.callStackLength() > 0 {
   332  		t.callStack[t.callStackLength()-1].Error = errExecutionReverted
   333  		if t.revertedContract == emptyAddr {
   334  			if t.callStack[t.callStackLength()-1].To == nil {
   335  				t.revertedContract = log.contract.Address()
   336  			} else {
   337  				t.revertedContract = *t.callStack[t.callStackLength()-1].To
   338  			}
   339  		}
   340  		return nil
   341  	}
   342  	if log.depth == t.callStackLength()-1 {
   343  		// Pop off the last call and get the execution results
   344  		call := t.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(uint256.NewInt(0)) != 0 {
   353  				toAddr := common.HexToAddress(ret.Hex())
   354  				call.To = &toAddr
   355  				call.Output = hexutil.Encode(log.env.StateDB.GetCode(common.HexToAddress(ret.Hex())))
   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(uint256.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 := t.callStackLength()
   381  		if left == 0 {
   382  			left = 1 // added to avoid index out of range in golang
   383  			t.callStack = []*InternalCall{{}}
   384  		}
   385  		if t.callStack[left-1] == nil {
   386  			t.callStack[left-1] = &InternalCall{}
   387  		}
   388  		if len(t.callStack[left-1].Calls) == 0 {
   389  			t.callStack[left-1].Calls = []*InternalCall{}
   390  		}
   391  		t.callStack[left-1].Calls = append(t.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 (t *InternalTxTracer) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) {
   400  	if t.err == nil {
   401  		// Apart from the error, everything matches the previous invocation
   402  		t.errValue = err.Error()
   403  
   404  		log := &tracerLog{
   405  			env, pc, op, gas, cost,
   406  			scope.Memory, scope.Stack, scope.Contract, depth, err,
   407  		}
   408  		// fault does not return an error
   409  		t.fault(log)
   410  	}
   411  }
   412  
   413  // CaptureEnd is called after the call finishes to finalize the tracing.
   414  func (t *InternalTxTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
   415  	t.ctx["output"] = hexutil.Encode(output)
   416  	t.ctx["gasUsed"] = gasUsed
   417  
   418  	if err != nil {
   419  		t.ctx["error"] = err
   420  	}
   421  }
   422  
   423  func (t *InternalTxTracer) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
   424  }
   425  
   426  func (t *InternalTxTracer) CaptureExit(output []byte, gasUsed uint64, err error) {}
   427  
   428  func (t *InternalTxTracer) CaptureTxStart(gasLimit uint64) {
   429  	t.gasLimit = gasLimit
   430  }
   431  
   432  func (t *InternalTxTracer) CaptureTxEnd(restGas uint64) {
   433  	t.ctx["gasUsed"] = t.gasLimit - restGas
   434  }
   435  
   436  func (t *InternalTxTracer) GetResult() (*InternalTxTrace, error) {
   437  	result := t.result()
   438  	// Clean up the JavaScript environment
   439  	t.reset()
   440  	return result, t.err
   441  }
   442  
   443  // reset clears data collected during the previous tracing.
   444  // It should act like calling jst.vm.DestroyHeap() and jst.vm.Destroy() at tracers.Tracer
   445  func (t *InternalTxTracer) reset() {
   446  	t.callStack = []*InternalCall{}
   447  	t.output = nil
   448  
   449  	t.descended = false
   450  	t.revertedContract = common.Address{}
   451  	t.initialized = false
   452  	t.revertString = ""
   453  }
   454  
   455  // result is invoked when all the opcodes have been iterated over and returns
   456  // the final result of the tracing.
   457  func (t *InternalTxTracer) result() *InternalTxTrace {
   458  	if _, exist := t.ctx["type"]; !exist {
   459  		t.ctx["type"] = ""
   460  	}
   461  	if _, exist := t.ctx["from"]; !exist {
   462  		t.ctx["from"] = nil
   463  	}
   464  	if _, exist := t.ctx["to"]; !exist {
   465  		t.ctx["to"] = nil
   466  	}
   467  	if _, exist := t.ctx["value"]; !exist {
   468  		t.ctx["value"] = big.NewInt(0)
   469  	}
   470  	if _, exist := t.ctx["gas"]; !exist {
   471  		t.ctx["gas"] = uint64(0)
   472  	}
   473  	if _, exist := t.ctx["gasUsed"]; !exist {
   474  		t.ctx["gasUsed"] = uint64(0)
   475  	}
   476  	if _, exist := t.ctx["input"]; !exist {
   477  		t.ctx["input"] = ""
   478  	}
   479  	if _, exist := t.ctx["output"]; !exist {
   480  		t.ctx["output"] = ""
   481  	}
   482  	if _, exist := t.ctx["time"]; !exist {
   483  		t.ctx["time"] = time.Duration(0)
   484  	}
   485  	if t.callStackLength() == 0 {
   486  		t.callStack = []*InternalCall{{}}
   487  	}
   488  	var from, to *common.Address
   489  	if addr, ok := t.ctx["from"].(common.Address); ok {
   490  		from = &addr
   491  	}
   492  	if addr, ok := t.ctx["to"].(common.Address); ok {
   493  		to = &addr
   494  	}
   495  
   496  	result := &InternalTxTrace{
   497  		Type:    t.ctx["type"].(string),
   498  		From:    from,
   499  		To:      to,
   500  		Value:   "0x" + t.ctx["value"].(*big.Int).Text(16),
   501  		Gas:     t.ctx["gas"].(uint64),
   502  		GasUsed: t.ctx["gasUsed"].(uint64),
   503  		Input:   t.ctx["input"].(string),
   504  		Output:  t.ctx["output"].(string),
   505  		Time:    t.ctx["time"].(time.Duration),
   506  	}
   507  
   508  	nestedCalls := []*InternalTxTrace{}
   509  	for _, call := range t.callStack[0].Calls {
   510  		nestedCalls = append(nestedCalls, call.ToTrace())
   511  	}
   512  	result.Calls = nestedCalls
   513  
   514  	if t.callStack[0].Error != nil {
   515  		result.Error = t.callStack[0].Error
   516  	} else if ctxErr := t.ctx["error"]; ctxErr != nil {
   517  		result.Error = ctxErr.(error)
   518  	}
   519  	if result.Error != nil && (result.Error.Error() != errExecutionReverted.Error() || result.Output == "0x") {
   520  		result.Output = "" // delete result.output;
   521  	}
   522  	if err := t.ctx["error"]; err != nil && err.(error).Error() == ErrExecutionReverted.Error() {
   523  		outputHex := t.ctx["output"].(string) // it is already a hex string
   524  
   525  		if s, err := abi.UnpackRevert(common.FromHex(outputHex)); err == nil {
   526  			t.revertString = s
   527  		} else {
   528  			t.revertString = ""
   529  		}
   530  
   531  		contract := t.revertedContract
   532  		message := t.revertString
   533  		result.Reverted = &RevertedInfo{Contract: &contract, Message: message}
   534  	}
   535  	return result
   536  }
   537  
   538  // InternalTxLogs returns the captured tracerLog entries.
   539  func (t *InternalTxTracer) InternalTxLogs() []*InternalCall { return t.callStack }
   540  
   541  // fault is invoked when the actual execution of an opcode fails.
   542  func (t *InternalTxTracer) fault(log *tracerLog) {
   543  	if t.callStackLength() == 0 {
   544  		return
   545  	}
   546  	// If the topmost call already reverted, don't handle the additional fault again
   547  	if t.callStack[t.callStackLength()-1].Error != nil {
   548  		return
   549  	}
   550  	// Pop off the just failed call
   551  	call := t.callStackPop()
   552  	call.Error = log.err
   553  
   554  	// Consume all available gas and clean any leftovers
   555  	if call.Gas != uint64(0) {
   556  		call.GasUsed = call.Gas
   557  	}
   558  	call.GasIn, call.GasCost = uint64(0), uint64(0)
   559  	call.OutOff, call.OutLen = nil, nil
   560  
   561  	// Flatten the failed call into its parent
   562  	left := t.callStackLength()
   563  	if left > 0 {
   564  		if t.callStack[left-1] == nil {
   565  			t.callStack[left-1] = &InternalCall{}
   566  		}
   567  		if len(t.callStack[left-1].Calls) == 0 {
   568  			t.callStack[left-1].Calls = []*InternalCall{}
   569  		}
   570  		t.callStack[left-1].Calls = append(t.callStack[left-1].Calls, call)
   571  		return
   572  	}
   573  	// Last call failed too, leave it in the stack
   574  	t.callStack = append(t.callStack, call)
   575  }
   576  
   577  func (t *InternalTxTracer) callStackLength() int {
   578  	return len(t.callStack)
   579  }
   580  
   581  func (t *InternalTxTracer) callStackPop() *InternalCall {
   582  	if t.callStackLength() == 0 {
   583  		return &InternalCall{}
   584  	}
   585  
   586  	topItem := t.callStack[t.callStackLength()-1]
   587  	t.callStack = t.callStack[:t.callStackLength()-1]
   588  	return topItem
   589  }