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 }