Skip to content

Commit

Permalink
Handle migrated celo headers (#143)
Browse files Browse the repository at this point in the history
This PR updates the header encoding and decoding for celo blocks to be able to support data migrated from celo-blockchain.

Our post gingerbread headers actually decode into the op-geth header structure without any changes. Since adding fields to the end of a struct does not cause rlp decoding to fail for previously encoded values. Our pre-gingerbread don't decode into the op-geth header structure because the pre-gingerbread headers miss fields from the middle of the struct so we need to handle those explicitly.

The decision on how to decode is made based on introspecting the rlp and looking at the second field which is a hash (length 32 bytes) post-gingerbread, but an address (length 20 bytes) pre-gingerbread.

The decision on how to encode is made based on the value of gasLimit if zero then it's a pre-gingerbread header.

Additionally we needed to allow genesis blocks in a CeL2 migrated chain context (by checking if Cel2Time is set and non zero) that lack GasLimit and Difficulty because those fields were considered required by op-geth but not present in early Celo.

Finally some tests that used blocks with zero gas limits needed to be updated since with a zero gas limit the different encoding was now breaking them, and adding a non zero gas limit meant the block hash changed.
  • Loading branch information
piersy authored and karlb committed Jul 10, 2024
1 parent e2f7bcd commit 004e586
Show file tree
Hide file tree
Showing 11 changed files with 239 additions and 150 deletions.
72 changes: 35 additions & 37 deletions core/gen_genesis.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 12 additions & 7 deletions core/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ type Genesis struct {
Nonce uint64 `json:"nonce"`
Timestamp uint64 `json:"timestamp"`
ExtraData []byte `json:"extraData"`
GasLimit uint64 `json:"gasLimit" gencodec:"required"`
Difficulty *big.Int `json:"difficulty" gencodec:"required"`
GasLimit uint64 `json:"gasLimit"`
Difficulty *big.Int `json:"difficulty"`
Mixhash common.Hash `json:"mixHash"`
Coinbase common.Address `json:"coinbase"`
Alloc types.GenesisAlloc `json:"alloc" gencodec:"required"`
Expand Down Expand Up @@ -475,11 +475,16 @@ func (g *Genesis) ToBlock() *types.Block {
Coinbase: g.Coinbase,
Root: root,
}
if g.GasLimit == 0 {
head.GasLimit = params.GenesisGasLimit
}
if g.Difficulty == nil && g.Mixhash == (common.Hash{}) {
head.Difficulty = params.GenesisDifficulty
// Don't set defaults for gas limit and difficulty for migrated celo chains.
// I.E. when Cel2Time is set & non zero. Since migrated celo chains can
// have gas limit and difficulty unset in the genesis.
if g.Config.Cel2Time == nil || g.Config.IsCel2(0) {
if g.GasLimit == 0 {
head.GasLimit = params.GenesisGasLimit
}
if g.Difficulty == nil && g.Mixhash == (common.Hash{}) {
head.Difficulty = params.GenesisDifficulty
}
}
if g.Config != nil && g.Config.IsLondon(common.Big0) {
if g.BaseFee != nil {
Expand Down
154 changes: 154 additions & 0 deletions core/types/celo_block.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package types

import (
"io"
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rlp"
)

type IstanbulExtra rlp.RawValue

type beforeGingerbreadHeader struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
Coinbase common.Address `json:"miner" gencodec:"required"`
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"`
Number *big.Int `json:"number" gencodec:"required"`
GasUsed uint64 `json:"gasUsed" gencodec:"required"`
Time uint64 `json:"timestamp" gencodec:"required"`
Extra []byte `json:"extraData" gencodec:"required"`
}

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

func (h *Header) DecodeRLP(s *rlp.Stream) error {
var raw rlp.RawValue
err := s.Decode(&raw)
if err != nil {
return err
}

preGingerbread, err := isPreGingerbreadHeader(raw)
if err != nil {
return err
}

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

h.ParentHash = decodedHeader.ParentHash
h.Coinbase = decodedHeader.Coinbase
h.Root = decodedHeader.Root
h.TxHash = decodedHeader.TxHash
h.ReceiptHash = decodedHeader.ReceiptHash
h.Bloom = decodedHeader.Bloom
h.Number = decodedHeader.Number
h.GasUsed = decodedHeader.GasUsed
h.Time = decodedHeader.Time
h.Extra = decodedHeader.Extra
} else {
// After gingerbread
decodedHeader := afterGingerbreadHeader{}
err = rlp.DecodeBytes(raw, &decodedHeader)

h.ParentHash = decodedHeader.ParentHash
h.UncleHash = decodedHeader.UncleHash
h.Coinbase = decodedHeader.Coinbase
h.Root = decodedHeader.Root
h.TxHash = decodedHeader.TxHash
h.ReceiptHash = decodedHeader.ReceiptHash
h.Bloom = decodedHeader.Bloom
h.Difficulty = decodedHeader.Difficulty
h.Number = decodedHeader.Number
h.GasLimit = decodedHeader.GasLimit
h.GasUsed = decodedHeader.GasUsed
h.Time = decodedHeader.Time
h.Extra = decodedHeader.Extra
h.MixDigest = decodedHeader.MixDigest
h.Nonce = decodedHeader.Nonce
h.BaseFee = decodedHeader.BaseFee
h.WithdrawalsHash = decodedHeader.WithdrawalsHash
h.BlobGasUsed = decodedHeader.BlobGasUsed
h.ExcessBlobGas = decodedHeader.ExcessBlobGas
h.ParentBeaconRoot = decodedHeader.ParentBeaconRoot
}

return err
}

// EncodeRLP implements encodes the Header to an RLP data stream.
func (h *Header) EncodeRLP(w io.Writer) error {
// We check for a pre gingerbread header by looking for (GasLimit == 0)
// here. We don't use Difficulty because CopyHeader can end up setting a
// nil Difficulty to a zero difficulty, so testing for nil difficulty is
// not reliable, and post gingerbread difficulty is hardcoded to zero. Also
// testing for base fee is not reliable because some older eth blocks had
// no base fee and they are used in some tests.
if h.GasLimit == 0 {
// Encode the header
encodedHeader := beforeGingerbreadHeader{
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,
}

return rlp.Encode(w, &encodedHeader)
}

// After gingerbread
encodedHeader := afterGingerbreadHeader{
ParentHash: h.ParentHash,
UncleHash: h.UncleHash,
Coinbase: h.Coinbase,
Root: h.Root,
TxHash: h.TxHash,
ReceiptHash: h.ReceiptHash,
Bloom: h.Bloom,
Difficulty: h.Difficulty,
Number: h.Number,
GasLimit: h.GasLimit,
GasUsed: h.GasUsed,
Time: h.Time,
Extra: h.Extra,
MixDigest: h.MixDigest,
Nonce: h.Nonce,
BaseFee: h.BaseFee,
WithdrawalsHash: h.WithdrawalsHash,
BlobGasUsed: h.BlobGasUsed,
ExcessBlobGas: h.ExcessBlobGas,
ParentBeaconRoot: h.ParentBeaconRoot,
}

return rlp.Encode(w, &encodedHeader)
}

// isPreGingerbreadHeader introspects the header rlp to check the length of the
// second element of the list (the first element describes the list). Pre
// gingerbread the second element of a header is an address which is 20 bytes
// long, post gingerbread the second element is a hash which is 32 bytes long.
func isPreGingerbreadHeader(buf []byte) (bool, error) {
var contentSize uint64
var err error
for i := 0; i < 3; i++ {
buf, _, _, contentSize, err = rlp.ReadNext(buf)
if err != nil {
return false, err
}
}

return contentSize == common.AddressLength, nil
}
85 changes: 0 additions & 85 deletions core/types/gen_header_rlp.go

This file was deleted.

Loading

0 comments on commit 004e586

Please sign in to comment.