Skip to main content

reth_execution_types/
chain.rs

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