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 }