reth_execution_types/
chain.rs

1//! Contains [Chain], a chain of blocks and their final state.
2
3use 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/// A chain of blocks and their final state.
17///
18/// The chain contains the state of accounts after execution of its blocks,
19/// changesets for those blocks (and their transactions), as well as the blocks themselves.
20///
21/// Used inside the `BlockchainTree`.
22///
23/// # Warning
24///
25/// A chain of blocks should not be empty.
26#[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    /// All blocks in this chain.
30    blocks: BTreeMap<BlockNumber, RecoveredBlock<N::Block>>,
31    /// The outcome of block execution for this chain.
32    ///
33    /// This field contains the state of all accounts after the execution of all blocks in this
34    /// chain, ranging from the [`Chain::first`] block to the [`Chain::tip`] block, inclusive.
35    ///
36    /// Additionally, it includes the individual state changes that led to the current state.
37    execution_outcome: ExecutionOutcome<N::Receipt>,
38    /// State trie updates after block is added to the chain.
39    /// NOTE: Currently, trie updates are present only for
40    /// single-block chains that extend the canonical chain.
41    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    /// Create new Chain from blocks and state.
56    ///
57    /// # Warning
58    ///
59    /// A chain of blocks should not be empty.
60    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    /// Create new Chain from a single block and its state.
73    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    /// Get the blocks in this chain.
82    pub const fn blocks(&self) -> &BTreeMap<BlockNumber, RecoveredBlock<N::Block>> {
83        &self.blocks
84    }
85
86    /// Consumes the type and only returns the blocks in this chain.
87    pub fn into_blocks(self) -> BTreeMap<BlockNumber, RecoveredBlock<N::Block>> {
88        self.blocks
89    }
90
91    /// Returns an iterator over all headers in the block with increasing block numbers.
92    pub fn headers(&self) -> impl Iterator<Item = SealedHeader<N::BlockHeader>> + '_ {
93        self.blocks.values().map(|block| block.clone_sealed_header())
94    }
95
96    /// Get cached trie updates for this chain.
97    pub const fn trie_updates(&self) -> Option<&TrieUpdates> {
98        self.trie_updates.as_ref()
99    }
100
101    /// Remove cached trie updates for this chain.
102    pub fn clear_trie_updates(&mut self) {
103        self.trie_updates.take();
104    }
105
106    /// Get execution outcome of this chain
107    pub const fn execution_outcome(&self) -> &ExecutionOutcome<N::Receipt> {
108        &self.execution_outcome
109    }
110
111    /// Get mutable execution outcome of this chain
112    pub const fn execution_outcome_mut(&mut self) -> &mut ExecutionOutcome<N::Receipt> {
113        &mut self.execution_outcome
114    }
115
116    /// Prepends the given state to the current state.
117    pub fn prepend_state(&mut self, state: BundleState) {
118        self.execution_outcome.prepend_state(state);
119        self.trie_updates.take(); // invalidate cached trie updates
120    }
121
122    /// Return true if chain is empty and has no blocks.
123    pub fn is_empty(&self) -> bool {
124        self.blocks.is_empty()
125    }
126
127    /// Return block number of the block hash.
128    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    /// Returns the block with matching hash.
133    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    /// Return execution outcome at the `block_number` or None if block is not known
138    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    /// Destructure the chain into its inner components:
155    /// 1. The blocks contained in the chain.
156    /// 2. The execution outcome representing the final state.
157    /// 3. The optional trie updates.
158    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    /// Destructure the chain into its inner components:
165    /// 1. A reference to the blocks contained in the chain.
166    /// 2. A reference to the execution outcome representing the final state.
167    pub const fn inner(&self) -> (ChainBlocks<'_, N::Block>, &ExecutionOutcome<N::Receipt>) {
168        (ChainBlocks { blocks: Cow::Borrowed(&self.blocks) }, &self.execution_outcome)
169    }
170
171    /// Returns an iterator over all the receipts of the blocks in the chain.
172    pub fn block_receipts_iter(&self) -> impl Iterator<Item = &Vec<N::Receipt>> + '_ {
173        self.execution_outcome.receipts().iter()
174    }
175
176    /// Returns an iterator over all blocks in the chain with increasing block number.
177    pub fn blocks_iter(&self) -> impl Iterator<Item = &RecoveredBlock<N::Block>> + '_ {
178        self.blocks().iter().map(|block| block.1)
179    }
180
181    /// Returns an iterator over all blocks and their receipts in the chain.
182    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    /// Get the block at which this chain forked.
189    pub fn fork_block(&self) -> ForkBlock {
190        let first = self.first();
191        ForkBlock {
192            number: first.header().number().saturating_sub(1),
193            hash: first.header().parent_hash(),
194        }
195    }
196
197    /// Get the first block in this chain.
198    ///
199    /// # Panics
200    ///
201    /// If chain doesn't have any blocks.
202    #[track_caller]
203    pub fn first(&self) -> &RecoveredBlock<N::Block> {
204        self.blocks.first_key_value().expect("Chain should have at least one block").1
205    }
206
207    /// Get the tip of the chain.
208    ///
209    /// # Panics
210    ///
211    /// If chain doesn't have any blocks.
212    #[track_caller]
213    pub fn tip(&self) -> &RecoveredBlock<N::Block> {
214        self.blocks.last_key_value().expect("Chain should have at least one block").1
215    }
216
217    /// Returns length of the chain.
218    pub fn len(&self) -> usize {
219        self.blocks.len()
220    }
221
222    /// Returns the range of block numbers in the chain.
223    ///
224    /// # Panics
225    ///
226    /// If chain doesn't have any blocks.
227    pub fn range(&self) -> RangeInclusive<BlockNumber> {
228        self.first().header().number()..=self.tip().header().number()
229    }
230
231    /// Get all receipts for the given block.
232    pub fn receipts_by_block_hash(&self, block_hash: BlockHash) -> Option<Vec<&N::Receipt>> {
233        let num = self.block_number(block_hash)?;
234        Some(self.execution_outcome.receipts_by_block(num).iter().collect())
235    }
236
237    /// Get all receipts with attachment.
238    ///
239    /// Attachment includes block number, block hash, transaction hash and transaction index.
240    pub fn receipts_with_attachment(&self) -> Vec<BlockReceipts<N::Receipt>>
241    where
242        N::SignedTx: Encodable2718,
243    {
244        let mut receipt_attach = Vec::with_capacity(self.blocks().len());
245        for ((block_num, block), receipts) in
246            self.blocks().iter().zip(self.execution_outcome.receipts().iter())
247        {
248            let mut tx_receipts = Vec::with_capacity(receipts.len());
249            for (tx, receipt) in block.body().transactions().iter().zip(receipts.iter()) {
250                tx_receipts.push((tx.trie_hash(), receipt.clone()));
251            }
252            let block_num_hash = BlockNumHash::new(*block_num, block.hash());
253            receipt_attach.push(BlockReceipts { block: block_num_hash, tx_receipts });
254        }
255        receipt_attach
256    }
257
258    /// Append a single block with state to the chain.
259    /// This method assumes that blocks attachment to the chain has already been validated.
260    pub fn append_block(
261        &mut self,
262        block: RecoveredBlock<N::Block>,
263        execution_outcome: ExecutionOutcome<N::Receipt>,
264    ) {
265        self.blocks.insert(block.header().number(), block);
266        self.execution_outcome.extend(execution_outcome);
267        self.trie_updates.take(); // reset
268    }
269
270    /// Merge two chains by appending the given chain into the current one.
271    ///
272    /// The state of accounts for this chain is set to the state of the newest chain.
273    ///
274    /// Returns the passed `other` chain in [`Result::Err`] variant if the chains could not be
275    /// connected.
276    pub fn append_chain(&mut self, other: Self) -> Result<(), Self> {
277        let chain_tip = self.tip();
278        let other_fork_block = other.fork_block();
279        if chain_tip.hash() != other_fork_block.hash {
280            return Err(other)
281        }
282
283        // Insert blocks from other chain
284        self.blocks.extend(other.blocks);
285        self.execution_outcome.extend(other.execution_outcome);
286        self.trie_updates.take(); // reset
287
288        Ok(())
289    }
290}
291
292/// Wrapper type for `blocks` display in `Chain`
293#[derive(Debug)]
294pub struct DisplayBlocksChain<'a, B: reth_primitives_traits::Block>(
295    pub &'a BTreeMap<BlockNumber, RecoveredBlock<B>>,
296);
297
298impl<B: reth_primitives_traits::Block> fmt::Display for DisplayBlocksChain<'_, B> {
299    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
300        let mut list = f.debug_list();
301        let mut values = self.0.values().map(|block| block.num_hash());
302        if values.len() <= 3 {
303            list.entries(values);
304        } else {
305            list.entry(&values.next().unwrap());
306            list.entry(&format_args!("..."));
307            list.entry(&values.next_back().unwrap());
308        }
309        list.finish()
310    }
311}
312
313/// All blocks in the chain
314#[derive(Clone, Debug, Default, PartialEq, Eq)]
315pub struct ChainBlocks<'a, B: Block> {
316    blocks: Cow<'a, BTreeMap<BlockNumber, RecoveredBlock<B>>>,
317}
318
319impl<B: Block<Body: BlockBody<Transaction: SignedTransaction>>> ChainBlocks<'_, B> {
320    /// Creates a consuming iterator over all blocks in the chain with increasing block number.
321    ///
322    /// Note: this always yields at least one block.
323    #[inline]
324    pub fn into_blocks(self) -> impl Iterator<Item = RecoveredBlock<B>> {
325        self.blocks.into_owned().into_values()
326    }
327
328    /// Creates an iterator over all blocks in the chain with increasing block number.
329    #[inline]
330    pub fn iter(&self) -> impl Iterator<Item = (&BlockNumber, &RecoveredBlock<B>)> {
331        self.blocks.iter()
332    }
333
334    /// Get the tip of the chain.
335    ///
336    /// # Note
337    ///
338    /// Chains always have at least one block.
339    #[inline]
340    pub fn tip(&self) -> &RecoveredBlock<B> {
341        self.blocks.last_key_value().expect("Chain should have at least one block").1
342    }
343
344    /// Get the _first_ block of the chain.
345    ///
346    /// # Note
347    ///
348    /// Chains always have at least one block.
349    #[inline]
350    pub fn first(&self) -> &RecoveredBlock<B> {
351        self.blocks.first_key_value().expect("Chain should have at least one block").1
352    }
353
354    /// Returns an iterator over all transactions in the chain.
355    #[inline]
356    pub fn transactions(&self) -> impl Iterator<Item = &<B::Body as BlockBody>::Transaction> + '_ {
357        self.blocks.values().flat_map(|block| block.body().transactions_iter())
358    }
359
360    /// Returns an iterator over all transactions and their senders.
361    #[inline]
362    pub fn transactions_with_sender(
363        &self,
364    ) -> impl Iterator<Item = (&Address, &<B::Body as BlockBody>::Transaction)> + '_ {
365        self.blocks.values().flat_map(|block| block.transactions_with_sender())
366    }
367
368    /// Returns an iterator over all [`Recovered`] in the blocks
369    ///
370    /// Note: This clones the transactions since it is assumed this is part of a shared [Chain].
371    #[inline]
372    pub fn transactions_ecrecovered(
373        &self,
374    ) -> impl Iterator<Item = Recovered<<B::Body as BlockBody>::Transaction>> + '_ {
375        self.transactions_with_sender().map(|(signer, tx)| tx.clone().with_signer(*signer))
376    }
377
378    /// Returns an iterator over all transaction hashes in the block
379    #[inline]
380    pub fn transaction_hashes(&self) -> impl Iterator<Item = TxHash> + '_ {
381        self.blocks
382            .values()
383            .flat_map(|block| block.body().transactions_iter().map(|tx| tx.trie_hash()))
384    }
385}
386
387impl<B: Block> IntoIterator for ChainBlocks<'_, B> {
388    type Item = (BlockNumber, RecoveredBlock<B>);
389    type IntoIter = alloc::collections::btree_map::IntoIter<BlockNumber, RecoveredBlock<B>>;
390
391    fn into_iter(self) -> Self::IntoIter {
392        self.blocks.into_owned().into_iter()
393    }
394}
395
396/// Used to hold receipts and their attachment.
397#[derive(Default, Clone, Debug, PartialEq, Eq)]
398pub struct BlockReceipts<T = reth_ethereum_primitives::Receipt> {
399    /// Block identifier
400    pub block: BlockNumHash,
401    /// Transaction identifier and receipt.
402    pub tx_receipts: Vec<(TxHash, T)>,
403}
404
405/// Bincode-compatible [`Chain`] serde implementation.
406#[cfg(feature = "serde-bincode-compat")]
407pub(super) mod serde_bincode_compat {
408    use crate::{serde_bincode_compat, ExecutionOutcome};
409    use alloc::{borrow::Cow, collections::BTreeMap};
410    use alloy_primitives::BlockNumber;
411    use reth_ethereum_primitives::EthPrimitives;
412    use reth_primitives_traits::{
413        serde_bincode_compat::{RecoveredBlock, SerdeBincodeCompat},
414        Block, NodePrimitives,
415    };
416    use reth_trie_common::serde_bincode_compat::updates::TrieUpdates;
417    use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
418    use serde_with::{DeserializeAs, SerializeAs};
419
420    /// Bincode-compatible [`super::Chain`] serde implementation.
421    ///
422    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
423    /// ```rust
424    /// use reth_execution_types::{serde_bincode_compat, Chain};
425    /// use serde::{Deserialize, Serialize};
426    /// use serde_with::serde_as;
427    ///
428    /// #[serde_as]
429    /// #[derive(Serialize, Deserialize)]
430    /// struct Data {
431    ///     #[serde_as(as = "serde_bincode_compat::Chain")]
432    ///     chain: Chain,
433    /// }
434    /// ```
435    #[derive(Debug, Serialize, Deserialize)]
436    pub struct Chain<'a, N = EthPrimitives>
437    where
438        N: NodePrimitives<
439            Block: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static,
440        >,
441    {
442        blocks: RecoveredBlocks<'a, N::Block>,
443        execution_outcome: serde_bincode_compat::ExecutionOutcome<'a, N::Receipt>,
444        trie_updates: Option<TrieUpdates<'a>>,
445    }
446
447    #[derive(Debug)]
448    struct RecoveredBlocks<
449        'a,
450        B: reth_primitives_traits::Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat>
451            + 'static,
452    >(Cow<'a, BTreeMap<BlockNumber, reth_primitives_traits::RecoveredBlock<B>>>);
453
454    impl<B> Serialize for RecoveredBlocks<'_, B>
455    where
456        B: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static,
457    {
458        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
459        where
460            S: Serializer,
461        {
462            let mut state = serializer.serialize_map(Some(self.0.len()))?;
463
464            for (block_number, block) in self.0.iter() {
465                state.serialize_entry(block_number, &RecoveredBlock::<'_, B>::from(block))?;
466            }
467
468            state.end()
469        }
470    }
471
472    impl<'de, B> Deserialize<'de> for RecoveredBlocks<'_, B>
473    where
474        B: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static,
475    {
476        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
477        where
478            D: Deserializer<'de>,
479        {
480            Ok(Self(Cow::Owned(
481                BTreeMap::<BlockNumber, RecoveredBlock<'_, B>>::deserialize(deserializer)
482                    .map(|blocks| blocks.into_iter().map(|(n, b)| (n, b.into())).collect())?,
483            )))
484        }
485    }
486
487    impl<'a, N> From<&'a super::Chain<N>> for Chain<'a, N>
488    where
489        N: NodePrimitives<
490            Block: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static,
491        >,
492    {
493        fn from(value: &'a super::Chain<N>) -> Self {
494            Self {
495                blocks: RecoveredBlocks(Cow::Borrowed(&value.blocks)),
496                execution_outcome: value.execution_outcome.as_repr(),
497                trie_updates: value.trie_updates.as_ref().map(Into::into),
498            }
499        }
500    }
501
502    impl<'a, N> From<Chain<'a, N>> for super::Chain<N>
503    where
504        N: NodePrimitives<
505            Block: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static,
506        >,
507    {
508        fn from(value: Chain<'a, N>) -> Self {
509            Self {
510                blocks: value.blocks.0.into_owned(),
511                execution_outcome: ExecutionOutcome::from_repr(value.execution_outcome),
512                trie_updates: value.trie_updates.map(Into::into),
513            }
514        }
515    }
516
517    impl<N> SerializeAs<super::Chain<N>> for Chain<'_, N>
518    where
519        N: NodePrimitives<
520            Block: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static,
521        >,
522    {
523        fn serialize_as<S>(source: &super::Chain<N>, serializer: S) -> Result<S::Ok, S::Error>
524        where
525            S: Serializer,
526        {
527            Chain::from(source).serialize(serializer)
528        }
529    }
530
531    impl<'de, N> DeserializeAs<'de, super::Chain<N>> for Chain<'de, N>
532    where
533        N: NodePrimitives<
534            Block: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static,
535        >,
536    {
537        fn deserialize_as<D>(deserializer: D) -> Result<super::Chain<N>, D::Error>
538        where
539            D: Deserializer<'de>,
540        {
541            Chain::deserialize(deserializer).map(Into::into)
542        }
543    }
544
545    #[cfg(test)]
546    mod tests {
547        use super::super::{serde_bincode_compat, Chain};
548        use arbitrary::Arbitrary;
549        use rand::Rng;
550        use reth_primitives_traits::RecoveredBlock;
551        use serde::{Deserialize, Serialize};
552        use serde_with::serde_as;
553
554        #[test]
555        fn test_chain_bincode_roundtrip() {
556            #[serde_as]
557            #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
558            struct Data {
559                #[serde_as(as = "serde_bincode_compat::Chain")]
560                chain: Chain,
561            }
562
563            let mut bytes = [0u8; 1024];
564            rand::rng().fill(bytes.as_mut_slice());
565            let data = Data {
566                chain: Chain::new(
567                    vec![RecoveredBlock::arbitrary(&mut arbitrary::Unstructured::new(&bytes))
568                        .unwrap()],
569                    Default::default(),
570                    None,
571                ),
572            };
573
574            let encoded = bincode::serialize(&data).unwrap();
575            let decoded: Data = bincode::deserialize(&encoded).unwrap();
576            assert_eq!(decoded, data);
577        }
578    }
579}
580
581#[cfg(test)]
582mod tests {
583    use super::*;
584    use alloy_consensus::TxType;
585    use alloy_primitives::{Address, B256};
586    use reth_ethereum_primitives::Receipt;
587    use revm::{primitives::HashMap, state::AccountInfo};
588
589    #[test]
590    fn chain_append() {
591        let block: RecoveredBlock<reth_ethereum_primitives::Block> = Default::default();
592        let block1_hash = B256::new([0x01; 32]);
593        let block2_hash = B256::new([0x02; 32]);
594        let block3_hash = B256::new([0x03; 32]);
595        let block4_hash = B256::new([0x04; 32]);
596
597        let mut block1 = block.clone();
598        let mut block2 = block.clone();
599        let mut block3 = block.clone();
600        let mut block4 = block;
601
602        block1.set_hash(block1_hash);
603        block2.set_hash(block2_hash);
604        block3.set_hash(block3_hash);
605        block4.set_hash(block4_hash);
606
607        block3.set_parent_hash(block2_hash);
608
609        let mut chain1: Chain =
610            Chain { blocks: BTreeMap::from([(1, block1), (2, block2)]), ..Default::default() };
611
612        let chain2 =
613            Chain { blocks: BTreeMap::from([(3, block3), (4, block4)]), ..Default::default() };
614
615        assert!(chain1.append_chain(chain2.clone()).is_ok());
616
617        // chain1 got changed so this will fail
618        assert!(chain1.append_chain(chain2).is_err());
619    }
620
621    #[test]
622    fn test_number_split() {
623        let execution_outcome1: ExecutionOutcome = ExecutionOutcome::new(
624            BundleState::new(
625                vec![(
626                    Address::new([2; 20]),
627                    None,
628                    Some(AccountInfo::default()),
629                    HashMap::default(),
630                )],
631                vec![vec![(Address::new([2; 20]), None, vec![])]],
632                vec![],
633            ),
634            vec![vec![]],
635            1,
636            vec![],
637        );
638
639        let execution_outcome2 = ExecutionOutcome::new(
640            BundleState::new(
641                vec![(
642                    Address::new([3; 20]),
643                    None,
644                    Some(AccountInfo::default()),
645                    HashMap::default(),
646                )],
647                vec![vec![(Address::new([3; 20]), None, vec![])]],
648                vec![],
649            ),
650            vec![vec![]],
651            2,
652            vec![],
653        );
654
655        let mut block1: RecoveredBlock<reth_ethereum_primitives::Block> = Default::default();
656        let block1_hash = B256::new([15; 32]);
657        block1.set_block_number(1);
658        block1.set_hash(block1_hash);
659        block1.push_sender(Address::new([4; 20]));
660
661        let mut block2: RecoveredBlock<reth_ethereum_primitives::Block> = Default::default();
662        let block2_hash = B256::new([16; 32]);
663        block2.set_block_number(2);
664        block2.set_hash(block2_hash);
665        block2.push_sender(Address::new([4; 20]));
666
667        let mut block_state_extended = execution_outcome1;
668        block_state_extended.extend(execution_outcome2);
669
670        let chain: Chain =
671            Chain::new(vec![block1.clone(), block2.clone()], block_state_extended, None);
672
673        // return tip state
674        assert_eq!(
675            chain.execution_outcome_at_block(block2.number),
676            Some(chain.execution_outcome.clone())
677        );
678        // state at unknown block
679        assert_eq!(chain.execution_outcome_at_block(100), None);
680    }
681
682    #[test]
683    fn receipts_by_block_hash() {
684        // Create a default RecoveredBlock object
685        let block: RecoveredBlock<reth_ethereum_primitives::Block> = Default::default();
686
687        // Define block hashes for block1 and block2
688        let block1_hash = B256::new([0x01; 32]);
689        let block2_hash = B256::new([0x02; 32]);
690
691        // Clone the default block into block1 and block2
692        let mut block1 = block.clone();
693        let mut block2 = block;
694
695        // Set the hashes of block1 and block2
696        block1.set_hash(block1_hash);
697        block2.set_hash(block2_hash);
698
699        // Create a random receipt object, receipt1
700        let receipt1 = Receipt {
701            tx_type: TxType::Legacy,
702            cumulative_gas_used: 46913,
703            logs: vec![],
704            success: true,
705        };
706
707        // Create another random receipt object, receipt2
708        let receipt2 = Receipt {
709            tx_type: TxType::Legacy,
710            cumulative_gas_used: 1325345,
711            logs: vec![],
712            success: true,
713        };
714
715        // Create a Receipts object with a vector of receipt vectors
716        let receipts = vec![vec![receipt1.clone()], vec![receipt2]];
717
718        // Create an ExecutionOutcome object with the created bundle, receipts, an empty requests
719        // vector, and first_block set to 10
720        let execution_outcome = ExecutionOutcome {
721            bundle: Default::default(),
722            receipts,
723            requests: vec![],
724            first_block: 10,
725        };
726
727        // Create a Chain object with a BTreeMap of blocks mapped to their block numbers,
728        // including block1_hash and block2_hash, and the execution_outcome
729        let chain: Chain = Chain {
730            blocks: BTreeMap::from([(10, block1), (11, block2)]),
731            execution_outcome: execution_outcome.clone(),
732            ..Default::default()
733        };
734
735        // Assert that the proper receipt vector is returned for block1_hash
736        assert_eq!(chain.receipts_by_block_hash(block1_hash), Some(vec![&receipt1]));
737
738        // Create an ExecutionOutcome object with a single receipt vector containing receipt1
739        let execution_outcome1 = ExecutionOutcome {
740            bundle: Default::default(),
741            receipts: vec![vec![receipt1]],
742            requests: vec![],
743            first_block: 10,
744        };
745
746        // Assert that the execution outcome at the first block contains only the first receipt
747        assert_eq!(chain.execution_outcome_at_block(10), Some(execution_outcome1));
748
749        // Assert that the execution outcome at the tip block contains the whole execution outcome
750        assert_eq!(chain.execution_outcome_at_block(11), Some(execution_outcome));
751    }
752}