diff --git a/core/state/state.libevm_test.go b/core/state/state.libevm_test.go index 8b682cb49b7a..406425b07ce9 100644 --- a/core/state/state.libevm_test.go +++ b/core/state/state.libevm_test.go @@ -45,7 +45,10 @@ func TestGetSetExtra(t *testing.T) { t.Cleanup(types.TestOnlyClearRegisteredExtras) // Just as its Data field is a pointer, the registered type is a pointer to // test deep copying. - payloads := types.RegisterExtras[types.NOOPHeaderHooks, *types.NOOPHeaderHooks, *accountExtra]().StateAccount + payloads := types.RegisterExtras[ + types.NOOPHeaderHooks, *types.NOOPHeaderHooks, + types.NOOPBodyHooks, *types.NOOPBodyHooks, + *accountExtra]().StateAccount rng := ethtest.NewPseudoRand(42) addr := rng.Address() diff --git a/core/state/state_object.libevm_test.go b/core/state/state_object.libevm_test.go index d04de5a1aaa3..4cdae081d257 100644 --- a/core/state/state_object.libevm_test.go +++ b/core/state/state_object.libevm_test.go @@ -46,21 +46,34 @@ func TestStateObjectEmpty(t *testing.T) { { name: "explicit false bool", registerAndSet: func(acc *types.StateAccount) { - types.RegisterExtras[types.NOOPHeaderHooks, *types.NOOPHeaderHooks, bool]().StateAccount.Set(acc, false) + types.RegisterExtras[ + types.NOOPHeaderHooks, *types.NOOPHeaderHooks, + types.NOOPBodyHooks, *types.NOOPBodyHooks, + bool]().StateAccount.Set(acc, false) + types.RegisterExtras[ + types.NOOPHeaderHooks, *types.NOOPHeaderHooks, + types.NOOPBodyHooks, *types.NOOPBodyHooks, + bool]().StateAccount.Set(acc, false) }, wantEmpty: true, }, { name: "implicit false bool", registerAndSet: func(*types.StateAccount) { - types.RegisterExtras[types.NOOPHeaderHooks, *types.NOOPHeaderHooks, bool]() + types.RegisterExtras[ + types.NOOPHeaderHooks, *types.NOOPHeaderHooks, + types.NOOPBodyHooks, *types.NOOPBodyHooks, + bool]() }, wantEmpty: true, }, { name: "true bool", registerAndSet: func(acc *types.StateAccount) { - types.RegisterExtras[types.NOOPHeaderHooks, *types.NOOPHeaderHooks, bool]().StateAccount.Set(acc, true) + types.RegisterExtras[ + types.NOOPHeaderHooks, *types.NOOPHeaderHooks, + types.NOOPBodyHooks, *types.NOOPBodyHooks, + bool]().StateAccount.Set(acc, true) }, wantEmpty: false, }, diff --git a/core/types/block.go b/core/types/block.go index 9d056fc8bc4c..836126adba85 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -176,6 +176,8 @@ type Body struct { Transactions []*Transaction Uncles []*Header Withdrawals []*Withdrawal `rlp:"optional"` + + extra *pseudo.Type // See [RegisterExtras] } // Block represents an Ethereum block. @@ -338,7 +340,11 @@ func (b *Block) EncodeRLP(w io.Writer) error { // Body returns the non-header content of the block. // Note the returned data is not an independent copy. func (b *Block) Body() *Body { - return &Body{b.transactions, b.uncles, b.withdrawals} + return &Body{ + Transactions: b.transactions, + Uncles: b.uncles, + Withdrawals: b.withdrawals, + } } // Accessors for body data. These do not return a copy because the content diff --git a/core/types/block.libevm.go b/core/types/block.libevm.go index e029c67d374a..e702cbb9168d 100644 --- a/core/types/block.libevm.go +++ b/core/types/block.libevm.go @@ -44,7 +44,7 @@ func (h *Header) hooks() HeaderHooks { return new(NOOPHeaderHooks) } -func (e ExtraPayloads[HPtr, SA]) hooksFromHeader(h *Header) HeaderHooks { +func (e ExtraPayloads[HPtr, BodyExtraPtr, SA]) hooksFromHeader(h *Header) HeaderHooks { return e.Header.Get(h) } @@ -118,3 +118,66 @@ func (n *NOOPHeaderHooks) Copy(h *Header) *Header { func CopyHeader(h *Header) *Header { return h.hooks().Copy(h) } + +// BodyHooks are required for all types registered with [RegisterExtras] for +// [Body] payloads. +type BodyHooks interface { + EncodeRLP(*Body, io.Writer) error + DecodeRLP(*Body, *rlp.Stream) error +} + +// hooks returns the Body's registered BodyHooks, if any, otherwise a +// [*NOOPBodyHooks] suitable for running the default behaviour. +func (b *Body) hooks() BodyHooks { + if r := registeredExtras; r.Registered() { + return r.Get().hooks.hooksFromBody(b) + } + return new(NOOPBodyHooks) +} + +func (e ExtraPayloads[HPtr, BodyExtraPtr, SA]) hooksFromBody(b *Body) BodyHooks { + return e.Body.Get(b) +} + +var _ interface { + rlp.Encoder + rlp.Decoder +} = (*Body)(nil) + +// EncodeRLP implements the [rlp.Encoder] interface. +func (b *Body) EncodeRLP(w io.Writer) error { + return b.hooks().EncodeRLP(b, w) +} + +// DecodeRLP implements the [rlp.Decoder] interface. +func (b *Body) DecodeRLP(s *rlp.Stream) error { + return b.hooks().DecodeRLP(b, s) +} + +func (b *Body) extraPayload() *pseudo.Type { + r := registeredExtras + if !r.Registered() { + // See params.ChainConfig.extraPayload() for panic rationale. + panic(fmt.Sprintf("%T.extraPayload() called before RegisterExtras()", r)) + } + if b.extra == nil { + b.extra = r.Get().newBody() + } + return b.extra +} + +// NOOPBodyHooks implements [BodyHooks] such that they are equivalent to +// no type having been registered. +type NOOPBodyHooks struct{} + +var _ BodyHooks = (*NOOPBodyHooks)(nil) + +func (*NOOPBodyHooks) EncodeRLP(b *Body, w io.Writer) error { + type withoutMethods Body + return rlp.Encode(w, (*withoutMethods)(b)) +} + +func (*NOOPBodyHooks) DecodeRLP(b *Body, s *rlp.Stream) error { + type withoutMethods Body + return s.Decode((*withoutMethods)(b)) +} diff --git a/core/types/block.libevm_test.go b/core/types/block.libevm_test.go index 48ba5883e122..5fdd2005bdec 100644 --- a/core/types/block.libevm_test.go +++ b/core/types/block.libevm_test.go @@ -79,11 +79,39 @@ func (hh *stubHeaderHooks) Copy(h *Header) *Header { return h } +type stubBodyHooks struct { + encoding []byte + gotRawRLPToDecode []byte + setBodyToOnUnmarshalOrDecode Body + + errEncode, errDecode error +} + +func (bh *stubBodyHooks) EncodeRLP(b *Body, w io.Writer) error { + if _, err := w.Write(bh.encoding); err != nil { + return err + } + return bh.errEncode +} + +func (bh *stubBodyHooks) DecodeRLP(b *Body, s *rlp.Stream) error { + r, err := s.Raw() + if err != nil { + return err + } + bh.gotRawRLPToDecode = r + *b = bh.setBodyToOnUnmarshalOrDecode + return bh.errDecode +} + func TestHeaderHooks(t *testing.T) { TestOnlyClearRegisteredExtras() defer TestOnlyClearRegisteredExtras() - extras := RegisterExtras[stubHeaderHooks, *stubHeaderHooks, struct{}]() + extras := RegisterExtras[ + stubHeaderHooks, *stubHeaderHooks, + stubBodyHooks, *stubBodyHooks, + struct{}]() rng := ethtest.NewPseudoRand(13579) suffix := rng.Bytes(8) diff --git a/core/types/rlp_backwards_compat.libevm_test.go b/core/types/rlp_backwards_compat.libevm_test.go index 8b22eac29752..98cef45d53e8 100644 --- a/core/types/rlp_backwards_compat.libevm_test.go +++ b/core/types/rlp_backwards_compat.libevm_test.go @@ -40,7 +40,10 @@ func TestHeaderRLPBackwardsCompatibility(t *testing.T) { { name: "no-op header hooks", register: func() { - RegisterExtras[NOOPHeaderHooks, *NOOPHeaderHooks, struct{}]() + RegisterExtras[ + NOOPHeaderHooks, *NOOPHeaderHooks, + NOOPBodyHooks, *NOOPBodyHooks, + struct{}]() }, }, } diff --git a/core/types/rlp_payload.libevm.go b/core/types/rlp_payload.libevm.go index 90d5b7e43e33..f6b72689f941 100644 --- a/core/types/rlp_payload.libevm.go +++ b/core/types/rlp_payload.libevm.go @@ -46,13 +46,21 @@ func RegisterExtras[ HeaderHooks *H }, + BodyExtra any, BodyExtraPtr interface { + BodyHooks + *BodyExtra + }, SA any, -]() ExtraPayloads[HPtr, SA] { - extra := ExtraPayloads[HPtr, SA]{ +]() ExtraPayloads[HPtr, BodyExtraPtr, SA] { + extra := ExtraPayloads[HPtr, BodyExtraPtr, SA]{ Header: pseudo.NewAccessor[*Header, HPtr]( (*Header).extraPayload, func(h *Header, t *pseudo.Type) { h.extra = t }, ), + Body: pseudo.NewAccessor[*Body, BodyExtraPtr]( + (*Body).extraPayload, + func(b *Body, t *pseudo.Type) { b.extra = t }, + ), StateAccount: pseudo.NewAccessor[StateOrSlimAccount, SA]( func(a StateOrSlimAccount) *pseudo.Type { return a.extra().payload() }, func(a StateOrSlimAccount, t *pseudo.Type) { a.extra().t = t }, @@ -63,10 +71,11 @@ func RegisterExtras[ var x SA return fmt.Sprintf("%T", x) }(), - // The [ExtraPayloads] that we returns is based on [HPtr,SA], not [H,SA] - // so our constructors MUST match that. This guarantees that calls to - // the [HeaderHooks] methods will never be performed on a nil pointer. - newHeader: pseudo.NewConstructor[H]().NewPointer, // i.e. non-nil HPtr + // The [ExtraPayloads] that we returns is based on [HPtr,BodyExtraPtr,SA], not + // [H,BodyExtra,SA] so our constructors MUST match that. This guarantees that calls to + // the [HeaderHooks] and [BodyHooks] methods will never be performed on a nil pointer. + newHeader: pseudo.NewConstructor[H]().NewPointer, // i.e. non-nil HPtr + newBody: pseudo.NewConstructor[BodyExtra]().NewPointer, // i.e. non-nil BodyExtraPtr newStateAccount: pseudo.NewConstructor[SA]().Zero, cloneStateAccount: extra.cloneStateAccount, hooks: extra, @@ -87,11 +96,14 @@ func TestOnlyClearRegisteredExtras() { var registeredExtras register.AtMostOnce[*extraConstructors] type extraConstructors struct { - stateAccountType string - newHeader, newStateAccount func() *pseudo.Type - cloneStateAccount func(*StateAccountExtra) *StateAccountExtra - hooks interface { + stateAccountType string + newHeader func() *pseudo.Type + newBody func() *pseudo.Type + newStateAccount func() *pseudo.Type + cloneStateAccount func(*StateAccountExtra) *StateAccountExtra + hooks interface { hooksFromHeader(*Header) HeaderHooks + hooksFromBody(*Body) BodyHooks } } @@ -105,14 +117,15 @@ func (e *StateAccountExtra) clone() *StateAccountExtra { } // ExtraPayloads provides strongly typed access to the extra payload carried by -// [Header], [StateAccount], and [SlimAccount] structs. The only valid way to +// [Header], [Body], [StateAccount], and [SlimAccount] structs. The only valid way to // construct an instance is by a call to [RegisterExtras]. -type ExtraPayloads[HPtr HeaderHooks, SA any] struct { +type ExtraPayloads[HPtr HeaderHooks, BodyExtraPtr BodyHooks, SA any] struct { Header pseudo.Accessor[*Header, HPtr] + Body pseudo.Accessor[*Body, BodyExtraPtr] StateAccount pseudo.Accessor[StateOrSlimAccount, SA] // Also provides [SlimAccount] access. } -func (ExtraPayloads[HPtr, SA]) cloneStateAccount(s *StateAccountExtra) *StateAccountExtra { +func (ExtraPayloads[HPtr, BodyExtraPtr, SA]) cloneStateAccount(s *StateAccountExtra) *StateAccountExtra { v := pseudo.MustNewValue[SA](s.t) return &StateAccountExtra{ t: pseudo.From(v.Get()).Type, diff --git a/core/types/state_account.libevm_test.go b/core/types/state_account.libevm_test.go index 6fd601ef9d7e..2458df8ace40 100644 --- a/core/types/state_account.libevm_test.go +++ b/core/types/state_account.libevm_test.go @@ -46,7 +46,10 @@ func TestStateAccountRLP(t *testing.T) { explicitFalseBoolean := test{ name: "explicit false-boolean extra", register: func() { - RegisterExtras[NOOPHeaderHooks, *NOOPHeaderHooks, bool]() + RegisterExtras[ + NOOPHeaderHooks, *NOOPHeaderHooks, + NOOPBodyHooks, *NOOPBodyHooks, + bool]() }, acc: &StateAccount{ Nonce: 0x444444, @@ -76,7 +79,10 @@ func TestStateAccountRLP(t *testing.T) { { name: "true-boolean extra", register: func() { - RegisterExtras[NOOPHeaderHooks, *NOOPHeaderHooks, bool]() + RegisterExtras[ + NOOPHeaderHooks, *NOOPHeaderHooks, + NOOPBodyHooks, *NOOPBodyHooks, + bool]() }, acc: &StateAccount{ Nonce: 0x444444, diff --git a/core/types/state_account_storage.libevm_test.go b/core/types/state_account_storage.libevm_test.go index 9db9ee552d81..adf3ed15bc02 100644 --- a/core/types/state_account_storage.libevm_test.go +++ b/core/types/state_account_storage.libevm_test.go @@ -73,7 +73,10 @@ func TestStateAccountExtraViaTrieStorage(t *testing.T) { { name: "true-boolean payload", registerAndSetExtra: func(a *types.StateAccount) (*types.StateAccount, assertion) { - e := types.RegisterExtras[types.NOOPHeaderHooks, *types.NOOPHeaderHooks, bool]() + e := types.RegisterExtras[ + types.NOOPHeaderHooks, *types.NOOPHeaderHooks, + types.NOOPBodyHooks, *types.NOOPBodyHooks, + bool]() e.StateAccount.Set(a, true) return a, func(t *testing.T, got *types.StateAccount) { //nolint:thelper assert.Truef(t, e.StateAccount.Get(got), "") @@ -84,7 +87,10 @@ func TestStateAccountExtraViaTrieStorage(t *testing.T) { { name: "explicit false-boolean payload", registerAndSetExtra: func(a *types.StateAccount) (*types.StateAccount, assertion) { - e := types.RegisterExtras[types.NOOPHeaderHooks, *types.NOOPHeaderHooks, bool]() + e := types.RegisterExtras[ + types.NOOPHeaderHooks, *types.NOOPHeaderHooks, + types.NOOPBodyHooks, *types.NOOPBodyHooks, + bool]() e.StateAccount.Set(a, false) // the explicit part return a, func(t *testing.T, got *types.StateAccount) { //nolint:thelper @@ -96,7 +102,10 @@ func TestStateAccountExtraViaTrieStorage(t *testing.T) { { name: "implicit false-boolean payload", registerAndSetExtra: func(a *types.StateAccount) (*types.StateAccount, assertion) { - e := types.RegisterExtras[types.NOOPHeaderHooks, *types.NOOPHeaderHooks, bool]() + e := types.RegisterExtras[ + types.NOOPHeaderHooks, *types.NOOPHeaderHooks, + types.NOOPBodyHooks, *types.NOOPBodyHooks, + bool]() // Note that `a` is reflected, unchanged (the implicit part). return a, func(t *testing.T, got *types.StateAccount) { //nolint:thelper assert.Falsef(t, e.StateAccount.Get(got), "") @@ -107,7 +116,10 @@ func TestStateAccountExtraViaTrieStorage(t *testing.T) { { name: "arbitrary payload", registerAndSetExtra: func(a *types.StateAccount) (*types.StateAccount, assertion) { - e := types.RegisterExtras[types.NOOPHeaderHooks, *types.NOOPHeaderHooks, arbitraryPayload]() + e := types.RegisterExtras[ + types.NOOPHeaderHooks, *types.NOOPHeaderHooks, + types.NOOPBodyHooks, *types.NOOPBodyHooks, + arbitraryPayload]() p := arbitraryPayload{arbitraryData} e.StateAccount.Set(a, p) return a, func(t *testing.T, got *types.StateAccount) { //nolint:thelper