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, MERKLE_CHANGESETS_RETENTION_BLOCKS,
35 MINIMUM_PRUNING_DISTANCE,
36};
37
38#[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 pub fn is_empty(&self) -> bool {
46 self.0.is_empty()
47 }
48
49 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 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 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 let tip = 3000000;
146 let pruned_block = Some(400);
147
148 let result = config.group_by_block(tip, pruned_block).unwrap();
149
150 assert_eq!(result.len(), 1);
152 assert_eq!(result[&500], vec![&address], "Address should be grouped under block 500");
153
154 let tip = 300;
156 let pruned_block = Some(400);
157
158 let result = config.group_by_block(tip, pruned_block).unwrap();
159
160 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 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 let pruned_block = Some(50);
198
199 let result = config.group_by_block(tip, pruned_block).unwrap();
200
201 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 let pruned_block = Some(800);
208
209 let result = config.group_by_block(tip, pruned_block).unwrap();
210
211 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 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 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 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 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 assert_eq!(config.lowest_block_with_distance(tip, pruned_block).unwrap(), Some(100000));
332 }
333}