mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-17 17:33:47 +00:00
core, eth, internal: finalize call tracer and needed extras
This commit is contained in:
parent
087b79d3f0
commit
becec012ef
11 changed files with 234 additions and 112 deletions
|
|
@ -61,6 +61,11 @@ func (l *JSONLogger) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cos
|
||||||
return l.encoder.Encode(log)
|
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.
|
// CaptureEnd is triggered at end of execution.
|
||||||
func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error {
|
func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error {
|
||||||
type endLog struct {
|
type endLog struct {
|
||||||
|
|
|
||||||
|
|
@ -144,12 +144,17 @@ func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err er
|
||||||
)
|
)
|
||||||
contract.Input = input
|
contract.Input = input
|
||||||
|
|
||||||
defer func() {
|
if in.cfg.Debug {
|
||||||
if err != nil && !logged && in.cfg.Debug {
|
defer func() {
|
||||||
in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err)
|
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
|
// The Interpreter main run loop (contextual). This loop runs until either an
|
||||||
// explicit STOP, RETURN or SELFDESTRUCT is executed, an error occurred during
|
// 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
|
// the execution of one of the operations or until the done flag is set by the
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,7 @@ func (s *StructLog) OpName() string {
|
||||||
type Tracer interface {
|
type Tracer interface {
|
||||||
CaptureStart(from common.Address, to common.Address, call bool, input []byte, gas uint64, value *big.Int) error
|
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
|
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
|
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
|
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 {
|
func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error {
|
||||||
fmt.Printf("0x%x", output)
|
fmt.Printf("0x%x", output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
11
eth/api.go
11
eth/api.go
|
|
@ -24,14 +24,12 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"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/log"
|
||||||
"github.com/ethereum/go-ethereum/miner"
|
"github.com/ethereum/go-ethereum/miner"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
|
@ -40,8 +38,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultTraceTimeout = 5 * time.Second
|
|
||||||
|
|
||||||
// PublicEthereumAPI provides an API to access Ethereum full node-related
|
// PublicEthereumAPI provides an API to access Ethereum full node-related
|
||||||
// information.
|
// information.
|
||||||
type PublicEthereumAPI struct {
|
type PublicEthereumAPI struct {
|
||||||
|
|
@ -345,13 +341,6 @@ func NewPrivateDebugAPI(config *params.ChainConfig, eth *Ethereum) *PrivateDebug
|
||||||
return &PrivateDebugAPI{config: config, eth: eth}
|
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.
|
// 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) {
|
func (api *PrivateDebugAPI) Preimage(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
|
||||||
db := core.PreimageTable(api.eth.ChainDb())
|
db := core.PreimageTable(api.eth.ChainDb())
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,24 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"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.
|
// txTraceResult is the result of a single transaction trace.
|
||||||
type txTraceResult struct {
|
type txTraceResult struct {
|
||||||
Result interface{} `json:"result,omitempty"` // Trace results procuded by the tracer
|
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
|
// TraceChain returns the structured logs created during the execution of EVM
|
||||||
// between two blocks (excluding start) and returns them as a JSON object.
|
// 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
|
// Fetch the block interval that we want to trace
|
||||||
var from, to *types.Block
|
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
|
// traceChain configures a new tracer according to the provided configuration, and
|
||||||
// executes all the transactions contained within. The return value will be one item
|
// executes all the transactions contained within. The return value will be one item
|
||||||
// per transaction, dependent on the requestd tracer.
|
// 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
|
// Tracing a chain is a **long** operation, only do with subscriptions
|
||||||
notifier, supported := rpc.NotifierFromContext(ctx)
|
notifier, supported := rpc.NotifierFromContext(ctx)
|
||||||
if !supported {
|
if !supported {
|
||||||
|
|
@ -166,6 +184,8 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
|
||||||
sub := notifier.CreateSubscription()
|
sub := notifier.CreateSubscription()
|
||||||
|
|
||||||
// Ensure we have a valid starting state before doing any work
|
// Ensure we have a valid starting state before doing any work
|
||||||
|
origin := start.NumberU64()
|
||||||
|
|
||||||
memdb, _ := ethdb.NewMemDatabase()
|
memdb, _ := ethdb.NewMemDatabase()
|
||||||
db := &ephemeralDatabase{
|
db := &ephemeralDatabase{
|
||||||
diskdb: api.eth.ChainDb(),
|
diskdb: api.eth.ChainDb(),
|
||||||
|
|
@ -173,10 +193,28 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
|
||||||
}
|
}
|
||||||
statedb, err := state.New(start.Root(), state.NewDatabase(db))
|
statedb, err := state.New(start.Root(), state.NewDatabase(db))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
// 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
|
// 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()
|
threads := runtime.NumCPU()
|
||||||
if threads > blocks {
|
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)
|
res, err := api.traceTx(ctx, msg, vmctx, task.statedb, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
task.results[i] = &txTraceResult{Error: err.Error()}
|
task.results[i] = &txTraceResult{Error: err.Error()}
|
||||||
|
log.Warn("Tracing failed", "err", err)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
task.statedb.DeleteSuicides()
|
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
|
// Print progress logs if long enough time elapsed
|
||||||
if time.Since(logged) > 8*time.Second {
|
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()
|
logged = time.Now()
|
||||||
}
|
}
|
||||||
// Retrieve the next block to trace
|
// Retrieve the next block to trace
|
||||||
|
|
@ -263,16 +306,19 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
|
||||||
failed = fmt.Errorf("block #%d not found", number)
|
failed = fmt.Errorf("block #%d not found", number)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
// Send the block over to the concurrent tracers
|
// Send the block over to the concurrent tracers (if not in the rewind phase)
|
||||||
txs := block.Transactions()
|
if number > origin {
|
||||||
|
txs := block.Transactions()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case tasks <- &blockTraceTask{statedb: statedb.Copy(), block: block, results: make([]*txTraceResult, len(txs))}:
|
case tasks <- &blockTraceTask{statedb: statedb.Copy(), block: block, results: make([]*txTraceResult, len(txs))}:
|
||||||
case <-notifier.Closed():
|
case <-notifier.Closed():
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
traced += uint64(len(txs))
|
||||||
|
} else {
|
||||||
|
atomic.StoreUint64(&complete, number)
|
||||||
}
|
}
|
||||||
traced += uint64(len(txs))
|
|
||||||
|
|
||||||
// Generate the next state snapshot fast without tracing
|
// Generate the next state snapshot fast without tracing
|
||||||
_, _, _, err := api.eth.blockchain.Processor().Process(block, statedb, vm.Config{})
|
_, _, _, err := api.eth.blockchain.Processor().Process(block, statedb, vm.Config{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -314,7 +360,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
|
||||||
go func() {
|
go func() {
|
||||||
var (
|
var (
|
||||||
done = make(map[uint64]*blockTraceResult)
|
done = make(map[uint64]*blockTraceResult)
|
||||||
next = start.NumberU64() + 1
|
next = origin + 1
|
||||||
)
|
)
|
||||||
for res := range results {
|
for res := range results {
|
||||||
// Queue up next received result
|
// 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
|
// TraceBlockByNumber returns the structured logs created during the execution of
|
||||||
// EVM and returns them as a JSON object.
|
// 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
|
// Fetch the block that we want to trace
|
||||||
var block *types.Block
|
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
|
// TraceBlockByHash returns the structured logs created during the execution of
|
||||||
// EVM and returns them as a JSON object.
|
// 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)
|
block := api.eth.blockchain.GetBlockByHash(hash)
|
||||||
if block == nil {
|
if block == nil {
|
||||||
return nil, fmt.Errorf("block #%x not found", hash)
|
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
|
// TraceBlock returns the structured logs created during the execution of EVM
|
||||||
// and returns them as a JSON object.
|
// 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)
|
block := new(types.Block)
|
||||||
if err := rlp.Decode(bytes.NewReader(blob), block); err != nil {
|
if err := rlp.Decode(bytes.NewReader(blob), block); err != nil {
|
||||||
return nil, fmt.Errorf("could not decode block: %v", err)
|
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
|
// TraceBlockFromFile returns the structured logs created during the execution of
|
||||||
// EVM and returns them as a JSON object.
|
// 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)
|
blob, err := ioutil.ReadFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not read file: %v", err)
|
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
|
// traceBlock configures a new tracer according to the provided configuration, and
|
||||||
// executes all the transactions contained within. The return value will be one item
|
// executes all the transactions contained within. The return value will be one item
|
||||||
// per transaction, dependent on the requestd tracer.
|
// 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
|
// Create the parent state database
|
||||||
if err := api.eth.engine.VerifyHeader(api.eth.blockchain, block.Header(), true); err != nil {
|
if err := api.eth.engine.VerifyHeader(api.eth.blockchain, block.Header(), true); err != nil {
|
||||||
return nil, err
|
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
|
// TraceTransaction returns the structured logs created during the execution of EVM
|
||||||
// and returns them as a JSON object.
|
// 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
|
// Retrieve the transaction and assemble its EVM context
|
||||||
tx, blockHash, _, index := core.GetTransaction(api.eth.ChainDb(), hash)
|
tx, blockHash, _, index := core.GetTransaction(api.eth.ChainDb(), hash)
|
||||||
if tx == nil {
|
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
|
// traceTx configures a new tracer according to the provided configuration, and
|
||||||
// executes the given message in the provided environment. The return value will
|
// executes the given message in the provided environment. The return value will
|
||||||
// be tracer dependent.
|
// 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
|
// Assemble the structured logger or the JavaScript tracer
|
||||||
var (
|
var (
|
||||||
tracer vm.Tracer
|
tracer vm.Tracer
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -10,6 +10,12 @@
|
||||||
|
|
||||||
// step is invoked for every opcode that the VM executes.
|
// step is invoked for every opcode that the VM executes.
|
||||||
step: function(log, db) {
|
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
|
// We only care about system opcodes, faster if we pre-check once
|
||||||
var syscall = (log.op.toNumber() & 0xf0) == 0xf0;
|
var syscall = (log.op.toNumber() & 0xf0) == 0xf0;
|
||||||
if (syscall) {
|
if (syscall) {
|
||||||
|
|
@ -39,9 +45,7 @@
|
||||||
if (this.callstack[left-1].calls === undefined) {
|
if (this.callstack[left-1].calls === undefined) {
|
||||||
this.callstack[left-1].calls = [];
|
this.callstack[left-1].calls = [];
|
||||||
}
|
}
|
||||||
this.callstack[left-1].calls.push({
|
this.callstack[left-1].calls.push({type: op});
|
||||||
type: op,
|
|
||||||
});
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// If a new method invocation is being done, add to the call stack
|
// 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 an existing call is returning, pop off the call stack
|
||||||
if (syscall && op == 'REVERT') {
|
if (syscall && op == 'REVERT') {
|
||||||
this.callstack[this.callstack.length - 1].revert = true;
|
this.callstack[this.callstack.length - 1].error = "execution reverted";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (log.getDepth() == this.callstack.length - 1) {
|
if (log.getDepth() == this.callstack.length - 1) {
|
||||||
|
|
@ -105,21 +109,22 @@
|
||||||
if (!ret.equals(0)) {
|
if (!ret.equals(0)) {
|
||||||
call.to = toHex(toAddress(ret.toString(16)));
|
call.to = toHex(toAddress(ret.toString(16)));
|
||||||
call.output = toHex(db.getCode(toAddress(ret.toString(16))));
|
call.output = toHex(db.getCode(toAddress(ret.toString(16))));
|
||||||
} else {
|
} else if (call.error === undefined) {
|
||||||
call.revert = true;
|
call.error = "internal failure"; // TODO(karalabe): surface these faults somehow
|
||||||
}
|
}
|
||||||
} else {
|
} 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) {
|
if (call.gas !== undefined) {
|
||||||
call.gasUsed = '0x' + bigInt(call.gasIn - call.gasCost + call.gas - log.getGas()).toString(16);
|
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);
|
||||||
var ret = log.stack.peek(0);
|
if (!ret.equals(0)) {
|
||||||
if (!ret.equals(0)) {
|
call.output = toHex(log.memory.slice(call.outOff, call.outOff + call.outLen));
|
||||||
call.output = toHex(log.memory.slice(call.outOff, call.outOff + call.outLen));
|
} else if (call.error === undefined) {
|
||||||
} else {
|
call.error = "internal failure"; // TODO(karalabe): surface these faults somehow
|
||||||
call.revert = true;
|
}
|
||||||
}
|
}
|
||||||
|
delete call.gasIn; delete call.gasCost;
|
||||||
delete call.outOff; delete call.outLen;
|
delete call.outOff; delete call.outLen;
|
||||||
}
|
}
|
||||||
if (call.gas !== undefined) {
|
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
|
// result is invoked when all the opcodes have been iterated over and returns
|
||||||
// the final result of the tracing.
|
// the final result of the tracing.
|
||||||
result: function(ctx) {
|
result: function(ctx) {
|
||||||
|
|
@ -151,13 +187,44 @@
|
||||||
if (this.callstack[0].calls !== undefined) {
|
if (this.callstack[0].calls !== undefined) {
|
||||||
result.calls = this.callstack[0].calls;
|
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;
|
result.error = ctx.error;
|
||||||
|
}
|
||||||
|
if (result.error !== undefined) {
|
||||||
delete result.output;
|
delete result.output;
|
||||||
}
|
}
|
||||||
if (this.callstack[0].revert) {
|
return this.finalize(result);
|
||||||
result.revert = true;
|
},
|
||||||
|
|
||||||
|
// 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@
|
||||||
// step is invoked for every opcode that the VM executes.
|
// step is invoked for every opcode that the VM executes.
|
||||||
step: function(log, db) { },
|
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
|
// result is invoked when all the opcodes have been iterated over and returns
|
||||||
// the final result of the tracing.
|
// the final result of the tracing.
|
||||||
result: function(ctx) { }
|
result: function(ctx) { }
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@
|
||||||
// step is invoked for every opcode that the VM executes.
|
// step is invoked for every opcode that the VM executes.
|
||||||
step: function(log, db) { this.count++ },
|
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
|
// result is invoked when all the opcodes have been iterated over and returns
|
||||||
// the final result of the tracing.
|
// the final result of the tracing.
|
||||||
result: function(ctx) { return this.count }
|
result: function(ctx) { return this.count }
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"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
|
// makeSlice convert an unsafe memory pointer with the given type into a Go byte
|
||||||
// slice.
|
// 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 {
|
func makeSlice(ptr unsafe.Pointer, size uint) []byte {
|
||||||
var sl = struct {
|
var sl = struct {
|
||||||
addr uintptr
|
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.
|
// popSlice pops a buffer off the JavaScript stack and returns it as a slice.
|
||||||
func popSlice(ctx *duktape.Context) []byte {
|
func popSlice(ctx *duktape.Context) []byte {
|
||||||
blob := makeSlice(ctx.GetBuffer(-1))
|
blob := common.CopyBytes(makeSlice(ctx.GetBuffer(-1)))
|
||||||
ctx.Pop()
|
ctx.Pop()
|
||||||
return blob
|
return blob
|
||||||
}
|
}
|
||||||
|
|
@ -292,9 +295,8 @@ type JavascriptTracer struct {
|
||||||
depthValue *uint // Swappable depth value wrapped by a log accessor
|
depthValue *uint // Swappable depth value wrapped by a log accessor
|
||||||
errorValue *string // Swappable error value wrapped by a log accessor
|
errorValue *string // Swappable error value wrapped by a log accessor
|
||||||
|
|
||||||
ctx map[string]interface{} // Transaction context gathered throughout execution
|
ctx map[string]interface{} // Transaction context gathered throughout execution
|
||||||
err error // Error, if one has occurred
|
err error // Error, if one has occurred
|
||||||
result interface{} // Final result to return to the user
|
|
||||||
|
|
||||||
interrupt uint32 // Atomic flag to signal execution interruption
|
interrupt uint32 // Atomic flag to signal execution interruption
|
||||||
reason error // Textual reason for the interruption
|
reason error // Textual reason for the interruption
|
||||||
|
|
@ -319,28 +321,22 @@ func NewJavascriptTracer(code string) (*JavascriptTracer, error) {
|
||||||
}
|
}
|
||||||
// Set up builtins for this environment
|
// Set up builtins for this environment
|
||||||
tracer.vm.PushGlobalGoFunction("toHex", func(ctx *duktape.Context) int {
|
tracer.vm.PushGlobalGoFunction("toHex", func(ctx *duktape.Context) int {
|
||||||
arg := makeSlice(ctx.GetBuffer(-1))
|
ctx.PushString(hexutil.Encode(popSlice(ctx)))
|
||||||
ctx.Pop()
|
|
||||||
|
|
||||||
ctx.PushString(hexutil.Encode(arg))
|
|
||||||
return 1
|
return 1
|
||||||
})
|
})
|
||||||
tracer.vm.PushGlobalGoFunction("toAddress", func(ctx *duktape.Context) int {
|
tracer.vm.PushGlobalGoFunction("toAddress", func(ctx *duktape.Context) int {
|
||||||
// Try interpreting the argument as a binary blob
|
var addr common.Address
|
||||||
ptr, size := ctx.GetBuffer(-1)
|
if ptr, size := ctx.GetBuffer(-1); ptr != nil {
|
||||||
if ptr != nil {
|
addr = common.BytesToAddress(makeSlice(ptr, size))
|
||||||
ctx.Pop()
|
} else {
|
||||||
copy(makeSlice(ctx.PushFixedBuffer(20), 20), makeSlice(ptr, size))
|
addr = common.HexToAddress(ctx.GetString(-1))
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
// Nope, not a binary blob, interpret as a hex string
|
|
||||||
addr := common.HexToAddress(ctx.GetString(-1))
|
|
||||||
ctx.Pop()
|
ctx.Pop()
|
||||||
copy(makeSlice(ctx.PushFixedBuffer(20), 20), addr[:])
|
copy(makeSlice(ctx.PushFixedBuffer(20), 20), addr[:])
|
||||||
return 1
|
return 1
|
||||||
})
|
})
|
||||||
tracer.vm.PushGlobalGoFunction("isPrecompiled", func(ctx *duktape.Context) int {
|
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)
|
ctx.PushBoolean(ok)
|
||||||
return 1
|
return 1
|
||||||
})
|
})
|
||||||
|
|
@ -356,6 +352,11 @@ func NewJavascriptTracer(code string) (*JavascriptTracer, error) {
|
||||||
}
|
}
|
||||||
tracer.vm.Pop()
|
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") {
|
if !tracer.vm.GetPropString(tracer.tracerObject, "result") {
|
||||||
return nil, fmt.Errorf("Trace object must expose a function result()")
|
return nil, fmt.Errorf("Trace object must expose a function result()")
|
||||||
}
|
}
|
||||||
|
|
@ -402,7 +403,7 @@ func NewJavascriptTracer(code string) (*JavascriptTracer, error) {
|
||||||
}
|
}
|
||||||
return 1
|
return 1
|
||||||
})
|
})
|
||||||
tracer.vm.PutPropString(logObject, "error")
|
tracer.vm.PutPropString(logObject, "getError")
|
||||||
|
|
||||||
tracer.vm.PutPropString(tracer.stateObject, "log")
|
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
|
// call executes a method on a JS object, catching any errors, formatting and
|
||||||
// returning them as error objects.
|
// 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
|
// Execute the JavaScript call and return any error
|
||||||
jst.vm.PushString(method)
|
jst.vm.PushString(method)
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
|
|
@ -434,17 +435,7 @@ func (jst *JavascriptTracer) call(method string, args ...string) (ret interface{
|
||||||
return nil, errors.New(err)
|
return nil, errors.New(err)
|
||||||
}
|
}
|
||||||
// No error occurred, extract return value and return
|
// No error occurred, extract return value and return
|
||||||
if jst.vm.IsUndefined(-1) {
|
return json.RawMessage(jst.vm.JsonEncode(-1)), nil
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func wrapError(context string, err error) error {
|
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
|
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.
|
// 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 {
|
func (jst *JavascriptTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error {
|
||||||
jst.ctx["output"] = output
|
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
|
// 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
|
// Transform the context into a JavaScript object and inject into the state
|
||||||
obj := jst.vm.PushObject()
|
obj := jst.vm.PushObject()
|
||||||
|
|
||||||
|
|
@ -552,7 +559,7 @@ func (jst *JavascriptTracer) GetResult() (result interface{}, err error) {
|
||||||
jst.vm.PutPropString(jst.stateObject, "ctx")
|
jst.vm.PutPropString(jst.stateObject, "ctx")
|
||||||
|
|
||||||
// Finalize the trace and return the results
|
// 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 {
|
if err != nil {
|
||||||
jst.err = wrapError("result", err)
|
jst.err = wrapError("result", err)
|
||||||
}
|
}
|
||||||
|
|
@ -560,5 +567,5 @@ func (jst *JavascriptTracer) GetResult() (result interface{}, err error) {
|
||||||
jst.vm.DestroyHeap()
|
jst.vm.DestroyHeap()
|
||||||
jst.vm.Destroy()
|
jst.vm.Destroy()
|
||||||
|
|
||||||
return jst.result, jst.err
|
return result, jst.err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,10 @@
|
||||||
package ethapi
|
package ethapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -42,7 +43,7 @@ func (account) ReturnGas(*big.Int) {}
|
||||||
func (account) SetCode(common.Hash, []byte) {}
|
func (account) SetCode(common.Hash, []byte) {}
|
||||||
func (account) ForEachStorage(cb func(key, value common.Hash) bool) {}
|
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})
|
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)
|
contract := vm.NewContract(account{}, account{}, big.NewInt(0), 10000)
|
||||||
|
|
@ -56,7 +57,7 @@ func runTrace(tracer *JavascriptTracer) (interface{}, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTracing(t *testing.T) {
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -65,18 +66,13 @@ func TestTracing(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
if !bytes.Equal(ret, []byte("3")) {
|
||||||
value, ok := ret.(int)
|
t.Errorf("Expected return value to be 3, got %s", string(ret))
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStack(t *testing.T) {
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -85,15 +81,13 @@ func TestStack(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
if !bytes.Equal(ret, []byte("[0,1,2]")) {
|
||||||
expected := []int{0, 1, 2}
|
t.Errorf("Expected return value to be [0,1,2], got %s", string(ret))
|
||||||
if !reflect.DeepEqual(ret, expected) {
|
|
||||||
t.Errorf("Expected return value to be %#v, got %#v", expected, ret)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOpcodes(t *testing.T) {
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -102,10 +96,8 @@ func TestOpcodes(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
if !bytes.Equal(ret, []byte("[\"PUSH1\",\"PUSH1\",\"STOP\"]")) {
|
||||||
expected := []string{"PUSH1", "PUSH1", "STOP"}
|
t.Errorf("Expected return value to be [\"PUSH1\",\"PUSH1\",\"STOP\"], got %s", string(ret))
|
||||||
if !reflect.DeepEqual(ret, expected) {
|
|
||||||
t.Errorf("Expected return value to be %#v, got %#v", expected, ret)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -129,7 +121,7 @@ func TestHalt(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHaltBetweenSteps(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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue