From a8ffd92f0c5b004beacedb8b0b3d8a598e63ebd6 Mon Sep 17 00:00:00 2001 From: Nathan Date: Tue, 30 Apr 2024 17:22:02 +0800 Subject: [PATCH 1/7] params: print time value instead of pointer in ConfigCompatError (#29514) Cherry-pick of `69f815f6f5791e0e48160bdad284773d0ffb1ba9`. --- params/config.go | 12 ++++++++++-- params/config_test.go | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/params/config.go b/params/config.go index 1708bc78de76..4690569f6120 100644 --- a/params/config.go +++ b/params/config.go @@ -880,7 +880,7 @@ func newTimestampCompatError(what string, storedtime, newtime *uint64) *ConfigCo NewTime: newtime, RewindToTime: 0, } - if rew != nil { + if rew != nil && *rew != 0 { err.RewindToTime = *rew - 1 } return err @@ -890,7 +890,15 @@ func (err *ConfigCompatError) Error() string { if err.StoredBlock != nil { return fmt.Sprintf("mismatching %s in database (have block %d, want block %d, rewindto block %d)", err.What, err.StoredBlock, err.NewBlock, err.RewindToBlock) } - return fmt.Sprintf("mismatching %s in database (have timestamp %d, want timestamp %d, rewindto timestamp %d)", err.What, err.StoredTime, err.NewTime, err.RewindToTime) + + if err.StoredTime == nil && err.NewTime == nil { + return "" + } else if err.StoredTime == nil && err.NewTime != nil { + return fmt.Sprintf("mismatching %s in database (have timestamp nil, want timestamp %d, rewindto timestamp %d)", err.What, *err.NewTime, err.RewindToTime) + } else if err.StoredTime != nil && err.NewTime == nil { + return fmt.Sprintf("mismatching %s in database (have timestamp %d, want timestamp nil, rewindto timestamp %d)", err.What, *err.StoredTime, err.RewindToTime) + } + return fmt.Sprintf("mismatching %s in database (have timestamp %d, want timestamp %d, rewindto timestamp %d)", err.What, *err.StoredTime, *err.NewTime, err.RewindToTime) } // Rules wraps ChainConfig and is merely syntactic sugar or can be used for functions diff --git a/params/config_test.go b/params/config_test.go index 707566aa46fc..6b222d3a6735 100644 --- a/params/config_test.go +++ b/params/config_test.go @@ -23,6 +23,7 @@ import ( "time" "github.com/ava-labs/libevm/common/math" + "github.com/stretchr/testify/require" ) func TestCheckCompatible(t *testing.T) { @@ -137,3 +138,20 @@ func TestConfigRules(t *testing.T) { t.Errorf("expected %v to be shanghai", stamp) } } + +func TestTimestampCompatError(t *testing.T) { + require.Equal(t, new(ConfigCompatError).Error(), "") + + errWhat := "Shanghai fork timestamp" + require.Equal(t, newTimestampCompatError(errWhat, nil, newUint64(1681338455)).Error(), + "mismatching Shanghai fork timestamp in database (have timestamp nil, want timestamp 1681338455, rewindto timestamp 1681338454)") + + require.Equal(t, newTimestampCompatError(errWhat, newUint64(1681338455), nil).Error(), + "mismatching Shanghai fork timestamp in database (have timestamp 1681338455, want timestamp nil, rewindto timestamp 1681338454)") + + require.Equal(t, newTimestampCompatError(errWhat, newUint64(1681338455), newUint64(600624000)).Error(), + "mismatching Shanghai fork timestamp in database (have timestamp 1681338455, want timestamp 600624000, rewindto timestamp 600623999)") + + require.Equal(t, newTimestampCompatError(errWhat, newUint64(0), newUint64(1681338455)).Error(), + "mismatching Shanghai fork timestamp in database (have timestamp 0, want timestamp 1681338455, rewindto timestamp 0)") +} From d10c4a3d1ff29c94ec36ac40d31bc869918850fb Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 13 Jan 2025 15:00:13 -0800 Subject: [PATCH 2/7] legacy precompile gas fix --- libevm/legacy/legacy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libevm/legacy/legacy.go b/libevm/legacy/legacy.go index f9ff73090f0a..ffd9b88d1018 100644 --- a/libevm/legacy/legacy.go +++ b/libevm/legacy/legacy.go @@ -31,7 +31,7 @@ func (c PrecompiledStatefulContract) Upgrade() vm.PrecompiledStatefulContract { return func(env vm.PrecompileEnvironment, input []byte) ([]byte, error) { gas := env.Gas() ret, remainingGas, err := c(env, input, gas) - if used := gas - remainingGas; used < gas { + if used := gas - remainingGas; used <= gas { env.UseGas(used) } return ret, err From 050824131de5857ba4519552c1d6691b2525acfb Mon Sep 17 00:00:00 2001 From: Quentin Mc Gaw Date: Tue, 21 Jan 2025 10:30:01 +0100 Subject: [PATCH 3/7] feat(core/types): header copy hooks --- core/types/block.go | 3 +-- core/types/block.libevm.go | 10 ++++++++++ core/types/block.libevm_test.go | 4 ++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/core/types/block.go b/core/types/block.go index 692cf01c076b..0a483686f9ae 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -277,8 +277,7 @@ 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 { +func copyHeader(h *Header) *Header { cpy := *h if cpy.Difficulty = new(big.Int); h.Difficulty != nil { cpy.Difficulty.Set(h.Difficulty) diff --git a/core/types/block.libevm.go b/core/types/block.libevm.go index ca1e6d7e57d9..c3d351489b62 100644 --- a/core/types/block.libevm.go +++ b/core/types/block.libevm.go @@ -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 @@ -108,3 +109,12 @@ 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 copyHeader(h) +} + +// CopyHeader creates a deep copy of a block header. +func CopyHeader(h *Header) *Header { + return h.hooks().Copy(h) +} diff --git a/core/types/block.libevm_test.go b/core/types/block.libevm_test.go index 7f5cfd83c540..48ba5883e122 100644 --- a/core/types/block.libevm_test.go +++ b/core/types/block.libevm_test.go @@ -75,6 +75,10 @@ func (hh *stubHeaderHooks) DecodeRLP(h *Header, s *rlp.Stream) error { return hh.errDecode } +func (hh *stubHeaderHooks) Copy(h *Header) *Header { + return h +} + func TestHeaderHooks(t *testing.T) { TestOnlyClearRegisteredExtras() defer TestOnlyClearRegisteredExtras() From b9e5186290df36b27cf67d0147aadef63913b89d Mon Sep 17 00:00:00 2001 From: Quentin Mc Gaw Date: Tue, 21 Jan 2025 17:20:40 +0100 Subject: [PATCH 4/7] Export `copyHeader` as `CopyEthHeader` --- core/types/block.go | 4 +++- core/types/block.libevm.go | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/types/block.go b/core/types/block.go index 0a483686f9ae..9d056fc8bc4c 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -277,7 +277,9 @@ func NewBlockWithWithdrawals(header *Header, txs []*Transaction, uncles []*Heade return b.WithWithdrawals(withdrawals) } -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) diff --git a/core/types/block.libevm.go b/core/types/block.libevm.go index c3d351489b62..e029c67d374a 100644 --- a/core/types/block.libevm.go +++ b/core/types/block.libevm.go @@ -111,7 +111,7 @@ func (*NOOPHeaderHooks) DecodeRLP(h *Header, s *rlp.Stream) error { } func (n *NOOPHeaderHooks) Copy(h *Header) *Header { - return copyHeader(h) + return CopyEthHeader(h) } // CopyHeader creates a deep copy of a block header. From 7f6afca5658fa1d30a0678ec55eb896257cd7369 Mon Sep 17 00:00:00 2001 From: Quentin Mc Gaw Date: Fri, 17 Jan 2025 14:39:22 +0100 Subject: [PATCH 5/7] feat(core/types): `BodyHooks` for RLP overrides --- core/state/state.libevm_test.go | 5 +- core/state/state_object.libevm_test.go | 19 +++++- core/types/block.go | 8 ++- core/types/block.libevm.go | 65 ++++++++++++++++++- core/types/block.libevm_test.go | 30 ++++++++- .../types/rlp_backwards_compat.libevm_test.go | 5 +- core/types/rlp_payload.libevm.go | 39 +++++++---- core/types/state_account.libevm_test.go | 10 ++- .../state_account_storage.libevm_test.go | 20 ++++-- 9 files changed, 174 insertions(+), 27 deletions(-) diff --git a/core/state/state.libevm_test.go b/core/state/state.libevm_test.go index 8b682cb49b7a..406425b07ce9 100644 --- a/core/state/state.libevm_test.go +++ b/core/state/state.libevm_test.go @@ -45,7 +45,10 @@ 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, + *accountExtra]().StateAccount rng := ethtest.NewPseudoRand(42) addr := rng.Address() diff --git a/core/state/state_object.libevm_test.go b/core/state/state_object.libevm_test.go index d04de5a1aaa3..4cdae081d257 100644 --- a/core/state/state_object.libevm_test.go +++ b/core/state/state_object.libevm_test.go @@ -46,21 +46,34 @@ 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, + bool]().StateAccount.Set(acc, false) + types.RegisterExtras[ + types.NOOPHeaderHooks, *types.NOOPHeaderHooks, + types.NOOPBodyHooks, *types.NOOPBodyHooks, + 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, + 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, + bool]().StateAccount.Set(acc, true) }, wantEmpty: false, }, diff --git a/core/types/block.go b/core/types/block.go index 9d056fc8bc4c..836126adba85 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -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. @@ -338,7 +340,11 @@ func (b *Block) EncodeRLP(w io.Writer) error { // 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} + return &Body{ + Transactions: b.transactions, + Uncles: b.uncles, + Withdrawals: b.withdrawals, + } } // Accessors for body data. These do not return a copy because the content diff --git a/core/types/block.libevm.go b/core/types/block.libevm.go index e029c67d374a..e702cbb9168d 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, SA]) hooksFromHeader(h *Header) HeaderHooks { +func (e ExtraPayloads[HPtr, BodyExtraPtr, SA]) hooksFromHeader(h *Header) HeaderHooks { return e.Header.Get(h) } @@ -118,3 +118,66 @@ func (n *NOOPHeaderHooks) Copy(h *Header) *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, 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)) +} diff --git a/core/types/block.libevm_test.go b/core/types/block.libevm_test.go index 48ba5883e122..5fdd2005bdec 100644 --- a/core/types/block.libevm_test.go +++ b/core/types/block.libevm_test.go @@ -79,11 +79,39 @@ 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 +} + func TestHeaderHooks(t *testing.T) { TestOnlyClearRegisteredExtras() defer TestOnlyClearRegisteredExtras() - extras := RegisterExtras[stubHeaderHooks, *stubHeaderHooks, struct{}]() + extras := RegisterExtras[ + stubHeaderHooks, *stubHeaderHooks, + stubBodyHooks, *stubBodyHooks, + struct{}]() rng := ethtest.NewPseudoRand(13579) suffix := rng.Bytes(8) diff --git a/core/types/rlp_backwards_compat.libevm_test.go b/core/types/rlp_backwards_compat.libevm_test.go index 8b22eac29752..98cef45d53e8 100644 --- a/core/types/rlp_backwards_compat.libevm_test.go +++ b/core/types/rlp_backwards_compat.libevm_test.go @@ -40,7 +40,10 @@ func TestHeaderRLPBackwardsCompatibility(t *testing.T) { { name: "no-op header hooks", register: func() { - RegisterExtras[NOOPHeaderHooks, *NOOPHeaderHooks, struct{}]() + RegisterExtras[ + NOOPHeaderHooks, *NOOPHeaderHooks, + NOOPBodyHooks, *NOOPBodyHooks, + struct{}]() }, }, } diff --git a/core/types/rlp_payload.libevm.go b/core/types/rlp_payload.libevm.go index 90d5b7e43e33..f6b72689f941 100644 --- a/core/types/rlp_payload.libevm.go +++ b/core/types/rlp_payload.libevm.go @@ -46,13 +46,21 @@ func RegisterExtras[ HeaderHooks *H }, + BodyExtra any, BodyExtraPtr interface { + BodyHooks + *BodyExtra + }, SA any, -]() ExtraPayloads[HPtr, SA] { - extra := ExtraPayloads[HPtr, SA]{ +]() ExtraPayloads[HPtr, BodyExtraPtr, SA] { + extra := ExtraPayloads[HPtr, BodyExtraPtr, SA]{ Header: pseudo.NewAccessor[*Header, HPtr]( (*Header).extraPayload, func(h *Header, t *pseudo.Type) { h.extra = t }, ), + Body: pseudo.NewAccessor[*Body, BodyExtraPtr]( + (*Body).extraPayload, + func(b *Body, 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 }, @@ -63,10 +71,11 @@ func RegisterExtras[ var x SA return fmt.Sprintf("%T", x) }(), - // The [ExtraPayloads] that we returns is based on [HPtr,SA], not [H,SA] - // so our constructors MUST match that. This guarantees that calls to - // the [HeaderHooks] methods will never be performed on a nil pointer. - newHeader: pseudo.NewConstructor[H]().NewPointer, // i.e. non-nil HPtr + // 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 newStateAccount: pseudo.NewConstructor[SA]().Zero, cloneStateAccount: extra.cloneStateAccount, hooks: extra, @@ -87,11 +96,14 @@ func TestOnlyClearRegisteredExtras() { var registeredExtras register.AtMostOnce[*extraConstructors] type extraConstructors struct { - stateAccountType string - newHeader, newStateAccount func() *pseudo.Type - cloneStateAccount func(*StateAccountExtra) *StateAccountExtra - hooks interface { + stateAccountType string + newHeader func() *pseudo.Type + newBody func() *pseudo.Type + newStateAccount func() *pseudo.Type + cloneStateAccount func(*StateAccountExtra) *StateAccountExtra + hooks interface { hooksFromHeader(*Header) HeaderHooks + hooksFromBody(*Body) BodyHooks } } @@ -105,14 +117,15 @@ func (e *StateAccountExtra) clone() *StateAccountExtra { } // ExtraPayloads provides strongly typed access to the extra payload carried by -// [Header], [StateAccount], and [SlimAccount] structs. The only valid way to +// [Header], [Body], [StateAccount], and [SlimAccount] structs. The only valid way to // construct an instance is by a call to [RegisterExtras]. -type ExtraPayloads[HPtr HeaderHooks, SA any] struct { +type ExtraPayloads[HPtr HeaderHooks, BodyExtraPtr BodyHooks, SA any] struct { Header pseudo.Accessor[*Header, HPtr] + Body pseudo.Accessor[*Body, BodyExtraPtr] StateAccount pseudo.Accessor[StateOrSlimAccount, SA] // Also provides [SlimAccount] access. } -func (ExtraPayloads[HPtr, SA]) cloneStateAccount(s *StateAccountExtra) *StateAccountExtra { +func (ExtraPayloads[HPtr, BodyExtraPtr, 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 6fd601ef9d7e..2458df8ace40 100644 --- a/core/types/state_account.libevm_test.go +++ b/core/types/state_account.libevm_test.go @@ -46,7 +46,10 @@ func TestStateAccountRLP(t *testing.T) { explicitFalseBoolean := test{ name: "explicit false-boolean extra", register: func() { - RegisterExtras[NOOPHeaderHooks, *NOOPHeaderHooks, bool]() + RegisterExtras[ + NOOPHeaderHooks, *NOOPHeaderHooks, + NOOPBodyHooks, *NOOPBodyHooks, + bool]() }, acc: &StateAccount{ Nonce: 0x444444, @@ -76,7 +79,10 @@ func TestStateAccountRLP(t *testing.T) { { name: "true-boolean extra", register: func() { - RegisterExtras[NOOPHeaderHooks, *NOOPHeaderHooks, bool]() + RegisterExtras[ + NOOPHeaderHooks, *NOOPHeaderHooks, + NOOPBodyHooks, *NOOPBodyHooks, + bool]() }, acc: &StateAccount{ Nonce: 0x444444, diff --git a/core/types/state_account_storage.libevm_test.go b/core/types/state_account_storage.libevm_test.go index 9db9ee552d81..adf3ed15bc02 100644 --- a/core/types/state_account_storage.libevm_test.go +++ b/core/types/state_account_storage.libevm_test.go @@ -73,7 +73,10 @@ func TestStateAccountExtraViaTrieStorage(t *testing.T) { { name: "true-boolean payload", registerAndSetExtra: func(a *types.StateAccount) (*types.StateAccount, assertion) { - e := types.RegisterExtras[types.NOOPHeaderHooks, *types.NOOPHeaderHooks, bool]() + e := types.RegisterExtras[ + types.NOOPHeaderHooks, *types.NOOPHeaderHooks, + types.NOOPBodyHooks, *types.NOOPBodyHooks, + bool]() e.StateAccount.Set(a, true) return a, func(t *testing.T, got *types.StateAccount) { //nolint:thelper assert.Truef(t, e.StateAccount.Get(got), "") @@ -84,7 +87,10 @@ func TestStateAccountExtraViaTrieStorage(t *testing.T) { { name: "explicit false-boolean payload", registerAndSetExtra: func(a *types.StateAccount) (*types.StateAccount, assertion) { - e := types.RegisterExtras[types.NOOPHeaderHooks, *types.NOOPHeaderHooks, bool]() + e := types.RegisterExtras[ + types.NOOPHeaderHooks, *types.NOOPHeaderHooks, + types.NOOPBodyHooks, *types.NOOPBodyHooks, + bool]() e.StateAccount.Set(a, false) // the explicit part return a, func(t *testing.T, got *types.StateAccount) { //nolint:thelper @@ -96,7 +102,10 @@ func TestStateAccountExtraViaTrieStorage(t *testing.T) { { name: "implicit false-boolean payload", registerAndSetExtra: func(a *types.StateAccount) (*types.StateAccount, assertion) { - e := types.RegisterExtras[types.NOOPHeaderHooks, *types.NOOPHeaderHooks, bool]() + e := types.RegisterExtras[ + types.NOOPHeaderHooks, *types.NOOPHeaderHooks, + types.NOOPBodyHooks, *types.NOOPBodyHooks, + bool]() // Note that `a` is reflected, unchanged (the implicit part). return a, func(t *testing.T, got *types.StateAccount) { //nolint:thelper assert.Falsef(t, e.StateAccount.Get(got), "") @@ -107,7 +116,10 @@ func TestStateAccountExtraViaTrieStorage(t *testing.T) { { name: "arbitrary payload", registerAndSetExtra: func(a *types.StateAccount) (*types.StateAccount, assertion) { - e := types.RegisterExtras[types.NOOPHeaderHooks, *types.NOOPHeaderHooks, arbitraryPayload]() + e := types.RegisterExtras[ + types.NOOPHeaderHooks, *types.NOOPHeaderHooks, + types.NOOPBodyHooks, *types.NOOPBodyHooks, + arbitraryPayload]() p := arbitraryPayload{arbitraryData} e.StateAccount.Set(a, p) return a, func(t *testing.T, got *types.StateAccount) { //nolint:thelper From e27ce122e0f10bcd00bb74900c891e9306519327 Mon Sep 17 00:00:00 2001 From: Quentin Mc Gaw Date: Fri, 17 Jan 2025 14:39:22 +0100 Subject: [PATCH 6/7] feat(core/types): `BodyHooks` for RLP overrides --- core/state/state.libevm_test.go | 1 + core/state/state_object.libevm_test.go | 4 + core/types/block.go | 10 ++- core/types/block.libevm.go | 77 ++++++++++++++++++- core/types/block.libevm_test.go | 30 ++++++++ .../types/rlp_backwards_compat.libevm_test.go | 2 +- core/types/rlp_payload.libevm.go | 36 ++++++--- core/types/state_account.libevm_test.go | 2 + .../state_account_storage.libevm_test.go | 4 + 9 files changed, 147 insertions(+), 19 deletions(-) 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) From 11c780f117f8970eb025ef1ad3679dc5c02e9eae Mon Sep 17 00:00:00 2001 From: Quentin Mc Gaw Date: Wed, 22 Jan 2025 10:21:41 +0100 Subject: [PATCH 7/7] Block `Body` hook --- core/types/block.go | 5 +++-- core/types/block.libevm.go | 11 +++++++++++ core/types/block.libevm_test.go | 4 ++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/core/types/block.go b/core/types/block.go index 64007c3a93b8..ad4dfbe7cb71 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -339,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, diff --git a/core/types/block.libevm.go b/core/types/block.libevm.go index e287839b596e..bf39f84ed698 100644 --- a/core/types/block.libevm.go +++ b/core/types/block.libevm.go @@ -187,6 +187,7 @@ func (*NOOPBodyHooks) DecodeRLP(b *Body, s *rlp.Stream) error { 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 @@ -243,6 +244,10 @@ 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 } @@ -254,3 +259,9 @@ func (b *Block) SetUncles(uncles []*Header) { 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) +} diff --git a/core/types/block.libevm_test.go b/core/types/block.libevm_test.go index 06cd0cef5416..6999a91cb563 100644 --- a/core/types/block.libevm_test.go +++ b/core/types/block.libevm_test.go @@ -133,6 +133,10 @@ func (bh *stubBlockHooks) DecodeRLP(b *Block, s *rlp.Stream) error { return bh.errDecode } +func (bh *stubBlockHooks) Body(b *Block) *Body { + return b.EthBody() +} + func TestHeaderHooks(t *testing.T) { TestOnlyClearRegisteredExtras() defer TestOnlyClearRegisteredExtras()