1use crate::{
73 common::{
74 compression::{snappy_compress, snappy_decompress},
75 decode::DecodeCompressedRlp,
76 },
77 e2s::{error::E2sError, types::Entry},
78};
79use alloy_consensus::{Block, BlockBody, Header};
80use alloy_primitives::{B256, U256};
81use alloy_rlp::{Decodable, Encodable};
82use sha2::{Digest, Sha256};
83
84pub use crate::common::compression::SnappyRlpCodec;
85
86pub const COMPRESSED_HEADER: [u8; 2] = [0x03, 0x00];
89
90pub const COMPRESSED_BODY: [u8; 2] = [0x04, 0x00];
92
93pub const COMPRESSED_RECEIPTS: [u8; 2] = [0x05, 0x00];
95
96pub const TOTAL_DIFFICULTY: [u8; 2] = [0x06, 0x00];
98
99pub const ACCUMULATOR: [u8; 2] = [0x07, 0x00];
101
102pub const MAX_BLOCKS_PER_ERA1: usize = 8192;
104
105#[derive(Debug, Clone)]
107pub struct CompressedHeader {
108 pub data: Vec<u8>,
110}
111
112impl CompressedHeader {
113 pub const fn new(data: Vec<u8>) -> Self {
115 Self { data }
116 }
117
118 pub fn from_rlp(rlp_data: &[u8]) -> Result<Self, E2sError> {
120 Ok(Self { data: snappy_compress(rlp_data)? })
121 }
122
123 pub fn decompress(&self) -> Result<Vec<u8>, E2sError> {
125 snappy_decompress(&self.data)
126 }
127
128 pub fn to_entry(&self) -> Entry {
130 Entry::new(COMPRESSED_HEADER, self.data.clone())
131 }
132
133 pub fn from_entry(entry: &Entry) -> Result<Self, E2sError> {
135 if entry.entry_type != COMPRESSED_HEADER {
136 return Err(E2sError::Ssz(format!(
137 "Invalid entry type for CompressedHeader: expected {:02x}{:02x}, got {:02x}{:02x}",
138 COMPRESSED_HEADER[0],
139 COMPRESSED_HEADER[1],
140 entry.entry_type[0],
141 entry.entry_type[1]
142 )));
143 }
144
145 Ok(Self { data: entry.data.clone() })
146 }
147
148 pub fn decode_header(&self) -> Result<Header, E2sError> {
150 self.decode()
151 }
152
153 pub fn from_header<H: Encodable>(header: &H) -> Result<Self, E2sError> {
155 let encoder = SnappyRlpCodec::new();
156 let compressed = encoder.encode(header)?;
157 Ok(Self::new(compressed))
158 }
159}
160
161impl DecodeCompressedRlp for CompressedHeader {
162 fn decode<T: Decodable>(&self) -> Result<T, E2sError> {
163 let decoder = SnappyRlpCodec::<T>::new();
164 decoder.decode(&self.data)
165 }
166}
167
168#[derive(Debug, Clone)]
170pub struct CompressedBody {
171 pub data: Vec<u8>,
173}
174
175impl CompressedBody {
176 pub const fn new(data: Vec<u8>) -> Self {
178 Self { data }
179 }
180
181 pub fn from_rlp(rlp_data: &[u8]) -> Result<Self, E2sError> {
183 Ok(Self { data: snappy_compress(rlp_data)? })
184 }
185
186 pub fn decompress(&self) -> Result<Vec<u8>, E2sError> {
188 snappy_decompress(&self.data)
189 }
190
191 pub fn to_entry(&self) -> Entry {
193 Entry::new(COMPRESSED_BODY, self.data.clone())
194 }
195
196 pub fn from_entry(entry: &Entry) -> Result<Self, E2sError> {
198 if entry.entry_type != COMPRESSED_BODY {
199 return Err(E2sError::Ssz(format!(
200 "Invalid entry type for CompressedBody: expected {:02x}{:02x}, got {:02x}{:02x}",
201 COMPRESSED_BODY[0], COMPRESSED_BODY[1], entry.entry_type[0], entry.entry_type[1]
202 )));
203 }
204
205 Ok(Self { data: entry.data.clone() })
206 }
207
208 pub fn decode_body<T: Decodable, H: Decodable>(&self) -> Result<BlockBody<T, H>, E2sError> {
210 let decompressed = self.decompress()?;
211 Self::decode_body_from_decompressed(&decompressed)
212 }
213
214 pub fn decode_body_from_decompressed<T: Decodable, H: Decodable>(
216 data: &[u8],
217 ) -> Result<BlockBody<T, H>, E2sError> {
218 alloy_rlp::decode_exact::<BlockBody<T, H>>(data)
219 .map_err(|e| E2sError::Rlp(format!("Failed to decode RLP data: {e}")))
220 }
221
222 pub fn from_body<B: Encodable>(body: &B) -> Result<Self, E2sError> {
224 let encoder = SnappyRlpCodec::new();
225 let compressed = encoder.encode(body)?;
226 Ok(Self::new(compressed))
227 }
228}
229
230impl DecodeCompressedRlp for CompressedBody {
231 fn decode<T: Decodable>(&self) -> Result<T, E2sError> {
232 let decoder = SnappyRlpCodec::<T>::new();
233 decoder.decode(&self.data)
234 }
235}
236
237#[derive(Debug, Clone)]
239pub struct CompressedReceipts {
240 pub data: Vec<u8>,
242}
243
244impl CompressedReceipts {
245 pub const fn new(data: Vec<u8>) -> Self {
247 Self { data }
248 }
249
250 pub fn from_rlp(rlp_data: &[u8]) -> Result<Self, E2sError> {
252 Ok(Self { data: snappy_compress(rlp_data)? })
253 }
254
255 pub fn decompress(&self) -> Result<Vec<u8>, E2sError> {
257 snappy_decompress(&self.data)
258 }
259
260 pub fn to_entry(&self) -> Entry {
262 Entry::new(COMPRESSED_RECEIPTS, self.data.clone())
263 }
264
265 pub fn from_entry(entry: &Entry) -> Result<Self, E2sError> {
267 if entry.entry_type != COMPRESSED_RECEIPTS {
268 return Err(E2sError::Ssz(format!(
269 "Invalid entry type for CompressedReceipts: expected {:02x}{:02x}, got {:02x}{:02x}",
270 COMPRESSED_RECEIPTS[0], COMPRESSED_RECEIPTS[1],
271 entry.entry_type[0], entry.entry_type[1]
272 )));
273 }
274
275 Ok(Self { data: entry.data.clone() })
276 }
277
278 pub fn decode<T: Decodable>(&self) -> Result<T, E2sError> {
280 let decoder = SnappyRlpCodec::<T>::new();
281 decoder.decode(&self.data)
282 }
283
284 pub fn from_encodable<T: Encodable>(data: &T) -> Result<Self, E2sError> {
286 let encoder = SnappyRlpCodec::<T>::new();
287 let compressed = encoder.encode(data)?;
288 Ok(Self::new(compressed))
289 }
290 pub fn encode_receipts_to_rlp<T: Encodable>(receipts: &[T]) -> Result<Vec<u8>, E2sError> {
292 let mut rlp_data = Vec::new();
293 alloy_rlp::encode_list(receipts, &mut rlp_data);
294 Ok(rlp_data)
295 }
296
297 pub fn from_encodable_list<T: Encodable>(receipts: &[T]) -> Result<Self, E2sError> {
299 let rlp_data = Self::encode_receipts_to_rlp(receipts)?;
300 Self::from_rlp(&rlp_data)
301 }
302}
303
304impl DecodeCompressedRlp for CompressedReceipts {
305 fn decode<T: Decodable>(&self) -> Result<T, E2sError> {
306 let decoder = SnappyRlpCodec::<T>::new();
307 decoder.decode(&self.data)
308 }
309}
310
311#[derive(Debug, Clone)]
313pub struct TotalDifficulty {
314 pub value: U256,
316}
317
318impl TotalDifficulty {
319 pub const fn new(value: U256) -> Self {
321 Self { value }
322 }
323
324 pub fn to_entry(&self) -> Entry {
326 let data = self.value.to_le_bytes::<32>().to_vec();
328 Entry::new(TOTAL_DIFFICULTY, data)
329 }
330
331 pub fn from_entry(entry: &Entry) -> Result<Self, E2sError> {
333 if entry.entry_type != TOTAL_DIFFICULTY {
334 return Err(E2sError::Ssz(format!(
335 "Invalid entry type for TotalDifficulty: expected {:02x}{:02x}, got {:02x}{:02x}",
336 TOTAL_DIFFICULTY[0], TOTAL_DIFFICULTY[1], entry.entry_type[0], entry.entry_type[1]
337 )));
338 }
339
340 if entry.data.len() != 32 {
341 return Err(E2sError::Ssz(format!(
342 "Invalid data length for TotalDifficulty: expected 32, got {}",
343 entry.data.len()
344 )));
345 }
346
347 let value = U256::from_le_slice(&entry.data);
349
350 Ok(Self { value })
351 }
352}
353
354#[derive(Debug, Clone)]
357pub struct Accumulator {
358 pub root: B256,
360}
361
362impl Accumulator {
363 pub const fn new(root: B256) -> Self {
365 Self { root }
366 }
367
368 pub fn to_entry(&self) -> Entry {
370 Entry::new(ACCUMULATOR, self.root.to_vec())
371 }
372
373 pub fn from_entry(entry: &Entry) -> Result<Self, E2sError> {
375 if entry.entry_type != ACCUMULATOR {
376 return Err(E2sError::Ssz(format!(
377 "Invalid entry type for Accumulator: expected {:02x}{:02x}, got {:02x}{:02x}",
378 ACCUMULATOR[0], ACCUMULATOR[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 Accumulator: expected 32, got {}",
385 entry.data.len()
386 )));
387 }
388
389 let mut root = [0u8; 32];
390 root.copy_from_slice(&entry.data);
391
392 Ok(Self { root: B256::from(root) })
393 }
394
395 pub fn from_header_records(records: &[HeaderRecord]) -> Result<Self, E2sError> {
405 let capacity = MAX_BLOCKS_PER_ERA1;
406
407 if records.len() > capacity {
408 return Err(E2sError::Ssz(format!(
409 "Too many header records: got {}, max {}",
410 records.len(),
411 capacity
412 )));
413 }
414
415 let mut leaves = Vec::with_capacity(capacity);
417 for record in records {
418 let mut data = [0u8; 64];
419 data[..32].copy_from_slice(record.block_hash.as_slice());
420 data[32..].copy_from_slice(&record.total_difficulty.to_le_bytes::<32>());
421 leaves.push(<[u8; 32]>::from(Sha256::digest(data)));
422 }
423
424 leaves.resize(capacity, [0u8; 32]);
426
427 while leaves.len() > 1 {
429 let mut next_level = Vec::with_capacity(leaves.len() / 2);
430 for pair in leaves.as_chunks::<2>().0 {
431 let mut data = [0u8; 64];
432 data[..32].copy_from_slice(&pair[0]);
433 data[32..].copy_from_slice(&pair[1]);
434 next_level.push(<[u8; 32]>::from(Sha256::digest(data)));
435 }
436 leaves = next_level;
437 }
438
439 let merkle_root = leaves[0];
440
441 let mut mix = [0u8; 64];
443 mix[..32].copy_from_slice(&merkle_root);
444 let length = records.len() as u64;
445 mix[32..40].copy_from_slice(&length.to_le_bytes());
446 Ok(Self { root: B256::from(<[u8; 32]>::from(Sha256::digest(mix))) })
449 }
450}
451
452#[derive(Debug, Clone)]
456pub struct HeaderRecord {
457 pub block_hash: B256,
459 pub total_difficulty: U256,
461}
462
463#[derive(Debug, Clone)]
465pub struct BlockTuple {
466 pub header: CompressedHeader,
468
469 pub body: CompressedBody,
471
472 pub receipts: CompressedReceipts,
474
475 pub total_difficulty: TotalDifficulty,
477}
478
479impl BlockTuple {
480 pub const fn new(
482 header: CompressedHeader,
483 body: CompressedBody,
484 receipts: CompressedReceipts,
485 total_difficulty: TotalDifficulty,
486 ) -> Self {
487 Self { header, body, receipts, total_difficulty }
488 }
489
490 pub fn size(&self) -> usize {
492 self.header.to_entry().size() +
493 self.body.to_entry().size() +
494 self.receipts.to_entry().size() +
495 self.total_difficulty.to_entry().size()
496 }
497
498 pub fn to_alloy_block<T: Decodable>(&self) -> Result<Block<T>, E2sError> {
500 let header: Header = self.header.decode()?;
501 let body: BlockBody<T> = self.body.decode()?;
502
503 Ok(Block::new(header, body))
504 }
505
506 pub fn from_alloy_block<T: Encodable, R: Encodable>(
508 block: &Block<T>,
509 receipts: &R,
510 total_difficulty: U256,
511 ) -> Result<Self, E2sError> {
512 let header = CompressedHeader::from_header(&block.header)?;
513 let body = CompressedBody::from_body(&block.body)?;
514
515 let compressed_receipts = CompressedReceipts::from_encodable(receipts)?;
516
517 let difficulty = TotalDifficulty::new(total_difficulty);
518
519 Ok(Self::new(header, body, compressed_receipts, difficulty))
520 }
521}
522
523#[cfg(test)]
524mod tests {
525 use super::*;
526 use crate::test_utils::{create_header, create_test_receipt, create_test_receipts};
527 use alloy_eips::eip4895::Withdrawals;
528 use alloy_primitives::{b256, Bytes, U256};
529 use reth_ethereum_primitives::{Receipt, TxType};
530
531 #[test]
532 fn test_header_conversion_roundtrip() {
533 let header = create_header();
534
535 let compressed_header = CompressedHeader::from_header(&header).unwrap();
536
537 let decoded_header = compressed_header.decode_header().unwrap();
538
539 assert_eq!(header.number, decoded_header.number);
540 assert_eq!(header.difficulty, decoded_header.difficulty);
541 assert_eq!(header.timestamp, decoded_header.timestamp);
542 assert_eq!(header.gas_used, decoded_header.gas_used);
543 assert_eq!(header.parent_hash, decoded_header.parent_hash);
544 assert_eq!(header.base_fee_per_gas, decoded_header.base_fee_per_gas);
545 }
546
547 #[test]
548 fn test_block_body_conversion() {
549 let block_body: BlockBody<Bytes> =
550 BlockBody { transactions: vec![], ommers: vec![], withdrawals: None };
551
552 let compressed_body = CompressedBody::from_body(&block_body).unwrap();
553
554 let decoded_body: BlockBody<Bytes> = compressed_body.decode_body().unwrap();
555
556 assert_eq!(decoded_body.transactions.len(), 0);
557 assert_eq!(decoded_body.ommers.len(), 0);
558 assert_eq!(decoded_body.withdrawals, None);
559 }
560
561 #[test]
562 fn test_total_difficulty_roundtrip() {
563 let value = U256::from(123456789u64);
564
565 let total_difficulty = TotalDifficulty::new(value);
566
567 let entry = total_difficulty.to_entry();
568
569 assert_eq!(entry.entry_type, TOTAL_DIFFICULTY);
570
571 let recovered = TotalDifficulty::from_entry(&entry).unwrap();
572
573 assert_eq!(recovered.value, value);
574 }
575
576 #[test]
577 fn test_total_difficulty_ssz_le_encoding() {
578 let value = U256::ONE;
581 let td = TotalDifficulty::new(value);
582 let entry = td.to_entry();
583
584 assert_eq!(entry.data[0], 1, "First byte must be 1 (little-endian)");
586 assert_eq!(entry.data[31], 0, "Last byte must be 0 (little-endian)");
587 }
588
589 #[test]
590 fn test_compression_roundtrip() {
591 let rlp_data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
592
593 let compressed_header = CompressedHeader::from_rlp(&rlp_data).unwrap();
595 let decompressed = compressed_header.decompress().unwrap();
596 assert_eq!(decompressed, rlp_data);
597
598 let compressed_body = CompressedBody::from_rlp(&rlp_data).unwrap();
600 let decompressed = compressed_body.decompress().unwrap();
601 assert_eq!(decompressed, rlp_data);
602
603 let compressed_receipts = CompressedReceipts::from_rlp(&rlp_data).unwrap();
605 let decompressed = compressed_receipts.decompress().unwrap();
606 assert_eq!(decompressed, rlp_data);
607 }
608
609 #[test]
610 fn test_block_tuple_with_data() {
611 let header = create_header();
613
614 let transactions = vec![Bytes::from(vec![1, 2, 3, 4]), Bytes::from(vec![5, 6, 7, 8])];
615
616 let withdrawals = Some(Withdrawals(vec![]));
617
618 let block_body = BlockBody { transactions, ommers: vec![], withdrawals };
619
620 let block = Block::new(header, block_body);
621
622 let receipts: Vec<u8> = Vec::new();
623
624 let block_tuple =
625 BlockTuple::from_alloy_block(&block, &receipts, U256::from(123456u64)).unwrap();
626
627 let decoded_block: Block<Bytes> = block_tuple.to_alloy_block().unwrap();
629
630 assert_eq!(decoded_block.header.number, 100);
632 assert_eq!(decoded_block.body.transactions.len(), 2);
633 assert_eq!(decoded_block.body.transactions[0], Bytes::from(vec![1, 2, 3, 4]));
634 assert_eq!(decoded_block.body.transactions[1], Bytes::from(vec![5, 6, 7, 8]));
635 assert!(decoded_block.body.withdrawals.is_some());
636 }
637
638 #[test]
639 fn test_single_receipt_compression_roundtrip() {
640 let test_receipt = create_test_receipt(TxType::Eip1559, true, 21000, 2);
641
642 let compressed_receipts =
644 CompressedReceipts::from_encodable(&test_receipt).expect("Failed to compress receipt");
645
646 assert!(!compressed_receipts.data.is_empty());
648
649 let decoded_receipt: Receipt =
651 compressed_receipts.decode().expect("Failed to decode compressed receipt");
652
653 assert_eq!(decoded_receipt.tx_type, test_receipt.tx_type);
655 assert_eq!(decoded_receipt.success, test_receipt.success);
656 assert_eq!(decoded_receipt.cumulative_gas_used, test_receipt.cumulative_gas_used);
657 assert_eq!(decoded_receipt.logs.len(), test_receipt.logs.len());
658
659 for (original_log, decoded_log) in test_receipt.logs.iter().zip(decoded_receipt.logs.iter())
661 {
662 assert_eq!(decoded_log.address, original_log.address);
663 assert_eq!(decoded_log.data.topics(), original_log.data.topics());
664 }
665 }
666
667 #[test]
668 fn test_accumulator_from_header_records_known_vectors() {
669 let expected_empty =
672 b256!("4a8c3a07c8d23adc5bac61157555c3c784d53d9bc110c1370809bd23cd93777d");
673 let expected_single_zero =
674 b256!("81fd641249670887a731386e756a7a1538dc781b1b0bf016889045d350812817");
675 let expected_single_nonzero =
676 b256!("ada35c48d81117f4fd588554cd4c4752356336e84cb41106dea1ceb4cfac8799");
677
678 let acc_empty = Accumulator::from_header_records(&[]).unwrap();
680 assert_eq!(acc_empty.root, expected_empty);
681
682 let records = vec![HeaderRecord { block_hash: B256::ZERO, total_difficulty: U256::ZERO }];
684 let acc = Accumulator::from_header_records(&records).unwrap();
685 assert_eq!(acc.root, expected_single_zero);
686
687 let records2 = vec![HeaderRecord {
689 block_hash: B256::repeat_byte(1u8),
690 total_difficulty: U256::from(100u64),
691 }];
692 let acc2 = Accumulator::from_header_records(&records2).unwrap();
693 assert_eq!(acc2.root, expected_single_nonzero);
694 }
695
696 #[test]
697 fn test_accumulator_rejects_oversized_input() {
698 let records = vec![
699 HeaderRecord { block_hash: B256::ZERO, total_difficulty: U256::ZERO };
700 MAX_BLOCKS_PER_ERA1 + 1
701 ];
702 assert!(Accumulator::from_header_records(&records).is_err());
703 }
704
705 #[test]
706 fn test_receipt_list_compression() {
707 let receipts = create_test_receipts();
708
709 let compressed_receipts = CompressedReceipts::from_encodable_list(&receipts)
711 .expect("Failed to compress receipt list");
712
713 let decoded_receipts: Vec<Receipt> =
717 compressed_receipts.decode().expect("Failed to decode compressed receipt list");
718
719 assert_eq!(decoded_receipts.len(), receipts.len());
721
722 for (original, decoded) in receipts.iter().zip(decoded_receipts.iter()) {
723 assert_eq!(decoded.tx_type, original.tx_type);
724 assert_eq!(decoded.success, original.success);
725 assert_eq!(decoded.cumulative_gas_used, original.cumulative_gas_used);
726 assert_eq!(decoded.logs.len(), original.logs.len());
727
728 for (original_log, decoded_log) in original.logs.iter().zip(decoded.logs.iter()) {
729 assert_eq!(decoded_log.address, original_log.address);
730 assert_eq!(decoded_log.data.topics(), original_log.data.topics());
731 }
732 }
733 }
734
735 #[test]
736 fn era1_decoders_reject_trailing_rlp() {
737 let compressed = snappy_compress(&[1, 2]).unwrap();
738 let codec = SnappyRlpCodec::<u64>::new();
739 for result in [
740 codec.decode(&compressed),
741 CompressedHeader::new(compressed.clone()).decode::<u64>(),
742 CompressedBody::new(compressed.clone()).decode::<u64>(),
743 CompressedReceipts::new(compressed).decode::<u64>(),
744 ] {
745 assert_eq!(
746 result.unwrap_err().to_string(),
747 "RLP error: Trailing bytes after RLP value: 1 byte(s) remain"
748 );
749 }
750 }
751}