diff --git a/cmd/evm/json_logger.go b/cmd/evm/json_logger.go
index eb7b0c4660..777f2ac8d2 100644
--- a/cmd/evm/json_logger.go
+++ b/cmd/evm/json_logger.go
@@ -19,6 +19,7 @@ package main
import (
"encoding/json"
"io"
+ "math/big"
"time"
"github.com/ethereum/go-ethereum/common"
@@ -35,6 +36,10 @@ func NewJSONLogger(cfg *vm.LogConfig, writer io.Writer) *JSONLogger {
return &JSONLogger{json.NewEncoder(writer), cfg}
}
+func (l *JSONLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error {
+ return nil
+}
+
// CaptureState outputs state information on the logger.
func (l *JSONLogger) CaptureState(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 {
log := vm.StructLog{
diff --git a/core/vm/evm.go b/core/vm/evm.go
index cbb5a03ce7..d56a3d57f5 100644
--- a/core/vm/evm.go
+++ b/core/vm/evm.go
@@ -19,6 +19,7 @@ package vm
import (
"math/big"
"sync/atomic"
+ "time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
@@ -171,7 +172,13 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
contract := NewContract(caller, to, value, gas)
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
+ if evm.vmConfig.Debug && evm.depth == 0 {
+ evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)
+ }
+ start := time.Now()
+
ret, err = run(evm, contract, input)
+
// When an error was returned by the EVM or when setting the creation code
// above we revert to the snapshot and consume any gas remaining. Additionally
// when we're in homestead this also counts for code storage gas errors.
@@ -181,6 +188,9 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
contract.UseGas(contract.Gas)
}
}
+ if evm.vmConfig.Debug && evm.depth == 0 {
+ evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
+ }
return ret, contract.Gas, err
}
@@ -338,7 +348,14 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I
if evm.vmConfig.NoRecursion && evm.depth > 0 {
return nil, contractAddr, gas, nil
}
+
+ if evm.vmConfig.Debug && evm.depth == 0 {
+ evm.vmConfig.Tracer.CaptureStart(caller.Address(), contractAddr, true, code, gas, value)
+ }
+ start := time.Now()
+
ret, err = run(evm, contract, nil)
+
// check whether the max code size has been exceeded
maxCodeSizeExceeded := evm.ChainConfig().IsEIP158(evm.BlockNumber) && len(ret) > params.MaxCodeSize
// if the contract creation ran successfully and no errors were returned
@@ -367,6 +384,9 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I
if maxCodeSizeExceeded && err == nil {
err = errMaxCodeSizeExceeded
}
+ if evm.vmConfig.Debug && evm.depth == 0 {
+ evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
+ }
return ret, contractAddr, contract.Gas, err
}
diff --git a/core/vm/logger.go b/core/vm/logger.go
index 75309da921..5465beeb7a 100644
--- a/core/vm/logger.go
+++ b/core/vm/logger.go
@@ -84,6 +84,7 @@ func (s *StructLog) OpName() string {
// Note that reference types are actual VM data structures; make copies
// if you need to retain them beyond the current call.
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
CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error
}
@@ -111,6 +112,10 @@ func NewStructLogger(cfg *LogConfig) *StructLogger {
return logger
}
+func (l *StructLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error {
+ return nil
+}
+
// CaptureState logs a new structured log message and pushes it out to the environment
//
// CaptureState also tracks SSTORE ops to track dirty values.
diff --git a/eth/api.go b/eth/api.go
index c748f75de4..caed002611 100644
--- a/eth/api.go
+++ b/eth/api.go
@@ -34,6 +34,7 @@ import (
"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/eth/tracers"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/miner"
@@ -488,21 +489,23 @@ func (t *timeoutError) Error() string {
// 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, txHash common.Hash, config *TraceArgs) (interface{}, error) {
- var tracer vm.Tracer
+ var (
+ tracer vm.Tracer
+ err error
+ )
if config != nil && config.Tracer != nil {
timeout := defaultTraceTimeout
if config.Timeout != nil {
- var err error
if timeout, err = time.ParseDuration(*config.Timeout); err != nil {
return nil, err
}
}
-
- var err error
+ if tracer, ok := tracers.Tracer(*config.Tracer); ok {
+ *config.Tracer = tracer
+ }
if tracer, err = ethapi.NewJavascriptTracer(*config.Tracer); err != nil {
return nil, err
}
-
// Handle timeouts and RPC cancellations
deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
go func() {
diff --git a/eth/tracers/internal/tracers/assets.go b/eth/tracers/internal/tracers/assets.go
new file mode 100644
index 0000000000..b15bb0e5c5
--- /dev/null
+++ b/eth/tracers/internal/tracers/assets.go
@@ -0,0 +1,281 @@
+// Code generated by go-bindata.
+// sources:
+// call_tracer.js
+// noop_tracer.js
+// opcount_tracer.js
+// DO NOT EDIT!
+
+package tracers
+
+import (
+ "bytes"
+ "compress/gzip"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
+)
+
+func bindataRead(data []byte, name string) ([]byte, error) {
+ gz, err := gzip.NewReader(bytes.NewBuffer(data))
+ if err != nil {
+ return nil, fmt.Errorf("Read %q: %v", name, err)
+ }
+
+ var buf bytes.Buffer
+ _, err = io.Copy(&buf, gz)
+ clErr := gz.Close()
+
+ if err != nil {
+ return nil, fmt.Errorf("Read %q: %v", name, err)
+ }
+ if clErr != nil {
+ return nil, err
+ }
+
+ return buf.Bytes(), nil
+}
+
+type asset struct {
+ bytes []byte
+ info os.FileInfo
+}
+
+type bindataFileInfo struct {
+ name string
+ size int64
+ mode os.FileMode
+ modTime time.Time
+}
+
+func (fi bindataFileInfo) Name() string {
+ return fi.name
+}
+func (fi bindataFileInfo) Size() int64 {
+ return fi.size
+}
+func (fi bindataFileInfo) Mode() os.FileMode {
+ return fi.mode
+}
+func (fi bindataFileInfo) ModTime() time.Time {
+ return fi.modTime
+}
+func (fi bindataFileInfo) IsDir() bool {
+ return false
+}
+func (fi bindataFileInfo) Sys() interface{} {
+ return nil
+}
+
+var _call_tracerJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x9c\x57\xcd\x6e\xdc\x48\x0e\x3e\xb7\x9e\x82\x93\x43\xdc\x8d\x74\xd4\x76\x92\xcd\xa1\x0d\xcd\xc2\xbb\x31\x76\x03\x78\xe3\x41\xc6\x33\x73\x08\x7c\xa8\x96\x28\x75\x8d\xa5\x2a\x6d\x15\xd5\x3f\x18\xf8\xdd\x17\x64\x95\x64\xb5\xec\x09\x16\xe3\x93\x9a\x45\xb2\xf8\xf3\xf1\x63\x79\xb5\x82\x5c\xd5\xf5\x9d\x53\x39\x3a\xd0\x1e\x14\x94\x5d\x5d\xc3\xa6\xb6\x7b\x03\xe4\x94\xf1\x2a\x27\x6d\xe5\x9b\x55\x68\xab\x08\xf0\xc0\xbf\xc8\x83\x32\x05\x38\x6c\xad\xe3\xef\xba\x4e\x56\x2b\xa0\x2d\x82\x36\x84\xce\xa8\x5a\x7c\x7b\x68\x54\x81\xb0\x39\x82\x1a\x3b\x5c\x82\xaa\xad\xa9\x60\xaf\x69\x0b\xca\x1c\xa1\xf3\x58\x76\x35\x68\x53\x5a\xd7\x28\x56\x49\x93\x3f\x92\xd9\x6a\x05\xda\xec\x6c\x2e\x12\x1f\x42\x34\xe8\x09\x0b\x28\x14\x29\xf0\xe4\xba\x9c\x3a\x87\x90\x5b\x43\x4a\x1b\x6d\x2a\x8e\x65\x12\x88\x35\x12\xb2\xf8\x0b\x51\xe1\x01\xf3\x8e\xdd\x4c\x23\x4b\x93\xd9\xe8\xc6\x35\x7c\xbb\x5f\x26\x4f\x76\xa4\xf2\x07\x8e\x82\xdd\xe7\x9d\x73\x68\x08\x1c\xe6\x9d\xf3\x7a\x87\xa2\x02\x41\xc7\x96\xa2\x73\xfd\xeb\x7f\xe2\x55\xc1\xf5\xe0\x64\xe4\xb8\x40\x9f\xa3\x29\xb0\x90\x2a\x3f\x78\xd8\x6f\x91\xb6\xe8\x60\x8f\x67\x3b\x84\xdf\x3b\x4f\x23\x9d\xd2\xd9\x06\x94\x01\xdb\x11\x37\x64\xd4\x23\x6d\xc8\x8a\x43\xc5\xdf\x06\x9d\xc4\x93\x26\xb3\xc1\x78\x0d\xa5\xaa\x3d\xc6\x7b\x3d\x61\xcb\xb9\x70\xba\x0f\xec\xd9\x3a\xc0\x1d\xba\x23\xd8\x36\xb7\x05\x86\x6e\x73\x16\x43\x12\xe8\xd3\x64\xc6\x76\x6b\x28\x3b\x23\xd7\xce\x6b\x5b\x2d\xa1\xd8\x2c\xe0\x8f\x64\xc6\x6e\x7f\x43\xb0\xa6\x3e\x42\xae\x1c\x82\xda\xd8\x8e\xc0\x1f\x3d\x61\x13\xdd\xfa\x25\x94\xca\x73\xf0\xba\x84\x3d\x42\xeb\xf0\x6d\xbe\x45\xae\x99\xc9\x31\x99\xcd\x76\xca\xb1\x85\x14\x33\x03\xf6\x9f\xda\x36\x25\xfb\xa5\x6b\x36\xe8\xe6\x0b\x78\x0d\xe7\x87\xf2\x7c\x01\x59\x26\x1f\x97\xc9\x6c\xa6\x4b\x98\x47\x9b\x10\x88\x78\xb1\x2d\x64\x30\xd8\xff\x4c\x4e\x9b\x6a\xbe\x60\xfd\xc7\x10\xeb\xe7\x52\xf0\xb4\x87\x06\x69\x6b\x8b\x11\xd6\xb8\x30\x1b\x64\x34\x15\xd6\xe0\x12\x54\x51\x00\xd9\xd0\xf7\xa1\xcb\xa7\x17\xc3\xeb\xd7\x7c\xe3\x0f\x19\xbc\xfa\x7a\x7d\xf7\xcb\xd7\x2f\xaf\x4e\x24\xbf\x5e\x7f\xbd\x7b\x15\x83\x5b\xad\xe0\xe7\x07\xdd\x0a\xf2\x25\x7f\xdb\xb4\xba\xc6\x31\xd6\x97\x40\x5b\xeb\x11\xb8\x8a\x02\x81\x52\x99\xbc\x6f\x8d\xef\x33\x24\x0b\x19\x90\xbd\x2a\x0a\x87\xde\x4b\xad\x24\xb2\xb4\x45\x7c\x98\x5f\x2c\xd2\x7f\x1c\x09\xfd\x7c\x21\x49\x4b\xb0\xda\xff\xe4\x30\xde\x57\xcc\xc9\x2e\x62\x48\x33\x87\xd4\x39\xc3\x9f\x8f\x31\xc4\x5b\x46\xe1\x5e\x7b\x84\x4a\x09\x20\xbd\x6d\x26\xe3\x0d\x05\x92\xd2\xf5\x10\x8f\x2d\x4b\xee\x19\x17\x3e\x83\xb3\x4f\xd7\x37\xd7\xff\xba\xba\xbb\xfe\xe7\xd5\xcd\xcd\x19\xfc\x1d\xce\x61\x0d\x17\x8b\xcb\xa4\xd7\xd6\xe6\x56\xf4\x27\x61\xbf\x83\x37\xec\x68\x91\x7e\x36\xf4\xf1\x43\x68\x58\xd4\xbf\x36\x05\x64\xd1\xee\xcd\xd4\xee\xfd\x33\xbb\x98\xc8\x95\xf7\xd8\x6c\x6a\x7c\x4e\x4f\x91\xbf\x84\xca\x3c\x59\x87\x32\x05\x5c\x9e\x1a\xb9\x0d\xfd\xcd\x11\x8c\xa1\x52\x74\x6c\x71\x0d\x00\x60\xdb\xa5\x08\x78\x26\x45\xc0\x01\xa9\x3c\xb7\x9d\xa1\x70\x42\x56\xe4\x00\x64\x83\x40\x9b\xb6\xa3\xb5\x08\xfe\x8d\x07\x69\x58\x83\x8d\x75\xc7\xd4\xd7\x3a\xc7\xb9\xa4\xb6\x0c\x99\x2e\x16\xc1\xa6\x52\xfe\x93\x2e\xcb\xb5\xb8\xaf\x94\x87\xb7\xf2\x95\x5b\x1f\x6f\xb1\x1d\xdd\xf2\xf9\xb4\x20\x1f\x26\x05\x19\xb4\x6f\xd0\x3c\xd7\xfe\xdb\x44\x5b\xa0\x30\xe0\x26\xe0\xf8\xb4\xa5\x3d\x74\x84\x67\x76\xaa\xee\x10\x32\x38\x3b\x3f\x9c\x3d\xef\xcd\xbb\x45\x7a\x87\x07\x9a\x5f\x7c\x0c\xed\x14\x8c\xd1\x56\xfb\x74\xa0\xc4\xb4\xed\xfc\x76\x2e\x23\x7c\x39\x9c\x3e\x11\x5f\x06\xe4\x3a\xe6\x87\x88\xd4\xd3\x31\x7e\x91\x2b\x99\x11\x4f\xd9\x70\x09\x0e\xc9\x69\xdc\x21\x68\x3a\xf3\xe2\x92\xf7\x85\xdd\x2b\x93\x63\x0a\xbf\x61\xf0\x68\x10\x65\xde\xe3\xae\x63\xaa\x12\xe2\xe5\x7d\xa5\xcd\x13\x0d\x28\x59\x05\x0e\xa1\x51\x47\xd8\x20\xb3\xe2\xc3\x11\xb8\x47\xc5\xd1\xa8\x46\xe7\x3e\xf8\x93\x3d\xe7\xb0\x52\x4e\xdc\x3a\xfc\x6f\x17\xb6\x18\x03\x4f\xe5\xd4\xa9\xba\x3e\x42\xa5\x77\x68\xc4\x7a\xfe\xee\xfd\xf9\x39\x78\xd2\x2d\x9a\x62\x09\x1f\xdf\xaf\x3e\x7e\x00\xd7\xd5\xb8\x48\x23\xe9\x9c\x56\x27\x76\x82\x0f\xb8\xf0\x05\xb6\xb4\x85\x1f\x61\x52\xe0\x1a\x4d\x45\xdb\xbe\x6b\xa7\x87\xdf\x5e\xd4\x85\xb7\x70\x71\x2f\x98\xcb\x7a\xf4\x85\xf6\x01\xd6\x1e\xff\xa2\x23\xce\xed\x12\x56\x2b\xb8\xbb\xfd\x74\x0b\xf3\x07\xe5\x54\xad\x36\xb8\x58\xc3\xb5\x6b\xd2\x34\x3d\xc5\xc7\x18\x01\xb2\xbe\x26\xfc\x6d\x00\x0f\xda\x13\x53\xb5\xb4\x44\x7b\x08\x00\xd1\xa6\x5a\x42\x6b\x5b\x21\xa5\xff\x83\xb8\x99\xb1\x02\x4d\xf7\xc0\x16\x5c\x3b\xde\x89\x14\xe1\x77\xf9\x1c\x7f\xa7\x35\xcf\xb2\xef\x16\x7d\xb5\x82\x9f\x46\x11\xd5\xca\x53\x04\x92\x29\xa0\xc2\xb0\x6e\x87\x07\x03\x38\xf4\x5d\x4d\x7e\xc2\x42\xd3\xb1\xb1\x6d\xcf\x75\x12\x70\xa5\xfc\x2f\x5e\xca\x15\x47\x71\xa3\xab\xf4\x0b\xee\x3f\x1b\x9a\xf7\x0a\x91\x42\xf8\xeb\x0d\xf4\x42\x26\x99\xc9\x98\x0e\xfa\xfc\xf7\x3d\x87\x13\xbb\x02\x6b\x24\x3c\xf1\x3c\x8a\xd0\x76\xd4\x76\x52\xd2\x97\x49\xb0\x57\x12\x2a\x1c\xfd\xe8\x63\x0d\x0c\xb6\x78\x7e\x55\x50\x7b\x49\x7c\x83\xe6\x69\x1d\xb4\x3c\x55\xfd\x3a\x1f\xed\xfc\x5a\x7b\x19\x75\xb2\x6d\x63\x3d\xf5\x65\xaf\xb1\xa4\xe7\x65\x0f\x7d\x0d\x4e\x05\x03\xa2\x95\xc1\xf9\xc9\x7c\x8d\x36\xfa\x94\xdf\x5e\xd8\xb8\x57\x6d\xab\xf8\x4d\x59\x1f\x41\x13\xec\xd5\xe8\xb9\x1b\xd8\x4b\x9b\xdf\x91\xe9\xc8\xc4\xe0\x5b\x87\x3b\x6d\x3b\x0f\xd6\x60\x1f\xc8\x64\x1c\x39\xae\xb7\x17\xf7\x41\x02\x59\x96\x41\x67\x0a\x2c\xb5\x19\x58\x63\x3a\xc0\x13\x0b\xf8\x76\xff\x67\x94\x7d\xaa\x3a\x49\xf0\x31\x99\x3d\xc6\xa7\x66\xc0\xf1\xf8\xb1\xb9\xdf\xa2\x19\x1e\xea\xf1\x51\x03\x5b\xb5\x43\xd8\x20\x1a\xd0\x84\x4e\x71\xda\x76\x87\x2e\xfe\xa3\xc1\xc5\xf2\xe2\x8e\x6d\x4a\xcd\x3b\x3c\x3a\x8e\xaf\x6d\xe6\x6a\x6d\xaa\x34\x99\x05\xf9\xe8\x95\x9a\xd3\x21\x64\xcb\x0d\x8d\x56\x71\x9f\x0f\xeb\x3c\xa7\x43\xca\x3f\x64\x4d\x0e\x3b\xfd\xe9\x75\xc5\xe7\x2c\x0e\x7b\x74\xb4\xda\xc7\x0a\x64\xc3\xb1\xac\x43\xd6\x88\x33\xc3\x67\x22\x1b\x06\x45\xd4\x2a\xe5\x83\x9b\x17\x46\x8b\x0e\xa7\x93\xd5\x1b\xf0\x74\xaf\xff\xdc\x80\x8f\x27\x46\x93\x57\x07\x2b\x8a\x28\x9c\x86\x71\x5c\x8f\x4f\x83\x28\x26\xaa\x9b\x51\x7d\x74\x23\xf5\x79\xbc\x1c\xef\xa1\x31\xca\x23\xdf\xff\x38\x0c\x42\xa8\xf6\x80\xa6\xa9\xc1\x98\x47\xf9\x06\x74\xce\x3a\xf8\xe1\x05\x9c\x46\x47\x41\x21\x83\x41\xb9\xf7\x10\x20\x12\xbb\x7b\x99\xcc\x1e\x93\xc7\xe4\x7f\x01\x00\x00\xff\xff\x91\x76\xe8\xd1\xe3\x0e\x00\x00")
+
+func call_tracerJsBytes() ([]byte, error) {
+ return bindataRead(
+ _call_tracerJs,
+ "call_tracer.js",
+ )
+}
+
+func call_tracerJs() (*asset, error) {
+ bytes, err := call_tracerJsBytes()
+ if err != nil {
+ return nil, err
+ }
+
+ info := bindataFileInfo{name: "call_tracer.js", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
+ a := &asset{bytes: bytes, info: info}
+ return a, nil
+}
+
+var _noop_tracerJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x4c\xcf\xc1\x6a\x23\x31\x10\x04\xd0\xb3\xf5\x15\x75\xdc\x05\xe3\xb9\xef\x27\x2c\xec\x69\x43\xee\x2d\x4d\x8d\x47\x8e\xa2\x9e\xb4\x7a\x26\x0e\xc6\xff\x1e\x46\x26\xe0\x9b\x68\xa8\x57\xa5\x61\x40\x55\x5d\x5e\x4c\x12\x0d\xb9\xe1\xb2\x36\x87\xcf\x44\x14\x63\xd4\x4a\x44\xcd\x85\xb6\x14\x71\x22\xe9\x48\x18\x3f\xd6\x6c\x1c\x31\x99\xbe\x43\xf0\x57\x36\xf9\x9f\x2c\x2f\x1e\x86\x01\x1a\x2f\x4c\x0e\x57\x44\x62\x6d\x12\x0b\x21\x0d\x02\x37\xa9\x4d\x92\x67\xad\xfb\x3b\xd1\x4e\xe1\x16\x0e\xc3\x80\xe6\x5c\xf6\xee\x5c\x37\x7d\xdb\x5d\x35\x70\xa3\x7d\x41\x97\xde\xe8\xb3\x3c\x46\xbd\xfe\x03\xaf\x4c\xab\xb3\x9d\xc2\x61\xcf\xfd\xc1\xb4\xd6\x8e\xfe\x2a\x7a\x3e\x62\x8c\xbf\x71\xc3\xfd\x18\xba\x6c\x6c\x6b\xf1\x67\xfb\x73\x66\x85\x94\xd2\xb9\x07\xdf\x30\xcb\x46\x44\xb2\x22\x3b\x4d\x9c\x23\x74\xa3\x41\xea\x08\xa3\xaf\x56\x5b\xe7\xf6\xcc\x94\xab\x94\x1f\x58\xa7\x7e\xdb\xbf\x93\xeb\xf9\x14\x0e\x8f\xfb\xd3\xa8\xe4\xd7\x3e\x28\xdc\xc3\x77\x00\x00\x00\xff\xff\x8f\x9c\x5f\x55\x6c\x01\x00\x00")
+
+func noop_tracerJsBytes() ([]byte, error) {
+ return bindataRead(
+ _noop_tracerJs,
+ "noop_tracer.js",
+ )
+}
+
+func noop_tracerJs() (*asset, error) {
+ bytes, err := noop_tracerJsBytes()
+ if err != nil {
+ return nil, err
+ }
+
+ info := bindataFileInfo{name: "noop_tracer.js", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
+ a := &asset{bytes: bytes, info: info}
+ return a, nil
+}
+
+var _opcount_tracerJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x5c\x90\xb1\x6e\xeb\x30\x0c\x45\x67\xeb\x2b\xee\xf8\x1e\x12\xd8\x9d\xb3\x77\xcc\x56\x64\x97\x6d\x3a\x56\xe3\x50\x01\x49\xb9\x09\x82\xfc\x7b\x21\xb9\x2e\x8c\x8e\x22\xc8\x73\xee\x55\xd3\x20\xde\xba\x98\xd8\x3e\xc4\x77\x24\x08\x0a\x0f\xf5\xd7\xdb\x44\xb0\x65\x64\xa3\x37\x7c\x26\x35\x94\x45\x85\x8d\x04\x4e\xd7\x96\x04\x71\x40\x60\x35\x49\x9d\x85\xc8\xea\x9a\x06\x74\xa7\x2e\x19\xf5\x68\x1f\x65\xf3\xfd\x74\x44\x4b\x43\x14\x2a\x4f\x13\xcf\xea\xcb\x3a\x8c\xe4\x1a\xd8\x1b\xf5\xb5\x7b\xba\xaa\x69\x16\x43\x11\x5f\xfe\x7a\x32\x67\xeb\xfa\x15\xd5\xae\x2a\x67\x07\xbc\xed\x5d\xa1\xa8\xd1\x2d\x37\x09\x3c\xc7\x0b\xf5\x18\xa2\x80\x66\x92\x47\x29\xdb\xd3\x52\x29\xe3\x4f\xc7\x15\xa3\xb5\xab\xf2\xdd\x01\x43\xe2\x62\xf8\x37\xc5\xf3\x1e\x7d\xfb\x1f\x4f\xd8\x18\xb4\x2e\x96\xdd\x0e\xaf\x1f\x8d\x90\xa6\xc9\xb6\xa2\xaf\x91\x18\x7e\x9a\x0a\x7b\x71\x29\x46\x3f\x13\x5a\x22\x46\x30\x92\xdc\x16\x71\x26\x81\xe7\x1e\x42\x96\x84\xb5\xe0\xf2\xcd\x10\xd8\x4f\x2b\x38\x0e\xeb\x8f\x75\x81\xcf\xb5\xab\x96\xf9\x26\x61\x67\xf7\x9c\x6e\xa1\x6c\x42\xe2\xe5\x5e\xee\x3b\x00\x00\xff\xff\x6e\xdf\xbf\xab\xdc\x01\x00\x00")
+
+func opcount_tracerJsBytes() ([]byte, error) {
+ return bindataRead(
+ _opcount_tracerJs,
+ "opcount_tracer.js",
+ )
+}
+
+func opcount_tracerJs() (*asset, error) {
+ bytes, err := opcount_tracerJsBytes()
+ if err != nil {
+ return nil, err
+ }
+
+ info := bindataFileInfo{name: "opcount_tracer.js", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
+ a := &asset{bytes: bytes, info: info}
+ return a, nil
+}
+
+// Asset loads and returns the asset for the given name.
+// It returns an error if the asset could not be found or
+// could not be loaded.
+func Asset(name string) ([]byte, error) {
+ cannonicalName := strings.Replace(name, "\\", "/", -1)
+ if f, ok := _bindata[cannonicalName]; ok {
+ a, err := f()
+ if err != nil {
+ return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
+ }
+ return a.bytes, nil
+ }
+ return nil, fmt.Errorf("Asset %s not found", name)
+}
+
+// MustAsset is like Asset but panics when Asset would return an error.
+// It simplifies safe initialization of global variables.
+func MustAsset(name string) []byte {
+ a, err := Asset(name)
+ if err != nil {
+ panic("asset: Asset(" + name + "): " + err.Error())
+ }
+
+ return a
+}
+
+// AssetInfo loads and returns the asset info for the given name.
+// It returns an error if the asset could not be found or
+// could not be loaded.
+func AssetInfo(name string) (os.FileInfo, error) {
+ cannonicalName := strings.Replace(name, "\\", "/", -1)
+ if f, ok := _bindata[cannonicalName]; ok {
+ a, err := f()
+ if err != nil {
+ return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
+ }
+ return a.info, nil
+ }
+ return nil, fmt.Errorf("AssetInfo %s not found", name)
+}
+
+// AssetNames returns the names of the assets.
+func AssetNames() []string {
+ names := make([]string, 0, len(_bindata))
+ for name := range _bindata {
+ names = append(names, name)
+ }
+ return names
+}
+
+// _bindata is a table, holding each asset generator, mapped to its name.
+var _bindata = map[string]func() (*asset, error){
+ "call_tracer.js": call_tracerJs,
+ "noop_tracer.js": noop_tracerJs,
+ "opcount_tracer.js": opcount_tracerJs,
+}
+
+// AssetDir returns the file names below a certain
+// directory embedded in the file by go-bindata.
+// For example if you run go-bindata on data/... and data contains the
+// following hierarchy:
+// data/
+// foo.txt
+// img/
+// a.png
+// b.png
+// then AssetDir("data") would return []string{"foo.txt", "img"}
+// AssetDir("data/img") would return []string{"a.png", "b.png"}
+// AssetDir("foo.txt") and AssetDir("notexist") would return an error
+// AssetDir("") will return []string{"data"}.
+func AssetDir(name string) ([]string, error) {
+ node := _bintree
+ if len(name) != 0 {
+ cannonicalName := strings.Replace(name, "\\", "/", -1)
+ pathList := strings.Split(cannonicalName, "/")
+ for _, p := range pathList {
+ node = node.Children[p]
+ if node == nil {
+ return nil, fmt.Errorf("Asset %s not found", name)
+ }
+ }
+ }
+ if node.Func != nil {
+ return nil, fmt.Errorf("Asset %s not found", name)
+ }
+ rv := make([]string, 0, len(node.Children))
+ for childName := range node.Children {
+ rv = append(rv, childName)
+ }
+ return rv, nil
+}
+
+type bintree struct {
+ Func func() (*asset, error)
+ Children map[string]*bintree
+}
+
+var _bintree = &bintree{nil, map[string]*bintree{
+ "call_tracer.js": {call_tracerJs, map[string]*bintree{}},
+ "noop_tracer.js": {noop_tracerJs, map[string]*bintree{}},
+ "opcount_tracer.js": {opcount_tracerJs, map[string]*bintree{}},
+}}
+
+// RestoreAsset restores an asset under the given directory
+func RestoreAsset(dir, name string) error {
+ data, err := Asset(name)
+ if err != nil {
+ return err
+ }
+ info, err := AssetInfo(name)
+ if err != nil {
+ return err
+ }
+ err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
+ if err != nil {
+ return err
+ }
+ err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
+ if err != nil {
+ return err
+ }
+ err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// RestoreAssets restores an asset under the given directory recursively
+func RestoreAssets(dir, name string) error {
+ children, err := AssetDir(name)
+ // File
+ if err != nil {
+ return RestoreAsset(dir, name)
+ }
+ // Dir
+ for _, child := range children {
+ err = RestoreAssets(dir, filepath.Join(name, child))
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func _filePath(dir, name string) string {
+ cannonicalName := strings.Replace(name, "\\", "/", -1)
+ return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
+}
diff --git a/eth/tracers/internal/tracers/call_tracer.js b/eth/tracers/internal/tracers/call_tracer.js
new file mode 100644
index 0000000000..e4b3124c37
--- /dev/null
+++ b/eth/tracers/internal/tracers/call_tracer.js
@@ -0,0 +1,117 @@
+// callTracer is a full blown transaction tracer that extracts and reports all
+// the internal calls made by a transaction, along with any useful information.
+{
+ // invocations is a nested data structure containing all the internal contract
+ // calls executed by a transaction.
+ invocations: [],
+
+ // callstack is the current recursive call stack of the EVM execution.
+ callstack: [],
+
+ // descended tracks whether we've just descended from an outer transaction into
+ // an inner call.
+ descended: false,
+
+ // step is invoked for every opcode that the VM executes.
+ step: function(log, db) {
+ // We only care about system opcodes, faster if we pre-check once
+ var syscall = (log.op.toNumber() & 0xf0) == 0xf0;
+ if (syscall) {
+ var op = log.op.toString();
+ }
+ // If a new method invocation is being done, add to the call stack
+ if (syscall && op != "RETURN" && op != "REVERT") {
+ // Skip any pre-compile invocations, those are just fancy opcodes
+ var to = toAddress(log.stack.peek(1).Bytes());
+ if (isPrecompiled(to)) {
+ return
+ }
+ // Otherwise gather some internal call details
+ var off = (op == 'DELEGATECALL' ? 0 : 1);
+
+ var inOff = log.stack.peek(2 + off).Int64();
+ var inEnd = inOff + log.stack.peek(3 + off).Int64();
+
+ // Assemble the internal call report and store for completion
+ var call = {
+ type: op,
+ from: log.account,
+ to: to,
+ input: toHex(log.memory.slice(inOff, inEnd)),
+ gasDiff: log.gas - log.cost,
+ outOff: log.stack.peek(4 + off).Int64(),
+ outLen: log.stack.peek(5 + off).Int64()
+ };
+ if (op != 'DELEGATECALL') {
+ call.value = '0x' + log.stack.peek(2).Text(16);
+ }
+ this.callstack.push(call);
+ this.descended = true
+ return;
+ }
+ // If we've just descended into an inner call, retrieve it's true allowance. We
+ // need to extract if from within the call as there may be funky gas dynamics
+ // with regard to requested and actually given gas (2300 stipend, 63/64 rule).
+ if (this.descended) {
+ if (log.depth > this.callstack.length) {
+ this.callstack[this.callstack.length - 1].gas = log.gas;
+ } else {
+ this.callstack[this.callstack.length - 1].gas = 2300; // TODO (karalabe): Erm...
+ }
+ this.descended = false;
+ }
+ // If an existing call is returning, pop off the call stack
+ if (syscall && op == 'REVERT') {
+ call.revert = true;
+ return;
+ }
+ if (log.depth == this.callstack.length) {
+ // Pop off the last call and get the execution results
+ var call = this.callstack.pop();
+
+ call.gasUsed = '0x' + big.NewInt(call.gas - log.gas + call.gasDiff).Text(16);
+ call.gas = '0x' + big.NewInt(call.gas).Text(16);
+ delete call.gasDiff;
+
+ call.output = toHex(log.memory.slice(call.outOff, call.outOff + call.outLen));
+ delete call.outOff;
+ delete call.outLen;
+
+ // Append to the invocation list if topmost
+ var left = this.callstack.length;
+
+ if (left == 0) {
+ this.invocations.push(call);
+ return
+ }
+ // Apparently it was a nested call, inject into the previous one
+ if (this.callstack[left-1].calls === undefined) {
+ this.callstack[left-1].calls = [];
+ }
+ this.callstack[left-1].calls.push(call);
+ }
+ },
+
+ // result is invoked when all the opcodes have been iterated over and returns
+ // the final result of the tracing.
+ result: function(ctx) {
+ var result = {
+ type: ctx.type,
+ from: toAddress(ctx.from),
+ to: toAddress(ctx.to),
+ value: '0x' + ctx.value.Text(16),
+ gas: '0x' + big.NewInt(ctx.gas).Text(16),
+ gasUsed: '0x' + big.NewInt(ctx.gasUsed).Text(16),
+ input: toHex(ctx.input),
+ output: toHex(ctx.output),
+ time: ctx.time,
+ };
+ if (this.invocations.length > 0) {
+ result.calls = this.invocations;
+ }
+ if (ctx.error !== undefined) {
+ result.error = ctx.error;
+ }
+ return result;
+ }
+}
diff --git a/eth/tracers/internal/tracers/noop_tracer.js b/eth/tracers/internal/tracers/noop_tracer.js
new file mode 100644
index 0000000000..e79782c600
--- /dev/null
+++ b/eth/tracers/internal/tracers/noop_tracer.js
@@ -0,0 +1,10 @@
+// noopTracer is just the barebone boilerplate code required from a JavaScript
+// object to be usable as a transaction tracer.
+{
+ // step is invoked for every opcode that the VM executes.
+ step: 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) { }
+}
diff --git a/eth/tracers/internal/tracers/opcount_tracer.js b/eth/tracers/internal/tracers/opcount_tracer.js
new file mode 100644
index 0000000000..3bbe4497a6
--- /dev/null
+++ b/eth/tracers/internal/tracers/opcount_tracer.js
@@ -0,0 +1,13 @@
+// opcountTracer is a sample tracer that just counts the number of instructions
+// executed by the EVM before the transaction terminated.
+{
+ // count tracks the number of EVM instructions executed.
+ count: 0,
+
+ // step is invoked for every opcode that the VM executes.
+ step: function(log, db) { this.count++ },
+
+ // 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 }
+}
diff --git a/eth/tracers/internal/tracers/tracers.go b/eth/tracers/internal/tracers/tracers.go
new file mode 100644
index 0000000000..dcf0d49dae
--- /dev/null
+++ b/eth/tracers/internal/tracers/tracers.go
@@ -0,0 +1,21 @@
+// Copyright 2017 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+//go:generate go-bindata -nometadata -o assets.go -pkg tracers -ignore ((tracers)|(assets)).go ./...
+//go:generate gofmt -s -w assets.go
+
+// Package tracers contains the actual JavaScript tracer assets.
+package tracers
diff --git a/eth/tracers/tracers.go b/eth/tracers/tracers.go
new file mode 100644
index 0000000000..f55a812eb7
--- /dev/null
+++ b/eth/tracers/tracers.go
@@ -0,0 +1,51 @@
+// Copyright 2017 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+// Package tracers is a collection of JavaScript transaction tracers.
+package tracers
+
+import (
+ "strings"
+ "unicode"
+
+ "github.com/ethereum/go-ethereum/eth/tracers/internal/tracers"
+)
+
+// all contains all the built in JavaScript tracers by name.
+var all = make(map[string]string)
+
+// init retrieves the JavaScript transaction tracers included in go-ethereum.
+func init() {
+ for _, file := range tracers.AssetNames() {
+ // Convert the underscored tracer file name into a camelcase tracer name
+ pieces := strings.Split(strings.TrimSuffix(file, ".js"), "_")
+ for i := 1; i < len(pieces); i++ {
+ pieces[i] = string(unicode.ToUpper(rune(pieces[i][0]))) + pieces[i][1:]
+ }
+ name := strings.Join(pieces, "")
+
+ // Retrieve and store the tracer
+ all[name] = string(tracers.MustAsset(file))
+ }
+}
+
+// Tracer retrieves a specific JavaScript tracer by name.
+func Tracer(name string) (string, bool) {
+ if tracer, ok := all[name]; ok {
+ return tracer, true
+ }
+ return "", false
+}
diff --git a/internal/ethapi/tracer.go b/internal/ethapi/tracer.go
index 71cafc6e97..f2f5d09448 100644
--- a/internal/ethapi/tracer.go
+++ b/internal/ethapi/tracer.go
@@ -37,23 +37,23 @@ func (fb *fakeBig) NewInt(x int64) *big.Int {
return big.NewInt(x)
}
-// OpCodeWrapper provides a JavaScript-friendly wrapper around OpCode, to convince Otto to treat it
+// opCodeWrapper provides a JavaScript-friendly wrapper around OpCode, to convince Otto to treat it
// as an object, instead of a number.
type opCodeWrapper struct {
op vm.OpCode
}
-// toNumber returns the ID of this opcode as an integer
+// toNumber returns the ID of this opcode as an integer.
func (ocw *opCodeWrapper) toNumber() int {
return int(ocw.op)
}
-// toString returns the string representation of the opcode
+// toString returns the string representation of the opcode.
func (ocw *opCodeWrapper) toString() string {
return ocw.op.String()
}
-// isPush returns true if the op is a Push
+// isPush returns true if the op is a Push.
func (ocw *opCodeWrapper) isPush() bool {
return ocw.op.IsPush()
}
@@ -200,8 +200,11 @@ func (c *contractWrapper) toValue(vm *otto.Otto) otto.Value {
// JavascriptTracer provides an implementation of Tracer that evaluates a
// Javascript function for each VM execution step.
type JavascriptTracer struct {
+ inited bool // Flag whether the context was already inited from the EVM
+
vm *otto.Otto // Javascript VM instance
traceobj *otto.Object // User-supplied object to call
+ ctx map[string]interface{} // Map for the execution `ctx` arg to `step`
op *opCodeWrapper // Wrapper around the VM opcode
log map[string]interface{} // (Reusable) map for the `log` arg to `step`
logvalue otto.Value // JS view of `log`
@@ -218,14 +221,19 @@ type JavascriptTracer struct {
// code specifies a Javascript snippet, which must evaluate to an expression
// returning an object with 'step' and 'result' functions.
func NewJavascriptTracer(code string) (*JavascriptTracer, error) {
- vm := otto.New()
- vm.Interrupt = make(chan func(), 1)
+ jsvm := otto.New()
+ jsvm.Interrupt = make(chan func(), 1)
// Set up builtins for this environment
- vm.Set("big", &fakeBig{})
- vm.Set("toHex", hexutil.Encode)
+ jsvm.Set("big", &fakeBig{})
+ jsvm.Set("toHex", hexutil.Encode)
+ jsvm.Set("toAddress", common.BytesToAddress)
+ jsvm.Set("isPrecompiled", func(addr []byte) bool {
+ _, ok := vm.PrecompiledContractsByzantium[common.BytesToAddress(addr)]
+ return ok
+ })
- jstracer, err := vm.Object("(" + code + ")")
+ jstracer, err := jsvm.Object("(" + code + ")")
if err != nil {
return nil, err
}
@@ -254,23 +262,24 @@ func NewJavascriptTracer(code string) (*JavascriptTracer, error) {
contract = new(contractWrapper)
)
log := map[string]interface{}{
- "op": op.toValue(vm),
- "memory": mem.toValue(vm),
- "stack": stack.toValue(vm),
- "contract": contract.toValue(vm),
+ "op": op.toValue(jsvm),
+ "memory": mem.toValue(jsvm),
+ "stack": stack.toValue(jsvm),
+ "contract": contract.toValue(jsvm),
}
- logvalue, _ := vm.ToValue(log)
+ logvalue, _ := jsvm.ToValue(log)
return &JavascriptTracer{
- vm: vm,
+ vm: jsvm,
traceobj: jstracer,
+ ctx: make(map[string]interface{}),
op: op,
log: log,
logvalue: logvalue,
memory: mem,
stack: stack,
db: db,
- dbvalue: db.toValue(vm),
+ dbvalue: db.toValue(jsvm),
contract: contract,
err: nil,
}, nil
@@ -317,8 +326,27 @@ func wrapError(context string, err error) error {
return fmt.Errorf("%v in server-side tracer function '%v'", message, context)
}
-// CaptureState implements the Tracer interface to trace a single step of VM execution
+// CaptureState implements the Tracer interface to initialize the tracing operation.
+func (jst *JavascriptTracer) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error {
+ jst.ctx["type"] = "CALL"
+ if create {
+ jst.ctx["type"] = "CREATE"
+ }
+ jst.ctx["from"] = from
+ jst.ctx["to"] = to
+ jst.ctx["input"] = input
+ jst.ctx["gas"] = gas
+ jst.ctx["value"] = value
+
+ return nil
+}
+
+// CaptureState implements the Tracer interface to trace a single step of VM execution.
func (jst *JavascriptTracer) CaptureState(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.inited {
+ jst.ctx["block"] = env.BlockNumber.Uint64()
+ jst.inited = true
+ }
if jst.err == nil {
jst.op.op = op
jst.memory.memory = memory
@@ -344,21 +372,23 @@ func (jst *JavascriptTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode,
return nil
}
-// CaptureEnd is called after the call finishes
+// 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 {
- //TODO! @Arachnid please figure out of there's anything we can use this method for
+ jst.ctx["output"] = output
+ jst.ctx["gasUsed"] = gasUsed
+ jst.ctx["time"] = t.String()
+ if err != nil {
+ jst.ctx["error"] = err.Error()
+ }
+ ctxvalue, _ := jst.vm.ToValue(jst.ctx)
+ jst.result, jst.err = jst.callSafely("result", ctxvalue, jst.dbvalue)
+ if jst.err != nil {
+ jst.err = wrapError("result", jst.err)
+ }
return nil
}
// GetResult calls the Javascript 'result' function and returns its value, or any accumulated error
func (jst *JavascriptTracer) GetResult() (result interface{}, err error) {
- if jst.err != nil {
- return nil, jst.err
- }
-
- result, err = jst.callSafely("result")
- if err != nil {
- err = wrapError("result", err)
- }
- return
+ return jst.result, jst.err
}