1use crate::ExecutionOutcome;
4use alloc::{borrow::Cow, collections::BTreeMap};
5use alloy_consensus::BlockHeader;
6use alloy_eips::{eip1898::ForkBlock, eip2718::Encodable2718, BlockNumHash};
7use alloy_primitives::{Address, BlockHash, BlockNumber, TxHash};
8use core::{fmt, ops::RangeInclusive};
9use reth_execution_errors::{BlockExecutionError, InternalBlockExecutionError};
10use reth_primitives::{
11 transaction::SignedTransactionIntoRecoveredExt, RecoveredTx, SealedBlockFor,
12 SealedBlockWithSenders, SealedHeader,
13};
14use reth_primitives_traits::{Block, BlockBody, NodePrimitives, SignedTransaction};
15use reth_trie::updates::TrieUpdates;
16use revm::db::BundleState;
17
18#[derive(Clone, Debug, PartialEq, Eq)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30pub struct Chain<N: NodePrimitives = reth_primitives::EthPrimitives> {
31 blocks: BTreeMap<BlockNumber, SealedBlockWithSenders<N::Block>>,
33 execution_outcome: ExecutionOutcome<N::Receipt>,
40 trie_updates: Option<TrieUpdates>,
44}
45
46impl<N: NodePrimitives> Default for Chain<N> {
47 fn default() -> Self {
48 Self {
49 blocks: Default::default(),
50 execution_outcome: Default::default(),
51 trie_updates: Default::default(),
52 }
53 }
54}
55
56impl<N: NodePrimitives> Chain<N> {
57 pub fn new(
63 blocks: impl IntoIterator<Item = SealedBlockWithSenders<N::Block>>,
64 execution_outcome: ExecutionOutcome<N::Receipt>,
65 trie_updates: Option<TrieUpdates>,
66 ) -> Self {
67 let blocks = blocks.into_iter().map(|b| (b.number(), b)).collect::<BTreeMap<_, _>>();
68 debug_assert!(!blocks.is_empty(), "Chain should have at least one block");
69
70 Self { blocks, execution_outcome, trie_updates }
71 }
72
73 pub fn from_block(
75 block: SealedBlockWithSenders<N::Block>,
76 execution_outcome: ExecutionOutcome<N::Receipt>,
77 trie_updates: Option<TrieUpdates>,
78 ) -> Self {
79 Self::new([block], execution_outcome, trie_updates)
80 }
81
82 pub const fn blocks(&self) -> &BTreeMap<BlockNumber, SealedBlockWithSenders<N::Block>> {
84 &self.blocks
85 }
86
87 pub fn into_blocks(self) -> BTreeMap<BlockNumber, SealedBlockWithSenders<N::Block>> {
89 self.blocks
90 }
91
92 pub fn headers(&self) -> impl Iterator<Item = SealedHeader<N::BlockHeader>> + '_ {
94 self.blocks.values().map(|block| block.header.clone())
95 }
96
97 pub const fn trie_updates(&self) -> Option<&TrieUpdates> {
99 self.trie_updates.as_ref()
100 }
101
102 pub fn clear_trie_updates(&mut self) {
104 self.trie_updates.take();
105 }
106
107 pub const fn execution_outcome(&self) -> &ExecutionOutcome<N::Receipt> {
109 &self.execution_outcome
110 }
111
112 pub fn execution_outcome_mut(&mut self) -> &mut ExecutionOutcome<N::Receipt> {
114 &mut self.execution_outcome
115 }
116
117 pub fn prepend_state(&mut self, state: BundleState) {
119 self.execution_outcome.prepend_state(state);
120 self.trie_updates.take(); }
122
123 pub fn is_empty(&self) -> bool {
125 self.blocks.is_empty()
126 }
127
128 pub fn block_number(&self, block_hash: BlockHash) -> Option<BlockNumber> {
130 self.blocks.iter().find_map(|(num, block)| (block.hash() == block_hash).then_some(*num))
131 }
132
133 pub fn block(&self, block_hash: BlockHash) -> Option<&SealedBlockFor<N::Block>> {
135 self.block_with_senders(block_hash).map(|block| &block.block)
136 }
137
138 pub fn block_with_senders(
140 &self,
141 block_hash: BlockHash,
142 ) -> Option<&SealedBlockWithSenders<N::Block>> {
143 self.blocks.iter().find_map(|(_num, block)| (block.hash() == block_hash).then_some(block))
144 }
145
146 pub fn execution_outcome_at_block(
148 &self,
149 block_number: BlockNumber,
150 ) -> Option<ExecutionOutcome<N::Receipt>> {
151 if self.tip().number() == block_number {
152 return Some(self.execution_outcome.clone())
153 }
154
155 if self.blocks.contains_key(&block_number) {
156 let mut execution_outcome = self.execution_outcome.clone();
157 execution_outcome.revert_to(block_number);
158 return Some(execution_outcome)
159 }
160 None
161 }
162
163 pub fn into_inner(
168 self,
169 ) -> (ChainBlocks<'static, N::Block>, ExecutionOutcome<N::Receipt>, Option<TrieUpdates>) {
170 (ChainBlocks { blocks: Cow::Owned(self.blocks) }, self.execution_outcome, self.trie_updates)
171 }
172
173 pub const fn inner(&self) -> (ChainBlocks<'_, N::Block>, &ExecutionOutcome<N::Receipt>) {
177 (ChainBlocks { blocks: Cow::Borrowed(&self.blocks) }, &self.execution_outcome)
178 }
179
180 pub fn block_receipts_iter(&self) -> impl Iterator<Item = &Vec<Option<N::Receipt>>> + '_ {
182 self.execution_outcome.receipts().iter()
183 }
184
185 pub fn blocks_iter(&self) -> impl Iterator<Item = &SealedBlockWithSenders<N::Block>> + '_ {
187 self.blocks().iter().map(|block| block.1)
188 }
189
190 pub fn blocks_and_receipts(
192 &self,
193 ) -> impl Iterator<Item = (&SealedBlockWithSenders<N::Block>, &Vec<Option<N::Receipt>>)> + '_
194 {
195 self.blocks_iter().zip(self.block_receipts_iter())
196 }
197
198 #[track_caller]
200 pub fn fork_block(&self) -> ForkBlock {
201 let first = self.first();
202 ForkBlock { number: first.number().saturating_sub(1), hash: first.parent_hash() }
203 }
204
205 #[track_caller]
211 pub fn first(&self) -> &SealedBlockWithSenders<N::Block> {
212 self.blocks.first_key_value().expect("Chain should have at least one block").1
213 }
214
215 #[track_caller]
221 pub fn tip(&self) -> &SealedBlockWithSenders<N::Block> {
222 self.blocks.last_key_value().expect("Chain should have at least one block").1
223 }
224
225 pub fn len(&self) -> usize {
227 self.blocks.len()
228 }
229
230 pub fn range(&self) -> RangeInclusive<BlockNumber> {
236 self.first().number()..=self.tip().number()
237 }
238
239 pub fn receipts_by_block_hash(&self, block_hash: BlockHash) -> Option<Vec<&N::Receipt>> {
241 let num = self.block_number(block_hash)?;
242 self.execution_outcome.receipts_by_block(num).iter().map(Option::as_ref).collect()
243 }
244
245 pub fn receipts_with_attachment(&self) -> Vec<BlockReceipts<N::Receipt>>
249 where
250 N::SignedTx: Encodable2718,
251 {
252 let mut receipt_attach = Vec::with_capacity(self.blocks().len());
253 for ((block_num, block), receipts) in
254 self.blocks().iter().zip(self.execution_outcome.receipts().iter())
255 {
256 let mut tx_receipts = Vec::with_capacity(receipts.len());
257 for (tx, receipt) in block.body.transactions().iter().zip(receipts.iter()) {
258 tx_receipts.push((
259 tx.trie_hash(),
260 receipt.as_ref().expect("receipts have not been pruned").clone(),
261 ));
262 }
263 let block_num_hash = BlockNumHash::new(*block_num, block.hash());
264 receipt_attach.push(BlockReceipts { block: block_num_hash, tx_receipts });
265 }
266 receipt_attach
267 }
268
269 pub fn append_block(
272 &mut self,
273 block: SealedBlockWithSenders<N::Block>,
274 execution_outcome: ExecutionOutcome<N::Receipt>,
275 ) {
276 self.blocks.insert(block.number(), block);
277 self.execution_outcome.extend(execution_outcome);
278 self.trie_updates.take(); }
280
281 pub fn append_chain(&mut self, other: Self) -> Result<(), BlockExecutionError> {
285 let chain_tip = self.tip();
286 let other_fork_block = other.fork_block();
287 if chain_tip.hash() != other_fork_block.hash {
288 return Err(InternalBlockExecutionError::AppendChainDoesntConnect {
289 chain_tip: Box::new(chain_tip.num_hash()),
290 other_chain_fork: Box::new(other_fork_block),
291 }
292 .into())
293 }
294
295 self.blocks.extend(other.blocks);
297 self.execution_outcome.extend(other.execution_outcome);
298 self.trie_updates.take(); Ok(())
301 }
302
303 #[track_caller]
324 pub fn split(mut self, split_at: ChainSplitTarget) -> ChainSplit<N> {
325 let chain_tip = *self.blocks.last_entry().expect("chain is never empty").key();
326 let block_number = match split_at {
327 ChainSplitTarget::Hash(block_hash) => {
328 let Some(block_number) = self.block_number(block_hash) else {
329 return ChainSplit::NoSplitPending(self)
330 };
331 if block_number == chain_tip {
333 return ChainSplit::NoSplitCanonical(self)
334 }
335 block_number
336 }
337 ChainSplitTarget::Number(block_number) => {
338 if block_number > chain_tip {
339 return ChainSplit::NoSplitPending(self)
340 }
341 if block_number == chain_tip {
342 return ChainSplit::NoSplitCanonical(self)
343 }
344 if block_number < *self.blocks.first_entry().expect("chain is never empty").key() {
345 return ChainSplit::NoSplitPending(self)
346 }
347 block_number
348 }
349 };
350
351 let split_at = block_number + 1;
352 let higher_number_blocks = self.blocks.split_off(&split_at);
353
354 let execution_outcome = std::mem::take(&mut self.execution_outcome);
355 let (canonical_block_exec_outcome, pending_block_exec_outcome) =
356 execution_outcome.split_at(split_at);
357
358 ChainSplit::Split {
361 canonical: Self {
362 execution_outcome: canonical_block_exec_outcome.expect("split in range"),
363 blocks: self.blocks,
364 trie_updates: None,
365 },
366 pending: Self {
367 execution_outcome: pending_block_exec_outcome,
368 blocks: higher_number_blocks,
369 trie_updates: None,
370 },
371 }
372 }
373}
374
375#[derive(Debug)]
377pub struct DisplayBlocksChain<'a, B: reth_primitives_traits::Block>(
378 pub &'a BTreeMap<BlockNumber, SealedBlockWithSenders<B>>,
379);
380
381impl<B: reth_primitives_traits::Block> fmt::Display for DisplayBlocksChain<'_, B> {
382 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
383 let mut list = f.debug_list();
384 let mut values = self.0.values().map(|block| block.num_hash());
385 if values.len() <= 3 {
386 list.entries(values);
387 } else {
388 list.entry(&values.next().unwrap());
389 list.entry(&format_args!("..."));
390 list.entry(&values.next_back().unwrap());
391 }
392 list.finish()
393 }
394}
395
396#[derive(Clone, Debug, Default, PartialEq, Eq)]
398pub struct ChainBlocks<'a, B: Block> {
399 blocks: Cow<'a, BTreeMap<BlockNumber, SealedBlockWithSenders<B>>>,
400}
401
402impl<B: Block<Body: BlockBody<Transaction: SignedTransaction>>> ChainBlocks<'_, B> {
403 #[inline]
407 pub fn into_blocks(self) -> impl Iterator<Item = SealedBlockWithSenders<B>> {
408 self.blocks.into_owned().into_values()
409 }
410
411 #[inline]
413 pub fn iter(&self) -> impl Iterator<Item = (&BlockNumber, &SealedBlockWithSenders<B>)> {
414 self.blocks.iter()
415 }
416
417 #[inline]
423 pub fn tip(&self) -> &SealedBlockWithSenders<B> {
424 self.blocks.last_key_value().expect("Chain should have at least one block").1
425 }
426
427 #[inline]
433 pub fn first(&self) -> &SealedBlockWithSenders<B> {
434 self.blocks.first_key_value().expect("Chain should have at least one block").1
435 }
436
437 #[inline]
439 pub fn transactions(&self) -> impl Iterator<Item = &<B::Body as BlockBody>::Transaction> + '_ {
440 self.blocks.values().flat_map(|block| block.body.transactions().iter())
441 }
442
443 #[inline]
445 pub fn transactions_with_sender(
446 &self,
447 ) -> impl Iterator<Item = (&Address, &<B::Body as BlockBody>::Transaction)> + '_ {
448 self.blocks.values().flat_map(|block| block.transactions_with_sender())
449 }
450
451 #[inline]
455 pub fn transactions_ecrecovered(
456 &self,
457 ) -> impl Iterator<Item = RecoveredTx<<B::Body as BlockBody>::Transaction>> + '_ {
458 self.transactions_with_sender().map(|(signer, tx)| tx.clone().with_signer(*signer))
459 }
460
461 #[inline]
463 pub fn transaction_hashes(&self) -> impl Iterator<Item = TxHash> + '_ {
464 self.blocks.values().flat_map(|block| block.transactions().iter().map(|tx| tx.trie_hash()))
465 }
466}
467
468impl<B: Block> IntoIterator for ChainBlocks<'_, B> {
469 type Item = (BlockNumber, SealedBlockWithSenders<B>);
470 type IntoIter = std::collections::btree_map::IntoIter<BlockNumber, SealedBlockWithSenders<B>>;
471
472 fn into_iter(self) -> Self::IntoIter {
473 #[allow(clippy::unnecessary_to_owned)]
474 self.blocks.into_owned().into_iter()
475 }
476}
477
478#[derive(Default, Clone, Debug, PartialEq, Eq)]
480pub struct BlockReceipts<T = reth_primitives::Receipt> {
481 pub block: BlockNumHash,
483 pub tx_receipts: Vec<(TxHash, T)>,
485}
486
487#[derive(Clone, Copy, Debug, PartialEq, Eq)]
489pub enum ChainSplitTarget {
490 Number(BlockNumber),
492 Hash(BlockHash),
494}
495
496impl From<BlockNumber> for ChainSplitTarget {
497 fn from(number: BlockNumber) -> Self {
498 Self::Number(number)
499 }
500}
501
502impl From<BlockHash> for ChainSplitTarget {
503 fn from(hash: BlockHash) -> Self {
504 Self::Hash(hash)
505 }
506}
507
508#[derive(Clone, Debug, PartialEq, Eq)]
510pub enum ChainSplit<N: NodePrimitives = reth_primitives::EthPrimitives> {
511 NoSplitPending(Chain<N>),
515 NoSplitCanonical(Chain<N>),
518 Split {
521 canonical: Chain<N>,
525 pending: Chain<N>,
530 },
531}
532
533#[cfg(feature = "serde-bincode-compat")]
535pub(super) mod serde_bincode_compat {
536 use crate::ExecutionOutcome;
537 use alloc::borrow::Cow;
538 use alloy_primitives::BlockNumber;
539 use reth_primitives::{
540 serde_bincode_compat::SealedBlockWithSenders, EthPrimitives, NodePrimitives,
541 };
542 use reth_trie_common::serde_bincode_compat::updates::TrieUpdates;
543 use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
544 use serde_with::{DeserializeAs, SerializeAs};
545 use std::collections::BTreeMap;
546
547 #[derive(Debug, Serialize, Deserialize)]
563 pub struct Chain<'a, N = EthPrimitives>
564 where
565 N: NodePrimitives,
566 {
567 blocks: SealedBlocksWithSenders<'a, N::Block>,
568 execution_outcome: Cow<'a, ExecutionOutcome<N::Receipt>>,
569 trie_updates: Option<TrieUpdates<'a>>,
570 }
571
572 #[derive(Debug)]
573 struct SealedBlocksWithSenders<'a, B: reth_primitives_traits::Block>(
574 Cow<'a, BTreeMap<BlockNumber, reth_primitives::SealedBlockWithSenders<B>>>,
575 );
576
577 impl<B> Serialize for SealedBlocksWithSenders<'_, B>
578 where
579 B: reth_primitives_traits::Block,
580 {
581 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
582 where
583 S: Serializer,
584 {
585 let mut state = serializer.serialize_map(Some(self.0.len()))?;
586
587 for (block_number, block) in self.0.iter() {
588 state.serialize_entry(block_number, &SealedBlockWithSenders::<'_>::from(block))?;
589 }
590
591 state.end()
592 }
593 }
594
595 impl<'de, B> Deserialize<'de> for SealedBlocksWithSenders<'_, B>
596 where
597 B: reth_primitives_traits::Block,
598 {
599 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
600 where
601 D: Deserializer<'de>,
602 {
603 Ok(Self(Cow::Owned(
604 BTreeMap::<BlockNumber, SealedBlockWithSenders<'_, B>>::deserialize(deserializer)
605 .map(|blocks| blocks.into_iter().map(|(n, b)| (n, b.into())).collect())?,
606 )))
607 }
608 }
609
610 impl<'a, N> From<&'a super::Chain<N>> for Chain<'a, N>
611 where
612 N: NodePrimitives,
613 {
614 fn from(value: &'a super::Chain<N>) -> Self {
615 Self {
616 blocks: SealedBlocksWithSenders(Cow::Borrowed(&value.blocks)),
617 execution_outcome: Cow::Borrowed(&value.execution_outcome),
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 {
627 fn from(value: Chain<'a, N>) -> Self {
628 Self {
629 blocks: value.blocks.0.into_owned(),
630 execution_outcome: value.execution_outcome.into_owned(),
631 trie_updates: value.trie_updates.map(Into::into),
632 }
633 }
634 }
635
636 impl SerializeAs<super::Chain> for Chain<'_> {
637 fn serialize_as<S>(source: &super::Chain, serializer: S) -> Result<S::Ok, S::Error>
638 where
639 S: Serializer,
640 {
641 Chain::from(source).serialize(serializer)
642 }
643 }
644
645 impl<'de> DeserializeAs<'de, super::Chain> for Chain<'de> {
646 fn deserialize_as<D>(deserializer: D) -> Result<super::Chain, D::Error>
647 where
648 D: Deserializer<'de>,
649 {
650 Chain::deserialize(deserializer).map(Into::into)
651 }
652 }
653
654 #[cfg(test)]
655 mod tests {
656 use arbitrary::Arbitrary;
657 use rand::Rng;
658 use reth_primitives::SealedBlockWithSenders;
659 use serde::{Deserialize, Serialize};
660 use serde_with::serde_as;
661
662 use super::super::{serde_bincode_compat, Chain};
663
664 #[test]
665 fn test_chain_bincode_roundtrip() {
666 #[serde_as]
667 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
668 struct Data {
669 #[serde_as(as = "serde_bincode_compat::Chain")]
670 chain: Chain,
671 }
672
673 let mut bytes = [0u8; 1024];
674 rand::thread_rng().fill(bytes.as_mut_slice());
675 let data = Data {
676 chain: Chain::new(
677 vec![SealedBlockWithSenders::arbitrary(&mut arbitrary::Unstructured::new(
678 &bytes,
679 ))
680 .unwrap()],
681 Default::default(),
682 None,
683 ),
684 };
685
686 let encoded = bincode::serialize(&data).unwrap();
687 let decoded: Data = bincode::deserialize(&encoded).unwrap();
688 assert_eq!(decoded, data);
689 }
690 }
691}
692
693#[cfg(test)]
694mod tests {
695 use super::*;
696 use alloy_primitives::B256;
697 use revm::primitives::{AccountInfo, HashMap};
698
699 #[test]
700 fn chain_append() {
701 let block: SealedBlockWithSenders = Default::default();
702 let block1_hash = B256::new([0x01; 32]);
703 let block2_hash = B256::new([0x02; 32]);
704 let block3_hash = B256::new([0x03; 32]);
705 let block4_hash = B256::new([0x04; 32]);
706
707 let mut block1 = block.clone();
708 let mut block2 = block.clone();
709 let mut block3 = block.clone();
710 let mut block4 = block;
711
712 block1.block.header.set_hash(block1_hash);
713 block2.block.header.set_hash(block2_hash);
714 block3.block.header.set_hash(block3_hash);
715 block4.block.header.set_hash(block4_hash);
716
717 block3.set_parent_hash(block2_hash);
718
719 let mut chain1: Chain =
720 Chain { blocks: BTreeMap::from([(1, block1), (2, block2)]), ..Default::default() };
721
722 let chain2 =
723 Chain { blocks: BTreeMap::from([(3, block3), (4, block4)]), ..Default::default() };
724
725 assert!(chain1.append_chain(chain2.clone()).is_ok());
726
727 assert!(chain1.append_chain(chain2).is_err());
729 }
730
731 #[test]
732 fn test_number_split() {
733 let execution_outcome1: ExecutionOutcome = ExecutionOutcome::new(
734 BundleState::new(
735 vec![(
736 Address::new([2; 20]),
737 None,
738 Some(AccountInfo::default()),
739 HashMap::default(),
740 )],
741 vec![vec![(Address::new([2; 20]), None, vec![])]],
742 vec![],
743 ),
744 vec![vec![]].into(),
745 1,
746 vec![],
747 );
748
749 let execution_outcome2 = ExecutionOutcome::new(
750 BundleState::new(
751 vec![(
752 Address::new([3; 20]),
753 None,
754 Some(AccountInfo::default()),
755 HashMap::default(),
756 )],
757 vec![vec![(Address::new([3; 20]), None, vec![])]],
758 vec![],
759 ),
760 vec![vec![]].into(),
761 2,
762 vec![],
763 );
764
765 let mut block1: SealedBlockWithSenders = Default::default();
766 let block1_hash = B256::new([15; 32]);
767 block1.set_block_number(1);
768 block1.set_hash(block1_hash);
769 block1.senders.push(Address::new([4; 20]));
770
771 let mut block2: SealedBlockWithSenders = Default::default();
772 let block2_hash = B256::new([16; 32]);
773 block2.set_block_number(2);
774 block2.set_hash(block2_hash);
775 block2.senders.push(Address::new([4; 20]));
776
777 let mut block_state_extended = execution_outcome1;
778 block_state_extended.extend(execution_outcome2);
779
780 let chain: Chain =
781 Chain::new(vec![block1.clone(), block2.clone()], block_state_extended, None);
782
783 let (split1_execution_outcome, split2_execution_outcome) =
784 chain.execution_outcome.clone().split_at(2);
785
786 let chain_split1 = Chain {
787 execution_outcome: split1_execution_outcome.unwrap(),
788 blocks: BTreeMap::from([(1, block1.clone())]),
789 trie_updates: None,
790 };
791
792 let chain_split2 = Chain {
793 execution_outcome: split2_execution_outcome,
794 blocks: BTreeMap::from([(2, block2.clone())]),
795 trie_updates: None,
796 };
797
798 assert_eq!(
800 chain.execution_outcome_at_block(block2.number),
801 Some(chain.execution_outcome.clone())
802 );
803 assert_eq!(
804 chain.execution_outcome_at_block(block1.number),
805 Some(chain_split1.execution_outcome.clone())
806 );
807 assert_eq!(chain.execution_outcome_at_block(100), None);
809
810 assert_eq!(
812 chain.clone().split(block1_hash.into()),
813 ChainSplit::Split { canonical: chain_split1, pending: chain_split2 }
814 );
815
816 assert_eq!(
818 chain.clone().split(B256::new([100; 32]).into()),
819 ChainSplit::NoSplitPending(chain.clone())
820 );
821
822 assert_eq!(chain.clone().split(10u64.into()), ChainSplit::NoSplitPending(chain.clone()));
824
825 assert_eq!(chain.clone().split(0u64.into()), ChainSplit::NoSplitPending(chain));
827 }
828
829 #[test]
830 #[cfg(not(feature = "optimism"))]
831 fn receipts_by_block_hash() {
832 use reth_primitives::{Receipt, Receipts, TxType};
833
834 let block: SealedBlockWithSenders = Default::default();
836
837 let block1_hash = B256::new([0x01; 32]);
839 let block2_hash = B256::new([0x02; 32]);
840
841 let mut block1 = block.clone();
843 let mut block2 = block;
844
845 block1.block.header.set_hash(block1_hash);
847 block2.block.header.set_hash(block2_hash);
848
849 let receipt1 = Receipt {
851 tx_type: TxType::Legacy,
852 cumulative_gas_used: 46913,
853 logs: vec![],
854 success: true,
855 };
856
857 let receipt2 = Receipt {
859 tx_type: TxType::Legacy,
860 cumulative_gas_used: 1325345,
861 logs: vec![],
862 success: true,
863 };
864
865 let receipts =
867 Receipts { receipt_vec: vec![vec![Some(receipt1.clone())], vec![Some(receipt2)]] };
868
869 let execution_outcome = ExecutionOutcome {
872 bundle: Default::default(),
873 receipts,
874 requests: vec![],
875 first_block: 10,
876 };
877
878 let chain: Chain = Chain {
881 blocks: BTreeMap::from([(10, block1), (11, block2)]),
882 execution_outcome: execution_outcome.clone(),
883 ..Default::default()
884 };
885
886 assert_eq!(chain.receipts_by_block_hash(block1_hash), Some(vec![&receipt1]));
888
889 let execution_outcome1 = ExecutionOutcome {
891 bundle: Default::default(),
892 receipts: Receipts { receipt_vec: vec![vec![Some(receipt1)]] },
893 requests: vec![],
894 first_block: 10,
895 };
896
897 assert_eq!(chain.execution_outcome_at_block(10), Some(execution_outcome1));
899
900 assert_eq!(chain.execution_outcome_at_block(11), Some(execution_outcome));
902 }
903}