1#![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#[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 pub fn is_empty(&self) -> bool {
43 self.0.is_empty()
44 }
45
46 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 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 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 let tip = 3000000;
144 let pruned_block = Some(400);
145
146 let result = config.group_by_block(tip, pruned_block).unwrap();
147
148 assert_eq!(result.len(), 1);
150 assert_eq!(result[&500], vec![&address], "Address should be grouped under block 500");
151
152 let tip = 300;
154 let pruned_block = Some(400);
155
156 let result = config.group_by_block(tip, pruned_block).unwrap();
157
158 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 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 let pruned_block = Some(50);
196
197 let result = config.group_by_block(tip, pruned_block).unwrap();
198
199 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 let pruned_block = Some(800);
206
207 let result = config.group_by_block(tip, pruned_block).unwrap();
208
209 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 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 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 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 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 assert_eq!(config.lowest_block_with_distance(tip, pruned_block).unwrap(), Some(100000));
330 }
331}