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 692cf01c076b..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. @@ -277,8 +279,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) @@ -337,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 ca1e6d7e57d9..e702cbb9168d 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 @@ -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, SA]) hooksFromHeader(h *Header) HeaderHooks { return e.Header.Get(h) } @@ -108,3 +109,75 @@ 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, 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 7f5cfd83c540..5fdd2005bdec 100644 --- a/core/types/block.libevm_test.go +++ b/core/types/block.libevm_test.go @@ -75,11 +75,43 @@ 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 +} + 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 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 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)") +}