core, eth, internal: finalize call tracer and needed extras

This commit is contained in:
Péter Szilágyi 2017-12-15 17:00:23 +02:00
parent 087b79d3f0
commit becec012ef
11 changed files with 234 additions and 112 deletions

View file

@ -61,6 +61,11 @@ func (l *JSONLogger) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cos
return l.encoder.Encode(log)
}
// CaptureFault outputs state information on the logger.
func (l *JSONLogger) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error {
return nil
}
// CaptureEnd is triggered at end of execution.
func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error {
type endLog struct {

View file

@ -144,12 +144,17 @@ func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err er
)
contract.Input = input
if in.cfg.Debug {
defer func() {
if err != nil && !logged && in.cfg.Debug {
if err != nil {
if !logged {
in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err)
} else {
in.cfg.Tracer.CaptureFault(in.evm, pcCopy, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err)
}
}
}()
}
// The Interpreter main run loop (contextual). This loop runs until either an
// explicit STOP, RETURN or SELFDESTRUCT is executed, an error occurred during
// the execution of one of the operations or until the done flag is set by the

View file

@ -86,6 +86,7 @@ func (s *StructLog) OpName() string {
type Tracer interface {
CaptureStart(from common.Address, to common.Address, call bool, input []byte, gas uint64, value *big.Int) error
CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error
CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error
CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error
}
@ -166,6 +167,10 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui
return nil
}
func (l *StructLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error {
return nil
}
func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error {
fmt.Printf("0x%x", output)
if err != nil {

View file

@ -24,14 +24,12 @@ import (
"math/big"
"os"
"strings"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/miner"
"github.com/ethereum/go-ethereum/params"
@ -40,8 +38,6 @@ import (
"github.com/ethereum/go-ethereum/trie"
)
const defaultTraceTimeout = 5 * time.Second
// PublicEthereumAPI provides an API to access Ethereum full node-related
// information.
type PublicEthereumAPI struct {
@ -345,13 +341,6 @@ func NewPrivateDebugAPI(config *params.ChainConfig, eth *Ethereum) *PrivateDebug
return &PrivateDebugAPI{config: config, eth: eth}
}
// TraceArgs holds extra parameters to trace functions
type TraceArgs struct {
*vm.LogConfig
Tracer *string
Timeout *string
}
// Preimage is a debug API function that returns the preimage for a sha3 hash, if known.
func (api *PrivateDebugAPI) Preimage(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
db := core.PreimageTable(api.eth.ChainDb())

View file

@ -42,6 +42,24 @@ import (
"github.com/ethereum/go-ethereum/trie"
)
const (
// defaultTraceTimeout is the amount of time a single transaction can execute
// by default before being forcefully aborted.
defaultTraceTimeout = 5 * time.Second
// defaultTraceRewind is the number of blocks the tracer is willing to rewind
// and regenerate to produce the state necessary to run a specific transaction.
defaultTraceRewind = uint64(128)
)
// TraceConfig holds extra parameters to trace functions.
type TraceConfig struct {
*vm.LogConfig
Tracer *string
Timeout *string
Rewind *uint64
}
// txTraceResult is the result of a single transaction trace.
type txTraceResult struct {
Result interface{} `json:"result,omitempty"` // Trace results procuded by the tracer
@ -124,7 +142,7 @@ func (db *ephemeralDatabase) Prune(root common.Hash) {
// TraceChain returns the structured logs created during the execution of EVM
// between two blocks (excluding start) and returns them as a JSON object.
func (api *PrivateDebugAPI) TraceChain(ctx context.Context, start, end rpc.BlockNumber, config *TraceArgs) (*rpc.Subscription, error) {
func (api *PrivateDebugAPI) TraceChain(ctx context.Context, start, end rpc.BlockNumber, config *TraceConfig) (*rpc.Subscription, error) {
// Fetch the block interval that we want to trace
var from, to *types.Block
@ -157,7 +175,7 @@ func (api *PrivateDebugAPI) TraceChain(ctx context.Context, start, end rpc.Block
// traceChain configures a new tracer according to the provided configuration, and
// executes all the transactions contained within. The return value will be one item
// per transaction, dependent on the requestd tracer.
func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Block, config *TraceArgs) (*rpc.Subscription, error) {
func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Block, config *TraceConfig) (*rpc.Subscription, error) {
// Tracing a chain is a **long** operation, only do with subscriptions
notifier, supported := rpc.NotifierFromContext(ctx)
if !supported {
@ -166,17 +184,37 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
sub := notifier.CreateSubscription()
// Ensure we have a valid starting state before doing any work
origin := start.NumberU64()
memdb, _ := ethdb.NewMemDatabase()
db := &ephemeralDatabase{
diskdb: api.eth.ChainDb(),
memdb: memdb,
}
statedb, err := state.New(start.Root(), state.NewDatabase(db))
if err != nil {
// If the starting state is missing, allow some number of blocks to be rewound
rewind := defaultTraceRewind
if config.Rewind != nil {
rewind = *config.Rewind
}
// Find the most recent block that has the state available
for i := uint64(0); i < rewind; i++ {
start = api.eth.blockchain.GetBlock(start.ParentHash(), start.NumberU64()-1)
if start == nil {
break
}
if statedb, err = state.New(start.Root(), state.NewDatabase(db)); err == nil {
break
}
}
// If we still don't have the state available, bail out
if err != nil {
return nil, err
}
}
// Execute all the transaction contained within the chain concurrently for each block
blocks := int(end.NumberU64() - start.NumberU64())
blocks := int(end.NumberU64() - origin)
threads := runtime.NumCPU()
if threads > blocks {
@ -204,6 +242,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
res, err := api.traceTx(ctx, msg, vmctx, task.statedb, config)
if err != nil {
task.results[i] = &txTraceResult{Error: err.Error()}
log.Warn("Tracing failed", "err", err)
break
}
task.statedb.DeleteSuicides()
@ -254,7 +293,11 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
}
// Print progress logs if long enough time elapsed
if time.Since(logged) > 8*time.Second {
log.Info("Tracing chain segment", "start", start.NumberU64(), "end", end.NumberU64(), "current", number, "transactions", traced, "elapsed", time.Since(begin))
if number > origin {
log.Info("Tracing chain segment", "start", origin, "end", end.NumberU64(), "current", number, "transactions", traced, "elapsed", time.Since(begin))
} else {
log.Info("Preparing state for chain trace", "block", number, "start", origin, "elapsed", time.Since(begin))
}
logged = time.Now()
}
// Retrieve the next block to trace
@ -263,7 +306,8 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
failed = fmt.Errorf("block #%d not found", number)
break
}
// Send the block over to the concurrent tracers
// Send the block over to the concurrent tracers (if not in the rewind phase)
if number > origin {
txs := block.Transactions()
select {
@ -272,7 +316,9 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
return
}
traced += uint64(len(txs))
} else {
atomic.StoreUint64(&complete, number)
}
// Generate the next state snapshot fast without tracing
_, _, _, err := api.eth.blockchain.Processor().Process(block, statedb, vm.Config{})
if err != nil {
@ -314,7 +360,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
go func() {
var (
done = make(map[uint64]*blockTraceResult)
next = start.NumberU64() + 1
next = origin + 1
)
for res := range results {
// Queue up next received result
@ -341,7 +387,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
// TraceBlockByNumber returns the structured logs created during the execution of
// EVM and returns them as a JSON object.
func (api *PrivateDebugAPI) TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber, config *TraceArgs) ([]*txTraceResult, error) {
func (api *PrivateDebugAPI) TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber, config *TraceConfig) ([]*txTraceResult, error) {
// Fetch the block that we want to trace
var block *types.Block
@ -362,7 +408,7 @@ func (api *PrivateDebugAPI) TraceBlockByNumber(ctx context.Context, number rpc.B
// TraceBlockByHash returns the structured logs created during the execution of
// EVM and returns them as a JSON object.
func (api *PrivateDebugAPI) TraceBlockByHash(ctx context.Context, hash common.Hash, config *TraceArgs) ([]*txTraceResult, error) {
func (api *PrivateDebugAPI) TraceBlockByHash(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) {
block := api.eth.blockchain.GetBlockByHash(hash)
if block == nil {
return nil, fmt.Errorf("block #%x not found", hash)
@ -372,7 +418,7 @@ func (api *PrivateDebugAPI) TraceBlockByHash(ctx context.Context, hash common.Ha
// TraceBlock returns the structured logs created during the execution of EVM
// and returns them as a JSON object.
func (api *PrivateDebugAPI) TraceBlock(ctx context.Context, blob []byte, config *TraceArgs) ([]*txTraceResult, error) {
func (api *PrivateDebugAPI) TraceBlock(ctx context.Context, blob []byte, config *TraceConfig) ([]*txTraceResult, error) {
block := new(types.Block)
if err := rlp.Decode(bytes.NewReader(blob), block); err != nil {
return nil, fmt.Errorf("could not decode block: %v", err)
@ -382,7 +428,7 @@ func (api *PrivateDebugAPI) TraceBlock(ctx context.Context, blob []byte, config
// TraceBlockFromFile returns the structured logs created during the execution of
// EVM and returns them as a JSON object.
func (api *PrivateDebugAPI) TraceBlockFromFile(ctx context.Context, file string, config *TraceArgs) ([]*txTraceResult, error) {
func (api *PrivateDebugAPI) TraceBlockFromFile(ctx context.Context, file string, config *TraceConfig) ([]*txTraceResult, error) {
blob, err := ioutil.ReadFile(file)
if err != nil {
return nil, fmt.Errorf("could not read file: %v", err)
@ -393,7 +439,7 @@ func (api *PrivateDebugAPI) TraceBlockFromFile(ctx context.Context, file string,
// traceBlock configures a new tracer according to the provided configuration, and
// executes all the transactions contained within. The return value will be one item
// per transaction, dependent on the requestd tracer.
func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, config *TraceArgs) ([]*txTraceResult, error) {
func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, config *TraceConfig) ([]*txTraceResult, error) {
// Create the parent state database
if err := api.eth.engine.VerifyHeader(api.eth.blockchain, block.Header(), true); err != nil {
return nil, err
@ -469,7 +515,7 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block,
// TraceTransaction returns the structured logs created during the execution of EVM
// and returns them as a JSON object.
func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceArgs) (interface{}, error) {
func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) {
// Retrieve the transaction and assemble its EVM context
tx, blockHash, _, index := core.GetTransaction(api.eth.ChainDb(), hash)
if tx == nil {
@ -486,7 +532,7 @@ func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, hash common.Ha
// traceTx configures a new tracer according to the provided configuration, and
// executes the given message in the provided environment. The return value will
// be tracer dependent.
func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, vmctx vm.Context, statedb *state.StateDB, config *TraceArgs) (interface{}, error) {
func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, vmctx vm.Context, statedb *state.StateDB, config *TraceConfig) (interface{}, error) {
// Assemble the structured logger or the JavaScript tracer
var (
tracer vm.Tracer

File diff suppressed because one or more lines are too long

View file

@ -10,6 +10,12 @@
// step is invoked for every opcode that the VM executes.
step: function(log, db) {
// Capture any errors immediately
var error = log.getError();
if (error !== undefined) {
this.fault(log, db);
return;
}
// We only care about system opcodes, faster if we pre-check once
var syscall = (log.op.toNumber() & 0xf0) == 0xf0;
if (syscall) {
@ -39,9 +45,7 @@
if (this.callstack[left-1].calls === undefined) {
this.callstack[left-1].calls = [];
}
this.callstack[left-1].calls.push({
type: op,
});
this.callstack[left-1].calls.push({type: op});
return
}
// If a new method invocation is being done, add to the call stack
@ -89,7 +93,7 @@
}
// If an existing call is returning, pop off the call stack
if (syscall && op == 'REVERT') {
this.callstack[this.callstack.length - 1].revert = true;
this.callstack[this.callstack.length - 1].error = "execution reverted";
return;
}
if (log.getDepth() == this.callstack.length - 1) {
@ -105,21 +109,22 @@
if (!ret.equals(0)) {
call.to = toHex(toAddress(ret.toString(16)));
call.output = toHex(db.getCode(toAddress(ret.toString(16))));
} else {
call.revert = true;
} else if (call.error === undefined) {
call.error = "internal failure"; // TODO(karalabe): surface these faults somehow
}
} else {
// If the call was a CALL*, retrieve the output code
// If the call was a contract call, retrieve the gas usage and output
if (call.gas !== undefined) {
call.gasUsed = '0x' + bigInt(call.gasIn - call.gasCost + call.gas - log.getGas()).toString(16);
delete call.gasIn; delete call.gasCost;
}
var ret = log.stack.peek(0);
if (!ret.equals(0)) {
call.output = toHex(log.memory.slice(call.outOff, call.outOff + call.outLen));
} else {
call.revert = true;
} else if (call.error === undefined) {
call.error = "internal failure"; // TODO(karalabe): surface these faults somehow
}
}
delete call.gasIn; delete call.gasCost;
delete call.outOff; delete call.outLen;
}
if (call.gas !== undefined) {
@ -134,6 +139,37 @@
}
},
// fault is invoked when the actual execution of an opcode fails.
fault: function(log, db) {
// If the topmost call already reverted, don't handle the additional fault again
if (this.callstack[this.callstack.length - 1].error !== undefined) {
return;
}
// Pop off the just failed call
var call = this.callstack.pop();
call.error = log.getError();
// Consume all available gas and clean any leftovers
if (call.gas !== undefined) {
call.gas = '0x' + bigInt(call.gas).toString(16);
call.gasUsed = call.gas
}
delete call.gasIn; delete call.gasCost;
delete call.outOff; delete call.outLen;
// Flatten the failed call into its parent
var left = this.callstack.length;
if (left > 0) {
if (this.callstack[left-1].calls === undefined) {
this.callstack[left-1].calls = [];
}
this.callstack[left-1].calls.push(call);
return;
}
// Last call failed too, leave it in the stack
this.callstack.push(call);
},
// result is invoked when all the opcodes have been iterated over and returns
// the final result of the tracing.
result: function(ctx) {
@ -151,13 +187,44 @@
if (this.callstack[0].calls !== undefined) {
result.calls = this.callstack[0].calls;
}
if (ctx.error !== undefined) {
if (this.callstack[0].error !== undefined) {
result.error = this.callstack[0].error;
} else if (ctx.error !== undefined) {
result.error = ctx.error;
}
if (result.error !== undefined) {
delete result.output;
}
if (this.callstack[0].revert) {
result.revert = true;
return this.finalize(result);
},
// finalize recreates a call object using the final desired field oder for json
// serialization. This is a nicety feature to pass meaningfully ordered results
// to users who don't interpret it, just display it.
finalize: function(call) {
var sorted = {
type: call.type,
from: call.from,
to: call.to,
value: call.value,
gas: call.gas,
gasUsed: call.gasUsed,
input: call.input,
output: call.output,
error: call.error,
time: call.time,
calls: call.calls,
}
return result;
for (var key in sorted) {
if (sorted[key] === undefined) {
delete sorted[key];
}
}
if (sorted.calls !== undefined) {
for (var i=0; i<sorted.calls.length; i++) {
sorted.calls[i] = this.finalize(sorted.calls[i]);
}
}
return sorted;
}
}

View file

@ -4,6 +4,9 @@
// step is invoked for every opcode that the VM executes.
step: function(log, db) { },
// fault is invoked when the actual execution of an opcode fails.
fault: function(log, db) { },
// result is invoked when all the opcodes have been iterated over and returns
// the final result of the tracing.
result: function(ctx) { }

View file

@ -7,6 +7,9 @@
// step is invoked for every opcode that the VM executes.
step: function(log, db) { this.count++ },
// fault is invoked when the actual execution of an opcode fails.
fault: function(log, db) { },
// result is invoked when all the opcodes have been iterated over and returns
// the final result of the tracing.
result: function(ctx) { return this.count }

View file

@ -21,7 +21,6 @@ import (
"errors"
"fmt"
"math/big"
"reflect"
"sync/atomic"
"time"
"unsafe"
@ -38,6 +37,10 @@ const bigIntegerJS = `var bigInt=function(undefined){"use strict";var BASE=1e7,L
// makeSlice convert an unsafe memory pointer with the given type into a Go byte
// slice.
//
// Note, the returned slice uses the same memory area as the input arguments.
// If those are duktape stack items, popping them off **will** make the slice
// contents change.
func makeSlice(ptr unsafe.Pointer, size uint) []byte {
var sl = struct {
addr uintptr
@ -50,7 +53,7 @@ func makeSlice(ptr unsafe.Pointer, size uint) []byte {
// popSlice pops a buffer off the JavaScript stack and returns it as a slice.
func popSlice(ctx *duktape.Context) []byte {
blob := makeSlice(ctx.GetBuffer(-1))
blob := common.CopyBytes(makeSlice(ctx.GetBuffer(-1)))
ctx.Pop()
return blob
}
@ -294,7 +297,6 @@ type JavascriptTracer struct {
ctx map[string]interface{} // Transaction context gathered throughout execution
err error // Error, if one has occurred
result interface{} // Final result to return to the user
interrupt uint32 // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption
@ -319,28 +321,22 @@ func NewJavascriptTracer(code string) (*JavascriptTracer, error) {
}
// Set up builtins for this environment
tracer.vm.PushGlobalGoFunction("toHex", func(ctx *duktape.Context) int {
arg := makeSlice(ctx.GetBuffer(-1))
ctx.Pop()
ctx.PushString(hexutil.Encode(arg))
ctx.PushString(hexutil.Encode(popSlice(ctx)))
return 1
})
tracer.vm.PushGlobalGoFunction("toAddress", func(ctx *duktape.Context) int {
// Try interpreting the argument as a binary blob
ptr, size := ctx.GetBuffer(-1)
if ptr != nil {
ctx.Pop()
copy(makeSlice(ctx.PushFixedBuffer(20), 20), makeSlice(ptr, size))
return 1
var addr common.Address
if ptr, size := ctx.GetBuffer(-1); ptr != nil {
addr = common.BytesToAddress(makeSlice(ptr, size))
} else {
addr = common.HexToAddress(ctx.GetString(-1))
}
// Nope, not a binary blob, interpret as a hex string
addr := common.HexToAddress(ctx.GetString(-1))
ctx.Pop()
copy(makeSlice(ctx.PushFixedBuffer(20), 20), addr[:])
return 1
})
tracer.vm.PushGlobalGoFunction("isPrecompiled", func(ctx *duktape.Context) int {
_, ok := vm.PrecompiledContractsByzantium[common.BytesToAddress(makeSlice(ctx.GetBuffer(0)))]
_, ok := vm.PrecompiledContractsByzantium[common.BytesToAddress(popSlice(ctx))]
ctx.PushBoolean(ok)
return 1
})
@ -356,6 +352,11 @@ func NewJavascriptTracer(code string) (*JavascriptTracer, error) {
}
tracer.vm.Pop()
if !tracer.vm.GetPropString(tracer.tracerObject, "fault") {
return nil, fmt.Errorf("Trace object must expose a function fault()")
}
tracer.vm.Pop()
if !tracer.vm.GetPropString(tracer.tracerObject, "result") {
return nil, fmt.Errorf("Trace object must expose a function result()")
}
@ -402,7 +403,7 @@ func NewJavascriptTracer(code string) (*JavascriptTracer, error) {
}
return 1
})
tracer.vm.PutPropString(logObject, "error")
tracer.vm.PutPropString(logObject, "getError")
tracer.vm.PutPropString(tracer.stateObject, "log")
@ -420,7 +421,7 @@ func (jst *JavascriptTracer) Stop(err error) {
// call executes a method on a JS object, catching any errors, formatting and
// returning them as error objects.
func (jst *JavascriptTracer) call(method string, args ...string) (ret interface{}, err error) {
func (jst *JavascriptTracer) call(method string, args ...string) (json.RawMessage, error) {
// Execute the JavaScript call and return any error
jst.vm.PushString(method)
for _, arg := range args {
@ -434,17 +435,7 @@ func (jst *JavascriptTracer) call(method string, args ...string) (ret interface{
return nil, errors.New(err)
}
// No error occurred, extract return value and return
if jst.vm.IsUndefined(-1) {
return nil, nil
}
out := []byte(jst.vm.JsonEncode(-1))
for _, res := range []interface{}{new(bool), new(int), new(float64), new(string), new([]bool), new([]int), new([]float64), new([]string), new([]interface{}), new(map[string]interface{}), new(interface{})} {
if err := json.Unmarshal(out, res); err == nil {
return reflect.ValueOf(res).Elem().Interface(), nil
}
}
panic("failed to unmarshal result")
return json.RawMessage(jst.vm.JsonEncode(-1)), nil
}
func wrapError(context string, err error) error {
@ -508,6 +499,22 @@ func (jst *JavascriptTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode,
return nil
}
// CaptureFault implements the Tracer interface to trace an execution fault
// while running an opcode.
func (jst *JavascriptTracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error {
if jst.err == nil {
// Apart from the error, everything matches the previous invocation
jst.errorValue = new(string)
*jst.errorValue = err.Error()
_, err := jst.call("fault", "log", "db")
if err != nil {
jst.err = wrapError("fault", err)
}
}
return nil
}
// CaptureEnd is called after the call finishes to finalize the tracing.
func (jst *JavascriptTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error {
jst.ctx["output"] = output
@ -521,7 +528,7 @@ func (jst *JavascriptTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Du
}
// GetResult calls the Javascript 'result' function and returns its value, or any accumulated error
func (jst *JavascriptTracer) GetResult() (result interface{}, err error) {
func (jst *JavascriptTracer) GetResult() (json.RawMessage, error) {
// Transform the context into a JavaScript object and inject into the state
obj := jst.vm.PushObject()
@ -552,7 +559,7 @@ func (jst *JavascriptTracer) GetResult() (result interface{}, err error) {
jst.vm.PutPropString(jst.stateObject, "ctx")
// Finalize the trace and return the results
jst.result, err = jst.call("result", "ctx", "db")
result, err := jst.call("result", "ctx", "db")
if err != nil {
jst.err = wrapError("result", err)
}
@ -560,5 +567,5 @@ func (jst *JavascriptTracer) GetResult() (result interface{}, err error) {
jst.vm.DestroyHeap()
jst.vm.Destroy()
return jst.result, jst.err
return result, jst.err
}

View file

@ -17,9 +17,10 @@
package ethapi
import (
"bytes"
"encoding/json"
"errors"
"math/big"
"reflect"
"testing"
"time"
@ -42,7 +43,7 @@ func (account) ReturnGas(*big.Int) {}
func (account) SetCode(common.Hash, []byte) {}
func (account) ForEachStorage(cb func(key, value common.Hash) bool) {}
func runTrace(tracer *JavascriptTracer) (interface{}, error) {
func runTrace(tracer *JavascriptTracer) (json.RawMessage, error) {
env := vm.NewEVM(vm.Context{BlockNumber: big.NewInt(1)}, nil, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer})
contract := vm.NewContract(account{}, account{}, big.NewInt(0), 10000)
@ -56,7 +57,7 @@ func runTrace(tracer *JavascriptTracer) (interface{}, error) {
}
func TestTracing(t *testing.T) {
tracer, err := NewJavascriptTracer("{count: 0, step: function() { this.count += 1; }, result: function() { return this.count; }}")
tracer, err := NewJavascriptTracer("{count: 0, step: function() { this.count += 1; }, fault: function() {}, result: function() { return this.count; }}")
if err != nil {
t.Fatal(err)
}
@ -65,18 +66,13 @@ func TestTracing(t *testing.T) {
if err != nil {
t.Fatal(err)
}
value, ok := ret.(int)
if !ok {
t.Errorf("Expected return value to be float64, was %T", ret)
}
if value != 3 {
t.Errorf("Expected return value to be 3, got %v", value)
if !bytes.Equal(ret, []byte("3")) {
t.Errorf("Expected return value to be 3, got %s", string(ret))
}
}
func TestStack(t *testing.T) {
tracer, err := NewJavascriptTracer("{depths: [], step: function(log) { this.depths.push(log.stack.length()); }, result: function() { return this.depths; }}")
tracer, err := NewJavascriptTracer("{depths: [], step: function(log) { this.depths.push(log.stack.length()); }, fault: function() {}, result: function() { return this.depths; }}")
if err != nil {
t.Fatal(err)
}
@ -85,15 +81,13 @@ func TestStack(t *testing.T) {
if err != nil {
t.Fatal(err)
}
expected := []int{0, 1, 2}
if !reflect.DeepEqual(ret, expected) {
t.Errorf("Expected return value to be %#v, got %#v", expected, ret)
if !bytes.Equal(ret, []byte("[0,1,2]")) {
t.Errorf("Expected return value to be [0,1,2], got %s", string(ret))
}
}
func TestOpcodes(t *testing.T) {
tracer, err := NewJavascriptTracer("{opcodes: [], step: function(log) { this.opcodes.push(log.op.toString()); }, result: function() { return this.opcodes; }}")
tracer, err := NewJavascriptTracer("{opcodes: [], step: function(log) { this.opcodes.push(log.op.toString()); }, fault: function() {}, result: function() { return this.opcodes; }}")
if err != nil {
t.Fatal(err)
}
@ -102,10 +96,8 @@ func TestOpcodes(t *testing.T) {
if err != nil {
t.Fatal(err)
}
expected := []string{"PUSH1", "PUSH1", "STOP"}
if !reflect.DeepEqual(ret, expected) {
t.Errorf("Expected return value to be %#v, got %#v", expected, ret)
if !bytes.Equal(ret, []byte("[\"PUSH1\",\"PUSH1\",\"STOP\"]")) {
t.Errorf("Expected return value to be [\"PUSH1\",\"PUSH1\",\"STOP\"], got %s", string(ret))
}
}
@ -129,7 +121,7 @@ func TestHalt(t *testing.T) {
}
func TestHaltBetweenSteps(t *testing.T) {
tracer, err := NewJavascriptTracer("{step: function() {}, result: function() { return null; }}")
tracer, err := NewJavascriptTracer("{step: function() {}, fault: function() {}, result: function() { return null; }}")
if err != nil {
t.Fatal(err)
}