Skip to content

Commit

Permalink
feat(core/types): BlockHooks for RLP overrides
Browse files Browse the repository at this point in the history
  • Loading branch information
qdm12 committed Jan 22, 2025
1 parent e7193b7 commit abbba71
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 19 deletions.
1 change: 1 addition & 0 deletions core/state/state.libevm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func TestGetSetExtra(t *testing.T) {
payloads := types.RegisterExtras[
types.NOOPHeaderHooks, *types.NOOPHeaderHooks,
types.NOOPBodyHooks, *types.NOOPBodyHooks,
types.NOOPBlockHooks, *types.NOOPBlockHooks,
*accountExtra]().StateAccount

rng := ethtest.NewPseudoRand(42)
Expand Down
4 changes: 4 additions & 0 deletions core/state/state_object.libevm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,12 @@ func TestStateObjectEmpty(t *testing.T) {
types.RegisterExtras[
types.NOOPHeaderHooks, *types.NOOPHeaderHooks,
types.NOOPBodyHooks, *types.NOOPBodyHooks,
types.NOOPBlockHooks, *types.NOOPBlockHooks,
bool]().StateAccount.Set(acc, false)
types.RegisterExtras[
types.NOOPHeaderHooks, *types.NOOPHeaderHooks,
types.NOOPBodyHooks, *types.NOOPBodyHooks,
types.NOOPBlockHooks, *types.NOOPBlockHooks,
bool]().StateAccount.Set(acc, false)
},
wantEmpty: true,
Expand All @@ -63,6 +65,7 @@ func TestStateObjectEmpty(t *testing.T) {
types.RegisterExtras[
types.NOOPHeaderHooks, *types.NOOPHeaderHooks,
types.NOOPBodyHooks, *types.NOOPBodyHooks,
types.NOOPBlockHooks, *types.NOOPBlockHooks,
bool]()
},
wantEmpty: true,
Expand All @@ -73,6 +76,7 @@ func TestStateObjectEmpty(t *testing.T) {
types.RegisterExtras[
types.NOOPHeaderHooks, *types.NOOPHeaderHooks,
types.NOOPBodyHooks, *types.NOOPBodyHooks,
types.NOOPBlockHooks, *types.NOOPBlockHooks,
bool]().StateAccount.Set(acc, true)
},
wantEmpty: false,
Expand Down
10 changes: 6 additions & 4 deletions core/types/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ type Block struct {
// inter-peer block relay.
ReceivedAt time.Time
ReceivedFrom interface{}

extra *pseudo.Type // See [RegisterExtras]
}

// "external" block encoding. used for eth protocol, etc.
Expand Down Expand Up @@ -314,8 +316,8 @@ func CopyHeader(h *Header) *Header {
return &cpy
}

// DecodeRLP decodes a block from RLP.
func (b *Block) DecodeRLP(s *rlp.Stream) error {
// decodeRLP decodes a block from RLP.
func (b *Block) decodeRLP(s *rlp.Stream) error {
var eb extblock
_, size, _ := s.Kind()
if err := s.Decode(&eb); err != nil {
Expand All @@ -326,8 +328,8 @@ func (b *Block) DecodeRLP(s *rlp.Stream) error {
return nil
}

// EncodeRLP serializes a block as RLP.
func (b *Block) EncodeRLP(w io.Writer) error {
// encodeRLP serializes a block as RLP.
func (b *Block) encodeRLP(w io.Writer) error {
return rlp.Encode(w, &extblock{
Header: b.header,
Txs: b.transactions,
Expand Down
77 changes: 75 additions & 2 deletions core/types/block.libevm.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func (h *Header) hooks() HeaderHooks {
return new(NOOPHeaderHooks)
}

func (e ExtraPayloads[HPtr, BodyExtraPtr, SA]) hooksFromHeader(h *Header) HeaderHooks {
func (e ExtraPayloads[HPtr, BodyExtraPtr, BlockExtraPtr, SA]) hooksFromHeader(h *Header) HeaderHooks {
return e.Header.Get(h)
}

Expand Down Expand Up @@ -125,7 +125,7 @@ func (b *Body) hooks() BodyHooks {
return new(NOOPBodyHooks)
}

func (e ExtraPayloads[HPtr, BodyExtraPtr, SA]) hooksFromBody(b *Body) BodyHooks {
func (e ExtraPayloads[HPtr, BodyExtraPtr, BlockExtraPtr, SA]) hooksFromBody(b *Body) BodyHooks {
return e.Body.Get(b)
}

Expand Down Expand Up @@ -171,3 +171,76 @@ func (*NOOPBodyHooks) DecodeRLP(b *Body, s *rlp.Stream) error {
type withoutMethods Body
return s.Decode((*withoutMethods)(b))
}

// BlockHooks are required for all types registered with [RegisterExtras] for
// [Block] payloads.
type BlockHooks interface {
EncodeRLP(*Block, io.Writer) error
DecodeRLP(*Block, *rlp.Stream) error
}

// hooks returns the Block's registered BlockHooks, if any, otherwise a
// [*NOOPBlockHooks] suitable for running default behaviour.
func (b *Block) hooks() BlockHooks {
if r := registeredExtras; r.Registered() {
return r.Get().hooks.hooksFromBlock(b)
}
return new(NOOPBlockHooks)
}

func (e ExtraPayloads[HPtr, BodyExtraPtr, BlockExtraPtr, SA]) hooksFromBlock(b *Block) BlockHooks {
return e.Block.Get(b)
}

var _ interface {
rlp.Encoder
rlp.Decoder
} = (*Block)(nil)

// EncodeRLP implements the [rlp.Encoder] interface.
func (b *Block) EncodeRLP(w io.Writer) error {
return b.hooks().EncodeRLP(b, w)
}

// DecodeRLP implements the [rlp.Decoder] interface.
func (b *Block) DecodeRLP(s *rlp.Stream) error {
return b.hooks().DecodeRLP(b, s)
}

func (b *Block) 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().newBlock()
}
return b.extra
}

// NOOPBlockHooks implements [BlockHooks] such that they are equivalent to
// no type having been registered.
type NOOPBlockHooks struct{}

var _ BlockHooks = (*NOOPBlockHooks)(nil)

func (*NOOPBlockHooks) EncodeRLP(b *Block, w io.Writer) error {
return b.encodeRLP(w)
}

func (*NOOPBlockHooks) DecodeRLP(b *Block, s *rlp.Stream) error {
return b.decodeRLP(s)
}

func (b *Block) SetHeader(header *Header) {
b.header = header
}

func (b *Block) SetUncles(uncles []*Header) {
b.uncles = uncles
}

func (b *Block) SetTransactions(transactions Transactions) {
b.transactions = transactions
}
30 changes: 30 additions & 0 deletions core/types/block.libevm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,43 @@ func (bh *stubBodyHooks) DecodeRLP(b *Body, s *rlp.Stream) error {
return bh.errDecode
}

type stubBlockHooks struct {
suffix []byte
gotRawRLPToDecode []byte
setBlockToOnUnmarshalOrDecode Block

errEncode, errDecode error
}

func fakeBlockRLP(b *Block, suffix []byte) []byte {
return append(crypto.Keccak256(b.Header().ParentHash[:]), suffix...)
}

func (bh *stubBlockHooks) EncodeRLP(b *Block, w io.Writer) error {
if _, err := w.Write(fakeBlockRLP(b, bh.suffix)); err != nil {
return err
}
return bh.errEncode
}

func (bh *stubBlockHooks) DecodeRLP(b *Block, s *rlp.Stream) error {
r, err := s.Raw()
if err != nil {
return err
}
bh.gotRawRLPToDecode = r
*b = bh.setBlockToOnUnmarshalOrDecode
return bh.errDecode
}

func TestHeaderHooks(t *testing.T) {
TestOnlyClearRegisteredExtras()
defer TestOnlyClearRegisteredExtras()

extras := RegisterExtras[
stubHeaderHooks, *stubHeaderHooks,
stubBodyHooks, *stubBodyHooks,
stubBlockHooks, *stubBlockHooks,
struct{}]()
rng := ethtest.NewPseudoRand(13579)

Expand Down
2 changes: 1 addition & 1 deletion core/types/rlp_backwards_compat.libevm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func TestHeaderRLPBackwardsCompatibility(t *testing.T) {
RegisterExtras[
NOOPHeaderHooks, *NOOPHeaderHooks,
NOOPBodyHooks, *NOOPBodyHooks,
NOOPBlockHooks, *NOOPBlockHooks,
struct{}]()
},
},
Expand Down Expand Up @@ -88,7 +89,6 @@ func testHeaderRLPBackwardsCompatibility(t *testing.T) {
ExcessBlobGas: rng.Uint64Ptr(),
ParentBeaconRoot: rng.HashPtr(),
}
t.Logf("%T:\n%+v", hdr, hdr)

// WARNING: changing this hex might break backwards compatibility of RLP
// encoding (i.e. block hashes might change)!
Expand Down
36 changes: 24 additions & 12 deletions core/types/rlp_payload.libevm.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ import (
// The payloads can be accessed via the [pseudo.Accessor] methods of the
// [ExtraPayloads] returned by RegisterExtras. The default `SA` value accessed
// in this manner will be a zero-value `SA` while the default value from a
// [Header] is a non-nil `HPtr`. The latter guarantee ensures that hooks won't
// be called on nil-pointer receivers.
// [Header] is a non-nil `HPtr` and the default value from a [Block] is a non-nil
// `BlockExtraPtr`. The latter guarantee ensures that hooks won't be called on nil-pointer receivers.
func RegisterExtras[
H any, HPtr interface {
HeaderHooks
Expand All @@ -50,9 +50,13 @@ func RegisterExtras[
BodyHooks
*BodyExtra
},
BlockExtra any, BlockExtraPtr interface {
BlockHooks
*BlockExtra
},
SA any,
]() ExtraPayloads[HPtr, BodyExtraPtr, SA] {
extra := ExtraPayloads[HPtr, BodyExtraPtr, SA]{
]() ExtraPayloads[HPtr, BodyExtraPtr, BlockExtraPtr, SA] {
extra := ExtraPayloads[HPtr, BodyExtraPtr, BlockExtraPtr, SA]{
Header: pseudo.NewAccessor[*Header, HPtr](
(*Header).extraPayload,
func(h *Header, t *pseudo.Type) { h.extra = t },
Expand All @@ -61,6 +65,10 @@ func RegisterExtras[
(*Body).extraPayload,
func(b *Body, t *pseudo.Type) { b.extra = t },
),
Block: pseudo.NewAccessor[*Block, BlockExtraPtr](
(*Block).extraPayload,
func(b *Block, 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 @@ -71,11 +79,12 @@ func RegisterExtras[
var x SA
return fmt.Sprintf("%T", x)
}(),
// 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
// The [ExtraPayloads] that we returns is based on [HPtr,BodyExtraPtr,BlockExtraPtr,SA], not
// [H,BodyExtra,BlockExtra,SA] so our constructors MUST match that. This guarantees that calls to
// the [HeaderHooks], [BodyHooks] and [BlockHooks] 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
newBlock: pseudo.NewConstructor[BlockExtra]().NewPointer, // i.e. non-nil BlockExtraPtr
newStateAccount: pseudo.NewConstructor[SA]().Zero,
cloneStateAccount: extra.cloneStateAccount,
hooks: extra,
Expand All @@ -99,11 +108,13 @@ type extraConstructors struct {
stateAccountType string
newHeader func() *pseudo.Type
newBody func() *pseudo.Type
newBlock func() *pseudo.Type
newStateAccount func() *pseudo.Type
cloneStateAccount func(*StateAccountExtra) *StateAccountExtra
hooks interface {
hooksFromHeader(*Header) HeaderHooks
hooksFromBody(*Body) BodyHooks
hooksFromBlock(*Block) BlockHooks
}
}

Expand All @@ -117,15 +128,16 @@ func (e *StateAccountExtra) clone() *StateAccountExtra {
}

// ExtraPayloads provides strongly typed access to the extra payload carried by
// [Header], [Body], [StateAccount], and [SlimAccount] structs. The only valid way to
// [Header], [Body], [Block], [StateAccount], and [SlimAccount] structs. The only valid way to
// construct an instance is by a call to [RegisterExtras].
type ExtraPayloads[HPtr HeaderHooks, BodyExtraPtr BodyHooks, SA any] struct {
type ExtraPayloads[HPtr HeaderHooks, BodyExtraPtr BodyHooks, BlockExtraPtr BlockHooks, SA any] struct {
Header pseudo.Accessor[*Header, HPtr]
Body pseudo.Accessor[*Body, BodyExtraPtr]
Block pseudo.Accessor[*Block, BlockExtraPtr]
StateAccount pseudo.Accessor[StateOrSlimAccount, SA] // Also provides [SlimAccount] access.
}

func (ExtraPayloads[HPtr, BodyExtraPtr, SA]) cloneStateAccount(s *StateAccountExtra) *StateAccountExtra {
func (ExtraPayloads[HPtr, BodyExtraPtr, BlockExtraPtr, SA]) cloneStateAccount(s *StateAccountExtra) *StateAccountExtra {
v := pseudo.MustNewValue[SA](s.t)
return &StateAccountExtra{
t: pseudo.From(v.Get()).Type,
Expand Down
2 changes: 2 additions & 0 deletions core/types/state_account.libevm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func TestStateAccountRLP(t *testing.T) {
RegisterExtras[
NOOPHeaderHooks, *NOOPHeaderHooks,
NOOPBodyHooks, *NOOPBodyHooks,
NOOPBlockHooks, *NOOPBlockHooks,
bool]()
},
acc: &StateAccount{
Expand Down Expand Up @@ -82,6 +83,7 @@ func TestStateAccountRLP(t *testing.T) {
RegisterExtras[
NOOPHeaderHooks, *NOOPHeaderHooks,
NOOPBodyHooks, *NOOPBodyHooks,
NOOPBlockHooks, *NOOPBlockHooks,
bool]()
},
acc: &StateAccount{
Expand Down
4 changes: 4 additions & 0 deletions core/types/state_account_storage.libevm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ func TestStateAccountExtraViaTrieStorage(t *testing.T) {
e := types.RegisterExtras[
types.NOOPHeaderHooks, *types.NOOPHeaderHooks,
types.NOOPBodyHooks, *types.NOOPBodyHooks,
types.NOOPBlockHooks, *types.NOOPBlockHooks,
bool]()
e.StateAccount.Set(a, true)
return a, func(t *testing.T, got *types.StateAccount) { //nolint:thelper
Expand All @@ -90,6 +91,7 @@ func TestStateAccountExtraViaTrieStorage(t *testing.T) {
e := types.RegisterExtras[
types.NOOPHeaderHooks, *types.NOOPHeaderHooks,
types.NOOPBodyHooks, *types.NOOPBodyHooks,
types.NOOPBlockHooks, *types.NOOPBlockHooks,
bool]()
e.StateAccount.Set(a, false) // the explicit part

Expand All @@ -105,6 +107,7 @@ func TestStateAccountExtraViaTrieStorage(t *testing.T) {
e := types.RegisterExtras[
types.NOOPHeaderHooks, *types.NOOPHeaderHooks,
types.NOOPBodyHooks, *types.NOOPBodyHooks,
types.NOOPBlockHooks, *types.NOOPBlockHooks,
bool]()
// Note that `a` is reflected, unchanged (the implicit part).
return a, func(t *testing.T, got *types.StateAccount) { //nolint:thelper
Expand All @@ -119,6 +122,7 @@ func TestStateAccountExtraViaTrieStorage(t *testing.T) {
e := types.RegisterExtras[
types.NOOPHeaderHooks, *types.NOOPHeaderHooks,
types.NOOPBodyHooks, *types.NOOPBodyHooks,
types.NOOPBlockHooks, *types.NOOPBlockHooks,
arbitraryPayload]()
p := arbitraryPayload{arbitraryData}
e.StateAccount.Set(a, p)
Expand Down

0 comments on commit abbba71

Please sign in to comment.