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/SeismicSystems/seismic-reth/issues/"
7)]
8#![cfg_attr(not(test), warn(unused_crate_dependencies))]
9#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
10
11mod checkpoint;
12mod event;
13mod mode;
14mod pruner;
15mod segment;
16mod target;
17
18pub use checkpoint::PruneCheckpoint;
19pub use event::PrunerEvent;
20pub use mode::PruneMode;
21pub use pruner::{
22 PruneInterruptReason, PruneProgress, PrunedSegmentInfo, PrunerOutput, SegmentOutput,
23 SegmentOutputCheckpoint,
24};
25pub use segment::{PrunePurpose, PruneSegment, PruneSegmentError};
26use serde::{Deserialize, Serialize};
27use std::collections::BTreeMap;
28pub use target::{PruneModes, MINIMUM_PRUNING_DISTANCE};
29
30use alloy_primitives::{Address, BlockNumber};
31use std::ops::Deref;
32
33#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
35pub struct ReceiptsLogPruneConfig(pub BTreeMap<Address, PruneMode>);
36
37impl ReceiptsLogPruneConfig {
38 pub fn is_empty(&self) -> bool {
40 self.0.is_empty()
41 }
42
43 pub fn group_by_block(
59 &self,
60 tip: BlockNumber,
61 pruned_block: Option<BlockNumber>,
62 ) -> Result<BTreeMap<BlockNumber, Vec<&Address>>, PruneSegmentError> {
63 let mut map = BTreeMap::new();
64 let base_block = pruned_block.unwrap_or_default() + 1;
65
66 for (address, mode) in &self.0 {
67 let block = base_block.max(
75 mode.prune_target_block(tip, PruneSegment::ContractLogs, PrunePurpose::User)?
76 .map(|(block, _)| block)
77 .unwrap_or_default() +
78 1,
79 );
80
81 map.entry(block).or_insert_with(Vec::new).push(address)
82 }
83 Ok(map)
84 }
85
86 pub fn lowest_block_with_distance(
88 &self,
89 tip: BlockNumber,
90 pruned_block: Option<BlockNumber>,
91 ) -> Result<Option<BlockNumber>, PruneSegmentError> {
92 let pruned_block = pruned_block.unwrap_or_default();
93 let mut lowest = None;
94
95 for mode in self.values() {
96 if mode.is_distance() {
97 if let Some((block, _)) =
98 mode.prune_target_block(tip, PruneSegment::ContractLogs, PrunePurpose::User)?
99 {
100 lowest = Some(lowest.unwrap_or(u64::MAX).min(block));
101 }
102 }
103 }
104
105 Ok(lowest.map(|lowest| lowest.max(pruned_block)))
106 }
107}
108
109impl Deref for ReceiptsLogPruneConfig {
110 type Target = BTreeMap<Address, PruneMode>;
111
112 fn deref(&self) -> &Self::Target {
113 &self.0
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 #[test]
122 fn test_group_by_block_empty_config() {
123 let config = ReceiptsLogPruneConfig(BTreeMap::new());
124 let tip = 1000;
125 let pruned_block = None;
126
127 let result = config.group_by_block(tip, pruned_block).unwrap();
128 assert!(result.is_empty(), "The result should be empty when the config is empty");
129 }
130
131 #[test]
132 fn test_group_by_block_single_entry() {
133 let mut config_map = BTreeMap::new();
134 let address = Address::new([1; 20]);
135 let prune_mode = PruneMode::Before(500);
136 config_map.insert(address, prune_mode);
137
138 let config = ReceiptsLogPruneConfig(config_map);
139 let tip = 3000000;
141 let pruned_block = Some(400);
142
143 let result = config.group_by_block(tip, pruned_block).unwrap();
144
145 assert_eq!(result.len(), 1);
147 assert_eq!(result[&500], vec![&address], "Address should be grouped under block 500");
148
149 let tip = 300;
151 let pruned_block = Some(400);
152
153 let result = config.group_by_block(tip, pruned_block).unwrap();
154
155 assert_eq!(result.len(), 1);
157 assert_eq!(result[&401], vec![&address], "Address should be grouped under block 400");
158 }
159
160 #[test]
161 fn test_group_by_block_multiple_entries() {
162 let mut config_map = BTreeMap::new();
163 let address1 = Address::new([1; 20]);
164 let address2 = Address::new([2; 20]);
165 let prune_mode1 = PruneMode::Before(600);
166 let prune_mode2 = PruneMode::Before(800);
167 config_map.insert(address1, prune_mode1);
168 config_map.insert(address2, prune_mode2);
169
170 let config = ReceiptsLogPruneConfig(config_map);
171 let tip = 900000;
172 let pruned_block = Some(400);
173
174 let result = config.group_by_block(tip, pruned_block).unwrap();
175
176 assert_eq!(result.len(), 2);
178 assert_eq!(result[&600], vec![&address1], "Address1 should be grouped under block 600");
179 assert_eq!(result[&800], vec![&address2], "Address2 should be grouped under block 800");
180 }
181
182 #[test]
183 fn test_group_by_block_with_distance_prune_mode() {
184 let mut config_map = BTreeMap::new();
185 let address = Address::new([1; 20]);
186 let prune_mode = PruneMode::Distance(100000);
187 config_map.insert(address, prune_mode);
188
189 let config = ReceiptsLogPruneConfig(config_map);
190 let tip = 100100;
191 let pruned_block = Some(50);
193
194 let result = config.group_by_block(tip, pruned_block).unwrap();
195
196 assert_eq!(result.len(), 1);
198 assert_eq!(result[&101], vec![&address], "Address should be grouped under block 100");
199
200 let tip = 100100;
201 let pruned_block = Some(800);
203
204 let result = config.group_by_block(tip, pruned_block).unwrap();
205
206 assert_eq!(result.len(), 1);
208 assert_eq!(result[&801], vec![&address], "Address should be grouped under block 800");
209 }
210
211 #[test]
212 fn test_lowest_block_with_distance_empty_config() {
213 let config = ReceiptsLogPruneConfig(BTreeMap::new());
214 let tip = 1000;
215 let pruned_block = None;
216
217 let result = config.lowest_block_with_distance(tip, pruned_block).unwrap();
218 assert_eq!(result, None, "The result should be None when the config is empty");
219 }
220
221 #[test]
222 fn test_lowest_block_with_distance_no_distance_mode() {
223 let mut config_map = BTreeMap::new();
224 let address = Address::new([1; 20]);
225 let prune_mode = PruneMode::Before(500);
226 config_map.insert(address, prune_mode);
227
228 let config = ReceiptsLogPruneConfig(config_map);
229 let tip = 1000;
230 let pruned_block = None;
231
232 let result = config.lowest_block_with_distance(tip, pruned_block).unwrap();
233 assert_eq!(result, None, "The result should be None when there are no Distance modes");
234 }
235
236 #[test]
237 fn test_lowest_block_with_distance_single_entry() {
238 let mut config_map = BTreeMap::new();
239 let address = Address::new([1; 20]);
240 let prune_mode = PruneMode::Distance(100000);
241 config_map.insert(address, prune_mode);
242
243 let config = ReceiptsLogPruneConfig(config_map);
244
245 let tip = 100100;
246 let pruned_block = Some(400);
247
248 assert_eq!(
250 config.lowest_block_with_distance(tip, pruned_block).unwrap(),
251 Some(400),
252 "The lowest block should be 400"
253 );
254
255 let tip = 100100;
256 let pruned_block = Some(50);
257
258 assert_eq!(
260 config.lowest_block_with_distance(tip, pruned_block).unwrap(),
261 Some(100),
262 "The lowest block should be 100"
263 );
264 }
265
266 #[test]
267 fn test_lowest_block_with_distance_multiple_entries_last() {
268 let mut config_map = BTreeMap::new();
269 let address1 = Address::new([1; 20]);
270 let address2 = Address::new([2; 20]);
271 let prune_mode1 = PruneMode::Distance(100100);
272 let prune_mode2 = PruneMode::Distance(100300);
273 config_map.insert(address1, prune_mode1);
274 config_map.insert(address2, prune_mode2);
275
276 let config = ReceiptsLogPruneConfig(config_map);
277 let tip = 200300;
278 let pruned_block = Some(100);
279
280 assert_eq!(config.lowest_block_with_distance(tip, pruned_block).unwrap(), Some(100000));
285 }
286
287 #[test]
288 fn test_lowest_block_with_distance_multiple_entries_first() {
289 let mut config_map = BTreeMap::new();
290 let address1 = Address::new([1; 20]);
291 let address2 = Address::new([2; 20]);
292 let prune_mode1 = PruneMode::Distance(100400);
293 let prune_mode2 = PruneMode::Distance(100300);
294 config_map.insert(address1, prune_mode1);
295 config_map.insert(address2, prune_mode2);
296
297 let config = ReceiptsLogPruneConfig(config_map);
298 let tip = 200300;
299 let pruned_block = Some(100);
300
301 assert_eq!(config.lowest_block_with_distance(tip, pruned_block).unwrap(), Some(99900));
306 }
307
308 #[test]
309 fn test_lowest_block_with_distance_multiple_entries_pruned_block() {
310 let mut config_map = BTreeMap::new();
311 let address1 = Address::new([1; 20]);
312 let address2 = Address::new([2; 20]);
313 let prune_mode1 = PruneMode::Distance(100400);
314 let prune_mode2 = PruneMode::Distance(100300);
315 config_map.insert(address1, prune_mode1);
316 config_map.insert(address2, prune_mode2);
317
318 let config = ReceiptsLogPruneConfig(config_map);
319 let tip = 200300;
320 let pruned_block = Some(100000);
321
322 assert_eq!(config.lowest_block_with_distance(tip, pruned_block).unwrap(), Some(100000));
327 }
328}