Skip to content

Commit 85c1757

Browse files
authored
Eth header hash compatibility (#2130)
* Reorder header fields for Eth compatibility * Use custom RLP Header serialization To maintain compatibility with blocks from before Gingerbread, we need to use the same serialization as back then (when the fields were not present). This also ensures we keep the same header hashes, as those are based on the RLP representation. * Allow missing fields in JSON Header These are not present in older blocks and we want to be able to decode both the old and new blocks. * Minor tweaks for e2e test * Rename test to TestEthersJSCompatibilityDisableAfterGingerbread, so that `go test -run` can match that name without matching others * Check for sha3Uncles value * Add Ethereum hash compatibility test * Update monorepo commit Required change: * Update header hashing for eth compat * Use Header.SanityCheck from geth Not that we have reintroduces the removed header fields, we can check them in the SanityCheck, again. * Set new header fields in genesis, too The genesis handling can be brought close to geth than this, but this is already a step into the right direction. * Add case to test Celo header hash compat with blocks before Gingerbread.
1 parent 05b05ca commit 85c1757

8 files changed

+279
-35
lines changed

core/genesis.go

+7
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,13 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block {
278278
Coinbase: g.Coinbase,
279279
Root: root,
280280
}
281+
if g.Config != nil && g.Config.IsGFork(common.Big0) {
282+
head.Nonce = types.EncodeNonce(0)
283+
head.GasLimit = params.GenesisGasLimit
284+
head.Difficulty = common.Big0
285+
head.MixDigest = types.EmptyMixDigest
286+
head.UncleHash = types.EmptyUncleHash
287+
}
281288

282289
statedb.Commit(false)
283290
statedb.Database().TrieDB().Commit(root, true, nil)

core/types/block.go

+21-8
Original file line numberDiff line numberDiff line change
@@ -74,45 +74,48 @@ func (n *BlockNonce) UnmarshalText(input []byte) error {
7474
// Header represents a block header in the Ethereum blockchain.
7575
type Header struct {
7676
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
77+
UncleHash common.Hash `json:"sha3Uncles"`
7778
Coinbase common.Address `json:"miner" gencodec:"required"`
7879
Root common.Hash `json:"stateRoot" gencodec:"required"`
7980
TxHash common.Hash `json:"transactionsRoot" gencodec:"required"`
8081
ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"`
8182
Bloom Bloom `json:"logsBloom" gencodec:"required"`
83+
Difficulty *big.Int `json:"difficulty"`
8284
Number *big.Int `json:"number" gencodec:"required"`
85+
GasLimit uint64 `json:"gasLimit"`
8386
GasUsed uint64 `json:"gasUsed" gencodec:"required"`
8487
Time uint64 `json:"timestamp" gencodec:"required"`
8588
Extra []byte `json:"extraData" gencodec:"required"`
89+
MixDigest common.Hash `json:"mixHash"`
90+
Nonce BlockNonce `json:"nonce"`
91+
92+
// BaseFee was added by EIP-1559 and is ignored in legacy headers.
93+
BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"`
8694

8795
// Used to cache deserialized istanbul extra data
8896
extraLock sync.Mutex
8997
extraValue *IstanbulExtra
9098
extraError error
91-
92-
GasLimit uint64 `json:"gasLimit" rlp:"optional"`
93-
// Proof-of-work fields for Eth compatibility
94-
Difficulty *big.Int `json:"difficulty" rlp:"optional"`
95-
Nonce BlockNonce `json:"nonce" rlp:"optional"`
96-
UncleHash common.Hash `json:"sha3Uncles" rlp:"optional"`
97-
MixDigest common.Hash `json:"mixHash" rlp:"optional"`
9899
}
99100

100101
// field type overrides for gencodec
101102
type headerMarshaling struct {
103+
Difficulty *hexutil.Big
102104
Number *hexutil.Big
103105
GasLimit hexutil.Uint64
104106
GasUsed hexutil.Uint64
105107
Time hexutil.Uint64
106108
Extra hexutil.Bytes
109+
BaseFee *hexutil.Big
107110
Hash common.Hash `json:"hash"` // adds call to Hash() in MarshalJSON
108-
Difficulty *hexutil.Big
109111
}
110112

111113
// Hash returns the block hash of the header, which is simply the keccak256 hash of its
112114
// RLP encoding.
113115
func (h *Header) Hash() common.Hash {
114116
// Seal is reserved in extra-data. To prove block is signed by the proposer.
115117
if len(h.Extra) >= IstanbulExtraVanity {
118+
// This branch is always used during normal Celo operation, but not in all tests.
116119
if istanbulHeader := IstanbulFilteredHeader(h, true); istanbulHeader != nil {
117120
return rlpHash(istanbulHeader)
118121
}
@@ -136,9 +139,19 @@ func (h *Header) SanityCheck() error {
136139
if h.Number != nil && !h.Number.IsUint64() {
137140
return fmt.Errorf("too large block number: bitlen %d", h.Number.BitLen())
138141
}
142+
if h.Difficulty != nil {
143+
if diffLen := h.Difficulty.BitLen(); diffLen > 80 {
144+
return fmt.Errorf("too large block difficulty: bitlen %d", diffLen)
145+
}
146+
}
139147
if eLen := len(h.Extra); eLen > 100*1024 {
140148
return fmt.Errorf("too large block extradata: size %d", eLen)
141149
}
150+
if h.BaseFee != nil {
151+
if bfLen := h.BaseFee.BitLen(); bfLen > 256 {
152+
return fmt.Errorf("too large base fee: bitlen %d", bfLen)
153+
}
154+
}
142155
return nil
143156
}
144157

core/types/block_compatibility.go

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package types
2+
3+
import (
4+
"io"
5+
"math/big"
6+
"sync"
7+
8+
"github.com/celo-org/celo-blockchain/common"
9+
"github.com/celo-org/celo-blockchain/rlp"
10+
)
11+
12+
// This file takes care of supporting older block header formats from before
13+
// the gingerbread fork.
14+
15+
type beforeGingerbreadHeader struct {
16+
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
17+
Coinbase common.Address `json:"miner" gencodec:"required"`
18+
Root common.Hash `json:"stateRoot" gencodec:"required"`
19+
TxHash common.Hash `json:"transactionsRoot" gencodec:"required"`
20+
ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"`
21+
Bloom Bloom `json:"logsBloom" gencodec:"required"`
22+
Number *big.Int `json:"number" gencodec:"required"`
23+
GasUsed uint64 `json:"gasUsed" gencodec:"required"`
24+
Time uint64 `json:"timestamp" gencodec:"required"`
25+
Extra []byte `json:"extraData" gencodec:"required"`
26+
27+
// Used to cache deserialized istanbul extra data
28+
extraLock sync.Mutex
29+
extraValue *IstanbulExtra
30+
extraError error
31+
}
32+
33+
type afterGingerbreadHeader Header
34+
35+
func (h *Header) DecodeRLP(s *rlp.Stream) error {
36+
_, size, _ := s.Kind()
37+
var raw rlp.RawValue
38+
err := s.Decode(&raw)
39+
if err != nil {
40+
return err
41+
}
42+
headerSize := len(raw) - int(size)
43+
numElems, err := rlp.CountValues(raw[headerSize:])
44+
if err != nil {
45+
return err
46+
}
47+
if numElems == 10 {
48+
// Before gingerbread
49+
decodedHeader := beforeGingerbreadHeader{}
50+
err = rlp.DecodeBytes(raw, &decodedHeader)
51+
52+
h.ParentHash = decodedHeader.ParentHash
53+
h.Coinbase = decodedHeader.Coinbase
54+
h.Root = decodedHeader.Root
55+
h.TxHash = decodedHeader.TxHash
56+
h.ReceiptHash = decodedHeader.ReceiptHash
57+
h.Bloom = decodedHeader.Bloom
58+
h.Number = decodedHeader.Number
59+
h.GasUsed = decodedHeader.GasUsed
60+
h.Time = decodedHeader.Time
61+
h.Extra = decodedHeader.Extra
62+
} else {
63+
// After gingerbread
64+
decodedHeader := afterGingerbreadHeader{}
65+
err = rlp.DecodeBytes(raw, &decodedHeader)
66+
67+
h.ParentHash = decodedHeader.ParentHash
68+
h.UncleHash = decodedHeader.UncleHash
69+
h.Coinbase = decodedHeader.Coinbase
70+
h.Root = decodedHeader.Root
71+
h.TxHash = decodedHeader.TxHash
72+
h.ReceiptHash = decodedHeader.ReceiptHash
73+
h.Bloom = decodedHeader.Bloom
74+
h.Difficulty = decodedHeader.Difficulty
75+
h.Number = decodedHeader.Number
76+
h.GasLimit = decodedHeader.GasLimit
77+
h.GasUsed = decodedHeader.GasUsed
78+
h.Time = decodedHeader.Time
79+
h.Extra = decodedHeader.Extra
80+
h.MixDigest = decodedHeader.MixDigest
81+
h.Nonce = decodedHeader.Nonce
82+
h.BaseFee = decodedHeader.BaseFee
83+
}
84+
85+
return err
86+
}
87+
88+
func (h *Header) EncodeRLP(w io.Writer) error {
89+
if h.Difficulty == nil {
90+
// Before gingerbread hardfork Celo did not include all of
91+
// Ethereum's header fields. In that case we must omit the new
92+
// fields from the header when encoding as RLP to maintain the same encoding and hashes.
93+
// `Difficulty` is a safe way to check, since it is always nil before
94+
// gingerbread and always 0 after.
95+
rlpFields := []interface{}{
96+
h.ParentHash,
97+
h.Coinbase,
98+
h.Root,
99+
h.TxHash,
100+
h.ReceiptHash,
101+
h.Bloom,
102+
h.Number,
103+
h.GasUsed,
104+
h.Time,
105+
h.Extra,
106+
}
107+
return rlp.Encode(w, rlpFields)
108+
} else {
109+
rlpFields := []interface{}{
110+
h.ParentHash,
111+
h.UncleHash,
112+
h.Coinbase,
113+
h.Root,
114+
h.TxHash,
115+
h.ReceiptHash,
116+
h.Bloom,
117+
h.Difficulty,
118+
h.Number,
119+
h.GasLimit,
120+
h.GasUsed,
121+
h.Time,
122+
h.Extra,
123+
h.MixDigest,
124+
h.Nonce,
125+
h.BaseFee,
126+
}
127+
return rlp.Encode(w, rlpFields)
128+
}
129+
}
+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package types
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestEthereumHeaderHashCompatibility(t *testing.T) {
11+
// Ethereum tools calculating block header hashes show work on Celo just as
12+
// the do on Ethereum. To verify that this is the case, we import an
13+
// Ethereum header, calculate the hash and show that we calculate the same
14+
// hash as the header has on Ethereum.
15+
16+
// Last Ethereum block before the merge: https://etherscan.io/block/15537393
17+
ethereumJsonHeader := []byte(`{
18+
"number": "0xed14f1",
19+
"hash": "0x55b11b918355b1ef9c5db810302ebad0bf2544255b530cdce90674d5887bb286",
20+
"parentHash": "0x2b3ea3cd4befcab070812443affb08bf17a91ce382c714a536ca3cacab82278b",
21+
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
22+
"logsBloom": "0x00000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000008000000000000000000000000000000000000000000000000020000000000000000000800000000004000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008400000000001002000000000000000000000000000000000002000000020000000020000000000000000000000000000000000000000040000000000000000000000000",
23+
"transactionsRoot": "0xdd5eec02b019ff76e359b09bfa19395a2a0e97bc01e70d8d5491e640167c96a8",
24+
"stateRoot": "0x4919dafa6ac8becfbbd0c2808f6c9511a057c21e42839caff5dfb6d3ef514951",
25+
"receiptsRoot": "0xbaa842cfd552321a9c2450576126311e071680a1258032219c6490b663c1dab8",
26+
"miner": "0x829bd824b016326a401d083b33d092293333a830",
27+
"difficulty": "0x27472e1db3626a",
28+
"totalDifficulty": "0xc70d815d562d3cfa955",
29+
"extraData": "0xe4b883e5bda9e7a59ee4bb99e9b1bc460021",
30+
"size": "0x664",
31+
"gasLimit": "0x1c9c380",
32+
"gasUsed": "0x1c9a205",
33+
"timestamp": "0x6322c962",
34+
"uncles": [],
35+
"baseFeePerGas": "0xa1a4e5f06",
36+
"nonce": "0x62a3ee77461d4fc9",
37+
"mixHash": "0x4cbec03dddd4b939730a7fe6048729604d4266e82426d472a2b2024f3cc4043f"
38+
}`)
39+
var h Header
40+
var jsonHeader map[string]interface{}
41+
require.NoError(t, json.Unmarshal(ethereumJsonHeader, &jsonHeader))
42+
require.NoError(t, json.Unmarshal(ethereumJsonHeader, &h))
43+
require.Equal(t, jsonHeader["hash"], h.Hash().Hex())
44+
}
45+
46+
func TestCeloBeforeGingerbreadHeaderHashCompatibility(t *testing.T) {
47+
// Before Gingerbread, certain header fields were not included in the
48+
// header hash calculation. Blocks generated before Gingerbread must keep
49+
// the same hash, even though we added fields for later blocks.
50+
51+
// The `difficulty` field has been manually removed from the RPC result. It
52+
// is only added to old blocks by the RPCEthCompatibility option.
53+
celoJsonHeader := []byte(`{
54+
"epochSnarkData": {
55+
"bitmap": "0x325007fdf89adf7e7f7e2cfff8ff",
56+
"signature": "0x65c0b4238a63c90fbe93619f1f787cc9c24ecb07bb06501fe99bef938d6c74a16beb49593e4e5ead625c244cc20dce80"
57+
},
58+
"extraData": "0xd983010704846765746889676f312e31372e3133856c696e7578000000000000f8ccc0c080b84177cdc8f22ff53bd1ed75f2a09b920957d7cbbc01a283c8eb40c7dcf96bbcd41434e06fa9ddafbe61e83dec0a2cb11d5435971242b4a25e6f129768b183e8886700f8418e325007fdf89adf7e7f7e2cfff8ffb05e98abf6071b03bd3b2d420188991b04c874a2cce546e299fef6fccb2b2dc8828e42da4b3dffa483e5b7cf7e79807b8080f8418e3fffffffffffffffffffffffffffb06009638e885c0870c288f816403d0dd04055e4feb3b8e157dc0acdf5f0da3b84636a4c997234969ace1a56084733840080",
59+
"gasUsed": "0x1063b1",
60+
"hash": "0xf09d2d8cb7d4ac4c8b49b18030ce09c0be3a40a33a83bb627d27363513907240",
61+
"logsBloom": "0x2833101222d26aa2000821a9c0a28529948200409056708002002108083a02408908988388a0c480003126142e41a48c0190884321c43010005c8c0242209002379005102fc20c5e6941505a430a7da0085c115008f22f000010199042228c03000006300224d00420341400700408010e4107c09a001021601dc292043d930025843c00830170c00400103002f840421a4500710111981a0434805400d6e8104223b8009121010d0301404a88230c10380200107a2b00050020d18c44159440a0b024222425d2522142f12610eac0910c09612700153911b0a8a510c01060a1da1303a546482c40008a0d282a80b30020402411100f2c00404c305555200035",
62+
"miner": "0xaa397ca08bbebc31717f8ab2cea35320c51568e6",
63+
"number": "0x1229100",
64+
"parentHash": "0x1469ec2ace32ba4f18a7abbc634cf13119a07167b65bc0a2319bb2513625d1c7",
65+
"randomness": {
66+
"committed": "0x60dfa4c93980a2eb42e55df77f7ea945296b63b322ef8d593c955d699992e1aa",
67+
"revealed": "0xc22bede1a7919d30eda482820e1b6fd393543cc1dd4bac22b6b915b5812bf2bb"
68+
},
69+
"receiptsRoot": "0x7c0af0b3d3d9d750eb8dccffe633794583960b7e72bef95e33aaa0aaf09c58ac",
70+
"size": "0x5ee",
71+
"stateRoot": "0x481088b07b8e111f532b8edb2d2881e1747f5d61248b28c096c120b010b336f2",
72+
"timestamp": "0x644eb9f3",
73+
"totalDifficulty": "0x1229101",
74+
"transactions": [
75+
"0x8516bb82865b367d6c33b93cb69424b3e30ba19e4a23c95c39a4980cb8a803db",
76+
"0x9101d965f19a46eb6246f648fb906a26b966dcb4c5cb001a3422f18974be3dfc",
77+
"0xba94878140aefa8a0fc23ae885537224191e3bf4a6d1dcf0706103245a2d73ac",
78+
"0xfa8bc080e0925105b16a58a435cf757b510c010509545a6719138babcd52dfea"
79+
],
80+
"transactionsRoot": "0x4a1e09afdc7e40256ac262da49396fe7c5bae0b7d9f46e4bc1fe495ef6d8514b"
81+
}`)
82+
var h Header
83+
var jsonHeader map[string]interface{}
84+
require.NoError(t, json.Unmarshal(celoJsonHeader, &jsonHeader))
85+
require.NoError(t, json.Unmarshal(celoJsonHeader, &h))
86+
require.Equal(t, jsonHeader["hash"], h.Hash().Hex())
87+
}

0 commit comments

Comments
 (0)