diff --git a/txtrace/trace_logger.go b/txtrace/trace_logger.go index 867f518fa..71884381a 100644 --- a/txtrace/trace_logger.go +++ b/txtrace/trace_logger.go @@ -2,16 +2,17 @@ package txtrace import ( "encoding/json" + "errors" "math/big" "strings" "time" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/log" - "github.com/holiman/uint256" "github.com/Fantom-foundation/go-opera/gossip/txtrace" ) @@ -28,13 +29,11 @@ type TraceStructLogger struct { blockNumber big.Int value big.Int - gasUsed uint64 + gasLimit uint64 rootTrace *CallTrace inputData []byte - state []depthState traceAddress []uint32 stack []*big.Int - reverted bool output []byte err error } @@ -55,6 +54,8 @@ func (tr *TraceStructLogger) CaptureStart(env *vm.EVM, from common.Address, to c log.Error("Tracer CaptureStart failed", r) } }() + log.Debug("TraceStructLogger Capture Enter", "tx hash", tr.tx.String(), "create", create, "from", from.String(), "to", to.String(), "input", string(input), "gas", gas, "value", value.String()) + // Create main trace holder txTrace := CallTrace{ Actions: make([]ActionTrace, 0), @@ -63,30 +64,31 @@ func (tr *TraceStructLogger) CaptureStart(env *vm.EVM, from common.Address, to c // Check if To is defined. If not, it is create address call callType := CREATE var newAddress *common.Address - if tr.to != nil { + if !create { callType = CALL } else { newAddress = &to } // Store input data - tr.inputData = input - if gas == 0 && tr.gasUsed != 0 { - gas = tr.gasUsed + tr.inputData = common.CopyBytes(input) + // In new version of go-ethereum setting gas limit is done via callback CaptureTxStart + if tr.gasLimit == 0 && gas != 0 { + tr.gasLimit = gas } // Make transaction trace root object blockTrace := NewActionTrace(tr.blockHash, tr.blockNumber, tr.tx, uint64(tr.txIndex), callType) var txAction *AddressAction - if CREATE == callType { - txAction = NewAddressAction(tr.from, gas, tr.inputData, nil, hexutil.Big(tr.value), nil) + if create { + txAction = NewAddressAction(tr.from, tr.gasLimit, tr.inputData, nil, hexutil.Big(*value), nil) if newAddress != nil { blockTrace.Result.Address = newAddress code := hexutil.Bytes(tr.output) blockTrace.Result.Code = &code } } else { - txAction = NewAddressAction(tr.from, gas, tr.inputData, tr.to, hexutil.Big(tr.value), &callType) + txAction = NewAddressAction(tr.from, tr.gasLimit, tr.inputData, tr.to, hexutil.Big(*value), &callType) out := hexutil.Bytes(tr.output) blockTrace.Result.Output = &out } @@ -97,217 +99,112 @@ func (tr *TraceStructLogger) CaptureStart(env *vm.EVM, from common.Address, to c tr.rootTrace = &txTrace // Init all needed variables - tr.state = []depthState{{0, create}} tr.traceAddress = make([]uint32, 0) tr.rootTrace.Stack = append(tr.rootTrace.Stack, &tr.rootTrace.Actions[len(tr.rootTrace.Actions)-1]) } -// stackPosFromEnd returns object from stack at givven position from end of stack -func stackPosFromEnd(stackData []uint256.Int, pos int) *big.Int { - if len(stackData) <= pos || pos < 0 { - log.Warn("Tracer accessed out of bound stack", "size", len(stackData), "index", pos) - return new(big.Int) - } - return new(big.Int).Set(stackData[len(stackData)-1-pos].ToBig()) +// CaptureState is not used as transaction tracing doesn't need per instruction resolution +func (tr *TraceStructLogger) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { } -// CaptureState implements creating of traces based on getting opCodes from evm during contract processing -func (tr *TraceStructLogger) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { +func (tr *TraceStructLogger) CaptureEnter(op vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { defer func() { if r := recover(); r != nil { log.Error("Tracer CaptureState failed", r) } }() - // When going back from inner call - for lastState(tr.state).level >= depth { - result := tr.rootTrace.Stack[len(tr.rootTrace.Stack)-1].Result - if lastState(tr.state).create && result != nil { - if len(scope.Stack.Data()) > 0 { - addr := common.BytesToAddress(stackPosFromEnd(scope.Stack.Data(), 0).Bytes()) - result.Address = &addr - result.GasUsed = hexutil.Uint64(gas) - } - } - tr.traceAddress = removeTraceAddressLevel(tr.traceAddress, depth) - tr.state = tr.state[:len(tr.state)-1] - tr.rootTrace.Stack = tr.rootTrace.Stack[:len(tr.rootTrace.Stack)-1] - if lastState(tr.state).level == depth { - break - } + log.Debug("TraceStructLogger Capture Enter", "tx hash", tr.tx.String(), "op code", op.String(), "from", from.String(), "to", to.String(), "input", string(input), "gas", gas, "value", value.String()) + var ( + fromTrace *ActionTrace + trace *ActionTrace + ) + if tr.rootTrace != nil && len(tr.rootTrace.Stack) > 0 { + fromTrace = tr.rootTrace.Stack[len(tr.rootTrace.Stack)-1] + } else { + return } + if value == nil { + value = big.NewInt(0) + } + // copy values + toAddress := to + fromAddress := from // Match processed instruction and create trace based on it switch op { case vm.CREATE, vm.CREATE2: - tr.traceAddress = addTraceAddress(tr.traceAddress, depth) - fromTrace := tr.rootTrace.Stack[len(tr.rootTrace.Stack)-1] - - // Get input data from memory - // Note that these variables aren't verified and can be faulty - offset := stackPosFromEnd(scope.Stack.Data(), 1).Uint64() - inputSize := stackPosFromEnd(scope.Stack.Data(), 2).Uint64() - var input []byte - if inputSize > 0 { - if offset <= offset+inputSize && // no overflow - offset+inputSize <= uint64(len(scope.Memory.Data())) { - input = make([]byte, inputSize) - copy(input, scope.Memory.Data()[offset:offset+inputSize]) - } - // if it's a faulty SC call, assume that input is nil - } - // Create new trace - trace := NewActionTraceFromTrace(fromTrace, CREATE, tr.traceAddress) - from := scope.Contract.Address() - traceAction := NewAddressAction(&from, gas, input, nil, fromTrace.Action.Value, nil) + trace = NewActionTraceFromTrace(fromTrace, CREATE, tr.traceAddress) + traceAction := NewAddressAction(&fromAddress, gas, input, &toAddress, hexutil.Big(*value), nil) trace.Action = traceAction - trace.Result.GasUsed = hexutil.Uint64(gas) - fromTrace.childTraces = append(fromTrace.childTraces, trace) - tr.rootTrace.Stack = append(tr.rootTrace.Stack, trace) - tr.state = append(tr.state, depthState{depth, true}) case vm.CALL, vm.CALLCODE, vm.DELEGATECALL, vm.STATICCALL: - var ( - inOffset, inSize uint64 - retOffset, retSize uint64 - input []byte - value = big.NewInt(0) - ) - - if vm.DELEGATECALL == op || vm.STATICCALL == op { - // Note that these variables aren't verified and can be faulty - inOffset = stackPosFromEnd(scope.Stack.Data(), 2).Uint64() - inSize = stackPosFromEnd(scope.Stack.Data(), 3).Uint64() - retOffset = stackPosFromEnd(scope.Stack.Data(), 4).Uint64() - retSize = stackPosFromEnd(scope.Stack.Data(), 5).Uint64() - } else { - // Note that these variables aren't verified and can be faulty - inOffset = stackPosFromEnd(scope.Stack.Data(), 3).Uint64() - inSize = stackPosFromEnd(scope.Stack.Data(), 4).Uint64() - retOffset = stackPosFromEnd(scope.Stack.Data(), 5).Uint64() - retSize = stackPosFromEnd(scope.Stack.Data(), 6).Uint64() - // only CALL and CALLCODE need `value` field - value = stackPosFromEnd(scope.Stack.Data(), 2) - } - if inSize > 0 { - if inOffset <= inOffset+inSize && // no overflow - inOffset+inSize <= uint64(len(scope.Memory.Data())) { - input = make([]byte, inSize) - copy(input, scope.Memory.Data()[inOffset:inOffset+inSize]) - } - // if it's a faulty SC call, assume that input is nil - } - tr.traceAddress = addTraceAddress(tr.traceAddress, depth) - fromTrace := tr.rootTrace.Stack[len(tr.rootTrace.Stack)-1] - // create new trace - trace := NewActionTraceFromTrace(fromTrace, CALL, tr.traceAddress) - from := scope.Contract.Address() - addr := common.BytesToAddress(stackPosFromEnd(scope.Stack.Data(), 1).Bytes()) + + trace = NewActionTraceFromTrace(fromTrace, CALL, tr.traceAddress) callType := strings.ToLower(op.String()) - traceAction := NewAddressAction(&from, gas, input, &addr, hexutil.Big(*value), &callType) + traceAction := NewAddressAction(&fromAddress, gas, input, &toAddress, hexutil.Big(*value), &callType) trace.Action = traceAction - fromTrace.childTraces = append(fromTrace.childTraces, trace) - trace.Result.RetOffset = retOffset - trace.Result.RetSize = retSize - tr.rootTrace.Stack = append(tr.rootTrace.Stack, trace) - tr.state = append(tr.state, depthState{depth, false}) - - case vm.RETURN, vm.STOP: - if tr != nil { - result := tr.rootTrace.Stack[len(tr.rootTrace.Stack)-1].Result - if result != nil { - var data []byte - - if vm.STOP != op { - // Note that these variables aren't verified and can be faulty - offset := stackPosFromEnd(scope.Stack.Data(), 0).Uint64() - size := stackPosFromEnd(scope.Stack.Data(), 1).Uint64() - if size > 0 { - if offset <= offset+size && // no overflow - offset+size <= uint64(len(scope.Memory.Data())) { - data = make([]byte, size) - copy(data, scope.Memory.Data()[offset:offset+size]) - } - // if it's a faulty SC call, assume that input is nil - } - } - - if lastState(tr.state).create { - code := hexutil.Bytes(data) - result.Code = &code - } else { - result.GasUsed = hexutil.Uint64(gas) - out := hexutil.Bytes(data) - result.Output = &out - } - } - } - - case vm.REVERT: - tr.reverted = true - tr.rootTrace.Stack[len(tr.rootTrace.Stack)-1].Result = nil - tr.rootTrace.Stack[len(tr.rootTrace.Stack)-1].Error = "Reverted" case vm.SELFDESTRUCT: - tr.traceAddress = addTraceAddress(tr.traceAddress, depth) - fromTrace := tr.rootTrace.Stack[len(tr.rootTrace.Stack)-1] - trace := NewActionTraceFromTrace(fromTrace, SELFDESTRUCT, tr.traceAddress) - action := fromTrace.Action - - from := scope.Contract.Address() - traceAction := NewAddressAction(nil, 0, nil, nil, action.Value, nil) - traceAction.Address = &from - // set refund values - refundAddress := common.BytesToAddress(stackPosFromEnd(scope.Stack.Data(), 0).Bytes()) - traceAction.RefundAddress = &refundAddress - // Add `balance` field for convenient usage - traceAction.Balance = &traceAction.Value + + trace = NewActionTraceFromTrace(fromTrace, SELFDESTRUCT, tr.traceAddress) + traceAction := NewAddressAction(&fromAddress, gas, input, nil, hexutil.Big(*value), nil) + traceAction.Address = &fromAddress + traceAction.RefundAddress = &toAddress + traceAction.Balance = (*hexutil.Big)(value) trace.Action = traceAction - fromTrace.childTraces = append(fromTrace.childTraces, trace) } + tr.rootTrace.Stack = append(tr.rootTrace.Stack, trace) } -// CaptureEnd is called after the call finishes to finalize the tracing. -func (tr *TraceStructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) { +// CaptureExit is called when returning from an inner call +func (tr *TraceStructLogger) CaptureExit(output []byte, gasUsed uint64, err error) { defer func() { if r := recover(); r != nil { - log.Error("Tracer CaptureEnd failed", r) + log.Error("Tracer CaptureExit failed", r) } }() - log.Debug("TraceStructLogger capture END", "tx hash", tr.tx.String(), "duration", t, "gasUsed", gasUsed, "error", err) - if err != nil && err != vm.ErrExecutionReverted { - if tr.rootTrace != nil && tr.rootTrace.Stack != nil && len(tr.rootTrace.Stack) > 0 { - tr.rootTrace.Stack[len(tr.rootTrace.Stack)-1].Result = nil - tr.rootTrace.Stack[len(tr.rootTrace.Stack)-1].Error = err.Error() - } + log.Debug("TraceStructLogger Capture Exit", "tx hash", tr.tx.String(), "output", string(output), "gasUsed", gasUsed, "error", err) + + if tr.rootTrace == nil { + return } - if gasUsed > 0 { - if tr.rootTrace.Actions[0].Result != nil { - tr.rootTrace.Actions[0].Result.GasUsed = hexutil.Uint64(gasUsed) - } - tr.rootTrace.lastTrace().Action.Gas = hexutil.Uint64(gasUsed) - tr.gasUsed = gasUsed + size := len(tr.rootTrace.Stack) + if size <= 1 { + return } - tr.output = output -} -func (*TraceStructLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + trace := tr.rootTrace.Stack[len(tr.rootTrace.Stack)-1] + tr.rootTrace.Stack = tr.rootTrace.Stack[:len(tr.rootTrace.Stack)-1] + + parent := tr.rootTrace.Stack[len(tr.rootTrace.Stack)-1] + parent.childTraces = append(parent.childTraces, trace) + + trace.processOutput(output, err, false) + + result := trace.Result + if result != nil { + result.GasUsed = hexutil.Uint64(gasUsed) + } } -// CaptureExit is called when returning from an inner call -func (tr *TraceStructLogger) CaptureExit(output []byte, gasUsed uint64, err error) { +// CaptureEnd is called after the call finishes to finalize the tracing. +func (tr *TraceStructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) { defer func() { if r := recover(); r != nil { - log.Error("Tracer CaptureExit failed", r) + log.Error("Tracer CaptureEnd failed", r) } }() - // When going back from inner call - result := tr.rootTrace.Stack[len(tr.rootTrace.Stack)-1].Result - if result != nil { - result.GasUsed = hexutil.Uint64(gasUsed) - out := hexutil.Bytes(output) - result.Output = &out + log.Info("TraceStructLogger Capture END", "tx hash", tr.tx.String(), "duration", t, "gasUsed", gasUsed, "error", err) + + if tr.rootTrace != nil && tr.rootTrace.lastTrace() != nil { + + trace := tr.rootTrace.lastTrace() + trace.processOutput(output, err, true) + if trace.Result != nil { + trace.Result.GasUsed = hexutil.Uint64(gasUsed) + } } } @@ -316,13 +213,47 @@ func (tr *TraceStructLogger) CaptureExit(output []byte, gasUsed uint64, err erro func (tr *TraceStructLogger) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { } +// Handle output data and error +func (trace *ActionTrace) processOutput(output []byte, err error, rootTrace bool) { + output = common.CopyBytes(output) + if err == nil { + switch trace.TraceType { + case CREATE: + trace.Result.Code = (*hexutil.Bytes)(&output) + if !rootTrace { + trace.Result.Address = trace.Action.To + trace.Action.To = nil + } + case CALL: + trace.Result.Output = (*hexutil.Bytes)(&output) + default: + } + return + } else { + trace.Result = nil + } + + trace.Error = err.Error() + if trace.TraceType == CREATE { + trace.Action.To = nil + } + if !errors.Is(err, vm.ErrExecutionReverted) || len(output) == 0 { + return + } + if len(output) < 4 { + return + } + if unpacked, err := abi.UnpackRevert(output); err == nil { + trace.Error = unpacked + } +} + // Reset function to be able to reuse logger func (tr *TraceStructLogger) reset() { tr.to = nil tr.from = nil tr.inputData = nil tr.rootTrace = nil - tr.reverted = false } // SetTx basic setter @@ -367,17 +298,17 @@ func (tr *TraceStructLogger) SetNewAddress(newAddress common.Address) { // SetGasUsed basic setter func (tr *TraceStructLogger) SetGasUsed(gasUsed uint64) { - tr.gasUsed = gasUsed + tr.gasLimit = gasUsed } // ProcessTx finalizes trace proces and stores result into key-value persistant store func (tr *TraceStructLogger) ProcessTx() { - if tr.rootTrace != nil { - tr.rootTrace.lastTrace().Action.Gas = hexutil.Uint64(tr.gasUsed) + if tr.rootTrace != nil && tr.rootTrace.lastTrace() != nil { + tr.rootTrace.lastTrace().Action.Gas = hexutil.Uint64(tr.gasLimit) if tr.rootTrace.lastTrace().Result != nil { - tr.rootTrace.lastTrace().Result.GasUsed = hexutil.Uint64(tr.gasUsed) + tr.rootTrace.lastTrace().Result.GasUsed = hexutil.Uint64(tr.gasLimit) } - tr.rootTrace.processLastTrace() + tr.rootTrace.processTraces() } } @@ -388,7 +319,6 @@ func (tr *TraceStructLogger) SaveTrace() { tr.rootTrace.AddTrace(GetErrorTraceFromLogger(tr)) } - //if tr.store != nil && tr.rootTrace != nil { if tr.store != nil { // Convert trace objects to json byte array and save it tracesBytes, _ := json.Marshal(tr.rootTrace.Actions) @@ -534,81 +464,38 @@ type AddressAction struct { // TraceActionResult holds information related to result of the // processed transaction type TraceActionResult struct { - GasUsed hexutil.Uint64 `json:"gasUsed"` - Output *hexutil.Bytes `json:"output,omitempty"` - Code *hexutil.Bytes `json:"code,omitempty"` - Address *common.Address `json:"address,omitempty"` - RetOffset uint64 `json:"-"` - RetSize uint64 `json:"-"` + GasUsed hexutil.Uint64 `json:"gasUsed"` + Output *hexutil.Bytes `json:"output,omitempty"` + Code *hexutil.Bytes `json:"code,omitempty"` + Address *common.Address `json:"address,omitempty"` } -// depthState is struct for having state of logs processing -type depthState struct { - level int - create bool -} - -// returns last state -func lastState(state []depthState) *depthState { - return &state[len(state)-1] -} - -// adds trace address and retuns it -func addTraceAddress(traceAddress []uint32, depth int) []uint32 { - index := depth - 1 - result := make([]uint32, len(traceAddress)) - copy(result, traceAddress) - if len(result) <= index { - result = append(result, 0) - } else { - result[index]++ - } - return result -} - -// removes trace address based on depth of process -func removeTraceAddressLevel(traceAddress []uint32, depth int) []uint32 { - if len(traceAddress) > depth { - result := make([]uint32, len(traceAddress)) - copy(result, traceAddress) - - result = result[:len(result)-1] - return result - } - return traceAddress -} - -// processLastTrace initiates final information distribution +// processTraces initiates final information distribution // accros result traces -func (callTrace *CallTrace) processLastTrace() { +func (callTrace *CallTrace) processTraces() { trace := &callTrace.Actions[len(callTrace.Actions)-1] - callTrace.processTrace(trace) + callTrace.processTrace(trace, []uint32{}) } // processTrace goes thru all trace results and sets info -func (callTrace *CallTrace) processTrace(trace *ActionTrace) { +func (callTrace *CallTrace) processTrace(trace *ActionTrace, traceAddress []uint32) { + trace.TraceAddress = traceAddress trace.Subtraces = uint64(len(trace.childTraces)) - for _, childTrace := range trace.childTraces { - if CALL == trace.TraceType { - childTrace.Action.From = trace.Action.To - } else { - if trace.Result != nil { - childTrace.Action.From = trace.Result.Address - } - } - - if childTrace.Result != nil { - if trace.Action.Gas > childTrace.Result.GasUsed { - childTrace.Action.Gas = trace.Action.Gas - childTrace.Result.GasUsed - } else { - childTrace.Action.Gas = childTrace.Result.GasUsed - } - } + for i, childTrace := range trace.childTraces { + childAddress := childTraceAddress(traceAddress, i) + childTrace.TraceAddress = childAddress callTrace.AddTrace(childTrace) - callTrace.processTrace(callTrace.lastTrace()) + callTrace.processTrace(callTrace.lastTrace(), childAddress) } } +func childTraceAddress(a []uint32, i int) []uint32 { + child := make([]uint32, 0, len(a)+1) + child = append(child, a...) + child = append(child, uint32(i)) + return child +} + // GetErrorTrace constructs filled error trace func GetErrorTrace(blockHash common.Hash, blockNumber big.Int, from *common.Address, to *common.Address, txHash common.Hash, index uint64, err error) *ActionTrace { return createErrorTrace(blockHash, blockNumber, from, to, txHash, 0, []byte{}, hexutil.Big{}, index, err) @@ -619,7 +506,7 @@ func GetErrorTraceFromLogger(tr *TraceStructLogger) *ActionTrace { if tr == nil { return nil } else { - return createErrorTrace(tr.blockHash, tr.blockNumber, tr.from, tr.to, tr.tx, tr.gasUsed, tr.inputData, hexutil.Big(tr.value), uint64(tr.txIndex), tr.err) + return createErrorTrace(tr.blockHash, tr.blockNumber, tr.from, tr.to, tr.tx, tr.gasLimit, tr.inputData, hexutil.Big(tr.value), uint64(tr.txIndex), tr.err) } }