reth_revm/
batch.rs

1//! Helper for handling execution of multiple blocks.
2
3use alloc::vec::Vec;
4
5use alloy_eips::eip7685::Requests;
6use alloy_primitives::{map::HashSet, Address, BlockNumber, Log};
7use reth_execution_errors::{BlockExecutionError, InternalBlockExecutionError};
8use reth_primitives::Receipts;
9use reth_primitives_traits::Receipt;
10use reth_prune_types::{PruneMode, PruneModes, PruneSegmentError, MINIMUM_PRUNING_DISTANCE};
11use revm::db::states::bundle_state::BundleRetention;
12
13/// Takes care of:
14///  - recording receipts during execution of multiple blocks.
15///  - pruning receipts according to the pruning configuration.
16///  - batch range if known
17#[derive(Debug)]
18pub struct BlockBatchRecord<T = reth_primitives::Receipt> {
19    /// Pruning configuration.
20    prune_modes: PruneModes,
21    /// The collection of receipts.
22    /// Outer vector stores receipts for each block sequentially.
23    /// The inner vector stores receipts ordered by transaction number.
24    ///
25    /// If receipt is None it means it is pruned.
26    receipts: Receipts<T>,
27    /// The collection of EIP-7685 requests.
28    /// Outer vector stores requests for each block sequentially.
29    /// The inner vector stores requests ordered by transaction number.
30    ///
31    /// A transaction may have zero or more requests, so the length of the inner vector is not
32    /// guaranteed to be the same as the number of transactions.
33    requests: Vec<Requests>,
34    /// Memoized address pruning filter.
35    ///
36    /// Empty implies that there is going to be addresses to include in the filter in a future
37    /// block. None means there isn't any kind of configuration.
38    pruning_address_filter: Option<(u64, HashSet<Address>)>,
39    /// First block will be initialized to `None`
40    /// and be set to the block number of first block executed.
41    first_block: Option<BlockNumber>,
42    /// The maximum known block.
43    tip: Option<BlockNumber>,
44}
45
46impl<T> Default for BlockBatchRecord<T> {
47    fn default() -> Self {
48        Self {
49            prune_modes: Default::default(),
50            receipts: Default::default(),
51            requests: Default::default(),
52            pruning_address_filter: Default::default(),
53            first_block: Default::default(),
54            tip: Default::default(),
55        }
56    }
57}
58
59impl<T> BlockBatchRecord<T> {
60    /// Create a new receipts recorder with the given pruning configuration.
61    pub fn new(prune_modes: PruneModes) -> Self
62    where
63        T: Default,
64    {
65        Self { prune_modes, ..Default::default() }
66    }
67
68    /// Set prune modes.
69    pub fn set_prune_modes(&mut self, prune_modes: PruneModes) {
70        self.prune_modes = prune_modes;
71    }
72
73    /// Set the first block number of the batch.
74    pub fn set_first_block(&mut self, first_block: BlockNumber) {
75        self.first_block = Some(first_block);
76    }
77
78    /// Returns the first block of the batch if known.
79    pub const fn first_block(&self) -> Option<BlockNumber> {
80        self.first_block
81    }
82
83    /// Set tip - highest known block number.
84    pub fn set_tip(&mut self, tip: BlockNumber) {
85        self.tip = Some(tip);
86    }
87
88    /// Returns the tip of the batch if known.
89    pub const fn tip(&self) -> Option<BlockNumber> {
90        self.tip
91    }
92
93    /// Returns the recorded receipts.
94    pub const fn receipts(&self) -> &Receipts<T> {
95        &self.receipts
96    }
97
98    /// Returns all recorded receipts.
99    pub fn take_receipts(&mut self) -> Receipts<T> {
100        core::mem::take(&mut self.receipts)
101    }
102
103    /// Returns the recorded requests.
104    pub fn requests(&self) -> &[Requests] {
105        &self.requests
106    }
107
108    /// Returns all recorded requests.
109    pub fn take_requests(&mut self) -> Vec<Requests> {
110        core::mem::take(&mut self.requests)
111    }
112
113    /// Returns the [`BundleRetention`] for the given block based on the configured prune modes.
114    pub fn bundle_retention(&self, block_number: BlockNumber) -> BundleRetention {
115        if self.tip.is_none_or(|tip| {
116            !self
117                .prune_modes
118                .account_history
119                .is_some_and(|mode| mode.should_prune(block_number, tip)) &&
120                !self
121                    .prune_modes
122                    .storage_history
123                    .is_some_and(|mode| mode.should_prune(block_number, tip))
124        }) {
125            BundleRetention::Reverts
126        } else {
127            BundleRetention::PlainState
128        }
129    }
130
131    /// Save receipts to the executor.
132    pub fn save_receipts(&mut self, receipts: Vec<T>) -> Result<(), BlockExecutionError>
133    where
134        T: Receipt<Log = Log>,
135    {
136        let mut receipts = receipts.into_iter().map(Some).collect();
137        // Prune receipts if necessary.
138        self.prune_receipts(&mut receipts).map_err(InternalBlockExecutionError::from)?;
139        // Save receipts.
140        self.receipts.push(receipts);
141        Ok(())
142    }
143
144    /// Prune receipts according to the pruning configuration.
145    fn prune_receipts(&mut self, receipts: &mut Vec<Option<T>>) -> Result<(), PruneSegmentError>
146    where
147        T: Receipt<Log = Log>,
148    {
149        let (Some(first_block), Some(tip)) = (self.first_block, self.tip) else { return Ok(()) };
150
151        let block_number = first_block + self.receipts.len() as u64;
152
153        // Block receipts should not be retained
154        if self.prune_modes.receipts == Some(PruneMode::Full) ||
155            // [`PruneSegment::Receipts`] takes priority over [`PruneSegment::ContractLogs`]
156            self.prune_modes.receipts.is_some_and(|mode| mode.should_prune(block_number, tip))
157        {
158            receipts.clear();
159            return Ok(())
160        }
161
162        // All receipts from the last 128 blocks are required for blockchain tree, even with
163        // [`PruneSegment::ContractLogs`].
164        let prunable_receipts =
165            PruneMode::Distance(MINIMUM_PRUNING_DISTANCE).should_prune(block_number, tip);
166        if !prunable_receipts {
167            return Ok(())
168        }
169
170        let contract_log_pruner = self.prune_modes.receipts_log_filter.group_by_block(tip, None)?;
171
172        if !contract_log_pruner.is_empty() {
173            let (prev_block, filter) =
174                self.pruning_address_filter.get_or_insert_with(|| (0, Default::default()));
175            for (_, addresses) in contract_log_pruner.range(*prev_block..=block_number) {
176                filter.extend(addresses.iter().copied());
177            }
178        }
179
180        if let Some((_, filter)) = &self.pruning_address_filter {
181            for receipt in receipts.iter_mut() {
182                // If there is an address_filter, it does not contain any of the
183                // contract addresses, then remove this receipt.
184                let inner_receipt = receipt.as_ref().expect("receipts have not been pruned");
185                if !inner_receipt.logs().iter().any(|log| filter.contains(&log.address)) {
186                    receipt.take();
187                }
188            }
189        }
190
191        Ok(())
192    }
193
194    /// Save EIP-7685 requests to the executor.
195    pub fn save_requests(&mut self, requests: Requests) {
196        self.requests.push(requests);
197    }
198}
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203    use alloc::collections::BTreeMap;
204    use alloy_primitives::Address;
205    use reth_primitives::{Log, Receipt};
206    use reth_prune_types::{PruneMode, ReceiptsLogPruneConfig};
207
208    #[test]
209    fn test_save_receipts_empty() {
210        let mut recorder: BlockBatchRecord = BlockBatchRecord::default();
211        // Create an empty vector of receipts
212        let receipts = vec![];
213
214        // Verify that saving receipts completes without error
215        assert!(recorder.save_receipts(receipts).is_ok());
216        // Verify that the saved receipts are equal to a nested empty vector
217        assert_eq!(*recorder.receipts(), vec![vec![]].into());
218    }
219
220    #[test]
221    fn test_save_receipts_non_empty_no_pruning() {
222        let mut recorder = BlockBatchRecord::default();
223        let receipts = vec![Receipt::default()];
224
225        // Verify that saving receipts completes without error
226        assert!(recorder.save_receipts(receipts).is_ok());
227        // Verify that there is one block of receipts
228        assert_eq!(recorder.receipts().len(), 1);
229        // Verify that the first block contains one receipt
230        assert_eq!(recorder.receipts()[0].len(), 1);
231        // Verify that the saved receipt is the default receipt
232        assert_eq!(recorder.receipts()[0][0], Some(Receipt::default()));
233    }
234
235    #[test]
236    fn test_save_receipts_with_pruning_no_prunable_receipts() {
237        let mut recorder = BlockBatchRecord::default();
238
239        // Set the first block number
240        recorder.set_first_block(1);
241        // Set the tip (highest known block)
242        recorder.set_tip(130);
243
244        // Create a vector of receipts with a default receipt
245        let receipts = vec![Receipt::default()];
246
247        // Verify that saving receipts completes without error
248        assert!(recorder.save_receipts(receipts).is_ok());
249        // Verify that there is one block of receipts
250        assert_eq!(recorder.receipts().len(), 1);
251        // Verify that the first block contains one receipt
252        assert_eq!(recorder.receipts()[0].len(), 1);
253        // Verify that the saved receipt is the default receipt
254        assert_eq!(recorder.receipts()[0][0], Some(Receipt::default()));
255    }
256
257    #[test]
258    fn test_save_receipts_with_pruning_no_tip() {
259        // Create a PruneModes with receipts set to PruneMode::Full
260        let prune_modes = PruneModes { receipts: Some(PruneMode::Full), ..Default::default() };
261
262        let mut recorder = BlockBatchRecord::new(prune_modes);
263
264        // Set the first block number
265        recorder.set_first_block(1);
266        // Create a vector of receipts with a default receipt
267        let receipts = vec![Receipt::default()];
268
269        // Verify that saving receipts completes without error
270        assert!(recorder.save_receipts(receipts).is_ok());
271        // Verify that there is one block of receipts
272        assert_eq!(recorder.receipts().len(), 1);
273        // Verify that the first block contains one receipt
274        assert_eq!(recorder.receipts()[0].len(), 1);
275        // Verify that the saved receipt is the default receipt
276        assert_eq!(recorder.receipts()[0][0], Some(Receipt::default()));
277    }
278
279    #[test]
280    fn test_save_receipts_with_pruning_no_block_number() {
281        // Create a PruneModes with receipts set to PruneMode::Full
282        let prune_modes = PruneModes { receipts: Some(PruneMode::Full), ..Default::default() };
283
284        // Create a BlockBatchRecord with the prune_modes
285        let mut recorder = BlockBatchRecord::new(prune_modes);
286
287        // Set the tip (highest known block)
288        recorder.set_tip(130);
289
290        // Create a vector of receipts with a default receipt
291        let receipts = vec![Receipt::default()];
292
293        // Verify that saving receipts completes without error
294        assert!(recorder.save_receipts(receipts).is_ok());
295        // Verify that there is one block of receipts
296        assert_eq!(recorder.receipts().len(), 1);
297        // Verify that the first block contains one receipt
298        assert_eq!(recorder.receipts()[0].len(), 1);
299        // Verify that the saved receipt is the default receipt
300        assert_eq!(recorder.receipts()[0][0], Some(Receipt::default()));
301    }
302
303    // Test saving receipts with pruning configuration and receipts should be pruned
304    #[test]
305    fn test_save_receipts_with_pruning_should_prune() {
306        // Create a PruneModes with receipts set to PruneMode::Full
307        let prune_modes = PruneModes { receipts: Some(PruneMode::Full), ..Default::default() };
308
309        // Create a BlockBatchRecord with the prune_modes
310        let mut recorder = BlockBatchRecord::new(prune_modes);
311
312        // Set the first block number
313        recorder.set_first_block(1);
314        // Set the tip (highest known block)
315        recorder.set_tip(130);
316
317        // Create a vector of receipts with a default receipt
318        let receipts = vec![Receipt::default()];
319
320        // Verify that saving receipts completes without error
321        assert!(recorder.save_receipts(receipts).is_ok());
322        // Verify that there is one block of receipts
323        assert_eq!(recorder.receipts().len(), 1);
324        // Verify that the receipts are pruned (empty)
325        assert!(recorder.receipts()[0].is_empty());
326    }
327
328    // Test saving receipts with address filter pruning
329    #[test]
330    fn test_save_receipts_with_address_filter_pruning() {
331        // Create a PruneModes with receipts_log_filter configuration
332        let prune_modes = PruneModes {
333            receipts_log_filter: ReceiptsLogPruneConfig(BTreeMap::from([
334                (Address::with_last_byte(1), PruneMode::Before(1300001)),
335                (Address::with_last_byte(2), PruneMode::Before(1300002)),
336                (Address::with_last_byte(3), PruneMode::Distance(1300003)),
337            ])),
338            ..Default::default()
339        };
340
341        // Create a BlockBatchRecord with the prune_modes
342        let mut recorder = BlockBatchRecord::new(prune_modes);
343
344        // Set the first block number
345        recorder.set_first_block(1);
346        // Set the tip (highest known block)
347        recorder.set_tip(1300000);
348
349        // With a receipt that should be pruned (address 4 not in the log filter)
350        let mut receipt = Receipt::default();
351        receipt.logs.push(Log { address: Address::with_last_byte(4), ..Default::default() });
352        let receipts = vec![receipt.clone()];
353        assert!(recorder.save_receipts(receipts).is_ok());
354        // Verify that the receipts are pruned (empty)
355        assert_eq!(recorder.receipts().len(), 1);
356        assert_eq!(recorder.receipts()[0], vec![None]);
357
358        // With a receipt that should not be pruned (address 1 in the log filter)
359        let mut receipt1 = Receipt::default();
360        receipt1.logs.push(Log { address: Address::with_last_byte(1), ..Default::default() });
361        let receipts = vec![receipt1.clone()];
362        assert!(recorder.save_receipts(receipts).is_ok());
363        // Verify that the second block of receipts contains the receipt
364        assert_eq!(recorder.receipts().len(), 2);
365        assert_eq!(recorder.receipts()[1][0], Some(receipt1));
366
367        // With a receipt that should not be pruned (address 2 in the log filter)
368        let mut receipt2 = Receipt::default();
369        receipt2.logs.push(Log { address: Address::with_last_byte(2), ..Default::default() });
370        let receipts = vec![receipt2.clone()];
371        assert!(recorder.save_receipts(receipts).is_ok());
372        // Verify that the third block of receipts contains the receipt
373        assert_eq!(recorder.receipts().len(), 3);
374        assert_eq!(recorder.receipts()[2][0], Some(receipt2));
375
376        // With a receipt that should not be pruned (address 3 in the log filter)
377        let mut receipt3 = Receipt::default();
378        receipt3.logs.push(Log { address: Address::with_last_byte(3), ..Default::default() });
379        let receipts = vec![receipt3.clone()];
380        assert!(recorder.save_receipts(receipts).is_ok());
381        // Verify that the fourth block of receipts contains the receipt
382        assert_eq!(recorder.receipts().len(), 4);
383        assert_eq!(recorder.receipts()[3][0], Some(receipt3));
384    }
385}