Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detect silent failures (no REVERT or error message) #4

Merged
merged 9 commits into from
Jun 21, 2022
67 changes: 65 additions & 2 deletions eth/tracers/logger/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,22 @@ func (s *StructLog) ErrorString() string {
return ""
}

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
// a track record of modified storage which is used in reporting snapshots of the
// contract their storage.
type StructLogger struct {
current *wrappedLog
depth int

cfg Config
env *vm.EVM

Expand Down Expand Up @@ -143,6 +153,8 @@ func (l *StructLogger) Reset() {
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
func (l *StructLogger) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
l.env = env
l.depth = 0
l.current = &wrappedLog{}
}

// CaptureState logs a new structured log message and pushes it out to the environment
Expand All @@ -162,6 +174,35 @@ func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, s
memory := scope.Memory
stack := scope.Stack
contract := scope.Contract
for ; l.depth > depth-1; l.depth = l.depth - 1 {
i := l.depth - (depth - 1)
if l.current.error == nil {
switch stack.Data()[len(stack.Data())-i].Bytes32()[31] {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit hard for me to follow the two loop iterators in my head. If I understand correctly, we always have i == l.depth - (depth - 1), right? Maybe instead of tracking i, we can calculate

stackIndex := len(stack.Data()) - (l.depth - (depth - 1))

on each iteration.
This would also allow us to make a bounds check on the variable instead of crashing out-of-bounds if for some reason the three variables scope.Stack, depth and l.depth get out of sync. What do you think?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And what do we do when it happens?
NOT crash?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider making the code clean enough such that the fatal error that is raised will allow some insight into what went wrong. I'm not aware of the context in which this is run (the filename says "logger", but this is not logging code), so if you say a fatal crash is appropriate so be it.

If you could even include a test, that would be amazing.

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
}

// Copy a snapshot of the current memory state to a new buffer
var mem []byte
if l.cfg.EnableMemory {
Expand Down Expand Up @@ -211,7 +252,7 @@ func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, s
}
// create a new snapshot of the EVM.
log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rdata, storage, depth, l.env.StateDB.GetRefund(), err}
l.logs = append(l.logs, log)
l.current.log = log
}

// CaptureFault implements the EVMLogger interface to trace an execution fault
Expand All @@ -221,6 +262,17 @@ func (l *StructLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, s

// CaptureEnd is called after the call finishes to finalize the tracing.
func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {
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
if l.cfg.Debug {
Expand Down Expand Up @@ -271,8 +323,19 @@ func (l *StructLogger) CaptureTxEnd(restGas uint64) {
l.usedGas = l.gasLimit - restGas
}

// 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.logs }
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 }
Expand Down
65 changes: 65 additions & 0 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1156,6 +1156,71 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args TransactionA
return DoEstimateGas(ctx, s.b, args, bNrOrHash, s.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{}{
Expand Down