Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core/types): Block RLP + Body hooks #105

Draft
wants to merge 2 commits into
base: qdm12/core/types/body-hooks-rlp
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
15 changes: 9 additions & 6 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 @@ -315,8 +317,8 @@ func CopyEthHeader(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 @@ -327,8 +329,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 All @@ -337,9 +339,10 @@ func (b *Block) EncodeRLP(w io.Writer) error {
})
}

// Body returns the non-header content of the block.
// EthBody returns the non-header content of an Ethereum block.
// Note the returned data is not an independent copy.
func (b *Block) Body() *Body {
// Use [Block.Body] instead if your block has any registered extra.
func (b *Block) EthBody() *Body {
return &Body{
Transactions: b.transactions,
Uncles: b.uncles,
Expand Down
88 changes: 86 additions & 2 deletions 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, 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 @@ -135,7 +135,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 @@ -181,3 +181,87 @@ 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
Body(*Block) *Body
}

// 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 (*NOOPBlockHooks) Body(b *Block) *Body {
return b.EthBody()
}

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
}

// Body returns the non-header content of the block.
// Note the returned data is not an independent copy.
func (b *Block) Body() *Body {
return b.hooks().Body(b)
}
34 changes: 34 additions & 0 deletions core/types/block.libevm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,47 @@ 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 (bh *stubBlockHooks) Body(b *Block) *Body {
return b.EthBody()
}

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