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_ws",
36 value_name = "ETHERSCAN_API_URL"
37 )]
38 pub etherscan: Option<Option<String>>,
39
40 #[arg(
42 long = "debug.rpc-consensus-ws",
43 help_heading = "Debug",
44 conflicts_with = "tip",
45 conflicts_with = "etherscan"
46 )]
47 pub rpc_consensus_ws: Option<String>,
48
49 #[arg(long = "debug.skip-fcu", help_heading = "Debug")]
51 pub skip_fcu: Option<usize>,
52
53 #[arg(long = "debug.skip-new-payload", help_heading = "Debug")]
55 pub skip_new_payload: Option<usize>,
56
57 #[arg(long = "debug.reorg-frequency", help_heading = "Debug")]
59 pub reorg_frequency: Option<usize>,
60
61 #[arg(long = "debug.reorg-depth", requires = "reorg_frequency", help_heading = "Debug")]
63 pub reorg_depth: Option<usize>,
64
65 #[arg(long = "debug.engine-api-store", help_heading = "Debug", value_name = "PATH")]
69 pub engine_api_store: Option<PathBuf>,
70
71 #[arg(
75 long = "debug.invalid-block-hook",
76 help_heading = "Debug",
77 value_parser = InvalidBlockSelectionValueParser::default(),
78 default_value = "witness"
79 )]
80 pub invalid_block_hook: Option<InvalidBlockSelection>,
81
82 #[arg(
89 long = "debug.healthy-node-rpc-url",
90 help_heading = "Debug",
91 value_name = "URL",
92 verbatim_doc_comment
93 )]
94 pub healthy_node_rpc_url: Option<String>,
95
96 #[arg(long = "ethstats", help_heading = "Debug")]
99 pub ethstats: Option<String>,
100}
101
102impl Default for DebugArgs {
103 fn default() -> Self {
104 Self {
105 terminate: false,
106 tip: None,
107 max_block: None,
108 etherscan: None,
109 rpc_consensus_ws: None,
110 skip_fcu: None,
111 skip_new_payload: None,
112 reorg_frequency: None,
113 reorg_depth: None,
114 engine_api_store: None,
115 invalid_block_hook: Some(InvalidBlockSelection::default()),
116 healthy_node_rpc_url: None,
117 ethstats: None,
118 }
119 }
120}
121
122#[derive(Debug, Clone, PartialEq, Eq, derive_more::Deref)]
133pub struct InvalidBlockSelection(HashSet<InvalidBlockHookType>);
134
135impl Default for InvalidBlockSelection {
136 fn default() -> Self {
137 Self([InvalidBlockHookType::Witness].into())
138 }
139}
140
141impl InvalidBlockSelection {
142 pub fn try_from_selection<I, T>(selection: I) -> Result<Self, T::Error>
182 where
183 I: IntoIterator<Item = T>,
184 T: TryInto<InvalidBlockHookType>,
185 {
186 selection.into_iter().map(TryInto::try_into).collect()
187 }
188
189 pub fn to_selection(&self) -> HashSet<InvalidBlockHookType> {
191 self.0.clone()
192 }
193}
194
195impl From<&[InvalidBlockHookType]> for InvalidBlockSelection {
196 fn from(s: &[InvalidBlockHookType]) -> Self {
197 Self(s.iter().copied().collect())
198 }
199}
200
201impl From<Vec<InvalidBlockHookType>> for InvalidBlockSelection {
202 fn from(s: Vec<InvalidBlockHookType>) -> Self {
203 Self(s.into_iter().collect())
204 }
205}
206
207impl<const N: usize> From<[InvalidBlockHookType; N]> for InvalidBlockSelection {
208 fn from(s: [InvalidBlockHookType; N]) -> Self {
209 Self(s.iter().copied().collect())
210 }
211}
212
213impl FromIterator<InvalidBlockHookType> for InvalidBlockSelection {
214 fn from_iter<I>(iter: I) -> Self
215 where
216 I: IntoIterator<Item = InvalidBlockHookType>,
217 {
218 Self(iter.into_iter().collect())
219 }
220}
221
222impl FromStr for InvalidBlockSelection {
223 type Err = ParseError;
224
225 fn from_str(s: &str) -> Result<Self, Self::Err> {
226 if s.is_empty() {
227 return Ok(Self(Default::default()))
228 }
229 let hooks = s.split(',').map(str::trim).peekable();
230 Self::try_from_selection(hooks)
231 }
232}
233
234impl fmt::Display for InvalidBlockSelection {
235 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
236 write!(f, "[{}]", self.0.iter().map(|s| s.to_string()).collect::<Vec<_>>().join(", "))
237 }
238}
239
240#[derive(Clone, Debug, Default)]
242#[non_exhaustive]
243struct InvalidBlockSelectionValueParser;
244
245impl TypedValueParser for InvalidBlockSelectionValueParser {
246 type Value = InvalidBlockSelection;
247
248 fn parse_ref(
249 &self,
250 _cmd: &Command,
251 arg: Option<&Arg>,
252 value: &OsStr,
253 ) -> Result<Self::Value, clap::Error> {
254 let val =
255 value.to_str().ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8))?;
256 val.parse::<InvalidBlockSelection>().map_err(|err| {
257 let arg = arg.map(|a| a.to_string()).unwrap_or_else(|| "...".to_owned());
258 let possible_values = InvalidBlockHookType::all_variant_names().to_vec().join(",");
259 let msg = format!(
260 "Invalid value '{val}' for {arg}: {err}.\n [possible values: {possible_values}]"
261 );
262 clap::Error::raw(clap::error::ErrorKind::InvalidValue, msg)
263 })
264 }
265
266 fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
267 let values = InvalidBlockHookType::all_variant_names().iter().map(PossibleValue::new);
268 Some(Box::new(values))
269 }
270}
271
272#[derive(
274 Debug,
275 Clone,
276 Copy,
277 PartialEq,
278 Eq,
279 Hash,
280 AsRefStr,
281 IntoStaticStr,
282 VariantNames,
283 VariantArray,
284 EnumIter,
285)]
286#[strum(serialize_all = "kebab-case")]
287pub enum InvalidBlockHookType {
288 Witness,
290 PreState,
292 Opcode,
294}
295
296impl FromStr for InvalidBlockHookType {
297 type Err = ParseError;
298
299 fn from_str(s: &str) -> Result<Self, Self::Err> {
300 Ok(match s {
301 "witness" => Self::Witness,
302 "prestate" => Self::PreState,
303 "opcode" => Self::Opcode,
304 _ => return Err(ParseError::VariantNotFound),
305 })
306 }
307}
308
309impl TryFrom<&str> for InvalidBlockHookType {
310 type Error = ParseError;
311 fn try_from(s: &str) -> Result<Self, <Self as TryFrom<&str>>::Error> {
312 FromStr::from_str(s)
313 }
314}
315
316impl fmt::Display for InvalidBlockHookType {
317 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
318 f.pad(self.as_ref())
319 }
320}
321
322impl InvalidBlockHookType {
323 pub const fn all_variant_names() -> &'static [&'static str] {
325 <Self as VariantNames>::VARIANTS
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332 use clap::Parser;
333
334 #[derive(Parser)]
336 struct CommandParser<T: Args> {
337 #[command(flatten)]
338 args: T,
339 }
340
341 #[test]
342 fn test_parse_default_debug_args() {
343 let default_args = DebugArgs::default();
344 let args = CommandParser::<DebugArgs>::parse_from(["reth"]).args;
345 assert_eq!(args, default_args);
346 }
347
348 #[test]
349 fn test_parse_invalid_block_args() {
350 let expected_args = DebugArgs {
351 invalid_block_hook: Some(InvalidBlockSelection::from([InvalidBlockHookType::Witness])),
352 ..Default::default()
353 };
354 let args = CommandParser::<DebugArgs>::parse_from([
355 "reth",
356 "--debug.invalid-block-hook",
357 "witness",
358 ])
359 .args;
360 assert_eq!(args, expected_args);
361
362 let expected_args = DebugArgs {
363 invalid_block_hook: Some(InvalidBlockSelection::from([
364 InvalidBlockHookType::Witness,
365 InvalidBlockHookType::PreState,
366 ])),
367 ..Default::default()
368 };
369 let args = CommandParser::<DebugArgs>::parse_from([
370 "reth",
371 "--debug.invalid-block-hook",
372 "witness,prestate",
373 ])
374 .args;
375 assert_eq!(args, expected_args);
376
377 let args = CommandParser::<DebugArgs>::parse_from([
378 "reth",
379 "--debug.invalid-block-hook",
380 "witness,prestate,prestate",
381 ])
382 .args;
383 assert_eq!(args, expected_args);
384
385 let args = CommandParser::<DebugArgs>::parse_from([
386 "reth",
387 "--debug.invalid-block-hook",
388 "witness,witness,prestate",
389 ])
390 .args;
391 assert_eq!(args, expected_args);
392
393 let args = CommandParser::<DebugArgs>::parse_from([
394 "reth",
395 "--debug.invalid-block-hook",
396 "prestate,witness,prestate",
397 ])
398 .args;
399 assert_eq!(args, expected_args);
400 }
401}