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.reorg-frequency", help_heading = "Debug")]
63 pub reorg_frequency: Option<usize>,
64
65 #[arg(long = "debug.reorg-depth", requires = "reorg_frequency", help_heading = "Debug")]
67 pub reorg_depth: Option<usize>,
68
69 #[arg(long = "debug.engine-api-store", help_heading = "Debug", value_name = "PATH")]
73 pub engine_api_store: Option<PathBuf>,
74
75 #[arg(
79 long = "debug.invalid-block-hook",
80 help_heading = "Debug",
81 value_parser = InvalidBlockSelectionValueParser::default(),
82 default_value = "witness"
83 )]
84 pub invalid_block_hook: Option<InvalidBlockSelection>,
85
86 #[arg(
93 long = "debug.healthy-node-rpc-url",
94 help_heading = "Debug",
95 value_name = "URL",
96 verbatim_doc_comment
97 )]
98 pub healthy_node_rpc_url: Option<String>,
99
100 #[arg(long = "ethstats", help_heading = "Debug")]
103 pub ethstats: Option<String>,
104
105 #[arg(long = "debug.startup-sync-state-idle", help_heading = "Debug")]
110 pub startup_sync_state_idle: bool,
111}
112
113impl Default for DebugArgs {
114 fn default() -> Self {
115 Self {
116 terminate: false,
117 tip: None,
118 max_block: None,
119 etherscan: None,
120 rpc_consensus_url: None,
121 skip_fcu: None,
122 skip_new_payload: None,
123 reorg_frequency: None,
124 reorg_depth: None,
125 engine_api_store: None,
126 invalid_block_hook: Some(InvalidBlockSelection::default()),
127 healthy_node_rpc_url: None,
128 ethstats: None,
129 startup_sync_state_idle: false,
130 }
131 }
132}
133
134#[derive(Debug, Clone, PartialEq, Eq, derive_more::Deref)]
145pub struct InvalidBlockSelection(HashSet<InvalidBlockHookType>);
146
147impl Default for InvalidBlockSelection {
148 fn default() -> Self {
149 Self([InvalidBlockHookType::Witness].into())
150 }
151}
152
153impl InvalidBlockSelection {
154 pub fn try_from_selection<I, T>(selection: I) -> Result<Self, T::Error>
194 where
195 I: IntoIterator<Item = T>,
196 T: TryInto<InvalidBlockHookType>,
197 {
198 selection.into_iter().map(TryInto::try_into).collect()
199 }
200
201 pub fn to_selection(&self) -> HashSet<InvalidBlockHookType> {
203 self.0.clone()
204 }
205}
206
207impl From<&[InvalidBlockHookType]> for InvalidBlockSelection {
208 fn from(s: &[InvalidBlockHookType]) -> Self {
209 Self(s.iter().copied().collect())
210 }
211}
212
213impl From<Vec<InvalidBlockHookType>> for InvalidBlockSelection {
214 fn from(s: Vec<InvalidBlockHookType>) -> Self {
215 Self(s.into_iter().collect())
216 }
217}
218
219impl<const N: usize> From<[InvalidBlockHookType; N]> for InvalidBlockSelection {
220 fn from(s: [InvalidBlockHookType; N]) -> Self {
221 Self(s.iter().copied().collect())
222 }
223}
224
225impl FromIterator<InvalidBlockHookType> for InvalidBlockSelection {
226 fn from_iter<I>(iter: I) -> Self
227 where
228 I: IntoIterator<Item = InvalidBlockHookType>,
229 {
230 Self(iter.into_iter().collect())
231 }
232}
233
234impl FromStr for InvalidBlockSelection {
235 type Err = ParseError;
236
237 fn from_str(s: &str) -> Result<Self, Self::Err> {
238 if s.is_empty() {
239 return Ok(Self(Default::default()))
240 }
241 let hooks = s.split(',').map(str::trim).peekable();
242 Self::try_from_selection(hooks)
243 }
244}
245
246impl fmt::Display for InvalidBlockSelection {
247 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248 write!(f, "[{}]", self.0.iter().map(|s| s.to_string()).collect::<Vec<_>>().join(", "))
249 }
250}
251
252#[derive(Clone, Debug, Default)]
254#[non_exhaustive]
255struct InvalidBlockSelectionValueParser;
256
257impl TypedValueParser for InvalidBlockSelectionValueParser {
258 type Value = InvalidBlockSelection;
259
260 fn parse_ref(
261 &self,
262 _cmd: &Command,
263 arg: Option<&Arg>,
264 value: &OsStr,
265 ) -> Result<Self::Value, clap::Error> {
266 let val =
267 value.to_str().ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8))?;
268 val.parse::<InvalidBlockSelection>().map_err(|err| {
269 let arg = arg.map(|a| a.to_string()).unwrap_or_else(|| "...".to_owned());
270 let possible_values = InvalidBlockHookType::all_variant_names().to_vec().join(",");
271 let msg = format!(
272 "Invalid value '{val}' for {arg}: {err}.\n [possible values: {possible_values}]"
273 );
274 clap::Error::raw(clap::error::ErrorKind::InvalidValue, msg)
275 })
276 }
277
278 fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
279 let values = InvalidBlockHookType::all_variant_names().iter().map(PossibleValue::new);
280 Some(Box::new(values))
281 }
282}
283
284#[derive(
286 Debug,
287 Clone,
288 Copy,
289 PartialEq,
290 Eq,
291 Hash,
292 AsRefStr,
293 IntoStaticStr,
294 VariantNames,
295 VariantArray,
296 EnumIter,
297)]
298#[strum(serialize_all = "kebab-case")]
299pub enum InvalidBlockHookType {
300 Witness,
302 PreState,
304 Opcode,
306}
307
308impl FromStr for InvalidBlockHookType {
309 type Err = ParseError;
310
311 fn from_str(s: &str) -> Result<Self, Self::Err> {
312 Ok(match s {
313 "witness" => Self::Witness,
314 "prestate" => Self::PreState,
315 "opcode" => Self::Opcode,
316 _ => return Err(ParseError::VariantNotFound),
317 })
318 }
319}
320
321impl TryFrom<&str> for InvalidBlockHookType {
322 type Error = ParseError;
323 fn try_from(s: &str) -> Result<Self, <Self as TryFrom<&str>>::Error> {
324 FromStr::from_str(s)
325 }
326}
327
328impl fmt::Display for InvalidBlockHookType {
329 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
330 f.pad(self.as_ref())
331 }
332}
333
334impl InvalidBlockHookType {
335 pub const fn all_variant_names() -> &'static [&'static str] {
337 <Self as VariantNames>::VARIANTS
338 }
339}
340
341#[cfg(test)]
342mod tests {
343 use super::*;
344 use clap::Parser;
345
346 #[derive(Parser)]
348 struct CommandParser<T: Args> {
349 #[command(flatten)]
350 args: T,
351 }
352
353 #[test]
354 fn test_parse_default_debug_args() {
355 let default_args = DebugArgs::default();
356 let args = CommandParser::<DebugArgs>::parse_from(["reth"]).args;
357 assert_eq!(args, default_args);
358 }
359
360 #[test]
361 fn test_parse_invalid_block_args() {
362 let expected_args = DebugArgs {
363 invalid_block_hook: Some(InvalidBlockSelection::from([InvalidBlockHookType::Witness])),
364 ..Default::default()
365 };
366 let args = CommandParser::<DebugArgs>::parse_from([
367 "reth",
368 "--debug.invalid-block-hook",
369 "witness",
370 ])
371 .args;
372 assert_eq!(args, expected_args);
373
374 let expected_args = DebugArgs {
375 invalid_block_hook: Some(InvalidBlockSelection::from([
376 InvalidBlockHookType::Witness,
377 InvalidBlockHookType::PreState,
378 ])),
379 ..Default::default()
380 };
381 let args = CommandParser::<DebugArgs>::parse_from([
382 "reth",
383 "--debug.invalid-block-hook",
384 "witness,prestate",
385 ])
386 .args;
387 assert_eq!(args, expected_args);
388
389 let args = CommandParser::<DebugArgs>::parse_from([
390 "reth",
391 "--debug.invalid-block-hook",
392 "witness,prestate,prestate",
393 ])
394 .args;
395 assert_eq!(args, expected_args);
396
397 let args = CommandParser::<DebugArgs>::parse_from([
398 "reth",
399 "--debug.invalid-block-hook",
400 "witness,witness,prestate",
401 ])
402 .args;
403 assert_eq!(args, expected_args);
404
405 let args = CommandParser::<DebugArgs>::parse_from([
406 "reth",
407 "--debug.invalid-block-hook",
408 "prestate,witness,prestate",
409 ])
410 .args;
411 assert_eq!(args, expected_args);
412 }
413}