1use alloy_primitives::B256;
4use clap::{
5 builder::{PossibleValue, TypedValueParser},
6 Arg, Args, Command,
7};
8use std::{collections::HashSet, ffi::OsStr, fmt, path::PathBuf, str::FromStr};
9use strum::{AsRefStr, EnumIter, IntoStaticStr, ParseError, VariantArray, VariantNames};
10
11#[derive(Debug, Clone, Args, PartialEq, Eq)]
13#[command(next_help_heading = "Debug")]
14pub struct DebugArgs {
15 #[arg(long = "debug.terminate", help_heading = "Debug")]
17 pub terminate: bool,
18
19 #[arg(long = "debug.tip", help_heading = "Debug")]
23 pub tip: Option<B256>,
24
25 #[arg(long = "debug.max-block", help_heading = "Debug")]
27 pub max_block: Option<u64>,
28
29 #[arg(
32 long = "debug.etherscan",
33 help_heading = "Debug",
34 conflicts_with = "tip",
35 conflicts_with = "rpc_consensus_url",
36 value_name = "ETHERSCAN_API_URL"
37 )]
38 pub etherscan: Option<Option<String>>,
39
40 #[arg(
44 long = "debug.rpc-consensus-url",
45 alias = "debug.rpc-consensus-ws",
46 help_heading = "Debug",
47 conflicts_with = "tip",
48 conflicts_with = "etherscan",
49 value_name = "RPC_URL"
50 )]
51 pub rpc_consensus_url: Option<String>,
52
53 #[arg(long = "debug.skip-fcu", help_heading = "Debug")]
55 pub skip_fcu: Option<usize>,
56
57 #[arg(long = "debug.skip-new-payload", help_heading = "Debug")]
59 pub skip_new_payload: Option<usize>,
60
61 #[arg(long = "debug.skip-genesis-validation", help_heading = "Debug")]
68 pub skip_genesis_validation: bool,
69
70 #[arg(long = "debug.reorg-frequency", help_heading = "Debug")]
72 pub reorg_frequency: Option<usize>,
73
74 #[arg(long = "debug.reorg-depth", requires = "reorg_frequency", help_heading = "Debug")]
76 pub reorg_depth: Option<usize>,
77
78 #[arg(long = "debug.engine-api-store", help_heading = "Debug", value_name = "PATH")]
82 pub engine_api_store: Option<PathBuf>,
83
84 #[arg(
88 long = "debug.invalid-block-hook",
89 help_heading = "Debug",
90 value_parser = InvalidBlockSelectionValueParser::default(),
91 default_value = "witness"
92 )]
93 pub invalid_block_hook: Option<InvalidBlockSelection>,
94
95 #[arg(
102 long = "debug.healthy-node-rpc-url",
103 help_heading = "Debug",
104 value_name = "URL",
105 verbatim_doc_comment
106 )]
107 pub healthy_node_rpc_url: Option<String>,
108
109 #[arg(long = "ethstats", help_heading = "Debug")]
112 pub ethstats: Option<String>,
113
114 #[arg(long = "debug.startup-sync-state-idle", help_heading = "Debug")]
119 pub startup_sync_state_idle: bool,
120}
121
122impl Default for DebugArgs {
123 fn default() -> Self {
124 Self {
125 terminate: false,
126 tip: None,
127 max_block: None,
128 etherscan: None,
129 rpc_consensus_url: None,
130 skip_fcu: None,
131 skip_new_payload: None,
132 skip_genesis_validation: false,
133 reorg_frequency: None,
134 reorg_depth: None,
135 engine_api_store: None,
136 invalid_block_hook: Some(InvalidBlockSelection::default()),
137 healthy_node_rpc_url: None,
138 ethstats: None,
139 startup_sync_state_idle: false,
140 }
141 }
142}
143
144#[derive(Debug, Clone, PartialEq, Eq, derive_more::Deref)]
155pub struct InvalidBlockSelection(HashSet<InvalidBlockHookType>);
156
157impl Default for InvalidBlockSelection {
158 fn default() -> Self {
159 Self([InvalidBlockHookType::Witness].into())
160 }
161}
162
163impl InvalidBlockSelection {
164 pub fn try_from_selection<I, T>(selection: I) -> Result<Self, T::Error>
204 where
205 I: IntoIterator<Item = T>,
206 T: TryInto<InvalidBlockHookType>,
207 {
208 selection.into_iter().map(TryInto::try_into).collect()
209 }
210
211 pub fn to_selection(&self) -> HashSet<InvalidBlockHookType> {
213 self.0.clone()
214 }
215}
216
217impl From<&[InvalidBlockHookType]> for InvalidBlockSelection {
218 fn from(s: &[InvalidBlockHookType]) -> Self {
219 Self(s.iter().copied().collect())
220 }
221}
222
223impl From<Vec<InvalidBlockHookType>> for InvalidBlockSelection {
224 fn from(s: Vec<InvalidBlockHookType>) -> Self {
225 Self(s.into_iter().collect())
226 }
227}
228
229impl<const N: usize> From<[InvalidBlockHookType; N]> for InvalidBlockSelection {
230 fn from(s: [InvalidBlockHookType; N]) -> Self {
231 Self(s.iter().copied().collect())
232 }
233}
234
235impl FromIterator<InvalidBlockHookType> for InvalidBlockSelection {
236 fn from_iter<I>(iter: I) -> Self
237 where
238 I: IntoIterator<Item = InvalidBlockHookType>,
239 {
240 Self(iter.into_iter().collect())
241 }
242}
243
244impl FromStr for InvalidBlockSelection {
245 type Err = ParseError;
246
247 fn from_str(s: &str) -> Result<Self, Self::Err> {
248 if s.is_empty() {
249 return Ok(Self(Default::default()))
250 }
251 let hooks = s.split(',').map(str::trim).peekable();
252 Self::try_from_selection(hooks)
253 }
254}
255
256impl fmt::Display for InvalidBlockSelection {
257 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258 write!(f, "[{}]", self.0.iter().map(|s| s.to_string()).collect::<Vec<_>>().join(", "))
259 }
260}
261
262#[derive(Clone, Debug, Default)]
264#[non_exhaustive]
265struct InvalidBlockSelectionValueParser;
266
267impl TypedValueParser for InvalidBlockSelectionValueParser {
268 type Value = InvalidBlockSelection;
269
270 fn parse_ref(
271 &self,
272 _cmd: &Command,
273 arg: Option<&Arg>,
274 value: &OsStr,
275 ) -> Result<Self::Value, clap::Error> {
276 let val =
277 value.to_str().ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8))?;
278 val.parse::<InvalidBlockSelection>().map_err(|err| {
279 let arg = arg.map(|a| a.to_string()).unwrap_or_else(|| "...".to_owned());
280 let possible_values = InvalidBlockHookType::all_variant_names().to_vec().join(",");
281 let msg = format!(
282 "Invalid value '{val}' for {arg}: {err}.\n [possible values: {possible_values}]"
283 );
284 clap::Error::raw(clap::error::ErrorKind::InvalidValue, msg)
285 })
286 }
287
288 fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
289 let values = InvalidBlockHookType::all_variant_names().iter().map(PossibleValue::new);
290 Some(Box::new(values))
291 }
292}
293
294#[derive(
296 Debug,
297 Clone,
298 Copy,
299 PartialEq,
300 Eq,
301 Hash,
302 AsRefStr,
303 IntoStaticStr,
304 VariantNames,
305 VariantArray,
306 EnumIter,
307)]
308#[strum(serialize_all = "kebab-case")]
309pub enum InvalidBlockHookType {
310 Witness,
312 PreState,
314 Opcode,
316}
317
318impl FromStr for InvalidBlockHookType {
319 type Err = ParseError;
320
321 fn from_str(s: &str) -> Result<Self, Self::Err> {
322 Ok(match s {
323 "witness" => Self::Witness,
324 "prestate" => Self::PreState,
325 "opcode" => Self::Opcode,
326 _ => return Err(ParseError::VariantNotFound),
327 })
328 }
329}
330
331impl TryFrom<&str> for InvalidBlockHookType {
332 type Error = ParseError;
333 fn try_from(s: &str) -> Result<Self, <Self as TryFrom<&str>>::Error> {
334 FromStr::from_str(s)
335 }
336}
337
338impl fmt::Display for InvalidBlockHookType {
339 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
340 f.pad(self.as_ref())
341 }
342}
343
344impl InvalidBlockHookType {
345 pub const fn all_variant_names() -> &'static [&'static str] {
347 <Self as VariantNames>::VARIANTS
348 }
349}
350
351#[cfg(test)]
352mod tests {
353 use super::*;
354 use clap::Parser;
355
356 #[derive(Parser)]
358 struct CommandParser<T: Args> {
359 #[command(flatten)]
360 args: T,
361 }
362
363 #[test]
364 fn test_parse_default_debug_args() {
365 let default_args = DebugArgs::default();
366 let args = CommandParser::<DebugArgs>::parse_from(["reth"]).args;
367 assert_eq!(args, default_args);
368 }
369
370 #[test]
371 fn test_parse_invalid_block_args_none() {
372 let expected_args = DebugArgs {
373 invalid_block_hook: Some(InvalidBlockSelection::from(vec![])),
374 ..Default::default()
375 };
376 let args =
377 CommandParser::<DebugArgs>::parse_from(["reth", "--debug.invalid-block-hook", ""]).args;
378 assert_eq!(args, expected_args);
379 }
380
381 #[test]
382 fn test_parse_invalid_block_args() {
383 let expected_args = DebugArgs {
384 invalid_block_hook: Some(InvalidBlockSelection::from([InvalidBlockHookType::Witness])),
385 ..Default::default()
386 };
387 let args = CommandParser::<DebugArgs>::parse_from([
388 "reth",
389 "--debug.invalid-block-hook",
390 "witness",
391 ])
392 .args;
393 assert_eq!(args, expected_args);
394
395 let expected_args = DebugArgs {
396 invalid_block_hook: Some(InvalidBlockSelection::from([
397 InvalidBlockHookType::Witness,
398 InvalidBlockHookType::PreState,
399 ])),
400 ..Default::default()
401 };
402 let args = CommandParser::<DebugArgs>::parse_from([
403 "reth",
404 "--debug.invalid-block-hook",
405 "witness,prestate",
406 ])
407 .args;
408 assert_eq!(args, expected_args);
409
410 let args = CommandParser::<DebugArgs>::parse_from([
411 "reth",
412 "--debug.invalid-block-hook",
413 "witness,prestate,prestate",
414 ])
415 .args;
416 assert_eq!(args, expected_args);
417
418 let args = CommandParser::<DebugArgs>::parse_from([
419 "reth",
420 "--debug.invalid-block-hook",
421 "witness,witness,prestate",
422 ])
423 .args;
424 assert_eq!(args, expected_args);
425
426 let args = CommandParser::<DebugArgs>::parse_from([
427 "reth",
428 "--debug.invalid-block-hook",
429 "prestate,witness,prestate",
430 ])
431 .args;
432 assert_eq!(args, expected_args);
433 }
434}