diff --git a/core/state/state.libevm_test.go b/core/state/state.libevm_test.go index 406425b07ce9..92399d7d1406 100644 --- a/core/state/state.libevm_test.go +++ b/core/state/state.libevm_test.go @@ -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) diff --git a/core/state/state_object.libevm_test.go b/core/state/state_object.libevm_test.go index 4cdae081d257..2bea150678f9 100644 --- a/core/state/state_object.libevm_test.go +++ b/core/state/state_object.libevm_test.go @@ -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, @@ -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, @@ -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, diff --git a/core/types/block.go b/core/types/block.go index 836126adba85..64007c3a93b8 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -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. @@ -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 { @@ -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, diff --git a/core/types/block.libevm.go b/core/types/block.libevm.go index e702cbb9168d..e287839b596e 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, BodyExtraPtr, SA]) hooksFromHeader(h *Header) HeaderHooks { +func (e ExtraPayloads[HPtr, BodyExtraPtr, BlockExtraPtr, SA]) hooksFromHeader(h *Header) HeaderHooks { return e.Header.Get(h) } @@ -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) } @@ -181,3 +181,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 +} diff --git a/core/types/block.libevm_test.go b/core/types/block.libevm_test.go index 5fdd2005bdec..06cd0cef5416 100644 --- a/core/types/block.libevm_test.go +++ b/core/types/block.libevm_test.go @@ -104,6 +104,35 @@ 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() @@ -111,6 +140,7 @@ func TestHeaderHooks(t *testing.T) { extras := RegisterExtras[ stubHeaderHooks, *stubHeaderHooks, stubBodyHooks, *stubBodyHooks, + stubBlockHooks, *stubBlockHooks, struct{}]() rng := ethtest.NewPseudoRand(13579) diff --git a/core/types/rlp_backwards_compat.libevm_test.go b/core/types/rlp_backwards_compat.libevm_test.go index 98cef45d53e8..a1eca739d2aa 100644 --- a/core/types/rlp_backwards_compat.libevm_test.go +++ b/core/types/rlp_backwards_compat.libevm_test.go @@ -43,6 +43,7 @@ func TestHeaderRLPBackwardsCompatibility(t *testing.T) { RegisterExtras[ NOOPHeaderHooks, *NOOPHeaderHooks, NOOPBodyHooks, *NOOPBodyHooks, + NOOPBlockHooks, *NOOPBlockHooks, struct{}]() }, }, @@ -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)! diff --git a/core/types/rlp_payload.libevm.go b/core/types/rlp_payload.libevm.go index f6b72689f941..04f1a6541014 100644 --- a/core/types/rlp_payload.libevm.go +++ b/core/types/rlp_payload.libevm.go @@ -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 @@ -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 }, @@ -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 }, @@ -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, @@ -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 } } @@ -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, diff --git a/core/types/state_account.libevm_test.go b/core/types/state_account.libevm_test.go index 2458df8ace40..cf83d0f4db43 100644 --- a/core/types/state_account.libevm_test.go +++ b/core/types/state_account.libevm_test.go @@ -49,6 +49,7 @@ func TestStateAccountRLP(t *testing.T) { RegisterExtras[ NOOPHeaderHooks, *NOOPHeaderHooks, NOOPBodyHooks, *NOOPBodyHooks, + NOOPBlockHooks, *NOOPBlockHooks, bool]() }, acc: &StateAccount{ @@ -82,6 +83,7 @@ func TestStateAccountRLP(t *testing.T) { RegisterExtras[ NOOPHeaderHooks, *NOOPHeaderHooks, NOOPBodyHooks, *NOOPBodyHooks, + NOOPBlockHooks, *NOOPBlockHooks, bool]() }, acc: &StateAccount{ diff --git a/core/types/state_account_storage.libevm_test.go b/core/types/state_account_storage.libevm_test.go index adf3ed15bc02..e82a01771486 100644 --- a/core/types/state_account_storage.libevm_test.go +++ b/core/types/state_account_storage.libevm_test.go @@ -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 @@ -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 @@ -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 @@ -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)