Skip to main content

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