Skip to content

Commit d94011e

Browse files
committed
feat: add toolkit for exporting and transforming missing block headers fields
1 parent df08f60 commit d94011e

File tree

10 files changed

+891
-0
lines changed

10 files changed

+891
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
data/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
FROM golang:1.22
2+
3+
WORKDIR /app
4+
5+
COPY go.mod go.sum ./
6+
7+
RUN go mod download
8+
9+
COPY . .
10+
11+
RUN go build -o main .
12+
13+
ENTRYPOINT ["./main"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Export missing block header fields toolkit
2+
3+
A toolkit for exporting and transforming missing block header fields of Scroll before {{upgrade_name}} TODO: replace when upgrade is clear.
4+
5+
## Context
6+
We are using the [Clique consensus](https://eips.ethereum.org/EIPS/eip-225) in Scroll L2. Amongst others, it requires the following header fields:
7+
- `extraData`
8+
- `difficulty`
9+
10+
However, before {{upgrade_name}}, these fields were not stored on L1/DA.
11+
In order for nodes to be able to reconstruct the correct block hashes when only reading data from L1,
12+
we need to provide the historical values of these fields to these nodes through a separate file.
13+
14+
This toolkit provides commands to export the missing fields, deduplicate the data and create a file
15+
with the missing fields that can be used to reconstruct the correct block hashes when only reading data from L1.
16+
17+
The toolkit provides the following commands:
18+
- `fetch` - Fetch missing block header fields from a running Scroll L2 node and store in a file
19+
- `dedup` - Deduplicate the headers file, print unique values and create a new file with the deduplicated headers
20+
21+
## Binary layout deduplicated missing header fields file
22+
The deduplicated header file binary layout is as follows:
23+
24+
```plaintext
25+
<unique_vanity_count:uint8><unique_vanity_1:[32]byte>...<unique_vanity_n:[32]byte><header_1:header>...<header_n:header>
26+
27+
Where:
28+
- unique_vanity_count: number of unique vanities n
29+
- unique_vanity_i: unique vanity i
30+
- header_i: block header i
31+
- header:
32+
<vanity_index:uint8><flags:uint8><seal:[65|85]byte>
33+
- vanity_index: index of the vanity in the unique vanity list
34+
- flags: bitmask, lsb first
35+
- bit 0: 0 if difficulty is 2, 1 if difficulty is 1
36+
- bit 1: 0 if seal length is 65, 1 if seal length is 85
37+
- rest: 0
38+
```
39+
40+
## How to run
41+
Each of the commands has its own set of flags and options. To display the help message run with `--help` flag.
42+
43+
1. Fetch the missing block header fields from a running Scroll L2 node via RPC and store in a file (approx 40min for 5.5M blocks).
44+
2. Deduplicate the headers file, print unique values and create a new file with the deduplicated headers
45+
46+
```bash
47+
go run main.go fetch --rpc=http://localhost:8545 --start=0 --end=100 --batch=10 --parallelism=10 --output=headers.bin --humanOutput=true
48+
go run main.go dedup --input=headers.bin --output=headers-dedup.bin
49+
```
50+
51+
52+
### With Docker
53+
To run the toolkit with Docker, build the Docker image and run the commands inside the container.
54+
55+
```bash
56+
docker build -t export-headers-toolkit .
57+
58+
# depending on the Docker config maybe finding the RPC container's IP with docker inspect is necessary. Potentially host IP works: http://172.17.0.1:8545
59+
docker run --rm -v "$(pwd)":/app/result export-headers-toolkit fetch --rpc=<address> --start=0 --end=5422047 --batch=10000 --parallelism=10 --output=/app/result/headers.bin --humanOutput=/app/result/headers.csv
60+
docker run --rm -v "$(pwd)":/app/result export-headers-toolkit dedup --input=/app/result/headers.bin --output=/app/result/headers-dedup.bin
61+
```
62+
63+
64+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package cmd
2+
3+
import (
4+
"bufio"
5+
"encoding/binary"
6+
"fmt"
7+
"io"
8+
"log"
9+
"os"
10+
11+
"github.com/spf13/cobra"
12+
13+
"github.com/scroll-tech/go-ethereum/export-headers-toolkit/types"
14+
)
15+
16+
// dedupCmd represents the dedup command
17+
var dedupCmd = &cobra.Command{
18+
Use: "dedup",
19+
Short: "Deduplicate the headers file, print unique values and create a new file with the deduplicated headers",
20+
Long: `Deduplicate the headers file, print unique values and create a new file with the deduplicated headers.
21+
22+
The binary layout of the deduplicated file is as follows:
23+
- 1 byte for the count of unique vanity
24+
- 32 bytes for each unique vanity
25+
- for each header:
26+
- 1 byte for the index of the vanity in the unique vanity list
27+
- 1 byte (bitmask, lsb first):
28+
- bit 0: 0 if difficulty is 2, 1 if difficulty is 1
29+
- bit 1: 0 if seal length is 65, 1 if seal length is 85
30+
- rest: 0
31+
- 65 or 85 bytes for the seal`,
32+
Run: func(cmd *cobra.Command, args []string) {
33+
inputFile, err := cmd.Flags().GetString("input")
34+
if err != nil {
35+
log.Fatalf("Error reading output flag: %v", err)
36+
}
37+
outputFile, err := cmd.Flags().GetString("output")
38+
if err != nil {
39+
log.Fatalf("Error reading output flag: %v", err)
40+
}
41+
42+
runDedup(inputFile, outputFile)
43+
},
44+
}
45+
46+
func init() {
47+
rootCmd.AddCommand(dedupCmd)
48+
49+
dedupCmd.Flags().String("input", "headers.bin", "headers file")
50+
dedupCmd.Flags().String("output", "headers-dedup.bin", "deduplicated, binary formatted file")
51+
}
52+
53+
func runDedup(inputFile, outputFile string) {
54+
reader := newHeaderReader(inputFile)
55+
defer reader.close()
56+
57+
// track header fields we've seen
58+
seenDifficulty := make(map[uint64]bool)
59+
seenVanity := make(map[[32]byte]bool)
60+
seenSealLen := make(map[int]bool)
61+
62+
reader.read(func(header *types.Header) {
63+
seenDifficulty[header.Difficulty] = true
64+
seenVanity[header.Vanity()] = true
65+
seenSealLen[header.SealLen()] = true
66+
})
67+
68+
// Print report
69+
fmt.Println("--------------------------------------------------")
70+
fmt.Printf("Unique values seen in the headers file (last seen block: %d):\n", reader.lastHeader.Number)
71+
fmt.Printf("Distinct count: Difficulty:%d, Vanity:%d, SealLen:%d\n", len(seenDifficulty), len(seenVanity), len(seenSealLen))
72+
fmt.Printf("--------------------------------------------------\n\n")
73+
74+
for diff := range seenDifficulty {
75+
fmt.Printf("Difficulty: %d\n", diff)
76+
}
77+
78+
for vanity := range seenVanity {
79+
fmt.Printf("Vanity: %x\n", vanity)
80+
}
81+
82+
for sealLen := range seenSealLen {
83+
fmt.Printf("SealLen: %d\n", sealLen)
84+
}
85+
}
86+
87+
type headerReader struct {
88+
file *os.File
89+
reader *bufio.Reader
90+
lastHeader *types.Header
91+
}
92+
93+
func newHeaderReader(inputFile string) *headerReader {
94+
f, err := os.Open(inputFile)
95+
if err != nil {
96+
log.Fatalf("Error opening input file: %v", err)
97+
}
98+
99+
h := &headerReader{
100+
file: f,
101+
reader: bufio.NewReader(f),
102+
}
103+
104+
return h
105+
}
106+
107+
func (h *headerReader) read(callback func(header *types.Header)) {
108+
headerSizeBytes := make([]byte, types.HeaderSizeSerialized)
109+
110+
for {
111+
_, err := io.ReadFull(h.reader, headerSizeBytes)
112+
if err != nil {
113+
if err == io.EOF {
114+
break
115+
}
116+
log.Fatalf("Error reading headerSizeBytes: %v", err)
117+
}
118+
headerSize := binary.BigEndian.Uint16(headerSizeBytes)
119+
120+
headerBytes := make([]byte, headerSize)
121+
_, err = io.ReadFull(h.reader, headerBytes)
122+
if err != nil {
123+
if err == io.EOF {
124+
break
125+
}
126+
log.Fatalf("Error reading headerBytes: %v", err)
127+
}
128+
header := new(types.Header).FromBytes(headerBytes)
129+
130+
// sanity check: make sure headers are in order
131+
if h.lastHeader != nil && header.Number != h.lastHeader.Number+1 {
132+
fmt.Println("lastHeader:", h.lastHeader.String())
133+
log.Fatalf("Missing block: %d, got %d instead", h.lastHeader.Number+1, header.Number)
134+
}
135+
h.lastHeader = header
136+
137+
callback(header)
138+
}
139+
}
140+
141+
func (h *headerReader) close() {
142+
h.file.Close()
143+
}

0 commit comments

Comments
 (0)