Skip to content

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

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

Closed
Closed
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
6 changes: 5 additions & 1 deletion core/state/state.libevm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ 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,
types.NOOPBlockHooks, *types.NOOPBlockHooks,
*accountExtra]().StateAccount

rng := ethtest.NewPseudoRand(42)
addr := rng.Address()
Expand Down
23 changes: 20 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,38 @@ 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,
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,
},
{
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,
types.NOOPBlockHooks, *types.NOOPBlockHooks,
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,
types.NOOPBlockHooks, *types.NOOPBlockHooks,
bool]().StateAccount.Set(acc, true)
},
wantEmpty: false,
},
Expand Down
28 changes: 19 additions & 9 deletions 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 @@ -209,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 @@ -277,8 +281,9 @@ func NewBlockWithWithdrawals(header *Header, txs []*Transaction, uncles []*Heade
return b.WithWithdrawals(withdrawals)
}

// CopyHeader creates a deep copy of a block header.
func CopyHeader(h *Header) *Header {
// CopyEthHeader creates a deep copy of an Ethereum block header.
// Use [CopyHeader] instead if your header has any registered extra.
func CopyEthHeader(h *Header) *Header {
cpy := *h
if cpy.Difficulty = new(big.Int); h.Difficulty != nil {
cpy.Difficulty.Set(h.Difficulty)
Expand Down Expand Up @@ -312,8 +317,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 @@ -324,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 @@ -334,10 +339,15 @@ 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 {
return &Body{b.transactions, b.uncles, b.withdrawals}
// Use [Block.Body] instead if your block has any registered extra.
func (b *Block) EthBody() *Body {
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
159 changes: 158 additions & 1 deletion core/types/block.libevm.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type HeaderHooks interface {
UnmarshalJSON(*Header, []byte) error //nolint:govet
EncodeRLP(*Header, io.Writer) error
DecodeRLP(*Header, *rlp.Stream) error
Copy(*Header) *Header
}

// hooks returns the Header's registered HeaderHooks, if any, otherwise a
Expand All @@ -43,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, BlockExtraPtr, SA]) hooksFromHeader(h *Header) HeaderHooks {
return e.Header.Get(h)
}

Expand Down Expand Up @@ -108,3 +109,159 @@ func (*NOOPHeaderHooks) DecodeRLP(h *Header, s *rlp.Stream) error {
type withoutMethods Header
return s.Decode((*withoutMethods)(h))
}

func (n *NOOPHeaderHooks) Copy(h *Header) *Header {
return CopyEthHeader(h)
}

// CopyHeader creates a deep copy of a block 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, BlockExtraPtr, 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))
}

// 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)
}
68 changes: 67 additions & 1 deletion core/types/block.libevm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,77 @@ func (hh *stubHeaderHooks) DecodeRLP(h *Header, s *rlp.Stream) error {
return hh.errDecode
}

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
}

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, struct{}]()
extras := RegisterExtras[
stubHeaderHooks, *stubHeaderHooks,
stubBodyHooks, *stubBodyHooks,
stubBlockHooks, *stubBlockHooks,
struct{}]()
rng := ethtest.NewPseudoRand(13579)

suffix := rng.Bytes(8)
Expand Down
Loading