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 alloy_consensus::Header;
92    use alloy_primitives::PrimitiveSignature as Signature;
93    use reth_execution_types::Chain;
94    use reth_primitives::{
95        BlockBody, SealedBlock, SealedBlockWithSenders, SealedHeader, Transaction,
96        TransactionSigned,
97    };
98
99    use super::*;
100
101    #[test]
102    fn test_finalized_tracker() {
103        let mut tracker = BlobStoreCanonTracker::default();
104
105        let block1 = vec![B256::random()];
106        let block2 = vec![B256::random()];
107        let block3 = vec![B256::random()];
108        tracker.add_block(1, block1.clone());
109        tracker.add_block(2, block2.clone());
110        tracker.add_block(3, block3.clone());
111
112        assert_eq!(tracker.on_finalized_block(0), BlobStoreUpdates::None);
113        assert_eq!(tracker.on_finalized_block(1), BlobStoreUpdates::Finalized(block1));
114        assert_eq!(
115            tracker.on_finalized_block(3),
116            BlobStoreUpdates::Finalized(block2.into_iter().chain(block3).collect::<Vec<_>>())
117        );
118    }
119
120    #[test]
121    fn test_add_new_chain_blocks() {
122        let mut tracker = BlobStoreCanonTracker::default();
123
124        // Create sample transactions
125        let tx1_hash = B256::random(); // EIP-4844 transaction
126        let tx2_hash = B256::random(); // EIP-4844 transaction
127        let tx3_hash = B256::random(); // Non-EIP-4844 transaction
128
129        // Creating a first block with EIP-4844 transactions
130        let block1 = SealedBlockWithSenders {
131            block: SealedBlock {
132                header: SealedHeader::new(
133                    Header { number: 10, ..Default::default() },
134                    B256::random(),
135                ),
136                body: BlockBody {
137                    transactions: vec![
138                        TransactionSigned::new(
139                            Transaction::Eip4844(Default::default()),
140                            Signature::test_signature(),
141                            tx1_hash,
142                        ),
143                        TransactionSigned::new(
144                            Transaction::Eip4844(Default::default()),
145                            Signature::test_signature(),
146                            tx2_hash,
147                        ),
148                        // Another transaction that is not EIP-4844
149                        TransactionSigned::new(
150                            Transaction::Eip7702(Default::default()),
151                            Signature::test_signature(),
152                            B256::random(),
153                        ),
154                    ],
155                    ..Default::default()
156                },
157            },
158            ..Default::default()
159        };
160
161        // Creating a second block with EIP-1559 and EIP-2930 transactions
162        // Note: This block does not contain any EIP-4844 transactions
163        let block2 = SealedBlockWithSenders {
164            block: SealedBlock {
165                header: SealedHeader::new(
166                    Header { number: 11, ..Default::default() },
167                    B256::random(),
168                ),
169                body: BlockBody {
170                    transactions: vec![
171                        TransactionSigned::new(
172                            Transaction::Eip1559(Default::default()),
173                            Signature::test_signature(),
174                            tx3_hash,
175                        ),
176                        TransactionSigned::new(
177                            Transaction::Eip2930(Default::default()),
178                            Signature::test_signature(),
179                            tx2_hash,
180                        ),
181                    ],
182                    ..Default::default()
183                },
184            },
185            ..Default::default()
186        };
187
188        // Extract blocks from the chain
189        let chain: Chain = Chain::new(vec![block1, block2], Default::default(), None);
190        let blocks = chain.into_inner().0;
191
192        // Add new chain blocks to the tracker
193        tracker.add_new_chain_blocks(&blocks);
194
195        // Tx1 and tx2 should be in the block containing EIP-4844 transactions
196        assert_eq!(tracker.blob_txs_in_blocks.get(&10).unwrap(), &vec![tx1_hash, tx2_hash]);
197        // No transactions should be in the block containing non-EIP-4844 transactions
198        assert!(tracker.blob_txs_in_blocks.get(&11).unwrap().is_empty());
199    }
200}