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