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
246        self.blocks_and_receipts().for_each(|(block, receipts)| {
247            let block_num_hash = BlockNumHash::new(block.number(), block.hash());
248
249            let tx_receipts = block
250                .body()
251                .transactions()
252                .iter()
253                .zip(receipts)
254                .map(|(tx, receipt)| (tx.trie_hash(), receipt.clone()))
255                .collect();
256
257            receipt_attach.push(BlockReceipts {
258                block: block_num_hash,
259                tx_receipts,
260                timestamp: block.timestamp(),
261            });
262        });
263
264        receipt_attach
265    }
266
267    /// Append a single block with state to the chain.
268    /// This method assumes that blocks attachment to the chain has already been validated.
269    pub fn append_block(
270        &mut self,
271        block: RecoveredBlock<N::Block>,
272        execution_outcome: ExecutionOutcome<N::Receipt>,
273    ) {
274        self.blocks.insert(block.header().number(), block);
275        self.execution_outcome.extend(execution_outcome);
276        self.trie_updates.take(); // reset
277    }
278
279    /// Merge two chains by appending the given chain into the current one.
280    ///
281    /// The state of accounts for this chain is set to the state of the newest chain.
282    ///
283    /// Returns the passed `other` chain in [`Result::Err`] variant if the chains could not be
284    /// connected.
285    pub fn append_chain(&mut self, other: Self) -> Result<(), Self> {
286        let chain_tip = self.tip();
287        let other_fork_block = other.fork_block();
288        if chain_tip.hash() != other_fork_block.hash {
289            return Err(other)
290        }
291
292        // Insert blocks from other chain
293        self.blocks.extend(other.blocks);
294        self.execution_outcome.extend(other.execution_outcome);
295        self.trie_updates.take(); // reset
296
297        Ok(())
298    }
299}
300
301/// Wrapper type for `blocks` display in `Chain`
302#[derive(Debug)]
303pub struct DisplayBlocksChain<'a, B: reth_primitives_traits::Block>(
304    pub &'a BTreeMap<BlockNumber, RecoveredBlock<B>>,
305);
306
307impl<B: reth_primitives_traits::Block> fmt::Display for DisplayBlocksChain<'_, B> {
308    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309        let mut list = f.debug_list();
310        let mut values = self.0.values().map(|block| block.num_hash());
311        if values.len() <= 3 {
312            list.entries(values);
313        } else {
314            list.entry(&values.next().unwrap());
315            list.entry(&format_args!("..."));
316            list.entry(&values.next_back().unwrap());
317        }
318        list.finish()
319    }
320}
321
322/// All blocks in the chain
323#[derive(Clone, Debug, Default, PartialEq, Eq)]
324pub struct ChainBlocks<'a, B: Block> {
325    blocks: Cow<'a, BTreeMap<BlockNumber, RecoveredBlock<B>>>,
326}
327
328impl<B: Block<Body: BlockBody<Transaction: SignedTransaction>>> ChainBlocks<'_, B> {
329    /// Creates a consuming iterator over all blocks in the chain with increasing block number.
330    ///
331    /// Note: this always yields at least one block.
332    #[inline]
333    pub fn into_blocks(self) -> impl Iterator<Item = RecoveredBlock<B>> {
334        self.blocks.into_owned().into_values()
335    }
336
337    /// Creates an iterator over all blocks in the chain with increasing block number.
338    #[inline]
339    pub fn iter(&self) -> impl Iterator<Item = (&BlockNumber, &RecoveredBlock<B>)> {
340        self.blocks.iter()
341    }
342
343    /// Get the tip of the chain.
344    ///
345    /// # Note
346    ///
347    /// Chains always have at least one block.
348    #[inline]
349    pub fn tip(&self) -> &RecoveredBlock<B> {
350        self.blocks.last_key_value().expect("Chain should have at least one block").1
351    }
352
353    /// Get the _first_ block of the chain.
354    ///
355    /// # Note
356    ///
357    /// Chains always have at least one block.
358    #[inline]
359    pub fn first(&self) -> &RecoveredBlock<B> {
360        self.blocks.first_key_value().expect("Chain should have at least one block").1
361    }
362
363    /// Returns an iterator over all transactions in the chain.
364    #[inline]
365    pub fn transactions(&self) -> impl Iterator<Item = &<B::Body as BlockBody>::Transaction> + '_ {
366        self.blocks.values().flat_map(|block| block.body().transactions_iter())
367    }
368
369    /// Returns an iterator over all transactions and their senders.
370    #[inline]
371    pub fn transactions_with_sender(
372        &self,
373    ) -> impl Iterator<Item = (&Address, &<B::Body as BlockBody>::Transaction)> + '_ {
374        self.blocks.values().flat_map(|block| block.transactions_with_sender())
375    }
376
377    /// Returns an iterator over all [`Recovered`] in the blocks
378    ///
379    /// Note: This clones the transactions since it is assumed this is part of a shared [Chain].
380    #[inline]
381    pub fn transactions_ecrecovered(
382        &self,
383    ) -> impl Iterator<Item = Recovered<<B::Body as BlockBody>::Transaction>> + '_ {
384        self.transactions_with_sender().map(|(signer, tx)| tx.clone().with_signer(*signer))
385    }
386
387    /// Returns an iterator over all transaction hashes in the block
388    #[inline]
389    pub fn transaction_hashes(&self) -> impl Iterator<Item = TxHash> + '_ {
390        self.blocks
391            .values()
392            .flat_map(|block| block.body().transactions_iter().map(|tx| tx.trie_hash()))
393    }
394}
395
396impl<B: Block> IntoIterator for ChainBlocks<'_, B> {
397    type Item = (BlockNumber, RecoveredBlock<B>);
398    type IntoIter = alloc::collections::btree_map::IntoIter<BlockNumber, RecoveredBlock<B>>;
399
400    fn into_iter(self) -> Self::IntoIter {
401        self.blocks.into_owned().into_iter()
402    }
403}
404
405/// Used to hold receipts and their attachment.
406#[derive(Default, Clone, Debug, PartialEq, Eq)]
407pub struct BlockReceipts<T = reth_ethereum_primitives::Receipt> {
408    /// Block identifier
409    pub block: BlockNumHash,
410    /// Transaction identifier and receipt.
411    pub tx_receipts: Vec<(TxHash, T)>,
412    /// Block timestamp
413    pub timestamp: u64,
414}
415
416/// Bincode-compatible [`Chain`] serde implementation.
417#[cfg(feature = "serde-bincode-compat")]
418pub(super) mod serde_bincode_compat {
419    use crate::{serde_bincode_compat, ExecutionOutcome};
420    use alloc::{borrow::Cow, collections::BTreeMap};
421    use alloy_primitives::BlockNumber;
422    use reth_ethereum_primitives::EthPrimitives;
423    use reth_primitives_traits::{
424        serde_bincode_compat::{RecoveredBlock, SerdeBincodeCompat},
425        Block, NodePrimitives,
426    };
427    use reth_trie_common::serde_bincode_compat::updates::TrieUpdates;
428    use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
429    use serde_with::{DeserializeAs, SerializeAs};
430
431    /// Bincode-compatible [`super::Chain`] serde implementation.
432    ///
433    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
434    /// ```rust
435    /// use reth_execution_types::{serde_bincode_compat, Chain};
436    /// use serde::{Deserialize, Serialize};
437    /// use serde_with::serde_as;
438    ///
439    /// #[serde_as]
440    /// #[derive(Serialize, Deserialize)]
441    /// struct Data {
442    ///     #[serde_as(as = "serde_bincode_compat::Chain")]
443    ///     chain: Chain,
444    /// }
445    /// ```
446    #[derive(Debug, Serialize, Deserialize)]
447    pub struct Chain<'a, N = EthPrimitives>
448    where
449        N: NodePrimitives<
450            Block: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static,
451        >,
452    {
453        blocks: RecoveredBlocks<'a, N::Block>,
454        execution_outcome: serde_bincode_compat::ExecutionOutcome<'a, N::Receipt>,
455        trie_updates: Option<TrieUpdates<'a>>,
456    }
457
458    #[derive(Debug)]
459    struct RecoveredBlocks<
460        'a,
461        B: reth_primitives_traits::Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat>
462            + 'static,
463    >(Cow<'a, BTreeMap<BlockNumber, reth_primitives_traits::RecoveredBlock<B>>>);
464
465    impl<B> Serialize for RecoveredBlocks<'_, B>
466    where
467        B: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static,
468    {
469        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
470        where
471            S: Serializer,
472        {
473            let mut state = serializer.serialize_map(Some(self.0.len()))?;
474
475            for (block_number, block) in self.0.iter() {
476                state.serialize_entry(block_number, &RecoveredBlock::<'_, B>::from(block))?;
477            }
478
479            state.end()
480        }
481    }
482
483    impl<'de, B> Deserialize<'de> for RecoveredBlocks<'_, B>
484    where
485        B: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static,
486    {
487        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
488        where
489            D: Deserializer<'de>,
490        {
491            Ok(Self(Cow::Owned(
492                BTreeMap::<BlockNumber, RecoveredBlock<'_, B>>::deserialize(deserializer)
493                    .map(|blocks| blocks.into_iter().map(|(n, b)| (n, b.into())).collect())?,
494            )))
495        }
496    }
497
498    impl<'a, N> From<&'a super::Chain<N>> for Chain<'a, N>
499    where
500        N: NodePrimitives<
501            Block: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static,
502        >,
503    {
504        fn from(value: &'a super::Chain<N>) -> Self {
505            Self {
506                blocks: RecoveredBlocks(Cow::Borrowed(&value.blocks)),
507                execution_outcome: value.execution_outcome.as_repr(),
508                trie_updates: value.trie_updates.as_ref().map(Into::into),
509            }
510        }
511    }
512
513    impl<'a, N> From<Chain<'a, N>> for super::Chain<N>
514    where
515        N: NodePrimitives<
516            Block: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static,
517        >,
518    {
519        fn from(value: Chain<'a, N>) -> Self {
520            Self {
521                blocks: value.blocks.0.into_owned(),
522                execution_outcome: ExecutionOutcome::from_repr(value.execution_outcome),
523                trie_updates: value.trie_updates.map(Into::into),
524            }
525        }
526    }
527
528    impl<N> SerializeAs<super::Chain<N>> for Chain<'_, N>
529    where
530        N: NodePrimitives<
531            Block: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static,
532        >,
533    {
534        fn serialize_as<S>(source: &super::Chain<N>, serializer: S) -> Result<S::Ok, S::Error>
535        where
536            S: Serializer,
537        {
538            Chain::from(source).serialize(serializer)
539        }
540    }
541
542    impl<'de, N> DeserializeAs<'de, super::Chain<N>> for Chain<'de, N>
543    where
544        N: NodePrimitives<
545            Block: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static,
546        >,
547    {
548        fn deserialize_as<D>(deserializer: D) -> Result<super::Chain<N>, D::Error>
549        where
550            D: Deserializer<'de>,
551        {
552            Chain::deserialize(deserializer).map(Into::into)
553        }
554    }
555
556    #[cfg(test)]
557    mod tests {
558        use super::super::{serde_bincode_compat, Chain};
559        use arbitrary::Arbitrary;
560        use rand::Rng;
561        use reth_primitives_traits::RecoveredBlock;
562        use serde::{Deserialize, Serialize};
563        use serde_with::serde_as;
564
565        #[test]
566        fn test_chain_bincode_roundtrip() {
567            #[serde_as]
568            #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
569            struct Data {
570                #[serde_as(as = "serde_bincode_compat::Chain")]
571                chain: Chain,
572            }
573
574            let mut bytes = [0u8; 1024];
575            rand::rng().fill(bytes.as_mut_slice());
576            let data = Data {
577                chain: Chain::new(
578                    vec![RecoveredBlock::arbitrary(&mut arbitrary::Unstructured::new(&bytes))
579                        .unwrap()],
580                    Default::default(),
581                    None,
582                ),
583            };
584
585            let encoded = bincode::serialize(&data).unwrap();
586            let decoded: Data = bincode::deserialize(&encoded).unwrap();
587            assert_eq!(decoded, data);
588        }
589    }
590}
591
592#[cfg(test)]
593mod tests {
594    use super::*;
595    use alloy_consensus::TxType;
596    use alloy_primitives::{Address, B256};
597    use reth_ethereum_primitives::Receipt;
598    use revm::{primitives::HashMap, state::AccountInfo};
599
600    #[test]
601    fn chain_append() {
602        let block: RecoveredBlock<reth_ethereum_primitives::Block> = Default::default();
603        let block1_hash = B256::new([0x01; 32]);
604        let block2_hash = B256::new([0x02; 32]);
605        let block3_hash = B256::new([0x03; 32]);
606        let block4_hash = B256::new([0x04; 32]);
607
608        let mut block1 = block.clone();
609        let mut block2 = block.clone();
610        let mut block3 = block.clone();
611        let mut block4 = block;
612
613        block1.set_hash(block1_hash);
614        block2.set_hash(block2_hash);
615        block3.set_hash(block3_hash);
616        block4.set_hash(block4_hash);
617
618        block3.set_parent_hash(block2_hash);
619
620        let mut chain1: Chain =
621            Chain { blocks: BTreeMap::from([(1, block1), (2, block2)]), ..Default::default() };
622
623        let chain2 =
624            Chain { blocks: BTreeMap::from([(3, block3), (4, block4)]), ..Default::default() };
625
626        assert!(chain1.append_chain(chain2.clone()).is_ok());
627
628        // chain1 got changed so this will fail
629        assert!(chain1.append_chain(chain2).is_err());
630    }
631
632    #[test]
633    fn test_number_split() {
634        let execution_outcome1: ExecutionOutcome = ExecutionOutcome::new(
635            BundleState::new(
636                vec![(
637                    Address::new([2; 20]),
638                    None,
639                    Some(AccountInfo::default()),
640                    HashMap::default(),
641                )],
642                vec![vec![(Address::new([2; 20]), None, vec![])]],
643                vec![],
644            ),
645            vec![vec![]],
646            1,
647            vec![],
648        );
649
650        let execution_outcome2 = ExecutionOutcome::new(
651            BundleState::new(
652                vec![(
653                    Address::new([3; 20]),
654                    None,
655                    Some(AccountInfo::default()),
656                    HashMap::default(),
657                )],
658                vec![vec![(Address::new([3; 20]), None, vec![])]],
659                vec![],
660            ),
661            vec![vec![]],
662            2,
663            vec![],
664        );
665
666        let mut block1: RecoveredBlock<reth_ethereum_primitives::Block> = Default::default();
667        let block1_hash = B256::new([15; 32]);
668        block1.set_block_number(1);
669        block1.set_hash(block1_hash);
670        block1.push_sender(Address::new([4; 20]));
671
672        let mut block2: RecoveredBlock<reth_ethereum_primitives::Block> = Default::default();
673        let block2_hash = B256::new([16; 32]);
674        block2.set_block_number(2);
675        block2.set_hash(block2_hash);
676        block2.push_sender(Address::new([4; 20]));
677
678        let mut block_state_extended = execution_outcome1;
679        block_state_extended.extend(execution_outcome2);
680
681        let chain: Chain =
682            Chain::new(vec![block1.clone(), block2.clone()], block_state_extended, None);
683
684        // return tip state
685        assert_eq!(
686            chain.execution_outcome_at_block(block2.number),
687            Some(chain.execution_outcome.clone())
688        );
689        // state at unknown block
690        assert_eq!(chain.execution_outcome_at_block(100), None);
691    }
692
693    #[test]
694    fn receipts_by_block_hash() {
695        // Create a default RecoveredBlock object
696        let block: RecoveredBlock<reth_ethereum_primitives::Block> = Default::default();
697
698        // Define block hashes for block1 and block2
699        let block1_hash = B256::new([0x01; 32]);
700        let block2_hash = B256::new([0x02; 32]);
701
702        // Clone the default block into block1 and block2
703        let mut block1 = block.clone();
704        let mut block2 = block;
705
706        // Set the hashes of block1 and block2
707        block1.set_hash(block1_hash);
708        block2.set_hash(block2_hash);
709
710        // Create a random receipt object, receipt1
711        let receipt1 = Receipt {
712            tx_type: TxType::Legacy,
713            cumulative_gas_used: 46913,
714            logs: vec![],
715            success: true,
716        };
717
718        // Create another random receipt object, receipt2
719        let receipt2 = Receipt {
720            tx_type: TxType::Legacy,
721            cumulative_gas_used: 1325345,
722            logs: vec![],
723            success: true,
724        };
725
726        // Create a Receipts object with a vector of receipt vectors
727        let receipts = vec![vec![receipt1.clone()], vec![receipt2]];
728
729        // Create an ExecutionOutcome object with the created bundle, receipts, an empty requests
730        // vector, and first_block set to 10
731        let execution_outcome = ExecutionOutcome {
732            bundle: Default::default(),
733            receipts,
734            requests: vec![],
735            first_block: 10,
736        };
737
738        // Create a Chain object with a BTreeMap of blocks mapped to their block numbers,
739        // including block1_hash and block2_hash, and the execution_outcome
740        let chain: Chain = Chain {
741            blocks: BTreeMap::from([(10, block1), (11, block2)]),
742            execution_outcome: execution_outcome.clone(),
743            ..Default::default()
744        };
745
746        // Assert that the proper receipt vector is returned for block1_hash
747        assert_eq!(chain.receipts_by_block_hash(block1_hash), Some(vec![&receipt1]));
748
749        // Create an ExecutionOutcome object with a single receipt vector containing receipt1
750        let execution_outcome1 = ExecutionOutcome {
751            bundle: Default::default(),
752            receipts: vec![vec![receipt1]],
753            requests: vec![],
754            first_block: 10,
755        };
756
757        // Assert that the execution outcome at the first block contains only the first receipt
758        assert_eq!(chain.execution_outcome_at_block(10), Some(execution_outcome1));
759
760        // Assert that the execution outcome at the tip block contains the whole execution outcome
761        assert_eq!(chain.execution_outcome_at_block(11), Some(execution_outcome));
762    }
763}