1use crate::args::error::ReceiptsLogError;
4use alloy_primitives::{Address, BlockNumber};
5use clap::{builder::RangedU64ValueParser, Args};
6use reth_config::config::PruneConfig;
7use reth_prune_types::{PruneMode, PruneModes, ReceiptsLogPruneConfig, MINIMUM_PRUNING_DISTANCE};
8use std::collections::BTreeMap;
9
10#[derive(Debug, Clone, Args, PartialEq, Eq, Default)]
12#[command(next_help_heading = "Pruning")]
13pub struct PruningArgs {
14 #[arg(long, default_value_t = false)]
16 pub full: bool,
17
18 #[arg(long, value_parser = RangedU64ValueParser::<u64>::new().range(1..),)]
20 pub block_interval: Option<u64>,
21
22 #[arg(long = "prune.senderrecovery.full", conflicts_with_all = &["sender_recovery_distance", "sender_recovery_before"])]
25 pub sender_recovery_full: bool,
26 #[arg(long = "prune.senderrecovery.distance", value_name = "BLOCKS", conflicts_with_all = &["sender_recovery_full", "sender_recovery_before"])]
29 pub sender_recovery_distance: Option<u64>,
30 #[arg(long = "prune.senderrecovery.before", value_name = "BLOCK_NUMBER", conflicts_with_all = &["sender_recovery_full", "sender_recovery_distance"])]
33 pub sender_recovery_before: Option<BlockNumber>,
34
35 #[arg(long = "prune.transactionlookup.full", conflicts_with_all = &["transaction_lookup_distance", "transaction_lookup_before"])]
38 pub transaction_lookup_full: bool,
39 #[arg(long = "prune.transactionlookup.distance", value_name = "BLOCKS", conflicts_with_all = &["transaction_lookup_full", "transaction_lookup_before"])]
42 pub transaction_lookup_distance: Option<u64>,
43 #[arg(long = "prune.transactionlookup.before", value_name = "BLOCK_NUMBER", conflicts_with_all = &["transaction_lookup_full", "transaction_lookup_distance"])]
46 pub transaction_lookup_before: Option<BlockNumber>,
47
48 #[arg(long = "prune.receipts.full", conflicts_with_all = &["receipts_distance", "receipts_before"])]
51 pub receipts_full: bool,
52 #[arg(long = "prune.receipts.distance", value_name = "BLOCKS", conflicts_with_all = &["receipts_full", "receipts_before"])]
54 pub receipts_distance: Option<u64>,
55 #[arg(long = "prune.receipts.before", value_name = "BLOCK_NUMBER", conflicts_with_all = &["receipts_full", "receipts_distance"])]
57 pub receipts_before: Option<BlockNumber>,
58 #[arg(long = "prune.receiptslogfilter", value_name = "FILTER_CONFIG", conflicts_with_all = &["receipts_full", "receipts_distance", "receipts_before"], value_parser = parse_receipts_log_filter)]
63 pub receipts_log_filter: Option<ReceiptsLogPruneConfig>,
64
65 #[arg(long = "prune.accounthistory.full", conflicts_with_all = &["account_history_distance", "account_history_before"])]
68 pub account_history_full: bool,
69 #[arg(long = "prune.accounthistory.distance", value_name = "BLOCKS", conflicts_with_all = &["account_history_full", "account_history_before"])]
71 pub account_history_distance: Option<u64>,
72 #[arg(long = "prune.accounthistory.before", value_name = "BLOCK_NUMBER", conflicts_with_all = &["account_history_full", "account_history_distance"])]
75 pub account_history_before: Option<BlockNumber>,
76
77 #[arg(long = "prune.storagehistory.full", conflicts_with_all = &["storage_history_distance", "storage_history_before"])]
80 pub storage_history_full: bool,
81 #[arg(long = "prune.storagehistory.distance", value_name = "BLOCKS", conflicts_with_all = &["storage_history_full", "storage_history_before"])]
84 pub storage_history_distance: Option<u64>,
85 #[arg(long = "prune.storagehistory.before", value_name = "BLOCK_NUMBER", conflicts_with_all = &["storage_history_full", "storage_history_distance"])]
88 pub storage_history_before: Option<BlockNumber>,
89}
90
91impl PruningArgs {
92 pub fn prune_config(&self) -> Option<PruneConfig> {
94 let mut config = PruneConfig::default();
96
97 if self.full {
99 config = PruneConfig {
100 block_interval: config.block_interval,
101 segments: PruneModes {
102 sender_recovery: Some(PruneMode::Full),
103 transaction_lookup: None,
104 receipts: Some(PruneMode::Distance(MINIMUM_PRUNING_DISTANCE)),
105 account_history: Some(PruneMode::Distance(MINIMUM_PRUNING_DISTANCE)),
106 storage_history: Some(PruneMode::Distance(MINIMUM_PRUNING_DISTANCE)),
107 receipts_log_filter: Default::default(),
108 },
109 }
110 }
111
112 if let Some(block_interval) = self.block_interval {
114 config.block_interval = block_interval as usize;
115 }
116 if let Some(mode) = self.sender_recovery_prune_mode() {
117 config.segments.sender_recovery = Some(mode);
118 }
119 if let Some(mode) = self.transaction_lookup_prune_mode() {
120 config.segments.transaction_lookup = Some(mode);
121 }
122 if let Some(mode) = self.receipts_prune_mode() {
123 config.segments.receipts = Some(mode);
124 }
125 if let Some(mode) = self.account_history_prune_mode() {
126 config.segments.account_history = Some(mode);
127 }
128 if let Some(mode) = self.storage_history_prune_mode() {
129 config.segments.storage_history = Some(mode);
130 }
131 if let Some(receipt_logs) =
132 self.receipts_log_filter.as_ref().filter(|c| !c.is_empty()).cloned()
133 {
134 config.segments.receipts_log_filter = receipt_logs;
135 config.segments.receipts.take();
138 }
139
140 Some(config)
141 }
142
143 const fn sender_recovery_prune_mode(&self) -> Option<PruneMode> {
144 if self.sender_recovery_full {
145 Some(PruneMode::Full)
146 } else if let Some(distance) = self.sender_recovery_distance {
147 Some(PruneMode::Distance(distance))
148 } else if let Some(block_number) = self.sender_recovery_before {
149 Some(PruneMode::Before(block_number))
150 } else {
151 None
152 }
153 }
154
155 const fn transaction_lookup_prune_mode(&self) -> Option<PruneMode> {
156 if self.transaction_lookup_full {
157 Some(PruneMode::Full)
158 } else if let Some(distance) = self.transaction_lookup_distance {
159 Some(PruneMode::Distance(distance))
160 } else if let Some(block_number) = self.transaction_lookup_before {
161 Some(PruneMode::Before(block_number))
162 } else {
163 None
164 }
165 }
166
167 const fn receipts_prune_mode(&self) -> Option<PruneMode> {
168 if self.receipts_full {
169 Some(PruneMode::Full)
170 } else if let Some(distance) = self.receipts_distance {
171 Some(PruneMode::Distance(distance))
172 } else if let Some(block_number) = self.receipts_before {
173 Some(PruneMode::Before(block_number))
174 } else {
175 None
176 }
177 }
178
179 const fn account_history_prune_mode(&self) -> Option<PruneMode> {
180 if self.account_history_full {
181 Some(PruneMode::Full)
182 } else if let Some(distance) = self.account_history_distance {
183 Some(PruneMode::Distance(distance))
184 } else if let Some(block_number) = self.account_history_before {
185 Some(PruneMode::Before(block_number))
186 } else {
187 None
188 }
189 }
190
191 const fn storage_history_prune_mode(&self) -> Option<PruneMode> {
192 if self.storage_history_full {
193 Some(PruneMode::Full)
194 } else if let Some(distance) = self.storage_history_distance {
195 Some(PruneMode::Distance(distance))
196 } else if let Some(block_number) = self.storage_history_before {
197 Some(PruneMode::Before(block_number))
198 } else {
199 None
200 }
201 }
202}
203
204pub(crate) fn parse_receipts_log_filter(
206 value: &str,
207) -> Result<ReceiptsLogPruneConfig, ReceiptsLogError> {
208 let mut config = BTreeMap::new();
209 let filters = value.split(',');
211 for filter in filters {
212 let parts: Vec<&str> = filter.split(':').collect();
213 if parts.len() < 2 {
214 return Err(ReceiptsLogError::InvalidFilterFormat(filter.to_string()));
215 }
216 let address = parts[0]
218 .parse::<Address>()
219 .map_err(|_| ReceiptsLogError::InvalidAddress(parts[0].to_string()))?;
220
221 let prune_mode = match parts[1] {
223 "full" => PruneMode::Full,
224 s if s.starts_with("distance") => {
225 if parts.len() < 3 {
226 return Err(ReceiptsLogError::InvalidFilterFormat(filter.to_string()));
227 }
228 let distance =
229 parts[2].parse::<u64>().map_err(ReceiptsLogError::InvalidDistance)?;
230 PruneMode::Distance(distance)
231 }
232 s if s.starts_with("before") => {
233 if parts.len() < 3 {
234 return Err(ReceiptsLogError::InvalidFilterFormat(filter.to_string()));
235 }
236 let block_number =
237 parts[2].parse::<u64>().map_err(ReceiptsLogError::InvalidBlockNumber)?;
238 PruneMode::Before(block_number)
239 }
240 _ => return Err(ReceiptsLogError::InvalidPruneMode(parts[1].to_string())),
241 };
242 config.insert(address, prune_mode);
243 }
244 Ok(ReceiptsLogPruneConfig(config))
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250 use alloy_primitives::address;
251 use clap::Parser;
252
253 #[derive(Parser)]
255 struct CommandParser<T: Args> {
256 #[command(flatten)]
257 args: T,
258 }
259
260 #[test]
261 fn pruning_args_sanity_check() {
262 let args = CommandParser::<PruningArgs>::parse_from([
263 "reth",
264 "--prune.receiptslogfilter",
265 "0x0000000000000000000000000000000000000003:before:5000000",
266 ])
267 .args;
268 let mut config = ReceiptsLogPruneConfig::default();
269 config.0.insert(
270 address!("0x0000000000000000000000000000000000000003"),
271 PruneMode::Before(5000000),
272 );
273 assert_eq!(args.receipts_log_filter, Some(config));
274 }
275
276 #[test]
277 fn parse_receiptslogfilter() {
278 let default_args = PruningArgs::default();
279 let args = CommandParser::<PruningArgs>::parse_from(["reth"]).args;
280 assert_eq!(args, default_args);
281 }
282
283 #[test]
284 fn test_parse_receipts_log_filter() {
285 let filter1 = "0x0000000000000000000000000000000000000001:full";
286 let filter2 = "0x0000000000000000000000000000000000000002:distance:1000";
287 let filter3 = "0x0000000000000000000000000000000000000003:before:5000000";
288 let filters = [filter1, filter2, filter3].join(",");
289
290 let result = parse_receipts_log_filter(&filters);
292 assert!(result.is_ok());
293 let config = result.unwrap();
294 assert_eq!(config.0.len(), 3);
295
296 let addr1: Address = "0x0000000000000000000000000000000000000001".parse().unwrap();
298 let addr2: Address = "0x0000000000000000000000000000000000000002".parse().unwrap();
299 let addr3: Address = "0x0000000000000000000000000000000000000003".parse().unwrap();
300
301 assert_eq!(config.0.get(&addr1), Some(&PruneMode::Full));
302 assert_eq!(config.0.get(&addr2), Some(&PruneMode::Distance(1000)));
303 assert_eq!(config.0.get(&addr3), Some(&PruneMode::Before(5000000)));
304 }
305
306 #[test]
307 fn test_parse_receipts_log_filter_invalid_filter_format() {
308 let result = parse_receipts_log_filter("invalid_format");
309 assert!(matches!(result, Err(ReceiptsLogError::InvalidFilterFormat(_))));
310 }
311
312 #[test]
313 fn test_parse_receipts_log_filter_invalid_address() {
314 let result = parse_receipts_log_filter("invalid_address:full");
315 assert!(matches!(result, Err(ReceiptsLogError::InvalidAddress(_))));
316 }
317
318 #[test]
319 fn test_parse_receipts_log_filter_invalid_prune_mode() {
320 let result =
321 parse_receipts_log_filter("0x0000000000000000000000000000000000000000:invalid_mode");
322 assert!(matches!(result, Err(ReceiptsLogError::InvalidPruneMode(_))));
323 }
324
325 #[test]
326 fn test_parse_receipts_log_filter_invalid_distance() {
327 let result = parse_receipts_log_filter(
328 "0x0000000000000000000000000000000000000000:distance:invalid_distance",
329 );
330 assert!(matches!(result, Err(ReceiptsLogError::InvalidDistance(_))));
331 }
332
333 #[test]
334 fn test_parse_receipts_log_filter_invalid_block_number() {
335 let result = parse_receipts_log_filter(
336 "0x0000000000000000000000000000000000000000:before:invalid_block",
337 );
338 assert!(matches!(result, Err(ReceiptsLogError::InvalidBlockNumber(_))));
339 }
340}