diff --git a/core/state/state.libevm_test.go b/core/state/state.libevm_test.go index bd909203102b..0d816038f0cc 100644 --- a/core/state/state.libevm_test.go +++ b/core/state/state.libevm_test.go @@ -47,7 +47,7 @@ func TestGetSetExtra(t *testing.T) { // test deep copying. payloads := types.RegisterExtras[ types.NOOPHeaderHooks, *types.NOOPHeaderHooks, - types.NOOPBodyHooks, *types.NOOPBodyHooks, + types.NOOPBlockBodyHooks, *types.NOOPBlockBodyHooks, *accountExtra, ]().StateAccount diff --git a/core/state/state_object.libevm_test.go b/core/state/state_object.libevm_test.go index 034764d419f2..299241495cee 100644 --- a/core/state/state_object.libevm_test.go +++ b/core/state/state_object.libevm_test.go @@ -48,7 +48,7 @@ func TestStateObjectEmpty(t *testing.T) { registerAndSet: func(acc *types.StateAccount) { types.RegisterExtras[ types.NOOPHeaderHooks, *types.NOOPHeaderHooks, - types.NOOPBodyHooks, *types.NOOPBodyHooks, + types.NOOPBlockBodyHooks, *types.NOOPBlockBodyHooks, bool, ]().StateAccount.Set(acc, false) }, @@ -59,7 +59,7 @@ func TestStateObjectEmpty(t *testing.T) { registerAndSet: func(*types.StateAccount) { types.RegisterExtras[ types.NOOPHeaderHooks, *types.NOOPHeaderHooks, - types.NOOPBodyHooks, *types.NOOPBodyHooks, + types.NOOPBlockBodyHooks, *types.NOOPBlockBodyHooks, bool, ]() }, @@ -70,7 +70,7 @@ func TestStateObjectEmpty(t *testing.T) { registerAndSet: func(acc *types.StateAccount) { types.RegisterExtras[ types.NOOPHeaderHooks, *types.NOOPHeaderHooks, - types.NOOPBodyHooks, *types.NOOPBodyHooks, + types.NOOPBlockBodyHooks, *types.NOOPBlockBodyHooks, bool, ]().StateAccount.Set(acc, true) }, diff --git a/core/types/backwards_compat.libevm_test.go b/core/types/backwards_compat.libevm_test.go index b7dcc20cb464..dca9245fcde3 100644 --- a/core/types/backwards_compat.libevm_test.go +++ b/core/types/backwards_compat.libevm_test.go @@ -30,11 +30,11 @@ import ( "github.com/ava-labs/libevm/rlp" ) -func TestBodyRLPBackwardsCompatibility(t *testing.T) { - newTx := func(nonce uint64) *Transaction { return NewTx(&LegacyTx{Nonce: nonce}) } - newHdr := func(hashLow byte) *Header { return &Header{ParentHash: common.Hash{hashLow}} } - newWithdraw := func(idx uint64) *Withdrawal { return &Withdrawal{Index: idx} } +func newTx(nonce uint64) *Transaction { return NewTx(&LegacyTx{Nonce: nonce}) } +func newHdr(parentHashHigh byte) *Header { return &Header{ParentHash: common.Hash{parentHashHigh}} } +func newWithdraw(idx uint64) *Withdrawal { return &Withdrawal{Index: idx} } +func blockBodyRLPTestInputs() []*Body { // We build up test-case [Body] instances from the Cartesian product of each // of these components. txMatrix := [][]*Transaction{ @@ -61,8 +61,11 @@ func TestBodyRLPBackwardsCompatibility(t *testing.T) { } } } + return bodies +} - for _, body := range bodies { +func TestBodyRLPBackwardsCompatibility(t *testing.T) { + for _, body := range blockBodyRLPTestInputs() { t.Run("", func(t *testing.T) { t.Cleanup(func() { if t.Failed() { @@ -86,8 +89,10 @@ func TestBodyRLPBackwardsCompatibility(t *testing.T) { t.Run("Decode", func(t *testing.T) { got := new(Body) err := rlp.DecodeBytes(wantRLP, got) - require.NoErrorf(t, err, "rlp.DecodeBytes(rlp.EncodeToBytes(%T), %T) resulted in %s", - (*withoutMethods)(body), got, pretty.Sprint(got)) + require.NoErrorf( + t, err, "rlp.DecodeBytes(rlp.EncodeToBytes(%T), %T) resulted in %s", + (*withoutMethods)(body), got, pretty.Sprint(got), + ) want := body // Regular RLP decoding will never leave these non-optional @@ -112,17 +117,94 @@ func TestBodyRLPBackwardsCompatibility(t *testing.T) { } } +func TestBlockRLPBackwardsCompatibility(t *testing.T) { + TestOnlyClearRegisteredExtras() + t.Cleanup(TestOnlyClearRegisteredExtras) + + RegisterExtras[ + NOOPHeaderHooks, *NOOPHeaderHooks, + NOOPBlockBodyHooks, *NOOPBlockBodyHooks, // types under test + struct{}, + ]() + + // Note that there are also a number of tests in `block_test.go` that ensure + // backwards compatibility as [NOOPBlockBodyHooks] are used by default when + // nothing is registered (the above registration is only for completeness). + + for _, body := range blockBodyRLPTestInputs() { + t.Run("", func(t *testing.T) { + // [Block] doesn't export most of its fields so uses [extblock] as a + // proxy for RLP encoding, which is what we therefore use as the + // backwards-compatible gold standard. + hdr := newHdr(99) + block := extblock{ + Header: hdr, + Txs: body.Transactions, + Uncles: body.Uncles, + Withdrawals: body.Withdrawals, + } + + // We've added [extblock.EncodeRLP] and [extblock.DecodeRLP] for our + // hooks. + type withoutMethods extblock + + wantRLP, err := rlp.EncodeToBytes(withoutMethods(block)) + require.NoErrorf(t, err, "rlp.EncodeToBytes([%T with methods stripped])", block) + + // Our input to RLP might not be the canonical RLP output. + var wantBlock extblock + err = rlp.DecodeBytes(wantRLP, (*withoutMethods)(&wantBlock)) + require.NoErrorf(t, err, "rlp.DecodeBytes(..., [%T with methods stripped])", &wantBlock) + + t.Run("Encode", func(t *testing.T) { + b := NewBlockWithHeader(hdr).WithBody(*body).WithWithdrawals(body.Withdrawals) + got, err := rlp.EncodeToBytes(b) + require.NoErrorf(t, err, "rlp.EncodeToBytes(%T)", b) + + assert.Equalf(t, wantRLP, got, "expect %T RLP identical to that from %T struct stripped of methods", got, extblock{}) + }) + + t.Run("Decode", func(t *testing.T) { + var gotBlock Block + err := rlp.DecodeBytes(wantRLP, &gotBlock) + require.NoErrorf(t, err, "rlp.DecodeBytes(..., %T)", &gotBlock) + + got := extblock{ + gotBlock.Header(), + gotBlock.Transactions(), + gotBlock.Uncles(), + gotBlock.Withdrawals(), + nil, // unexported libevm hooks + } + + opts := cmp.Options{ + cmp.Comparer((*Header).equalHash), + cmp.Comparer((*Transaction).equalHash), + cmpopts.IgnoreUnexported(extblock{}), + } + if diff := cmp.Diff(wantBlock, got, opts); diff != "" { + t.Errorf("rlp.DecodeBytes([RLP from %T stripped of methods], ...) diff (-want +got):\n%s", extblock{}, diff) + } + }) + }) + } +} + // cChainBodyExtras carries the same additional fields as the Avalanche C-Chain -// (ava-labs/coreth) [Body] and implements [BodyHooks] to achieve equivalent RLP -// {en,de}coding. +// (ava-labs/coreth) [Body] and implements [BlockBodyHooks] to achieve +// equivalent RLP {en,de}coding. +// +// It is not intended as a full test of ava-labs/coreth existing functionality, +// which should be implemented when that module consumes libevm, but as proof of +// equivalence of the [rlp.Fields] approach. type cChainBodyExtras struct { Version uint32 ExtData *[]byte } -var _ BodyHooks = (*cChainBodyExtras)(nil) +var _ BlockBodyHooks = (*cChainBodyExtras)(nil) -func (e *cChainBodyExtras) RLPFieldsForEncoding(b *Body) *rlp.Fields { +func (e *cChainBodyExtras) BodyRLPFieldsForEncoding(b *Body) *rlp.Fields { // The Avalanche C-Chain uses all of the geth required fields (but none of // the optional ones) so there's no need to explicitly list them. This // pattern might not be ideal for readability but is used here for @@ -132,13 +214,13 @@ func (e *cChainBodyExtras) RLPFieldsForEncoding(b *Body) *rlp.Fields { // compatibility so this is safe to do, but only for the required fields. return &rlp.Fields{ Required: append( - NOOPBodyHooks{}.RLPFieldsForEncoding(b).Required, + NOOPBlockBodyHooks{}.BodyRLPFieldsForEncoding(b).Required, e.Version, e.ExtData, ), } } -func (e *cChainBodyExtras) RLPFieldPointersForDecoding(b *Body) *rlp.Fields { +func (e *cChainBodyExtras) BodyRLPFieldPointersForDecoding(b *Body) *rlp.Fields { // An alternative to the pattern used above is to explicitly list all // fields for better introspection. return &rlp.Fields{ @@ -151,6 +233,20 @@ func (e *cChainBodyExtras) RLPFieldPointersForDecoding(b *Body) *rlp.Fields { } } +// See [cChainBodyExtras] intent. + +func (e *cChainBodyExtras) Copy() *cChainBodyExtras { + panic("unimplemented") +} + +func (e *cChainBodyExtras) BlockRLPFieldsForEncoding(b *BlockRLPProxy) *rlp.Fields { + panic("unimplemented") +} + +func (e *cChainBodyExtras) BlockRLPFieldPointersForDecoding(b *BlockRLPProxy) *rlp.Fields { + panic("unimplemented") +} + func TestBodyRLPCChainCompat(t *testing.T) { // The inputs to this test were used to generate the expected RLP with // ava-labs/coreth. This serves as both an example of how to use [BodyHooks] diff --git a/core/types/backwards_compat_diffpkg.libevm_test.go b/core/types/backwards_compat_diffpkg.libevm_test.go index 669c3fd0ea7d..e4c0f2b6d1b2 100644 --- a/core/types/backwards_compat_diffpkg.libevm_test.go +++ b/core/types/backwards_compat_diffpkg.libevm_test.go @@ -42,7 +42,7 @@ func TestHeaderRLPBackwardsCompatibility(t *testing.T) { register: func() { RegisterExtras[ NOOPHeaderHooks, *NOOPHeaderHooks, - NOOPBodyHooks, *NOOPBodyHooks, + NOOPBlockBodyHooks, *NOOPBlockBodyHooks, struct{}, ]() }, diff --git a/core/types/block.go b/core/types/block.go index f6a46e1bfb3c..95d0df68f727 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. @@ -219,6 +221,8 @@ type extblock struct { Txs []*Transaction Uncles []*Header Withdrawals []*Withdrawal `rlp:"optional"` + + hooks BlockBodyHooks // libevm: MUST be unexported + populated from [Block.hooks] } // NewBlock creates a new block. The input data is copied, changes to header and to the @@ -318,6 +322,7 @@ func CopyHeader(h *Header) *Header { // DecodeRLP decodes a block from RLP. func (b *Block) DecodeRLP(s *rlp.Stream) error { var eb extblock + eb.hooks = b.hooks() _, size, _ := s.Kind() if err := s.Decode(&eb); err != nil { return err @@ -334,13 +339,14 @@ func (b *Block) EncodeRLP(w io.Writer) error { Txs: b.transactions, Uncles: b.uncles, Withdrawals: b.withdrawals, + hooks: b.hooks(), }) } // 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, nil /* unexported extras field */} + return &Body{b.transactions, b.uncles, b.withdrawals, b.cloneExtra()} } // Accessors for body data. These do not return a copy because the content @@ -458,6 +464,7 @@ func (b *Block) WithSeal(header *Header) *Block { transactions: b.transactions, uncles: b.uncles, withdrawals: b.withdrawals, + extra: b.cloneExtra(), } } @@ -468,6 +475,7 @@ func (b *Block) WithBody(body Body) *Block { transactions: make([]*Transaction, len(body.Transactions)), uncles: make([]*Header, len(body.Uncles)), withdrawals: b.withdrawals, + extra: body.cloneExtra(), } copy(block.transactions, body.Transactions) for i := range body.Uncles { @@ -482,6 +490,7 @@ func (b *Block) WithWithdrawals(withdrawals []*Withdrawal) *Block { header: b.header, transactions: b.transactions, uncles: b.uncles, + extra: b.cloneExtra(), } if withdrawals != nil { block.withdrawals = make([]*Withdrawal, len(withdrawals)) diff --git a/core/types/block.libevm.go b/core/types/block.libevm.go index 747b34b576a8..a512118dd413 100644 --- a/core/types/block.libevm.go +++ b/core/types/block.libevm.go @@ -20,6 +20,7 @@ import ( "encoding/json" "io" + "github.com/ava-labs/libevm/libevm/pseudo" "github.com/ava-labs/libevm/rlp" ) @@ -84,46 +85,95 @@ func (*NOOPHeaderHooks) DecodeRLP(h *Header, s *rlp.Stream) error { } func (*NOOPHeaderHooks) PostCopy(dst *Header) {} -var _ interface { +var _ = []interface { rlp.Encoder rlp.Decoder -} = (*Body)(nil) +}{ + (*Body)(nil), + (*extblock)(nil), +} // EncodeRLP implements the [rlp.Encoder] interface. func (b *Body) EncodeRLP(w io.Writer) error { - return b.hooks().RLPFieldsForEncoding(b).EncodeRLP(w) + return b.hooks().BodyRLPFieldsForEncoding(b).EncodeRLP(w) } // DecodeRLP implements the [rlp.Decoder] interface. func (b *Body) DecodeRLP(s *rlp.Stream) error { - return b.hooks().RLPFieldPointersForDecoding(b).DecodeRLP(s) + return b.hooks().BodyRLPFieldPointersForDecoding(b).DecodeRLP(s) +} + +// BlockRLPProxy exports the geth-internal type used for RLP {en,de}coding of a +// [Block]. +type BlockRLPProxy extblock + +func (b *extblock) EncodeRLP(w io.Writer) error { + bb := (*BlockRLPProxy)(b) + return b.hooks.BlockRLPFieldsForEncoding(bb).EncodeRLP(w) } -// BodyHooks are required for all types registered with [RegisterExtras] for -// [Body] payloads. -type BodyHooks interface { - RLPFieldsForEncoding(*Body) *rlp.Fields - RLPFieldPointersForDecoding(*Body) *rlp.Fields +func (b *extblock) DecodeRLP(s *rlp.Stream) error { + bb := (*BlockRLPProxy)(b) + return b.hooks.BlockRLPFieldPointersForDecoding(bb).DecodeRLP(s) } -// NOOPBodyHooks implements [BodyHooks] such that they are equivalent to no type -// having been registered. -type NOOPBodyHooks struct{} +// BlockBodyHooks are required for all types registered with [RegisterExtras] +// for [Block] and [Body] payloads. +type BlockBodyHooks interface { + BlockRLPFieldsForEncoding(*BlockRLPProxy) *rlp.Fields + BlockRLPFieldPointersForDecoding(*BlockRLPProxy) *rlp.Fields + BodyRLPFieldsForEncoding(*Body) *rlp.Fields + BodyRLPFieldPointersForDecoding(*Body) *rlp.Fields +} -// The RLP-related methods of [NOOPBodyHooks] make assumptions about the struct -// fields and their order, which we lock in here as a change detector. If this -// breaks then it MUST be updated and the RLP methods reviewed + new +// NOOPBlockBodyHooks implements [BlockBodyHooks] such that they are equivalent +// to no type having been registered. +type NOOPBlockBodyHooks struct{} + +var _ BlockBodyPayload[*NOOPBlockBodyHooks] = NOOPBlockBodyHooks{} + +func (NOOPBlockBodyHooks) Copy() *NOOPBlockBodyHooks { return &NOOPBlockBodyHooks{} } + +// The RLP-related methods of [NOOPBlockBodyHooks] make assumptions about the +// struct fields and their order, which we lock in here as a change detector. If +// these break then they MUST be updated and the RLP methods reviewed + new // backwards-compatibility tests added. -var _ = &Body{[]*Transaction{}, []*Header{}, []*Withdrawal{}, nil /* extra unexported type */} +var ( + _ = &Body{ + []*Transaction{}, []*Header{}, []*Withdrawal{}, // geth + &pseudo.Type{}, // libevm + } + _ = extblock{ + &Header{}, []*Transaction{}, []*Header{}, []*Withdrawal{}, // geth + BlockBodyHooks(nil), // libevm + } + // Demonstrate identity of these two types, by definition but useful for + // inspection here. + _ = extblock(BlockRLPProxy{}) +) + +func (NOOPBlockBodyHooks) BlockRLPFieldsForEncoding(b *BlockRLPProxy) *rlp.Fields { + return &rlp.Fields{ + Required: []any{b.Header, b.Txs, b.Uncles}, + Optional: []any{b.Withdrawals}, + } +} + +func (NOOPBlockBodyHooks) BlockRLPFieldPointersForDecoding(b *BlockRLPProxy) *rlp.Fields { + return &rlp.Fields{ + Required: []any{&b.Header, &b.Txs, &b.Uncles}, + Optional: []any{&b.Withdrawals}, + } +} -func (NOOPBodyHooks) RLPFieldsForEncoding(b *Body) *rlp.Fields { +func (NOOPBlockBodyHooks) BodyRLPFieldsForEncoding(b *Body) *rlp.Fields { return &rlp.Fields{ Required: []any{b.Transactions, b.Uncles}, Optional: []any{b.Withdrawals}, } } -func (NOOPBodyHooks) RLPFieldPointersForDecoding(b *Body) *rlp.Fields { +func (NOOPBlockBodyHooks) BodyRLPFieldPointersForDecoding(b *Body) *rlp.Fields { return &rlp.Fields{ Required: []any{&b.Transactions, &b.Uncles}, Optional: []any{&b.Withdrawals}, diff --git a/core/types/block.libevm_test.go b/core/types/block.libevm_test.go index 39898c7aec3d..8e9a250e759d 100644 --- a/core/types/block.libevm_test.go +++ b/core/types/block.libevm_test.go @@ -21,6 +21,8 @@ import ( "errors" "fmt" "io" + "reflect" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -88,7 +90,7 @@ func TestHeaderHooks(t *testing.T) { extras := RegisterExtras[ stubHeaderHooks, *stubHeaderHooks, - NOOPBodyHooks, *NOOPBodyHooks, + NOOPBlockBodyHooks, *NOOPBlockBodyHooks, struct{}, ]() rng := ethtest.NewPseudoRand(13579) @@ -200,3 +202,71 @@ func TestHeaderHooks(t *testing.T) { } }) } + +type blockPayload struct { + NOOPBlockBodyHooks + x int +} + +func (p *blockPayload) Copy() *blockPayload { + return &blockPayload{x: p.x} +} + +func TestBlockWithX(t *testing.T) { + TestOnlyClearRegisteredExtras() + t.Cleanup(TestOnlyClearRegisteredExtras) + + extras := RegisterExtras[ + NOOPHeaderHooks, *NOOPHeaderHooks, + blockPayload, *blockPayload, + struct{}, + ]() + + typ := reflect.TypeOf(&Block{}) + for i := 0; i < typ.NumMethod(); i++ { + method := typ.Method(i).Name + if method == "Withdrawals" || !strings.HasPrefix(method, "With") { + continue + } + + block := NewBlockWithHeader(&Header{}) + const initialPayload = int(42) + payload := &blockPayload{ + x: initialPayload, + } + extras.Block.Set(block, payload) + + t.Run(method, func(t *testing.T) { + var newBlock *Block + + switch method { + case "WithBody": + var body Body + extras.Body.Set(&body, payload) + newBlock = block.WithBody(body) + case "WithSeal": + newBlock = block.WithSeal(&Header{}) + case "WithWithdrawals": + newBlock = block.WithWithdrawals(nil) + default: + t.Fatalf("method call not implemented: %s", method) + } + + payload.x++ + // This specifically uses `require` instead of `assert` because a + // failure here invalidates the next test, which demonstrates a deep + // copy. + require.Equalf(t, initialPayload+1, extras.Block.Get(block).x, "%T payload %T after modification via pointer", block, payload) + + switch got := extras.Block.Get(newBlock); got.x { + case initialPayload: // expected + case 0: + t.Errorf("%T payload %T got zero value; the payload was probably not copied, resulting in a default being created", newBlock, got) + case initialPayload + 1: + t.Errorf("%T payload %T got same value as modified original; the payload was probably shallow copied", newBlock, got) + default: + t.Errorf("%T payload %T got %d, want %d; this is unexpected even as an error so you're on your own here", newBlock, got, got.x, initialPayload) + } + }) + } +} diff --git a/core/types/rlp_payload.libevm.go b/core/types/rlp_payload.libevm.go index 10fd1dbabb9a..98aebe1cc4b0 100644 --- a/core/types/rlp_payload.libevm.go +++ b/core/types/rlp_payload.libevm.go @@ -27,9 +27,9 @@ import ( ) // RegisterExtras registers the type `HPtr` to be carried as an extra payload in -// [Header] structs and the type `SA` in [StateAccount] and [SlimAccount] -// structs. It is expected to be called in an `init()` function and MUST NOT be -// called more than once. +// [Header] structs, the type `BPtr` in [Block] and [Body] structs, and the type +// `SA` in [StateAccount] and [SlimAccount] structs. It is expected to be called +// in an `init()` function and MUST NOT be called more than once. // // The `SA` payload will be treated as an extra struct field for the purposes of // RLP encoding and decoding. RLP handling is plumbed through to the `SA` via @@ -39,15 +39,15 @@ 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] or [Block] / [Body] is a non-nil `HPtr` or `BPtr` respectively. The +// latter guarantee ensures that hooks won't be called on nil-pointer receivers. func RegisterExtras[ H any, HPtr interface { HeaderHooks *H }, B any, BPtr interface { - BodyHooks + BlockBodyPayload[BPtr] *B }, SA any, @@ -61,6 +61,10 @@ func RegisterExtras[ (*Body).extraPayload, func(b *Body, t *pseudo.Type) { b.extra = t }, ), + Block: pseudo.NewAccessor[*Block, BPtr]( + (*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 }, @@ -72,16 +76,25 @@ func RegisterExtras[ return fmt.Sprintf("%T", x) }(), // The [ExtraPayloads] that we returns is based on [HPtr,BPtr,SA], not - // [H,B,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. + // [H,B,SA] so our constructors MUST match that. This guarantees that + // calls to the [HeaderHooks] and [BlockBodyHooks] methods will never be + // performed on a nil pointer. newHeader: pseudo.NewConstructor[H]().NewPointer, // i.e. non-nil HPtr - newBody: pseudo.NewConstructor[B]().NewPointer, // i.e. non-nil BPtr + newBlockOrBody: pseudo.NewConstructor[B]().NewPointer, // i.e. non-nil BPtr newStateAccount: pseudo.NewConstructor[SA]().Zero, hooks: extra, }) return extra } +// A BlockBodyPayload is an implementation of [BlockBodyHooks] that is also able +// to clone itself. Both [Block.Body] and [Block.WithBody] require this +// functionality to copy the payload between the types. +type BlockBodyPayload[BPtr any] interface { + BlockBodyHooks + Copy() BPtr +} + // TestOnlyClearRegisteredExtras clears the [Extras] previously passed to // [RegisterExtras]. It panics if called from a non-testing call stack. // @@ -97,11 +110,14 @@ var registeredExtras register.AtMostOnce[*extraConstructors] type extraConstructors struct { stateAccountType string newHeader func() *pseudo.Type - newBody func() *pseudo.Type + newBlockOrBody func() *pseudo.Type newStateAccount func() *pseudo.Type hooks interface { hooksFromHeader(*Header) HeaderHooks - hooksFromBody(*Body) BodyHooks + hooksFromBody(*Body) BlockBodyHooks + hooksFromBlock(*Block) BlockBodyHooks + cloneBlockPayload(*Block) *pseudo.Type + cloneBodyPayload(*Body) *pseudo.Type cloneStateAccount(*StateAccountExtra) *StateAccountExtra } } @@ -126,12 +142,16 @@ func (h *Header) extraPayload() *pseudo.Type { func (b *Body) extraPayload() *pseudo.Type { return extraPayloadOrSetDefault(&b.extra, func(c *extraConstructors) *pseudo.Type { - return c.newBody() + return c.newBlockOrBody() + }) +} + +func (b *Block) extraPayload() *pseudo.Type { + return extraPayloadOrSetDefault(&b.extra, func(c *extraConstructors) *pseudo.Type { + return c.newBlockOrBody() }) } -// hooks returns the [Header]'s registered [HeaderHooks], if any, otherwise a -// [NOOPHeaderHooks] suitable for running default behaviour. func (h *Header) hooks() HeaderHooks { if r := registeredExtras; r.Registered() { return r.Get().hooks.hooksFromHeader(h) @@ -139,13 +159,18 @@ func (h *Header) hooks() HeaderHooks { return new(NOOPHeaderHooks) } -// hooks returns the [Body]'s registered [BodyHooks], if any, otherwise a -// [NOOPBodyHooks] suitable for running default behaviour. -func (b *Body) hooks() BodyHooks { +func (b *Body) hooks() BlockBodyHooks { if r := registeredExtras; r.Registered() { return r.Get().hooks.hooksFromBody(b) } - return NOOPBodyHooks{} + return NOOPBlockBodyHooks{} +} + +func (b *Block) hooks() BlockBodyHooks { + if r := registeredExtras; r.Registered() { + return r.Get().hooks.hooksFromBlock(b) + } + return NOOPBlockBodyHooks{} } func (e *StateAccountExtra) clone() *StateAccountExtra { @@ -160,14 +185,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 // construct an instance is by a call to [RegisterExtras]. -type ExtraPayloads[HPtr HeaderHooks, BPtr BodyHooks, SA any] struct { +type ExtraPayloads[HPtr HeaderHooks, BPtr BlockBodyPayload[BPtr], SA any] struct { Header pseudo.Accessor[*Header, HPtr] + Block pseudo.Accessor[*Block, BPtr] Body pseudo.Accessor[*Body, BPtr] StateAccount pseudo.Accessor[StateOrSlimAccount, SA] // Also provides [SlimAccount] access. } -func (e ExtraPayloads[HPtr, BPtr, SA]) hooksFromHeader(h *Header) HeaderHooks { return e.Header.Get(h) } -func (e ExtraPayloads[HPtr, BPtr, SA]) hooksFromBody(b *Body) BodyHooks { return e.Body.Get(b) } +func (e ExtraPayloads[HPtr, BPtr, SA]) hooksFromHeader(h *Header) HeaderHooks { return e.Header.Get(h) } +func (e ExtraPayloads[HPtr, BPtr, SA]) hooksFromBody(b *Body) BlockBodyHooks { return e.Body.Get(b) } +func (e ExtraPayloads[HPtr, BPtr, SA]) hooksFromBlock(b *Block) BlockBodyHooks { return e.Block.Get(b) } func (ExtraPayloads[HPtr, BPtr, SA]) cloneStateAccount(s *StateAccountExtra) *StateAccountExtra { v := pseudo.MustNewValue[SA](s.t) @@ -176,6 +203,43 @@ func (ExtraPayloads[HPtr, BPtr, SA]) cloneStateAccount(s *StateAccountExtra) *St } } +// blockOrBody is an interface for use as a method argument as they can't +// introduce new generic type parameters. +type blockOrBody interface { + isBlockOrBody() // noop to restrict type as [Header.extraPayload] otherwise matches + extraPayload() *pseudo.Type +} + +func (*Block) isBlockOrBody() {} +func (*Body) isBlockOrBody() {} + +func (e ExtraPayloads[HPtr, BPtr, SA]) cloneBodyPayload(b *Body) *pseudo.Type { + return e.cloneBlockOrBodyPayload(b) +} + +func (e ExtraPayloads[HPtr, BPtr, SA]) cloneBlockPayload(b *Block) *pseudo.Type { + return e.cloneBlockOrBodyPayload(b) +} + +func (ExtraPayloads[HPtr, BPtr, SA]) cloneBlockOrBodyPayload(b blockOrBody) *pseudo.Type { + v := pseudo.MustNewValue[BPtr](b.extraPayload()) + return pseudo.From(v.Get().Copy()).Type +} + +func (b *Body) cloneExtra() *pseudo.Type { + if r := registeredExtras; r.Registered() { + return r.Get().hooks.cloneBodyPayload(b) + } + return nil +} + +func (b *Block) cloneExtra() *pseudo.Type { + if r := registeredExtras; r.Registered() { + return r.Get().hooks.cloneBlockPayload(b) + } + return nil +} + // StateOrSlimAccount is implemented by both [StateAccount] and [SlimAccount], // allowing for their [StateAccountExtra] payloads to be accessed in a type-safe // manner by [ExtraPayloads] instances. diff --git a/core/types/state_account.libevm_test.go b/core/types/state_account.libevm_test.go index 8ae8185d38e4..9a91017674b7 100644 --- a/core/types/state_account.libevm_test.go +++ b/core/types/state_account.libevm_test.go @@ -48,7 +48,7 @@ func TestStateAccountRLP(t *testing.T) { register: func() { RegisterExtras[ NOOPHeaderHooks, *NOOPHeaderHooks, - NOOPBodyHooks, *NOOPBodyHooks, + NOOPBlockBodyHooks, *NOOPBlockBodyHooks, bool, ]() }, @@ -82,7 +82,7 @@ func TestStateAccountRLP(t *testing.T) { register: func() { RegisterExtras[ NOOPHeaderHooks, *NOOPHeaderHooks, - NOOPBodyHooks, *NOOPBodyHooks, + NOOPBlockBodyHooks, *NOOPBlockBodyHooks, bool, ]() }, diff --git a/core/types/state_account_storage.libevm_test.go b/core/types/state_account_storage.libevm_test.go index 29311a8d1db9..340621e591f6 100644 --- a/core/types/state_account_storage.libevm_test.go +++ b/core/types/state_account_storage.libevm_test.go @@ -75,7 +75,7 @@ func TestStateAccountExtraViaTrieStorage(t *testing.T) { registerAndSetExtra: func(a *types.StateAccount) (*types.StateAccount, assertion) { e := types.RegisterExtras[ types.NOOPHeaderHooks, *types.NOOPHeaderHooks, - types.NOOPBodyHooks, *types.NOOPBodyHooks, + types.NOOPBlockBodyHooks, *types.NOOPBlockBodyHooks, bool, ]() e.StateAccount.Set(a, true) @@ -90,7 +90,7 @@ func TestStateAccountExtraViaTrieStorage(t *testing.T) { registerAndSetExtra: func(a *types.StateAccount) (*types.StateAccount, assertion) { e := types.RegisterExtras[ types.NOOPHeaderHooks, *types.NOOPHeaderHooks, - types.NOOPBodyHooks, *types.NOOPBodyHooks, + types.NOOPBlockBodyHooks, *types.NOOPBlockBodyHooks, bool, ]() e.StateAccount.Set(a, false) // the explicit part @@ -106,7 +106,7 @@ func TestStateAccountExtraViaTrieStorage(t *testing.T) { registerAndSetExtra: func(a *types.StateAccount) (*types.StateAccount, assertion) { e := types.RegisterExtras[ types.NOOPHeaderHooks, *types.NOOPHeaderHooks, - types.NOOPBodyHooks, *types.NOOPBodyHooks, + types.NOOPBlockBodyHooks, *types.NOOPBlockBodyHooks, bool, ]() // Note that `a` is reflected, unchanged (the implicit part). @@ -121,7 +121,7 @@ func TestStateAccountExtraViaTrieStorage(t *testing.T) { registerAndSetExtra: func(a *types.StateAccount) (*types.StateAccount, assertion) { e := types.RegisterExtras[ types.NOOPHeaderHooks, *types.NOOPHeaderHooks, - types.NOOPBodyHooks, *types.NOOPBodyHooks, + types.NOOPBlockBodyHooks, *types.NOOPBlockBodyHooks, arbitraryPayload, ]() p := arbitraryPayload{arbitraryData}