Skip to main content

reth_prune_types/
lib.rs

1//! Commonly used types for prune usage.
2
3#![doc(
4    html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
5    html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
6    issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
7)]
8#![cfg_attr(not(test), warn(unused_crate_dependencies))]
9#![cfg_attr(docsrs, feature(doc_cfg))]
10#![cfg_attr(not(feature = "std"), no_std)]
11
12extern crate alloc;
13
14mod checkpoint;
15mod event;
16mod mode;
17mod pruner;
18mod segment;
19mod target;
20
21use alloc::{collections::BTreeMap, vec::Vec};
22use alloy_primitives::{Address, BlockNumber};
23use core::ops::Deref;
24
25pub use checkpoint::PruneCheckpoint;
26pub use event::PrunerEvent;
27pub use mode::PruneMode;
28pub use pruner::{
29    PruneInterruptReason, PruneProgress, PrunedSegmentInfo, PrunerOutput, SegmentOutput,
30    SegmentOutputCheckpoint,
31};
32pub use segment::{PrunePurpose, PruneSegment, PruneSegmentError};
33pub use target::{
34    PruneModes, UnwindTargetPrunedError, MINIMUM_DISTANCE, MINIMUM_UNWIND_SAFE_DISTANCE,
35};
36
37/// Configuration for pruning receipts not associated with logs emitted by the specified contracts.
38#[derive(Debug, Clone, PartialEq, Eq, Default)]
39#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))]
40pub struct ReceiptsLogPruneConfig(pub BTreeMap<Address, PruneMode>);
41
42impl ReceiptsLogPruneConfig {
43    /// Checks if the configuration is empty
44    pub fn is_empty(&self) -> bool {
45        self.0.is_empty()
46    }
47
48    /// Given the `tip` block number, consolidates the structure so it can easily be queried for
49    /// filtering across a range of blocks.
50    ///
51    /// Example:
52    ///
53    /// `{ addrA: Before(872), addrB: Before(500), addrC: Distance(128) }`
54    ///
55    ///    for `tip: 1000`, gets transformed to a map such as:
56    ///
57    /// `{ 500: [addrB], 872: [addrA, addrC] }`
58    ///
59    /// The [`BlockNumber`] key of the new map should be viewed as `PruneMode::Before(block)`, which
60    /// makes the previous result equivalent to
61    ///
62    /// `{ Before(500): [addrB], Before(872): [addrA, addrC] }`
63    pub fn group_by_block(
64        &self,
65        tip: BlockNumber,
66        pruned_block: Option<BlockNumber>,
67    ) -> Result<BTreeMap<BlockNumber, Vec<&Address>>, PruneSegmentError> {
68        let mut map = BTreeMap::new();
69        let base_block = pruned_block.unwrap_or_default() + 1;
70
71        for (address, mode) in &self.0 {
72            // Getting `None`, means that there is nothing to prune yet, so we need it to include in
73            // the BTreeMap (block = 0), otherwise it will be excluded.
74            // Reminder that this BTreeMap works as an inclusion list that excludes (prunes) all
75            // other receipts.
76            //
77            // Reminder, that we increment because the [`BlockNumber`] key of the new map should be
78            // viewed as `PruneMode::Before(block)`
79            let block = base_block.max(
80                mode.prune_target_block(tip, PruneSegment::ContractLogs, PrunePurpose::User)?
81                    .map(|(block, _)| block)
82                    .unwrap_or_default() +
83                    1,
84            );
85
86            map.entry(block).or_insert_with(Vec::new).push(address)
87        }
88        Ok(map)
89    }
90
91    /// Returns the lowest block where we start filtering logs which use `PruneMode::Distance(_)`.
92    pub fn lowest_block_with_distance(
93        &self,
94        tip: BlockNumber,
95        pruned_block: Option<BlockNumber>,
96    ) -> Result<Option<BlockNumber>, PruneSegmentError> {
97        let pruned_block = pruned_block.unwrap_or_default();
98        let mut lowest = None;
99
100        for mode in self.values() {
101            if mode.is_distance() &&
102                let Some((block, _)) =
103                    mode.prune_target_block(tip, PruneSegment::ContractLogs, PrunePurpose::User)?
104            {
105                lowest = Some(lowest.unwrap_or(u64::MAX).min(block));
106            }
107        }
108
109        Ok(lowest.map(|lowest| lowest.max(pruned_block)))
110    }
111}
112
113impl Deref for ReceiptsLogPruneConfig {
114    type Target = BTreeMap<Address, PruneMode>;
115
116    fn deref(&self) -> &Self::Target {
117        &self.0
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn test_group_by_block_empty_config() {
127        let config = ReceiptsLogPruneConfig(BTreeMap::new());
128        let tip = 1000;
129        let pruned_block = None;
130
131        let result = config.group_by_block(tip, pruned_block).unwrap();
132        assert!(result.is_empty(), "The result should be empty when the config is empty");
133    }
134
135    #[test]
136    fn test_group_by_block_single_entry() {
137        let mut config_map = BTreeMap::new();
138        let address = Address::new([1; 20]);
139        let prune_mode = PruneMode::Before(500);
140        config_map.insert(address, prune_mode);
141
142        let config = ReceiptsLogPruneConfig(config_map);
143        // Big tip to have something to prune for the target block
144        let tip = 3000000;
145        let pruned_block = Some(400);
146
147        let result = config.group_by_block(tip, pruned_block).unwrap();
148
149        // Expect one entry with block 500 and the corresponding address
150        assert_eq!(result.len(), 1);
151        assert_eq!(result[&500], vec![&address], "Address should be grouped under block 500");
152
153        // Tip smaller than the target block, so that we have nothing to prune for the block
154        let tip = 300;
155        let pruned_block = Some(400);
156
157        let result = config.group_by_block(tip, pruned_block).unwrap();
158
159        // Expect one entry with block 400 and the corresponding address
160        assert_eq!(result.len(), 1);
161        assert_eq!(result[&401], vec![&address], "Address should be grouped under block 400");
162    }
163
164    #[test]
165    fn test_group_by_block_multiple_entries() {
166        let mut config_map = BTreeMap::new();
167        let address1 = Address::new([1; 20]);
168        let address2 = Address::new([2; 20]);
169        let prune_mode1 = PruneMode::Before(600);
170        let prune_mode2 = PruneMode::Before(800);
171        config_map.insert(address1, prune_mode1);
172        config_map.insert(address2, prune_mode2);
173
174        let config = ReceiptsLogPruneConfig(config_map);
175        let tip = 900000;
176        let pruned_block = Some(400);
177
178        let result = config.group_by_block(tip, pruned_block).unwrap();
179
180        // Expect two entries: one for block 600 and another for block 800
181        assert_eq!(result.len(), 2);
182        assert_eq!(result[&600], vec![&address1], "Address1 should be grouped under block 600");
183        assert_eq!(result[&800], vec![&address2], "Address2 should be grouped under block 800");
184    }
185
186    #[test]
187    fn test_group_by_block_with_distance_prune_mode() {
188        let mut config_map = BTreeMap::new();
189        let address = Address::new([1; 20]);
190        let prune_mode = PruneMode::Distance(100000);
191        config_map.insert(address, prune_mode);
192
193        let config = ReceiptsLogPruneConfig(config_map);
194        let tip = 100100;
195        // Pruned block is smaller than the target block
196        let pruned_block = Some(50);
197
198        let result = config.group_by_block(tip, pruned_block).unwrap();
199
200        // Expect the entry to be grouped under block 100 (tip - distance)
201        assert_eq!(result.len(), 1);
202        assert_eq!(result[&101], vec![&address], "Address should be grouped under block 100");
203
204        let tip = 100100;
205        // Pruned block is larger than the target block
206        let pruned_block = Some(800);
207
208        let result = config.group_by_block(tip, pruned_block).unwrap();
209
210        // Expect the entry to be grouped under block 800 which is larger than tip - distance
211        assert_eq!(result.len(), 1);
212        assert_eq!(result[&801], vec![&address], "Address should be grouped under block 800");
213    }
214
215    #[test]
216    fn test_lowest_block_with_distance_empty_config() {
217        let config = ReceiptsLogPruneConfig(BTreeMap::new());
218        let tip = 1000;
219        let pruned_block = None;
220
221        let result = config.lowest_block_with_distance(tip, pruned_block).unwrap();
222        assert_eq!(result, None, "The result should be None when the config is empty");
223    }
224
225    #[test]
226    fn test_lowest_block_with_distance_no_distance_mode() {
227        let mut config_map = BTreeMap::new();
228        let address = Address::new([1; 20]);
229        let prune_mode = PruneMode::Before(500);
230        config_map.insert(address, prune_mode);
231
232        let config = ReceiptsLogPruneConfig(config_map);
233        let tip = 1000;
234        let pruned_block = None;
235
236        let result = config.lowest_block_with_distance(tip, pruned_block).unwrap();
237        assert_eq!(result, None, "The result should be None when there are no Distance modes");
238    }
239
240    #[test]
241    fn test_lowest_block_with_distance_single_entry() {
242        let mut config_map = BTreeMap::new();
243        let address = Address::new([1; 20]);
244        let prune_mode = PruneMode::Distance(100000);
245        config_map.insert(address, prune_mode);
246
247        let config = ReceiptsLogPruneConfig(config_map);
248
249        let tip = 100100;
250        let pruned_block = Some(400);
251
252        // Expect the lowest block to be 400 as 400 > 100100 - 100000 (tip - distance)
253        assert_eq!(
254            config.lowest_block_with_distance(tip, pruned_block).unwrap(),
255            Some(400),
256            "The lowest block should be 400"
257        );
258
259        let tip = 100100;
260        let pruned_block = Some(50);
261
262        // Expect the lowest block to be 100 as 100 > 50 (pruned block)
263        assert_eq!(
264            config.lowest_block_with_distance(tip, pruned_block).unwrap(),
265            Some(100),
266            "The lowest block should be 100"
267        );
268    }
269
270    #[test]
271    fn test_lowest_block_with_distance_multiple_entries_last() {
272        let mut config_map = BTreeMap::new();
273        let address1 = Address::new([1; 20]);
274        let address2 = Address::new([2; 20]);
275        let prune_mode1 = PruneMode::Distance(100100);
276        let prune_mode2 = PruneMode::Distance(100300);
277        config_map.insert(address1, prune_mode1);
278        config_map.insert(address2, prune_mode2);
279
280        let config = ReceiptsLogPruneConfig(config_map);
281        let tip = 200300;
282        let pruned_block = Some(100);
283
284        // The lowest block should be 200300 - 100300 = 100000:
285        // - First iteration will return 100200 => 200300 - 100100 = 100200
286        // - Second iteration will return 100000 => 200300 - 100300 = 100000 < 100200
287        // - Final result is 100000
288        assert_eq!(config.lowest_block_with_distance(tip, pruned_block).unwrap(), Some(100000));
289    }
290
291    #[test]
292    fn test_lowest_block_with_distance_multiple_entries_first() {
293        let mut config_map = BTreeMap::new();
294        let address1 = Address::new([1; 20]);
295        let address2 = Address::new([2; 20]);
296        let prune_mode1 = PruneMode::Distance(100400);
297        let prune_mode2 = PruneMode::Distance(100300);
298        config_map.insert(address1, prune_mode1);
299        config_map.insert(address2, prune_mode2);
300
301        let config = ReceiptsLogPruneConfig(config_map);
302        let tip = 200300;
303        let pruned_block = Some(100);
304
305        // The lowest block should be 200300 - 100400 = 99900:
306        // - First iteration, lowest block is 200300 - 100400 = 99900
307        // - Second iteration, lowest block is still 99900 < 200300 - 100300 = 100000
308        // - Final result is 99900
309        assert_eq!(config.lowest_block_with_distance(tip, pruned_block).unwrap(), Some(99900));
310    }
311
312    #[test]
313    fn test_lowest_block_with_distance_multiple_entries_pruned_block() {
314        let mut config_map = BTreeMap::new();
315        let address1 = Address::new([1; 20]);
316        let address2 = Address::new([2; 20]);
317        let prune_mode1 = PruneMode::Distance(100400);
318        let prune_mode2 = PruneMode::Distance(100300);
319        config_map.insert(address1, prune_mode1);
320        config_map.insert(address2, prune_mode2);
321
322        let config = ReceiptsLogPruneConfig(config_map);
323        let tip = 200300;
324        let pruned_block = Some(100000);
325
326        // The lowest block should be 100000 because:
327        // - Lowest is 200300 - 100400 = 99900 < 200300 - 100300 = 100000
328        // - Lowest is compared to the pruned block 100000: 100000 > 99900
329        // - Finally the lowest block is 100000
330        assert_eq!(config.lowest_block_with_distance(tip, pruned_block).unwrap(), Some(100000));
331    }
332}