1use crate::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::LazyTrieData;
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, LazyTrieData>,
44}
45
46type ChainTxReceiptMeta<'a, N> = (
47 &'a Arc<RecoveredBlock<<N as NodePrimitives>::Block>>,
48 IndexedTx<'a, <N as NodePrimitives>::Block>,
49 &'a <N as NodePrimitives>::Receipt,
50 &'a [<N as NodePrimitives>::Receipt],
51);
52
53impl<N: NodePrimitives> Default for Chain<N> {
54 fn default() -> Self {
55 Self {
56 blocks: Default::default(),
57 execution_outcome: Default::default(),
58 trie_data: Default::default(),
59 }
60 }
61}
62
63impl<N: NodePrimitives> Chain<N> {
64 pub fn new(
70 blocks: impl IntoIterator<Item: Into<Arc<RecoveredBlock<N::Block>>>>,
71 execution_outcome: ExecutionOutcome<N::Receipt>,
72 trie_data: BTreeMap<BlockNumber, LazyTrieData>,
73 ) -> Self {
74 let blocks = blocks
75 .into_iter()
76 .map(|b| {
77 let block = b.into();
78 (block.header().number(), block)
79 })
80 .collect::<BTreeMap<_, _>>();
81 debug_assert!(!blocks.is_empty(), "Chain should have at least one block");
82
83 Self { blocks, execution_outcome, trie_data }
84 }
85
86 pub fn from_block(
88 block: impl Into<Arc<RecoveredBlock<N::Block>>>,
89 execution_outcome: ExecutionOutcome<N::Receipt>,
90 trie_data: LazyTrieData,
91 ) -> Self {
92 let block = block.into();
93 let block_number = block.header().number();
94 Self::new([block], execution_outcome, BTreeMap::from([(block_number, trie_data)]))
95 }
96
97 pub const fn blocks(&self) -> &BTreeMap<BlockNumber, Arc<RecoveredBlock<N::Block>>> {
99 &self.blocks
100 }
101
102 pub fn into_blocks(self) -> BTreeMap<BlockNumber, Arc<RecoveredBlock<N::Block>>> {
104 self.blocks
105 }
106
107 pub fn headers(&self) -> impl Iterator<Item = SealedHeader<N::BlockHeader>> + '_ {
109 self.blocks.values().map(|block| block.clone_sealed_header())
110 }
111
112 pub const fn trie_data(&self) -> &BTreeMap<BlockNumber, LazyTrieData> {
114 &self.trie_data
115 }
116
117 pub fn trie_data_at(&self, block_number: BlockNumber) -> Option<&LazyTrieData> {
119 self.trie_data.get(&block_number)
120 }
121
122 pub fn clear_trie_data(&mut self) {
124 self.trie_data.clear();
125 }
126
127 pub const fn execution_outcome(&self) -> &ExecutionOutcome<N::Receipt> {
129 &self.execution_outcome
130 }
131
132 pub const fn execution_outcome_mut(&mut self) -> &mut ExecutionOutcome<N::Receipt> {
134 &mut self.execution_outcome
135 }
136
137 pub fn is_empty(&self) -> bool {
139 self.blocks.is_empty()
140 }
141
142 pub fn block_number(&self, block_hash: BlockHash) -> Option<BlockNumber> {
144 self.blocks.iter().find_map(|(num, block)| (block.hash() == block_hash).then_some(*num))
145 }
146
147 pub fn recovered_block(&self, block_hash: BlockHash) -> Option<&RecoveredBlock<N::Block>> {
149 self.blocks
150 .iter()
151 .find_map(|(_num, block)| (block.hash() == block_hash).then_some(block.as_ref()))
152 }
153
154 pub fn execution_outcome_at_block(
156 &self,
157 block_number: BlockNumber,
158 ) -> Option<ExecutionOutcome<N::Receipt>> {
159 if self.tip().number() == block_number {
160 return Some(self.execution_outcome.clone())
161 }
162
163 if self.blocks.contains_key(&block_number) {
164 let mut execution_outcome = self.execution_outcome.clone();
165 execution_outcome.revert_to(block_number);
166 return Some(execution_outcome)
167 }
168 None
169 }
170
171 #[expect(clippy::type_complexity)]
176 pub fn into_inner(
177 self,
178 ) -> (
179 ChainBlocks<'static, N::Block>,
180 ExecutionOutcome<N::Receipt>,
181 BTreeMap<BlockNumber, LazyTrieData>,
182 ) {
183 (ChainBlocks { blocks: Cow::Owned(self.blocks) }, self.execution_outcome, self.trie_data)
184 }
185
186 pub const fn inner(&self) -> (ChainBlocks<'_, N::Block>, &ExecutionOutcome<N::Receipt>) {
190 (ChainBlocks { blocks: Cow::Borrowed(&self.blocks) }, &self.execution_outcome)
191 }
192
193 pub fn block_receipts_iter(&self) -> impl Iterator<Item = &Vec<N::Receipt>> + '_ {
195 self.execution_outcome.receipts().iter()
196 }
197
198 pub fn receipts_iter(&self) -> impl Iterator<Item = &N::Receipt> + '_ {
200 self.block_receipts_iter().flatten()
201 }
202
203 pub fn logs_iter(&self) -> impl Iterator<Item = &Log> + '_
205 where
206 N::Receipt: TxReceipt<Log = Log>,
207 {
208 self.receipts_iter().flat_map(|receipt| receipt.logs())
209 }
210
211 pub fn blocks_iter(&self) -> impl Iterator<Item = &Arc<RecoveredBlock<N::Block>>> + '_ {
213 self.blocks().values()
214 }
215
216 pub fn transactions_iter(&self) -> impl Iterator<Item = &N::SignedTx> + '_ {
218 self.blocks_iter().flat_map(|block| block.body().transactions())
219 }
220
221 pub fn transaction_hashes(&self) -> impl Iterator<Item = &TxHash> + '_ {
223 self.transactions_iter().map(|tx| tx.tx_hash())
224 }
225
226 pub fn transactions_recovered_iter(
228 &self,
229 ) -> impl Iterator<Item = Recovered<&N::SignedTx>> + '_ {
230 self.blocks_iter().flat_map(|block| block.transactions_recovered())
231 }
232
233 pub fn blocks_and_receipts(
235 &self,
236 ) -> impl Iterator<Item = (&Arc<RecoveredBlock<N::Block>>, &Vec<N::Receipt>)> + '_ {
237 self.blocks_iter().zip(self.block_receipts_iter())
238 }
239
240 pub fn find_transaction_and_receipt_by_hash(
244 &self,
245 tx_hash: TxHash,
246 ) -> Option<ChainTxReceiptMeta<'_, N>> {
247 for (block, receipts) in self.blocks_and_receipts() {
248 let Some(indexed_tx) = block.find_indexed(tx_hash) else {
249 continue;
250 };
251 let receipt = receipts.get(indexed_tx.index())?;
252 return Some((block, indexed_tx, receipt, receipts.as_slice()));
253 }
254
255 None
256 }
257
258 pub fn fork_block(&self) -> ForkBlock {
260 let first = self.first();
261 ForkBlock {
262 number: first.header().number().saturating_sub(1),
263 hash: first.header().parent_hash(),
264 }
265 }
266
267 #[track_caller]
273 pub fn first(&self) -> &RecoveredBlock<N::Block> {
274 self.blocks.first_key_value().expect("Chain should have at least one block").1
275 }
276
277 #[track_caller]
283 pub fn tip(&self) -> &RecoveredBlock<N::Block> {
284 self.blocks.last_key_value().expect("Chain should have at least one block").1
285 }
286
287 pub fn len(&self) -> usize {
289 self.blocks.len()
290 }
291
292 pub fn range(&self) -> RangeInclusive<BlockNumber> {
298 self.first().header().number()..=self.tip().header().number()
299 }
300
301 pub fn receipts_by_block_hash(&self, block_hash: BlockHash) -> Option<Vec<&N::Receipt>> {
303 let num = self.block_number(block_hash)?;
304 Some(self.execution_outcome.receipts_by_block(num).iter().collect())
305 }
306
307 pub fn receipts_with_attachment(&self) -> Vec<BlockReceipts<N::Receipt>> {
311 let mut receipt_attach = Vec::with_capacity(self.blocks().len());
312
313 self.blocks_and_receipts().for_each(|(block, receipts)| {
314 let block_num_hash = BlockNumHash::new(block.number(), block.hash());
315
316 let tx_receipts = block
317 .body()
318 .transactions()
319 .iter()
320 .zip(receipts)
321 .map(|(tx, receipt)| (*tx.tx_hash(), receipt.clone()))
322 .collect();
323
324 receipt_attach.push(BlockReceipts {
325 block: block_num_hash,
326 tx_receipts,
327 timestamp: block.timestamp(),
328 });
329 });
330
331 receipt_attach
332 }
333
334 pub fn append_block(
337 &mut self,
338 block: impl Into<Arc<RecoveredBlock<N::Block>>>,
339 execution_outcome: ExecutionOutcome<N::Receipt>,
340 trie_data: LazyTrieData,
341 ) {
342 let block = block.into();
343 let block_number = block.header().number();
344 self.blocks.insert(block_number, block);
345 self.execution_outcome.extend(execution_outcome);
346 self.trie_data.insert(block_number, trie_data);
347 }
348
349 pub fn append_chain(&mut self, other: Self) -> Result<(), Self> {
356 let chain_tip = self.tip();
357 let other_fork_block = other.fork_block();
358 if chain_tip.hash() != other_fork_block.hash {
359 return Err(other)
360 }
361
362 self.blocks.extend(other.blocks);
364 self.execution_outcome.extend(other.execution_outcome);
365 self.trie_data.extend(other.trie_data);
366
367 Ok(())
368 }
369}
370
371#[derive(Debug)]
373pub struct DisplayBlocksChain<'a, B: reth_primitives_traits::Block>(
374 pub &'a BTreeMap<BlockNumber, Arc<RecoveredBlock<B>>>,
375);
376
377impl<B: reth_primitives_traits::Block> fmt::Display for DisplayBlocksChain<'_, B> {
378 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
379 let mut list = f.debug_list();
380 let mut values = self.0.values().map(|block| block.num_hash());
381 if values.len() <= 3 {
382 list.entries(values);
383 } else {
384 list.entry(&values.next().unwrap());
385 list.entry(&format_args!("..."));
386 list.entry(&values.next_back().unwrap());
387 }
388 list.finish()
389 }
390}
391
392#[derive(Clone, Debug, Default, PartialEq, Eq)]
394pub struct ChainBlocks<'a, B: Block> {
395 blocks: Cow<'a, BTreeMap<BlockNumber, Arc<RecoveredBlock<B>>>>,
396}
397
398impl<B: Block<Body: BlockBody<Transaction: SignedTransaction>>> ChainBlocks<'_, B> {
399 #[inline]
403 pub fn into_blocks(self) -> impl Iterator<Item = Arc<RecoveredBlock<B>>> {
404 self.blocks.into_owned().into_values()
405 }
406
407 #[inline]
409 pub fn iter(&self) -> impl Iterator<Item = (&BlockNumber, &RecoveredBlock<B>)> {
410 self.blocks.iter().map(|(number, block)| (number, block.as_ref()))
411 }
412
413 #[inline]
419 pub fn tip(&self) -> &RecoveredBlock<B> {
420 self.blocks.last_key_value().expect("Chain should have at least one block").1.as_ref()
421 }
422
423 #[inline]
429 pub fn first(&self) -> &RecoveredBlock<B> {
430 self.blocks.first_key_value().expect("Chain should have at least one block").1.as_ref()
431 }
432
433 #[inline]
435 pub fn transactions(&self) -> impl Iterator<Item = &<B::Body as BlockBody>::Transaction> + '_ {
436 self.blocks.values().flat_map(|block| block.body().transactions_iter())
437 }
438
439 #[inline]
441 pub fn transactions_with_sender(
442 &self,
443 ) -> impl Iterator<Item = (&Address, &<B::Body as BlockBody>::Transaction)> + '_ {
444 self.blocks.values().flat_map(|block| block.transactions_with_sender())
445 }
446
447 #[inline]
451 pub fn transactions_ecrecovered(
452 &self,
453 ) -> impl Iterator<Item = Recovered<<B::Body as BlockBody>::Transaction>> + '_ {
454 self.transactions_with_sender().map(|(signer, tx)| tx.clone().with_signer(*signer))
455 }
456
457 #[inline]
459 pub fn transaction_hashes(&self) -> impl Iterator<Item = TxHash> + '_ {
460 self.blocks
461 .values()
462 .flat_map(|block| block.body().transactions_iter().map(|tx| *tx.tx_hash()))
463 }
464
465 #[inline]
467 pub fn transaction_hashes_vec(&self) -> Vec<TxHash> {
468 let capacity = self.blocks.values().map(|block| block.body().transactions().len()).sum();
469
470 let mut hashes = Vec::with_capacity(capacity);
471 hashes.extend(self.transaction_hashes());
472 hashes
473 }
474
475 #[inline]
477 pub fn transaction_hashes_set(&self) -> HashSet<TxHash> {
478 let capacity = self.blocks.values().map(|block| block.body().transactions().len()).sum();
479
480 let mut hashes = HashSet::with_capacity_and_hasher(capacity, Default::default());
481 hashes.extend(self.transaction_hashes());
482 hashes
483 }
484}
485
486impl<B: Block> IntoIterator for ChainBlocks<'_, B> {
487 type Item = (BlockNumber, Arc<RecoveredBlock<B>>);
488 type IntoIter = alloc::collections::btree_map::IntoIter<BlockNumber, Arc<RecoveredBlock<B>>>;
489
490 fn into_iter(self) -> Self::IntoIter {
491 self.blocks.into_owned().into_iter()
492 }
493}
494
495#[derive(Default, Clone, Debug, PartialEq, Eq)]
497pub struct BlockReceipts<T = reth_ethereum_primitives::Receipt> {
498 pub block: BlockNumHash,
500 pub tx_receipts: Vec<(TxHash, T)>,
502 pub timestamp: u64,
504}
505
506#[cfg(feature = "serde-bincode-compat")]
508pub(super) mod serde_bincode_compat {
509 use crate::serde_bincode_compat;
510 use alloc::{collections::BTreeMap, sync::Arc, vec::Vec};
511 use alloy_primitives::{Address, BlockNumber, Bytes};
512 use alloy_rlp::Decodable;
513 use core::marker::PhantomData;
514 use reth_ethereum_primitives::EthPrimitives;
515 use reth_primitives_traits::{NodePrimitives, SealedBlock};
516 use reth_trie_common::ComputedTrieData;
517 use serde::{Deserialize, Deserializer, Serialize, Serializer};
518 use serde_with::{DeserializeAs, SerializeAs};
519
520 #[derive(Debug, Serialize, Deserialize)]
536 #[serde(bound = "")]
537 pub struct Chain<'a, N = EthPrimitives>
538 where
539 N: NodePrimitives,
540 {
541 #[serde(skip)]
542 _phantom: PhantomData<N>,
543 blocks: BTreeMap<BlockNumber, RecoveredBlockRepr>,
544 execution_outcome: serde_bincode_compat::ExecutionOutcome<'a>,
545 #[serde(default)]
546 trie_updates: BTreeMap<
547 BlockNumber,
548 reth_trie_common::serde_bincode_compat::updates::TrieUpdatesSorted<'a>,
549 >,
550 #[serde(default)]
551 hashed_state: BTreeMap<
552 BlockNumber,
553 reth_trie_common::serde_bincode_compat::hashed_state::HashedPostStateSorted<'a>,
554 >,
555 }
556
557 #[derive(Debug, Serialize, Deserialize)]
558 struct RecoveredBlockRepr {
559 rlp: Bytes,
560 senders: Vec<Address>,
561 }
562
563 impl<'a, N> From<&'a super::Chain<N>> for Chain<'a, N>
564 where
565 N: NodePrimitives,
566 {
567 fn from(value: &'a super::Chain<N>) -> Self {
568 Self {
569 _phantom: PhantomData,
570 blocks: value
571 .blocks
572 .iter()
573 .map(|(num, recovered)| {
574 let senders = recovered.senders().to_vec();
575 let rlp = Bytes::from(alloy_rlp::encode(recovered.sealed_block()));
576 (*num, RecoveredBlockRepr { rlp, senders })
577 })
578 .collect(),
579 execution_outcome: (&value.execution_outcome).into(),
580 trie_updates: value
581 .trie_data
582 .iter()
583 .map(|(k, v)| (*k, v.get().sorted.trie_updates.as_ref().into()))
584 .collect(),
585 hashed_state: value
586 .trie_data
587 .iter()
588 .map(|(k, v)| (*k, v.get().sorted.hashed_state.as_ref().into()))
589 .collect(),
590 }
591 }
592 }
593
594 impl<'a, N> From<Chain<'a, N>> for super::Chain<N>
595 where
596 N: NodePrimitives,
597 {
598 fn from(value: Chain<'a, N>) -> Self {
599 use reth_primitives_traits::RecoveredBlock;
600 use reth_trie_common::LazyTrieData;
601
602 let hashed_state_map: BTreeMap<_, _> =
603 value.hashed_state.into_iter().map(|(k, v)| (k, Arc::new(v.into()))).collect();
604
605 let trie_data: BTreeMap<BlockNumber, LazyTrieData> = value
606 .trie_updates
607 .into_iter()
608 .map(|(k, v)| {
609 let hashed_state = hashed_state_map.get(&k).cloned().unwrap_or_default();
610 (
611 k,
612 LazyTrieData::ready(ComputedTrieData::new(
613 hashed_state,
614 Arc::new(v.into()),
615 )),
616 )
617 })
618 .collect();
619
620 let blocks = value
621 .blocks
622 .into_iter()
623 .map(|(num, repr)| {
624 let block = N::Block::decode(&mut repr.rlp.as_ref())
625 .expect("invalid RLP for block in serde_bincode_compat");
626 let sealed = SealedBlock::new_unhashed(block);
627 (num, Arc::new(RecoveredBlock::new_sealed(sealed, repr.senders)))
628 })
629 .collect();
630
631 Self { blocks, execution_outcome: value.execution_outcome.into(), trie_data }
632 }
633 }
634
635 impl<N> SerializeAs<super::Chain<N>> for Chain<'_, N>
636 where
637 N: NodePrimitives,
638 {
639 fn serialize_as<S>(source: &super::Chain<N>, serializer: S) -> Result<S::Ok, S::Error>
640 where
641 S: Serializer,
642 {
643 Chain::from(source).serialize(serializer)
644 }
645 }
646
647 impl<'de, N> DeserializeAs<'de, super::Chain<N>> for Chain<'de, N>
648 where
649 N: NodePrimitives,
650 {
651 fn deserialize_as<D>(deserializer: D) -> Result<super::Chain<N>, D::Error>
652 where
653 D: Deserializer<'de>,
654 {
655 Chain::deserialize(deserializer).map(Into::into)
656 }
657 }
658
659 #[cfg(test)]
660 mod tests {
661 use super::super::{serde_bincode_compat, Chain};
662 use arbitrary::Arbitrary;
663 use rand::Rng;
664 use reth_primitives_traits::RecoveredBlock;
665 use serde::{Deserialize, Serialize};
666 use serde_with::serde_as;
667
668 #[test]
669 fn test_chain_bincode_roundtrip() {
670 use alloc::collections::BTreeMap;
671
672 #[serde_as]
673 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
674 struct Data {
675 #[serde_as(as = "serde_bincode_compat::Chain")]
676 chain: Chain,
677 }
678
679 let mut bytes = [0u8; 1024];
680 rand::rng().fill(bytes.as_mut_slice());
681 let data = Data {
682 chain: Chain::new(
683 vec![RecoveredBlock::arbitrary(&mut arbitrary::Unstructured::new(&bytes))
684 .unwrap()],
685 Default::default(),
686 BTreeMap::new(),
687 ),
688 };
689
690 let encoded = bincode::serialize(&data).unwrap();
691 let decoded: Data = bincode::deserialize(&encoded).unwrap();
692 assert_eq!(decoded, data);
693 }
694 }
695}
696
697#[cfg(test)]
698mod tests {
699 use super::*;
700 use alloy_consensus::TxType;
701 use alloy_primitives::{map::HashMap, Address, B256};
702 use reth_ethereum_primitives::Receipt;
703 use revm::{database::BundleState, state::AccountInfo};
704
705 #[test]
706 fn chain_append() {
707 let block: RecoveredBlock<reth_ethereum_primitives::Block> = Default::default();
708 let block1_hash = B256::new([0x01; 32]);
709 let block2_hash = B256::new([0x02; 32]);
710 let block3_hash = B256::new([0x03; 32]);
711 let block4_hash = B256::new([0x04; 32]);
712
713 let mut block1 = block.clone();
714 let mut block2 = block.clone();
715 let mut block3 = block.clone();
716 let mut block4 = block;
717
718 block1.set_hash(block1_hash);
719 block2.set_hash(block2_hash);
720 block3.set_hash(block3_hash);
721 block4.set_hash(block4_hash);
722
723 block3.set_parent_hash(block2_hash);
724
725 let mut chain1: Chain = Chain {
726 blocks: BTreeMap::from([(1, Arc::new(block1)), (2, Arc::new(block2))]),
727 ..Default::default()
728 };
729
730 let chain2 = Chain {
731 blocks: BTreeMap::from([(3, Arc::new(block3)), (4, Arc::new(block4))]),
732 ..Default::default()
733 };
734
735 assert!(chain1.append_chain(chain2.clone()).is_ok());
736
737 assert!(chain1.append_chain(chain2).is_err());
739 }
740
741 #[test]
742 fn test_number_split() {
743 let execution_outcome1: ExecutionOutcome = ExecutionOutcome::new(
744 BundleState::new(
745 vec![(
746 Address::new([2; 20]),
747 None,
748 Some(AccountInfo::default()),
749 HashMap::default(),
750 )],
751 vec![vec![(Address::new([2; 20]), None, vec![])]],
752 vec![],
753 ),
754 vec![vec![]],
755 1,
756 vec![],
757 );
758
759 let execution_outcome2 = ExecutionOutcome::new(
760 BundleState::new(
761 vec![(
762 Address::new([3; 20]),
763 None,
764 Some(AccountInfo::default()),
765 HashMap::default(),
766 )],
767 vec![vec![(Address::new([3; 20]), None, vec![])]],
768 vec![],
769 ),
770 vec![vec![]],
771 2,
772 vec![],
773 );
774
775 let mut block1: RecoveredBlock<reth_ethereum_primitives::Block> = Default::default();
776 let block1_hash = B256::new([15; 32]);
777 block1.set_block_number(1);
778 block1.set_hash(block1_hash);
779 block1.push_sender(Address::new([4; 20]));
780
781 let mut block2: RecoveredBlock<reth_ethereum_primitives::Block> = Default::default();
782 let block2_hash = B256::new([16; 32]);
783 block2.set_block_number(2);
784 block2.set_hash(block2_hash);
785 block2.push_sender(Address::new([4; 20]));
786
787 let mut block_state_extended = execution_outcome1;
788 block_state_extended.extend(execution_outcome2);
789
790 let chain: Chain =
791 Chain::new(vec![block1.clone(), block2.clone()], block_state_extended, BTreeMap::new());
792
793 assert_eq!(
795 chain.execution_outcome_at_block(block2.number),
796 Some(chain.execution_outcome.clone())
797 );
798 assert_eq!(chain.execution_outcome_at_block(100), None);
800 }
801
802 #[test]
803 fn receipts_by_block_hash() {
804 let block: RecoveredBlock<reth_ethereum_primitives::Block> = Default::default();
806
807 let block1_hash = B256::new([0x01; 32]);
809 let block2_hash = B256::new([0x02; 32]);
810
811 let mut block1 = block.clone();
813 let mut block2 = block;
814
815 block1.set_hash(block1_hash);
817 block2.set_hash(block2_hash);
818
819 let receipt1 = Receipt {
821 tx_type: TxType::Legacy,
822 cumulative_gas_used: 46913,
823 logs: vec![],
824 success: true,
825 };
826
827 let receipt2 = Receipt {
829 tx_type: TxType::Legacy,
830 cumulative_gas_used: 1325345,
831 logs: vec![],
832 success: true,
833 };
834
835 let receipts = vec![vec![receipt1.clone()], vec![receipt2]];
837
838 let execution_outcome = ExecutionOutcome {
841 bundle: Default::default(),
842 receipts,
843 requests: vec![],
844 first_block: 10,
845 };
846
847 let chain: Chain = Chain {
850 blocks: BTreeMap::from([(10, Arc::new(block1)), (11, Arc::new(block2))]),
851 execution_outcome: execution_outcome.clone(),
852 ..Default::default()
853 };
854
855 assert_eq!(chain.receipts_by_block_hash(block1_hash), Some(vec![&receipt1]));
857
858 let execution_outcome1 = ExecutionOutcome {
860 bundle: Default::default(),
861 receipts: vec![vec![receipt1]],
862 requests: vec![],
863 first_block: 10,
864 };
865
866 assert_eq!(chain.execution_outcome_at_block(10), Some(execution_outcome1));
868
869 assert_eq!(chain.execution_outcome_at_block(11), Some(execution_outcome));
871 }
872}