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:
parent
b0d44338bb
commit
8e69622c68
@ -46,6 +46,13 @@ type callContext struct {
|
|||||||
Miner common.Address `json:"miner"`
|
Miner common.Address `json:"miner"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// callLog is the result of LOG opCode
|
||||||
|
type callLog struct {
|
||||||
|
Address common.Address `json:"address"`
|
||||||
|
Topics []common.Hash `json:"topics"`
|
||||||
|
Data hexutil.Bytes `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
// callTrace is the result of a callTracer run.
|
// callTrace is the result of a callTracer run.
|
||||||
type callTrace struct {
|
type callTrace struct {
|
||||||
From common.Address `json:"from"`
|
From common.Address `json:"from"`
|
||||||
@ -57,6 +64,7 @@ type callTrace struct {
|
|||||||
Error string `json:"error,omitempty"`
|
Error string `json:"error,omitempty"`
|
||||||
Revertal string `json:"revertReason,omitempty"`
|
Revertal string `json:"revertReason,omitempty"`
|
||||||
Calls []callTrace `json:"calls,omitempty"`
|
Calls []callTrace `json:"calls,omitempty"`
|
||||||
|
Logs []callLog `json:"logs,omitempty"`
|
||||||
Value *hexutil.Big `json:"value,omitempty"`
|
Value *hexutil.Big `json:"value,omitempty"`
|
||||||
// Gencodec adds overridden fields at the end
|
// Gencodec adds overridden fields at the end
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
@ -81,6 +89,10 @@ func TestCallTracerNative(t *testing.T) {
|
|||||||
testCallTracer("callTracer", "call_tracer", t)
|
testCallTracer("callTracer", "call_tracer", t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCallTracerNativeWithLog(t *testing.T) {
|
||||||
|
testCallTracer("callTracer", "call_tracer_withLog", t)
|
||||||
|
}
|
||||||
|
|
||||||
func testCallTracer(tracerName string, dirPath string, t *testing.T) {
|
func testCallTracer(tracerName string, dirPath string, t *testing.T) {
|
||||||
isLegacy := strings.HasSuffix(dirPath, "_legacy")
|
isLegacy := strings.HasSuffix(dirPath, "_legacy")
|
||||||
files, err := os.ReadDir(filepath.Join("testdata", dirPath))
|
files, err := os.ReadDir(filepath.Join("testdata", dirPath))
|
||||||
|
115
eth/tracers/internal/tracetest/testdata/call_tracer_withLog/calldata.json
vendored
Normal file
115
eth/tracers/internal/tracetest/testdata/call_tracer_withLog/calldata.json
vendored
Normal file
File diff suppressed because one or more lines are too long
400
eth/tracers/internal/tracetest/testdata/call_tracer_withLog/delegatecall.json
vendored
Normal file
400
eth/tracers/internal/tracetest/testdata/call_tracer_withLog/delegatecall.json
vendored
Normal file
File diff suppressed because one or more lines are too long
2295
eth/tracers/internal/tracetest/testdata/call_tracer_withLog/multi_contracts.json
vendored
Normal file
2295
eth/tracers/internal/tracetest/testdata/call_tracer_withLog/multi_contracts.json
vendored
Normal file
File diff suppressed because one or more lines are too long
530
eth/tracers/internal/tracetest/testdata/call_tracer_withLog/multilogs.json
vendored
Normal file
530
eth/tracers/internal/tracetest/testdata/call_tracer_withLog/multilogs.json
vendored
Normal file
File diff suppressed because one or more lines are too long
286
eth/tracers/internal/tracetest/testdata/call_tracer_withLog/notopic.json
vendored
Normal file
286
eth/tracers/internal/tracetest/testdata/call_tracer_withLog/notopic.json
vendored
Normal file
File diff suppressed because one or more lines are too long
84
eth/tracers/internal/tracetest/testdata/call_tracer_withLog/simple.json
vendored
Normal file
84
eth/tracers/internal/tracetest/testdata/call_tracer_withLog/simple.json
vendored
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
{
|
||||||
|
"genesis": {
|
||||||
|
"difficulty": "8430028481555",
|
||||||
|
"extraData": "0xd783010302844765746887676f312e352e31856c696e7578",
|
||||||
|
"gasLimit": "3141592",
|
||||||
|
"hash": "0xde66937783697293f2e529d2034887c531535d78afa8c9051511ae12ba48fbea",
|
||||||
|
"miner": "0x2a65aca4d5fc5b5c859090a6c34d164135398226",
|
||||||
|
"mixHash": "0xba28a43bfbca4a2effbb76bb70d03482a8a0c92e2883ff36cbac3d7c6dbb7df5",
|
||||||
|
"nonce": "0xa3827ec0a82fe823",
|
||||||
|
"number": "765824",
|
||||||
|
"stateRoot": "0x8d96cb027a29f8ca0ccd6d31f9ea0656136ec8030ecda70bb9231849ed6f41a2",
|
||||||
|
"timestamp": "1451389443",
|
||||||
|
"totalDifficulty": "4838314986494741271",
|
||||||
|
"alloc": {
|
||||||
|
"0xd1220a0cf47c7b9be7a2e6ba89f429762e7b9adb": {
|
||||||
|
"balance": "0x14203bee2ea6fbe8c",
|
||||||
|
"nonce": "34"
|
||||||
|
},
|
||||||
|
"0xe2fe6b13287f28e193333fdfe7fedf2f6df6124a": {
|
||||||
|
"balance": "0x2717a9c870a286f4350"
|
||||||
|
},
|
||||||
|
"0xf4eced2f682ce333f96f2d8966c613ded8fc95dd": {
|
||||||
|
"balance": "0x0",
|
||||||
|
"code": "0x606060405260e060020a600035046306fdde038114610047578063313ce567146100a457806370a08231146100b057806395d89b41146100c8578063a9059cbb14610123575b005b61015260008054602060026001831615610100026000190190921691909104601f810182900490910260809081016040526060828152929190828280156101f55780601f106101ca576101008083540402835291602001916101f5565b6101c060025460ff1681565b6101c060043560036020526000908152604090205481565b610152600180546020601f6002600019610100858716150201909316929092049182018190040260809081016040526060828152929190828280156101f55780601f106101ca576101008083540402835291602001916101f5565b610045600435602435600160a060020a033316600090815260036020526040902054819010156101fd57610002565b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156101b25780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6060908152602090f35b820191906000526020600020905b8154815290600101906020018083116101d857829003601f168201915b505050505081565b600160a060020a03821660009081526040902054808201101561021f57610002565b806003600050600033600160a060020a03168152602001908152602001600020600082828250540392505081905550806003600050600084600160a060020a0316815260200190815260200160002060008282825054019250508190555081600160a060020a031633600160a060020a03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a3505056",
|
||||||
|
"storage": {
|
||||||
|
"0x1dae8253445d3a5edbe8200da9fc39bc4f11db9362181dc1b640d08c3c2fb4d6": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"0x8ba52aac7f255d80a49abcf003d6af4752aba5a9531cae94fde7ac8d72191d67": "0x000000000000000000000000000000000000000000000000000000000178e460"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"chainId": 1,
|
||||||
|
"homesteadBlock": 1150000,
|
||||||
|
"daoForkBlock": 1920000,
|
||||||
|
"daoForkSupport": true,
|
||||||
|
"eip150Block": 2463000,
|
||||||
|
"eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0",
|
||||||
|
"eip155Block": 2675000,
|
||||||
|
"eip158Block": 2675000,
|
||||||
|
"byzantiumBlock": 4370000,
|
||||||
|
"constantinopleBlock": 7280000,
|
||||||
|
"petersburgBlock": 7280000,
|
||||||
|
"istanbulBlock": 9069000,
|
||||||
|
"muirGlacierBlock": 9200000,
|
||||||
|
"berlinBlock": 12244000,
|
||||||
|
"londonBlock": 12965000,
|
||||||
|
"arrowGlacierBlock": 13773000,
|
||||||
|
"grayGlacierBlock": 15050000,
|
||||||
|
"terminalTotalDifficultyPassed": true,
|
||||||
|
"ethash": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"context": {
|
||||||
|
"number": "765825",
|
||||||
|
"difficulty": "8425912256743",
|
||||||
|
"timestamp": "1451389488",
|
||||||
|
"gasLimit": "3141592",
|
||||||
|
"miner": "0xe2fe6b13287f28e193333fdfe7fedf2f6df6124a"
|
||||||
|
},
|
||||||
|
"input": "0xf8aa22850ba43b740083024d4594f4eced2f682ce333f96f2d8966c613ded8fc95dd80b844a9059cbb000000000000000000000000dbf03b407c01e7cd3cbea99509d93f8dddc8c6fb00000000000000000000000000000000000000000000000000000000009896801ca067da548a2e0f381a957b9b51f086073375d6bfc7312cbc9540b3647ccab7db11a042c6e5b34bc7ba821e9c25b166fa13d82ad4b0d044d16174d5587d4f04ecfcd1",
|
||||||
|
"tracerConfig": {
|
||||||
|
"withLog": true
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"from": "0xd1220a0cf47c7b9be7a2e6ba89f429762e7b9adb",
|
||||||
|
"gas": "0x1f36d",
|
||||||
|
"gasUsed": "0xc6a5",
|
||||||
|
"to": "0xf4eced2f682ce333f96f2d8966c613ded8fc95dd",
|
||||||
|
"input": "0xa9059cbb000000000000000000000000dbf03b407c01e7cd3cbea99509d93f8dddc8c6fb0000000000000000000000000000000000000000000000000000000000989680",
|
||||||
|
"logs": [
|
||||||
|
{
|
||||||
|
"address": "0xf4eced2f682ce333f96f2d8966c613ded8fc95dd",
|
||||||
|
"topics": [
|
||||||
|
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
|
||||||
|
"0x000000000000000000000000d1220a0cf47c7b9be7a2e6ba89f429762e7b9adb",
|
||||||
|
"0x000000000000000000000000dbf03b407c01e7cd3cbea99509d93f8dddc8c6fb"
|
||||||
|
],
|
||||||
|
"data": "0x0000000000000000000000000000000000000000000000000000000000989680"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"value": "0x0",
|
||||||
|
"type": "CALL"
|
||||||
|
}
|
||||||
|
}
|
244
eth/tracers/internal/tracetest/testdata/call_tracer_withLog/tx_failed.json
vendored
Normal file
244
eth/tracers/internal/tracetest/testdata/call_tracer_withLog/tx_failed.json
vendored
Normal file
File diff suppressed because one or more lines are too long
107
eth/tracers/internal/tracetest/testdata/call_tracer_withLog/tx_partial_failed.json
vendored
Normal file
107
eth/tracers/internal/tracetest/testdata/call_tracer_withLog/tx_partial_failed.json
vendored
Normal file
File diff suppressed because one or more lines are too long
89
eth/tracers/internal/tracetest/testdata/call_tracer_withLog/with_onlyTopCall.json
vendored
Normal file
89
eth/tracers/internal/tracetest/testdata/call_tracer_withLog/with_onlyTopCall.json
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -36,6 +36,12 @@ func init() {
|
|||||||
register("callTracer", newCallTracer)
|
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 callFrame struct {
|
||||||
Type vm.OpCode `json:"-"`
|
Type vm.OpCode `json:"-"`
|
||||||
From common.Address `json:"from"`
|
From common.Address `json:"from"`
|
||||||
@ -47,6 +53,7 @@ type callFrame struct {
|
|||||||
Error string `json:"error,omitempty" rlp:"optional"`
|
Error string `json:"error,omitempty" rlp:"optional"`
|
||||||
Revertal string `json:"revertReason,omitempty"`
|
Revertal string `json:"revertReason,omitempty"`
|
||||||
Calls []callFrame `json:"calls,omitempty" rlp:"optional"`
|
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
|
// 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.
|
// nil if there are non-empty elements after in the struct.
|
||||||
Value *big.Int `json:"value,omitempty" rlp:"optional"`
|
Value *big.Int `json:"value,omitempty" rlp:"optional"`
|
||||||
@ -56,6 +63,10 @@ func (f callFrame) TypeString() string {
|
|||||||
return f.Type.String()
|
return f.Type.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f callFrame) failed() bool {
|
||||||
|
return len(f.Error) > 0
|
||||||
|
}
|
||||||
|
|
||||||
func (f *callFrame) capture(output []byte, err error) {
|
func (f *callFrame) capture(output []byte, err error) {
|
||||||
output = common.CopyBytes(output)
|
output = common.CopyBytes(output)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -98,6 +109,7 @@ type callTracer struct {
|
|||||||
|
|
||||||
type callTracerConfig struct {
|
type callTracerConfig struct {
|
||||||
OnlyTopCall bool `json:"onlyTopCall"` // If true, call tracer won't collect any subcalls
|
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
|
// 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.
|
// 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) {
|
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.
|
// 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).
|
// 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) {
|
func (t *callTracer) CaptureTxEnd(restGas uint64) {
|
||||||
t.callstack[0].GasUsed = t.gasLimit - restGas
|
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
|
// 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
|
t.reason = err
|
||||||
atomic.StoreUint32(&t.interrupt, 1)
|
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"`
|
Error string `json:"error,omitempty" rlp:"optional"`
|
||||||
Revertal string `json:"revertReason,omitempty"`
|
Revertal string `json:"revertReason,omitempty"`
|
||||||
Calls []callFrame `json:"calls,omitempty" rlp:"optional"`
|
Calls []callFrame `json:"calls,omitempty" rlp:"optional"`
|
||||||
|
Logs []callLog `json:"logs,omitempty" rlp:"optional"`
|
||||||
Value *hexutil.Big `json:"value,omitempty" rlp:"optional"`
|
Value *hexutil.Big `json:"value,omitempty" rlp:"optional"`
|
||||||
TypeString string `json:"type"`
|
TypeString string `json:"type"`
|
||||||
}
|
}
|
||||||
@ -40,6 +41,7 @@ func (c callFrame) MarshalJSON() ([]byte, error) {
|
|||||||
enc.Error = c.Error
|
enc.Error = c.Error
|
||||||
enc.Revertal = c.Revertal
|
enc.Revertal = c.Revertal
|
||||||
enc.Calls = c.Calls
|
enc.Calls = c.Calls
|
||||||
|
enc.Logs = c.Logs
|
||||||
enc.Value = (*hexutil.Big)(c.Value)
|
enc.Value = (*hexutil.Big)(c.Value)
|
||||||
enc.TypeString = c.TypeString()
|
enc.TypeString = c.TypeString()
|
||||||
return json.Marshal(&enc)
|
return json.Marshal(&enc)
|
||||||
@ -58,6 +60,7 @@ func (c *callFrame) UnmarshalJSON(input []byte) error {
|
|||||||
Error *string `json:"error,omitempty" rlp:"optional"`
|
Error *string `json:"error,omitempty" rlp:"optional"`
|
||||||
Revertal *string `json:"revertReason,omitempty"`
|
Revertal *string `json:"revertReason,omitempty"`
|
||||||
Calls []callFrame `json:"calls,omitempty" rlp:"optional"`
|
Calls []callFrame `json:"calls,omitempty" rlp:"optional"`
|
||||||
|
Logs []callLog `json:"logs,omitempty" rlp:"optional"`
|
||||||
Value *hexutil.Big `json:"value,omitempty" rlp:"optional"`
|
Value *hexutil.Big `json:"value,omitempty" rlp:"optional"`
|
||||||
}
|
}
|
||||||
var dec callFrame0
|
var dec callFrame0
|
||||||
@ -94,6 +97,9 @@ func (c *callFrame) UnmarshalJSON(input []byte) error {
|
|||||||
if dec.Calls != nil {
|
if dec.Calls != nil {
|
||||||
c.Calls = dec.Calls
|
c.Calls = dec.Calls
|
||||||
}
|
}
|
||||||
|
if dec.Logs != nil {
|
||||||
|
c.Logs = dec.Logs
|
||||||
|
}
|
||||||
if dec.Value != nil {
|
if dec.Value != nil {
|
||||||
c.Value = (*big.Int)(dec.Value)
|
c.Value = (*big.Int)(dec.Value)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user