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. 18package js 19 20import ( 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" 34 "github.com/ethereum/go-ethereum/core/vm" 35 "github.com/ethereum/go-ethereum/crypto" 36 tracers2 "github.com/ethereum/go-ethereum/eth/tracers" 37 "github.com/ethereum/go-ethereum/eth/tracers/js/internal/tracers" 38 "github.com/ethereum/go-ethereum/log" 39 "gopkg.in/olebedev/go-duktape.v3" 40) 41 42// camel converts a snake cased input string into a camel cased output. 43func camel(str string) string { 44 pieces := strings.Split(str, "_") 45 for i := 1; i < len(pieces); i++ { 46 pieces[i] = string(unicode.ToUpper(rune(pieces[i][0]))) + pieces[i][1:] 47 } 48 return strings.Join(pieces, "") 49} 50 51var assetTracers = make(map[string]string) 52 53// init retrieves the JavaScript transaction tracers included in go-ethereum. 54func init() { 55 for _, file := range tracers.AssetNames() { 56 name := camel(strings.TrimSuffix(file, ".js")) 57 assetTracers[name] = string(tracers.MustAsset(file)) 58 } 59 tracers2.RegisterLookup(true, newJsTracer) 60} 61 62// makeSlice convert an unsafe memory pointer with the given type into a Go byte 63// slice. 64// 65// Note, the returned slice uses the same memory area as the input arguments. 66// If those are duktape stack items, popping them off **will** make the slice 67// contents change. 68func makeSlice(ptr unsafe.Pointer, size uint) []byte { 69 var sl = struct { 70 addr uintptr 71 len int 72 cap int 73 }{uintptr(ptr), int(size), int(size)} 74 75 return *(*[]byte)(unsafe.Pointer(&sl)) 76} 77 78// popSlice pops a buffer off the JavaScript stack and returns it as a slice. 79func popSlice(ctx *duktape.Context) []byte { 80 blob := common.CopyBytes(makeSlice(ctx.GetBuffer(-1))) 81 ctx.Pop() 82 return blob 83} 84 85// pushBigInt create a JavaScript BigInteger in the VM. 86func pushBigInt(n *big.Int, ctx *duktape.Context) { 87 ctx.GetGlobalString("bigInt") 88 ctx.PushString(n.String()) 89 ctx.Call(1) 90} 91 92// opWrapper provides a JavaScript wrapper around OpCode. 93type opWrapper struct { 94 op vm.OpCode 95} 96 97// pushObject assembles a JSVM object wrapping a swappable opcode and pushes it 98// onto the VM stack. 99func (ow *opWrapper) pushObject(vm *duktape.Context) { 100 obj := vm.PushObject() 101 102 vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushInt(int(ow.op)); return 1 }) 103 vm.PutPropString(obj, "toNumber") 104 105 vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushString(ow.op.String()); return 1 }) 106 vm.PutPropString(obj, "toString") 107 108 vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushBoolean(ow.op.IsPush()); return 1 }) 109 vm.PutPropString(obj, "isPush") 110} 111 112// memoryWrapper provides a JavaScript wrapper around vm.Memory. 113type memoryWrapper struct { 114 memory *vm.Memory 115} 116 117// slice returns the requested range of memory as a byte slice. 118func (mw *memoryWrapper) slice(begin, end int64) []byte { 119 if end == begin { 120 return []byte{} 121 } 122 if end < begin || begin < 0 { 123 // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go 124 // runtime goes belly up https://github.com/golang/go/issues/15639. 125 log.Warn("Tracer accessed out of bound memory", "offset", begin, "end", end) 126 return nil 127 } 128 if mw.memory.Len() < int(end) { 129 // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go 130 // runtime goes belly up https://github.com/golang/go/issues/15639. 131 log.Warn("Tracer accessed out of bound memory", "available", mw.memory.Len(), "offset", begin, "size", end-begin) 132 return nil 133 } 134 return mw.memory.GetCopy(begin, end-begin) 135} 136 137// getUint returns the 32 bytes at the specified address interpreted as a uint. 138func (mw *memoryWrapper) getUint(addr int64) *big.Int { 139 if mw.memory.Len() < int(addr)+32 || addr < 0 { 140 // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go 141 // runtime goes belly up https://github.com/golang/go/issues/15639. 142 log.Warn("Tracer accessed out of bound memory", "available", mw.memory.Len(), "offset", addr, "size", 32) 143 return new(big.Int) 144 } 145 return new(big.Int).SetBytes(mw.memory.GetPtr(addr, 32)) 146} 147 148// pushObject assembles a JSVM object wrapping a swappable memory and pushes it 149// onto the VM stack. 150func (mw *memoryWrapper) pushObject(vm *duktape.Context) { 151 obj := vm.PushObject() 152 153 // Generate the `slice` method which takes two ints and returns a buffer 154 vm.PushGoFunction(func(ctx *duktape.Context) int { 155 blob := mw.slice(int64(ctx.GetInt(-2)), int64(ctx.GetInt(-1))) 156 ctx.Pop2() 157 158 ptr := ctx.PushFixedBuffer(len(blob)) 159 copy(makeSlice(ptr, uint(len(blob))), blob) 160 return 1 161 }) 162 vm.PutPropString(obj, "slice") 163 164 // Generate the `getUint` method which takes an int and returns a bigint 165 vm.PushGoFunction(func(ctx *duktape.Context) int { 166 offset := int64(ctx.GetInt(-1)) 167 ctx.Pop() 168 169 pushBigInt(mw.getUint(offset), ctx) 170 return 1 171 }) 172 vm.PutPropString(obj, "getUint") 173} 174 175// stackWrapper provides a JavaScript wrapper around vm.Stack. 176type stackWrapper struct { 177 stack *vm.Stack 178} 179 180// peek returns the nth-from-the-top element of the stack. 181func (sw *stackWrapper) peek(idx int) *big.Int { 182 if len(sw.stack.Data()) <= idx || idx < 0 { 183 // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go 184 // runtime goes belly up https://github.com/golang/go/issues/15639. 185 log.Warn("Tracer accessed out of bound stack", "size", len(sw.stack.Data()), "index", idx) 186 return new(big.Int) 187 } 188 return sw.stack.Back(idx).ToBig() 189} 190 191// pushObject assembles a JSVM object wrapping a swappable stack and pushes it 192// onto the VM stack. 193func (sw *stackWrapper) pushObject(vm *duktape.Context) { 194 obj := vm.PushObject() 195 196 vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushInt(len(sw.stack.Data())); return 1 }) 197 vm.PutPropString(obj, "length") 198 199 // Generate the `peek` method which takes an int and returns a bigint 200 vm.PushGoFunction(func(ctx *duktape.Context) int { 201 offset := ctx.GetInt(-1) 202 ctx.Pop() 203 204 pushBigInt(sw.peek(offset), ctx) 205 return 1 206 }) 207 vm.PutPropString(obj, "peek") 208} 209 210// dbWrapper provides a JavaScript wrapper around vm.Database. 211type dbWrapper struct { 212 db vm.StateDB 213} 214 215// pushObject assembles a JSVM object wrapping a swappable database and pushes it 216// onto the VM stack. 217func (dw *dbWrapper) pushObject(vm *duktape.Context) { 218 obj := vm.PushObject() 219 220 // Push the wrapper for statedb.GetBalance 221 vm.PushGoFunction(func(ctx *duktape.Context) int { 222 pushBigInt(dw.db.GetBalance(common.BytesToAddress(popSlice(ctx))), ctx) 223 return 1 224 }) 225 vm.PutPropString(obj, "getBalance") 226 227 // Push the wrapper for statedb.GetNonce 228 vm.PushGoFunction(func(ctx *duktape.Context) int { 229 ctx.PushInt(int(dw.db.GetNonce(common.BytesToAddress(popSlice(ctx))))) 230 return 1 231 }) 232 vm.PutPropString(obj, "getNonce") 233 234 // Push the wrapper for statedb.GetCode 235 vm.PushGoFunction(func(ctx *duktape.Context) int { 236 code := dw.db.GetCode(common.BytesToAddress(popSlice(ctx))) 237 238 ptr := ctx.PushFixedBuffer(len(code)) 239 copy(makeSlice(ptr, uint(len(code))), code) 240 return 1 241 }) 242 vm.PutPropString(obj, "getCode") 243 244 // Push the wrapper for statedb.GetState 245 vm.PushGoFunction(func(ctx *duktape.Context) int { 246 hash := popSlice(ctx) 247 addr := popSlice(ctx) 248 249 state := dw.db.GetState(common.BytesToAddress(addr), common.BytesToHash(hash)) 250 251 ptr := ctx.PushFixedBuffer(len(state)) 252 copy(makeSlice(ptr, uint(len(state))), state[:]) 253 return 1 254 }) 255 vm.PutPropString(obj, "getState") 256 257 // Push the wrapper for statedb.Exists 258 vm.PushGoFunction(func(ctx *duktape.Context) int { 259 ctx.PushBoolean(dw.db.Exist(common.BytesToAddress(popSlice(ctx)))) 260 return 1 261 }) 262 vm.PutPropString(obj, "exists") 263} 264 265// contractWrapper provides a JavaScript wrapper around vm.Contract 266type contractWrapper struct { 267 contract *vm.Contract 268} 269 270// pushObject assembles a JSVM object wrapping a swappable contract and pushes it 271// onto the VM stack. 272func (cw *contractWrapper) pushObject(vm *duktape.Context) { 273 obj := vm.PushObject() 274 275 // Push the wrapper for contract.Caller 276 vm.PushGoFunction(func(ctx *duktape.Context) int { 277 ptr := ctx.PushFixedBuffer(20) 278 copy(makeSlice(ptr, 20), cw.contract.Caller().Bytes()) 279 return 1 280 }) 281 vm.PutPropString(obj, "getCaller") 282 283 // Push the wrapper for contract.Address 284 vm.PushGoFunction(func(ctx *duktape.Context) int { 285 ptr := ctx.PushFixedBuffer(20) 286 copy(makeSlice(ptr, 20), cw.contract.Address().Bytes()) 287 return 1 288 }) 289 vm.PutPropString(obj, "getAddress") 290 291 // Push the wrapper for contract.Value 292 vm.PushGoFunction(func(ctx *duktape.Context) int { 293 pushBigInt(cw.contract.Value(), ctx) 294 return 1 295 }) 296 vm.PutPropString(obj, "getValue") 297 298 // Push the wrapper for contract.Input 299 vm.PushGoFunction(func(ctx *duktape.Context) int { 300 blob := cw.contract.Input 301 302 ptr := ctx.PushFixedBuffer(len(blob)) 303 copy(makeSlice(ptr, uint(len(blob))), blob) 304 return 1 305 }) 306 vm.PutPropString(obj, "getInput") 307} 308 309type frame struct { 310 typ *string 311 from *common.Address 312 to *common.Address 313 input []byte 314 gas *uint 315 value *big.Int 316} 317 318func newFrame() *frame { 319 return &frame{ 320 typ: new(string), 321 from: new(common.Address), 322 to: new(common.Address), 323 gas: new(uint), 324 } 325} 326 327func (f *frame) pushObject(vm *duktape.Context) { 328 obj := vm.PushObject() 329 330 vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, *f.typ); return 1 }) 331 vm.PutPropString(obj, "getType") 332 333 vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, *f.from); return 1 }) 334 vm.PutPropString(obj, "getFrom") 335 336 vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, *f.to); return 1 }) 337 vm.PutPropString(obj, "getTo") 338 339 vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, f.input); return 1 }) 340 vm.PutPropString(obj, "getInput") 341 342 vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, *f.gas); return 1 }) 343 vm.PutPropString(obj, "getGas") 344 345 vm.PushGoFunction(func(ctx *duktape.Context) int { 346 if f.value != nil { 347 pushValue(ctx, f.value) 348 } else { 349 ctx.PushUndefined() 350 } 351 return 1 352 }) 353 vm.PutPropString(obj, "getValue") 354} 355 356type frameResult struct { 357 gasUsed *uint 358 output []byte 359 errorValue *string 360} 361 362func newFrameResult() *frameResult { 363 return &frameResult{ 364 gasUsed: new(uint), 365 } 366} 367 368func (r *frameResult) pushObject(vm *duktape.Context) { 369 obj := vm.PushObject() 370 371 vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, *r.gasUsed); return 1 }) 372 vm.PutPropString(obj, "getGasUsed") 373 374 vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, r.output); return 1 }) 375 vm.PutPropString(obj, "getOutput") 376 377 vm.PushGoFunction(func(ctx *duktape.Context) int { 378 if r.errorValue != nil { 379 pushValue(ctx, *r.errorValue) 380 } else { 381 ctx.PushUndefined() 382 } 383 return 1 384 }) 385 vm.PutPropString(obj, "getError") 386} 387 388// jsTracer provides an implementation of Tracer that evaluates a Javascript 389// function for each VM execution step. 390type jsTracer struct { 391 vm *duktape.Context // Javascript VM instance 392 env *vm.EVM // EVM instance executing the code being traced 393 394 tracerObject int // Stack index of the tracer JavaScript object 395 stateObject int // Stack index of the global state to pull arguments from 396 397 opWrapper *opWrapper // Wrapper around the VM opcode 398 stackWrapper *stackWrapper // Wrapper around the VM stack 399 memoryWrapper *memoryWrapper // Wrapper around the VM memory 400 contractWrapper *contractWrapper // Wrapper around the contract object 401 dbWrapper *dbWrapper // Wrapper around the VM environment 402 403 pcValue *uint // Swappable pc value wrapped by a log accessor 404 gasValue *uint // Swappable gas value wrapped by a log accessor 405 costValue *uint // Swappable cost value wrapped by a log accessor 406 depthValue *uint // Swappable depth value wrapped by a log accessor 407 errorValue *string // Swappable error value wrapped by a log accessor 408 refundValue *uint // Swappable refund value wrapped by a log accessor 409 410 frame *frame // Represents entry into call frame. Fields are swappable 411 frameResult *frameResult // Represents exit from a call frame. Fields are swappable 412 413 ctx map[string]interface{} // Transaction context gathered throughout execution 414 err error // Error, if one has occurred 415 416 interrupt uint32 // Atomic flag to signal execution interruption 417 reason error // Textual reason for the interruption 418 419 activePrecompiles []common.Address // Updated on CaptureStart based on given rules 420 traceSteps bool // When true, will invoke step() on each opcode 421 traceCallFrames bool // When true, will invoke enter() and exit() js funcs 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. 427func 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. 640func (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. 647func (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 678func wrapError(context string, err error) error { 679 return fmt.Errorf("%v in server-side tracer function '%v'", err, context) 680} 681 682// CaptureStart implements the Tracer interface to initialize the tracing operation. 683func (jst *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { 684 jst.env = env 685 jst.ctx["type"] = "CALL" 686 if create { 687 jst.ctx["type"] = "CREATE" 688 } 689 jst.ctx["from"] = from 690 jst.ctx["to"] = to 691 jst.ctx["input"] = input 692 jst.ctx["gas"] = gas 693 jst.ctx["gasPrice"] = env.TxContext.GasPrice 694 jst.ctx["value"] = value 695 696 // Initialize the context 697 jst.ctx["block"] = env.Context.BlockNumber.Uint64() 698 jst.dbWrapper.db = env.StateDB 699 // Update list of precompiles based on current block 700 rules := env.ChainConfig().Rules(env.Context.BlockNumber) 701 jst.activePrecompiles = vm.ActivePrecompiles(rules) 702 703 // Compute intrinsic gas 704 isHomestead := env.ChainConfig().IsHomestead(env.Context.BlockNumber) 705 isIstanbul := env.ChainConfig().IsIstanbul(env.Context.BlockNumber) 706 intrinsicGas, err := core.IntrinsicGas(input, nil, jst.ctx["type"] == "CREATE", isHomestead, isIstanbul) 707 if err != nil { 708 return 709 } 710 jst.ctx["intrinsicGas"] = intrinsicGas 711} 712 713// CaptureState implements the Tracer interface to trace a single step of VM execution. 714func (jst *jsTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { 715 if !jst.traceSteps { 716 return 717 } 718 if jst.err != nil { 719 return 720 } 721 // If tracing was interrupted, set the error and stop 722 if atomic.LoadUint32(&jst.interrupt) > 0 { 723 jst.err = jst.reason 724 jst.env.Cancel() 725 return 726 } 727 jst.opWrapper.op = op 728 jst.stackWrapper.stack = scope.Stack 729 jst.memoryWrapper.memory = scope.Memory 730 jst.contractWrapper.contract = scope.Contract 731 732 *jst.pcValue = uint(pc) 733 *jst.gasValue = uint(gas) 734 *jst.costValue = uint(cost) 735 *jst.depthValue = uint(depth) 736 *jst.refundValue = uint(jst.env.StateDB.GetRefund()) 737 738 jst.errorValue = nil 739 if err != nil { 740 jst.errorValue = new(string) 741 *jst.errorValue = err.Error() 742 } 743 744 if _, err := jst.call(true, "step", "log", "db"); err != nil { 745 jst.err = wrapError("step", err) 746 } 747} 748 749// CaptureFault implements the Tracer interface to trace an execution fault 750func (jst *jsTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { 751 if jst.err != nil { 752 return 753 } 754 // Apart from the error, everything matches the previous invocation 755 jst.errorValue = new(string) 756 *jst.errorValue = err.Error() 757 758 if _, err := jst.call(true, "fault", "log", "db"); err != nil { 759 jst.err = wrapError("fault", err) 760 } 761} 762 763// CaptureEnd is called after the call finishes to finalize the tracing. 764func (jst *jsTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) { 765 jst.ctx["output"] = output 766 jst.ctx["time"] = t.String() 767 jst.ctx["gasUsed"] = gasUsed 768 769 if err != nil { 770 jst.ctx["error"] = err.Error() 771 } 772} 773 774// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). 775func (jst *jsTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { 776 if !jst.traceCallFrames { 777 return 778 } 779 if jst.err != nil { 780 return 781 } 782 // If tracing was interrupted, set the error and stop 783 if atomic.LoadUint32(&jst.interrupt) > 0 { 784 jst.err = jst.reason 785 return 786 } 787 788 *jst.frame.typ = typ.String() 789 *jst.frame.from = from 790 *jst.frame.to = to 791 jst.frame.input = common.CopyBytes(input) 792 *jst.frame.gas = uint(gas) 793 jst.frame.value = nil 794 if value != nil { 795 jst.frame.value = new(big.Int).SetBytes(value.Bytes()) 796 } 797 798 if _, err := jst.call(true, "enter", "frame"); err != nil { 799 jst.err = wrapError("enter", err) 800 } 801} 802 803// CaptureExit is called when EVM exits a scope, even if the scope didn't 804// execute any code. 805func (jst *jsTracer) CaptureExit(output []byte, gasUsed uint64, err error) { 806 if !jst.traceCallFrames { 807 return 808 } 809 // If tracing was interrupted, set the error and stop 810 if atomic.LoadUint32(&jst.interrupt) > 0 { 811 jst.err = jst.reason 812 return 813 } 814 815 jst.frameResult.output = common.CopyBytes(output) 816 *jst.frameResult.gasUsed = uint(gasUsed) 817 jst.frameResult.errorValue = nil 818 if err != nil { 819 jst.frameResult.errorValue = new(string) 820 *jst.frameResult.errorValue = err.Error() 821 } 822 823 if _, err := jst.call(true, "exit", "frameResult"); err != nil { 824 jst.err = wrapError("exit", err) 825 } 826} 827 828// GetResult calls the Javascript 'result' function and returns its value, or any accumulated error 829func (jst *jsTracer) GetResult() (json.RawMessage, error) { 830 // Transform the context into a JavaScript object and inject into the state 831 obj := jst.vm.PushObject() 832 833 for key, val := range jst.ctx { 834 jst.addToObj(obj, key, val) 835 } 836 jst.vm.PutPropString(jst.stateObject, "ctx") 837 838 // Finalize the trace and return the results 839 result, err := jst.call(false, "result", "ctx", "db") 840 if err != nil { 841 jst.err = wrapError("result", err) 842 } 843 // Clean up the JavaScript environment 844 jst.vm.DestroyHeap() 845 jst.vm.Destroy() 846 847 return result, jst.err 848} 849 850// addToObj pushes a field to a JS object. 851func (jst *jsTracer) addToObj(obj int, key string, val interface{}) { 852 pushValue(jst.vm, val) 853 jst.vm.PutPropString(obj, key) 854} 855 856func pushValue(ctx *duktape.Context, val interface{}) { 857 switch val := val.(type) { 858 case uint64: 859 ctx.PushUint(uint(val)) 860 case string: 861 ctx.PushString(val) 862 case []byte: 863 ptr := ctx.PushFixedBuffer(len(val)) 864 copy(makeSlice(ptr, uint(len(val))), val) 865 case common.Address: 866 ptr := ctx.PushFixedBuffer(20) 867 copy(makeSlice(ptr, 20), val[:]) 868 case *big.Int: 869 pushBigInt(val, ctx) 870 case int: 871 ctx.PushInt(val) 872 case uint: 873 ctx.PushUint(val) 874 case common.Hash: 875 ptr := ctx.PushFixedBuffer(32) 876 copy(makeSlice(ptr, 32), val[:]) 877 default: 878 panic(fmt.Sprintf("unsupported type: %T", val)) 879 } 880} 881