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::{
34 PruneModes, UnwindTargetPrunedError, MINIMUM_DISTANCE, MINIMUM_UNWIND_SAFE_DISTANCE,
35};
36
37#[derive(Debug, Clone, PartialEq, Eq, Default)]
39#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))]
40pub struct ReceiptsLogPruneConfig(pub BTreeMap<Address, PruneMode>);
41
42impl ReceiptsLogPruneConfig {
43 pub fn is_empty(&self) -> bool {
45 self.0.is_empty()
46 }
47
48 pub fn group_by_block(
64 &self,
65 tip: BlockNumber,
66 pruned_block: Option<BlockNumber>,
67 ) -> Result<BTreeMap<BlockNumber, Vec<&Address>>, PruneSegmentError> {
68 let mut map = BTreeMap::new();
69 let base_block = pruned_block.unwrap_or_default() + 1;
70
71 for (address, mode) in &self.0 {
72 let block = base_block.max(
80 mode.prune_target_block(tip, PruneSegment::ContractLogs, PrunePurpose::User)?
81 .map(|(block, _)| block)
82 .unwrap_or_default() +
83 1,
84 );
85
86 map.entry(block).or_insert_with(Vec::new).push(address)
87 }
88 Ok(map)
89 }
90
91 pub fn lowest_block_with_distance(
93 &self,
94 tip: BlockNumber,
95 pruned_block: Option<BlockNumber>,
96 ) -> Result<Option<BlockNumber>, PruneSegmentError> {
97 let pruned_block = pruned_block.unwrap_or_default();
98 let mut lowest = None;
99
100 for mode in self.values() {
101 if mode.is_distance() &&
102 let Some((block, _)) =
103 mode.prune_target_block(tip, PruneSegment::ContractLogs, PrunePurpose::User)?
104 {
105 lowest = Some(lowest.unwrap_or(u64::MAX).min(block));
106 }
107 }
108
109 Ok(lowest.map(|lowest| lowest.max(pruned_block)))
110 }
111}
112
113impl Deref for ReceiptsLogPruneConfig {
114 type Target = BTreeMap<Address, PruneMode>;
115
116 fn deref(&self) -> &Self::Target {
117 &self.0
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn test_group_by_block_empty_config() {
127 let config = ReceiptsLogPruneConfig(BTreeMap::new());
128 let tip = 1000;
129 let pruned_block = None;
130
131 let result = config.group_by_block(tip, pruned_block).unwrap();
132 assert!(result.is_empty(), "The result should be empty when the config is empty");
133 }
134
135 #[test]
136 fn test_group_by_block_single_entry() {
137 let mut config_map = BTreeMap::new();
138 let address = Address::new([1; 20]);
139 let prune_mode = PruneMode::Before(500);
140 config_map.insert(address, prune_mode);
141
142 let config = ReceiptsLogPruneConfig(config_map);
143 let tip = 3000000;
145 let pruned_block = Some(400);
146
147 let result = config.group_by_block(tip, pruned_block).unwrap();
148
149 assert_eq!(result.len(), 1);
151 assert_eq!(result[&500], vec![&address], "Address should be grouped under block 500");
152
153 let tip = 300;
155 let pruned_block = Some(400);
156
157 let result = config.group_by_block(tip, pruned_block).unwrap();
158
159 assert_eq!(result.len(), 1);
161 assert_eq!(result[&401], vec![&address], "Address should be grouped under block 400");
162 }
163
164 #[test]
165 fn test_group_by_block_multiple_entries() {
166 let mut config_map = BTreeMap::new();
167 let address1 = Address::new([1; 20]);
168 let address2 = Address::new([2; 20]);
169 let prune_mode1 = PruneMode::Before(600);
170 let prune_mode2 = PruneMode::Before(800);
171 config_map.insert(address1, prune_mode1);
172 config_map.insert(address2, prune_mode2);
173
174 let config = ReceiptsLogPruneConfig(config_map);
175 let tip = 900000;
176 let pruned_block = Some(400);
177
178 let result = config.group_by_block(tip, pruned_block).unwrap();
179
180 assert_eq!(result.len(), 2);
182 assert_eq!(result[&600], vec![&address1], "Address1 should be grouped under block 600");
183 assert_eq!(result[&800], vec![&address2], "Address2 should be grouped under block 800");
184 }
185
186 #[test]
187 fn test_group_by_block_with_distance_prune_mode() {
188 let mut config_map = BTreeMap::new();
189 let address = Address::new([1; 20]);
190 let prune_mode = PruneMode::Distance(100000);
191 config_map.insert(address, prune_mode);
192
193 let config = ReceiptsLogPruneConfig(config_map);
194 let tip = 100100;
195 let pruned_block = Some(50);
197
198 let result = config.group_by_block(tip, pruned_block).unwrap();
199
200 assert_eq!(result.len(), 1);
202 assert_eq!(result[&101], vec![&address], "Address should be grouped under block 100");
203
204 let tip = 100100;
205 let pruned_block = Some(800);
207
208 let result = config.group_by_block(tip, pruned_block).unwrap();
209
210 assert_eq!(result.len(), 1);
212 assert_eq!(result[&801], vec![&address], "Address should be grouped under block 800");
213 }
214
215 #[test]
216 fn test_lowest_block_with_distance_empty_config() {
217 let config = ReceiptsLogPruneConfig(BTreeMap::new());
218 let tip = 1000;
219 let pruned_block = None;
220
221 let result = config.lowest_block_with_distance(tip, pruned_block).unwrap();
222 assert_eq!(result, None, "The result should be None when the config is empty");
223 }
224
225 #[test]
226 fn test_lowest_block_with_distance_no_distance_mode() {
227 let mut config_map = BTreeMap::new();
228 let address = Address::new([1; 20]);
229 let prune_mode = PruneMode::Before(500);
230 config_map.insert(address, prune_mode);
231
232 let config = ReceiptsLogPruneConfig(config_map);
233 let tip = 1000;
234 let pruned_block = None;
235
236 let result = config.lowest_block_with_distance(tip, pruned_block).unwrap();
237 assert_eq!(result, None, "The result should be None when there are no Distance modes");
238 }
239
240 #[test]
241 fn test_lowest_block_with_distance_single_entry() {
242 let mut config_map = BTreeMap::new();
243 let address = Address::new([1; 20]);
244 let prune_mode = PruneMode::Distance(100000);
245 config_map.insert(address, prune_mode);
246
247 let config = ReceiptsLogPruneConfig(config_map);
248
249 let tip = 100100;
250 let pruned_block = Some(400);
251
252 assert_eq!(
254 config.lowest_block_with_distance(tip, pruned_block).unwrap(),
255 Some(400),
256 "The lowest block should be 400"
257 );
258
259 let tip = 100100;
260 let pruned_block = Some(50);
261
262 assert_eq!(
264 config.lowest_block_with_distance(tip, pruned_block).unwrap(),
265 Some(100),
266 "The lowest block should be 100"
267 );
268 }
269
270 #[test]
271 fn test_lowest_block_with_distance_multiple_entries_last() {
272 let mut config_map = BTreeMap::new();
273 let address1 = Address::new([1; 20]);
274 let address2 = Address::new([2; 20]);
275 let prune_mode1 = PruneMode::Distance(100100);
276 let prune_mode2 = PruneMode::Distance(100300);
277 config_map.insert(address1, prune_mode1);
278 config_map.insert(address2, prune_mode2);
279
280 let config = ReceiptsLogPruneConfig(config_map);
281 let tip = 200300;
282 let pruned_block = Some(100);
283
284 assert_eq!(config.lowest_block_with_distance(tip, pruned_block).unwrap(), Some(100000));
289 }
290
291 #[test]
292 fn test_lowest_block_with_distance_multiple_entries_first() {
293 let mut config_map = BTreeMap::new();
294 let address1 = Address::new([1; 20]);
295 let address2 = Address::new([2; 20]);
296 let prune_mode1 = PruneMode::Distance(100400);
297 let prune_mode2 = PruneMode::Distance(100300);
298 config_map.insert(address1, prune_mode1);
299 config_map.insert(address2, prune_mode2);
300
301 let config = ReceiptsLogPruneConfig(config_map);
302 let tip = 200300;
303 let pruned_block = Some(100);
304
305 assert_eq!(config.lowest_block_with_distance(tip, pruned_block).unwrap(), Some(99900));
310 }
311
312 #[test]
313 fn test_lowest_block_with_distance_multiple_entries_pruned_block() {
314 let mut config_map = BTreeMap::new();
315 let address1 = Address::new([1; 20]);
316 let address2 = Address::new([2; 20]);
317 let prune_mode1 = PruneMode::Distance(100400);
318 let prune_mode2 = PruneMode::Distance(100300);
319 config_map.insert(address1, prune_mode1);
320 config_map.insert(address2, prune_mode2);
321
322 let config = ReceiptsLogPruneConfig(config_map);
323 let tip = 200300;
324 let pruned_block = Some(100000);
325
326 assert_eq!(config.lowest_block_with_distance(tip, pruned_block).unwrap(), Some(100000));
331 }
332}