eth/tracers: add withLog to callTracer (#25991)
In some cases, it is desirable to capture what is triggered by each trace, when using the `callTracer`. For example: call `USDT.transfer` will trigger a `Transfer(from, to, value)` event.
This PR adds the option to capture logs to the call tracer, by specifying `{"withLog": true}` in the tracerconfig.
Any logs belonging to failed/reverted call-scopes are removed from the output, to prevent interpretation mistakes.
Signed-off-by: Delweng <delweng@gmail.com>
Co-authored-by: Sina Mahmoodi <itz.s1na@gmail.com>
This commit is contained in:
@@ -36,6 +36,12 @@ func init() {
|
||||
register("callTracer", newCallTracer)
|
||||
}
|
||||
|
||||
type callLog struct {
|
||||
Address common.Address `json:"address"`
|
||||
Topics []common.Hash `json:"topics"`
|
||||
Data hexutil.Bytes `json:"data"`
|
||||
}
|
||||
|
||||
type callFrame struct {
|
||||
Type vm.OpCode `json:"-"`
|
||||
From common.Address `json:"from"`
|
||||
@@ -47,6 +53,7 @@ type callFrame struct {
|
||||
Error string `json:"error,omitempty" rlp:"optional"`
|
||||
Revertal string `json:"revertReason,omitempty"`
|
||||
Calls []callFrame `json:"calls,omitempty" rlp:"optional"`
|
||||
Logs []callLog `json:"logs,omitempty" rlp:"optional"`
|
||||
// Placed at end on purpose. The RLP will be decoded to 0 instead of
|
||||
// nil if there are non-empty elements after in the struct.
|
||||
Value *big.Int `json:"value,omitempty" rlp:"optional"`
|
||||
@@ -56,6 +63,10 @@ func (f callFrame) TypeString() string {
|
||||
return f.Type.String()
|
||||
}
|
||||
|
||||
func (f callFrame) failed() bool {
|
||||
return len(f.Error) > 0
|
||||
}
|
||||
|
||||
func (f *callFrame) capture(output []byte, err error) {
|
||||
output = common.CopyBytes(output)
|
||||
if err == nil {
|
||||
@@ -98,6 +109,7 @@ type callTracer struct {
|
||||
|
||||
type callTracerConfig struct {
|
||||
OnlyTopCall bool `json:"onlyTopCall"` // If true, call tracer won't collect any subcalls
|
||||
WithLog bool `json:"withLog"` // If true, call tracer will collect event logs
|
||||
}
|
||||
|
||||
// newCallTracer returns a native go tracer which tracks
|
||||
@@ -137,10 +149,38 @@ func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration,
|
||||
|
||||
// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
|
||||
func (t *callTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
|
||||
// Only logs need to be captured via opcode processing
|
||||
if !t.config.WithLog {
|
||||
return
|
||||
}
|
||||
// Avoid processing nested calls when only caring about top call
|
||||
if t.config.OnlyTopCall && depth > 0 {
|
||||
return
|
||||
}
|
||||
switch op {
|
||||
case vm.LOG0, vm.LOG1, vm.LOG2, vm.LOG3, vm.LOG4:
|
||||
size := int(op - vm.LOG0)
|
||||
|
||||
stack := scope.Stack
|
||||
stackData := stack.Data()
|
||||
|
||||
// Don't modify the stack
|
||||
mStart := stackData[len(stackData)-1]
|
||||
mSize := stackData[len(stackData)-2]
|
||||
topics := make([]common.Hash, size)
|
||||
for i := 0; i < size; i++ {
|
||||
topic := stackData[len(stackData)-2-(i+1)]
|
||||
topics[i] = common.Hash(topic.Bytes32())
|
||||
}
|
||||
|
||||
data := scope.Memory.GetCopy(int64(mStart.Uint64()), int64(mSize.Uint64()))
|
||||
log := callLog{Address: scope.Contract.Address(), Topics: topics, Data: hexutil.Bytes(data)}
|
||||
t.callstack[len(t.callstack)-1].Logs = append(t.callstack[len(t.callstack)-1].Logs, log)
|
||||
}
|
||||
}
|
||||
|
||||
// CaptureFault implements the EVMLogger interface to trace an execution fault.
|
||||
func (t *callTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) {
|
||||
func (t *callTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
|
||||
}
|
||||
|
||||
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
|
||||
@@ -191,6 +231,10 @@ func (t *callTracer) CaptureTxStart(gasLimit uint64) {
|
||||
|
||||
func (t *callTracer) CaptureTxEnd(restGas uint64) {
|
||||
t.callstack[0].GasUsed = t.gasLimit - restGas
|
||||
if t.config.WithLog {
|
||||
// Logs are not emitted when the call fails
|
||||
clearFailedLogs(&t.callstack[0], false)
|
||||
}
|
||||
}
|
||||
|
||||
// GetResult returns the json-encoded nested list of call traces, and any
|
||||
@@ -211,3 +255,16 @@ func (t *callTracer) Stop(err error) {
|
||||
t.reason = err
|
||||
atomic.StoreUint32(&t.interrupt, 1)
|
||||
}
|
||||
|
||||
// clearFailedLogs clears the logs of a callframe and all its children
|
||||
// in case of execution failure.
|
||||
func clearFailedLogs(cf *callFrame, parentFailed bool) {
|
||||
failed := cf.failed() || parentFailed
|
||||
// Clear own logs
|
||||
if failed {
|
||||
cf.Logs = nil
|
||||
}
|
||||
for i := range cf.Calls {
|
||||
clearFailedLogs(&cf.Calls[i], failed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ func (c callFrame) MarshalJSON() ([]byte, error) {
|
||||
Error string `json:"error,omitempty" rlp:"optional"`
|
||||
Revertal string `json:"revertReason,omitempty"`
|
||||
Calls []callFrame `json:"calls,omitempty" rlp:"optional"`
|
||||
Logs []callLog `json:"logs,omitempty" rlp:"optional"`
|
||||
Value *hexutil.Big `json:"value,omitempty" rlp:"optional"`
|
||||
TypeString string `json:"type"`
|
||||
}
|
||||
@@ -40,6 +41,7 @@ func (c callFrame) MarshalJSON() ([]byte, error) {
|
||||
enc.Error = c.Error
|
||||
enc.Revertal = c.Revertal
|
||||
enc.Calls = c.Calls
|
||||
enc.Logs = c.Logs
|
||||
enc.Value = (*hexutil.Big)(c.Value)
|
||||
enc.TypeString = c.TypeString()
|
||||
return json.Marshal(&enc)
|
||||
@@ -58,6 +60,7 @@ func (c *callFrame) UnmarshalJSON(input []byte) error {
|
||||
Error *string `json:"error,omitempty" rlp:"optional"`
|
||||
Revertal *string `json:"revertReason,omitempty"`
|
||||
Calls []callFrame `json:"calls,omitempty" rlp:"optional"`
|
||||
Logs []callLog `json:"logs,omitempty" rlp:"optional"`
|
||||
Value *hexutil.Big `json:"value,omitempty" rlp:"optional"`
|
||||
}
|
||||
var dec callFrame0
|
||||
@@ -94,6 +97,9 @@ func (c *callFrame) UnmarshalJSON(input []byte) error {
|
||||
if dec.Calls != nil {
|
||||
c.Calls = dec.Calls
|
||||
}
|
||||
if dec.Logs != nil {
|
||||
c.Logs = dec.Logs
|
||||
}
|
||||
if dec.Value != nil {
|
||||
c.Value = (*big.Int)(dec.Value)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user