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