diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go index 596ee97146..c5d37591d9 100644 --- a/eth/tracers/logger/logger.go +++ b/eth/tracers/logger/logger.go @@ -195,6 +195,13 @@ func (s *StructLog) toLegacyJSON() json.RawMessage { return element } +type wrappedLog struct { + parent *wrappedLog + error error + log StructLog + children []*wrappedLog +} + // StructLogger is an EVM state logger and implements EVMLogger. // // StructLogger can capture state based on the given Log configuration and also keeps @@ -204,6 +211,9 @@ func (s *StructLog) toLegacyJSON() json.RawMessage { // A StructLogger can either yield it's output immediately (streaming) or store for // later output. type StructLogger struct { + current *wrappedLog + depth int + cfg Config env *tracing.VMContext @@ -274,7 +284,38 @@ func (l *StructLogger) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope stack = scope.StackData() stackLen = len(stack) ) + + for ; l.depth > depth-1; l.depth = l.depth - 1 { + i := l.depth - (depth - 1) + if l.current.error == nil { + switch stack[len(stack)-i].Bytes32()[31] { + case 0x00: + l.current.error = fmt.Errorf("call failed") + } + } + l.current = l.current.parent + } + if err != nil { + l.current.error = err + } + switch op { + case vm.CALL, vm.DELEGATECALL, vm.STATICCALL, vm.CALLCODE: + l.depth = l.depth + 1 + wl := &wrappedLog{ + parent: l.current, + error: l.current.error, + } + l.current.children = append(l.current.children, wl) + l.current = wl + case vm.REVERT: + l.current.error = vm.ErrExecutionReverted + return + default: + return + } + log := StructLog{pc, op, gas, cost, nil, len(memory), nil, nil, nil, depth, l.env.StateDB.GetRefund(), err} + l.current.log = log if l.cfg.EnableMemory { log.Memory = memory } @@ -331,6 +372,18 @@ func (l *StructLogger) OnExit(depth int, output []byte, gasUsed uint64, err erro if l.skip { return } + + for ; l.depth > 1; l.depth-- { + l.current = l.current.parent + } + l.current.log = StructLog{ + Op: vm.CALL, + GasCost: gasUsed, + ReturnData: output, + Depth: 0, + Err: err, + } + l.output = output l.err = err // TODO @holiman, should we output the per-scope output? @@ -370,6 +423,8 @@ func (l *StructLogger) Stop(err error) { func (l *StructLogger) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) { l.env = env + l.depth = 0 + l.current = &wrappedLog{} } func (l *StructLogger) OnSystemCallStart(env *tracing.VMContext) { l.skip = true @@ -392,6 +447,20 @@ func (l *StructLogger) OnTxEnd(receipt *types.Receipt, err error) { } } +// Depth first append for all children (stack max depth is 1024) +func (l *wrappedLog) getLogs() []StructLog { + var logs []StructLog + l.log.Err = l.error + logs = append(logs, l.log) + for _, child := range l.children { + logs = append(logs, child.getLogs()...) + } + return logs +} + +// StructLogs returns the captured log entries. +func (l *StructLogger) StructLogs() []StructLog { return l.current.getLogs() } + // Error returns the VM error captured by the trace. func (l *StructLogger) Error() error { return l.err } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 92249f7829..0adab2d939 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -864,6 +864,71 @@ func (api *BlockChainAPI) EstimateGas(ctx context.Context, args TransactionArgs, return DoEstimateGas(ctx, api.b, args, bNrOrHash, overrides, blockOverrides, api.b.RPCGasCap()) } +// ExecutionResult groups all structured logs emitted by the EVM +// while replaying a transaction in debug mode as well as transaction +// execution status, the amount of gas used and the return value +type ExecutionResult struct { + Gas uint64 `json:"gas"` + Failed bool `json:"failed"` + ReturnValue string `json:"returnValue"` + StructLogs []StructLogRes `json:"structLogs"` +} + +// StructLogRes stores a structured log emitted by the EVM while replaying a +// transaction in debug mode +type StructLogRes struct { + Pc uint64 `json:"pc"` + Op string `json:"op"` + Gas uint64 `json:"gas"` + GasCost uint64 `json:"gasCost"` + Depth int `json:"depth"` + Error string `json:"error,omitempty"` + Stack *[]string `json:"stack,omitempty"` + Memory *[]string `json:"memory,omitempty"` + Storage *map[string]string `json:"storage,omitempty"` +} + +// FormatLogs formats EVM returned structured logs for json output +func FormatLogs(logs []logger.StructLog) []StructLogRes { + formatted := make([]StructLogRes, len(logs)) + for index, trace := range logs { + var errString string + if trace.Err != nil { + errString = trace.Err.Error() + } + formatted[index] = StructLogRes{ + Pc: trace.Pc, + Op: trace.Op.String(), + Gas: trace.Gas, + GasCost: trace.GasCost, + Depth: trace.Depth, + Error: errString, + } + if trace.Stack != nil { + stack := make([]string, len(trace.Stack)) + for i, stackValue := range trace.Stack { + stack[i] = stackValue.Hex() + } + formatted[index].Stack = &stack + } + if trace.Memory != nil { + memory := make([]string, 0, (len(trace.Memory)+31)/32) + for i := 0; i+32 <= len(trace.Memory); i += 32 { + memory = append(memory, fmt.Sprintf("%x", trace.Memory[i:i+32])) + } + formatted[index].Memory = &memory + } + if trace.Storage != nil { + storage := make(map[string]string) + for i, storageValue := range trace.Storage { + storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue) + } + formatted[index].Storage = &storage + } + } + return formatted +} + // RPCMarshalHeader converts the given header to the RPC output . func RPCMarshalHeader(head *types.Header) map[string]interface{} { result := map[string]interface{}{