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