diff --git a/cmd/evm/json_logger.go b/cmd/evm/json_logger.go index eb7b0c46600e3..777f2ac8d2546 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 093c7d4c148ce..08ebee180c57f 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" @@ -167,6 +168,11 @@ 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, snapshot, 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 @@ -177,6 +183,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 } @@ -334,6 +343,12 @@ 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, snapshot, contract, nil) // check whether the max code size has been exceeded maxCodeSizeExceeded := evm.ChainConfig().IsEIP158(evm.BlockNumber) && len(ret) > params.MaxCodeSize @@ -363,6 +378,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 75309da921fac..5465beeb7acb3 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 e91f51bb99693..977820e75f5c7 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" @@ -483,21 +484,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 0000000000000..14bcc8143fe30 --- /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\x56\x4f\x6f\xdb\x3a\x12\x3f\x5b\x9f\x62\x7a\x8a\x8d\x3a\x72\xd2\x76\x73\x70\xa0\x2e\xb2\x8d\xb1\x5b\x20\xbb\x29\xba\x79\x7d\x87\x22\x07\x5a\x1a\xc9\x6c\x28\x52\x8f\x1c\xf9\x0f\xda\x7c\xf7\x87\x21\x29\x45\x56\xd2\x3e\xe0\xf9\x24\x0f\x67\x7e\x9c\xbf\xbf\xe1\x62\x01\xb9\x50\xea\xce\x8a\x1c\x2d\x48\x07\x02\xca\x56\x29\x58\x2b\xb3\xd3\x40\x56\x68\x27\x72\x92\xc6\x7f\xb3\x0a\x6d\x04\x01\xee\xf9\x1f\x39\x10\xba\x00\x8b\x8d\xb1\xfc\xad\x54\xb2\x58\x00\x6d\x10\xa4\x26\xb4\x5a\x28\x8f\xed\xa0\x16\x05\xc2\xfa\x00\x62\x08\x38\x07\xa1\x8c\xae\x60\x27\x69\x03\x42\x1f\xa0\x75\x58\xb6\x0a\xa4\x2e\x8d\xad\x05\xab\xa4\xc9\xf7\x64\xb2\x58\x80\xd4\x5b\x93\x7b\x89\x0b\x2e\x6a\x74\x84\x05\x14\x82\x04\x38\xb2\x6d\x4e\xad\x45\xc8\x8d\x26\x21\xb5\xd4\x15\xfb\x32\x72\xc4\x68\xef\xb2\xc7\x0b\x5e\xe1\x1e\xf3\x96\x61\xc6\x9e\xa5\xc9\x64\x70\xe3\x12\xbe\xde\xcf\x93\x27\x3b\x12\xf9\x03\x7b\xc1\xf0\x79\x6b\x2d\x6a\x02\x8b\x79\x6b\x9d\xdc\xa2\x57\x81\xa0\x63\x4a\xaf\xb3\xfa\xf2\xdf\x78\x55\x80\xee\x41\x06\xc0\x05\xba\x1c\x75\x81\x85\xcf\xf2\x83\x83\xdd\x06\x69\x83\x16\x76\x78\xb2\x45\xf8\xd6\x3a\x1a\xe8\x94\xd6\xd4\x20\x34\x98\x96\xb8\x20\x83\x1a\x49\x4d\xc6\x03\x0a\xfe\xd6\x68\xbd\x3f\x69\x32\xe9\x8d\x97\x50\x0a\xe5\x30\xde\xeb\x08\x1b\x8e\x85\xc3\x7d\x60\x64\x63\x01\xb7\x68\x0f\x60\x9a\xdc\x14\x18\xaa\xcd\x51\xf4\x41\xa0\x4b\x93\x09\xdb\x2d\xa1\x6c\xb5\xbf\x76\xaa\x4c\x35\x87\x62\x3d\x83\xef\xc9\x84\x61\x3f\x96\xbe\x46\x3b\xa8\x91\x36\xa6\x18\xd4\x8f\x2f\x5b\x23\x57\xa8\x30\x1a\xe7\x20\x8a\x02\xc8\x84\x5c\xf6\x99\x4b\x26\x13\x59\x02\xa3\xa6\xa6\x81\x2c\x83\x93\x0f\x57\x37\x37\x27\xf0\xe3\x07\x8c\x64\x1f\x6e\xaf\x57\x63\xf9\xf5\xea\x66\xf5\xef\xab\xbb\x95\xb7\x09\x2e\xb1\x4f\xff\x7f\x90\x8d\x6f\xb3\xc6\xe2\x69\x6e\xea\x46\x2a\x1c\x36\xd6\x1c\x68\x63\x1c\x82\xb0\x31\xdf\xa5\xd0\x79\x97\x07\xc7\x20\x5b\x61\xd9\xd7\x0c\xc8\x5c\x15\x85\x45\xe7\xbc\x8b\xde\xe5\xb4\x41\x7c\x98\x9e\xcf\xd2\x7f\x1d\x08\xdd\x74\x36\xbb\x64\x0b\x8e\x42\xba\x4f\x16\xe3\x7d\xc5\x94\xcc\x2c\xba\x34\xb1\x48\xad\xd5\xfc\xf9\x18\x5d\xbc\xe5\x92\xef\xa4\x43\xa8\x84\xaf\xbe\x33\xf5\x68\x96\xa0\x40\x12\x52\xf5\xfe\x98\xb2\x84\xec\x28\x55\x47\xe1\xc3\x3f\xe1\x0c\x96\x70\x3e\xbb\x4c\x3a\x0b\xa9\x6f\xbd\xcd\xc8\xf5\x37\xf0\x9a\xc1\x66\xe9\x47\x4d\x17\xef\xa6\xc1\xff\xa0\xbf\xd2\x05\x64\xd1\xee\xf5\xd8\xee\xed\x33\xbb\x18\xcc\x95\x73\x58\xaf\x15\x3e\xe7\x83\x48\x18\x9e\x3b\x1c\x19\x8b\xbe\xed\x38\x45\x0a\xb9\x14\xdd\xcd\x5e\x37\x8b\xd9\xa2\x43\x83\x4b\x00\x88\x95\x9e\x7b\x21\x0f\x42\x2f\x14\x79\x6e\x5a\x4d\xe1\x84\x8c\x97\x03\x90\x09\x02\xa9\x9b\x96\x96\x5e\xf0\x1f\xdc\xfb\x84\xd5\x58\x1b\x7b\x48\x9d\x92\x39\x4e\x7d\x78\xf3\x10\xed\x6c\x16\x6c\x2a\xe1\xae\x65\x59\x2e\x3d\x7c\x25\x1c\x9c\xfa\xaf\xdc\xb8\x78\x8b\x69\xe9\x96\xcf\xc7\x49\x79\x37\x4a\x4a\xaf\x7d\x83\xfa\xb9\xf6\x3f\x46\xda\xbe\x25\xfa\xfe\x89\xa5\x7d\xf5\x93\xce\xf6\x74\x92\x6e\x85\x6a\x11\x32\x38\x39\xdb\x9f\x3c\xaf\xd1\x9b\x59\x7a\x87\x7b\x9a\x9e\x5f\x84\xb2\xfa\x7e\xa3\x8d\x74\x69\xcf\x45\x69\xd3\xba\xcd\x94\xff\x06\x15\x7f\xfa\xc4\x38\x19\x90\x6d\x31\xe9\xbb\x96\x75\x1e\xfb\x59\x7f\x91\xa4\x98\x8a\x8e\x69\x68\x0e\x16\xc9\x4a\xdc\x22\x48\x3a\x71\x1e\x92\x89\xda\xec\x84\xce\x31\x85\xdf\x31\x20\x6a\x44\x4f\x0a\x71\xc9\x80\x2c\x03\xe3\xf1\xa2\x90\xfa\x89\x2b\x84\xe7\x60\x8b\x50\x8b\x03\xac\x91\xe9\xe8\xe1\x00\x5c\xa7\xe2\xa0\x45\x2d\x73\x17\xf0\xfc\x82\xb1\x58\x09\xeb\x61\x2d\xfe\xd1\x86\xf5\xc1\x0d\x28\x72\x6a\x85\x52\x07\xa8\xe4\x16\xb5\xb7\x9e\xbe\x79\x7b\x76\x06\x8e\x64\x83\xba\x98\xc3\xc5\xdb\xc5\xc5\x3b\xb0\xad\xc2\x59\x1a\x99\xe9\x38\x3b\xb1\x12\x5d\xb1\x0a\x6c\x68\x03\xef\x61\x94\x60\x85\xba\xa2\x4d\x57\xb5\xe3\xc3\xaf\x2f\xea\xc2\x29\x9c\xdf\xfb\xbe\xcb\xba\x0e\x0c\xe5\x03\x54\x0e\xff\x26\x10\xc7\x76\x09\x8b\x05\xdc\xdd\x5e\xdf\xc2\xf4\x41\x58\xa1\xc4\x1a\x67\x4b\x58\xd9\x3a\x4d\xd3\xe3\xfe\x18\x76\x80\xdf\x1b\xc7\x85\x17\x1a\x70\x2f\x1d\x31\x9f\xfb\x92\x48\x07\xa1\x41\xa4\xae\xe6\xd0\x98\xc6\x13\xd4\x5f\xb1\xfb\xe7\xd5\x97\xd5\xe7\xbb\xae\xa3\x7d\x43\x5b\xde\x42\x14\xfb\xee\xf2\x79\xe3\x1d\x27\x3b\xcb\x7e\x99\xed\xc5\x02\x3e\x0d\x5c\x51\xc2\x51\xec\x20\x5d\x40\x85\x61\xc1\xf5\x2b\x1a\x2c\xba\x56\x91\x1b\xd1\xd0\x78\x5e\x4c\xd3\x91\x9d\x77\xb8\x12\xee\x37\xe7\xf3\x14\x67\x70\x2d\xab\xf4\x7f\xb8\xfb\xa8\x69\xda\x29\x44\xfe\xe0\xaf\xd7\xd0\x09\x99\x61\x46\xf3\xd9\xeb\xf3\xef\x57\x80\x23\xbb\x02\x15\x12\x1e\x21\x0f\x3c\x34\x2d\x35\xad\x4f\xe9\xcb\x0c\xd8\x29\x79\x1e\x1c\xfc\xe9\x7c\x0d\xf4\x35\x7b\x7e\x55\x50\x7b\x49\x7c\x83\xfa\x69\x1f\x34\x3c\x4e\xdd\xb2\x1f\xbc\x08\x94\x74\x7e\xc6\xc9\x34\xb5\x71\xd4\xa5\x5d\x61\x49\xcf\xd3\x1e\xea\x1a\x40\x7d\x0f\x78\xad\x0c\xce\x8e\x06\x6b\xb0\xd6\xc7\xc4\xf6\xc2\xda\xbd\x6a\x1a\xc1\xaf\x38\x75\x00\x49\xb0\x13\x83\x07\x66\xa0\x2d\xa9\xbf\x21\xf3\x90\x8e\xce\x37\x16\xb7\xd2\xb4\x0e\x8c\xc6\xce\x91\xd1\x1c\xb2\x5f\xa7\xe7\xf7\x41\x02\x59\x96\x41\xab\x0b\x2c\xa5\xee\xe9\x62\x3c\xb9\x23\x0b\xf8\x7a\xff\x33\xae\x3e\x56\x1d\x05\xf8\x98\x4c\x1e\xe3\xe3\x2e\xf4\xf1\xf0\x79\xb7\xdb\xa0\xee\x9f\xc6\xf1\x65\x03\x1b\xb1\x45\x58\x23\x6a\x90\x84\x56\x70\xd8\x66\x8b\x36\x3e\xed\x39\x59\xce\xc3\xb1\x4d\x29\x79\x89\x47\xe0\xf8\xbe\x65\x92\x96\xba\x4a\x93\x49\x90\x0f\xde\x85\x39\xed\x43\xb4\x5c\xd0\x68\x15\x17\x7a\xbf\xcf\x73\xda\xa7\xfc\xc7\xef\xc8\x7e\xa1\x3f\x3d\xb1\xf8\x9c\xc5\x61\x89\x0e\xf6\xfa\x50\x81\x4c\x38\xf6\x7b\x90\x35\xe2\xcc\xf0\x99\x97\xf5\x83\xe2\xd5\x2a\xe1\x02\xcc\x0b\xa3\x45\xfb\xe3\xc9\xea\x0c\x78\xba\x97\x3f\x37\xe0\xe3\x91\xd1\xe8\xc9\xc1\x8a\x5e\x14\x4e\xc3\x38\x2e\x87\xa7\x41\x14\x03\x95\xf5\x20\x3f\xb2\xf6\xf9\xf1\xaf\x82\xbe\xdb\x86\x5d\x1e\x89\xfe\x7d\x3f\x08\x21\xdb\x7d\x37\x8d\x0d\x86\x3c\xca\x37\xa0\xb5\xc6\xc2\xab\x17\xfa\x34\x02\x05\x85\x0c\x7a\xe5\x0e\x21\xb4\x48\xac\xee\x65\x32\x79\x4c\x1e\x93\x3f\x03\x00\x00\xff\xff\xba\x96\x49\x56\x55\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 0000000000000..05ca04e233cd2 --- /dev/null +++ b/eth/tracers/internal/tracers/call_tracer.js @@ -0,0 +1,112 @@ +// 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) { + // If a new method invocation is being done, add to the call stack + if (log.op == 'CALL' || log.op == 'CALLCODE' || log.op == 'DELEGATECALL') { + // 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 = (log.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: log.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 (log.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 (log.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 0000000000000..e79782c6001fd --- /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 0000000000000..3bbe4497a69e9 --- /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 0000000000000..dcf0d49daee83 --- /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 0000000000000..f55a812eb781e --- /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 0516265270f3c..2a0354cc28035 100644 --- a/internal/ethapi/tracer.go +++ b/internal/ethapi/tracer.go @@ -37,7 +37,7 @@ 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 @@ -202,6 +202,8 @@ func (c *contractWrapper) toValue(vm *otto.Otto) otto.Value { type JavascriptTracer struct { 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` + ctxvalue otto.Value // JS view of `ctx` log map[string]interface{} // (Reusable) map for the `log` arg to `step` logvalue otto.Value // JS view of `log` memory *memoryWrapper // Wrapper around the VM memory @@ -213,20 +215,26 @@ type JavascriptTracer struct { contract *contractWrapper // Wrapper around the contract object contractvalue otto.Value // JS view of `contract` err error // Error, if one has occurred + result interface{} // Final result to return to the user } // NewJavascriptTracer instantiates a new JavascriptTracer instance. // 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) - - jstracer, err := vm.Object("(" + code + ")") + 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 := jsvm.Object("(" + code + ")") if err != nil { return nil, err } @@ -248,9 +256,13 @@ func NewJavascriptTracer(code string) (*JavascriptTracer, error) { return nil, fmt.Errorf("Trace object must expose a function result()") } + // Create the persistent context object + ctx := make(map[string]interface{}) + ctxvalue, _ := jsvm.ToValue(ctx) + // Create the persistent log object log := make(map[string]interface{}) - logvalue, _ := vm.ToValue(log) + logvalue, _ := jsvm.ToValue(log) // Create persistent wrappers for memory and stack mem := &memoryWrapper{} @@ -259,18 +271,20 @@ func NewJavascriptTracer(code string) (*JavascriptTracer, error) { contract := &contractWrapper{} return &JavascriptTracer{ - vm: vm, + vm: jsvm, traceobj: jstracer, + ctx: ctx, + ctxvalue: ctxvalue, log: log, logvalue: logvalue, memory: mem, - memvalue: mem.toValue(vm), + memvalue: mem.toValue(jsvm), stack: stack, - stackvalue: stack.toValue(vm), + stackvalue: stack.toValue(jsvm), db: db, - dbvalue: db.toValue(vm), + dbvalue: db.toValue(jsvm), contract: contract, - contractvalue: contract.toValue(vm), + contractvalue: contract.toValue(jsvm), err: nil, }, nil } @@ -316,7 +330,22 @@ 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.err == nil { jst.memory.memory = memory @@ -329,14 +358,16 @@ func (jst *JavascriptTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, jst.log["pc"] = pc jst.log["op"] = ocw.toValue(jst.vm) jst.log["gas"] = gas - jst.log["gasPrice"] = cost + jst.log["cost"] = cost jst.log["memory"] = jst.memvalue jst.log["stack"] = jst.stackvalue jst.log["contract"] = jst.contractvalue jst.log["depth"] = depth jst.log["account"] = contract.Address() - jst.log["err"] = err - + delete(jst.log, "error") + if err != nil { + jst.log["error"] = err + } _, err := jst.callSafely("step", jst.logvalue, jst.dbvalue) if err != nil { jst.err = wrapError("step", err) @@ -345,21 +376,22 @@ 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() + } + jst.result, jst.err = jst.callSafely("result", jst.ctxvalue) + 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 }