Skip to content

Commit 7f6afca

Browse files
committed
feat(core/types): BodyHooks for RLP overrides
1 parent b9e5186 commit 7f6afca

9 files changed

+174
-27
lines changed

core/state/state.libevm_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ func TestGetSetExtra(t *testing.T) {
4545
t.Cleanup(types.TestOnlyClearRegisteredExtras)
4646
// Just as its Data field is a pointer, the registered type is a pointer to
4747
// test deep copying.
48-
payloads := types.RegisterExtras[types.NOOPHeaderHooks, *types.NOOPHeaderHooks, *accountExtra]().StateAccount
48+
payloads := types.RegisterExtras[
49+
types.NOOPHeaderHooks, *types.NOOPHeaderHooks,
50+
types.NOOPBodyHooks, *types.NOOPBodyHooks,
51+
*accountExtra]().StateAccount
4952

5053
rng := ethtest.NewPseudoRand(42)
5154
addr := rng.Address()

core/state/state_object.libevm_test.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,21 +46,34 @@ func TestStateObjectEmpty(t *testing.T) {
4646
{
4747
name: "explicit false bool",
4848
registerAndSet: func(acc *types.StateAccount) {
49-
types.RegisterExtras[types.NOOPHeaderHooks, *types.NOOPHeaderHooks, bool]().StateAccount.Set(acc, false)
49+
types.RegisterExtras[
50+
types.NOOPHeaderHooks, *types.NOOPHeaderHooks,
51+
types.NOOPBodyHooks, *types.NOOPBodyHooks,
52+
bool]().StateAccount.Set(acc, false)
53+
types.RegisterExtras[
54+
types.NOOPHeaderHooks, *types.NOOPHeaderHooks,
55+
types.NOOPBodyHooks, *types.NOOPBodyHooks,
56+
bool]().StateAccount.Set(acc, false)
5057
},
5158
wantEmpty: true,
5259
},
5360
{
5461
name: "implicit false bool",
5562
registerAndSet: func(*types.StateAccount) {
56-
types.RegisterExtras[types.NOOPHeaderHooks, *types.NOOPHeaderHooks, bool]()
63+
types.RegisterExtras[
64+
types.NOOPHeaderHooks, *types.NOOPHeaderHooks,
65+
types.NOOPBodyHooks, *types.NOOPBodyHooks,
66+
bool]()
5767
},
5868
wantEmpty: true,
5969
},
6070
{
6171
name: "true bool",
6272
registerAndSet: func(acc *types.StateAccount) {
63-
types.RegisterExtras[types.NOOPHeaderHooks, *types.NOOPHeaderHooks, bool]().StateAccount.Set(acc, true)
73+
types.RegisterExtras[
74+
types.NOOPHeaderHooks, *types.NOOPHeaderHooks,
75+
types.NOOPBodyHooks, *types.NOOPBodyHooks,
76+
bool]().StateAccount.Set(acc, true)
6477
},
6578
wantEmpty: false,
6679
},

core/types/block.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ type Body struct {
176176
Transactions []*Transaction
177177
Uncles []*Header
178178
Withdrawals []*Withdrawal `rlp:"optional"`
179+
180+
extra *pseudo.Type // See [RegisterExtras]
179181
}
180182

181183
// Block represents an Ethereum block.
@@ -338,7 +340,11 @@ func (b *Block) EncodeRLP(w io.Writer) error {
338340
// Body returns the non-header content of the block.
339341
// Note the returned data is not an independent copy.
340342
func (b *Block) Body() *Body {
341-
return &Body{b.transactions, b.uncles, b.withdrawals}
343+
return &Body{
344+
Transactions: b.transactions,
345+
Uncles: b.uncles,
346+
Withdrawals: b.withdrawals,
347+
}
342348
}
343349

344350
// Accessors for body data. These do not return a copy because the content

core/types/block.libevm.go

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func (h *Header) hooks() HeaderHooks {
4444
return new(NOOPHeaderHooks)
4545
}
4646

47-
func (e ExtraPayloads[HPtr, SA]) hooksFromHeader(h *Header) HeaderHooks {
47+
func (e ExtraPayloads[HPtr, BodyExtraPtr, SA]) hooksFromHeader(h *Header) HeaderHooks {
4848
return e.Header.Get(h)
4949
}
5050

@@ -118,3 +118,66 @@ func (n *NOOPHeaderHooks) Copy(h *Header) *Header {
118118
func CopyHeader(h *Header) *Header {
119119
return h.hooks().Copy(h)
120120
}
121+
122+
// BodyHooks are required for all types registered with [RegisterExtras] for
123+
// [Body] payloads.
124+
type BodyHooks interface {
125+
EncodeRLP(*Body, io.Writer) error
126+
DecodeRLP(*Body, *rlp.Stream) error
127+
}
128+
129+
// hooks returns the Body's registered BodyHooks, if any, otherwise a
130+
// [*NOOPBodyHooks] suitable for running the default behaviour.
131+
func (b *Body) hooks() BodyHooks {
132+
if r := registeredExtras; r.Registered() {
133+
return r.Get().hooks.hooksFromBody(b)
134+
}
135+
return new(NOOPBodyHooks)
136+
}
137+
138+
func (e ExtraPayloads[HPtr, BodyExtraPtr, SA]) hooksFromBody(b *Body) BodyHooks {
139+
return e.Body.Get(b)
140+
}
141+
142+
var _ interface {
143+
rlp.Encoder
144+
rlp.Decoder
145+
} = (*Body)(nil)
146+
147+
// EncodeRLP implements the [rlp.Encoder] interface.
148+
func (b *Body) EncodeRLP(w io.Writer) error {
149+
return b.hooks().EncodeRLP(b, w)
150+
}
151+
152+
// DecodeRLP implements the [rlp.Decoder] interface.
153+
func (b *Body) DecodeRLP(s *rlp.Stream) error {
154+
return b.hooks().DecodeRLP(b, s)
155+
}
156+
157+
func (b *Body) extraPayload() *pseudo.Type {
158+
r := registeredExtras
159+
if !r.Registered() {
160+
// See params.ChainConfig.extraPayload() for panic rationale.
161+
panic(fmt.Sprintf("%T.extraPayload() called before RegisterExtras()", r))
162+
}
163+
if b.extra == nil {
164+
b.extra = r.Get().newBody()
165+
}
166+
return b.extra
167+
}
168+
169+
// NOOPBodyHooks implements [BodyHooks] such that they are equivalent to
170+
// no type having been registered.
171+
type NOOPBodyHooks struct{}
172+
173+
var _ BodyHooks = (*NOOPBodyHooks)(nil)
174+
175+
func (*NOOPBodyHooks) EncodeRLP(b *Body, w io.Writer) error {
176+
type withoutMethods Body
177+
return rlp.Encode(w, (*withoutMethods)(b))
178+
}
179+
180+
func (*NOOPBodyHooks) DecodeRLP(b *Body, s *rlp.Stream) error {
181+
type withoutMethods Body
182+
return s.Decode((*withoutMethods)(b))
183+
}

core/types/block.libevm_test.go

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,39 @@ func (hh *stubHeaderHooks) Copy(h *Header) *Header {
7979
return h
8080
}
8181

82+
type stubBodyHooks struct {
83+
encoding []byte
84+
gotRawRLPToDecode []byte
85+
setBodyToOnUnmarshalOrDecode Body
86+
87+
errEncode, errDecode error
88+
}
89+
90+
func (bh *stubBodyHooks) EncodeRLP(b *Body, w io.Writer) error {
91+
if _, err := w.Write(bh.encoding); err != nil {
92+
return err
93+
}
94+
return bh.errEncode
95+
}
96+
97+
func (bh *stubBodyHooks) DecodeRLP(b *Body, s *rlp.Stream) error {
98+
r, err := s.Raw()
99+
if err != nil {
100+
return err
101+
}
102+
bh.gotRawRLPToDecode = r
103+
*b = bh.setBodyToOnUnmarshalOrDecode
104+
return bh.errDecode
105+
}
106+
82107
func TestHeaderHooks(t *testing.T) {
83108
TestOnlyClearRegisteredExtras()
84109
defer TestOnlyClearRegisteredExtras()
85110

86-
extras := RegisterExtras[stubHeaderHooks, *stubHeaderHooks, struct{}]()
111+
extras := RegisterExtras[
112+
stubHeaderHooks, *stubHeaderHooks,
113+
stubBodyHooks, *stubBodyHooks,
114+
struct{}]()
87115
rng := ethtest.NewPseudoRand(13579)
88116

89117
suffix := rng.Bytes(8)

core/types/rlp_backwards_compat.libevm_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ func TestHeaderRLPBackwardsCompatibility(t *testing.T) {
4040
{
4141
name: "no-op header hooks",
4242
register: func() {
43-
RegisterExtras[NOOPHeaderHooks, *NOOPHeaderHooks, struct{}]()
43+
RegisterExtras[
44+
NOOPHeaderHooks, *NOOPHeaderHooks,
45+
NOOPBodyHooks, *NOOPBodyHooks,
46+
struct{}]()
4447
},
4548
},
4649
}

core/types/rlp_payload.libevm.go

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,21 @@ func RegisterExtras[
4646
HeaderHooks
4747
*H
4848
},
49+
BodyExtra any, BodyExtraPtr interface {
50+
BodyHooks
51+
*BodyExtra
52+
},
4953
SA any,
50-
]() ExtraPayloads[HPtr, SA] {
51-
extra := ExtraPayloads[HPtr, SA]{
54+
]() ExtraPayloads[HPtr, BodyExtraPtr, SA] {
55+
extra := ExtraPayloads[HPtr, BodyExtraPtr, SA]{
5256
Header: pseudo.NewAccessor[*Header, HPtr](
5357
(*Header).extraPayload,
5458
func(h *Header, t *pseudo.Type) { h.extra = t },
5559
),
60+
Body: pseudo.NewAccessor[*Body, BodyExtraPtr](
61+
(*Body).extraPayload,
62+
func(b *Body, t *pseudo.Type) { b.extra = t },
63+
),
5664
StateAccount: pseudo.NewAccessor[StateOrSlimAccount, SA](
5765
func(a StateOrSlimAccount) *pseudo.Type { return a.extra().payload() },
5866
func(a StateOrSlimAccount, t *pseudo.Type) { a.extra().t = t },
@@ -63,10 +71,11 @@ func RegisterExtras[
6371
var x SA
6472
return fmt.Sprintf("%T", x)
6573
}(),
66-
// The [ExtraPayloads] that we returns is based on [HPtr,SA], not [H,SA]
67-
// so our constructors MUST match that. This guarantees that calls to
68-
// the [HeaderHooks] methods will never be performed on a nil pointer.
69-
newHeader: pseudo.NewConstructor[H]().NewPointer, // i.e. non-nil HPtr
74+
// The [ExtraPayloads] that we returns is based on [HPtr,BodyExtraPtr,SA], not
75+
// [H,BodyExtra,SA] so our constructors MUST match that. This guarantees that calls to
76+
// the [HeaderHooks] and [BodyHooks] methods will never be performed on a nil pointer.
77+
newHeader: pseudo.NewConstructor[H]().NewPointer, // i.e. non-nil HPtr
78+
newBody: pseudo.NewConstructor[BodyExtra]().NewPointer, // i.e. non-nil BodyExtraPtr
7079
newStateAccount: pseudo.NewConstructor[SA]().Zero,
7180
cloneStateAccount: extra.cloneStateAccount,
7281
hooks: extra,
@@ -87,11 +96,14 @@ func TestOnlyClearRegisteredExtras() {
8796
var registeredExtras register.AtMostOnce[*extraConstructors]
8897

8998
type extraConstructors struct {
90-
stateAccountType string
91-
newHeader, newStateAccount func() *pseudo.Type
92-
cloneStateAccount func(*StateAccountExtra) *StateAccountExtra
93-
hooks interface {
99+
stateAccountType string
100+
newHeader func() *pseudo.Type
101+
newBody func() *pseudo.Type
102+
newStateAccount func() *pseudo.Type
103+
cloneStateAccount func(*StateAccountExtra) *StateAccountExtra
104+
hooks interface {
94105
hooksFromHeader(*Header) HeaderHooks
106+
hooksFromBody(*Body) BodyHooks
95107
}
96108
}
97109

@@ -105,14 +117,15 @@ func (e *StateAccountExtra) clone() *StateAccountExtra {
105117
}
106118

107119
// ExtraPayloads provides strongly typed access to the extra payload carried by
108-
// [Header], [StateAccount], and [SlimAccount] structs. The only valid way to
120+
// [Header], [Body], [StateAccount], and [SlimAccount] structs. The only valid way to
109121
// construct an instance is by a call to [RegisterExtras].
110-
type ExtraPayloads[HPtr HeaderHooks, SA any] struct {
122+
type ExtraPayloads[HPtr HeaderHooks, BodyExtraPtr BodyHooks, SA any] struct {
111123
Header pseudo.Accessor[*Header, HPtr]
124+
Body pseudo.Accessor[*Body, BodyExtraPtr]
112125
StateAccount pseudo.Accessor[StateOrSlimAccount, SA] // Also provides [SlimAccount] access.
113126
}
114127

115-
func (ExtraPayloads[HPtr, SA]) cloneStateAccount(s *StateAccountExtra) *StateAccountExtra {
128+
func (ExtraPayloads[HPtr, BodyExtraPtr, SA]) cloneStateAccount(s *StateAccountExtra) *StateAccountExtra {
116129
v := pseudo.MustNewValue[SA](s.t)
117130
return &StateAccountExtra{
118131
t: pseudo.From(v.Get()).Type,

core/types/state_account.libevm_test.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,10 @@ func TestStateAccountRLP(t *testing.T) {
4646
explicitFalseBoolean := test{
4747
name: "explicit false-boolean extra",
4848
register: func() {
49-
RegisterExtras[NOOPHeaderHooks, *NOOPHeaderHooks, bool]()
49+
RegisterExtras[
50+
NOOPHeaderHooks, *NOOPHeaderHooks,
51+
NOOPBodyHooks, *NOOPBodyHooks,
52+
bool]()
5053
},
5154
acc: &StateAccount{
5255
Nonce: 0x444444,
@@ -76,7 +79,10 @@ func TestStateAccountRLP(t *testing.T) {
7679
{
7780
name: "true-boolean extra",
7881
register: func() {
79-
RegisterExtras[NOOPHeaderHooks, *NOOPHeaderHooks, bool]()
82+
RegisterExtras[
83+
NOOPHeaderHooks, *NOOPHeaderHooks,
84+
NOOPBodyHooks, *NOOPBodyHooks,
85+
bool]()
8086
},
8187
acc: &StateAccount{
8288
Nonce: 0x444444,

core/types/state_account_storage.libevm_test.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,10 @@ func TestStateAccountExtraViaTrieStorage(t *testing.T) {
7373
{
7474
name: "true-boolean payload",
7575
registerAndSetExtra: func(a *types.StateAccount) (*types.StateAccount, assertion) {
76-
e := types.RegisterExtras[types.NOOPHeaderHooks, *types.NOOPHeaderHooks, bool]()
76+
e := types.RegisterExtras[
77+
types.NOOPHeaderHooks, *types.NOOPHeaderHooks,
78+
types.NOOPBodyHooks, *types.NOOPBodyHooks,
79+
bool]()
7780
e.StateAccount.Set(a, true)
7881
return a, func(t *testing.T, got *types.StateAccount) { //nolint:thelper
7982
assert.Truef(t, e.StateAccount.Get(got), "")
@@ -84,7 +87,10 @@ func TestStateAccountExtraViaTrieStorage(t *testing.T) {
8487
{
8588
name: "explicit false-boolean payload",
8689
registerAndSetExtra: func(a *types.StateAccount) (*types.StateAccount, assertion) {
87-
e := types.RegisterExtras[types.NOOPHeaderHooks, *types.NOOPHeaderHooks, bool]()
90+
e := types.RegisterExtras[
91+
types.NOOPHeaderHooks, *types.NOOPHeaderHooks,
92+
types.NOOPBodyHooks, *types.NOOPBodyHooks,
93+
bool]()
8894
e.StateAccount.Set(a, false) // the explicit part
8995

9096
return a, func(t *testing.T, got *types.StateAccount) { //nolint:thelper
@@ -96,7 +102,10 @@ func TestStateAccountExtraViaTrieStorage(t *testing.T) {
96102
{
97103
name: "implicit false-boolean payload",
98104
registerAndSetExtra: func(a *types.StateAccount) (*types.StateAccount, assertion) {
99-
e := types.RegisterExtras[types.NOOPHeaderHooks, *types.NOOPHeaderHooks, bool]()
105+
e := types.RegisterExtras[
106+
types.NOOPHeaderHooks, *types.NOOPHeaderHooks,
107+
types.NOOPBodyHooks, *types.NOOPBodyHooks,
108+
bool]()
100109
// Note that `a` is reflected, unchanged (the implicit part).
101110
return a, func(t *testing.T, got *types.StateAccount) { //nolint:thelper
102111
assert.Falsef(t, e.StateAccount.Get(got), "")
@@ -107,7 +116,10 @@ func TestStateAccountExtraViaTrieStorage(t *testing.T) {
107116
{
108117
name: "arbitrary payload",
109118
registerAndSetExtra: func(a *types.StateAccount) (*types.StateAccount, assertion) {
110-
e := types.RegisterExtras[types.NOOPHeaderHooks, *types.NOOPHeaderHooks, arbitraryPayload]()
119+
e := types.RegisterExtras[
120+
types.NOOPHeaderHooks, *types.NOOPHeaderHooks,
121+
types.NOOPBodyHooks, *types.NOOPBodyHooks,
122+
arbitraryPayload]()
111123
p := arbitraryPayload{arbitraryData}
112124
e.StateAccount.Set(a, p)
113125
return a, func(t *testing.T, got *types.StateAccount) { //nolint:thelper

0 commit comments

Comments
 (0)