Skip to content

Commit

Permalink
feat(core/types): BodyHooks for RLP overrides
Browse files Browse the repository at this point in the history
  • Loading branch information
qdm12 committed Jan 22, 2025
1 parent b9e5186 commit 7f6afca
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 27 deletions.
5 changes: 4 additions & 1 deletion core/state/state.libevm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
19 changes: 16 additions & 3 deletions core/state/state_object.libevm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Expand Down
8 changes: 7 additions & 1 deletion core/types/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
65 changes: 64 additions & 1 deletion core/types/block.libevm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down Expand Up @@ -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))
}
30 changes: 29 additions & 1 deletion core/types/block.libevm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 4 additions & 1 deletion core/types/rlp_backwards_compat.libevm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}]()
},
},
}
Expand Down
39 changes: 26 additions & 13 deletions core/types/rlp_payload.libevm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand All @@ -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,
Expand All @@ -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
}
}

Expand All @@ -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,
Expand Down
10 changes: 8 additions & 2 deletions core/types/state_account.libevm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
20 changes: 16 additions & 4 deletions core/types/state_account_storage.libevm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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), "")
Expand All @@ -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
Expand All @@ -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), "")
Expand All @@ -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
Expand Down

0 comments on commit 7f6afca

Please sign in to comment.