Skip to main content

reth_transaction_pool/blobstore/
tracker.rs

1//! Support for maintaining the blob pool.
2
3use alloy_consensus::{transaction::TxHashRef, Typed2718};
4use alloy_primitives::{BlockNumber, B256};
5use reth_execution_types::ChainBlocks;
6use reth_primitives_traits::{Block, BlockBody, SignedTransaction};
7use std::collections::BTreeMap;
8
9/// The type that is used to track canonical blob transactions.
10#[derive(Debug, Default, Eq, PartialEq)]
11pub struct BlobStoreCanonTracker {
12    /// Keeps track of the blob transactions included in blocks.
13    blob_txs_in_blocks: BTreeMap<BlockNumber, Vec<B256>>,
14}
15
16impl BlobStoreCanonTracker {
17    /// Adds a block to the blob store maintenance.
18    pub fn add_block(
19        &mut self,
20        block_number: BlockNumber,
21        blob_txs: impl IntoIterator<Item = B256>,
22    ) {
23        self.blob_txs_in_blocks.insert(block_number, blob_txs.into_iter().collect());
24    }
25
26    /// Adds all blocks to the tracked list of blocks.
27    ///
28    /// Replaces any previously tracked blocks with the set of transactions.
29    pub fn add_blocks(
30        &mut self,
31        blocks: impl IntoIterator<Item = (BlockNumber, impl IntoIterator<Item = B256>)>,
32    ) {
33        for (block_number, blob_txs) in blocks {
34            self.add_block(block_number, blob_txs);
35        }
36    }
37
38    /// Adds all blob transactions from the given chain to the tracker.
39    ///
40    /// Note: In case this is a chain that's part of a reorg, this replaces previously tracked
41    /// blocks.
42    pub fn add_new_chain_blocks<B>(&mut self, blocks: &ChainBlocks<'_, B>)
43    where
44        B: Block<Body: BlockBody<Transaction: SignedTransaction>>,
45    {
46        let blob_txs = blocks.iter().map(|(num, block)| {
47            let iter = block
48                .body()
49                .transactions()
50                .iter()
51                .filter(|tx| tx.is_eip4844())
52                .map(|tx| *tx.tx_hash());
53            (*num, iter)
54        });
55        self.add_blocks(blob_txs);
56    }
57
58    /// Invoked when a block is finalized.
59    ///
60    /// This returns all blob transactions that were included in blocks that are now finalized.
61    pub fn on_finalized_block(&mut self, finalized_block: BlockNumber) -> BlobStoreUpdates {
62        let mut finalized = Vec::new();
63        while let Some(entry) = self.blob_txs_in_blocks.first_entry() {
64            if *entry.key() <= finalized_block {
65                finalized.extend(entry.remove_entry().1);
66            } else {
67                break
68            }
69        }
70
71        if finalized.is_empty() {
72            BlobStoreUpdates::None
73        } else {
74            BlobStoreUpdates::Finalized(finalized)
75        }
76    }
77}
78
79/// Updates that should be applied to the blob store.
80#[derive(Debug, Eq, PartialEq)]
81pub enum BlobStoreUpdates {
82    /// No updates.
83    None,
84    /// Delete the given finalized transactions from the blob store.
85    Finalized(Vec<B256>),
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91    use alloy_consensus::{Header, Signed};
92    use alloy_primitives::Signature;
93    use reth_ethereum_primitives::Transaction;
94    use reth_execution_types::Chain;
95    use reth_primitives_traits::{RecoveredBlock, SealedBlock, SealedHeader};
96
97    #[test]
98    fn test_finalized_tracker() {
99        let mut tracker = BlobStoreCanonTracker::default();
100
101        let block1 = vec![B256::random()];
102        let block2 = vec![B256::random()];
103        let block3 = vec![B256::random()];
104        tracker.add_block(1, block1.clone());
105        tracker.add_block(2, block2.clone());
106        tracker.add_block(3, block3.clone());
107
108        assert_eq!(tracker.on_finalized_block(0), BlobStoreUpdates::None);
109        assert_eq!(tracker.on_finalized_block(1), BlobStoreUpdates::Finalized(block1));
110        assert_eq!(
111            tracker.on_finalized_block(3),
112            BlobStoreUpdates::Finalized(block2.into_iter().chain(block3).collect::<Vec<_>>())
113        );
114    }
115
116    #[test]
117    fn test_add_new_chain_blocks() {
118        let mut tracker = BlobStoreCanonTracker::default();
119        // Create sample transactions
120        let tx1_signed = Signed::new_unhashed(
121            Transaction::Eip4844(Default::default()),
122            Signature::test_signature(),
123        ); // EIP-4844 transaction
124        let tx2_signed = Signed::new_unhashed(
125            Transaction::Eip4844(Default::default()),
126            Signature::test_signature(),
127        ); // EIP-4844 transaction
128
129        let tx1_hash = *tx1_signed.hash();
130        let tx2_hash = *tx2_signed.hash();
131        // Creating a first block with EIP-4844 transactions
132        let block1 = RecoveredBlock::new_sealed(
133            SealedBlock::from_sealed_parts(
134                SealedHeader::new(Header { number: 10, ..Default::default() }, B256::random()),
135                alloy_consensus::BlockBody {
136                    transactions: vec![
137                        tx1_signed.into(),
138                        tx2_signed.into(),
139                        // Another transaction that is not EIP-4844
140                        Signed::new_unhashed(
141                            Transaction::Eip7702(Default::default()),
142                            Signature::test_signature(),
143                        )
144                        .into(),
145                    ],
146                    ..Default::default()
147                },
148            ),
149            Default::default(),
150        );
151
152        // Creating a second block with EIP-1559 and EIP-2930 transactions
153        // Note: This block does not contain any EIP-4844 transactions
154        let block2 = RecoveredBlock::new_sealed(
155            SealedBlock::from_sealed_parts(
156                SealedHeader::new(Header { number: 11, ..Default::default() }, B256::random()),
157                alloy_consensus::BlockBody {
158                    transactions: vec![
159                        Signed::new_unhashed(
160                            Transaction::Eip1559(Default::default()),
161                            Signature::test_signature(),
162                        )
163                        .into(),
164                        Signed::new_unhashed(
165                            Transaction::Eip2930(Default::default()),
166                            Signature::test_signature(),
167                        )
168                        .into(),
169                    ],
170                    ..Default::default()
171                },
172            ),
173            Default::default(),
174        );
175
176        // Extract blocks from the chain
177        let chain: Chain = Chain::new(vec![block1, block2], Default::default(), BTreeMap::new());
178        let blocks = chain.into_inner().0;
179
180        // Add new chain blocks to the tracker
181        tracker.add_new_chain_blocks(&blocks);
182
183        // Tx1 and tx2 should be in the block containing EIP-4844 transactions
184        assert_eq!(tracker.blob_txs_in_blocks.get(&10).unwrap(), &vec![tx1_hash, tx2_hash]);
185        // No transactions should be in the block containing non-EIP-4844 transactions
186        assert!(tracker.blob_txs_in_blocks.get(&11).unwrap().is_empty());
187    }
188}