Skip to main content

reth_node_core/args/
benchmark_args.rs

1//! clap [Args](clap::Args) for benchmark configuration
2
3use clap::Args;
4use std::{path::PathBuf, str::FromStr};
5
6/// Parameters for benchmark configuration
7#[derive(Debug, Args, PartialEq, Eq, Default, Clone)]
8#[command(next_help_heading = "Benchmark")]
9pub struct BenchmarkArgs {
10    /// Run the benchmark from a specific block.
11    #[arg(long, verbatim_doc_comment)]
12    pub from: Option<u64>,
13
14    /// Run the benchmark to a specific block.
15    #[arg(long, verbatim_doc_comment)]
16    pub to: Option<u64>,
17
18    /// Number of blocks to advance from the current head block.
19    /// When specified, automatically sets --from to current head + 1 and --to to current head +
20    /// advance. Cannot be used together with explicit --from and --to arguments.
21    #[arg(long, conflicts_with_all = &["from", "to"], verbatim_doc_comment)]
22    pub advance: Option<u64>,
23
24    /// Path to a JWT secret to use for the authenticated engine-API RPC server.
25    ///
26    /// This will perform JWT authentication for all requests to the given engine RPC url.
27    ///
28    /// If no path is provided, a secret will be generated and stored in the datadir under
29    /// `<DIR>/<CHAIN_ID>/jwt.hex`. For mainnet this would be `~/.local/share/reth/mainnet/jwt.hex`
30    /// by default.
31    #[arg(
32        long = "jwt-secret",
33        alias = "jwtsecret",
34        value_name = "PATH",
35        global = true,
36        required = false
37    )]
38    pub auth_jwtsecret: Option<PathBuf>,
39
40    /// The RPC url to use for sending engine requests.
41    #[arg(
42        long,
43        value_name = "ENGINE_RPC_URL",
44        verbatim_doc_comment,
45        default_value = "http://localhost:8551"
46    )]
47    pub engine_rpc_url: String,
48
49    /// The `WebSocket` RPC URL to use for persistence subscriptions.
50    ///
51    /// If not provided, will attempt to derive from engine-rpc-url by:
52    /// - Converting http/https to ws/wss
53    /// - Using port 8546 (standard RPC `WebSocket` port)
54    ///
55    /// Example: `ws://localhost:8546`
56    #[arg(long, value_name = "WS_RPC_URL", verbatim_doc_comment)]
57    pub ws_rpc_url: Option<String>,
58
59    /// The path to the output directory for granular benchmark results.
60    #[arg(long, short, value_name = "BENCHMARK_OUTPUT", verbatim_doc_comment)]
61    pub output: Option<PathBuf>,
62
63    /// Optional Prometheus metrics endpoint to scrape after each block.
64    ///
65    /// When provided, reth-bench will fetch metrics from this URL after each
66    /// `newPayload` / `forkchoiceUpdated` call, recording per-block execution
67    /// and state root durations. Results are written to `metrics.csv` in the
68    /// output directory.
69    ///
70    /// Example: `http://127.0.0.1:9001/metrics`
71    #[arg(long = "metrics-url", value_name = "URL", verbatim_doc_comment)]
72    pub metrics_url: Option<String>,
73
74    /// Number of retries for fetching blocks from `--rpc-url` after a failure.
75    ///
76    /// Use `0` to fail immediately, or `forever` to never stop retrying.
77    #[arg(
78        long = "rpc-block-fetch-retries",
79        value_name = "RETRIES",
80        default_value = "10",
81        value_parser = parse_rpc_block_fetch_retries,
82        verbatim_doc_comment
83    )]
84    pub rpc_block_fetch_retries: RpcBlockFetchRetries,
85
86    /// Use `reth_newPayload` endpoint instead of `engine_newPayload*`.
87    ///
88    /// The `reth_newPayload` endpoint is a reth-specific extension that takes `ExecutionData`
89    /// directly, waits for persistence and cache updates to complete before processing,
90    /// and returns server-side timing breakdowns (latency, persistence wait, cache wait).
91    ///
92    /// Cannot be used with `--wait-for-persistence` because `reth_newPayload` already
93    /// waits for persistence by default.
94    #[arg(long, default_value = "false", verbatim_doc_comment)]
95    pub reth_new_payload: bool,
96
97    /// Control when `reth_newPayload` waits for in-flight persistence.
98    ///
99    /// Accepts `always` (wait on every block), `never` (default), or a number N
100    /// to wait every N blocks and skip the rest.
101    ///
102    /// Requires `--reth-new-payload`.
103    #[arg(
104        long = "wait-for-persistence",
105        value_name = "MODE",
106        num_args = 0..=1,
107        default_missing_value = "always",
108        value_parser = clap::value_parser!(WaitForPersistence),
109        requires = "reth_new_payload",
110        verbatim_doc_comment
111    )]
112    pub wait_for_persistence: Option<WaitForPersistence>,
113
114    /// Skip waiting for execution cache and sparse trie locks before processing.
115    ///
116    /// Only works with `--reth-new-payload`. When set, passes `wait_for_caches: false`
117    /// to the `reth_newPayload` endpoint.
118    #[arg(long, default_value = "false", verbatim_doc_comment, requires = "reth_new_payload")]
119    pub no_wait_for_caches: bool,
120
121    /// Fetch and replay RLP-encoded blocks. Implies `reth_new_payload`.
122    #[arg(long, default_value = "false", verbatim_doc_comment)]
123    pub rlp_blocks: bool,
124}
125
126/// Retry strategy for fetching blocks from the benchmark RPC provider.
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128pub enum RpcBlockFetchRetries {
129    /// Retry up to `u32` times after the first failed attempt.
130    Finite(u32),
131    /// Retry forever.
132    Forever,
133}
134
135impl RpcBlockFetchRetries {
136    /// Returns the maximum number of retries for the `RetryBackoffLayer`.
137    pub const fn as_max_retries(self) -> u32 {
138        match self {
139            Self::Finite(n) => n,
140            Self::Forever => u32::MAX,
141        }
142    }
143}
144
145impl Default for RpcBlockFetchRetries {
146    fn default() -> Self {
147        Self::Finite(10)
148    }
149}
150
151impl FromStr for RpcBlockFetchRetries {
152    type Err = String;
153
154    fn from_str(s: &str) -> Result<Self, Self::Err> {
155        let s = s.trim();
156        if s.eq_ignore_ascii_case("forever") ||
157            s.eq_ignore_ascii_case("infinite") ||
158            s.eq_ignore_ascii_case("inf")
159        {
160            return Ok(Self::Forever)
161        }
162
163        let retries = s
164            .parse::<u32>()
165            .map_err(|_| format!("invalid retry value {s:?}, expected a number or 'forever'"))?;
166        Ok(Self::Finite(retries))
167    }
168}
169
170fn parse_rpc_block_fetch_retries(value: &str) -> Result<RpcBlockFetchRetries, String> {
171    value.parse()
172}
173
174/// Controls when `reth_newPayload` waits for in-flight persistence to complete.
175#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
176pub enum WaitForPersistence {
177    /// Wait for persistence on every block (default `reth_newPayload` behavior).
178    #[default]
179    Always,
180    /// Never wait for persistence.
181    Never,
182    /// Wait for persistence every N blocks, skip for the rest.
183    EveryN(u64),
184}
185
186impl WaitForPersistence {
187    /// Returns the `wait_for_persistence` RPC parameter value for a given block number.
188    ///
189    /// - `None` → use the server default (true)
190    /// - `Some(false)` → skip waiting
191    /// - `Some(true)` → explicitly wait
192    pub const fn rpc_value(self, block_number: u64) -> Option<bool> {
193        match self {
194            Self::Always => None,
195            Self::Never => Some(false),
196            Self::EveryN(n) => {
197                if block_number.is_multiple_of(n) {
198                    Some(true)
199                } else {
200                    Some(false)
201                }
202            }
203        }
204    }
205}
206
207impl FromStr for WaitForPersistence {
208    type Err = String;
209
210    fn from_str(s: &str) -> Result<Self, Self::Err> {
211        let s = s.trim();
212        if s.eq_ignore_ascii_case("always") {
213            return Ok(Self::Always)
214        }
215        if s.eq_ignore_ascii_case("never") {
216            return Ok(Self::Never)
217        }
218        let n = s.parse::<u64>().map_err(|_| {
219            format!("invalid value {s:?}, expected 'always', 'never', or a block interval number")
220        })?;
221        if n == 0 {
222            return Err("block interval must be > 0, use 'never' to disable".to_string())
223        }
224        Ok(Self::EveryN(n))
225    }
226}
227
228#[cfg(test)]
229mod tests {
230    use super::*;
231    use clap::Parser;
232
233    /// A helper type to parse Args more easily
234    #[derive(Parser)]
235    struct CommandParser<T: Args> {
236        #[command(flatten)]
237        args: T,
238    }
239
240    #[test]
241    fn test_parse_benchmark_args() {
242        let default_args = BenchmarkArgs {
243            engine_rpc_url: "http://localhost:8551".to_string(),
244            ..Default::default()
245        };
246        let args = CommandParser::<BenchmarkArgs>::parse_from(["reth-bench"]).args;
247        assert_eq!(args, default_args);
248    }
249
250    #[test]
251    fn test_parse_rpc_block_fetch_retries_forever() {
252        let args = CommandParser::<BenchmarkArgs>::parse_from([
253            "reth-bench",
254            "--rpc-block-fetch-retries",
255            "forever",
256        ])
257        .args;
258        assert_eq!(args.rpc_block_fetch_retries, RpcBlockFetchRetries::Forever);
259    }
260
261    #[test]
262    fn test_parse_rpc_block_fetch_retries_number() {
263        let args = CommandParser::<BenchmarkArgs>::parse_from([
264            "reth-bench",
265            "--rpc-block-fetch-retries",
266            "7",
267        ])
268        .args;
269        assert_eq!(args.rpc_block_fetch_retries, RpcBlockFetchRetries::Finite(7));
270    }
271
272    #[test]
273    fn test_parse_wait_for_persistence() {
274        let args = CommandParser::<BenchmarkArgs>::parse_from([
275            "reth-bench",
276            "--reth-new-payload",
277            "--wait-for-persistence",
278            "always",
279        ])
280        .args;
281        assert_eq!(args.wait_for_persistence, Some(WaitForPersistence::Always));
282
283        let args = CommandParser::<BenchmarkArgs>::parse_from([
284            "reth-bench",
285            "--reth-new-payload",
286            "--wait-for-persistence",
287            "never",
288        ])
289        .args;
290        assert_eq!(args.wait_for_persistence, Some(WaitForPersistence::Never));
291
292        let args = CommandParser::<BenchmarkArgs>::parse_from([
293            "reth-bench",
294            "--reth-new-payload",
295            "--wait-for-persistence",
296            "10",
297        ])
298        .args;
299        assert_eq!(args.wait_for_persistence, Some(WaitForPersistence::EveryN(10)));
300
301        // bare --wait-for-persistence (no value) defaults to Always
302        let args = CommandParser::<BenchmarkArgs>::parse_from([
303            "reth-bench",
304            "--reth-new-payload",
305            "--wait-for-persistence",
306        ])
307        .args;
308        assert_eq!(args.wait_for_persistence, Some(WaitForPersistence::Always));
309
310        // default is None
311        let args = CommandParser::<BenchmarkArgs>::parse_from(["reth-bench"]).args;
312        assert_eq!(args.wait_for_persistence, None);
313    }
314}