mirror of https://github.com/status-im/op-geth.git
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"`
|
||||
}
|
||||
|
||||
// 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.
|
||||
type callTrace struct {
|
||||
From common.Address `json:"from"`
|
||||
|
@ -57,6 +64,7 @@ type callTrace struct {
|
|||
Error string `json:"error,omitempty"`
|
||||
Revertal string `json:"revertReason,omitempty"`
|
||||
Calls []callTrace `json:"calls,omitempty"`
|
||||
Logs []callLog `json:"logs,omitempty"`
|
||||
Value *hexutil.Big `json:"value,omitempty"`
|
||||
// Gencodec adds overridden fields at the end
|
||||
Type string `json:"type"`
|
||||
|
@ -81,6 +89,10 @@ func TestCallTracerNative(t *testing.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) {
|
||||
isLegacy := strings.HasSuffix(dirPath, "_legacy")
|
||||
files, err := os.ReadDir(filepath.Join("testdata", dirPath))
|
||||
|
|
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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -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"
|
||||
}
|
||||
}
|
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue