Skip to content

Commit 13e3e76

Browse files
committed
refactor: introduce rlp.OptionalFields instead of []any
1 parent 64d39ca commit 13e3e76

File tree

4 files changed

+69
-38
lines changed

4 files changed

+69
-38
lines changed

core/types/block.libevm.go

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -119,21 +119,22 @@ var _ interface {
119119

120120
// EncodeRLP implements the [rlp.Encoder] interface.
121121
func (b *Body) EncodeRLP(dst io.Writer) error {
122-
req, opt := b.hooks().RLPFieldsForEncoding(b)
123-
return rlp.EncodeStructFields(dst, req, opt)
122+
required, optional := b.hooks().RLPFieldsForEncoding(b)
123+
return rlp.EncodeStructFields(dst, required, optional)
124124
}
125125

126126
// DecodeRLP implements the [rlp.Decoder] interface.
127127
func (b *Body) DecodeRLP(s *rlp.Stream) error {
128-
req, opt := b.hooks().RLPFieldPointersForDecoding(b)
129-
return s.DecodeStructFields(req, opt)
128+
return s.DecodeStructFields(
129+
b.hooks().RLPFieldPointersForDecoding(b),
130+
)
130131
}
131132

132133
// BodyHooks are required for all types registered with [RegisterExtras] for
133134
// [Body] payloads.
134135
type BodyHooks interface {
135-
RLPFieldsForEncoding(*Body) (required, optional []any)
136-
RLPFieldPointersForDecoding(*Body) (required, optional []any)
136+
RLPFieldsForEncoding(*Body) ([]any, *rlp.OptionalFields)
137+
RLPFieldPointersForDecoding(*Body) ([]any, *rlp.OptionalFields)
137138
}
138139

139140
// TestOnlyRegisterBodyHooks is a temporary means of "registering" BodyHooks for
@@ -164,10 +165,10 @@ type NOOPBodyHooks struct{}
164165
// backwards-compatibility tests added.
165166
var _ = &Body{[]*Transaction{}, []*Header{}, []*Withdrawal{}}
166167

167-
func (NOOPBodyHooks) RLPFieldsForEncoding(b *Body) ([]any, []any) {
168-
return []any{b.Transactions, b.Uncles}, []any{b.Withdrawals}
168+
func (NOOPBodyHooks) RLPFieldsForEncoding(b *Body) ([]any, *rlp.OptionalFields) {
169+
return []any{b.Transactions, b.Uncles}, rlp.Optional(b.Withdrawals)
169170
}
170171

171-
func (NOOPBodyHooks) RLPFieldPointersForDecoding(b *Body) ([]any, []any) {
172-
return []any{&b.Transactions, &b.Uncles}, []any{&b.Withdrawals}
172+
func (NOOPBodyHooks) RLPFieldPointersForDecoding(b *Body) ([]any, *rlp.OptionalFields) {
173+
return []any{&b.Transactions, &b.Uncles}, rlp.Optional(&b.Withdrawals)
173174
}

core/types/rlp_backwards_compat.libevm_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -197,19 +197,19 @@ type cChainBodyExtras struct {
197197

198198
var _ BodyHooks = (*cChainBodyExtras)(nil)
199199

200-
func (e *cChainBodyExtras) RLPFieldsForEncoding(b *Body) ([]any, []any) {
200+
func (e *cChainBodyExtras) RLPFieldsForEncoding(b *Body) ([]any, *rlp.OptionalFields) {
201201
// The Avalanche C-Chain uses all of the geth required fields (but none of
202202
// the optional ones) so there's no need to explicitly list them. This
203203
// pattern might not be ideal for readability but is used here for
204204
// demonstrative purposes.
205205
//
206206
// All new fields will always be tagged as optional for backwards
207207
// compatibility so this is safe to do, but only for the required fields.
208-
req, _ /*drop all optional*/ := NOOPBodyHooks{}.RLPFieldsForEncoding(b)
209-
return append(req, e.Version, e.ExtData), nil
208+
required, _ /*drop all optional*/ := NOOPBodyHooks{}.RLPFieldsForEncoding(b)
209+
return append(required, e.Version, e.ExtData), nil
210210
}
211211

212-
func (e *cChainBodyExtras) RLPFieldPointersForDecoding(b *Body) ([]any, []any) {
212+
func (e *cChainBodyExtras) RLPFieldPointersForDecoding(b *Body) ([]any, *rlp.OptionalFields) {
213213
// An alternative to the pattern used above is to explicitly list all
214214
// fields for better introspection.
215215
return []any{

rlp/list.libevm.go

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,14 @@ func EncodeListToBuffer[T any](b EncoderBuffer, vals []T) error {
4949
})
5050
}
5151

52-
// EncodeStructFields encodes the `required` and `optional` slices to `w`,
52+
// EncodeStructFields encodes the required and optional slices to `w`,
5353
// concatenated as a single list, as if they were fields in a struct. The
54-
// optional "fields" are treated identically to those tagged with
55-
// `rlp:"optional"`.
54+
// optional "fields", which MAY be nil, are treated identically to those tagged
55+
// with `rlp:"optional"`.
5656
//
5757
// See the example for [Stream.DecodeStructFields].
58-
func EncodeStructFields(w io.Writer, required, optional []any) error {
59-
includeOptional, err := optionalFieldInclusionFlags(optional)
58+
func EncodeStructFields(w io.Writer, required []any, opt *OptionalFields) error {
59+
includeOptional, err := opt.inclusionFlags()
6060
if err != nil {
6161
return err
6262
}
@@ -69,7 +69,7 @@ func EncodeStructFields(w io.Writer, required, optional []any) error {
6969
}
7070
}
7171

72-
for i, v := range optional {
72+
for i, v := range opt.vals() {
7373
if !includeOptional[i] {
7474
return nil
7575
}
@@ -85,22 +85,51 @@ func EncodeStructFields(w io.Writer, required, optional []any) error {
8585
return b.Flush()
8686
}
8787

88+
// Optional returns the `vals` as [OptionalFields]; see the type's documentation
89+
// for the resulting behaviour.
90+
func Optional(vals ...any) *OptionalFields {
91+
return &OptionalFields{vals}
92+
}
93+
94+
// OptionalFields are treated by [EncodeStructFields] and
95+
// [Stream.DecodeStructFields] as if they were tagged with `rlp:"optional"`.
96+
type OptionalFields struct {
97+
// Note that the [OptionalFields] type exists primarily to improve
98+
// readability at the call sites of [EncodeStructFields] and
99+
// [Stream.DecodeStructFields]. While an `[]any` slice would suffice, it
100+
// results in ambiguous usage of field functionality.
101+
102+
v []any
103+
}
104+
105+
// vals is a convenience wrapper, returning o.v, but allowing for a nil
106+
// receiver, in which case it returns a nil slice.
107+
func (o *OptionalFields) vals() []any {
108+
if o == nil {
109+
return nil
110+
}
111+
return o.v
112+
}
113+
88114
var errUnsupportedOptionalFieldType = errors.New("unsupported optional field type")
89115

90-
// optionalFieldInclusionFlags returns a slice of booleans, the same length as
91-
// `vals`, indicating whether or not the respective optional value MUST be
92-
// written to a list. A value must be written if it or any later value is
93-
// non-nil; the returned slice is therefore monotonic non-increasing from true
94-
// to false.
95-
func optionalFieldInclusionFlags(vals []any) ([]bool, error) {
96-
flags := make([]bool, len(vals))
116+
// inclusionFlags returns a slice of booleans, the same length as `fs`,
117+
// indicating whether or not the respective field MUST be written to a list. A
118+
// field must be written if it or any later field value is non-nil; the returned
119+
// slice is therefore monotonic non-increasing from true to false.
120+
func (o *OptionalFields) inclusionFlags() ([]bool, error) {
121+
if o == nil {
122+
return nil, nil
123+
}
124+
125+
flags := make([]bool, len(o.v))
97126
var include bool
98-
for i := len(vals) - 1; i >= 0; i-- {
99-
switch v := reflect.ValueOf(vals[i]); v.Kind() {
127+
for i := len(o.v) - 1; i >= 0; i-- {
128+
switch v := reflect.ValueOf(o.v[i]); v.Kind() {
100129
case reflect.Slice, reflect.Pointer:
101130
include = include || !v.IsNil()
102131
default:
103-
return nil, fmt.Errorf("%w: %T", errUnsupportedOptionalFieldType, vals[i])
132+
return nil, fmt.Errorf("%w: %T", errUnsupportedOptionalFieldType, o.v[i])
104133
}
105134
flags[i] = include
106135
}
@@ -142,20 +171,21 @@ func DecodeList[T any](s *Stream) ([]*T, error) {
142171
}
143172

144173
// DecodeStructFields is the inverse of [EncodeStructFields]. All destination
145-
// fields, be they `required` or `optional`, MUST be pointers and all `optional`
146-
// fields MUST be provided in case they are present in the RLP being decoded.
174+
// fields, be they required or optional, MUST be pointers and all optional
175+
// fields MUST be provided in case they are present in the RLP being decoded. If
176+
// no optional fields exist, the argument MAY be nil.
147177
//
148178
// Typically, the arguments to this function mirror those passed to
149179
// [EncodeStructFields] except for being pointers. See the example.
150-
func (s *Stream) DecodeStructFields(required, optional []any) error {
180+
func (s *Stream) DecodeStructFields(required []any, opt *OptionalFields) error {
151181
return s.FromList(func() error {
152182
for _, v := range required {
153183
if err := s.Decode(v); err != nil {
154184
return err
155185
}
156186
}
157187

158-
for _, v := range optional {
188+
for _, v := range opt.vals() {
159189
if !s.MoreDataInList() {
160190
return nil
161191
}

rlp/list.libevm_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ func TestStructFieldHelpers(t *testing.T) {
107107
err = EncodeStructFields(
108108
&got,
109109
[]any{obj.A, obj.B, obj.C},
110-
[]any{obj.D, obj.E, obj.F},
110+
Optional(obj.D, obj.E, obj.F),
111111
)
112112
require.NoErrorf(t, err, "EncodeStructFields(..., [required], [optional])")
113113

@@ -119,7 +119,7 @@ func TestStructFieldHelpers(t *testing.T) {
119119
var got foo
120120
err := s.DecodeStructFields(
121121
[]any{&got.A, &got.B, &got.C},
122-
[]any{&got.D, &got.E, &got.F},
122+
Optional(&got.D, &got.E, &got.F),
123123
)
124124
require.NoError(t, err, "Stream.DecodeStructFields(...)")
125125

@@ -158,7 +158,7 @@ func ExampleStream_DecodeStructFields() {
158158
_ = EncodeStructFields(
159159
io.Discard,
160160
[]any{val.A, val.B},
161-
[]any{val.C},
161+
Optional(val.C),
162162
)
163163

164164
r := bytes.NewReader(nil /*arbitrary RLP buffer*/)
@@ -170,7 +170,7 @@ func ExampleStream_DecodeStructFields() {
170170
&val.A,
171171
Nillable(&val.B),
172172
},
173-
[]any{&val.C},
173+
Optional(&val.C),
174174
)
175175

176176
// Note the parallels between the arguments passed to

0 commit comments

Comments
 (0)