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
106impl Default for DebugArgs {
107 fn default() -> Self {
108 Self {
109 terminate: false,
110 tip: None,
111 max_block: None,
112 etherscan: None,
113 rpc_consensus_url: None,
114 skip_fcu: None,
115 skip_new_payload: None,
116 reorg_frequency: None,
117 reorg_depth: None,
118 engine_api_store: None,
119 invalid_block_hook: Some(InvalidBlockSelection::default()),
120 healthy_node_rpc_url: None,
121 ethstats: None,
122 }
123 }
124}
125
126#[derive(Debug, Clone, PartialEq, Eq, derive_more::Deref)]
137pub struct InvalidBlockSelection(HashSet<InvalidBlockHookType>);
138
139impl Default for InvalidBlockSelection {
140 fn default() -> Self {
141 Self([InvalidBlockHookType::Witness].into())
142 }
143}
144
145impl InvalidBlockSelection {
146 pub fn try_from_selection<I, T>(selection: I) -> Result<Self, T::Error>
186 where
187 I: IntoIterator<Item = T>,
188 T: TryInto<InvalidBlockHookType>,
189 {
190 selection.into_iter().map(TryInto::try_into).collect()
191 }
192
193 pub fn to_selection(&self) -> HashSet<InvalidBlockHookType> {
195 self.0.clone()
196 }
197}
198
199impl From<&[InvalidBlockHookType]> for InvalidBlockSelection {
200 fn from(s: &[InvalidBlockHookType]) -> Self {
201 Self(s.iter().copied().collect())
202 }
203}
204
205impl From<Vec<InvalidBlockHookType>> for InvalidBlockSelection {
206 fn from(s: Vec<InvalidBlockHookType>) -> Self {
207 Self(s.into_iter().collect())
208 }
209}
210
211impl<const N: usize> From<[InvalidBlockHookType; N]> for InvalidBlockSelection {
212 fn from(s: [InvalidBlockHookType; N]) -> Self {
213 Self(s.iter().copied().collect())
214 }
215}
216
217impl FromIterator<InvalidBlockHookType> for InvalidBlockSelection {
218 fn from_iter<I>(iter: I) -> Self
219 where
220 I: IntoIterator<Item = InvalidBlockHookType>,
221 {
222 Self(iter.into_iter().collect())
223 }
224}
225
226impl FromStr for InvalidBlockSelection {
227 type Err = ParseError;
228
229 fn from_str(s: &str) -> Result<Self, Self::Err> {
230 if s.is_empty() {
231 return Ok(Self(Default::default()))
232 }
233 let hooks = s.split(',').map(str::trim).peekable();
234 Self::try_from_selection(hooks)
235 }
236}
237
238impl fmt::Display for InvalidBlockSelection {
239 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240 write!(f, "[{}]", self.0.iter().map(|s| s.to_string()).collect::<Vec<_>>().join(", "))
241 }
242}
243
244#[derive(Clone, Debug, Default)]
246#[non_exhaustive]
247struct InvalidBlockSelectionValueParser;
248
249impl TypedValueParser for InvalidBlockSelectionValueParser {
250 type Value = InvalidBlockSelection;
251
252 fn parse_ref(
253 &self,
254 _cmd: &Command,
255 arg: Option<&Arg>,
256 value: &OsStr,
257 ) -> Result<Self::Value, clap::Error> {
258 let val =
259 value.to_str().ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8))?;
260 val.parse::<InvalidBlockSelection>().map_err(|err| {
261 let arg = arg.map(|a| a.to_string()).unwrap_or_else(|| "...".to_owned());
262 let possible_values = InvalidBlockHookType::all_variant_names().to_vec().join(",");
263 let msg = format!(
264 "Invalid value '{val}' for {arg}: {err}.\n [possible values: {possible_values}]"
265 );
266 clap::Error::raw(clap::error::ErrorKind::InvalidValue, msg)
267 })
268 }
269
270 fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
271 let values = InvalidBlockHookType::all_variant_names().iter().map(PossibleValue::new);
272 Some(Box::new(values))
273 }
274}
275
276#[derive(
278 Debug,
279 Clone,
280 Copy,
281 PartialEq,
282 Eq,
283 Hash,
284 AsRefStr,
285 IntoStaticStr,
286 VariantNames,
287 VariantArray,
288 EnumIter,
289)]
290#[strum(serialize_all = "kebab-case")]
291pub enum InvalidBlockHookType {
292 Witness,
294 PreState,
296 Opcode,
298}
299
300impl FromStr for InvalidBlockHookType {
301 type Err = ParseError;
302
303 fn from_str(s: &str) -> Result<Self, Self::Err> {
304 Ok(match s {
305 "witness" => Self::Witness,
306 "prestate" => Self::PreState,
307 "opcode" => Self::Opcode,
308 _ => return Err(ParseError::VariantNotFound),
309 })
310 }
311}
312
313impl TryFrom<&str> for InvalidBlockHookType {
314 type Error = ParseError;
315 fn try_from(s: &str) -> Result<Self, <Self as TryFrom<&str>>::Error> {
316 FromStr::from_str(s)
317 }
318}
319
320impl fmt::Display for InvalidBlockHookType {
321 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
322 f.pad(self.as_ref())
323 }
324}
325
326impl InvalidBlockHookType {
327 pub const fn all_variant_names() -> &'static [&'static str] {
329 <Self as VariantNames>::VARIANTS
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336 use clap::Parser;
337
338 #[derive(Parser)]
340 struct CommandParser<T: Args> {
341 #[command(flatten)]
342 args: T,
343 }
344
345 #[test]
346 fn test_parse_default_debug_args() {
347 let default_args = DebugArgs::default();
348 let args = CommandParser::<DebugArgs>::parse_from(["reth"]).args;
349 assert_eq!(args, default_args);
350 }
351
352 #[test]
353 fn test_parse_invalid_block_args() {
354 let expected_args = DebugArgs {
355 invalid_block_hook: Some(InvalidBlockSelection::from([InvalidBlockHookType::Witness])),
356 ..Default::default()
357 };
358 let args = CommandParser::<DebugArgs>::parse_from([
359 "reth",
360 "--debug.invalid-block-hook",
361 "witness",
362 ])
363 .args;
364 assert_eq!(args, expected_args);
365
366 let expected_args = DebugArgs {
367 invalid_block_hook: Some(InvalidBlockSelection::from([
368 InvalidBlockHookType::Witness,
369 InvalidBlockHookType::PreState,
370 ])),
371 ..Default::default()
372 };
373 let args = CommandParser::<DebugArgs>::parse_from([
374 "reth",
375 "--debug.invalid-block-hook",
376 "witness,prestate",
377 ])
378 .args;
379 assert_eq!(args, expected_args);
380
381 let args = CommandParser::<DebugArgs>::parse_from([
382 "reth",
383 "--debug.invalid-block-hook",
384 "witness,prestate,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,witness,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 "prestate,witness,prestate",
401 ])
402 .args;
403 assert_eq!(args, expected_args);
404 }
405}