github.com/codysnider/go-ethereum@v1.10.18-0.20220420071915-14f4ae99222a/eth/tracers/js/tracer.go (about) 1 // Copyright 2017 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 js is a collection of tracers written in javascript. 18 package js 19 20 import ( 21 "encoding/json" 22 "errors" 23 "fmt" 24 "math/big" 25 "strings" 26 "sync/atomic" 27 "time" 28 "unicode" 29 "unsafe" 30 31 "github.com/ethereum/go-ethereum/common" 32 "github.com/ethereum/go-ethereum/common/hexutil" 33 "github.com/ethereum/go-ethereum/core/vm" 34 "github.com/ethereum/go-ethereum/crypto" 35 tracers2 "github.com/ethereum/go-ethereum/eth/tracers" 36 "github.com/ethereum/go-ethereum/eth/tracers/js/internal/tracers" 37 "github.com/ethereum/go-ethereum/log" 38 "gopkg.in/olebedev/go-duktape.v3" 39 ) 40 41 // camel converts a snake cased input string into a camel cased output. 42 func camel(str string) string { 43 pieces := strings.Split(str, "_") 44 for i := 1; i < len(pieces); i++ { 45 pieces[i] = string(unicode.ToUpper(rune(pieces[i][0]))) + pieces[i][1:] 46 } 47 return strings.Join(pieces, "") 48 } 49 50 var assetTracers = make(map[string]string) 51 52 // init retrieves the JavaScript transaction tracers included in go-ethereum. 53 func init() { 54 for _, file := range tracers.AssetNames() { 55 name := camel(strings.TrimSuffix(file, ".js")) 56 assetTracers[name] = string(tracers.MustAsset(file)) 57 } 58 tracers2.RegisterLookup(true, newJsTracer) 59 } 60 61 // makeSlice convert an unsafe memory pointer with the given type into a Go byte 62 // slice. 63 // 64 // Note, the returned slice uses the same memory area as the input arguments. 65 // If those are duktape stack items, popping them off **will** make the slice 66 // contents change. 67 func makeSlice(ptr unsafe.Pointer, size uint) []byte { 68 var sl = struct { 69 addr uintptr 70 len int 71 cap int 72 }{uintptr(ptr), int(size), int(size)} 73 74 return *(*[]byte)(unsafe.Pointer(&sl)) 75 } 76 77 // popSlice pops a buffer off the JavaScript stack and returns it as a slice. 78 func popSlice(ctx *duktape.Context) []byte { 79 blob := common.CopyBytes(makeSlice(ctx.GetBuffer(-1))) 80 ctx.Pop() 81 return blob 82 } 83 84 // pushBigInt create a JavaScript BigInteger in the VM. 85 func pushBigInt(n *big.Int, ctx *duktape.Context) { 86 ctx.GetGlobalString("bigInt") 87 ctx.PushString(n.String()) 88 ctx.Call(1) 89 } 90 91 // opWrapper provides a JavaScript wrapper around OpCode. 92 type opWrapper struct { 93 op vm.OpCode 94 } 95 96 // pushObject assembles a JSVM object wrapping a swappable opcode and pushes it 97 // onto the VM stack. 98 func (ow *opWrapper) pushObject(vm *duktape.Context) { 99 obj := vm.PushObject() 100 101 vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushInt(int(ow.op)); return 1 }) 102 vm.PutPropString(obj, "toNumber") 103 104 vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushString(ow.op.String()); return 1 }) 105 vm.PutPropString(obj, "toString") 106 107 vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushBoolean(ow.op.IsPush()); return 1 }) 108 vm.PutPropString(obj, "isPush") 109 } 110 111 // memoryWrapper provides a JavaScript wrapper around vm.Memory. 112 type memoryWrapper struct { 113 memory *vm.Memory 114 } 115 116 // slice returns the requested range of memory as a byte slice. 117 func (mw *memoryWrapper) slice(begin, end int64) []byte { 118 if end == begin { 119 return []byte{} 120 } 121 if end < begin || begin < 0 { 122 // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go 123 // runtime goes belly up https://github.com/golang/go/issues/15639. 124 log.Warn("Tracer accessed out of bound memory", "offset", begin, "end", end) 125 return nil 126 } 127 if mw.memory.Len() < int(end) { 128 // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go 129 // runtime goes belly up https://github.com/golang/go/issues/15639. 130 log.Warn("Tracer accessed out of bound memory", "available", mw.memory.Len(), "offset", begin, "size", end-begin) 131 return nil 132 } 133 return mw.memory.GetCopy(begin, end-begin) 134 } 135 136 // getUint returns the 32 bytes at the specified address interpreted as a uint. 137 func (mw *memoryWrapper) getUint(addr int64) *big.Int { 138 if mw.memory.Len() < int(addr)+32 || addr < 0 { 139 // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go 140 // runtime goes belly up https://github.com/golang/go/issues/15639. 141 log.Warn("Tracer accessed out of bound memory", "available", mw.memory.Len(), "offset", addr, "size", 32) 142 return new(big.Int) 143 } 144 return new(big.Int).SetBytes(mw.memory.GetPtr(addr, 32)) 145 } 146 147 // pushObject assembles a JSVM object wrapping a swappable memory and pushes it 148 // onto the VM stack. 149 func (mw *memoryWrapper) pushObject(vm *duktape.Context) { 150 obj := vm.PushObject() 151 152 // Generate the `slice` method which takes two ints and returns a buffer 153 vm.PushGoFunction(func(ctx *duktape.Context) int { 154 blob := mw.slice(int64(ctx.GetInt(-2)), int64(ctx.GetInt(-1))) 155 ctx.Pop2() 156 157 ptr := ctx.PushFixedBuffer(len(blob)) 158 copy(makeSlice(ptr, uint(len(blob))), blob) 159 return 1 160 }) 161 vm.PutPropString(obj, "slice") 162 163 // Generate the `getUint` method which takes an int and returns a bigint 164 vm.PushGoFunction(func(ctx *duktape.Context) int { 165 offset := int64(ctx.GetInt(-1)) 166 ctx.Pop() 167 168 pushBigInt(mw.getUint(offset), ctx) 169 return 1 170 }) 171 vm.PutPropString(obj, "getUint") 172 } 173 174 // stackWrapper provides a JavaScript wrapper around vm.Stack. 175 type stackWrapper struct { 176 stack *vm.Stack 177 } 178 179 // peek returns the nth-from-the-top element of the stack. 180 func (sw *stackWrapper) peek(idx int) *big.Int { 181 if len(sw.stack.Data()) <= idx || idx < 0 { 182 // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go 183 // runtime goes belly up https://github.com/golang/go/issues/15639. 184 log.Warn("Tracer accessed out of bound stack", "size", len(sw.stack.Data()), "index", idx) 185 return new(big.Int) 186 } 187 return sw.stack.Back(idx).ToBig() 188 } 189 190 // pushObject assembles a JSVM object wrapping a swappable stack and pushes it 191 // onto the VM stack. 192 func (sw *stackWrapper) pushObject(vm *duktape.Context) { 193 obj := vm.PushObject() 194 195 vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushInt(len(sw.stack.Data())); return 1 }) 196 vm.PutPropString(obj, "length") 197 198 // Generate the `peek` method which takes an int and returns a bigint 199 vm.PushGoFunction(func(ctx *duktape.Context) int { 200 offset := ctx.GetInt(-1) 201 ctx.Pop() 202 203 pushBigInt(sw.peek(offset), ctx) 204 return 1 205 }) 206 vm.PutPropString(obj, "peek") 207 } 208 209 // dbWrapper provides a JavaScript wrapper around vm.Database. 210 type dbWrapper struct { 211 db vm.StateDB 212 } 213 214 // pushObject assembles a JSVM object wrapping a swappable database and pushes it 215 // onto the VM stack. 216 func (dw *dbWrapper) pushObject(vm *duktape.Context) { 217 obj := vm.PushObject() 218 219 // Push the wrapper for statedb.GetBalance 220 vm.PushGoFunction(func(ctx *duktape.Context) int { 221 pushBigInt(dw.db.GetBalance(common.BytesToAddress(popSlice(ctx))), ctx) 222 return 1 223 }) 224 vm.PutPropString(obj, "getBalance") 225 226 // Push the wrapper for statedb.GetNonce 227 vm.PushGoFunction(func(ctx *duktape.Context) int { 228 ctx.PushInt(int(dw.db.GetNonce(common.BytesToAddress(popSlice(ctx))))) 229 return 1 230 }) 231 vm.PutPropString(obj, "getNonce") 232 233 // Push the wrapper for statedb.GetCode 234 vm.PushGoFunction(func(ctx *duktape.Context) int { 235 code := dw.db.GetCode(common.BytesToAddress(popSlice(ctx))) 236 237 ptr := ctx.PushFixedBuffer(len(code)) 238 copy(makeSlice(ptr, uint(len(code))), code) 239 return 1 240 }) 241 vm.PutPropString(obj, "getCode") 242 243 // Push the wrapper for statedb.GetState 244 vm.PushGoFunction(func(ctx *duktape.Context) int { 245 hash := popSlice(ctx) 246 addr := popSlice(ctx) 247 248 state := dw.db.GetState(common.BytesToAddress(addr), common.BytesToHash(hash)) 249 250 ptr := ctx.PushFixedBuffer(len(state)) 251 copy(makeSlice(ptr, uint(len(state))), state[:]) 252 return 1 253 }) 254 vm.PutPropString(obj, "getState") 255 256 // Push the wrapper for statedb.Exists 257 vm.PushGoFunction(func(ctx *duktape.Context) int { 258 ctx.PushBoolean(dw.db.Exist(common.BytesToAddress(popSlice(ctx)))) 259 return 1 260 }) 261 vm.PutPropString(obj, "exists") 262 } 263 264 // contractWrapper provides a JavaScript wrapper around vm.Contract 265 type contractWrapper struct { 266 contract *vm.Contract 267 } 268 269 // pushObject assembles a JSVM object wrapping a swappable contract and pushes it 270 // onto the VM stack. 271 func (cw *contractWrapper) pushObject(vm *duktape.Context) { 272 obj := vm.PushObject() 273 274 // Push the wrapper for contract.Caller 275 vm.PushGoFunction(func(ctx *duktape.Context) int { 276 ptr := ctx.PushFixedBuffer(20) 277 copy(makeSlice(ptr, 20), cw.contract.Caller().Bytes()) 278 return 1 279 }) 280 vm.PutPropString(obj, "getCaller") 281 282 // Push the wrapper for contract.Address 283 vm.PushGoFunction(func(ctx *duktape.Context) int { 284 ptr := ctx.PushFixedBuffer(20) 285 copy(makeSlice(ptr, 20), cw.contract.Address().Bytes()) 286 return 1 287 }) 288 vm.PutPropString(obj, "getAddress") 289 290 // Push the wrapper for contract.Value 291 vm.PushGoFunction(func(ctx *duktape.Context) int { 292 pushBigInt(cw.contract.Value(), ctx) 293 return 1 294 }) 295 vm.PutPropString(obj, "getValue") 296 297 // Push the wrapper for contract.Input 298 vm.PushGoFunction(func(ctx *duktape.Context) int { 299 blob := cw.contract.Input 300 301 ptr := ctx.PushFixedBuffer(len(blob)) 302 copy(makeSlice(ptr, uint(len(blob))), blob) 303 return 1 304 }) 305 vm.PutPropString(obj, "getInput") 306 } 307 308 type frame struct { 309 typ *string 310 from *common.Address 311 to *common.Address 312 input []byte 313 gas *uint 314 value *big.Int 315 } 316 317 func newFrame() *frame { 318 return &frame{ 319 typ: new(string), 320 from: new(common.Address), 321 to: new(common.Address), 322 gas: new(uint), 323 } 324 } 325 326 func (f *frame) pushObject(vm *duktape.Context) { 327 obj := vm.PushObject() 328 329 vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, *f.typ); return 1 }) 330 vm.PutPropString(obj, "getType") 331 332 vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, *f.from); return 1 }) 333 vm.PutPropString(obj, "getFrom") 334 335 vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, *f.to); return 1 }) 336 vm.PutPropString(obj, "getTo") 337 338 vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, f.input); return 1 }) 339 vm.PutPropString(obj, "getInput") 340 341 vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, *f.gas); return 1 }) 342 vm.PutPropString(obj, "getGas") 343 344 vm.PushGoFunction(func(ctx *duktape.Context) int { 345 if f.value != nil { 346 pushValue(ctx, f.value) 347 } else { 348 ctx.PushUndefined() 349 } 350 return 1 351 }) 352 vm.PutPropString(obj, "getValue") 353 } 354 355 type frameResult struct { 356 gasUsed *uint 357 output []byte 358 errorValue *string 359 } 360 361 func newFrameResult() *frameResult { 362 return &frameResult{ 363 gasUsed: new(uint), 364 } 365 } 366 367 func (r *frameResult) pushObject(vm *duktape.Context) { 368 obj := vm.PushObject() 369 370 vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, *r.gasUsed); return 1 }) 371 vm.PutPropString(obj, "getGasUsed") 372 373 vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, r.output); return 1 }) 374 vm.PutPropString(obj, "getOutput") 375 376 vm.PushGoFunction(func(ctx *duktape.Context) int { 377 if r.errorValue != nil { 378 pushValue(ctx, *r.errorValue) 379 } else { 380 ctx.PushUndefined() 381 } 382 return 1 383 }) 384 vm.PutPropString(obj, "getError") 385 } 386 387 // jsTracer provides an implementation of Tracer that evaluates a Javascript 388 // function for each VM execution step. 389 type jsTracer struct { 390 vm *duktape.Context // Javascript VM instance 391 env *vm.EVM // EVM instance executing the code being traced 392 393 tracerObject int // Stack index of the tracer JavaScript object 394 stateObject int // Stack index of the global state to pull arguments from 395 396 opWrapper *opWrapper // Wrapper around the VM opcode 397 stackWrapper *stackWrapper // Wrapper around the VM stack 398 memoryWrapper *memoryWrapper // Wrapper around the VM memory 399 contractWrapper *contractWrapper // Wrapper around the contract object 400 dbWrapper *dbWrapper // Wrapper around the VM environment 401 402 pcValue *uint // Swappable pc value wrapped by a log accessor 403 gasValue *uint // Swappable gas value wrapped by a log accessor 404 costValue *uint // Swappable cost value wrapped by a log accessor 405 depthValue *uint // Swappable depth value wrapped by a log accessor 406 errorValue *string // Swappable error value wrapped by a log accessor 407 refundValue *uint // Swappable refund value wrapped by a log accessor 408 409 frame *frame // Represents entry into call frame. Fields are swappable 410 frameResult *frameResult // Represents exit from a call frame. Fields are swappable 411 412 ctx map[string]interface{} // Transaction context gathered throughout execution 413 err error // Error, if one has occurred 414 415 interrupt uint32 // Atomic flag to signal execution interruption 416 reason error // Textual reason for the interruption 417 418 activePrecompiles []common.Address // Updated on CaptureStart based on given rules 419 traceSteps bool // When true, will invoke step() on each opcode 420 traceCallFrames bool // When true, will invoke enter() and exit() js funcs 421 gasLimit uint64 // Amount of gas bought for the whole tx 422 } 423 424 // New instantiates a new tracer instance. code specifies a Javascript snippet, 425 // which must evaluate to an expression returning an object with 'step', 'fault' 426 // and 'result' functions. 427 func newJsTracer(code string, ctx *tracers2.Context) (tracers2.Tracer, error) { 428 if c, ok := assetTracers[code]; ok { 429 code = c 430 } 431 if ctx == nil { 432 ctx = new(tracers2.Context) 433 } 434 tracer := &jsTracer{ 435 vm: duktape.New(), 436 ctx: make(map[string]interface{}), 437 opWrapper: new(opWrapper), 438 stackWrapper: new(stackWrapper), 439 memoryWrapper: new(memoryWrapper), 440 contractWrapper: new(contractWrapper), 441 dbWrapper: new(dbWrapper), 442 pcValue: new(uint), 443 gasValue: new(uint), 444 costValue: new(uint), 445 depthValue: new(uint), 446 refundValue: new(uint), 447 frame: newFrame(), 448 frameResult: newFrameResult(), 449 } 450 if ctx.BlockHash != (common.Hash{}) { 451 tracer.ctx["blockHash"] = ctx.BlockHash 452 453 if ctx.TxHash != (common.Hash{}) { 454 tracer.ctx["txIndex"] = ctx.TxIndex 455 tracer.ctx["txHash"] = ctx.TxHash 456 } 457 } 458 // Set up builtins for this environment 459 tracer.vm.PushGlobalGoFunction("toHex", func(ctx *duktape.Context) int { 460 ctx.PushString(hexutil.Encode(popSlice(ctx))) 461 return 1 462 }) 463 tracer.vm.PushGlobalGoFunction("toWord", func(ctx *duktape.Context) int { 464 var word common.Hash 465 if ptr, size := ctx.GetBuffer(-1); ptr != nil { 466 word = common.BytesToHash(makeSlice(ptr, size)) 467 } else { 468 word = common.HexToHash(ctx.GetString(-1)) 469 } 470 ctx.Pop() 471 copy(makeSlice(ctx.PushFixedBuffer(32), 32), word[:]) 472 return 1 473 }) 474 tracer.vm.PushGlobalGoFunction("toAddress", func(ctx *duktape.Context) int { 475 var addr common.Address 476 if ptr, size := ctx.GetBuffer(-1); ptr != nil { 477 addr = common.BytesToAddress(makeSlice(ptr, size)) 478 } else { 479 addr = common.HexToAddress(ctx.GetString(-1)) 480 } 481 ctx.Pop() 482 copy(makeSlice(ctx.PushFixedBuffer(20), 20), addr[:]) 483 return 1 484 }) 485 tracer.vm.PushGlobalGoFunction("toContract", func(ctx *duktape.Context) int { 486 var from common.Address 487 if ptr, size := ctx.GetBuffer(-2); ptr != nil { 488 from = common.BytesToAddress(makeSlice(ptr, size)) 489 } else { 490 from = common.HexToAddress(ctx.GetString(-2)) 491 } 492 nonce := uint64(ctx.GetInt(-1)) 493 ctx.Pop2() 494 495 contract := crypto.CreateAddress(from, nonce) 496 copy(makeSlice(ctx.PushFixedBuffer(20), 20), contract[:]) 497 return 1 498 }) 499 tracer.vm.PushGlobalGoFunction("toContract2", func(ctx *duktape.Context) int { 500 var from common.Address 501 if ptr, size := ctx.GetBuffer(-3); ptr != nil { 502 from = common.BytesToAddress(makeSlice(ptr, size)) 503 } else { 504 from = common.HexToAddress(ctx.GetString(-3)) 505 } 506 // Retrieve salt hex string from js stack 507 salt := common.HexToHash(ctx.GetString(-2)) 508 // Retrieve code slice from js stack 509 var code []byte 510 if ptr, size := ctx.GetBuffer(-1); ptr != nil { 511 code = common.CopyBytes(makeSlice(ptr, size)) 512 } else { 513 code = common.FromHex(ctx.GetString(-1)) 514 } 515 codeHash := crypto.Keccak256(code) 516 ctx.Pop3() 517 contract := crypto.CreateAddress2(from, salt, codeHash) 518 copy(makeSlice(ctx.PushFixedBuffer(20), 20), contract[:]) 519 return 1 520 }) 521 tracer.vm.PushGlobalGoFunction("isPrecompiled", func(ctx *duktape.Context) int { 522 addr := common.BytesToAddress(popSlice(ctx)) 523 for _, p := range tracer.activePrecompiles { 524 if p == addr { 525 ctx.PushBoolean(true) 526 return 1 527 } 528 } 529 ctx.PushBoolean(false) 530 return 1 531 }) 532 tracer.vm.PushGlobalGoFunction("slice", func(ctx *duktape.Context) int { 533 start, end := ctx.GetInt(-2), ctx.GetInt(-1) 534 ctx.Pop2() 535 536 blob := popSlice(ctx) 537 size := end - start 538 539 if start < 0 || start > end || end > len(blob) { 540 // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go 541 // runtime goes belly up https://github.com/golang/go/issues/15639. 542 log.Warn("Tracer accessed out of bound memory", "available", len(blob), "offset", start, "size", size) 543 ctx.PushFixedBuffer(0) 544 return 1 545 } 546 copy(makeSlice(ctx.PushFixedBuffer(size), uint(size)), blob[start:end]) 547 return 1 548 }) 549 // Push the JavaScript tracer as object #0 onto the JSVM stack and validate it 550 if err := tracer.vm.PevalString("(" + code + ")"); err != nil { 551 log.Warn("Failed to compile tracer", "err", err) 552 return nil, err 553 } 554 tracer.tracerObject = 0 // yeah, nice, eval can't return the index itself 555 556 hasStep := tracer.vm.GetPropString(tracer.tracerObject, "step") 557 tracer.vm.Pop() 558 559 if !tracer.vm.GetPropString(tracer.tracerObject, "fault") { 560 return nil, fmt.Errorf("trace object must expose a function fault()") 561 } 562 tracer.vm.Pop() 563 564 if !tracer.vm.GetPropString(tracer.tracerObject, "result") { 565 return nil, fmt.Errorf("trace object must expose a function result()") 566 } 567 tracer.vm.Pop() 568 569 hasEnter := tracer.vm.GetPropString(tracer.tracerObject, "enter") 570 tracer.vm.Pop() 571 hasExit := tracer.vm.GetPropString(tracer.tracerObject, "exit") 572 tracer.vm.Pop() 573 if hasEnter != hasExit { 574 return nil, fmt.Errorf("trace object must expose either both or none of enter() and exit()") 575 } 576 tracer.traceCallFrames = hasEnter && hasExit 577 tracer.traceSteps = hasStep 578 579 // Tracer is valid, inject the big int library to access large numbers 580 tracer.vm.EvalString(bigIntegerJS) 581 tracer.vm.PutGlobalString("bigInt") 582 583 // Push the global environment state as object #1 into the JSVM stack 584 tracer.stateObject = tracer.vm.PushObject() 585 586 logObject := tracer.vm.PushObject() 587 588 tracer.opWrapper.pushObject(tracer.vm) 589 tracer.vm.PutPropString(logObject, "op") 590 591 tracer.stackWrapper.pushObject(tracer.vm) 592 tracer.vm.PutPropString(logObject, "stack") 593 594 tracer.memoryWrapper.pushObject(tracer.vm) 595 tracer.vm.PutPropString(logObject, "memory") 596 597 tracer.contractWrapper.pushObject(tracer.vm) 598 tracer.vm.PutPropString(logObject, "contract") 599 600 tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.pcValue); return 1 }) 601 tracer.vm.PutPropString(logObject, "getPC") 602 603 tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.gasValue); return 1 }) 604 tracer.vm.PutPropString(logObject, "getGas") 605 606 tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.costValue); return 1 }) 607 tracer.vm.PutPropString(logObject, "getCost") 608 609 tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.depthValue); return 1 }) 610 tracer.vm.PutPropString(logObject, "getDepth") 611 612 tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.refundValue); return 1 }) 613 tracer.vm.PutPropString(logObject, "getRefund") 614 615 tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { 616 if tracer.errorValue != nil { 617 ctx.PushString(*tracer.errorValue) 618 } else { 619 ctx.PushUndefined() 620 } 621 return 1 622 }) 623 tracer.vm.PutPropString(logObject, "getError") 624 625 tracer.vm.PutPropString(tracer.stateObject, "log") 626 627 tracer.frame.pushObject(tracer.vm) 628 tracer.vm.PutPropString(tracer.stateObject, "frame") 629 630 tracer.frameResult.pushObject(tracer.vm) 631 tracer.vm.PutPropString(tracer.stateObject, "frameResult") 632 633 tracer.dbWrapper.pushObject(tracer.vm) 634 tracer.vm.PutPropString(tracer.stateObject, "db") 635 636 return tracer, nil 637 } 638 639 // Stop terminates execution of the tracer at the first opportune moment. 640 func (jst *jsTracer) Stop(err error) { 641 jst.reason = err 642 atomic.StoreUint32(&jst.interrupt, 1) 643 } 644 645 // call executes a method on a JS object, catching any errors, formatting and 646 // returning them as error objects. 647 func (jst *jsTracer) call(noret bool, method string, args ...string) (json.RawMessage, error) { 648 // Execute the JavaScript call and return any error 649 jst.vm.PushString(method) 650 for _, arg := range args { 651 jst.vm.GetPropString(jst.stateObject, arg) 652 } 653 code := jst.vm.PcallProp(jst.tracerObject, len(args)) 654 defer jst.vm.Pop() 655 656 if code != 0 { 657 err := jst.vm.SafeToString(-1) 658 return nil, errors.New(err) 659 } 660 // No error occurred, extract return value and return 661 if noret { 662 return nil, nil 663 } 664 // Push a JSON marshaller onto the stack. We can't marshal from the out- 665 // side because duktape can crash on large nestings and we can't catch 666 // C++ exceptions ourselves from Go. TODO(karalabe): Yuck, why wrap?! 667 jst.vm.PushString("(JSON.stringify)") 668 jst.vm.Eval() 669 670 jst.vm.Swap(-1, -2) 671 if code = jst.vm.Pcall(1); code != 0 { 672 err := jst.vm.SafeToString(-1) 673 return nil, errors.New(err) 674 } 675 return json.RawMessage(jst.vm.SafeToString(-1)), nil 676 } 677 678 func wrapError(context string, err error) error { 679 return fmt.Errorf("%v in server-side tracer function '%v'", err, context) 680 } 681 682 // CaptureTxStart implements the Tracer interface and is invoked at the beginning of 683 // transaction processing. 684 func (jst *jsTracer) CaptureTxStart(gasLimit uint64) { 685 jst.gasLimit = gasLimit 686 } 687 688 // CaptureTxStart implements the Tracer interface and is invoked at the end of 689 // transaction processing. 690 func (*jsTracer) CaptureTxEnd(restGas uint64) {} 691 692 // CaptureStart implements the Tracer interface and is invoked before executing the 693 // top-level call frame of a transaction. 694 func (jst *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { 695 jst.env = env 696 jst.ctx["type"] = "CALL" 697 if create { 698 jst.ctx["type"] = "CREATE" 699 } 700 jst.ctx["from"] = from 701 jst.ctx["to"] = to 702 jst.ctx["input"] = input 703 jst.ctx["gas"] = gas 704 jst.ctx["gasPrice"] = env.TxContext.GasPrice 705 jst.ctx["value"] = value 706 707 // Initialize the context 708 jst.ctx["block"] = env.Context.BlockNumber.Uint64() 709 jst.dbWrapper.db = env.StateDB 710 // Update list of precompiles based on current block 711 rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil) 712 jst.activePrecompiles = vm.ActivePrecompiles(rules) 713 714 // Intrinsic costs are the only things reduced from initial gas to this point 715 jst.ctx["intrinsicGas"] = jst.gasLimit - gas 716 } 717 718 // CaptureState implements the Tracer interface to trace a single step of VM execution. 719 func (jst *jsTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { 720 if !jst.traceSteps { 721 return 722 } 723 if jst.err != nil { 724 return 725 } 726 // If tracing was interrupted, set the error and stop 727 if atomic.LoadUint32(&jst.interrupt) > 0 { 728 jst.err = jst.reason 729 jst.env.Cancel() 730 return 731 } 732 jst.opWrapper.op = op 733 jst.stackWrapper.stack = scope.Stack 734 jst.memoryWrapper.memory = scope.Memory 735 jst.contractWrapper.contract = scope.Contract 736 737 *jst.pcValue = uint(pc) 738 *jst.gasValue = uint(gas) 739 *jst.costValue = uint(cost) 740 *jst.depthValue = uint(depth) 741 *jst.refundValue = uint(jst.env.StateDB.GetRefund()) 742 743 jst.errorValue = nil 744 if err != nil { 745 jst.errorValue = new(string) 746 *jst.errorValue = err.Error() 747 } 748 749 if _, err := jst.call(true, "step", "log", "db"); err != nil { 750 jst.err = wrapError("step", err) 751 } 752 } 753 754 // CaptureFault implements the Tracer interface to trace an execution fault 755 func (jst *jsTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { 756 if jst.err != nil { 757 return 758 } 759 // Apart from the error, everything matches the previous invocation 760 jst.errorValue = new(string) 761 *jst.errorValue = err.Error() 762 763 if _, err := jst.call(true, "fault", "log", "db"); err != nil { 764 jst.err = wrapError("fault", err) 765 } 766 } 767 768 // CaptureEnd is called after the top-level call finishes. 769 func (jst *jsTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) { 770 jst.ctx["output"] = output 771 jst.ctx["time"] = t.String() 772 jst.ctx["gasUsed"] = gasUsed 773 774 if err != nil { 775 jst.ctx["error"] = err.Error() 776 } 777 } 778 779 // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). 780 func (jst *jsTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { 781 if !jst.traceCallFrames { 782 return 783 } 784 if jst.err != nil { 785 return 786 } 787 // If tracing was interrupted, set the error and stop 788 if atomic.LoadUint32(&jst.interrupt) > 0 { 789 jst.err = jst.reason 790 return 791 } 792 793 *jst.frame.typ = typ.String() 794 *jst.frame.from = from 795 *jst.frame.to = to 796 jst.frame.input = common.CopyBytes(input) 797 *jst.frame.gas = uint(gas) 798 jst.frame.value = nil 799 if value != nil { 800 jst.frame.value = new(big.Int).SetBytes(value.Bytes()) 801 } 802 803 if _, err := jst.call(true, "enter", "frame"); err != nil { 804 jst.err = wrapError("enter", err) 805 } 806 } 807 808 // CaptureExit is called when EVM exits a scope, even if the scope didn't 809 // execute any code. 810 func (jst *jsTracer) CaptureExit(output []byte, gasUsed uint64, err error) { 811 if !jst.traceCallFrames { 812 return 813 } 814 // If tracing was interrupted, set the error and stop 815 if atomic.LoadUint32(&jst.interrupt) > 0 { 816 jst.err = jst.reason 817 return 818 } 819 820 jst.frameResult.output = common.CopyBytes(output) 821 *jst.frameResult.gasUsed = uint(gasUsed) 822 jst.frameResult.errorValue = nil 823 if err != nil { 824 jst.frameResult.errorValue = new(string) 825 *jst.frameResult.errorValue = err.Error() 826 } 827 828 if _, err := jst.call(true, "exit", "frameResult"); err != nil { 829 jst.err = wrapError("exit", err) 830 } 831 } 832 833 // GetResult calls the Javascript 'result' function and returns its value, or any accumulated error 834 func (jst *jsTracer) GetResult() (json.RawMessage, error) { 835 // Transform the context into a JavaScript object and inject into the state 836 obj := jst.vm.PushObject() 837 838 for key, val := range jst.ctx { 839 jst.addToObj(obj, key, val) 840 } 841 jst.vm.PutPropString(jst.stateObject, "ctx") 842 843 // Finalize the trace and return the results 844 result, err := jst.call(false, "result", "ctx", "db") 845 if err != nil { 846 jst.err = wrapError("result", err) 847 } 848 // Clean up the JavaScript environment 849 jst.vm.DestroyHeap() 850 jst.vm.Destroy() 851 852 return result, jst.err 853 } 854 855 // addToObj pushes a field to a JS object. 856 func (jst *jsTracer) addToObj(obj int, key string, val interface{}) { 857 pushValue(jst.vm, val) 858 jst.vm.PutPropString(obj, key) 859 } 860 861 func pushValue(ctx *duktape.Context, val interface{}) { 862 switch val := val.(type) { 863 case uint64: 864 ctx.PushUint(uint(val)) 865 case string: 866 ctx.PushString(val) 867 case []byte: 868 ptr := ctx.PushFixedBuffer(len(val)) 869 copy(makeSlice(ptr, uint(len(val))), val) 870 case common.Address: 871 ptr := ctx.PushFixedBuffer(20) 872 copy(makeSlice(ptr, 20), val[:]) 873 case *big.Int: 874 pushBigInt(val, ctx) 875 case int: 876 ctx.PushInt(val) 877 case uint: 878 ctx.PushUint(val) 879 case common.Hash: 880 ptr := ctx.PushFixedBuffer(32) 881 copy(makeSlice(ptr, 32), val[:]) 882 default: 883 panic(fmt.Sprintf("unsupported type: %T", val)) 884 } 885 }