From b6467c167a4e55fd54c4a93fba0d666521297997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Sat, 18 Nov 2017 14:23:23 +0200 Subject: [PATCH] cmd, core, eth/tracers: support fancier js tracing --- cmd/evm/json_logger.go | 5 + core/vm/evm.go | 20 ++ core/vm/logger.go | 5 + eth/api.go | 13 +- eth/tracers/internal/tracers/assets.go | 281 ++++++++++++++++++ eth/tracers/internal/tracers/call_tracer.js | 117 ++++++++ eth/tracers/internal/tracers/noop_tracer.js | 10 + .../internal/tracers/opcount_tracer.js | 13 + eth/tracers/internal/tracers/tracers.go | 21 ++ eth/tracers/tracers.go | 51 ++++ internal/ethapi/tracer.go | 86 ++++-- 11 files changed, 589 insertions(+), 33 deletions(-) create mode 100644 eth/tracers/internal/tracers/assets.go create mode 100644 eth/tracers/internal/tracers/call_tracer.js create mode 100644 eth/tracers/internal/tracers/noop_tracer.js create mode 100644 eth/tracers/internal/tracers/opcount_tracer.js create mode 100644 eth/tracers/internal/tracers/tracers.go create mode 100644 eth/tracers/tracers.go 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 }