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::{PruneModes, UnwindTargetPrunedError, 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                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        Ok(lowest.map(|lowest| lowest.max(pruned_block)))
108    }
109}
110
111impl Deref for ReceiptsLogPruneConfig {
112    type Target = BTreeMap<Address, PruneMode>;
113
114    fn deref(&self) -> &Self::Target {
115        &self.0
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn test_group_by_block_empty_config() {
125        let config = ReceiptsLogPruneConfig(BTreeMap::new());
126        let tip = 1000;
127        let pruned_block = None;
128
129        let result = config.group_by_block(tip, pruned_block).unwrap();
130        assert!(result.is_empty(), "The result should be empty when the config is empty");
131    }
132
133    #[test]
134    fn test_group_by_block_single_entry() {
135        let mut config_map = BTreeMap::new();
136        let address = Address::new([1; 20]);
137        let prune_mode = PruneMode::Before(500);
138        config_map.insert(address, prune_mode);
139
140        let config = ReceiptsLogPruneConfig(config_map);
141        // Big tip to have something to prune for the target block
142        let tip = 3000000;
143        let pruned_block = Some(400);
144
145        let result = config.group_by_block(tip, pruned_block).unwrap();
146
147        // Expect one entry with block 500 and the corresponding address
148        assert_eq!(result.len(), 1);
149        assert_eq!(result[&500], vec![&address], "Address should be grouped under block 500");
150
151        // Tip smaller than the target block, so that we have nothing to prune for the block
152        let tip = 300;
153        let pruned_block = Some(400);
154
155        let result = config.group_by_block(tip, pruned_block).unwrap();
156
157        // Expect one entry with block 400 and the corresponding address
158        assert_eq!(result.len(), 1);
159        assert_eq!(result[&401], vec![&address], "Address should be grouped under block 400");
160    }
161
162    #[test]
163    fn test_group_by_block_multiple_entries() {
164        let mut config_map = BTreeMap::new();
165        let address1 = Address::new([1; 20]);
166        let address2 = Address::new([2; 20]);
167        let prune_mode1 = PruneMode::Before(600);
168        let prune_mode2 = PruneMode::Before(800);
169        config_map.insert(address1, prune_mode1);
170        config_map.insert(address2, prune_mode2);
171
172        let config = ReceiptsLogPruneConfig(config_map);
173        let tip = 900000;
174        let pruned_block = Some(400);
175
176        let result = config.group_by_block(tip, pruned_block).unwrap();
177
178        // Expect two entries: one for block 600 and another for block 800
179        assert_eq!(result.len(), 2);
180        assert_eq!(result[&600], vec![&address1], "Address1 should be grouped under block 600");
181        assert_eq!(result[&800], vec![&address2], "Address2 should be grouped under block 800");
182    }
183
184    #[test]
185    fn test_group_by_block_with_distance_prune_mode() {
186        let mut config_map = BTreeMap::new();
187        let address = Address::new([1; 20]);
188        let prune_mode = PruneMode::Distance(100000);
189        config_map.insert(address, prune_mode);
190
191        let config = ReceiptsLogPruneConfig(config_map);
192        let tip = 100100;
193        // Pruned block is smaller than the target block
194        let pruned_block = Some(50);
195
196        let result = config.group_by_block(tip, pruned_block).unwrap();
197
198        // Expect the entry to be grouped under block 100 (tip - distance)
199        assert_eq!(result.len(), 1);
200        assert_eq!(result[&101], vec![&address], "Address should be grouped under block 100");
201
202        let tip = 100100;
203        // Pruned block is larger than the target block
204        let pruned_block = Some(800);
205
206        let result = config.group_by_block(tip, pruned_block).unwrap();
207
208        // Expect the entry to be grouped under block 800 which is larger than tip - distance
209        assert_eq!(result.len(), 1);
210        assert_eq!(result[&801], vec![&address], "Address should be grouped under block 800");
211    }
212
213    #[test]
214    fn test_lowest_block_with_distance_empty_config() {
215        let config = ReceiptsLogPruneConfig(BTreeMap::new());
216        let tip = 1000;
217        let pruned_block = None;
218
219        let result = config.lowest_block_with_distance(tip, pruned_block).unwrap();
220        assert_eq!(result, None, "The result should be None when the config is empty");
221    }
222
223    #[test]
224    fn test_lowest_block_with_distance_no_distance_mode() {
225        let mut config_map = BTreeMap::new();
226        let address = Address::new([1; 20]);
227        let prune_mode = PruneMode::Before(500);
228        config_map.insert(address, prune_mode);
229
230        let config = ReceiptsLogPruneConfig(config_map);
231        let tip = 1000;
232        let pruned_block = None;
233
234        let result = config.lowest_block_with_distance(tip, pruned_block).unwrap();
235        assert_eq!(result, None, "The result should be None when there are no Distance modes");
236    }
237
238    #[test]
239    fn test_lowest_block_with_distance_single_entry() {
240        let mut config_map = BTreeMap::new();
241        let address = Address::new([1; 20]);
242        let prune_mode = PruneMode::Distance(100000);
243        config_map.insert(address, prune_mode);
244
245        let config = ReceiptsLogPruneConfig(config_map);
246
247        let tip = 100100;
248        let pruned_block = Some(400);
249
250        // Expect the lowest block to be 400 as 400 > 100100 - 100000 (tip - distance)
251        assert_eq!(
252            config.lowest_block_with_distance(tip, pruned_block).unwrap(),
253            Some(400),
254            "The lowest block should be 400"
255        );
256
257        let tip = 100100;
258        let pruned_block = Some(50);
259
260        // Expect the lowest block to be 100 as 100 > 50 (pruned block)
261        assert_eq!(
262            config.lowest_block_with_distance(tip, pruned_block).unwrap(),
263            Some(100),
264            "The lowest block should be 100"
265        );
266    }
267
268    #[test]
269    fn test_lowest_block_with_distance_multiple_entries_last() {
270        let mut config_map = BTreeMap::new();
271        let address1 = Address::new([1; 20]);
272        let address2 = Address::new([2; 20]);
273        let prune_mode1 = PruneMode::Distance(100100);
274        let prune_mode2 = PruneMode::Distance(100300);
275        config_map.insert(address1, prune_mode1);
276        config_map.insert(address2, prune_mode2);
277
278        let config = ReceiptsLogPruneConfig(config_map);
279        let tip = 200300;
280        let pruned_block = Some(100);
281
282        // The lowest block should be 200300 - 100300 = 100000:
283        // - First iteration will return 100200 => 200300 - 100100 = 100200
284        // - Second iteration will return 100000 => 200300 - 100300 = 100000 < 100200
285        // - Final result is 100000
286        assert_eq!(config.lowest_block_with_distance(tip, pruned_block).unwrap(), Some(100000));
287    }
288
289    #[test]
290    fn test_lowest_block_with_distance_multiple_entries_first() {
291        let mut config_map = BTreeMap::new();
292        let address1 = Address::new([1; 20]);
293        let address2 = Address::new([2; 20]);
294        let prune_mode1 = PruneMode::Distance(100400);
295        let prune_mode2 = PruneMode::Distance(100300);
296        config_map.insert(address1, prune_mode1);
297        config_map.insert(address2, prune_mode2);
298
299        let config = ReceiptsLogPruneConfig(config_map);
300        let tip = 200300;
301        let pruned_block = Some(100);
302
303        // The lowest block should be 200300 - 100400 = 99900:
304        // - First iteration, lowest block is 200300 - 100400 = 99900
305        // - Second iteration, lowest block is still 99900 < 200300 - 100300 = 100000
306        // - Final result is 99900
307        assert_eq!(config.lowest_block_with_distance(tip, pruned_block).unwrap(), Some(99900));
308    }
309
310    #[test]
311    fn test_lowest_block_with_distance_multiple_entries_pruned_block() {
312        let mut config_map = BTreeMap::new();
313        let address1 = Address::new([1; 20]);
314        let address2 = Address::new([2; 20]);
315        let prune_mode1 = PruneMode::Distance(100400);
316        let prune_mode2 = PruneMode::Distance(100300);
317        config_map.insert(address1, prune_mode1);
318        config_map.insert(address2, prune_mode2);
319
320        let config = ReceiptsLogPruneConfig(config_map);
321        let tip = 200300;
322        let pruned_block = Some(100000);
323
324        // The lowest block should be 100000 because:
325        // - Lowest is 200300 - 100400 = 99900 < 200300 - 100300 = 100000
326        // - Lowest is compared to the pruned block 100000: 100000 > 99900
327        // - Finally the lowest block is 100000
328        assert_eq!(config.lowest_block_with_distance(tip, pruned_block).unwrap(), Some(100000));
329    }
330}