Skip to content

feat(core/types): fine-grained Body RLP override #109

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 23 commits into from
Feb 5, 2025
Merged

Conversation

ARR4N
Copy link
Collaborator

@ARR4N ARR4N commented Jan 29, 2025

Why this should be merged

Allows for modification of types.Body payload data + RLP encoding without placing the entire RLP burden on the libevm user as we did with types.HeaderHooks.

How this works

RLP encoding of a struct is simply a concatenation of RLP encodings of fields, encompassed by an RLP "list". The AppendRLPFields(rlp.EncoderBuffer, ...) hook exploits this and plugs in before all rlp:"optional"-tagged fields to allow for inclusion of any new fields. The EncoderBuffer SHOULD be used as the io.Writer passed when encoding each field: rlp.Encode(buffer, fieldValue).

Body doesn't have {En,De}codeRLP methods so they are implemented to identically replicate original behaviour when a no-op hook is present.

This pattern is sufficient for the ava-labs/coreth modifications of Body but can be modified / extended for more complex scenarios, like Header.

Note

This PR does not include registration of the hooks as that was not the initial goal and adding them would create too much PR bloat. There is a placeholder var todoRegisteredBodyHooks global variable that can only be set in tests.

How this was tested

  • Backwards compatibility: the new methods are fuzzed against a type withoutMethods Body passed directly to rlp.{En,De}code()
  • coreth compatibility: unit test of a local implementation of BodyHooks demonstrating reproducibility of RLP encoding.

@ARR4N ARR4N added the Status: 🔴 DO NOT MERGE This PR is not meant to be merged in its current state label Jan 29, 2025
@ARR4N ARR4N mentioned this pull request Jan 29, 2025
2 tasks
Copy link

@qdm12 qdm12 left a comment

Choose a reason for hiding this comment

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

Nice 👍 ! Definitely a good starting point/PR-to-refer-to for a header rlp rework later.

@ARR4N ARR4N removed the Status: 🔴 DO NOT MERGE This PR is not meant to be merged in its current state label Feb 3, 2025
@ARR4N ARR4N marked this pull request as ready for review February 3, 2025 15:21
@ARR4N ARR4N requested review from qdm12, a team, darioush and michaelkaplan13 and removed request for a team February 3, 2025 16:26
@ARR4N ARR4N requested a review from qdm12 February 4, 2025 12:30
Copy link

@qdm12 qdm12 left a comment

Choose a reason for hiding this comment

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

Just nits and 1 meme. Overall looking pretty good! 💯

Co-authored-by: Quentin McGaw <[email protected]>
Signed-off-by: Arran Schlosberg <[email protected]>
Copy link

@darioush darioush left a comment

Choose a reason for hiding this comment

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

fwiw I prefer the version with the entire body overrides.

Parsing and unparsing RLP seems error prone and complex, the simple version which overrides the entire RLP call seems obviously correct without the need for fuzz tests or reasoning about optional fields. There will be some duplication between block and body, but that doesn't seem like a problem to me.

this said I don't want to block the progress here and we can take whichever approach works to parse the bytes.

package common

// PointerTo is a convenience wrapper for creating a pointer to a value.
func PointerTo[T any](x T) *T { return &x }
Copy link

Choose a reason for hiding this comment

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

nit: Should we consider adding new code to separate packages when they are not needed for use in the shared package with geth?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In the overwhelming number of cases I do this (in the /libevm directory) but see my rationale for adding to common.

@ARR4N ARR4N merged commit eda3b59 into main Feb 5, 2025
4 checks passed
@ARR4N ARR4N deleted the arr4n/body-rlp-override branch February 5, 2025 10:52
Comment on lines +147 to +148
t.Run("", func(t *testing.T) {
t.Logf("\n%s", pretty.Sprint(body))
Copy link

Choose a reason for hiding this comment

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

nit there is no point running this in a subtest then really

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It limits the effects of t.Fatal and require to only the sub test.

@ARR4N
Copy link
Collaborator Author

ARR4N commented Feb 5, 2025

fwiw I prefer the version with the entire body overrides.

Parsing and unparsing RLP seems error prone and complex, the simple version which overrides the entire RLP call seems obviously correct without the need for fuzz tests or reasoning about optional fields. There will be some duplication between block and body, but that doesn't seem like a problem to me.

this said I don't want to block the progress here and we can take whichever approach works to parse the bytes.

Sorry, I was viewing comments in the file-changed tab so didn't see this. Lets address it in our next meeting because I think it's complex enough to warrant in-person discussion rather than asynchronously.

qdm12 added a commit that referenced this pull request Feb 5, 2025
## Why this should be merged

Allows for modification of `types.Body` payload data + RLP encoding
without placing the entire RLP burden on the `libevm` user as we did
with `types.HeaderHooks`.

## How this works

RLP encoding of a struct is simply a concatenation of RLP encodings of
fields, encompassed by an RLP "list". The
`AppendRLPFields(rlp.EncoderBuffer, ...)` hook exploits this and plugs
in before all `rlp:"optional"`-tagged fields to allow for inclusion of
any new fields. The `EncoderBuffer` SHOULD be used as the `io.Writer`
passed when encoding each field: `rlp.Encode(buffer, fieldValue)`.

`Body` doesn't have `{En,De}codeRLP` methods so they are implemented to
identically replicate original behaviour when a no-op hook is present.

This pattern is sufficient for the `ava-labs/coreth` modifications of
`Body` but can be modified / extended for more complex scenarios, like
`Header`.

> [!NOTE]
> This PR does not include registration of the hooks as that was not the
initial goal and adding them would create too much PR bloat. There is a
placeholder `var todoRegisteredBodyHooks` global variable that can only
be set in tests.

## How this was tested

- Backwards compatibility: the new methods are fuzzed against a `type
withoutMethods Body` passed directly to `rlp.{En,De}code()`
- `coreth` compatibility: unit test of a local implementation of
`BodyHooks` demonstrating reproducibility of RLP encoding.

---------

Signed-off-by: Arran Schlosberg <[email protected]>
Co-authored-by: Quentin McGaw <[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.

3 participants