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