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