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