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