Skip to content

Commit

Permalink
Replace Header RLP encoding/decoding with generated code (#288)
Browse files Browse the repository at this point in the history
Fixes celo-org/celo-blockchain-planning#720

This PR replaces the current RLP encoding/decoding of 'BeforeGingerbreadHeader' and 'AfterGingerbreadHeader' from reflection style function to auto generated function by `rlpgen`.

For code generation, `BeforeGingerbreadHeader` and `AfterGingerbreadHeader` are renamed from `beforeGingerbreadHeader` and `afterGingerbreadHeader`

---------

Co-authored-by: piersy <[email protected]>
  • Loading branch information
Kourin1996 and piersy authored Dec 4, 2024
1 parent 8736c4f commit 7d88daf
Show file tree
Hide file tree
Showing 5 changed files with 519 additions and 8 deletions.
1 change: 0 additions & 1 deletion core/types/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ func (n *BlockNonce) UnmarshalText(input []byte) error {
}

//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

// Header represents a block header in the Ethereum blockchain.
type Header struct {
Expand Down
16 changes: 9 additions & 7 deletions core/types/celo_block.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ import (
"github.com/ethereum/go-ethereum/rlp"
)

//go:generate go run ../../rlp/rlpgen -type BeforeGingerbreadHeader --encoder --decoder -out gen_before_gingerbread_header_rlp.go
//go:generate go run ../../rlp/rlpgen -type AfterGingerbreadHeader --encoder --decoder -out gen_after_gingerbread_header_rlp.go

type IstanbulExtra rlp.RawValue

type beforeGingerbreadHeader struct {
type BeforeGingerbreadHeader struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
Coinbase common.Address `json:"miner" gencodec:"required"`
Root common.Hash `json:"stateRoot" gencodec:"required"`
Expand All @@ -24,7 +27,7 @@ type beforeGingerbreadHeader struct {
}

// This type is required to avoid an infinite loop when decoding
type afterGingerbreadHeader Header
type AfterGingerbreadHeader Header

func (h *Header) DecodeRLP(s *rlp.Stream) error {
var raw rlp.RawValue
Expand All @@ -40,7 +43,7 @@ func (h *Header) DecodeRLP(s *rlp.Stream) error {

if preGingerbread { // Address
// Before gingerbread
decodedHeader := beforeGingerbreadHeader{}
decodedHeader := BeforeGingerbreadHeader{}
err = rlp.DecodeBytes(raw, &decodedHeader)

h.ParentHash = decodedHeader.ParentHash
Expand All @@ -56,7 +59,7 @@ func (h *Header) DecodeRLP(s *rlp.Stream) error {
h.Difficulty = new(big.Int)
} else {
// After gingerbread
decodedHeader := afterGingerbreadHeader{}
decodedHeader := AfterGingerbreadHeader{}
err = rlp.DecodeBytes(raw, &decodedHeader)

h.ParentHash = decodedHeader.ParentHash
Expand Down Expand Up @@ -87,8 +90,7 @@ func (h *Header) DecodeRLP(s *rlp.Stream) error {
// EncodeRLP implements encodes the Header to an RLP data stream.
func (h *Header) EncodeRLP(w io.Writer) error {
if h.IsPreGingerbread() {
// Encode the header
encodedHeader := beforeGingerbreadHeader{
encodedHeader := BeforeGingerbreadHeader{
ParentHash: h.ParentHash,
Coinbase: h.Coinbase,
Root: h.Root,
Expand All @@ -105,7 +107,7 @@ func (h *Header) EncodeRLP(w io.Writer) error {
}

// After gingerbread
encodedHeader := afterGingerbreadHeader{
encodedHeader := AfterGingerbreadHeader{
ParentHash: h.ParentHash,
UncleHash: h.UncleHash,
Coinbase: h.Coinbase,
Expand Down
175 changes: 175 additions & 0 deletions core/types/celo_block_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package types

import (
"bytes"
"math/big"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/rlp"
"github.com/stretchr/testify/assert"
)

// mockOldBeforeGingerbreadHeader is same as BeforeGingerbreadHeader
// but doesn't implement EncodeRLP and DecodeRLP
type mockOldBeforeGingerbreadHeader BeforeGingerbreadHeader

// mockOldAfterGingerbreadHeader is also same as AfterGingerbreadHeader
type mockOldAfterGingerbreadHeader Header

var (
mockBeforeGingerbreadHeader = &BeforeGingerbreadHeader{
ParentHash: common.HexToHash("0x112233445566778899001122334455667788990011223344556677889900aabb"),
Coinbase: common.HexToAddress("0x8888f1f195afa192cfee860698584c030f4c9db1"),
Root: EmptyRootHash,
TxHash: EmptyTxsHash,
ReceiptHash: EmptyReceiptsHash,
Bloom: Bloom(common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")),
Number: math.BigPow(2, 9),
GasUsed: 1476322,
Time: 9876543,
Extra: []byte("test before gingerbread header extra"),
}

mockWithdrawalHash = common.HexToHash("0x4585754a71d14791295bc094dc53eb0b32f21d92e58350a4140163a047b854a7")
mockExcessBlobGas = uint64(123456789)
mockBlobGasUsed = uint64(12345678)
mockParentBeaconRoot = common.HexToHash("0x9229c626ebd6328b3ddc7fe8636f2fd9a344f4c02e2e281f59a3b7e4e46833e5")
mockAfterGingerbreadHeader = &AfterGingerbreadHeader{
ParentHash: mockBeforeGingerbreadHeader.ParentHash,
UncleHash: EmptyUncleHash,
Coinbase: mockBeforeGingerbreadHeader.Coinbase,
Root: EmptyRootHash,
TxHash: EmptyTxsHash,
ReceiptHash: EmptyReceiptsHash,
Bloom: mockBeforeGingerbreadHeader.Bloom,
Difficulty: big.NewInt(17179869184),
Number: mockBeforeGingerbreadHeader.Number,
GasLimit: 12345678,
GasUsed: mockBeforeGingerbreadHeader.GasUsed,
Time: mockBeforeGingerbreadHeader.Time,
Extra: []byte("test after gingerbread header extra"),
MixDigest: common.HexToHash("0x036a0a7a3611ecd974ef274e603ceab81246fb50dc350519b9f47589e8fe3014"),
Nonce: EncodeNonce(12345),
BaseFee: math.BigPow(10, 8),
WithdrawalsHash: &mockWithdrawalHash,
ExcessBlobGas: &mockExcessBlobGas,
BlobGasUsed: &mockBlobGasUsed,
ParentBeaconRoot: &mockParentBeaconRoot,
}
)

func ToMockOldBeforeGingerbreadHeader(h *BeforeGingerbreadHeader) *mockOldBeforeGingerbreadHeader {
return &mockOldBeforeGingerbreadHeader{
ParentHash: h.ParentHash,
Coinbase: h.Coinbase,
Root: h.Root,
TxHash: h.TxHash,
ReceiptHash: h.ReceiptHash,
Bloom: h.Bloom,
Number: h.Number,
GasUsed: h.GasUsed,
Time: h.Time,
Extra: h.Extra,
}
}

func BeforeGingerbreadHeaderToHeader(h *BeforeGingerbreadHeader) *Header {
return &Header{
ParentHash: h.ParentHash,
Coinbase: h.Coinbase,
Root: h.Root,
TxHash: h.TxHash,
ReceiptHash: h.ReceiptHash,
Bloom: h.Bloom,
Number: h.Number,
GasUsed: h.GasUsed,
Time: h.Time,
Extra: h.Extra,
Difficulty: new(big.Int),
}
}

func TestRLPDecodeHeaderCompatibility(t *testing.T) {
tests := []struct {
name string
oldHeader interface{}
newHeader *Header
}{
{
name: "BeforeGingerbreadHeader",
oldHeader: ToMockOldBeforeGingerbreadHeader(mockBeforeGingerbreadHeader),
newHeader: BeforeGingerbreadHeaderToHeader(mockBeforeGingerbreadHeader),
},
{
name: "AfterGingerbreadHeader",
oldHeader: (*mockOldAfterGingerbreadHeader)(mockAfterGingerbreadHeader),
newHeader: (*Header)(mockAfterGingerbreadHeader),
},
}

for _, test := range tests {
test := test

t.Run(test.name, func(t *testing.T) {
t.Parallel()

r := bytes.NewBuffer([]byte{})

// encode by reflection style
err := rlp.Encode(r, test.oldHeader)
assert.NoError(t, err, "failed RLP encode")

// decode by generated code
decodedHeader := &Header{}
rlp.DecodeBytes(r.Bytes(), decodedHeader)
assert.NoError(t, err, "failed RLP decode")

assert.Equal(t, test.newHeader, decodedHeader)
})
}
}

func TestRlpEncodeHeaderCompatibility(t *testing.T) {
tests := []struct {
name string
oldHeader interface{} // header type which doesn't implement EncodeRLP
newHeader *Header // header type which implements EncodeRLP
}{
{
name: "BeforeGingerbreadHeader",
oldHeader: ToMockOldBeforeGingerbreadHeader(mockBeforeGingerbreadHeader),
newHeader: BeforeGingerbreadHeaderToHeader(mockBeforeGingerbreadHeader),
},
{
name: "AfterGingerbreadHeader",
oldHeader: (*mockOldAfterGingerbreadHeader)(mockAfterGingerbreadHeader),
newHeader: (*Header)(mockAfterGingerbreadHeader),
},
}

for _, test := range tests {
test := test

t.Run(test.name, func(t *testing.T) {
t.Parallel()

r := bytes.NewBuffer([]byte{})

// old RLP encoding
err := rlp.Encode(r, test.oldHeader)
assert.NoError(t, err, "failed RLP encode by reflection style")
oldEncodedData := r.Bytes()

r.Reset()

// new RLP encoding
err = rlp.Encode(r, test.newHeader)
assert.NoError(t, err, "failed RLP encode by generated code")
newEncodedData := r.Bytes()

assert.Equal(t, oldEncodedData, newEncodedData)
})
}
}
Loading

0 comments on commit 7d88daf

Please sign in to comment.