1use crate::ExecutionOutcome;
4use alloc::{borrow::Cow, collections::BTreeMap, vec::Vec};
5use alloy_consensus::{transaction::Recovered, BlockHeader};
6use alloy_eips::{eip1898::ForkBlock, eip2718::Encodable2718, BlockNumHash};
7use alloy_primitives::{Address, BlockHash, BlockNumber, TxHash};
8use core::{fmt, ops::RangeInclusive};
9use reth_primitives_traits::{
10 transaction::signed::SignedTransaction, Block, BlockBody, NodePrimitives, RecoveredBlock,
11 SealedHeader,
12};
13use reth_trie_common::updates::TrieUpdates;
14use revm_database::BundleState;
15
16#[derive(Clone, Debug, PartialEq, Eq)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28pub struct Chain<N: NodePrimitives = reth_ethereum_primitives::EthPrimitives> {
29 blocks: BTreeMap<BlockNumber, RecoveredBlock<N::Block>>,
31 execution_outcome: ExecutionOutcome<N::Receipt>,
38 trie_updates: Option<TrieUpdates>,
42}
43
44impl<N: NodePrimitives> Default for Chain<N> {
45 fn default() -> Self {
46 Self {
47 blocks: Default::default(),
48 execution_outcome: Default::default(),
49 trie_updates: Default::default(),
50 }
51 }
52}
53
54impl<N: NodePrimitives> Chain<N> {
55 pub fn new(
61 blocks: impl IntoIterator<Item = RecoveredBlock<N::Block>>,
62 execution_outcome: ExecutionOutcome<N::Receipt>,
63 trie_updates: Option<TrieUpdates>,
64 ) -> Self {
65 let blocks =
66 blocks.into_iter().map(|b| (b.header().number(), b)).collect::<BTreeMap<_, _>>();
67 debug_assert!(!blocks.is_empty(), "Chain should have at least one block");
68
69 Self { blocks, execution_outcome, trie_updates }
70 }
71
72 pub fn from_block(
74 block: RecoveredBlock<N::Block>,
75 execution_outcome: ExecutionOutcome<N::Receipt>,
76 trie_updates: Option<TrieUpdates>,
77 ) -> Self {
78 Self::new([block], execution_outcome, trie_updates)
79 }
80
81 pub const fn blocks(&self) -> &BTreeMap<BlockNumber, RecoveredBlock<N::Block>> {
83 &self.blocks
84 }
85
86 pub fn into_blocks(self) -> BTreeMap<BlockNumber, RecoveredBlock<N::Block>> {
88 self.blocks
89 }
90
91 pub fn headers(&self) -> impl Iterator<Item = SealedHeader<N::BlockHeader>> + '_ {
93 self.blocks.values().map(|block| block.clone_sealed_header())
94 }
95
96 pub const fn trie_updates(&self) -> Option<&TrieUpdates> {
98 self.trie_updates.as_ref()
99 }
100
101 pub fn clear_trie_updates(&mut self) {
103 self.trie_updates.take();
104 }
105
106 pub const fn execution_outcome(&self) -> &ExecutionOutcome<N::Receipt> {
108 &self.execution_outcome
109 }
110
111 pub fn execution_outcome_mut(&mut self) -> &mut ExecutionOutcome<N::Receipt> {
113 &mut self.execution_outcome
114 }
115
116 pub fn prepend_state(&mut self, state: BundleState) {
118 self.execution_outcome.prepend_state(state);
119 self.trie_updates.take(); }
121
122 pub fn is_empty(&self) -> bool {
124 self.blocks.is_empty()
125 }
126
127 pub fn block_number(&self, block_hash: BlockHash) -> Option<BlockNumber> {
129 self.blocks.iter().find_map(|(num, block)| (block.hash() == block_hash).then_some(*num))
130 }
131
132 pub fn recovered_block(&self, block_hash: BlockHash) -> Option<&RecoveredBlock<N::Block>> {
134 self.blocks.iter().find_map(|(_num, block)| (block.hash() == block_hash).then_some(block))
135 }
136
137 pub fn execution_outcome_at_block(
139 &self,
140 block_number: BlockNumber,
141 ) -> Option<ExecutionOutcome<N::Receipt>> {
142 if self.tip().number() == block_number {
143 return Some(self.execution_outcome.clone())
144 }
145
146 if self.blocks.contains_key(&block_number) {
147 let mut execution_outcome = self.execution_outcome.clone();
148 execution_outcome.revert_to(block_number);
149 return Some(execution_outcome)
150 }
151 None
152 }
153
154 pub fn into_inner(
159 self,
160 ) -> (ChainBlocks<'static, N::Block>, ExecutionOutcome<N::Receipt>, Option<TrieUpdates>) {
161 (ChainBlocks { blocks: Cow::Owned(self.blocks) }, self.execution_outcome, self.trie_updates)
162 }
163
164 pub const fn inner(&self) -> (ChainBlocks<'_, N::Block>, &ExecutionOutcome<N::Receipt>) {
168 (ChainBlocks { blocks: Cow::Borrowed(&self.blocks) }, &self.execution_outcome)
169 }
170
171 pub fn block_receipts_iter(&self) -> impl Iterator<Item = &Vec<N::Receipt>> + '_ {
173 self.execution_outcome.receipts().iter()
174 }
175
176 pub fn blocks_iter(&self) -> impl Iterator<Item = &RecoveredBlock<N::Block>> + '_ {
178 self.blocks().iter().map(|block| block.1)
179 }
180
181 pub fn blocks_and_receipts(
183 &self,
184 ) -> impl Iterator<Item = (&RecoveredBlock<N::Block>, &Vec<N::Receipt>)> + '_ {
185 self.blocks_iter().zip(self.block_receipts_iter())
186 }
187
188 #[track_caller]
190 pub fn fork_block(&self) -> ForkBlock {
191 let first = self.first();
192 ForkBlock {
193 number: first.header().number().saturating_sub(1),
194 hash: first.header().parent_hash(),
195 }
196 }
197
198 #[track_caller]
204 pub fn first(&self) -> &RecoveredBlock<N::Block> {
205 self.blocks.first_key_value().expect("Chain should have at least one block").1
206 }
207
208 #[track_caller]
214 pub fn tip(&self) -> &RecoveredBlock<N::Block> {
215 self.blocks.last_key_value().expect("Chain should have at least one block").1
216 }
217
218 pub fn len(&self) -> usize {
220 self.blocks.len()
221 }
222
223 pub fn range(&self) -> RangeInclusive<BlockNumber> {
229 self.first().header().number()..=self.tip().header().number()
230 }
231
232 pub fn receipts_by_block_hash(&self, block_hash: BlockHash) -> Option<Vec<&N::Receipt>> {
234 let num = self.block_number(block_hash)?;
235 Some(self.execution_outcome.receipts_by_block(num).iter().collect())
236 }
237
238 pub fn receipts_with_attachment(&self) -> Vec<BlockReceipts<N::Receipt>>
242 where
243 N::SignedTx: Encodable2718,
244 {
245 let mut receipt_attach = Vec::with_capacity(self.blocks().len());
246 for ((block_num, block), receipts) in
247 self.blocks().iter().zip(self.execution_outcome.receipts().iter())
248 {
249 let mut tx_receipts = Vec::with_capacity(receipts.len());
250 for (tx, receipt) in block.body().transactions().iter().zip(receipts.iter()) {
251 tx_receipts.push((tx.trie_hash(), receipt.clone()));
252 }
253 let block_num_hash = BlockNumHash::new(*block_num, block.hash());
254 receipt_attach.push(BlockReceipts { block: block_num_hash, tx_receipts });
255 }
256 receipt_attach
257 }
258
259 pub fn append_block(
262 &mut self,
263 block: RecoveredBlock<N::Block>,
264 execution_outcome: ExecutionOutcome<N::Receipt>,
265 ) {
266 self.blocks.insert(block.header().number(), block);
267 self.execution_outcome.extend(execution_outcome);
268 self.trie_updates.take(); }
270
271 pub fn append_chain(&mut self, other: Self) -> Result<(), Self> {
278 let chain_tip = self.tip();
279 let other_fork_block = other.fork_block();
280 if chain_tip.hash() != other_fork_block.hash {
281 return Err(other)
282 }
283
284 self.blocks.extend(other.blocks);
286 self.execution_outcome.extend(other.execution_outcome);
287 self.trie_updates.take(); Ok(())
290 }
291
292 #[track_caller]
313 pub fn split(mut self, split_at: ChainSplitTarget) -> ChainSplit<N> {
314 let chain_tip = *self.blocks.last_entry().expect("chain is never empty").key();
315 let block_number = match split_at {
316 ChainSplitTarget::Hash(block_hash) => {
317 let Some(block_number) = self.block_number(block_hash) else {
318 return ChainSplit::NoSplitPending(self)
319 };
320 if block_number == chain_tip {
322 return ChainSplit::NoSplitCanonical(self)
323 }
324 block_number
325 }
326 ChainSplitTarget::Number(block_number) => {
327 if block_number > chain_tip {
328 return ChainSplit::NoSplitPending(self)
329 }
330 if block_number == chain_tip {
331 return ChainSplit::NoSplitCanonical(self)
332 }
333 if block_number < *self.blocks.first_entry().expect("chain is never empty").key() {
334 return ChainSplit::NoSplitPending(self)
335 }
336 block_number
337 }
338 };
339
340 let split_at = block_number + 1;
341 let higher_number_blocks = self.blocks.split_off(&split_at);
342
343 let execution_outcome = core::mem::take(&mut self.execution_outcome);
344 let (canonical_block_exec_outcome, pending_block_exec_outcome) =
345 execution_outcome.split_at(split_at);
346
347 ChainSplit::Split {
350 canonical: Self {
351 execution_outcome: canonical_block_exec_outcome.expect("split in range"),
352 blocks: self.blocks,
353 trie_updates: None,
354 },
355 pending: Self {
356 execution_outcome: pending_block_exec_outcome,
357 blocks: higher_number_blocks,
358 trie_updates: None,
359 },
360 }
361 }
362}
363
364#[derive(Debug)]
366pub struct DisplayBlocksChain<'a, B: reth_primitives_traits::Block>(
367 pub &'a BTreeMap<BlockNumber, RecoveredBlock<B>>,
368);
369
370impl<B: reth_primitives_traits::Block> fmt::Display for DisplayBlocksChain<'_, B> {
371 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
372 let mut list = f.debug_list();
373 let mut values = self.0.values().map(|block| block.num_hash());
374 if values.len() <= 3 {
375 list.entries(values);
376 } else {
377 list.entry(&values.next().unwrap());
378 list.entry(&format_args!("..."));
379 list.entry(&values.next_back().unwrap());
380 }
381 list.finish()
382 }
383}
384
385#[derive(Clone, Debug, Default, PartialEq, Eq)]
387pub struct ChainBlocks<'a, B: Block> {
388 blocks: Cow<'a, BTreeMap<BlockNumber, RecoveredBlock<B>>>,
389}
390
391impl<B: Block<Body: BlockBody<Transaction: SignedTransaction>>> ChainBlocks<'_, B> {
392 #[inline]
396 pub fn into_blocks(self) -> impl Iterator<Item = RecoveredBlock<B>> {
397 self.blocks.into_owned().into_values()
398 }
399
400 #[inline]
402 pub fn iter(&self) -> impl Iterator<Item = (&BlockNumber, &RecoveredBlock<B>)> {
403 self.blocks.iter()
404 }
405
406 #[inline]
412 pub fn tip(&self) -> &RecoveredBlock<B> {
413 self.blocks.last_key_value().expect("Chain should have at least one block").1
414 }
415
416 #[inline]
422 pub fn first(&self) -> &RecoveredBlock<B> {
423 self.blocks.first_key_value().expect("Chain should have at least one block").1
424 }
425
426 #[inline]
428 pub fn transactions(&self) -> impl Iterator<Item = &<B::Body as BlockBody>::Transaction> + '_ {
429 self.blocks.values().flat_map(|block| block.body().transactions_iter())
430 }
431
432 #[inline]
434 pub fn transactions_with_sender(
435 &self,
436 ) -> impl Iterator<Item = (&Address, &<B::Body as BlockBody>::Transaction)> + '_ {
437 self.blocks.values().flat_map(|block| block.transactions_with_sender())
438 }
439
440 #[inline]
444 pub fn transactions_ecrecovered(
445 &self,
446 ) -> impl Iterator<Item = Recovered<<B::Body as BlockBody>::Transaction>> + '_ {
447 self.transactions_with_sender().map(|(signer, tx)| tx.clone().with_signer(*signer))
448 }
449
450 #[inline]
452 pub fn transaction_hashes(&self) -> impl Iterator<Item = TxHash> + '_ {
453 self.blocks
454 .values()
455 .flat_map(|block| block.body().transactions_iter().map(|tx| tx.trie_hash()))
456 }
457}
458
459impl<B: Block> IntoIterator for ChainBlocks<'_, B> {
460 type Item = (BlockNumber, RecoveredBlock<B>);
461 type IntoIter = alloc::collections::btree_map::IntoIter<BlockNumber, RecoveredBlock<B>>;
462
463 fn into_iter(self) -> Self::IntoIter {
464 #[allow(clippy::unnecessary_to_owned)]
465 self.blocks.into_owned().into_iter()
466 }
467}
468
469#[derive(Default, Clone, Debug, PartialEq, Eq)]
471pub struct BlockReceipts<T = reth_ethereum_primitives::Receipt> {
472 pub block: BlockNumHash,
474 pub tx_receipts: Vec<(TxHash, T)>,
476}
477
478#[derive(Clone, Copy, Debug, PartialEq, Eq)]
480pub enum ChainSplitTarget {
481 Number(BlockNumber),
483 Hash(BlockHash),
485}
486
487impl From<BlockNumber> for ChainSplitTarget {
488 fn from(number: BlockNumber) -> Self {
489 Self::Number(number)
490 }
491}
492
493impl From<BlockHash> for ChainSplitTarget {
494 fn from(hash: BlockHash) -> Self {
495 Self::Hash(hash)
496 }
497}
498
499#[derive(Clone, Debug, PartialEq, Eq)]
501#[allow(clippy::large_enum_variant)]
502pub enum ChainSplit<N: NodePrimitives = reth_ethereum_primitives::EthPrimitives> {
503 NoSplitPending(Chain<N>),
507 NoSplitCanonical(Chain<N>),
510 Split {
513 canonical: Chain<N>,
517 pending: Chain<N>,
522 },
523}
524
525#[cfg(feature = "serde-bincode-compat")]
527pub(super) mod serde_bincode_compat {
528 use crate::{serde_bincode_compat, ExecutionOutcome};
529 use alloc::borrow::Cow;
530 use alloy_primitives::BlockNumber;
531 use reth_ethereum_primitives::EthPrimitives;
532 use reth_primitives_traits::{
533 serde_bincode_compat::{RecoveredBlock, SerdeBincodeCompat},
534 Block, NodePrimitives,
535 };
536 use reth_trie_common::serde_bincode_compat::updates::TrieUpdates;
537 use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
538 use serde_with::{DeserializeAs, SerializeAs};
539 use std::collections::BTreeMap;
540
541 #[derive(Debug, Serialize, Deserialize)]
557 pub struct Chain<'a, N = EthPrimitives>
558 where
559 N: NodePrimitives<
560 Block: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static,
561 >,
562 {
563 blocks: RecoveredBlocks<'a, N::Block>,
564 execution_outcome: serde_bincode_compat::ExecutionOutcome<'a, N::Receipt>,
565 trie_updates: Option<TrieUpdates<'a>>,
566 }
567
568 #[derive(Debug)]
569 struct RecoveredBlocks<
570 'a,
571 B: reth_primitives_traits::Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat>
572 + 'static,
573 >(Cow<'a, BTreeMap<BlockNumber, reth_primitives_traits::RecoveredBlock<B>>>);
574
575 impl<B> Serialize for RecoveredBlocks<'_, B>
576 where
577 B: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static,
578 {
579 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
580 where
581 S: Serializer,
582 {
583 let mut state = serializer.serialize_map(Some(self.0.len()))?;
584
585 for (block_number, block) in self.0.iter() {
586 state.serialize_entry(block_number, &RecoveredBlock::<'_, B>::from(block))?;
587 }
588
589 state.end()
590 }
591 }
592
593 impl<'de, B> Deserialize<'de> for RecoveredBlocks<'_, B>
594 where
595 B: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static,
596 {
597 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
598 where
599 D: Deserializer<'de>,
600 {
601 Ok(Self(Cow::Owned(
602 BTreeMap::<BlockNumber, RecoveredBlock<'_, B>>::deserialize(deserializer)
603 .map(|blocks| blocks.into_iter().map(|(n, b)| (n, b.into())).collect())?,
604 )))
605 }
606 }
607
608 impl<'a, N> From<&'a super::Chain<N>> for Chain<'a, N>
609 where
610 N: NodePrimitives<
611 Block: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static,
612 >,
613 {
614 fn from(value: &'a super::Chain<N>) -> Self {
615 Self {
616 blocks: RecoveredBlocks(Cow::Borrowed(&value.blocks)),
617 execution_outcome: value.execution_outcome.as_repr(),
618 trie_updates: value.trie_updates.as_ref().map(Into::into),
619 }
620 }
621 }
622
623 impl<'a, N> From<Chain<'a, N>> for super::Chain<N>
624 where
625 N: NodePrimitives<
626 Block: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static,
627 >,
628 {
629 fn from(value: Chain<'a, N>) -> Self {
630 Self {
631 blocks: value.blocks.0.into_owned(),
632 execution_outcome: ExecutionOutcome::from_repr(value.execution_outcome),
633 trie_updates: value.trie_updates.map(Into::into),
634 }
635 }
636 }
637
638 impl<N> SerializeAs<super::Chain<N>> for Chain<'_, N>
639 where
640 N: NodePrimitives<
641 Block: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static,
642 >,
643 {
644 fn serialize_as<S>(source: &super::Chain<N>, serializer: S) -> Result<S::Ok, S::Error>
645 where
646 S: Serializer,
647 {
648 Chain::from(source).serialize(serializer)
649 }
650 }
651
652 impl<'de, N> DeserializeAs<'de, super::Chain<N>> for Chain<'de, N>
653 where
654 N: NodePrimitives<
655 Block: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static,
656 >,
657 {
658 fn deserialize_as<D>(deserializer: D) -> Result<super::Chain<N>, D::Error>
659 where
660 D: Deserializer<'de>,
661 {
662 Chain::deserialize(deserializer).map(Into::into)
663 }
664 }
665
666 #[cfg(test)]
667 mod tests {
668 use super::super::{serde_bincode_compat, Chain};
669 use arbitrary::Arbitrary;
670 use rand::Rng;
671 use reth_primitives_traits::RecoveredBlock;
672 use serde::{Deserialize, Serialize};
673 use serde_with::serde_as;
674
675 #[test]
676 fn test_chain_bincode_roundtrip() {
677 #[serde_as]
678 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
679 struct Data {
680 #[serde_as(as = "serde_bincode_compat::Chain")]
681 chain: Chain,
682 }
683
684 let mut bytes = [0u8; 1024];
685 rand::thread_rng().fill(bytes.as_mut_slice());
686 let data = Data {
687 chain: Chain::new(
688 vec![RecoveredBlock::arbitrary(&mut arbitrary::Unstructured::new(&bytes))
689 .unwrap()],
690 Default::default(),
691 None,
692 ),
693 };
694
695 let encoded = bincode::serialize(&data).unwrap();
696 let decoded: Data = bincode::deserialize(&encoded).unwrap();
697 assert_eq!(decoded, data);
698 }
699 }
700}
701
702#[cfg(test)]
703mod tests {
704 use super::*;
705 use alloy_consensus::TxType;
706 use alloy_primitives::{Address, B256};
707 use reth_ethereum_primitives::Receipt;
708 use revm::{primitives::HashMap, state::AccountInfo};
709
710 #[test]
711 fn chain_append() {
712 let block: RecoveredBlock<reth_ethereum_primitives::Block> = Default::default();
713 let block1_hash = B256::new([0x01; 32]);
714 let block2_hash = B256::new([0x02; 32]);
715 let block3_hash = B256::new([0x03; 32]);
716 let block4_hash = B256::new([0x04; 32]);
717
718 let mut block1 = block.clone();
719 let mut block2 = block.clone();
720 let mut block3 = block.clone();
721 let mut block4 = block;
722
723 block1.set_hash(block1_hash);
724 block2.set_hash(block2_hash);
725 block3.set_hash(block3_hash);
726 block4.set_hash(block4_hash);
727
728 block3.set_parent_hash(block2_hash);
729
730 let mut chain1: Chain =
731 Chain { blocks: BTreeMap::from([(1, block1), (2, block2)]), ..Default::default() };
732
733 let chain2 =
734 Chain { blocks: BTreeMap::from([(3, block3), (4, block4)]), ..Default::default() };
735
736 assert!(chain1.append_chain(chain2.clone()).is_ok());
737
738 assert!(chain1.append_chain(chain2).is_err());
740 }
741
742 #[test]
743 fn test_number_split() {
744 let execution_outcome1: ExecutionOutcome = ExecutionOutcome::new(
745 BundleState::new(
746 vec![(
747 Address::new([2; 20]),
748 None,
749 Some(AccountInfo::default()),
750 HashMap::default(),
751 )],
752 vec![vec![(Address::new([2; 20]), None, vec![])]],
753 vec![],
754 ),
755 vec![vec![]],
756 1,
757 vec![],
758 );
759
760 let execution_outcome2 = ExecutionOutcome::new(
761 BundleState::new(
762 vec![(
763 Address::new([3; 20]),
764 None,
765 Some(AccountInfo::default()),
766 HashMap::default(),
767 )],
768 vec![vec![(Address::new([3; 20]), None, vec![])]],
769 vec![],
770 ),
771 vec![vec![]],
772 2,
773 vec![],
774 );
775
776 let mut block1: RecoveredBlock<reth_ethereum_primitives::Block> = Default::default();
777 let block1_hash = B256::new([15; 32]);
778 block1.set_block_number(1);
779 block1.set_hash(block1_hash);
780 block1.push_sender(Address::new([4; 20]));
781
782 let mut block2: RecoveredBlock<reth_ethereum_primitives::Block> = Default::default();
783 let block2_hash = B256::new([16; 32]);
784 block2.set_block_number(2);
785 block2.set_hash(block2_hash);
786 block2.push_sender(Address::new([4; 20]));
787
788 let mut block_state_extended = execution_outcome1;
789 block_state_extended.extend(execution_outcome2);
790
791 let chain: Chain =
792 Chain::new(vec![block1.clone(), block2.clone()], block_state_extended, None);
793
794 let (split1_execution_outcome, split2_execution_outcome) =
795 chain.execution_outcome.clone().split_at(2);
796
797 let chain_split1 = Chain {
798 execution_outcome: split1_execution_outcome.unwrap(),
799 blocks: BTreeMap::from([(1, block1.clone())]),
800 trie_updates: None,
801 };
802
803 let chain_split2 = Chain {
804 execution_outcome: split2_execution_outcome,
805 blocks: BTreeMap::from([(2, block2.clone())]),
806 trie_updates: None,
807 };
808
809 assert_eq!(
811 chain.execution_outcome_at_block(block2.number),
812 Some(chain.execution_outcome.clone())
813 );
814 assert_eq!(
815 chain.execution_outcome_at_block(block1.number),
816 Some(chain_split1.execution_outcome.clone())
817 );
818 assert_eq!(chain.execution_outcome_at_block(100), None);
820
821 assert_eq!(
823 chain.clone().split(block1_hash.into()),
824 ChainSplit::Split { canonical: chain_split1, pending: chain_split2 }
825 );
826
827 assert_eq!(
829 chain.clone().split(B256::new([100; 32]).into()),
830 ChainSplit::NoSplitPending(chain.clone())
831 );
832
833 assert_eq!(chain.clone().split(10u64.into()), ChainSplit::NoSplitPending(chain.clone()));
835
836 assert_eq!(chain.clone().split(0u64.into()), ChainSplit::NoSplitPending(chain));
838 }
839
840 #[test]
841 fn receipts_by_block_hash() {
842 let block: RecoveredBlock<reth_ethereum_primitives::Block> = Default::default();
844
845 let block1_hash = B256::new([0x01; 32]);
847 let block2_hash = B256::new([0x02; 32]);
848
849 let mut block1 = block.clone();
851 let mut block2 = block;
852
853 block1.set_hash(block1_hash);
855 block2.set_hash(block2_hash);
856
857 let receipt1 = Receipt {
859 tx_type: TxType::Legacy,
860 cumulative_gas_used: 46913,
861 logs: vec![],
862 success: true,
863 };
864
865 let receipt2 = Receipt {
867 tx_type: TxType::Legacy,
868 cumulative_gas_used: 1325345,
869 logs: vec![],
870 success: true,
871 };
872
873 let receipts = vec![vec![receipt1.clone()], vec![receipt2]];
875
876 let execution_outcome = ExecutionOutcome {
879 bundle: Default::default(),
880 receipts,
881 requests: vec![],
882 first_block: 10,
883 };
884
885 let chain: Chain = Chain {
888 blocks: BTreeMap::from([(10, block1), (11, block2)]),
889 execution_outcome: execution_outcome.clone(),
890 ..Default::default()
891 };
892
893 assert_eq!(chain.receipts_by_block_hash(block1_hash), Some(vec![&receipt1]));
895
896 let execution_outcome1 = ExecutionOutcome {
898 bundle: Default::default(),
899 receipts: vec![vec![receipt1]],
900 requests: vec![],
901 first_block: 10,
902 };
903
904 assert_eq!(chain.execution_outcome_at_block(10), Some(execution_outcome1));
906
907 assert_eq!(chain.execution_outcome_at_block(11), Some(execution_outcome));
909 }
910}