Skip to content

eth/tracers/native: add erc7562 tracer #31006

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
May 20, 2025

Conversation

shahafn
Copy link
Contributor

@shahafn shahafn commented Jan 8, 2025

Resolves #30546

@shahafn shahafn requested a review from s1na as a code owner January 8, 2025 14:50
@rjl493456442
Copy link
Member

rjl493456442 commented Jan 9, 2025

Probably relevant with #30546 , @s1na please check it out.

Copy link
Member

@MariusVanDerWijden MariusVanDerWijden left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not opposed the idea of adding a 4337 style native tracer, but this one needs a bunch of refactors imo

@@ -0,0 +1,550 @@
// Copyright 2021 The go-ethereum Authors
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Copyright 2021 The go-ethereum Authors
// Copyright 2025 The go-ethereum Authors

}

type callFrameWithOpcodes struct {
Type vm.OpCode `json:"-"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYT about reusing some code from the call tracer?

diff --git a/eth/tracers/native/erc7562.go b/eth/tracers/native/erc7562.go
index cd1a48975f..7f80c7d168 100644
--- a/eth/tracers/native/erc7562.go
+++ b/eth/tracers/native/erc7562.go
@@ -48,20 +48,7 @@ type contractSizeWithOpcode struct {
 }
 
 type callFrameWithOpcodes struct {
-       Type         vm.OpCode       `json:"-"`
-       From         common.Address  `json:"from"`
-       Gas          uint64          `json:"gas"`
-       GasUsed      uint64          `json:"gasUsed"`
-       To           *common.Address `json:"to,omitempty" rlp:"optional"`
-       Input        []byte          `json:"input" rlp:"optional"`
-       Output       []byte          `json:"output,omitempty" rlp:"optional"`
-       Error        string          `json:"error,omitempty" rlp:"optional"`
-       RevertReason string          `json:"revertReason,omitempty"`
-       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"`
-       revertedSnapshot bool
+       callFrame
 
        AccessedSlots     accessedSlots                              `json:"accessedSlots"`
        ExtCodeAccessInfo []common.Address                           `json:"extCodeAccessInfo"`
@@ -228,12 +215,14 @@ func (t *erc7562Tracer) OnEnter(depth int, typ byte, from common.Address, to com
 
        toCopy := to
        call := callFrameWithOpcodes{
-               Type:  vm.OpCode(typ),
-               From:  from,
-               To:    &toCopy,
-               Input: common.CopyBytes(input),
-               Gas:   gas,
-               Value: value,
+               callFrame: callFrame{
+                       Type:  vm.OpCode(typ),
+                       From:  from,
+                       To:    &toCopy,
+                       Input: common.CopyBytes(input),
+                       Gas:   gas,
+                       Value: value,
+               },
                AccessedSlots: accessedSlots{
                        Reads:           map[string][]string{},
                        Writes:          map[string]uint64{},
                        ```

"github.com/holiman/uint256"
)

//go:generate go run github.com/fjl/gencodec -type callFrameWithOpcodes -field-override callFrameWithOpcodesMarshaling -out gen_callframewithopcodes_json.go
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//go:generate go run github.com/fjl/gencodec -type callFrameWithOpcodes -field-override callFrameWithOpcodesMarshaling -out gen_callframewithopcodes_json.go
//go:generate go run github.com/fjl/gencodec -type callFrameWithOpcodes -field-override callFrameMarshaling -out gen_callframewithopcodes_json.go

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

go generate doesn't work with that, and it seems that embedded structs aren't supported in gencodec. So my solution was to flatten out the struct instead. Wdyt?

}

// catchPanic handles panic recovery and logs the panic and stack trace.
func catchPanic() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really don't like this pattern. Your code should be correct, you should not rely on recovering. Rather fail fast and fix quickly

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I'd like to second this. Did you need this for debug purposes?

Panics here should not crash the node. API internal has recovery mechanism from panics. If you managed to crash the node then that's an issue we should look at.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I needed that for debugging purposes, since the errors on the hooks were silently ignored by geth it seems. I'm removing it.

return config
}

func newErc7562TracerObject(ctx *tracers.Context, cfg json.RawMessage) (*erc7562Tracer, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func newErc7562TracerObject(ctx *tracers.Context, cfg json.RawMessage) (*erc7562Tracer, error) {
func newErc7562TracerObject(cfg json.RawMessage) (*erc7562Tracer, error) {

ctx is unused here, no need to pass it then

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tracers.DefaultDirectory.Register("erc7562Tracer", newErc7562Tracer, false) expects a certain signature for the tracer that accepts that argument. That's also the case in the original callTracer

t.callstackWithOpcodes = append(t.callstackWithOpcodes, call)
}

func (t *erc7562Tracer) captureEnd(output []byte, gasUsed uint64, err error, reverted bool) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func (t *erc7562Tracer) captureEnd(output []byte, gasUsed uint64, err error, reverted bool) {
func (t *erc7562Tracer) captureEnd(output []byte, err error, reverted bool) {

gas used is unused

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

}

func incrementCount[K comparable](m map[K]uint64, k K) {
m[k] = m[k] + 1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry what?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

for i := 0; i < t.config.StackTopItemsSize && i < stackSize; i++ {
stackTopItems = append(stackTopItems, *peepStack(scope.StackData(), i))
}
opcodeWithStack = &opcodeWithPartialStack{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would switch on the opcode here instead of calling into multiple methods that might or might not do anything

@s1na
Copy link
Contributor

s1na commented Jan 21, 2025

It would be nice to have some spec-compliance tests.

ignoredOpcodes map[vm.OpCode]struct{}
callstackWithOpcodes []callFrameWithOpcodes
lastOpWithStack *opcodeWithPartialStack
Keccak map[string]struct{} `json:"keccak"`
Copy link
Contributor

@s1na s1na Jan 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this field can be un-exported and the json tag is not needed. This struct is not directly serialized.

- Removing unused code
- Adding test cases
@s1na
Copy link
Contributor

s1na commented Feb 21, 2025

@shahafn I see that you have added some test fixtures. I think you forgot to commit the test runner. Please push that too.

AccessedSlots accessedSlots `json:"accessedSlots"`
ExtCodeAccessInfo []common.Address `json:"extCodeAccessInfo"`
UsedOpcodes map[vm.OpCode]uint64 `json:"usedOpcodes"`
ContractSize map[common.Address]*contractSizeWithOpcode `json:"contractSize"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please add a short comment on these "new" fields what they should collect?

Output hexutil.Bytes
AccessedSlots accessedSlots `json:"accessedSlots"`
ExtCodeAccessInfo []common.Address `json:"extCodeAccessInfo"`
UsedOpcodes map[vm.OpCode]uint64 `json:"usedOpcodes"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably also use hexutil.Uint64 here for consistency?

UsedOpcodes map[vm.OpCode]uint64 `json:"usedOpcodes"`
ContractSize map[common.Address]*contractSizeWithOpcode `json:"contractSize"`
OutOfGas bool `json:"outOfGas"`
Calls []callFrameWithOpcodes `json:"calls,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the Marshaling struct you only need to specify the fields which you wish to override the type for. So in this case Calls is not necessary here, Same with OutOfGas, ContractSize, ExtCodeAccessInfo and AccessedSlots.

The reason for using this double-struct approach is so that in the tracer logic we can simply use normal Go types (like uint64) and when calling json.Marshal it will "automatically" understand how to encode them because we provide hints here that encoding should use these other specialized types (e.g. hexutil.Uint64).

type accessedSlots struct {
Reads map[string][]string `json:"reads"`
Writes map[string]uint64 `json:"writes"`
TransientReads map[string]uint64 `json:"transientReads"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have to agree on the output format. I think it's not great that some fields are decimal numbers in JSON and some are hex-encoded.

shahafn added 3 commits March 24, 2025 00:30
Comparing unmarshaled objects and sorting keccak array to prevent
cosmetic diffs
@lightclient
Copy link
Member

What do we want to do about this? On one hand it seems like a good thing for geth to offer, on another it is a complex tracer that we are not the experts in yet will need to maintain in perpetuity.

@shahafn
Copy link
Contributor Author

shahafn commented May 7, 2025

Hey @lightclient
We're planning on maintaining the tracer ourselves if that helps!

@s1na
Copy link
Contributor

s1na commented May 18, 2025

I have pushed the changes I had in mind here: https://github.com/s1na/go-ethereum/tree/erc7562-tracer. I can't directly push to your branch, it seems the organization should provide the permission. You can alternatively integrate from my branch directly. Otherwise it looks good to me.

* refactor keccak preimages

* simplify tests

* rm depth field

* more interrup handling

* hex opcode in output

* Respect ignored opcodes

* minor readability change

* common.Hash for storage access fields
s1na
s1na previously approved these changes May 19, 2025
Copy link
Contributor

@s1na s1na left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Contributor

@s1na s1na left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second time's the charm

@s1na s1na changed the title Adding erc7562 tracer eth/tracers/native: add erc7562 tracer May 20, 2025
@s1na s1na added this to the 1.15.12 milestone May 20, 2025
@s1na s1na merged commit 62aa6b2 into ethereum:master May 20, 2025
3 of 4 checks passed
Dargon789 pushed a commit to Dargon789/go-ethereum that referenced this pull request May 27, 2025
This PR introduces a new native tracer for AA bundlers. Bundlers participating in the alternative
mempool will need to validate userops. This tracer will return sufficient information for them to
decide whether griefing is possible. Resolves ethereum#30546

---------

Co-authored-by: Sina M <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Built-in ERC-4337 tracer
5 participants