Skip to content

Commit 44bd05a

Browse files
gzliudans1na
andauthored
eth/tracers: refactor traceTx to separate out struct logging ethereum#24326 (#1278)
* eth/tracers: refactor traceTx to separate out struct logging review fix Update eth/tracers/api.go Co-authored-by: Martin Holst Swende <[email protected]> Mv ExecutionResult type to logger package review fix impl GetResult for StructLogger make formatLogs private confused exit and end.. account for intrinsicGas in structlogger, fix TraceCall test Add Stop method to logger Simplify traceTx Fix test rm logger from blockchain test account for refund in structLogger * use tx hooks in struct logger * minor * avoid executionResult in struct logger * revert blockchain test changes Co-authored-by: Sina Mahmoodi <[email protected]>
1 parent 0bc25c3 commit 44bd05a

File tree

4 files changed

+162
-128
lines changed

4 files changed

+162
-128
lines changed

eth/tracers/api.go

Lines changed: 26 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -724,67 +724,47 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
724724
// executes the given message in the provided environment. The return value will
725725
// be tracer dependent.
726726
func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) {
727-
// Assemble the structured logger or the JavaScript tracer
728727
var (
729-
tracer vm.EVMLogger
728+
tracer Tracer
730729
err error
730+
timeout = defaultTraceTimeout
731731
txContext = core.NewEVMTxContext(message)
732732
)
733-
switch {
734-
case config == nil:
735-
tracer = logger.NewStructLogger(nil)
736-
case config.Tracer != nil:
737-
// Define a meaningful timeout of a single transaction trace
738-
timeout := defaultTraceTimeout
739-
if config.Timeout != nil {
740-
if timeout, err = time.ParseDuration(*config.Timeout); err != nil {
741-
return nil, err
742-
}
733+
if config == nil {
734+
config = &TraceConfig{}
735+
}
736+
// Default tracer is the struct logger
737+
tracer = logger.NewStructLogger(config.Config)
738+
if config.Tracer != nil {
739+
tracer, err = New(*config.Tracer, txctx, config.TracerConfig)
740+
if err != nil {
741+
return nil, err
743742
}
744-
if t, err := New(*config.Tracer, txctx, config.TracerConfig); err != nil {
743+
}
744+
// Define a meaningful timeout of a single transaction trace
745+
if config.Timeout != nil {
746+
if timeout, err = time.ParseDuration(*config.Timeout); err != nil {
745747
return nil, err
746-
} else {
747-
deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
748-
go func() {
749-
<-deadlineCtx.Done()
750-
if errors.Is(deadlineCtx.Err(), context.DeadlineExceeded) {
751-
t.Stop(errors.New("execution timeout"))
752-
}
753-
}()
754-
defer cancel()
755-
tracer = t
756748
}
757-
default:
758-
tracer = logger.NewStructLogger(config.Config)
759749
}
750+
deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
751+
go func() {
752+
<-deadlineCtx.Done()
753+
if errors.Is(deadlineCtx.Err(), context.DeadlineExceeded) {
754+
tracer.Stop(errors.New("execution timeout"))
755+
}
756+
}()
757+
defer cancel()
758+
760759
// Run the transaction with tracing enabled.
761760
vmenv := vm.NewEVM(vmctx, txContext, statedb, nil, api.backend.ChainConfig(), vm.Config{Tracer: tracer, NoBaseFee: true})
762-
763761
// Call SetTxContext to clear out the statedb access list
764762
statedb.SetTxContext(txctx.TxHash, txctx.TxIndex)
765-
766763
owner := common.Address{}
767-
result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()), owner)
768-
if err != nil {
764+
if _, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()), owner); err != nil {
769765
return nil, fmt.Errorf("tracing failed: %w", err)
770766
}
771-
772-
// Depending on the tracer type, format and return the output
773-
switch tracer := tracer.(type) {
774-
case *logger.StructLogger:
775-
return &ethapi.ExecutionResult{
776-
Gas: result.UsedGas,
777-
Failed: result.Failed(),
778-
ReturnValue: fmt.Sprintf("%x", result.Return()),
779-
StructLogs: ethapi.FormatLogs(tracer.StructLogs()),
780-
}, nil
781-
782-
case Tracer:
783-
return tracer.GetResult()
784-
785-
default:
786-
panic(fmt.Sprintf("bad tracer type %T", tracer))
787-
}
767+
return tracer.GetResult()
788768
}
789769

790770
// APIs return the collection of RPC services the tracer package offers.

eth/tracers/api_test.go

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import (
4040
"github.com/XinFinOrg/XDPoSChain/core/types"
4141
"github.com/XinFinOrg/XDPoSChain/core/vm"
4242
"github.com/XinFinOrg/XDPoSChain/crypto"
43+
"github.com/XinFinOrg/XDPoSChain/eth/tracers/logger"
4344
"github.com/XinFinOrg/XDPoSChain/ethdb"
4445
"github.com/XinFinOrg/XDPoSChain/internal/ethapi"
4546
"github.com/XinFinOrg/XDPoSChain/params"
@@ -224,11 +225,11 @@ func TestTraceCall(t *testing.T) {
224225
},
225226
config: nil,
226227
expectErr: nil,
227-
expect: &ethapi.ExecutionResult{
228+
expect: &logger.ExecutionResult{
228229
Gas: params.TxGas,
229230
Failed: false,
230231
ReturnValue: "",
231-
StructLogs: []ethapi.StructLogRes{},
232+
StructLogs: []logger.StructLogRes{},
232233
},
233234
},
234235
// Standard JSON trace upon the head, plain transfer.
@@ -241,11 +242,11 @@ func TestTraceCall(t *testing.T) {
241242
},
242243
config: nil,
243244
expectErr: nil,
244-
expect: &ethapi.ExecutionResult{
245+
expect: &logger.ExecutionResult{
245246
Gas: params.TxGas,
246247
Failed: false,
247248
ReturnValue: "",
248-
StructLogs: []ethapi.StructLogRes{},
249+
StructLogs: []logger.StructLogRes{},
249250
},
250251
},
251252
// Standard JSON trace upon the non-existent block, error expects
@@ -270,11 +271,11 @@ func TestTraceCall(t *testing.T) {
270271
},
271272
config: nil,
272273
expectErr: nil,
273-
expect: &ethapi.ExecutionResult{
274+
expect: &logger.ExecutionResult{
274275
Gas: params.TxGas,
275276
Failed: false,
276277
ReturnValue: "",
277-
StructLogs: []ethapi.StructLogRes{},
278+
StructLogs: []logger.StructLogRes{},
278279
},
279280
},
280281
// Tracing on 'pending' should fail
@@ -287,7 +288,6 @@ func TestTraceCall(t *testing.T) {
287288
},
288289
config: nil,
289290
expectErr: errors.New("tracing on top of pending is not supported"),
290-
expect: nil,
291291
},
292292
}
293293
for _, testspec := range testSuite {
@@ -305,8 +305,12 @@ func TestTraceCall(t *testing.T) {
305305
t.Errorf("Expect no error, get %v", err)
306306
continue
307307
}
308-
if !reflect.DeepEqual(result, testspec.expect) {
309-
t.Errorf("Result mismatch, want %v, get %v", testspec.expect, result)
308+
var have *logger.ExecutionResult
309+
if err := json.Unmarshal(result.(json.RawMessage), &have); err != nil {
310+
t.Errorf("failed to unmarshal result %v", err)
311+
}
312+
if !reflect.DeepEqual(have, testspec.expect) {
313+
t.Errorf("Result mismatch, want %v, get %v", testspec.expect, have)
310314
}
311315
}
312316
}
@@ -339,11 +343,15 @@ func TestTraceTransaction(t *testing.T) {
339343
if err != nil {
340344
t.Errorf("Failed to trace transaction %v", err)
341345
}
342-
if !reflect.DeepEqual(result, &ethapi.ExecutionResult{
346+
var have *logger.ExecutionResult
347+
if err := json.Unmarshal(result.(json.RawMessage), &have); err != nil {
348+
t.Errorf("failed to unmarshal result %v", err)
349+
}
350+
if !reflect.DeepEqual(have, &logger.ExecutionResult{
343351
Gas: params.TxGas,
344352
Failed: false,
345353
ReturnValue: "",
346-
StructLogs: []ethapi.StructLogRes{},
354+
StructLogs: []logger.StructLogRes{},
347355
}) {
348356
t.Error("Transaction tracing result is different")
349357
}

eth/tracers/logger/logger.go

Lines changed: 117 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ package logger
1818

1919
import (
2020
"encoding/hex"
21+
"encoding/json"
2122
"fmt"
2223
"io"
2324
"math/big"
2425
"strings"
26+
"sync/atomic"
2527
"time"
2628

2729
"github.com/XinFinOrg/XDPoSChain/common"
@@ -109,10 +111,15 @@ type StructLogger struct {
109111
cfg Config
110112
env *vm.EVM
111113

112-
storage map[common.Address]Storage
113-
logs []StructLog
114-
output []byte
115-
err error
114+
storage map[common.Address]Storage
115+
logs []StructLog
116+
output []byte
117+
err error
118+
gasLimit uint64
119+
usedGas uint64
120+
121+
interrupt uint32 // Atomic flag to signal execution interruption
122+
reason error // Textual reason for the interruption
116123
}
117124

118125
// NewStructLogger returns a new logger
@@ -143,13 +150,19 @@ func (l *StructLogger) CaptureStart(env *vm.EVM, from common.Address, to common.
143150
//
144151
// CaptureState also tracks SLOAD/SSTORE ops to track storage change.
145152
func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
146-
memory := scope.Memory
147-
stack := scope.Stack
148-
contract := scope.Contract
153+
// If tracing was interrupted, set the error and stop
154+
if atomic.LoadUint32(&l.interrupt) > 0 {
155+
l.env.Cancel()
156+
return
157+
}
149158
// check if already accumulated the specified number of logs
150159
if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) {
151160
return
152161
}
162+
163+
memory := scope.Memory
164+
stack := scope.Stack
165+
contract := scope.Contract
153166
// Copy a snapshot of the current memory state to a new buffer
154167
var mem []byte
155168
if l.cfg.EnableMemory {
@@ -222,11 +235,42 @@ func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration
222235
func (l *StructLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
223236
}
224237

225-
func (l *StructLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}
238+
func (l *StructLogger) CaptureExit(output []byte, gasUsed uint64, err error) {
239+
}
240+
241+
func (l *StructLogger) GetResult() (json.RawMessage, error) {
242+
// Tracing aborted
243+
if l.reason != nil {
244+
return nil, l.reason
245+
}
246+
failed := l.err != nil
247+
returnData := common.CopyBytes(l.output)
248+
// Return data when successful and revert reason when reverted, otherwise empty.
249+
returnVal := fmt.Sprintf("%x", returnData)
250+
if failed && l.err != vm.ErrExecutionReverted {
251+
returnVal = ""
252+
}
253+
return json.Marshal(&ExecutionResult{
254+
Gas: l.usedGas,
255+
Failed: failed,
256+
ReturnValue: returnVal,
257+
StructLogs: formatLogs(l.StructLogs()),
258+
})
259+
}
226260

227-
func (*StructLogger) CaptureTxStart(gasLimit uint64) {}
261+
// Stop terminates execution of the tracer at the first opportune moment.
262+
func (l *StructLogger) Stop(err error) {
263+
l.reason = err
264+
atomic.StoreUint32(&l.interrupt, 1)
265+
}
228266

229-
func (*StructLogger) CaptureTxEnd(restGas uint64) {}
267+
func (l *StructLogger) CaptureTxStart(gasLimit uint64) {
268+
l.gasLimit = gasLimit
269+
}
270+
271+
func (l *StructLogger) CaptureTxEnd(restGas uint64) {
272+
l.usedGas = l.gasLimit - restGas
273+
}
230274

231275
// StructLogs returns the captured log entries.
232276
func (l *StructLogger) StructLogs() []StructLog { return l.logs }
@@ -356,3 +400,66 @@ func (t *mdLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}
356400
func (*mdLogger) CaptureTxStart(gasLimit uint64) {}
357401

358402
func (*mdLogger) CaptureTxEnd(restGas uint64) {}
403+
404+
// ExecutionResult groups all structured logs emitted by the EVM
405+
// while replaying a transaction in debug mode as well as transaction
406+
// execution status, the amount of gas used and the return value
407+
type ExecutionResult struct {
408+
Gas uint64 `json:"gas"`
409+
Failed bool `json:"failed"`
410+
ReturnValue string `json:"returnValue"`
411+
StructLogs []StructLogRes `json:"structLogs"`
412+
}
413+
414+
// StructLogRes stores a structured log emitted by the EVM while replaying a
415+
// transaction in debug mode
416+
type StructLogRes struct {
417+
Pc uint64 `json:"pc"`
418+
Op string `json:"op"`
419+
Gas uint64 `json:"gas"`
420+
GasCost uint64 `json:"gasCost"`
421+
Depth int `json:"depth"`
422+
Error string `json:"error,omitempty"`
423+
Stack *[]string `json:"stack,omitempty"`
424+
Memory *[]string `json:"memory,omitempty"`
425+
Storage *map[string]string `json:"storage,omitempty"`
426+
RefundCounter uint64 `json:"refund,omitempty"`
427+
}
428+
429+
// formatLogs formats EVM returned structured logs for json output
430+
func formatLogs(logs []StructLog) []StructLogRes {
431+
formatted := make([]StructLogRes, len(logs))
432+
for index, trace := range logs {
433+
formatted[index] = StructLogRes{
434+
Pc: trace.Pc,
435+
Op: trace.Op.String(),
436+
Gas: trace.Gas,
437+
GasCost: trace.GasCost,
438+
Depth: trace.Depth,
439+
Error: trace.ErrorString(),
440+
RefundCounter: trace.RefundCounter,
441+
}
442+
if trace.Stack != nil {
443+
stack := make([]string, len(trace.Stack))
444+
for i, stackValue := range trace.Stack {
445+
stack[i] = stackValue.Hex()
446+
}
447+
formatted[index].Stack = &stack
448+
}
449+
if trace.Memory != nil {
450+
memory := make([]string, 0, (len(trace.Memory)+31)/32)
451+
for i := 0; i+32 <= len(trace.Memory); i += 32 {
452+
memory = append(memory, fmt.Sprintf("%x", trace.Memory[i:i+32]))
453+
}
454+
formatted[index].Memory = &memory
455+
}
456+
if trace.Storage != nil {
457+
storage := make(map[string]string)
458+
for i, storageValue := range trace.Storage {
459+
storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue)
460+
}
461+
formatted[index].Storage = &storage
462+
}
463+
}
464+
return formatted
465+
}

0 commit comments

Comments
 (0)