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