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))]
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#[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 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 let tip = 3000000;
143 let pruned_block = Some(400);
144
145 let result = config.group_by_block(tip, pruned_block).unwrap();
146
147 assert_eq!(result.len(), 1);
149 assert_eq!(result[&500], vec![&address], "Address should be grouped under block 500");
150
151 let tip = 300;
153 let pruned_block = Some(400);
154
155 let result = config.group_by_block(tip, pruned_block).unwrap();
156
157 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 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 let pruned_block = Some(50);
195
196 let result = config.group_by_block(tip, pruned_block).unwrap();
197
198 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 let pruned_block = Some(800);
205
206 let result = config.group_by_block(tip, pruned_block).unwrap();
207
208 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 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 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 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 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 assert_eq!(config.lowest_block_with_distance(tip, pruned_block).unwrap(), Some(100000));
329 }
330}