1use crate::e2s_types::{E2sError, Entry};
14use alloy_consensus::{Block, BlockBody, Header};
15use alloy_primitives::{B256, U256};
16use alloy_rlp::{Decodable, Encodable};
17use snap::{read::FrameDecoder, write::FrameEncoder};
18use std::{
19 io::{Read, Write},
20 marker::PhantomData,
21};
22
23pub const COMPRESSED_HEADER: [u8; 2] = [0x03, 0x00];
26
27pub const COMPRESSED_BODY: [u8; 2] = [0x04, 0x00];
29
30pub const COMPRESSED_RECEIPTS: [u8; 2] = [0x05, 0x00];
32
33pub const TOTAL_DIFFICULTY: [u8; 2] = [0x06, 0x00];
35
36pub const ACCUMULATOR: [u8; 2] = [0x07, 0x00];
38
39pub const MAX_BLOCKS_PER_ERA1: usize = 8192;
41
42#[derive(Debug, Clone, Default)]
44pub struct SnappyRlpCodec<T> {
45 _phantom: PhantomData<T>,
46}
47
48impl<T> SnappyRlpCodec<T> {
49 pub const fn new() -> Self {
51 Self { _phantom: PhantomData }
52 }
53}
54
55impl<T: Decodable> SnappyRlpCodec<T> {
56 pub fn decode(&self, compressed_data: &[u8]) -> Result<T, E2sError> {
58 let mut decoder = FrameDecoder::new(compressed_data);
59 let mut decompressed = Vec::new();
60 Read::read_to_end(&mut decoder, &mut decompressed).map_err(|e| {
61 E2sError::SnappyDecompression(format!("Failed to decompress data: {e}"))
62 })?;
63
64 let mut slice = decompressed.as_slice();
65 T::decode(&mut slice).map_err(|e| E2sError::Rlp(format!("Failed to decode RLP data: {e}")))
66 }
67}
68
69impl<T: Encodable> SnappyRlpCodec<T> {
70 pub fn encode(&self, data: &T) -> Result<Vec<u8>, E2sError> {
72 let mut rlp_data = Vec::new();
73 data.encode(&mut rlp_data);
74
75 let mut compressed = Vec::new();
76 {
77 let mut encoder = FrameEncoder::new(&mut compressed);
78
79 Write::write_all(&mut encoder, &rlp_data).map_err(|e| {
80 E2sError::SnappyCompression(format!("Failed to compress data: {e}"))
81 })?;
82
83 encoder.flush().map_err(|e| {
84 E2sError::SnappyCompression(format!("Failed to flush encoder: {e}"))
85 })?;
86 }
87
88 Ok(compressed)
89 }
90}
91
92#[derive(Debug, Clone)]
94pub struct CompressedHeader {
95 pub data: Vec<u8>,
97}
98
99pub trait DecodeCompressed {
101 fn decode<T: Decodable>(&self) -> Result<T, E2sError>;
103}
104
105impl CompressedHeader {
106 pub const fn new(data: Vec<u8>) -> Self {
108 Self { data }
109 }
110
111 pub fn from_rlp(rlp_data: &[u8]) -> Result<Self, E2sError> {
113 let mut compressed = Vec::new();
114 {
115 let mut encoder = FrameEncoder::new(&mut compressed);
116
117 Write::write_all(&mut encoder, rlp_data).map_err(|e| {
118 E2sError::SnappyCompression(format!("Failed to compress header: {e}"))
119 })?;
120
121 encoder.flush().map_err(|e| {
122 E2sError::SnappyCompression(format!("Failed to flush encoder: {e}"))
123 })?;
124 }
125 Ok(Self { data: compressed })
126 }
127
128 pub fn decompress(&self) -> Result<Vec<u8>, E2sError> {
130 let mut decoder = FrameDecoder::new(self.data.as_slice());
131 let mut decompressed = Vec::new();
132 Read::read_to_end(&mut decoder, &mut decompressed).map_err(|e| {
133 E2sError::SnappyDecompression(format!("Failed to decompress header: {e}"))
134 })?;
135
136 Ok(decompressed)
137 }
138
139 pub fn to_entry(&self) -> Entry {
141 Entry::new(COMPRESSED_HEADER, self.data.clone())
142 }
143
144 pub fn from_entry(entry: &Entry) -> Result<Self, E2sError> {
146 if entry.entry_type != COMPRESSED_HEADER {
147 return Err(E2sError::Ssz(format!(
148 "Invalid entry type for CompressedHeader: expected {:02x}{:02x}, got {:02x}{:02x}",
149 COMPRESSED_HEADER[0],
150 COMPRESSED_HEADER[1],
151 entry.entry_type[0],
152 entry.entry_type[1]
153 )));
154 }
155
156 Ok(Self { data: entry.data.clone() })
157 }
158
159 pub fn decode_header(&self) -> Result<Header, E2sError> {
161 self.decode()
162 }
163
164 pub fn from_header(header: &Header) -> Result<Self, E2sError> {
166 let encoder = SnappyRlpCodec::<Header>::new();
167 let compressed = encoder.encode(header)?;
168 Ok(Self::new(compressed))
169 }
170}
171
172impl DecodeCompressed for CompressedHeader {
173 fn decode<T: Decodable>(&self) -> Result<T, E2sError> {
174 let decoder = SnappyRlpCodec::<T>::new();
175 decoder.decode(&self.data)
176 }
177}
178
179#[derive(Debug, Clone)]
181pub struct CompressedBody {
182 pub data: Vec<u8>,
184}
185
186impl CompressedBody {
187 pub const fn new(data: Vec<u8>) -> Self {
189 Self { data }
190 }
191
192 pub fn from_rlp(rlp_data: &[u8]) -> Result<Self, E2sError> {
194 let mut compressed = Vec::new();
195 {
196 let mut encoder = FrameEncoder::new(&mut compressed);
197
198 Write::write_all(&mut encoder, rlp_data).map_err(|e| {
199 E2sError::SnappyCompression(format!("Failed to compress header: {e}"))
200 })?;
201
202 encoder.flush().map_err(|e| {
203 E2sError::SnappyCompression(format!("Failed to flush encoder: {e}"))
204 })?;
205 }
206 Ok(Self { data: compressed })
207 }
208
209 pub fn decompress(&self) -> Result<Vec<u8>, E2sError> {
211 let mut decoder = FrameDecoder::new(self.data.as_slice());
212 let mut decompressed = Vec::new();
213 Read::read_to_end(&mut decoder, &mut decompressed).map_err(|e| {
214 E2sError::SnappyDecompression(format!("Failed to decompress body: {e}"))
215 })?;
216
217 Ok(decompressed)
218 }
219
220 pub fn to_entry(&self) -> Entry {
222 Entry::new(COMPRESSED_BODY, self.data.clone())
223 }
224
225 pub fn from_entry(entry: &Entry) -> Result<Self, E2sError> {
227 if entry.entry_type != COMPRESSED_BODY {
228 return Err(E2sError::Ssz(format!(
229 "Invalid entry type for CompressedBody: expected {:02x}{:02x}, got {:02x}{:02x}",
230 COMPRESSED_BODY[0], COMPRESSED_BODY[1], entry.entry_type[0], entry.entry_type[1]
231 )));
232 }
233
234 Ok(Self { data: entry.data.clone() })
235 }
236
237 pub fn decode_body<T: Decodable, H: Decodable>(&self) -> Result<BlockBody<T, H>, E2sError> {
239 let decompressed = self.decompress()?;
240 Self::decode_body_from_decompressed(&decompressed)
241 }
242
243 pub fn decode_body_from_decompressed<T: Decodable, H: Decodable>(
245 data: &[u8],
246 ) -> Result<BlockBody<T, H>, E2sError> {
247 alloy_rlp::decode_exact::<BlockBody<T, H>>(data)
248 .map_err(|e| E2sError::Rlp(format!("Failed to decode RLP data: {e}")))
249 }
250
251 pub fn from_body<T: Encodable, H: Encodable>(body: &BlockBody<T, H>) -> Result<Self, E2sError> {
253 let encoder = SnappyRlpCodec::<BlockBody<T, H>>::new();
254 let compressed = encoder.encode(body)?;
255 Ok(Self::new(compressed))
256 }
257}
258
259impl DecodeCompressed for CompressedBody {
260 fn decode<T: Decodable>(&self) -> Result<T, E2sError> {
261 let decoder = SnappyRlpCodec::<T>::new();
262 decoder.decode(&self.data)
263 }
264}
265
266#[derive(Debug, Clone)]
268pub struct CompressedReceipts {
269 pub data: Vec<u8>,
271}
272
273impl CompressedReceipts {
274 pub const fn new(data: Vec<u8>) -> Self {
276 Self { data }
277 }
278
279 pub fn from_rlp(rlp_data: &[u8]) -> Result<Self, E2sError> {
281 let mut compressed = Vec::new();
282 {
283 let mut encoder = FrameEncoder::new(&mut compressed);
284
285 Write::write_all(&mut encoder, rlp_data).map_err(|e| {
286 E2sError::SnappyCompression(format!("Failed to compress header: {e}"))
287 })?;
288
289 encoder.flush().map_err(|e| {
290 E2sError::SnappyCompression(format!("Failed to flush encoder: {e}"))
291 })?;
292 }
293 Ok(Self { data: compressed })
294 }
295 pub fn decompress(&self) -> Result<Vec<u8>, E2sError> {
297 let mut decoder = FrameDecoder::new(self.data.as_slice());
298 let mut decompressed = Vec::new();
299 Read::read_to_end(&mut decoder, &mut decompressed).map_err(|e| {
300 E2sError::SnappyDecompression(format!("Failed to decompress receipts: {e}"))
301 })?;
302
303 Ok(decompressed)
304 }
305
306 pub fn to_entry(&self) -> Entry {
308 Entry::new(COMPRESSED_RECEIPTS, self.data.clone())
309 }
310
311 pub fn from_entry(entry: &Entry) -> Result<Self, E2sError> {
313 if entry.entry_type != COMPRESSED_RECEIPTS {
314 return Err(E2sError::Ssz(format!(
315 "Invalid entry type for CompressedReceipts: expected {:02x}{:02x}, got {:02x}{:02x}",
316 COMPRESSED_RECEIPTS[0], COMPRESSED_RECEIPTS[1],
317 entry.entry_type[0], entry.entry_type[1]
318 )));
319 }
320
321 Ok(Self { data: entry.data.clone() })
322 }
323
324 pub fn decode<T: Decodable>(&self) -> Result<T, E2sError> {
326 let decoder = SnappyRlpCodec::<T>::new();
327 decoder.decode(&self.data)
328 }
329
330 pub fn from_encodable<T: Encodable>(data: &T) -> Result<Self, E2sError> {
332 let encoder = SnappyRlpCodec::<T>::new();
333 let compressed = encoder.encode(data)?;
334 Ok(Self::new(compressed))
335 }
336}
337
338impl DecodeCompressed for CompressedReceipts {
339 fn decode<T: Decodable>(&self) -> Result<T, E2sError> {
340 let decoder = SnappyRlpCodec::<T>::new();
341 decoder.decode(&self.data)
342 }
343}
344
345#[derive(Debug, Clone)]
347pub struct TotalDifficulty {
348 pub value: U256,
350}
351
352impl TotalDifficulty {
353 pub const fn new(value: U256) -> Self {
355 Self { value }
356 }
357
358 pub fn to_entry(&self) -> Entry {
360 let mut data = [0u8; 32];
361
362 let be_bytes = self.value.to_be_bytes_vec();
363
364 if be_bytes.len() <= 32 {
365 data[32 - be_bytes.len()..].copy_from_slice(&be_bytes);
366 } else {
367 data.copy_from_slice(&be_bytes[be_bytes.len() - 32..]);
368 }
369
370 Entry::new(TOTAL_DIFFICULTY, data.to_vec())
371 }
372
373 pub fn from_entry(entry: &Entry) -> Result<Self, E2sError> {
375 if entry.entry_type != TOTAL_DIFFICULTY {
376 return Err(E2sError::Ssz(format!(
377 "Invalid entry type for TotalDifficulty: expected {:02x}{:02x}, got {:02x}{:02x}",
378 TOTAL_DIFFICULTY[0], TOTAL_DIFFICULTY[1], entry.entry_type[0], entry.entry_type[1]
379 )));
380 }
381
382 if entry.data.len() != 32 {
383 return Err(E2sError::Ssz(format!(
384 "Invalid data length for TotalDifficulty: expected 32, got {}",
385 entry.data.len()
386 )));
387 }
388
389 let value = U256::from_be_slice(&entry.data);
391
392 Ok(Self { value })
393 }
394}
395
396#[derive(Debug, Clone)]
399pub struct Accumulator {
400 pub root: B256,
402}
403
404impl Accumulator {
405 pub const fn new(root: B256) -> Self {
407 Self { root }
408 }
409
410 pub fn to_entry(&self) -> Entry {
412 Entry::new(ACCUMULATOR, self.root.to_vec())
413 }
414
415 pub fn from_entry(entry: &Entry) -> Result<Self, E2sError> {
417 if entry.entry_type != ACCUMULATOR {
418 return Err(E2sError::Ssz(format!(
419 "Invalid entry type for Accumulator: expected {:02x}{:02x}, got {:02x}{:02x}",
420 ACCUMULATOR[0], ACCUMULATOR[1], entry.entry_type[0], entry.entry_type[1]
421 )));
422 }
423
424 if entry.data.len() != 32 {
425 return Err(E2sError::Ssz(format!(
426 "Invalid data length for Accumulator: expected 32, got {}",
427 entry.data.len()
428 )));
429 }
430
431 let mut root = [0u8; 32];
432 root.copy_from_slice(&entry.data);
433
434 Ok(Self { root: B256::from(root) })
435 }
436}
437
438#[derive(Debug, Clone)]
440pub struct BlockTuple {
441 pub header: CompressedHeader,
443
444 pub body: CompressedBody,
446
447 pub receipts: CompressedReceipts,
449
450 pub total_difficulty: TotalDifficulty,
452}
453
454impl BlockTuple {
455 pub const fn new(
457 header: CompressedHeader,
458 body: CompressedBody,
459 receipts: CompressedReceipts,
460 total_difficulty: TotalDifficulty,
461 ) -> Self {
462 Self { header, body, receipts, total_difficulty }
463 }
464
465 pub fn to_alloy_block<T: Decodable>(&self) -> Result<Block<T>, E2sError> {
467 let header: Header = self.header.decode()?;
468 let body: BlockBody<T> = self.body.decode()?;
469
470 Ok(Block::new(header, body))
471 }
472
473 pub fn from_alloy_block<T: Encodable, R: Encodable>(
475 block: &Block<T>,
476 receipts: &R,
477 total_difficulty: U256,
478 ) -> Result<Self, E2sError> {
479 let header = CompressedHeader::from_header(&block.header)?;
480 let body = CompressedBody::from_body(&block.body)?;
481
482 let compressed_receipts = CompressedReceipts::from_encodable(receipts)?;
483
484 let difficulty = TotalDifficulty::new(total_difficulty);
485
486 Ok(Self::new(header, body, compressed_receipts, difficulty))
487 }
488}
489
490#[cfg(test)]
491mod tests {
492 use super::*;
493 use alloy_eips::eip4895::Withdrawals;
494 use alloy_primitives::{Address, Bytes, B64};
495
496 #[test]
497 fn test_header_conversion_roundtrip() {
498 let header = Header {
499 parent_hash: B256::default(),
500 ommers_hash: B256::default(),
501 beneficiary: Address::default(),
502 state_root: B256::default(),
503 transactions_root: B256::default(),
504 receipts_root: B256::default(),
505 logs_bloom: Default::default(),
506 difficulty: U256::from(123456u64),
507 number: 100,
508 gas_limit: 5000000,
509 gas_used: 21000,
510 timestamp: 1609459200,
511 extra_data: Bytes::default(),
512 mix_hash: B256::default(),
513 nonce: B64::default(),
514 base_fee_per_gas: Some(10),
515 withdrawals_root: None,
516 blob_gas_used: None,
517 excess_blob_gas: None,
518 parent_beacon_block_root: None,
519 requests_hash: None,
520 };
521
522 let compressed_header = CompressedHeader::from_header(&header).unwrap();
523
524 let decoded_header = compressed_header.decode_header().unwrap();
525
526 assert_eq!(header.number, decoded_header.number);
527 assert_eq!(header.difficulty, decoded_header.difficulty);
528 assert_eq!(header.timestamp, decoded_header.timestamp);
529 assert_eq!(header.gas_used, decoded_header.gas_used);
530 assert_eq!(header.parent_hash, decoded_header.parent_hash);
531 assert_eq!(header.base_fee_per_gas, decoded_header.base_fee_per_gas);
532 }
533
534 #[test]
535 fn test_block_body_conversion() {
536 let block_body: BlockBody<Bytes> =
537 BlockBody { transactions: vec![], ommers: vec![], withdrawals: None };
538
539 let compressed_body = CompressedBody::from_body(&block_body).unwrap();
540
541 let decoded_body: BlockBody<Bytes> = compressed_body.decode_body().unwrap();
542
543 assert_eq!(decoded_body.transactions.len(), 0);
544 assert_eq!(decoded_body.ommers.len(), 0);
545 assert_eq!(decoded_body.withdrawals, None);
546 }
547
548 #[test]
549 fn test_total_difficulty_roundtrip() {
550 let value = U256::from(123456789u64);
551
552 let total_difficulty = TotalDifficulty::new(value);
553
554 let entry = total_difficulty.to_entry();
555
556 assert_eq!(entry.entry_type, TOTAL_DIFFICULTY);
557
558 let recovered = TotalDifficulty::from_entry(&entry).unwrap();
559
560 assert_eq!(recovered.value, value);
561 }
562
563 #[test]
564 fn test_compression_roundtrip() {
565 let rlp_data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
566
567 let compressed_header = CompressedHeader::from_rlp(&rlp_data).unwrap();
569 let decompressed = compressed_header.decompress().unwrap();
570 assert_eq!(decompressed, rlp_data);
571
572 let compressed_body = CompressedBody::from_rlp(&rlp_data).unwrap();
574 let decompressed = compressed_body.decompress().unwrap();
575 assert_eq!(decompressed, rlp_data);
576
577 let compressed_receipts = CompressedReceipts::from_rlp(&rlp_data).unwrap();
579 let decompressed = compressed_receipts.decompress().unwrap();
580 assert_eq!(decompressed, rlp_data);
581 }
582
583 #[test]
584 fn test_block_tuple_with_data() {
585 let header = Header {
587 parent_hash: B256::default(),
588 ommers_hash: B256::default(),
589 beneficiary: Address::default(),
590 state_root: B256::default(),
591 transactions_root: B256::default(),
592 receipts_root: B256::default(),
593 logs_bloom: Default::default(),
594 difficulty: U256::from(123456u64),
595 number: 100,
596 gas_limit: 5000000,
597 gas_used: 21000,
598 timestamp: 1609459200,
599 extra_data: Bytes::default(),
600 mix_hash: B256::default(),
601 nonce: B64::default(),
602 base_fee_per_gas: Some(10),
603 withdrawals_root: Some(B256::default()),
604 blob_gas_used: None,
605 excess_blob_gas: None,
606 parent_beacon_block_root: None,
607 requests_hash: None,
608 };
609
610 let transactions = vec![Bytes::from(vec![1, 2, 3, 4]), Bytes::from(vec![5, 6, 7, 8])];
611
612 let withdrawals = Some(Withdrawals(vec![]));
613
614 let block_body = BlockBody { transactions, ommers: vec![], withdrawals };
615
616 let block = Block::new(header, block_body);
617
618 let receipts: Vec<u8> = Vec::new();
619
620 let block_tuple =
621 BlockTuple::from_alloy_block(&block, &receipts, U256::from(123456u64)).unwrap();
622
623 let decoded_block: Block<Bytes> = block_tuple.to_alloy_block().unwrap();
625
626 assert_eq!(decoded_block.header.number, 100);
628 assert_eq!(decoded_block.body.transactions.len(), 2);
629 assert_eq!(decoded_block.body.transactions[0], Bytes::from(vec![1, 2, 3, 4]));
630 assert_eq!(decoded_block.body.transactions[1], Bytes::from(vec![5, 6, 7, 8]));
631 assert!(decoded_block.body.withdrawals.is_some());
632 }
633}