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-state-root", help_heading = "Debug", hide = true)]
66 pub skip_state_root: bool,
67
68 #[arg(long = "debug.skip-genesis-validation", help_heading = "Debug")]
75 pub skip_genesis_validation: bool,
76
77 #[arg(long = "debug.reorg-frequency", help_heading = "Debug")]
79 pub reorg_frequency: Option<usize>,
80
81 #[arg(long = "debug.reorg-depth", requires = "reorg_frequency", help_heading = "Debug")]
83 pub reorg_depth: Option<usize>,
84
85 #[arg(long = "debug.engine-api-store", help_heading = "Debug", value_name = "PATH")]
89 pub engine_api_store: Option<PathBuf>,
90
91 #[arg(
95 long = "debug.invalid-block-hook",
96 help_heading = "Debug",
97 value_parser = InvalidBlockSelectionValueParser::default(),
98 default_value = "witness"
99 )]
100 pub invalid_block_hook: Option<InvalidBlockSelection>,
101
102 #[arg(
109 long = "debug.healthy-node-rpc-url",
110 help_heading = "Debug",
111 value_name = "URL",
112 verbatim_doc_comment
113 )]
114 pub healthy_node_rpc_url: Option<String>,
115
116 #[arg(long = "ethstats", help_heading = "Debug")]
119 pub ethstats: Option<String>,
120
121 #[arg(long = "debug.startup-sync-state-idle", help_heading = "Debug")]
126 pub startup_sync_state_idle: bool,
127}
128
129impl Default for DebugArgs {
130 fn default() -> Self {
131 Self {
132 terminate: false,
133 tip: None,
134 max_block: None,
135 etherscan: None,
136 rpc_consensus_url: None,
137 skip_fcu: None,
138 skip_new_payload: None,
139 skip_state_root: false,
140 skip_genesis_validation: false,
141 reorg_frequency: None,
142 reorg_depth: None,
143 engine_api_store: None,
144 invalid_block_hook: Some(InvalidBlockSelection::default()),
145 healthy_node_rpc_url: None,
146 ethstats: None,
147 startup_sync_state_idle: false,
148 }
149 }
150}
151
152#[derive(Debug, Clone, PartialEq, Eq, derive_more::Deref)]
163pub struct InvalidBlockSelection(HashSet<InvalidBlockHookType>);
164
165impl Default for InvalidBlockSelection {
166 fn default() -> Self {
167 Self([InvalidBlockHookType::Witness].into())
168 }
169}
170
171impl InvalidBlockSelection {
172 pub fn try_from_selection<I, T>(selection: I) -> Result<Self, T::Error>
212 where
213 I: IntoIterator<Item = T>,
214 T: TryInto<InvalidBlockHookType>,
215 {
216 selection.into_iter().map(TryInto::try_into).collect()
217 }
218
219 pub fn to_selection(&self) -> HashSet<InvalidBlockHookType> {
221 self.0.clone()
222 }
223}
224
225impl From<&[InvalidBlockHookType]> for InvalidBlockSelection {
226 fn from(s: &[InvalidBlockHookType]) -> Self {
227 Self(s.iter().copied().collect())
228 }
229}
230
231impl From<Vec<InvalidBlockHookType>> for InvalidBlockSelection {
232 fn from(s: Vec<InvalidBlockHookType>) -> Self {
233 Self(s.into_iter().collect())
234 }
235}
236
237impl<const N: usize> From<[InvalidBlockHookType; N]> for InvalidBlockSelection {
238 fn from(s: [InvalidBlockHookType; N]) -> Self {
239 Self(s.iter().copied().collect())
240 }
241}
242
243impl FromIterator<InvalidBlockHookType> for InvalidBlockSelection {
244 fn from_iter<I>(iter: I) -> Self
245 where
246 I: IntoIterator<Item = InvalidBlockHookType>,
247 {
248 Self(iter.into_iter().collect())
249 }
250}
251
252impl FromStr for InvalidBlockSelection {
253 type Err = ParseError;
254
255 fn from_str(s: &str) -> Result<Self, Self::Err> {
256 if s.is_empty() {
257 return Ok(Self(Default::default()))
258 }
259 let hooks = s.split(',').map(str::trim).peekable();
260 Self::try_from_selection(hooks)
261 }
262}
263
264impl fmt::Display for InvalidBlockSelection {
265 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266 write!(f, "[{}]", self.0.iter().map(|s| s.to_string()).collect::<Vec<_>>().join(", "))
267 }
268}
269
270#[derive(Clone, Debug, Default)]
272#[non_exhaustive]
273struct InvalidBlockSelectionValueParser;
274
275impl TypedValueParser for InvalidBlockSelectionValueParser {
276 type Value = InvalidBlockSelection;
277
278 fn parse_ref(
279 &self,
280 _cmd: &Command,
281 arg: Option<&Arg>,
282 value: &OsStr,
283 ) -> Result<Self::Value, clap::Error> {
284 let val =
285 value.to_str().ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8))?;
286 val.parse::<InvalidBlockSelection>().map_err(|err| {
287 let arg = arg.map(|a| a.to_string()).unwrap_or_else(|| "...".to_owned());
288 let possible_values = InvalidBlockHookType::all_variant_names().to_vec().join(",");
289 let msg = format!(
290 "Invalid value '{val}' for {arg}: {err}.\n [possible values: {possible_values}]"
291 );
292 clap::Error::raw(clap::error::ErrorKind::InvalidValue, msg)
293 })
294 }
295
296 fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
297 let values = InvalidBlockHookType::all_variant_names().iter().map(PossibleValue::new);
298 Some(Box::new(values))
299 }
300}
301
302#[derive(
304 Debug,
305 Clone,
306 Copy,
307 PartialEq,
308 Eq,
309 Hash,
310 AsRefStr,
311 IntoStaticStr,
312 VariantNames,
313 VariantArray,
314 EnumIter,
315)]
316#[strum(serialize_all = "kebab-case")]
317pub enum InvalidBlockHookType {
318 Witness,
320 PreState,
322 Opcode,
324}
325
326impl FromStr for InvalidBlockHookType {
327 type Err = ParseError;
328
329 fn from_str(s: &str) -> Result<Self, Self::Err> {
330 Ok(match s {
331 "witness" => Self::Witness,
332 "prestate" => Self::PreState,
333 "opcode" => Self::Opcode,
334 _ => return Err(ParseError::VariantNotFound),
335 })
336 }
337}
338
339impl TryFrom<&str> for InvalidBlockHookType {
340 type Error = ParseError;
341 fn try_from(s: &str) -> Result<Self, <Self as TryFrom<&str>>::Error> {
342 FromStr::from_str(s)
343 }
344}
345
346impl fmt::Display for InvalidBlockHookType {
347 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
348 f.pad(self.as_ref())
349 }
350}
351
352impl InvalidBlockHookType {
353 pub const fn all_variant_names() -> &'static [&'static str] {
355 <Self as VariantNames>::VARIANTS
356 }
357}
358
359#[cfg(test)]
360mod tests {
361 use super::*;
362 use clap::Parser;
363
364 #[derive(Parser)]
366 struct CommandParser<T: Args> {
367 #[command(flatten)]
368 args: T,
369 }
370
371 #[test]
372 fn test_parse_default_debug_args() {
373 let default_args = DebugArgs::default();
374 let args = CommandParser::<DebugArgs>::parse_from(["reth"]).args;
375 assert_eq!(args, default_args);
376 }
377
378 #[test]
379 fn test_parse_skip_state_root() {
380 let expected_args = DebugArgs { skip_state_root: true, ..Default::default() };
381 let args = CommandParser::<DebugArgs>::parse_from(["reth", "--debug.skip-state-root"]).args;
382 assert_eq!(args, expected_args);
383 }
384
385 #[test]
386 fn test_parse_invalid_block_args_none() {
387 let expected_args = DebugArgs {
388 invalid_block_hook: Some(InvalidBlockSelection::from(vec![])),
389 ..Default::default()
390 };
391 let args =
392 CommandParser::<DebugArgs>::parse_from(["reth", "--debug.invalid-block-hook", ""]).args;
393 assert_eq!(args, expected_args);
394 }
395
396 #[test]
397 fn test_parse_invalid_block_args() {
398 let expected_args = DebugArgs {
399 invalid_block_hook: Some(InvalidBlockSelection::from([InvalidBlockHookType::Witness])),
400 ..Default::default()
401 };
402 let args = CommandParser::<DebugArgs>::parse_from([
403 "reth",
404 "--debug.invalid-block-hook",
405 "witness",
406 ])
407 .args;
408 assert_eq!(args, expected_args);
409
410 let expected_args = DebugArgs {
411 invalid_block_hook: Some(InvalidBlockSelection::from([
412 InvalidBlockHookType::Witness,
413 InvalidBlockHookType::PreState,
414 ])),
415 ..Default::default()
416 };
417 let args = CommandParser::<DebugArgs>::parse_from([
418 "reth",
419 "--debug.invalid-block-hook",
420 "witness,prestate",
421 ])
422 .args;
423 assert_eq!(args, expected_args);
424
425 let args = CommandParser::<DebugArgs>::parse_from([
426 "reth",
427 "--debug.invalid-block-hook",
428 "witness,prestate,prestate",
429 ])
430 .args;
431 assert_eq!(args, expected_args);
432
433 let args = CommandParser::<DebugArgs>::parse_from([
434 "reth",
435 "--debug.invalid-block-hook",
436 "witness,witness,prestate",
437 ])
438 .args;
439 assert_eq!(args, expected_args);
440
441 let args = CommandParser::<DebugArgs>::parse_from([
442 "reth",
443 "--debug.invalid-block-hook",
444 "prestate,witness,prestate",
445 ])
446 .args;
447 assert_eq!(args, expected_args);
448 }
449}