reth_transaction_pool/blobstore/
tracker.rs

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