1use crate::{
9 e2s_file::{E2StoreReader, E2StoreWriter},
10 e2s_types::{E2sError, Version},
11 era1_types::{BlockIndex, Era1Group, Era1Id, BLOCK_INDEX},
12 execution_types::{
13 self, Accumulator, BlockTuple, CompressedBody, CompressedHeader, CompressedReceipts,
14 TotalDifficulty, MAX_BLOCKS_PER_ERA1,
15 },
16};
17use alloy_primitives::BlockNumber;
18use std::{
19 fs::File,
20 io::{Read, Seek, Write},
21 path::Path,
22};
23
24#[derive(Debug)]
26pub struct Era1File {
27 pub version: Version,
29
30 pub group: Era1Group,
32
33 pub id: Era1Id,
35}
36
37impl Era1File {
38 pub fn new(group: Era1Group, id: Era1Id) -> Self {
40 Self { version: Version, group, id }
41 }
42
43 pub fn get_block_by_number(&self, number: BlockNumber) -> Option<&BlockTuple> {
45 let index = (number - self.group.block_index.starting_number) as usize;
46 (index < self.group.blocks.len()).then(|| &self.group.blocks[index])
47 }
48
49 pub fn block_range(&self) -> std::ops::RangeInclusive<BlockNumber> {
51 let start = self.group.block_index.starting_number;
52 let end = start + (self.group.blocks.len() as u64) - 1;
53 start..=end
54 }
55
56 pub fn contains_block(&self, number: BlockNumber) -> bool {
58 self.block_range().contains(&number)
59 }
60}
61#[derive(Debug)]
63pub struct Era1Reader<R: Read> {
64 reader: E2StoreReader<R>,
65}
66
67impl<R: Read + Seek> Era1Reader<R> {
68 pub fn new(reader: R) -> Self {
70 Self { reader: E2StoreReader::new(reader) }
71 }
72
73 pub fn read(&mut self, network_name: String) -> Result<Era1File, E2sError> {
76 let _version_entry = match self.reader.read_version()? {
78 Some(entry) if entry.is_version() => entry,
79 Some(_) => return Err(E2sError::Ssz("First entry is not a Version entry".to_string())),
80 None => return Err(E2sError::Ssz("Empty Era1 file".to_string())),
81 };
82
83 let entries = self.reader.entries()?;
85
86 let entries = entries.into_iter().skip(1).collect::<Vec<_>>();
88
89 let mut headers = Vec::new();
91 let mut bodies = Vec::new();
92 let mut receipts = Vec::new();
93 let mut difficulties = Vec::new();
94 let mut other_entries = Vec::new();
95 let mut accumulator = None;
96 let mut block_index = None;
97
98 for entry in entries {
100 match entry.entry_type {
101 execution_types::COMPRESSED_HEADER => {
102 headers.push(CompressedHeader::from_entry(&entry)?);
103 }
104 execution_types::COMPRESSED_BODY => {
105 bodies.push(CompressedBody::from_entry(&entry)?);
106 }
107 execution_types::COMPRESSED_RECEIPTS => {
108 receipts.push(CompressedReceipts::from_entry(&entry)?);
109 }
110 execution_types::TOTAL_DIFFICULTY => {
111 difficulties.push(TotalDifficulty::from_entry(&entry)?);
112 }
113 execution_types::ACCUMULATOR => {
114 if accumulator.is_some() {
115 return Err(E2sError::Ssz("Multiple accumulator entries found".to_string()));
116 }
117 accumulator = Some(Accumulator::from_entry(&entry)?);
118 }
119 BLOCK_INDEX => {
120 if block_index.is_some() {
121 return Err(E2sError::Ssz("Multiple block index entries found".to_string()));
122 }
123 block_index = Some(BlockIndex::from_entry(&entry)?);
124 }
125 _ => {
126 other_entries.push(entry);
127 }
128 }
129 }
130
131 if headers.len() != bodies.len() ||
133 headers.len() != receipts.len() ||
134 headers.len() != difficulties.len()
135 {
136 return Err(E2sError::Ssz(format!(
137 "Mismatched block component counts: headers={}, bodies={}, receipts={}, difficulties={}",
138 headers.len(), bodies.len(), receipts.len(), difficulties.len()
139 )));
140 }
141
142 let mut blocks = Vec::with_capacity(headers.len());
143 for i in 0..headers.len() {
144 blocks.push(BlockTuple::new(
145 headers[i].clone(),
146 bodies[i].clone(),
147 receipts[i].clone(),
148 difficulties[i].clone(),
149 ));
150 }
151
152 let accumulator = accumulator
153 .ok_or_else(|| E2sError::Ssz("Era1 file missing accumulator entry".to_string()))?;
154
155 let block_index = block_index
156 .ok_or_else(|| E2sError::Ssz("Era1 file missing block index entry".to_string()))?;
157
158 let mut group = Era1Group::new(blocks, accumulator, block_index.clone());
159
160 for entry in other_entries {
162 group.add_entry(entry);
163 }
164
165 let id = Era1Id::new(
166 network_name,
167 block_index.starting_number,
168 block_index.offsets.len() as u32,
169 );
170
171 Ok(Era1File::new(group, id))
172 }
173}
174
175impl Era1Reader<File> {
176 pub fn open<P: AsRef<Path>>(
178 path: P,
179 network_name: impl Into<String>,
180 ) -> Result<Era1File, E2sError> {
181 let file = File::open(path).map_err(E2sError::Io)?;
182 let mut reader = Self::new(file);
183 reader.read(network_name.into())
184 }
185}
186
187#[derive(Debug)]
189pub struct Era1Writer<W: Write> {
190 writer: E2StoreWriter<W>,
191 has_written_version: bool,
192 has_written_blocks: bool,
193 has_written_accumulator: bool,
194 has_written_block_index: bool,
195}
196
197impl<W: Write> Era1Writer<W> {
198 pub fn new(writer: W) -> Self {
200 Self {
201 writer: E2StoreWriter::new(writer),
202 has_written_version: false,
203 has_written_blocks: false,
204 has_written_accumulator: false,
205 has_written_block_index: false,
206 }
207 }
208
209 pub fn write_version(&mut self) -> Result<(), E2sError> {
211 if self.has_written_version {
212 return Ok(());
213 }
214
215 self.writer.write_version()?;
216 self.has_written_version = true;
217 Ok(())
218 }
219
220 pub fn write_era1_file(&mut self, era1_file: &Era1File) -> Result<(), E2sError> {
222 self.write_version()?;
224
225 if era1_file.group.blocks.len() > MAX_BLOCKS_PER_ERA1 {
227 return Err(E2sError::Ssz("Era1 file cannot contain more than 8192 blocks".to_string()));
228 }
229
230 for block in &era1_file.group.blocks {
232 self.write_block(block)?;
233 }
234
235 for entry in &era1_file.group.other_entries {
237 self.writer.write_entry(entry)?;
238 }
239
240 self.write_accumulator(&era1_file.group.accumulator)?;
242
243 self.write_block_index(&era1_file.group.block_index)?;
245
246 self.writer.flush()?;
248
249 Ok(())
250 }
251
252 pub fn write_block(
254 &mut self,
255 block_tuple: &crate::execution_types::BlockTuple,
256 ) -> Result<(), E2sError> {
257 if !self.has_written_version {
258 self.write_version()?;
259 }
260
261 if self.has_written_accumulator || self.has_written_block_index {
262 return Err(E2sError::Ssz(
263 "Cannot write blocks after accumulator or block index".to_string(),
264 ));
265 }
266
267 let header_entry = block_tuple.header.to_entry();
269 self.writer.write_entry(&header_entry)?;
270
271 let body_entry = block_tuple.body.to_entry();
273 self.writer.write_entry(&body_entry)?;
274
275 let receipts_entry = block_tuple.receipts.to_entry();
277 self.writer.write_entry(&receipts_entry)?;
278
279 let difficulty_entry = block_tuple.total_difficulty.to_entry();
281 self.writer.write_entry(&difficulty_entry)?;
282
283 self.has_written_blocks = true;
284
285 Ok(())
286 }
287
288 pub fn write_accumulator(&mut self, accumulator: &Accumulator) -> Result<(), E2sError> {
290 if !self.has_written_version {
291 self.write_version()?;
292 }
293
294 if self.has_written_accumulator {
295 return Err(E2sError::Ssz("Accumulator already written".to_string()));
296 }
297
298 if self.has_written_block_index {
299 return Err(E2sError::Ssz("Cannot write accumulator after block index".to_string()));
300 }
301
302 let accumulator_entry = accumulator.to_entry();
303 self.writer.write_entry(&accumulator_entry)?;
304 self.has_written_accumulator = true;
305
306 Ok(())
307 }
308
309 pub fn write_block_index(&mut self, block_index: &BlockIndex) -> Result<(), E2sError> {
311 if !self.has_written_version {
312 self.write_version()?;
313 }
314
315 if self.has_written_block_index {
316 return Err(E2sError::Ssz("Block index already written".to_string()));
317 }
318
319 let block_index_entry = block_index.to_entry();
320 self.writer.write_entry(&block_index_entry)?;
321 self.has_written_block_index = true;
322
323 Ok(())
324 }
325
326 pub fn flush(&mut self) -> Result<(), E2sError> {
328 self.writer.flush()
329 }
330}
331
332impl Era1Writer<File> {
333 pub fn create<P: AsRef<Path>>(path: P, era1_file: &Era1File) -> Result<(), E2sError> {
335 let file = File::create(path).map_err(E2sError::Io)?;
336 let mut writer = Self::new(file);
337 writer.write_era1_file(era1_file)?;
338 Ok(())
339 }
340
341 pub fn create_with_id<P: AsRef<Path>>(
344 directory: P,
345 era1_file: &Era1File,
346 ) -> Result<(), E2sError> {
347 let filename = era1_file.id.to_file_name();
348 let path = directory.as_ref().join(filename);
349 Self::create(path, era1_file)
350 }
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356 use crate::execution_types::{
357 Accumulator, BlockTuple, CompressedBody, CompressedHeader, CompressedReceipts,
358 TotalDifficulty,
359 };
360 use alloy_primitives::{B256, U256};
361 use std::io::Cursor;
362 use tempfile::tempdir;
363
364 fn create_test_block(number: BlockNumber, data_size: usize) -> BlockTuple {
366 let header_data = vec![(number % 256) as u8; data_size];
367 let header = CompressedHeader::new(header_data);
368
369 let body_data = vec![((number + 1) % 256) as u8; data_size * 2];
370 let body = CompressedBody::new(body_data);
371
372 let receipts_data = vec![((number + 2) % 256) as u8; data_size];
373 let receipts = CompressedReceipts::new(receipts_data);
374
375 let difficulty = TotalDifficulty::new(U256::from(number * 1000));
376
377 BlockTuple::new(header, body, receipts, difficulty)
378 }
379
380 fn create_test_era1_file(
382 start_block: BlockNumber,
383 block_count: usize,
384 network: &str,
385 ) -> Era1File {
386 let mut blocks = Vec::with_capacity(block_count);
388 for i in 0..block_count {
389 let block_num = start_block + i as u64;
390 blocks.push(create_test_block(block_num, 32));
391 }
392
393 let accumulator = Accumulator::new(B256::from([0xAA; 32]));
394
395 let mut offsets = Vec::with_capacity(block_count);
396 for i in 0..block_count {
397 offsets.push(i as i64 * 100);
398 }
399 let block_index = BlockIndex::new(start_block, offsets);
400 let group = Era1Group::new(blocks, accumulator, block_index);
401 let id = Era1Id::new(network, start_block, block_count as u32);
402
403 Era1File::new(group, id)
404 }
405
406 #[test]
407 fn test_era1_roundtrip_memory() -> Result<(), E2sError> {
408 let start_block = 1000;
410 let era1_file = create_test_era1_file(1000, 5, "testnet");
411
412 let mut buffer = Vec::new();
414 {
415 let mut writer = Era1Writer::new(&mut buffer);
416 writer.write_era1_file(&era1_file)?;
417 }
418
419 let mut reader = Era1Reader::new(Cursor::new(&buffer));
421 let read_era1 = reader.read("testnet".to_string())?;
422
423 assert_eq!(read_era1.id.network_name, "testnet");
425 assert_eq!(read_era1.id.start_block, 1000);
426 assert_eq!(read_era1.id.block_count, 5);
427 assert_eq!(read_era1.group.blocks.len(), 5);
428
429 assert_eq!(read_era1.group.blocks[0].total_difficulty.value, U256::from(1000 * 1000));
431 assert_eq!(read_era1.group.blocks[1].total_difficulty.value, U256::from(1001 * 1000));
432
433 assert_eq!(read_era1.group.blocks[0].header.data, vec![(start_block % 256) as u8; 32]);
435 assert_eq!(read_era1.group.blocks[0].body.data, vec![((start_block + 1) % 256) as u8; 64]);
436 assert_eq!(
437 read_era1.group.blocks[0].receipts.data,
438 vec![((start_block + 2) % 256) as u8; 32]
439 );
440
441 assert!(read_era1.contains_block(1000));
443 assert!(read_era1.contains_block(1004));
444 assert!(!read_era1.contains_block(999));
445 assert!(!read_era1.contains_block(1005));
446
447 let block_1002 = read_era1.get_block_by_number(1002);
448 assert!(block_1002.is_some());
449 assert_eq!(block_1002.unwrap().header.data, vec![((start_block + 2) % 256) as u8; 32]);
450
451 Ok(())
452 }
453
454 #[test]
455 fn test_era1_roundtrip_file() -> Result<(), E2sError> {
456 let temp_dir = tempdir().expect("Failed to create temp directory");
458 let file_path = temp_dir.path().join("test_roundtrip.era1");
459
460 let era1_file = create_test_era1_file(2000, 3, "mainnet");
462 Era1Writer::create(&file_path, &era1_file)?;
463
464 let read_era1 = Era1Reader::open(&file_path, "mainnet")?;
466
467 assert_eq!(read_era1.id.network_name, "mainnet");
469 assert_eq!(read_era1.id.start_block, 2000);
470 assert_eq!(read_era1.id.block_count, 3);
471 assert_eq!(read_era1.group.blocks.len(), 3);
472
473 for i in 0..3 {
475 let block_num = 2000 + i as u64;
476 let block = read_era1.get_block_by_number(block_num);
477 assert!(block.is_some());
478 assert_eq!(block.unwrap().header.data, vec![block_num as u8; 32]);
479 }
480
481 Ok(())
482 }
483}