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