From 0a66516528e4e8ae58e6dc477eb65b784c3992d3 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Thu, 6 Feb 2025 13:43:22 +0000 Subject: [PATCH] refactor(core/types): simplify `Header` RLP overriding --- core/types/block.libevm.go | 52 +++++++++++++++---- core/types/block.libevm_test.go | 91 +++++++++++++-------------------- 2 files changed, 79 insertions(+), 64 deletions(-) diff --git a/core/types/block.libevm.go b/core/types/block.libevm.go index 832fe421f0af..a0466d84746a 100644 --- a/core/types/block.libevm.go +++ b/core/types/block.libevm.go @@ -20,7 +20,9 @@ import ( "encoding/json" "fmt" "io" + "math/big" + "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/libevm/pseudo" "github.com/ava-labs/libevm/libevm/testonly" "github.com/ava-labs/libevm/rlp" @@ -31,8 +33,8 @@ import ( type HeaderHooks interface { MarshalJSON(*Header) ([]byte, error) //nolint:govet // Type-specific override hook UnmarshalJSON(*Header, []byte) error //nolint:govet - EncodeRLP(*Header, io.Writer) error - DecodeRLP(*Header, *rlp.Stream) error + RLPFieldsForEncoding(*Header) *rlp.Fields + RLPFieldPointersForDecoding(*Header) *rlp.Fields PostCopy(dst *Header) } @@ -68,12 +70,12 @@ func (h *Header) UnmarshalJSON(b []byte) error { // EncodeRLP implements the [rlp.Encoder] interface. func (h *Header) EncodeRLP(w io.Writer) error { - return h.hooks().EncodeRLP(h, w) + return h.hooks().RLPFieldsForEncoding(h).EncodeRLP(w) } // DecodeRLP implements the [rlp.Decoder] interface. func (h *Header) DecodeRLP(s *rlp.Stream) error { - return h.hooks().DecodeRLP(h, s) + return h.hooks().RLPFieldPointersForDecoding(h).DecodeRLP(s) } func (h *Header) extraPayload() *pseudo.Type { @@ -102,14 +104,46 @@ func (*NOOPHeaderHooks) UnmarshalJSON(h *Header, b []byte) error { //nolint:gove return h.unmarshalJSON(b) } -func (*NOOPHeaderHooks) EncodeRLP(h *Header, w io.Writer) error { - return h.encodeRLP(w) +func init() { + var ( + h common.Hash + a common.Address + bi *big.Int + u uint64 + _ = Header{ + h, h, a, h, h, h, Bloom{}, bi, bi, u, u, u, []byte{}, h, BlockNonce{}, // required + bi, &h, &u, &u, &h, //optional + &pseudo.Type{}, // libevm + } + ) } -func (*NOOPHeaderHooks) DecodeRLP(h *Header, s *rlp.Stream) error { - type withoutMethods Header - return s.Decode((*withoutMethods)(h)) +func (*NOOPHeaderHooks) RLPFieldsForEncoding(h *Header) *rlp.Fields { + // TODO(arr4n): write a generator for this and the pointer equivalent, and + // include [NOOPBodyHooks] when running it. + return &rlp.Fields{ + Required: []any{ + h.ParentHash, h.UncleHash, h.Coinbase, h.Root, h.TxHash, h.ReceiptHash, h.Bloom, + h.Difficulty, h.Number, h.GasLimit, h.GasUsed, h.Time, h.Extra, h.MixDigest, h.Nonce, + }, + Optional: []any{ + h.BaseFee, h.WithdrawalsHash, h.BlobGasUsed, h.ExcessBlobGas, h.ParentBeaconRoot, + }, + } } + +func (*NOOPHeaderHooks) RLPFieldPointersForDecoding(h *Header) *rlp.Fields { + return &rlp.Fields{ + Required: []any{ + &h.ParentHash, &h.UncleHash, &h.Coinbase, &h.Root, &h.TxHash, &h.ReceiptHash, &h.Bloom, + &h.Difficulty, &h.Number, &h.GasLimit, &h.GasUsed, &h.Time, &h.Extra, &h.MixDigest, &h.Nonce, + }, + Optional: []any{ + &h.BaseFee, &h.WithdrawalsHash, &h.BlobGasUsed, &h.ExcessBlobGas, &h.ParentBeaconRoot, + }, + } +} + func (*NOOPHeaderHooks) PostCopy(dst *Header) {} var _ interface { diff --git a/core/types/block.libevm_test.go b/core/types/block.libevm_test.go index e89926767323..03b68b3a87a8 100644 --- a/core/types/block.libevm_test.go +++ b/core/types/block.libevm_test.go @@ -20,62 +20,62 @@ import ( "encoding/json" "errors" "fmt" - "io" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/ava-labs/libevm/common" . "github.com/ava-labs/libevm/core/types" - "github.com/ava-labs/libevm/crypto" "github.com/ava-labs/libevm/libevm/ethtest" "github.com/ava-labs/libevm/libevm/pseudo" "github.com/ava-labs/libevm/rlp" ) type stubHeaderHooks struct { - suffix []byte - gotRawJSONToUnmarshal, gotRawRLPToDecode []byte - setHeaderToOnUnmarshalOrDecode Header - accessor pseudo.Accessor[*Header, *stubHeaderHooks] - toCopy *stubHeaderHooks + suffix []byte + gotRawJSONToUnmarshal []byte + setHeaderToOnUnmarshal Header + accessor pseudo.Accessor[*Header, *stubHeaderHooks] + toCopy *stubHeaderHooks - errMarshal, errUnmarshal, errEncode, errDecode error + errMarshal, errUnmarshal error } func fakeHeaderJSON(h *Header, suffix []byte) []byte { return []byte(fmt.Sprintf(`"%#x:%#x"`, h.ParentHash, suffix)) } -func fakeHeaderRLP(h *Header, suffix []byte) []byte { - return append(crypto.Keccak256(h.ParentHash[:]), suffix...) -} - func (hh *stubHeaderHooks) MarshalJSON(h *Header) ([]byte, error) { //nolint:govet return fakeHeaderJSON(h, hh.suffix), hh.errMarshal } func (hh *stubHeaderHooks) UnmarshalJSON(h *Header, b []byte) error { //nolint:govet hh.gotRawJSONToUnmarshal = b - *h = hh.setHeaderToOnUnmarshalOrDecode + *h = hh.setHeaderToOnUnmarshal return hh.errUnmarshal } -func (hh *stubHeaderHooks) EncodeRLP(h *Header, w io.Writer) error { - if _, err := w.Write(fakeHeaderRLP(h, hh.suffix)); err != nil { - return err - } - return hh.errEncode +func directEncodeHeaderRLP(tb testing.TB, h *Header, extraPayloadSuffix []byte) []byte { + tb.Helper() + + // The encoded type mirrors the fields returned by + // [stubHeaderHooks.RLPFieldsForEncoding]. + buf, err := rlp.EncodeToBytes(struct { + ParentHash common.Hash + Suffix []byte + }{h.ParentHash, extraPayloadSuffix}) + + require.NoError(tb, err) + return buf } -func (hh *stubHeaderHooks) DecodeRLP(h *Header, s *rlp.Stream) error { - r, err := s.Raw() - if err != nil { - return err - } - hh.gotRawRLPToDecode = r - *h = hh.setHeaderToOnUnmarshalOrDecode - return hh.errDecode +func (hh *stubHeaderHooks) RLPFieldsForEncoding(h *Header) *rlp.Fields { + return &rlp.Fields{Required: []any{h.ParentHash, hh.suffix}} +} + +func (hh *stubHeaderHooks) RLPFieldPointersForDecoding(h *Header) *rlp.Fields { + return &rlp.Fields{Required: []any{&h.ParentHash, &hh.suffix}} } func (hh *stubHeaderHooks) PostCopy(dst *Header) { @@ -104,7 +104,7 @@ func TestHeaderHooks(t *testing.T) { t.Run("UnmarshalJSON", func(t *testing.T) { hdr := new(Header) stub := &stubHeaderHooks{ - setHeaderToOnUnmarshalOrDecode: Header{ + setHeaderToOnUnmarshal: Header{ Extra: []byte("can you solve this puzzle? 0xbda01b6cf56c303bd3f581599c0d5c0b"), }, } @@ -115,31 +115,26 @@ func TestHeaderHooks(t *testing.T) { require.NoErrorf(t, err, "json.Unmarshal()") assert.Equal(t, input, string(stub.gotRawJSONToUnmarshal), "raw JSON received by hook") - assert.Equal(t, &stub.setHeaderToOnUnmarshalOrDecode, hdr, "%T after JSON unmarshalling with hook", hdr) + assert.Equal(t, &stub.setHeaderToOnUnmarshal, hdr, "%T after JSON unmarshalling with hook", hdr) }) t.Run("EncodeRLP", func(t *testing.T) { got, err := rlp.EncodeToBytes(hdr) require.NoError(t, err, "rlp.EncodeToBytes(%T)", hdr) - assert.Equal(t, fakeHeaderRLP(hdr, suffix), got) + assert.Equal(t, directEncodeHeaderRLP(t, hdr, suffix), got) }) t.Run("DecodeRLP", func(t *testing.T) { - input, err := rlp.EncodeToBytes(rng.Bytes(8)) - require.NoError(t, err) + input := directEncodeHeaderRLP(t, hdr, suffix) - hdr := new(Header) - stub := &stubHeaderHooks{ - setHeaderToOnUnmarshalOrDecode: Header{ - Extra: []byte("arr4n was here"), - }, - } - extras.Header.Set(hdr, stub) - err = rlp.DecodeBytes(input, hdr) + got := new(Header) + stub := &stubHeaderHooks{} + extras.Header.Set(got, stub) + err := rlp.DecodeBytes(input, got) require.NoErrorf(t, err, "rlp.DecodeBytes(%#x)", input) - assert.Equal(t, input, stub.gotRawRLPToDecode, "raw RLP received by hooks") - assert.Equalf(t, &stub.setHeaderToOnUnmarshalOrDecode, hdr, "%T after RLP decoding with hook", hdr) + assert.Equalf(t, hdr.ParentHash, got.ParentHash, "RLP-decoded %T.ParentHash", hdr) + assert.Equalf(t, suffix, stub.suffix, "RLP-decoded %T.suffix", stub) }) t.Run("PostCopy", func(t *testing.T) { @@ -159,16 +154,12 @@ func TestHeaderHooks(t *testing.T) { t.Run("error_propagation", func(t *testing.T) { errMarshal := errors.New("whoops") errUnmarshal := errors.New("is it broken?") - errEncode := errors.New("uh oh") - errDecode := errors.New("something bad happened") hdr := new(Header) setStub := func() { extras.Header.Set(hdr, &stubHeaderHooks{ errMarshal: errMarshal, errUnmarshal: errUnmarshal, - errEncode: errEncode, - errDecode: errDecode, }) } @@ -184,15 +175,5 @@ func TestHeaderHooks(t *testing.T) { err := json.Unmarshal([]byte("{}"), hdr) assert.Equal(t, errUnmarshal, err, "via json.Unmarshal()") } - - setStub() // [stubHeaderHooks] completely overrides the Header - { - err := rlp.Encode(io.Discard, hdr) - assert.Equal(t, errEncode, err, "via rlp.Encode()") - } - { - err := rlp.DecodeBytes([]byte{0}, hdr) - assert.Equal(t, errDecode, err, "via rlp.DecodeBytes()") - } }) }