reth_era_utils/export/
era1.rs1use super::{ChunkAccumulator, EraBlockWriter, ExportBlock};
4use crate::Era1;
5use alloy_consensus::{BlockHeader, TxReceipt};
6use alloy_primitives::{B256, U256};
7use alloy_rlp::Encodable;
8use eyre::{eyre, Result};
9use reth_era::{
10 common::file_ops::{EraFileId, StreamWriter},
11 e2s::types::{Header, IndexEntry},
12 era1::{
13 file::Era1Writer,
14 types::{
15 execution::{
16 Accumulator, BlockTuple, CompressedBody, CompressedHeader, CompressedReceipts,
17 HeaderRecord, TotalDifficulty, MAX_BLOCKS_PER_ERA1,
18 },
19 group::{BlockIndex, Era1Id},
20 },
21 },
22};
23use reth_primitives_traits::Receipt;
24use std::path::{Path, PathBuf};
25
26impl EraBlockWriter for Era1 {
27 fn write_file<H, B, R>(
28 network: &str,
29 max_blocks_per_file: u64,
30 blocks: &[ExportBlock<H, B, R>],
31 dir: &Path,
32 ) -> Result<PathBuf>
33 where
34 H: BlockHeader + Encodable,
35 B: Encodable,
36 R: Receipt,
37 {
38 let accumulator = super::accumulator::<Accumulator, _, _, _>(blocks)?;
39 let file_path = dir.join(file_name(network, max_blocks_per_file, blocks, &accumulator));
40
41 let mut writer = Era1Writer::new(std::fs::File::create(&file_path)?);
42 writer.write_version()?;
43
44 let mut offsets = Vec::<i64>::with_capacity(blocks.len());
47 let mut position = Header::SIZE as i64; for block in blocks {
49 let tuple = compress_block(block)?;
50 offsets.push(position);
51 position += tuple.size() as i64;
52 writer.write_block(&tuple)?;
53 }
54
55 let index_position = position + accumulator.to_entry().size() as i64;
56 let relative: Vec<i64> = offsets.iter().map(|&abs| abs - index_position).collect();
57
58 writer.write_accumulator(&accumulator)?;
59 writer.write_block_index(&BlockIndex::new(blocks[0].header.number(), relative))?;
60 writer.flush()?;
61
62 Ok(file_path)
63 }
64}
65
66fn compress_block<H, B, R>(block: &ExportBlock<H, B, R>) -> Result<BlockTuple>
69where
70 H: BlockHeader + Encodable,
71 B: Encodable,
72 R: Receipt,
73{
74 let header = CompressedHeader::from_header(&block.header)?;
75 let body = CompressedBody::from_body(&block.body)?;
76 let receipts_with_bloom: Vec<_> =
77 block.receipts.iter().map(|r| TxReceipt::with_bloom_ref(r)).collect();
78 let receipts = CompressedReceipts::from_encodable_list(&receipts_with_bloom)
79 .map_err(|e| eyre!("Failed to compress receipts: {e}"))?;
80
81 Ok(BlockTuple::new(header, body, receipts, TotalDifficulty::new(block.total_difficulty)))
82}
83
84impl ChunkAccumulator for Accumulator {
85 fn from_pairs(records: &[(B256, U256)]) -> Result<Self> {
86 let records: Vec<HeaderRecord> = records
87 .iter()
88 .map(|&(block_hash, total_difficulty)| HeaderRecord { block_hash, total_difficulty })
89 .collect();
90 Self::from_header_records(&records).map_err(|e| eyre!("Failed to compute accumulator: {e}"))
91 }
92}
93
94fn file_name<H: BlockHeader, B, R>(
96 network: &str,
97 max_blocks_per_file: u64,
98 blocks: &[ExportBlock<H, B, R>],
99 accumulator: &Accumulator,
100) -> String {
101 let file_hash = super::short_hash(accumulator.root);
102 let id =
103 Era1Id::new(network, blocks[0].header.number(), blocks.len() as u32).with_hash(file_hash);
104 if max_blocks_per_file == MAX_BLOCKS_PER_ERA1 as u64 {
106 id.to_file_name()
107 } else {
108 id.with_era_count().to_file_name()
109 }
110}