Skip to content

Commit e02aeb3

Browse files
committed
add optional coinbase and nonce to missing header
1 parent 0463b3e commit e02aeb3

File tree

6 files changed

+137
-40
lines changed

6 files changed

+137
-40
lines changed

rollup/missing_header_fields/export-headers-toolkit/README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ A toolkit for exporting and transforming missing block header fields of Scroll b
66
We are using the [Clique consensus](https://eips.ethereum.org/EIPS/eip-225) in Scroll L2. Amongst others, it requires the following header fields:
77
- `extraData`
88
- `difficulty`
9+
- `coinbase`
10+
- `nonce`
911

1012
However, before EuclidV2, these fields were not stored on L1/DA.
1113
In order for nodes to be able to reconstruct the correct block hashes when only reading data from L1,
@@ -31,12 +33,16 @@ Where:
3133
- unique_vanity_i: unique vanity i
3234
- header_i: block header i
3335
- header:
34-
<flags:uint8><vanity_index:uint8><state_root:[32]byte><seal:[65|85]byte>
36+
<flags:uint8><vanity_index:uint8><state_root:[32]byte>[<coinbase:[20]byte>][<nonce:uint64>]<seal:[65|85]byte>
3537
- flags: bitmask, lsb first
38+
- bit 4: 1 if the header has a coinbase field
39+
- bit 5: 1 if the header has a nonce field
3640
- bit 6: 0 if difficulty is 2, 1 if difficulty is 1
3741
- bit 7: 0 if seal length is 65, 1 if seal length is 85
3842
- vanity_index: index of the vanity in the sorted vanities list (0-255)
3943
- state_root: 32 bytes of state root data
44+
- coinbase: 20 bytes of coinbase address (if present)
45+
- nonce: 8 bytes of nonce (if present)
4046
- seal: 65 or 85 bytes of seal data
4147
```
4248

rollup/missing_header_fields/export-headers-toolkit/cmd/dedup.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/spf13/cobra"
1616

1717
"github.com/scroll-tech/go-ethereum/common"
18+
coreTypes "github.com/scroll-tech/go-ethereum/core/types"
1819

1920
"github.com/scroll-tech/go-ethereum/export-headers-toolkit/types"
2021
)
@@ -224,7 +225,7 @@ func (h *csvHeaderReader) readNext() *types.Header {
224225
}
225226

226227
s := strings.Split(strings.TrimSpace(line), ",")
227-
if len(s) != 4 {
228+
if len(s) != 6 {
228229
log.Fatalf("Malformed CSV line: %q", line)
229230
}
230231

@@ -238,10 +239,11 @@ func (h *csvHeaderReader) readNext() *types.Header {
238239
}
239240

240241
stateRoot := common.HexToHash(s[2])
242+
coinbase := common.HexToAddress(s[3])
243+
nonceBytes := common.Hex2Bytes(s[4])
244+
extra := common.FromHex(strings.Split(s[5], "\n")[0])
241245

242-
extra := common.FromHex(strings.Split(s[3], "\n")[0])
243-
244-
header := types.NewHeader(num, difficulty, stateRoot, extra)
246+
header := types.NewHeader(num, difficulty, stateRoot, coinbase, coreTypes.BlockNonce(nonceBytes), extra)
245247
return header
246248
}
247249

@@ -280,13 +282,13 @@ func verifyOutputFile(verifyFile, outputFile string) {
280282
for {
281283
header := csvReader.readNext()
282284
if header == nil {
283-
if _, _, err = dedupReader.ReadNext(); err == nil {
285+
if _, _, _, _, err = dedupReader.ReadNext(); err == nil {
284286
log.Fatalf("Expected EOF, got more headers")
285287
}
286288
break
287289
}
288290

289-
difficulty, stateRoot, extraData, err := dedupReader.Read(header.Number)
291+
difficulty, stateRoot, coinbase, nonce, extraData, err := dedupReader.Read(header.Number)
290292
if err != nil {
291293
log.Fatalf("Error reading header: %v", err)
292294
}
@@ -297,6 +299,12 @@ func verifyOutputFile(verifyFile, outputFile string) {
297299
if header.StateRoot != stateRoot {
298300
log.Fatalf("StateRoot mismatch: headerNum %d: %s != %s", header.Number, header.StateRoot, stateRoot)
299301
}
302+
if header.Coinbase != coinbase {
303+
log.Fatalf("Coinbase mismatch: headerNum %d: %s != %s", header.Number, header.Coinbase.Hex(), coinbase.Hex())
304+
}
305+
if header.Nonce != nonce {
306+
log.Fatalf("Nonce mismatch: headerNum %d: %s != %s", header.Number, common.Bytes2Hex(header.Nonce[:]), common.Bytes2Hex(nonce[:]))
307+
}
300308
if !bytes.Equal(header.ExtraData, extraData) {
301309
log.Fatalf("ExtraData mismatch: headerNum %d: %x != %x", header.Number, header.ExtraData, extraData)
302310
}

rollup/missing_header_fields/export-headers-toolkit/cmd/missing_header_reader.go

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"os"
99

1010
"github.com/scroll-tech/go-ethereum/common"
11+
"github.com/scroll-tech/go-ethereum/core/types"
1112
)
1213

1314
// TODO: instead of duplicating this file, missing_header_fields.Reader should be used in toolkit
@@ -16,6 +17,8 @@ type missingHeader struct {
1617
headerNum uint64
1718
difficulty uint64
1819
stateRoot common.Hash
20+
coinbase common.Address
21+
nonce types.BlockNonce
1922
extraData []byte
2023
}
2124

@@ -56,54 +59,66 @@ func NewReader(filePath string) (*Reader, error) {
5659
return r, nil
5760
}
5861

59-
func (r *Reader) Read(headerNum uint64) (difficulty uint64, stateRoot common.Hash, extraData []byte, err error) {
62+
func (r *Reader) Read(headerNum uint64) (difficulty uint64, stateRoot common.Hash, coinbase common.Address, nonce types.BlockNonce, extraData []byte, err error) {
6063
if r.lastReadHeader == nil {
61-
if _, _, err = r.ReadNext(); err != nil {
62-
return 0, common.Hash{}, nil, err
64+
if _, _, _, _, err = r.ReadNext(); err != nil {
65+
return 0, common.Hash{}, common.Address{}, types.BlockNonce{}, nil, err
6366
}
6467
}
6568

6669
if headerNum > r.lastReadHeader.headerNum {
6770
// skip the headers until the requested header number
6871
for i := r.lastReadHeader.headerNum; i < headerNum; i++ {
69-
if _, _, err = r.ReadNext(); err != nil {
70-
return 0, common.Hash{}, nil, err
72+
if _, _, _, _, err = r.ReadNext(); err != nil {
73+
return 0, common.Hash{}, common.Address{}, types.BlockNonce{}, nil, err
7174
}
7275
}
7376
}
7477

7578
if headerNum == r.lastReadHeader.headerNum {
76-
return r.lastReadHeader.difficulty, r.lastReadHeader.stateRoot, r.lastReadHeader.extraData, nil
79+
return r.lastReadHeader.difficulty, r.lastReadHeader.stateRoot, r.lastReadHeader.coinbase, r.lastReadHeader.nonce, r.lastReadHeader.extraData, nil
7780
}
7881

7982
// headerNum < r.lastReadHeader.headerNum is not supported
80-
return 0, common.Hash{}, nil, fmt.Errorf("requested header %d below last read header number %d", headerNum, r.lastReadHeader.headerNum)
83+
return 0, common.Hash{}, common.Address{}, types.BlockNonce{}, nil, fmt.Errorf("requested header %d below last read header number %d", headerNum, r.lastReadHeader.headerNum)
8184
}
8285

83-
func (r *Reader) ReadNext() (difficulty uint64, extraData []byte, err error) {
86+
func (r *Reader) ReadNext() (difficulty uint64, coinbase common.Address, nonce types.BlockNonce, extraData []byte, err error) {
8487
// read the bitmask
8588
bitmaskByte, err := r.reader.ReadByte()
8689
if err != nil {
87-
return 0, nil, fmt.Errorf("failed to read bitmask: %v", err)
90+
return 0, common.Address{}, types.BlockNonce{}, nil, fmt.Errorf("failed to read bitmask: %v", err)
8891
}
8992

9093
bits := newBitMaskFromByte(bitmaskByte)
9194

9295
// read the vanity index
9396
vanityIndex, err := r.reader.ReadByte()
9497
if err != nil {
95-
return 0, nil, fmt.Errorf("failed to read vanity index: %v", err)
98+
return 0, common.Address{}, types.BlockNonce{}, nil, fmt.Errorf("failed to read vanity index: %v", err)
9699
}
97100

98101
var stateRoot common.Hash
99102
if _, err := io.ReadFull(r.reader, stateRoot[:]); err != nil {
100-
return 0, nil, fmt.Errorf("failed to read state root: %v", err)
103+
return 0, common.Address{}, types.BlockNonce{}, nil, fmt.Errorf("failed to read state root: %v", err)
104+
}
105+
106+
if bits.hasCoinbase() {
107+
if _, err = io.ReadFull(r.reader, coinbase[:]); err != nil {
108+
return 0, common.Address{}, types.BlockNonce{}, nil, fmt.Errorf("failed to read coinbase: %v", err)
109+
}
110+
}
111+
112+
if bits.hasNonce() {
113+
if _, err = io.ReadFull(r.reader, nonce[:]); err != nil {
114+
return 0, common.Address{}, types.BlockNonce{}, nil, fmt.Errorf("failed to read nonce: %v", err)
115+
}
101116
}
102117

103118
seal := make([]byte, bits.sealLen())
104119

105120
if _, err = io.ReadFull(r.reader, seal); err != nil {
106-
return 0, nil, fmt.Errorf("failed to read seal: %v", err)
121+
return 0, common.Address{}, types.BlockNonce{}, nil, fmt.Errorf("failed to read seal: %v", err)
107122
}
108123

109124
// construct the extraData field
@@ -119,16 +134,20 @@ func (r *Reader) ReadNext() (difficulty uint64, extraData []byte, err error) {
119134
headerNum: 0,
120135
difficulty: uint64(bits.difficulty()),
121136
stateRoot: stateRoot,
137+
coinbase: coinbase,
138+
nonce: nonce,
122139
extraData: b.Bytes(),
123140
}
124141
} else {
125142
r.lastReadHeader.headerNum++
126143
r.lastReadHeader.difficulty = uint64(bits.difficulty())
127144
r.lastReadHeader.stateRoot = stateRoot
145+
r.lastReadHeader.coinbase = coinbase
146+
r.lastReadHeader.nonce = nonce
128147
r.lastReadHeader.extraData = b.Bytes()
129148
}
130149

131-
return difficulty, b.Bytes(), nil
150+
return difficulty, coinbase, nonce, b.Bytes(), nil
132151
}
133152

134153
func (r *Reader) Close() error {

rollup/missing_header_fields/export-headers-toolkit/cmd/missing_header_writer.go

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import (
99
"os"
1010
"sort"
1111

12+
"github.com/scroll-tech/go-ethereum/common"
13+
coreTypes "github.com/scroll-tech/go-ethereum/core/types"
14+
1215
"github.com/scroll-tech/go-ethereum/export-headers-toolkit/types"
1316
)
1417

@@ -95,14 +98,17 @@ func (m *missingHeaderWriter) writeVanities() {
9598

9699
func (m *missingHeaderWriter) write(header *types.Header) {
97100
// 1. prepare the bitmask
98-
bits := newBitMask(int(header.Difficulty), header.SealLen())
101+
hasCoinbase := header.Coinbase != (common.Address{})
102+
hasNonce := header.Nonce != (coreTypes.BlockNonce{})
103+
104+
bits := newBitMask(hasCoinbase, hasNonce, int(header.Difficulty), header.SealLen())
99105
vanityIndex := m.sortedVanitiesMap[header.Vanity()]
100106

101107
if vanityIndex >= maxVanityCount {
102108
log.Fatalf("Vanity index %d exceeds maximum allowed %d", vanityIndex, maxVanityCount-1)
103109
}
104110

105-
// 2. write the header: bitmask, vanity index and seal
111+
// 2. write the header: bitmask, optional coinbase, optional nonce, vanity index and seal
106112
if _, err := m.writer.Write(bits.Bytes()); err != nil {
107113
log.Fatalf("Error writing bitmask: %v", err)
108114
}
@@ -112,15 +118,28 @@ func (m *missingHeaderWriter) write(header *types.Header) {
112118
if _, err := m.writer.Write(header.StateRoot[:]); err != nil {
113119
log.Fatalf("Error writing state root: %v", err)
114120
}
121+
if hasCoinbase {
122+
if _, err := m.writer.Write(header.Coinbase[:]); err != nil {
123+
log.Fatalf("Error writing coinbase: %v", err)
124+
}
125+
}
126+
127+
if hasNonce {
128+
if _, err := m.writer.Write(header.Nonce[:]); err != nil {
129+
log.Fatalf("Error writing nonce: %v", err)
130+
}
131+
}
115132
if _, err := m.writer.Write(header.Seal()); err != nil {
116133
log.Fatalf("Error writing seal: %v", err)
117134
}
118135
}
119136

120137
// bitMask is a bitmask that encodes the following information:
121138
//
122-
// bit 6: 0 if difficulty is 2, 1 if difficulty is 1
123-
// bit 7: 0 if seal length is 65, 1 if seal length is 85
139+
// bit 4: 1 if the header has a coinbase field
140+
// bit 5: 1 if the header has a nonce field
141+
// bit 6: 0 if difficulty is 2, 1 if difficulty is 1
142+
// bit 7: 0 if seal length is 65, 1 if seal length is 85
124143
type bitMask struct {
125144
b uint8
126145
}
@@ -129,9 +148,16 @@ func newBitMaskFromByte(b uint8) bitMask {
129148
return bitMask{b}
130149
}
131150

132-
func newBitMask(difficulty int, sealLen int) bitMask {
151+
func newBitMask(hasCoinbase bool, hasNonce bool, difficulty int, sealLen int) bitMask {
133152
b := uint8(0)
134153

154+
if hasCoinbase {
155+
b |= 1 << 4
156+
}
157+
158+
if hasNonce {
159+
b |= 1 << 5
160+
}
135161
if difficulty == 1 {
136162
b |= 1 << 6
137163
} else if difficulty != 2 {
@@ -165,6 +191,14 @@ func (b bitMask) sealLen() int {
165191
}
166192
}
167193

194+
func (b bitMask) hasCoinbase() bool {
195+
return (b.b>>4)&0x01 == 1
196+
}
197+
198+
func (b bitMask) hasNonce() bool {
199+
return (b.b>>5)&0x01 == 1
200+
}
201+
168202
func (b bitMask) Bytes() []byte {
169203
return []byte{b.b}
170204
}

rollup/missing_header_fields/export-headers-toolkit/cmd/missing_header_writer_test.go

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"crypto/rand"
66
"testing"
77

8+
coreTypes "github.com/scroll-tech/go-ethereum/core/types"
89
"github.com/stretchr/testify/assert"
910
"github.com/stretchr/testify/require"
1011

@@ -42,21 +43,27 @@ func TestMissingHeaderWriter(t *testing.T) {
4243
// Header0
4344
{
4445
seal := randomSeal(65)
45-
header := types.NewHeader(0, 2, stateRoot1, append(vanity1[:], seal...))
46+
coinbase := common.Address{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
47+
nonce := coreTypes.BlockNonce{0, 1, 2, 3, 4, 5, 6, 7}
48+
header := types.NewHeader(0, 2, stateRoot1, coinbase, nonce, append(vanity1[:], seal...))
4649
mhw.write(header)
4750

4851
// bit 6=0: difficulty 2, bit 7=0: seal length 65
49-
expectedBytes = append(expectedBytes, 0b00000000)
52+
expectedBytes = append(expectedBytes, 0b00110000)
5053
expectedBytes = append(expectedBytes, 0x00) // vanity index0
5154
expectedBytes = append(expectedBytes, stateRoot1[:]...)
55+
expectedBytes = append(expectedBytes, coinbase[:]...)
56+
expectedBytes = append(expectedBytes, nonce[:]...)
5257
expectedBytes = append(expectedBytes, seal...)
5358
require.Equal(t, expectedBytes, bytesBuffer.Bytes())
5459
}
5560

5661
// Header1
5762
{
63+
coinbase := common.Address{}
64+
nonce := coreTypes.BlockNonce{}
5865
seal := randomSeal(65)
59-
header := types.NewHeader(1, 1, stateRoot2, append(vanity2[:], seal...))
66+
header := types.NewHeader(1, 1, stateRoot2, coinbase, nonce, append(vanity2[:], seal...))
6067
mhw.write(header)
6168

6269
// bit 6=1: difficulty 1, bit 7=0: seal length 65
@@ -68,29 +75,35 @@ func TestMissingHeaderWriter(t *testing.T) {
6875
}
6976

7077
// Header2
78+
coinbase := common.Address{1}
79+
nonce := coreTypes.BlockNonce{201}
7180
{
7281
seal := randomSeal(85)
73-
header := types.NewHeader(2, 2, stateRoot1, append(vanity2[:], seal...))
82+
header := types.NewHeader(2, 2, stateRoot1, coinbase, nonce, append(vanity2[:], seal...))
7483
mhw.write(header)
7584

7685
// bit 6=0: difficulty 2, bit 7=1: seal length 85
77-
expectedBytes = append(expectedBytes, 0b10000000)
86+
expectedBytes = append(expectedBytes, 0b10110000)
7887
expectedBytes = append(expectedBytes, 0x01) // vanity index1
7988
expectedBytes = append(expectedBytes, stateRoot1[:]...)
89+
expectedBytes = append(expectedBytes, coinbase[:]...)
90+
expectedBytes = append(expectedBytes, nonce[:]...)
8091
expectedBytes = append(expectedBytes, seal...)
8192
require.Equal(t, expectedBytes, bytesBuffer.Bytes())
8293
}
8394

8495
// Header3
8596
{
8697
seal := randomSeal(85)
87-
header := types.NewHeader(3, 1, stateRoot2, append(vanity8[:], seal...))
98+
header := types.NewHeader(3, 1, stateRoot2, coinbase, nonce, append(vanity8[:], seal...))
8899
mhw.write(header)
89100

90101
// bit 6=1: difficulty 1, bit 7=1: seal length 85
91-
expectedBytes = append(expectedBytes, 0b11000000)
102+
expectedBytes = append(expectedBytes, 0b11110000)
92103
expectedBytes = append(expectedBytes, 0x02) // vanity index2
93104
expectedBytes = append(expectedBytes, stateRoot2[:]...)
105+
expectedBytes = append(expectedBytes, coinbase[:]...)
106+
expectedBytes = append(expectedBytes, nonce[:]...)
94107
expectedBytes = append(expectedBytes, seal...)
95108
require.Equal(t, expectedBytes, bytesBuffer.Bytes())
96109
}
@@ -99,13 +112,15 @@ func TestMissingHeaderWriter(t *testing.T) {
99112
{
100113
stateRoot3 := common.Hash{123}
101114
seal := randomSeal(65)
102-
header := types.NewHeader(4, 2, stateRoot3, append(vanity1[:], seal...))
115+
header := types.NewHeader(4, 2, stateRoot3, coinbase, nonce, append(vanity1[:], seal...))
103116
mhw.write(header)
104117

105118
// bit 6=0: difficulty 2, bit 7=0: seal length 65
106-
expectedBytes = append(expectedBytes, 0b00000000)
119+
expectedBytes = append(expectedBytes, 0b00110000)
107120
expectedBytes = append(expectedBytes, 0x00) // vanity index0
108121
expectedBytes = append(expectedBytes, stateRoot3[:]...)
122+
expectedBytes = append(expectedBytes, coinbase[:]...)
123+
expectedBytes = append(expectedBytes, nonce[:]...)
109124
expectedBytes = append(expectedBytes, seal...)
110125
require.Equal(t, expectedBytes, bytesBuffer.Bytes())
111126
}

0 commit comments

Comments
 (0)