mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-20 13:44:31 +00:00
## Why this should be merged Simplification of `types.Body` RLP overriding, resulting in reduced code at both the implementation and consumer ends. ## How this works Introduction of `rlp.Fields` type, to mirror regular RLP encoding of a struct. The RLP override hook now only needs to return the fields of interest, which MAY come from either the `Body` or the registered extra. This pattern allows for arbitrary modification of upstream fields via (1) reordering; (2) addition; (3) deletion; and (4) inverting required vs optional status. While less important for `Body`, this allows for complete support of `ava-labs/coreth` `Header` modifications, which make use of 1-3. ## How this was tested Existing backwards-compatibility tests + new unit tests for introduced functionality. --------- Signed-off-by: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> Co-authored-by: Quentin McGaw <quentin.mcgaw@avalabs.org>
139 lines
3.8 KiB
Go
139 lines
3.8 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
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"reflect"
|
|
)
|
|
|
|
// Fields mirror the RLP encoding of struct fields.
|
|
type Fields struct {
|
|
Required []any
|
|
Optional []any // equivalent to those tagged with `rlp:"optional"`
|
|
}
|
|
|
|
var _ interface {
|
|
Encoder
|
|
Decoder
|
|
} = (*Fields)(nil)
|
|
|
|
// EncodeRLP encodes the `f.Required` and `f.Optional` slices to `w`,
|
|
// concatenated as a single list, as if they were fields in a struct. The
|
|
// optional values are treated identically to those tagged with
|
|
// `rlp:"optional"`.
|
|
func (f *Fields) EncodeRLP(w io.Writer) error {
|
|
includeOptional, err := f.optionalInclusionFlags()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
b := NewEncoderBuffer(w)
|
|
err = b.InList(func() error {
|
|
for _, v := range f.Required {
|
|
if err := Encode(b, v); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for i, v := range f.Optional {
|
|
if !includeOptional[i] {
|
|
return nil
|
|
}
|
|
if err := Encode(b, v); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return b.Flush()
|
|
}
|
|
|
|
var errUnsupportedOptionalFieldType = errors.New("unsupported optional field type")
|
|
|
|
// optionalInclusionFlags returns a slice of booleans, the same length as
|
|
// `f.Optional`, indicating whether or not the respective field MUST be written
|
|
// to a list. A field must be written if it or any later field value is non-nil;
|
|
// the returned slice is therefore monotonic non-increasing from true to false.
|
|
func (f *Fields) optionalInclusionFlags() ([]bool, error) {
|
|
flags := make([]bool, len(f.Optional))
|
|
var include bool
|
|
for i := len(f.Optional) - 1; i >= 0; i-- {
|
|
switch v := reflect.ValueOf(f.Optional[i]); v.Kind() {
|
|
case reflect.Slice, reflect.Pointer:
|
|
include = include || !v.IsNil()
|
|
default:
|
|
return nil, fmt.Errorf("%w: %T", errUnsupportedOptionalFieldType, f.Optional[i])
|
|
}
|
|
flags[i] = include
|
|
}
|
|
return flags, nil
|
|
}
|
|
|
|
// DecodeRLP implements the [Decoder] interface. All destination fields, be they
|
|
// required or optional, MUST be pointers and all optional fields MUST be
|
|
// provided in case they are present in the RLP being decoded.
|
|
//
|
|
// Typically, the arguments to this method mirror those passed to
|
|
// [Fields.EncodeRLP] except for being pointers. See the example.
|
|
func (f *Fields) DecodeRLP(s *Stream) error {
|
|
return s.FromList(func() error {
|
|
for _, v := range f.Required {
|
|
if err := s.Decode(v); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for _, v := range f.Optional {
|
|
if !s.MoreDataInList() {
|
|
return nil
|
|
}
|
|
if err := s.Decode(v); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// Nillable wraps `field` to mirror the behaviour of an `rlp:"nil"` tag; i.e. if
|
|
// a zero-sized RLP item is decoded into the returned Decoder then it is dropped
|
|
// and `*field` is set to nil, otherwise the RLP item is decoded directly into
|
|
// `field`. The return argument is intended for use with [Fields].
|
|
func Nillable[T any](field **T) Decoder {
|
|
return &nillable[T]{field}
|
|
}
|
|
|
|
type nillable[T any] struct{ v **T }
|
|
|
|
func (n *nillable[T]) DecodeRLP(s *Stream) error {
|
|
_, size, err := s.Kind()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if size > 0 {
|
|
return s.Decode(n.v)
|
|
}
|
|
*n.v = nil
|
|
_, err = s.Raw() // consume the item
|
|
return err
|
|
}
|