1use crate::{DecodedRevmBal, ExecutionOutcome};
4use alloc::{borrow::Cow, collections::BTreeMap, sync::Arc, vec::Vec};
5use alloy_consensus::{
6 transaction::{Recovered, TxHashRef},
7 BlockHeader, TxReceipt,
8};
9use alloy_eips::{eip1898::ForkBlock, BlockNumHash};
10use alloy_primitives::{map::HashSet, Address, BlockHash, BlockNumber, Log, TxHash};
11use core::{fmt, ops::RangeInclusive};
12use reth_primitives_traits::{
13 transaction::signed::SignedTransaction, Block, BlockBody, IndexedTx, NodePrimitives,
14 RecoveredBlock, SealedHeader,
15};
16use reth_trie_common::BlockTrieData;
17
18#[derive(Clone, Debug, PartialEq, Eq)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30pub struct Chain<N: NodePrimitives = reth_ethereum_primitives::EthPrimitives> {
31 blocks: BTreeMap<BlockNumber, Arc<RecoveredBlock<N::Block>>>,
33 execution_outcome: ExecutionOutcome<N::Receipt>,
40 trie_data: BTreeMap<BlockNumber, BlockTrieData>,
44 #[cfg_attr(feature = "serde", serde(skip))]
52 bals: BTreeMap<BlockNumber, Arc<DecodedRevmBal>>,
53}
54
55type ChainTxReceiptMeta<'a, N> = (
56 &'a Arc<RecoveredBlock<<N as NodePrimitives>::Block>>,
57 IndexedTx<'a, <N as NodePrimitives>::Block>,
58 &'a <N as NodePrimitives>::Receipt,
59 &'a [<N as NodePrimitives>::Receipt],
60);
61
62impl<N: NodePrimitives> Default for Chain<N> {
63 fn default() -> Self {
64 Self {
65 blocks: Default::default(),
66 execution_outcome: Default::default(),
67 trie_data: Default::default(),
68 bals: Default::default(),
69 }
70 }
71}
72
73impl<N: NodePrimitives> Chain<N> {
74 pub fn new(
80 blocks: impl IntoIterator<Item: Into<Arc<RecoveredBlock<N::Block>>>>,
81 execution_outcome: ExecutionOutcome<N::Receipt>,
82 trie_data: BTreeMap<BlockNumber, BlockTrieData>,
83 ) -> Self {
84 let blocks = blocks
85 .into_iter()
86 .map(|b| {
87 let block = b.into();
88 (block.header().number(), block)
89 })
90 .collect::<BTreeMap<_, _>>();
91 debug_assert!(!blocks.is_empty(), "Chain should have at least one block");
92
93 Self { blocks, execution_outcome, trie_data, bals: Default::default() }
94 }
95
96 pub fn from_block(
98 block: impl Into<Arc<RecoveredBlock<N::Block>>>,
99 execution_outcome: ExecutionOutcome<N::Receipt>,
100 trie_data: BlockTrieData,
101 ) -> Self {
102 let block = block.into();
103 let block_number = block.header().number();
104 Self::new([block], execution_outcome, BTreeMap::from([(block_number, trie_data)]))
105 }
106
107 pub const fn blocks(&self) -> &BTreeMap<BlockNumber, Arc<RecoveredBlock<N::Block>>> {
109 &self.blocks
110 }
111
112 pub fn into_blocks(self) -> BTreeMap<BlockNumber, Arc<RecoveredBlock<N::Block>>> {
114 self.blocks
115 }
116
117 pub fn headers(&self) -> impl Iterator<Item = SealedHeader<N::BlockHeader>> + '_ {
119 self.blocks.values().map(|block| block.clone_sealed_header())
120 }
121
122 pub const fn trie_data(&self) -> &BTreeMap<BlockNumber, BlockTrieData> {
124 &self.trie_data
125 }
126
127 pub fn trie_data_at(&self, block_number: BlockNumber) -> Option<&BlockTrieData> {
129 self.trie_data.get(&block_number)
130 }
131
132 pub fn clear_trie_data(&mut self) {
134 self.trie_data.clear();
135 }
136
137 pub const fn bals(&self) -> &BTreeMap<BlockNumber, Arc<DecodedRevmBal>> {
141 &self.bals
142 }
143
144 pub fn bal_at(&self, block_number: BlockNumber) -> Option<&Arc<DecodedRevmBal>> {
148 self.bals.get(&block_number)
149 }
150
151 pub fn insert_bal(&mut self, block_number: BlockNumber, bal: Arc<DecodedRevmBal>) {
153 debug_assert!(
154 self.blocks.contains_key(&block_number),
155 "BAL must belong to a block of this chain"
156 );
157 self.bals.insert(block_number, bal);
158 }
159
160 pub const fn execution_outcome(&self) -> &ExecutionOutcome<N::Receipt> {
162 &self.execution_outcome
163 }
164
165 pub const fn execution_outcome_mut(&mut self) -> &mut ExecutionOutcome<N::Receipt> {
167 &mut self.execution_outcome
168 }
169
170 pub fn is_empty(&self) -> bool {
172 self.blocks.is_empty()
173 }
174
175 pub fn block_number(&self, block_hash: BlockHash) -> Option<BlockNumber> {
177 self.blocks.iter().find_map(|(num, block)| (block.hash() == block_hash).then_some(*num))
178 }
179
180 pub fn recovered_block(&self, block_hash: BlockHash) -> Option<&RecoveredBlock<N::Block>> {
182 self.blocks
183 .iter()
184 .find_map(|(_num, block)| (block.hash() == block_hash).then_some(block.as_ref()))
185 }
186
187 pub fn execution_outcome_at_block(
189 &self,
190 block_number: BlockNumber,
191 ) -> Option<ExecutionOutcome<N::Receipt>> {
192 if self.tip().number() == block_number {
193 return Some(self.execution_outcome.clone())
194 }
195
196 if self.blocks.contains_key(&block_number) {
197 let mut execution_outcome = self.execution_outcome.clone();
198 execution_outcome.revert_to(block_number);
199 return Some(execution_outcome)
200 }
201 None
202 }
203
204 #[expect(clippy::type_complexity)]
209 pub fn into_inner(
210 self,
211 ) -> (
212 ChainBlocks<'static, N::Block>,
213 ExecutionOutcome<N::Receipt>,
214 BTreeMap<BlockNumber, BlockTrieData>,
215 ) {
216 (ChainBlocks { blocks: Cow::Owned(self.blocks) }, self.execution_outcome, self.trie_data)
217 }
218
219 pub const fn inner(&self) -> (ChainBlocks<'_, N::Block>, &ExecutionOutcome<N::Receipt>) {
223 (ChainBlocks { blocks: Cow::Borrowed(&self.blocks) }, &self.execution_outcome)
224 }
225
226 pub fn block_receipts_iter(&self) -> impl Iterator<Item = &Vec<N::Receipt>> + '_ {
228 self.execution_outcome.receipts().iter()
229 }
230
231 pub fn receipts_iter(&self) -> impl Iterator<Item = &N::Receipt> + '_ {
233 self.block_receipts_iter().flatten()
234 }
235
236 pub fn logs_iter(&self) -> impl Iterator<Item = &Log> + '_
238 where
239 N::Receipt: TxReceipt<Log = Log>,
240 {
241 self.receipts_iter().flat_map(|receipt| receipt.logs())
242 }
243
244 pub fn blocks_iter(&self) -> impl Iterator<Item = &Arc<RecoveredBlock<N::Block>>> + '_ {
246 self.blocks().values()
247 }
248
249 pub fn transactions_iter(&self) -> impl Iterator<Item = &N::SignedTx> + '_ {
251 self.blocks_iter().flat_map(|block| block.body().transactions())
252 }
253
254 pub fn transaction_hashes(&self) -> impl Iterator<Item = &TxHash> + '_ {
256 self.transactions_iter().map(|tx| tx.tx_hash())
257 }
258
259 pub fn transactions_recovered_iter(
261 &self,
262 ) -> impl Iterator<Item = Recovered<&N::SignedTx>> + '_ {
263 self.blocks_iter().flat_map(|block| block.transactions_recovered())
264 }
265
266 pub fn blocks_and_receipts(
268 &self,
269 ) -> impl Iterator<Item = (&Arc<RecoveredBlock<N::Block>>, &Vec<N::Receipt>)> + '_ {
270 self.blocks_iter().zip(self.block_receipts_iter())
271 }
272
273 pub fn blocks_and_bals(
278 &self,
279 ) -> impl Iterator<Item = (&Arc<RecoveredBlock<N::Block>>, &Arc<DecodedRevmBal>)> + '_ {
280 self.bals
281 .iter()
282 .filter_map(|(number, bal)| self.blocks.get(number).map(|block| (block, bal)))
283 }
284
285 pub fn find_transaction_and_receipt_by_hash(
289 &self,
290 tx_hash: TxHash,
291 ) -> Option<ChainTxReceiptMeta<'_, N>> {
292 for (block, receipts) in self.blocks_and_receipts() {
293 let Some(indexed_tx) = block.find_indexed(tx_hash) else {
294 continue;
295 };
296 let receipt = receipts.get(indexed_tx.index())?;
297 return Some((block, indexed_tx, receipt, receipts.as_slice()));
298 }
299
300 None
301 }
302
303 pub fn fork_block(&self) -> ForkBlock {
305 let first = self.first();
306 ForkBlock {
307 number: first.header().number().saturating_sub(1),
308 hash: first.header().parent_hash(),
309 }
310 }
311
312 #[track_caller]
318 pub fn first(&self) -> &RecoveredBlock<N::Block> {
319 self.blocks.first_key_value().expect("Chain should have at least one block").1
320 }
321
322 #[track_caller]
328 pub fn tip(&self) -> &RecoveredBlock<N::Block> {
329 self.blocks.last_key_value().expect("Chain should have at least one block").1
330 }
331
332 pub fn len(&self) -> usize {
334 self.blocks.len()
335 }
336
337 pub fn range(&self) -> RangeInclusive<BlockNumber> {
343 self.first().header().number()..=self.tip().header().number()
344 }
345
346 pub fn receipts_by_block_hash(&self, block_hash: BlockHash) -> Option<Vec<&N::Receipt>> {
348 let num = self.block_number(block_hash)?;
349 Some(self.execution_outcome.receipts_by_block(num).iter().collect())
350 }
351
352 pub fn receipts_with_attachment(&self) -> Vec<BlockReceipts<N::Receipt>> {
356 let mut receipt_attach = Vec::with_capacity(self.blocks().len());
357
358 self.blocks_and_receipts().for_each(|(block, receipts)| {
359 let block_num_hash = BlockNumHash::new(block.number(), block.hash());
360
361 let tx_receipts = block
362 .body()
363 .transactions()
364 .iter()
365 .zip(receipts)
366 .map(|(tx, receipt)| (*tx.tx_hash(), receipt.clone()))
367 .collect();
368
369 receipt_attach.push(BlockReceipts {
370 block: block_num_hash,
371 tx_receipts,
372 timestamp: block.timestamp(),
373 });
374 });
375
376 receipt_attach
377 }
378
379 pub fn append_block(
382 &mut self,
383 block: impl Into<Arc<RecoveredBlock<N::Block>>>,
384 execution_outcome: ExecutionOutcome<N::Receipt>,
385 trie_data: BlockTrieData,
386 ) {
387 let block = block.into();
388 let block_number = block.header().number();
389 self.blocks.insert(block_number, block);
390 self.execution_outcome.extend(execution_outcome);
391 self.trie_data.insert(block_number, trie_data);
392 }
393
394 pub fn append_chain(&mut self, other: Self) -> Result<(), Self> {
401 let chain_tip = self.tip();
402 let other_fork_block = other.fork_block();
403 if chain_tip.hash() != other_fork_block.hash {
404 return Err(other)
405 }
406
407 self.blocks.extend(other.blocks);
409 self.execution_outcome.extend(other.execution_outcome);
410 self.trie_data.extend(other.trie_data);
411 self.bals.extend(other.bals);
412
413 Ok(())
414 }
415}
416
417#[derive(Debug)]
419pub struct DisplayBlocksChain<'a, B: reth_primitives_traits::Block>(
420 pub &'a BTreeMap<BlockNumber, Arc<RecoveredBlock<B>>>,
421);
422
423impl<B: reth_primitives_traits::Block> fmt::Display for DisplayBlocksChain<'_, B> {
424 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
425 let mut list = f.debug_list();
426 let mut values = self.0.values().map(|block| block.num_hash());
427 if values.len() <= 3 {
428 list.entries(values);
429 } else {
430 list.entry(&values.next().unwrap());
431 list.entry(&format_args!("..."));
432 list.entry(&values.next_back().unwrap());
433 }
434 list.finish()
435 }
436}
437
438#[derive(Clone, Debug, Default, PartialEq, Eq)]
440pub struct ChainBlocks<'a, B: Block> {
441 blocks: Cow<'a, BTreeMap<BlockNumber, Arc<RecoveredBlock<B>>>>,
442}
443
444impl<B: Block<Body: BlockBody<Transaction: SignedTransaction>>> ChainBlocks<'_, B> {
445 #[inline]
449 pub fn into_blocks(self) -> impl Iterator<Item = Arc<RecoveredBlock<B>>> {
450 self.blocks.into_owned().into_values()
451 }
452
453 #[inline]
455 pub fn iter(&self) -> impl Iterator<Item = (&BlockNumber, &RecoveredBlock<B>)> {
456 self.blocks.iter().map(|(number, block)| (number, block.as_ref()))
457 }
458
459 #[inline]
465 pub fn tip(&self) -> &RecoveredBlock<B> {
466 self.blocks.last_key_value().expect("Chain should have at least one block").1.as_ref()
467 }
468
469 #[inline]
475 pub fn first(&self) -> &RecoveredBlock<B> {
476 self.blocks.first_key_value().expect("Chain should have at least one block").1.as_ref()
477 }
478
479 #[inline]
481 pub fn transactions(&self) -> impl Iterator<Item = &<B::Body as BlockBody>::Transaction> + '_ {
482 self.blocks.values().flat_map(|block| block.body().transactions_iter())
483 }
484
485 #[inline]
487 pub fn transactions_with_sender(
488 &self,
489 ) -> impl Iterator<Item = (&Address, &<B::Body as BlockBody>::Transaction)> + '_ {
490 self.blocks.values().flat_map(|block| block.transactions_with_sender())
491 }
492
493 #[inline]
497 pub fn transactions_ecrecovered(
498 &self,
499 ) -> impl Iterator<Item = Recovered<<B::Body as BlockBody>::Transaction>> + '_ {
500 self.transactions_with_sender().map(|(signer, tx)| tx.clone().with_signer(*signer))
501 }
502
503 #[inline]
505 pub fn transaction_hashes(&self) -> impl Iterator<Item = TxHash> + '_ {
506 self.blocks
507 .values()
508 .flat_map(|block| block.body().transactions_iter().map(|tx| *tx.tx_hash()))
509 }
510
511 #[inline]
513 pub fn transaction_hashes_vec(&self) -> Vec<TxHash> {
514 let capacity = self.blocks.values().map(|block| block.body().transactions().len()).sum();
515
516 let mut hashes = Vec::with_capacity(capacity);
517 hashes.extend(self.transaction_hashes());
518 hashes
519 }
520
521 #[inline]
523 pub fn transaction_hashes_set(&self) -> HashSet<TxHash> {
524 let capacity = self.blocks.values().map(|block| block.body().transactions().len()).sum();
525
526 let mut hashes = HashSet::with_capacity_and_hasher(capacity, Default::default());
527 hashes.extend(self.transaction_hashes());
528 hashes
529 }
530}
531
532impl<B: Block> IntoIterator for ChainBlocks<'_, B> {
533 type Item = (BlockNumber, Arc<RecoveredBlock<B>>);
534 type IntoIter = alloc::collections::btree_map::IntoIter<BlockNumber, Arc<RecoveredBlock<B>>>;
535
536 fn into_iter(self) -> Self::IntoIter {
537 self.blocks.into_owned().into_iter()
538 }
539}
540
541#[derive(Default, Clone, Debug, PartialEq, Eq)]
543pub struct BlockReceipts<T = reth_ethereum_primitives::Receipt> {
544 pub block: BlockNumHash,
546 pub tx_receipts: Vec<(TxHash, T)>,
548 pub timestamp: u64,
550}
551
552#[cfg(feature = "serde-bincode-compat")]
554pub(super) mod serde_bincode_compat {
555 use crate::serde_bincode_compat;
556 use alloc::{collections::BTreeMap, sync::Arc, vec::Vec};
557 use alloy_primitives::{Address, BlockNumber, Bytes};
558 use alloy_rlp::Decodable;
559 use core::marker::PhantomData;
560 use reth_ethereum_primitives::EthPrimitives;
561 use reth_primitives_traits::{NodePrimitives, SealedBlock};
562 use reth_trie_common::{BlockTrieData, LazyHashedPostStateSorted};
563 use serde::{Deserialize, Deserializer, Serialize, Serializer};
564 use serde_with::{DeserializeAs, SerializeAs};
565
566 #[derive(Debug, Serialize, Deserialize)]
582 #[serde(bound = "")]
583 pub struct Chain<'a, N = EthPrimitives>
584 where
585 N: NodePrimitives,
586 {
587 #[serde(skip)]
588 _phantom: PhantomData<N>,
589 blocks: BTreeMap<BlockNumber, RecoveredBlockRepr>,
590 execution_outcome: serde_bincode_compat::ExecutionOutcome<'a>,
591 #[serde(default)]
592 trie_updates: BTreeMap<
593 BlockNumber,
594 reth_trie_common::serde_bincode_compat::updates::TrieUpdatesSorted<'a>,
595 >,
596 #[serde(default)]
597 hashed_state: BTreeMap<
598 BlockNumber,
599 reth_trie_common::serde_bincode_compat::hashed_state::HashedPostStateSorted<'a>,
600 >,
601 }
602
603 #[derive(Debug, Serialize, Deserialize)]
604 struct RecoveredBlockRepr {
605 rlp: Bytes,
606 senders: Vec<Address>,
607 }
608
609 impl<'a, N> From<&'a super::Chain<N>> for Chain<'a, N>
610 where
611 N: NodePrimitives,
612 {
613 fn from(value: &'a super::Chain<N>) -> Self {
614 Self {
615 _phantom: PhantomData,
616 blocks: value
617 .blocks
618 .iter()
619 .map(|(num, recovered)| {
620 let senders = recovered.senders().to_vec();
621 let rlp = Bytes::from(alloy_rlp::encode(recovered.sealed_block()));
622 (*num, RecoveredBlockRepr { rlp, senders })
623 })
624 .collect(),
625 execution_outcome: (&value.execution_outcome).into(),
626 trie_updates: value
627 .trie_data
628 .iter()
629 .map(|(number, data)| (*number, data.trie_updates.as_ref().into()))
630 .collect(),
631 hashed_state: value
632 .trie_data
633 .iter()
634 .map(|(number, data)| (*number, data.hashed_state.get().as_ref().into()))
635 .collect(),
636 }
637 }
638 }
639
640 impl<'a, N> From<Chain<'a, N>> for super::Chain<N>
641 where
642 N: NodePrimitives,
643 {
644 fn from(value: Chain<'a, N>) -> Self {
645 use reth_primitives_traits::RecoveredBlock;
646
647 let hashed_state_map: BTreeMap<_, _> =
648 value.hashed_state.into_iter().map(|(k, v)| (k, Arc::new(v.into()))).collect();
649
650 let trie_data = value
651 .trie_updates
652 .into_iter()
653 .map(|(k, v)| {
654 let hashed_state = hashed_state_map.get(&k).cloned().unwrap_or_default();
655 (
656 k,
657 BlockTrieData {
658 hashed_state: LazyHashedPostStateSorted::ready(hashed_state),
659 trie_updates: Arc::new(v.into()),
660 },
661 )
662 })
663 .collect();
664
665 let blocks = value
666 .blocks
667 .into_iter()
668 .map(|(num, repr)| {
669 let block = N::Block::decode(&mut repr.rlp.as_ref())
670 .expect("invalid RLP for block in serde_bincode_compat");
671 let sealed = SealedBlock::new_unhashed(block);
672 (num, Arc::new(RecoveredBlock::new_sealed(sealed, repr.senders)))
673 })
674 .collect();
675
676 Self {
679 blocks,
680 execution_outcome: value.execution_outcome.into(),
681 trie_data,
682 bals: BTreeMap::new(),
683 }
684 }
685 }
686
687 impl<N> SerializeAs<super::Chain<N>> for Chain<'_, N>
688 where
689 N: NodePrimitives,
690 {
691 fn serialize_as<S>(source: &super::Chain<N>, serializer: S) -> Result<S::Ok, S::Error>
692 where
693 S: Serializer,
694 {
695 Chain::from(source).serialize(serializer)
696 }
697 }
698
699 impl<'de, N> DeserializeAs<'de, super::Chain<N>> for Chain<'de, N>
700 where
701 N: NodePrimitives,
702 {
703 fn deserialize_as<D>(deserializer: D) -> Result<super::Chain<N>, D::Error>
704 where
705 D: Deserializer<'de>,
706 {
707 Chain::deserialize(deserializer).map(Into::into)
708 }
709 }
710
711 #[cfg(test)]
712 mod tests {
713 use super::super::{serde_bincode_compat, Chain};
714 use arbitrary::Arbitrary;
715 use rand::Rng;
716 use reth_primitives_traits::RecoveredBlock;
717 use serde::{Deserialize, Serialize};
718 use serde_with::serde_as;
719
720 #[test]
721 fn test_chain_bincode_roundtrip() {
722 use alloc::collections::BTreeMap;
723
724 #[serde_as]
725 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
726 struct Data {
727 #[serde_as(as = "serde_bincode_compat::Chain")]
728 chain: Chain,
729 }
730
731 let mut bytes = [0u8; 1024];
732 rand::rng().fill(bytes.as_mut_slice());
733 let data = Data {
734 chain: Chain::new(
735 vec![RecoveredBlock::arbitrary(&mut arbitrary::Unstructured::new(&bytes))
736 .unwrap()],
737 Default::default(),
738 BTreeMap::new(),
739 ),
740 };
741
742 let encoded = bincode::serialize(&data).unwrap();
743 let decoded: Data = bincode::deserialize(&encoded).unwrap();
744 assert_eq!(decoded, data);
745 }
746 }
747}
748
749#[cfg(test)]
750mod tests {
751 use super::*;
752 use alloy_consensus::TxType;
753 use alloy_primitives::{map::HashMap, Address, B256};
754 use reth_ethereum_primitives::Receipt;
755 use revm::{database::BundleState, state::AccountInfo};
756
757 #[test]
758 fn chain_append() {
759 let block: RecoveredBlock<reth_ethereum_primitives::Block> = Default::default();
760 let block1_hash = B256::new([0x01; 32]);
761 let block2_hash = B256::new([0x02; 32]);
762 let block3_hash = B256::new([0x03; 32]);
763 let block4_hash = B256::new([0x04; 32]);
764
765 let mut block1 = block.clone();
766 let mut block2 = block.clone();
767 let mut block3 = block.clone();
768 let mut block4 = block;
769
770 block1.set_hash(block1_hash);
771 block2.set_hash(block2_hash);
772 block3.set_hash(block3_hash);
773 block4.set_hash(block4_hash);
774
775 block3.set_parent_hash(block2_hash);
776
777 let mut chain1: Chain = Chain {
778 blocks: BTreeMap::from([(1, Arc::new(block1)), (2, Arc::new(block2))]),
779 ..Default::default()
780 };
781
782 let chain2 = Chain {
783 blocks: BTreeMap::from([(3, Arc::new(block3)), (4, Arc::new(block4))]),
784 ..Default::default()
785 };
786
787 assert!(chain1.append_chain(chain2.clone()).is_ok());
788
789 assert!(chain1.append_chain(chain2).is_err());
791 }
792
793 #[test]
794 fn test_number_split() {
795 let execution_outcome1: ExecutionOutcome = ExecutionOutcome::new(
796 BundleState::new(
797 vec![(
798 Address::new([2; 20]),
799 None,
800 Some(AccountInfo::default()),
801 HashMap::default(),
802 )],
803 vec![vec![(Address::new([2; 20]), None, vec![])]],
804 vec![],
805 ),
806 vec![vec![]],
807 1,
808 vec![],
809 );
810
811 let execution_outcome2 = ExecutionOutcome::new(
812 BundleState::new(
813 vec![(
814 Address::new([3; 20]),
815 None,
816 Some(AccountInfo::default()),
817 HashMap::default(),
818 )],
819 vec![vec![(Address::new([3; 20]), None, vec![])]],
820 vec![],
821 ),
822 vec![vec![]],
823 2,
824 vec![],
825 );
826
827 let mut block1: RecoveredBlock<reth_ethereum_primitives::Block> = Default::default();
828 let block1_hash = B256::new([15; 32]);
829 block1.set_block_number(1);
830 block1.set_hash(block1_hash);
831 block1.push_sender(Address::new([4; 20]));
832
833 let mut block2: RecoveredBlock<reth_ethereum_primitives::Block> = Default::default();
834 let block2_hash = B256::new([16; 32]);
835 block2.set_block_number(2);
836 block2.set_hash(block2_hash);
837 block2.push_sender(Address::new([4; 20]));
838
839 let mut block_state_extended = execution_outcome1;
840 block_state_extended.extend(execution_outcome2);
841
842 let chain: Chain =
843 Chain::new(vec![block1.clone(), block2.clone()], block_state_extended, BTreeMap::new());
844
845 assert_eq!(
847 chain.execution_outcome_at_block(block2.number),
848 Some(chain.execution_outcome.clone())
849 );
850 assert_eq!(chain.execution_outcome_at_block(100), None);
852 }
853
854 #[test]
855 fn receipts_by_block_hash() {
856 let block: RecoveredBlock<reth_ethereum_primitives::Block> = Default::default();
858
859 let block1_hash = B256::new([0x01; 32]);
861 let block2_hash = B256::new([0x02; 32]);
862
863 let mut block1 = block.clone();
865 let mut block2 = block;
866
867 block1.set_hash(block1_hash);
869 block2.set_hash(block2_hash);
870
871 let receipt1 = Receipt {
873 tx_type: TxType::Legacy,
874 cumulative_gas_used: 46913,
875 logs: vec![],
876 success: true,
877 };
878
879 let receipt2 = Receipt {
881 tx_type: TxType::Legacy,
882 cumulative_gas_used: 1325345,
883 logs: vec![],
884 success: true,
885 };
886
887 let receipts = vec![vec![receipt1.clone()], vec![receipt2]];
889
890 let execution_outcome = ExecutionOutcome {
893 bundle: Default::default(),
894 receipts,
895 requests: vec![],
896 first_block: 10,
897 };
898
899 let chain: Chain = Chain {
902 blocks: BTreeMap::from([(10, Arc::new(block1)), (11, Arc::new(block2))]),
903 execution_outcome: execution_outcome.clone(),
904 ..Default::default()
905 };
906
907 assert_eq!(chain.receipts_by_block_hash(block1_hash), Some(vec![&receipt1]));
909
910 let execution_outcome1 = ExecutionOutcome {
912 bundle: Default::default(),
913 receipts: vec![vec![receipt1]],
914 requests: vec![],
915 first_block: 10,
916 };
917
918 assert_eq!(chain.execution_outcome_at_block(10), Some(execution_outcome1));
920
921 assert_eq!(chain.execution_outcome_at_block(11), Some(execution_outcome));
923 }
924}