Unverified Commit 8e69622c authored by Delweng's avatar Delweng Committed by GitHub

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: 's avatarDelweng <delweng@gmail.com>
Co-authored-by: 's avatarSina Mahmoodi <itz.s1na@gmail.com>
parent b0d44338
......@@ -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))
......
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"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"
}
}
......@@ -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)
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment