go-ethereum/rlp/list.libevm.go
Arran Schlosberg eda3b59f67
feat(core/types): fine-grained Body RLP override (#109)
## 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 <519948+ARR4N@users.noreply.github.com>
Co-authored-by: Quentin McGaw <quentin.mcgaw@avalabs.org>
2025-02-05 10:52:28 +00:00

77 lines
2.4 KiB
Go

// Copyright 2025 the libevm authors.
//
// The libevm additions to go-ethereum are free software: you can redistribute
// them and/or modify them under the terms of the GNU Lesser General Public License
// as published by the Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
//
// The libevm additions are distributed in the hope that they will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
// General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see
// <http://www.gnu.org/licenses/>.
package rlp
// InList is a convenience wrapper, calling `fn` between calls to
// [EncoderBuffer.List] and [EncoderBuffer.ListEnd]. If `fn` returns an error,
// it is propagated directly.
func (b EncoderBuffer) InList(fn func() error) error {
l := b.List()
if err := fn(); err != nil {
return err
}
b.ListEnd(l)
return nil
}
// EncodeListToBuffer is equivalent to [Encode], writing the RLP encoding of
// each element to `b`, except that it wraps the writes inside a call to
// [EncoderBuffer.InList].
func EncodeListToBuffer[T any](b EncoderBuffer, vals []T) error {
return b.InList(func() error {
for _, v := range vals {
if err := Encode(b, v); err != nil {
return err
}
}
return nil
})
}
// FromList is a convenience wrapper, calling `fn` between calls to
// [Stream.List] and [Stream.ListEnd]. If `fn` returns an error, it is
// propagated directly.
func (s *Stream) FromList(fn func() error) error {
if _, err := s.List(); err != nil {
return err
}
if err := fn(); err != nil {
return err
}
return s.ListEnd()
}
// DecodeList assumes that the next item in `s` is a list and decodes every item
// in said list to a `*T`.
//
// The returned slice is guaranteed to be non-nil, even if the list is empty.
// This is in keeping with other behaviour in this package and it is therefore
// the responsibility of callers to respect `rlp:"nil"` struct tags.
func DecodeList[T any](s *Stream) ([]*T, error) {
vals := []*T{}
err := s.FromList(func() error {
for s.MoreDataInList() {
var v T
if err := s.Decode(&v); err != nil {
return err
}
vals = append(vals, &v)
}
return nil
})
return vals, err
}