diff --git a/core/types/block.go b/core/types/block.go index 5563d9e2839..319762323c2 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -58,11 +58,37 @@ func (n *BlockNonce) UnmarshalText(input []byte) error { return hexutil.UnmarshalFixedText("BlockNonce", input, n[:]) } +type HeaderSerializer interface { + EncodeRLP(h *Header, w io.Writer) error + DecodeRLP(h *Header, s *rlp.Stream) error +} + +var RegisteredHeaderSerializer HeaderSerializer + +// XXX: JSON marshalling should be handled as well. //go:generate go run github.com/fjl/gencodec -type Header -field-override headerMarshaling -out gen_header_json.go -//go:generate go run ../../rlp/rlpgen -type Header -out gen_header_rlp.go +//go:generate go run ../../rlp/rlpgen -type HeaderWithExtraPayload -out gen_header_rlp.go // Header represents a block header in the Ethereum blockchain. -type Header struct { +type Header HeaderWithExtraPayload + +func (obj *Header) EncodeRLP(w io.Writer) error { + if RegisteredHeaderSerializer != nil { + return RegisteredHeaderSerializer.EncodeRLP(obj, w) + } + return rlp.Encode(w, (*HeaderWithExtraPayload)(obj)) +} + +func (obj *Header) DecodeRLP(s *rlp.Stream) error { + if RegisteredHeaderSerializer != nil { + return RegisteredHeaderSerializer.DecodeRLP(obj, s) + } + return s.Decode((*HeaderWithExtraPayload)(obj)) +} + +type HeaderWithExtraPayload struct { // Note this name must be exported if we want to use rlpgen + ExtraPayload interface{} `json:"-" rlp:"-"` + ParentHash common.Hash `json:"parentHash" gencodec:"required"` UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"` Coinbase common.Address `json:"miner"` diff --git a/core/types/gen_header_json.go b/core/types/gen_header_json.go index f69d033ad33..47010bef6e6 100644 --- a/core/types/gen_header_json.go +++ b/core/types/gen_header_json.go @@ -16,6 +16,7 @@ var _ = (*headerMarshaling)(nil) // MarshalJSON marshals as JSON. func (h Header) MarshalJSON() ([]byte, error) { type Header struct { + ExtraPayload interface{} `json:"-" rlp:"-"` ParentHash common.Hash `json:"parentHash" gencodec:"required"` UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"` Coinbase common.Address `json:"miner"` @@ -39,6 +40,7 @@ func (h Header) MarshalJSON() ([]byte, error) { Hash common.Hash `json:"hash"` } var enc Header + enc.ExtraPayload = h.ExtraPayload enc.ParentHash = h.ParentHash enc.UncleHash = h.UncleHash enc.Coinbase = h.Coinbase @@ -66,6 +68,7 @@ func (h Header) MarshalJSON() ([]byte, error) { // UnmarshalJSON unmarshals from JSON. func (h *Header) UnmarshalJSON(input []byte) error { type Header struct { + ExtraPayload interface{} `json:"-" rlp:"-"` ParentHash *common.Hash `json:"parentHash" gencodec:"required"` UncleHash *common.Hash `json:"sha3Uncles" gencodec:"required"` Coinbase *common.Address `json:"miner"` @@ -91,6 +94,9 @@ func (h *Header) UnmarshalJSON(input []byte) error { if err := json.Unmarshal(input, &dec); err != nil { return err } + if dec.ExtraPayload != nil { + h.ExtraPayload = dec.ExtraPayload + } if dec.ParentHash == nil { return errors.New("missing required field 'parentHash' for Header") } diff --git a/core/types/gen_header_rlp.go b/core/types/gen_header_rlp.go index 848f91e7ce9..ea57e308e0b 100644 --- a/core/types/gen_header_rlp.go +++ b/core/types/gen_header_rlp.go @@ -5,7 +5,7 @@ package types import "github.com/ava-labs/libevm/rlp" import "io" -func (obj *Header) EncodeRLP(_w io.Writer) error { +func (obj *HeaderWithExtraPayload) EncodeRLP(_w io.Writer) error { w := rlp.NewEncoderBuffer(_w) _tmp0 := w.List() w.WriteBytes(obj.ParentHash[:]) diff --git a/core/types_ext/block.go b/core/types_ext/block.go new file mode 100644 index 00000000000..1002d9f837d --- /dev/null +++ b/core/types_ext/block.go @@ -0,0 +1,145 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package types contains data types related to Ethereum consensus. +package types_ext + +import ( + "io" + "math/big" + + "github.com/ava-labs/libevm/common" + "github.com/ava-labs/libevm/core/types" + "github.com/ava-labs/libevm/rlp" +) + +type ( + BlockNonce = types.BlockNonce + Bloom = types.Bloom +) + +func init() { + // Register the header type for RLP serialization + types.RegisteredHeaderSerializer = headerSerializable{} +} + +//go:generate go run ../../rlp/rlpgen -type headerSerializable -out gen_header_rlp.go +type Header = types.Header + +func (headerSerializable) EncodeRLP(h *types.Header, w io.Writer) error { + var hs headerSerializable + + // Set the shared fields with upstream + hs.ParentHash = h.ParentHash + hs.UncleHash = h.UncleHash + hs.Coinbase = h.Coinbase + hs.Root = h.Root + hs.TxHash = h.TxHash + hs.ReceiptHash = h.ReceiptHash + hs.Bloom = h.Bloom + hs.Difficulty = h.Difficulty + hs.Number = h.Number + hs.GasLimit = h.GasLimit + hs.GasUsed = h.GasUsed + hs.Time = h.Time + hs.Extra = h.Extra + hs.MixDigest = h.MixDigest + + // Set the extra payload + if h.ExtraPayload != nil { + hs.AddedStuff = h.ExtraPayload.(*headerExtra).AddedStuff + } + + return rlp.Encode(w, &hs) +} + +func (headerSerializable) DecodeRLP(h *types.Header, s *rlp.Stream) error { + var hs headerSerializable + if err := s.Decode(&hs); err != nil { + return err + } + + // Set the shared fields with upstream + h.ParentHash = hs.ParentHash + h.UncleHash = hs.UncleHash + h.Coinbase = hs.Coinbase + h.Root = hs.Root + h.TxHash = hs.TxHash + h.ReceiptHash = hs.ReceiptHash + h.Bloom = hs.Bloom + h.Difficulty = hs.Difficulty + h.Number = hs.Number + h.GasLimit = hs.GasLimit + h.GasUsed = hs.GasUsed + h.Time = hs.Time + h.Extra = hs.Extra + h.MixDigest = hs.MixDigest + h.Nonce = hs.Nonce + h.BaseFee = hs.BaseFee + h.BlobGasUsed = hs.BlobGasUsed + h.ExcessBlobGas = hs.ExcessBlobGas + h.ParentBeaconRoot = hs.ParentBeaconRoot + + // Set the extra payload + h.ExtraPayload = &headerExtra{ + AddedStuff: hs.AddedStuff, + } + return nil +} + +// Separate the extra payload from the header, so there is no duplicate of +// shared fields. +type headerExtra struct { + AddedStuff *common.Hash +} + +// The purpose of this struct is to specify the exact RLP serialization of the +// header, including shared fields and fields related to the extra payload. +type headerSerializable struct { + ParentHash common.Hash `json:"parentHash" gencodec:"required"` + UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"` + Coinbase common.Address `json:"miner"` + Root common.Hash `json:"stateRoot" gencodec:"required"` + TxHash common.Hash `json:"transactionsRoot" gencodec:"required"` + ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"` + Bloom Bloom `json:"logsBloom" gencodec:"required"` + Difficulty *big.Int `json:"difficulty" gencodec:"required"` + Number *big.Int `json:"number" gencodec:"required"` + GasLimit uint64 `json:"gasLimit" gencodec:"required"` + GasUsed uint64 `json:"gasUsed" gencodec:"required"` + Time uint64 `json:"timestamp" gencodec:"required"` + Extra []byte `json:"extraData" gencodec:"required"` + MixDigest common.Hash `json:"mixHash"` + Nonce BlockNonce `json:"nonce"` + + // Added stuff in the middle of the header + AddedStuff *common.Hash `json:"addedStuff" rlp:"optional"` + + // BaseFee was added by EIP-1559 and is ignored in legacy headers. + BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"` + + // BlobGasUsed was added by EIP-4844 and is ignored in legacy headers. + BlobGasUsed *uint64 `json:"blobGasUsed" rlp:"optional"` + + // ExcessBlobGas was added by EIP-4844 and is ignored in legacy headers. + ExcessBlobGas *uint64 `json:"excessBlobGas" rlp:"optional"` + + // ParentBeaconRoot was added by EIP-4788 and is ignored in legacy headers. + ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` +} + +// XXX: Need to support size too in upstream code +// var headerSize = common.StorageSize(reflect.TypeOf(Header{}).Size()) diff --git a/core/types_ext/block_test.go b/core/types_ext/block_test.go new file mode 100644 index 00000000000..cbc402dfa5a --- /dev/null +++ b/core/types_ext/block_test.go @@ -0,0 +1,36 @@ +package types_ext + +import ( + "math/big" + "testing" + + "github.com/ava-labs/libevm/common" + "github.com/ava-labs/libevm/rlp" + "github.com/stretchr/testify/require" +) + +func TestRLP(t *testing.T) { + // expected (eg, from network or disk) + added := common.Hash{111} + asIs := headerSerializable{ + Number: big.NewInt(10), // Shared field + AddedStuff: &added, // Added field + } + + rlpAsIs, err := rlp.EncodeToBytes(&asIs) + require.NoError(t, err) + + // now parse it via upstream type + var upstream Header + err = rlp.DecodeBytes(rlpAsIs, &upstream) + require.NoError(t, err) + + // check that the fields are the same + require.Equal(t, asIs.Number, upstream.Number) + require.Equal(t, asIs.AddedStuff, upstream.ExtraPayload.(*headerExtra).AddedStuff) + + // now encode the upstream type + rlpUpstream, err := rlp.EncodeToBytes(&upstream) + require.NoError(t, err) + require.Equal(t, rlpAsIs, rlpUpstream) +}