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