Skip to main content

reth_node_core/args/
rpc_server.rs

1//! clap [`Args`] for RPC related arguments.
2
3use crate::args::{
4    types::{MaxU32, ZeroAsNoneU64},
5    GasPriceOracleArgs, RpcStateCacheArgs,
6};
7use alloy_primitives::{map::AddressSet, utils::format_ether};
8use alloy_rpc_types_engine::JwtSecret;
9use clap::{
10    builder::{PossibleValue, RangedU64ValueParser, Resettable, TypedValueParser},
11    parser::ValueSource,
12    Arg, Args, Command,
13};
14use rand::Rng;
15use reth_cli_util::{
16    parse_duration_from_secs_or_ms, parse_ether_value, parsers::format_duration_as_secs_or_ms,
17};
18use reth_rpc_eth_types::builder::config::PendingBlockKind;
19use reth_rpc_server_types::{constants, RethRpcModule, RpcModuleSelection};
20use std::{
21    ffi::OsStr,
22    net::{IpAddr, Ipv4Addr},
23    path::PathBuf,
24    sync::OnceLock,
25    time::Duration,
26};
27use url::Url;
28
29use super::types::MaxOr;
30
31/// Global static RPC server defaults
32static RPC_SERVER_DEFAULTS: OnceLock<DefaultRpcServerArgs> = OnceLock::new();
33
34/// Default max number of subscriptions per connection.
35pub(crate) const RPC_DEFAULT_MAX_SUBS_PER_CONN: u32 = 1024;
36
37/// Default max request size in MB.
38pub(crate) const RPC_DEFAULT_MAX_REQUEST_SIZE_MB: u32 = 15;
39
40/// Default max response size in MB.
41///
42/// This is only relevant for very large trace responses.
43pub(crate) const RPC_DEFAULT_MAX_RESPONSE_SIZE_MB: u32 = 160;
44
45/// Default number of incoming connections.
46///
47/// This restricts how many active connections (http, ws) the server accepts.
48/// Once exceeded, the server can reject new connections.
49pub(crate) const RPC_DEFAULT_MAX_CONNECTIONS: u32 = 500;
50
51/// Default values for RPC server that can be customized
52///
53/// Global defaults can be set via [`DefaultRpcServerArgs::try_init`].
54#[derive(Debug, Clone)]
55pub struct DefaultRpcServerArgs {
56    http: bool,
57    http_addr: IpAddr,
58    http_port: u16,
59    http_disable_compression: bool,
60    http_compression_algorithms: Option<Vec<String>>,
61    http_decompression_algorithms: Option<Vec<String>>,
62    http_api: Option<RpcModuleSelection>,
63    http_corsdomain: Option<String>,
64    ws: bool,
65    ws_addr: IpAddr,
66    ws_port: u16,
67    ws_allowed_origins: Option<String>,
68    ws_api: Option<RpcModuleSelection>,
69    ipcdisable: bool,
70    ipcpath: String,
71    ipc_socket_permissions: Option<String>,
72    auth_addr: IpAddr,
73    auth_port: u16,
74    auth_jwtsecret: Option<PathBuf>,
75    auth_ipc: bool,
76    auth_ipc_path: String,
77    disable_auth_server: bool,
78    rpc_jwtsecret: Option<JwtSecret>,
79    rpc_disable_metrics: bool,
80    rpc_max_request_size: MaxU32,
81    rpc_max_response_size: MaxU32,
82    rpc_max_subscriptions_per_connection: MaxU32,
83    rpc_max_connections: MaxU32,
84    rpc_max_tracing_requests: usize,
85    rpc_max_blocking_io_requests: usize,
86    rpc_max_trace_filter_blocks: u64,
87    rpc_max_blocks_per_filter: ZeroAsNoneU64,
88    rpc_max_logs_per_response: ZeroAsNoneU64,
89    rpc_gas_cap: u64,
90    rpc_evm_memory_limit: u64,
91    rpc_tx_fee_cap: u128,
92    rpc_max_simulate_blocks: u64,
93    rpc_compute_state_root_for_eth_simulate: bool,
94    rpc_eth_proof_window: u64,
95    rpc_proof_permits: usize,
96    rpc_pending_block: PendingBlockKind,
97    rpc_forwarder: Option<Url>,
98    builder_disallow: Option<AddressSet>,
99    pub(super) rpc_state_cache: RpcStateCacheArgs,
100    pub(super) gas_price_oracle: GasPriceOracleArgs,
101    rpc_send_raw_transaction_sync_timeout: Duration,
102}
103
104impl DefaultRpcServerArgs {
105    /// Initialize the global RPC server defaults with this configuration
106    pub fn try_init(self) -> Result<(), Self> {
107        RPC_SERVER_DEFAULTS.set(self)
108    }
109
110    /// Get a reference to the global RPC server defaults
111    pub fn get_global() -> &'static Self {
112        RPC_SERVER_DEFAULTS.get_or_init(Self::default)
113    }
114
115    /// Set the default HTTP enabled state
116    pub const fn with_http(mut self, v: bool) -> Self {
117        self.http = v;
118        self
119    }
120
121    /// Set the default HTTP address
122    pub const fn with_http_addr(mut self, v: IpAddr) -> Self {
123        self.http_addr = v;
124        self
125    }
126
127    /// Set the default HTTP port
128    pub const fn with_http_port(mut self, v: u16) -> Self {
129        self.http_port = v;
130        self
131    }
132
133    /// Set whether to disable HTTP compression by default
134    pub const fn with_http_disable_compression(mut self, v: bool) -> Self {
135        self.http_disable_compression = v;
136        self
137    }
138
139    /// Set the default allowed HTTP response compression algorithms
140    pub fn with_http_compression_algorithms(mut self, v: Option<Vec<String>>) -> Self {
141        self.http_compression_algorithms = v;
142        self
143    }
144
145    /// Set the default allowed HTTP request decompression algorithms
146    pub fn with_http_decompression_algorithms(mut self, v: Option<Vec<String>>) -> Self {
147        self.http_decompression_algorithms = v;
148        self
149    }
150
151    /// Set the default HTTP API modules
152    pub fn with_http_api(mut self, v: Option<RpcModuleSelection>) -> Self {
153        self.http_api = v;
154        self
155    }
156
157    /// Set the default HTTP CORS domain
158    pub fn with_http_corsdomain(mut self, v: Option<String>) -> Self {
159        self.http_corsdomain = v;
160        self
161    }
162
163    /// Set the default WS enabled state
164    pub const fn with_ws(mut self, v: bool) -> Self {
165        self.ws = v;
166        self
167    }
168
169    /// Set the default WS address
170    pub const fn with_ws_addr(mut self, v: IpAddr) -> Self {
171        self.ws_addr = v;
172        self
173    }
174
175    /// Set the default WS port
176    pub const fn with_ws_port(mut self, v: u16) -> Self {
177        self.ws_port = v;
178        self
179    }
180
181    /// Set the default WS allowed origins
182    pub fn with_ws_allowed_origins(mut self, v: Option<String>) -> Self {
183        self.ws_allowed_origins = v;
184        self
185    }
186
187    /// Set the default WS API modules
188    pub fn with_ws_api(mut self, v: Option<RpcModuleSelection>) -> Self {
189        self.ws_api = v;
190        self
191    }
192
193    /// Set whether to disable IPC by default
194    pub const fn with_ipcdisable(mut self, v: bool) -> Self {
195        self.ipcdisable = v;
196        self
197    }
198
199    /// Set the default IPC path
200    pub fn with_ipcpath(mut self, v: String) -> Self {
201        self.ipcpath = v;
202        self
203    }
204
205    /// Set the default IPC socket permissions
206    pub fn with_ipc_socket_permissions(mut self, v: Option<String>) -> Self {
207        self.ipc_socket_permissions = v;
208        self
209    }
210
211    /// Set the default auth server address
212    pub const fn with_auth_addr(mut self, v: IpAddr) -> Self {
213        self.auth_addr = v;
214        self
215    }
216
217    /// Set the default auth server port
218    pub const fn with_auth_port(mut self, v: u16) -> Self {
219        self.auth_port = v;
220        self
221    }
222
223    /// Set the default auth JWT secret path
224    pub fn with_auth_jwtsecret(mut self, v: Option<PathBuf>) -> Self {
225        self.auth_jwtsecret = v;
226        self
227    }
228
229    /// Set the default auth IPC enabled state
230    pub const fn with_auth_ipc(mut self, v: bool) -> Self {
231        self.auth_ipc = v;
232        self
233    }
234
235    /// Set the default auth IPC path
236    pub fn with_auth_ipc_path(mut self, v: String) -> Self {
237        self.auth_ipc_path = v;
238        self
239    }
240
241    /// Set whether to disable the auth server by default
242    pub const fn with_disable_auth_server(mut self, v: bool) -> Self {
243        self.disable_auth_server = v;
244        self
245    }
246
247    /// Set the default RPC JWT secret
248    pub const fn with_rpc_jwtsecret(mut self, v: Option<JwtSecret>) -> Self {
249        self.rpc_jwtsecret = v;
250        self
251    }
252
253    /// Set whether to disable RPC request metrics by default
254    pub const fn with_rpc_disable_metrics(mut self, v: bool) -> Self {
255        self.rpc_disable_metrics = v;
256        self
257    }
258
259    /// Set the default max request size
260    pub const fn with_rpc_max_request_size(mut self, v: MaxU32) -> Self {
261        self.rpc_max_request_size = v;
262        self
263    }
264
265    /// Set the default max response size
266    pub const fn with_rpc_max_response_size(mut self, v: MaxU32) -> Self {
267        self.rpc_max_response_size = v;
268        self
269    }
270
271    /// Set the default max subscriptions per connection
272    pub const fn with_rpc_max_subscriptions_per_connection(mut self, v: MaxU32) -> Self {
273        self.rpc_max_subscriptions_per_connection = v;
274        self
275    }
276
277    /// Set the default max connections
278    pub const fn with_rpc_max_connections(mut self, v: MaxU32) -> Self {
279        self.rpc_max_connections = v;
280        self
281    }
282
283    /// Set the default max tracing requests
284    pub const fn with_rpc_max_tracing_requests(mut self, v: usize) -> Self {
285        self.rpc_max_tracing_requests = v;
286        self
287    }
288
289    /// Set the default max blocking IO requests
290    pub const fn with_rpc_max_blocking_io_requests(mut self, v: usize) -> Self {
291        self.rpc_max_blocking_io_requests = v;
292        self
293    }
294
295    /// Set the default max trace filter blocks
296    pub const fn with_rpc_max_trace_filter_blocks(mut self, v: u64) -> Self {
297        self.rpc_max_trace_filter_blocks = v;
298        self
299    }
300
301    /// Set the default max blocks per filter
302    pub const fn with_rpc_max_blocks_per_filter(mut self, v: ZeroAsNoneU64) -> Self {
303        self.rpc_max_blocks_per_filter = v;
304        self
305    }
306
307    /// Set the default max logs per response
308    pub const fn with_rpc_max_logs_per_response(mut self, v: ZeroAsNoneU64) -> Self {
309        self.rpc_max_logs_per_response = v;
310        self
311    }
312
313    /// Set the default gas cap
314    pub const fn with_rpc_gas_cap(mut self, v: u64) -> Self {
315        self.rpc_gas_cap = v;
316        self
317    }
318
319    /// Set the default EVM memory limit
320    pub const fn with_rpc_evm_memory_limit(mut self, v: u64) -> Self {
321        self.rpc_evm_memory_limit = v;
322        self
323    }
324
325    /// Set the default tx fee cap
326    pub const fn with_rpc_tx_fee_cap(mut self, v: u128) -> Self {
327        self.rpc_tx_fee_cap = v;
328        self
329    }
330
331    /// Set the default max simulate blocks
332    pub const fn with_rpc_max_simulate_blocks(mut self, v: u64) -> Self {
333        self.rpc_max_simulate_blocks = v;
334        self
335    }
336
337    /// Set whether to compute state roots for `eth_simulateV1` responses by default.
338    pub const fn with_rpc_compute_state_root_for_eth_simulate(mut self, v: bool) -> Self {
339        self.rpc_compute_state_root_for_eth_simulate = v;
340        self
341    }
342
343    /// Set the default eth proof window
344    pub const fn with_rpc_eth_proof_window(mut self, v: u64) -> Self {
345        self.rpc_eth_proof_window = v;
346        self
347    }
348
349    /// Set the default proof permits
350    pub const fn with_rpc_proof_permits(mut self, v: usize) -> Self {
351        self.rpc_proof_permits = v;
352        self
353    }
354
355    /// Set the default pending block kind
356    pub const fn with_rpc_pending_block(mut self, v: PendingBlockKind) -> Self {
357        self.rpc_pending_block = v;
358        self
359    }
360
361    /// Set the default RPC forwarder
362    pub fn with_rpc_forwarder(mut self, v: Option<Url>) -> Self {
363        self.rpc_forwarder = v;
364        self
365    }
366
367    /// Set the default builder disallow addresses
368    pub fn with_builder_disallow(mut self, v: Option<AddressSet>) -> Self {
369        self.builder_disallow = v;
370        self
371    }
372
373    /// Set the default RPC state cache args
374    pub const fn with_rpc_state_cache(mut self, v: RpcStateCacheArgs) -> Self {
375        self.rpc_state_cache = v;
376        self
377    }
378
379    /// Set the default gas price oracle args
380    pub const fn with_gas_price_oracle(mut self, v: GasPriceOracleArgs) -> Self {
381        self.gas_price_oracle = v;
382        self
383    }
384
385    /// Set the default send raw transaction sync timeout
386    pub const fn with_rpc_send_raw_transaction_sync_timeout(mut self, v: Duration) -> Self {
387        self.rpc_send_raw_transaction_sync_timeout = v;
388        self
389    }
390}
391
392impl Default for DefaultRpcServerArgs {
393    fn default() -> Self {
394        Self {
395            http: false,
396            http_addr: Ipv4Addr::LOCALHOST.into(),
397            http_port: constants::DEFAULT_HTTP_RPC_PORT,
398            http_disable_compression: false,
399            http_compression_algorithms: None,
400            http_decompression_algorithms: None,
401            http_api: None,
402            http_corsdomain: None,
403            ws: false,
404            ws_addr: Ipv4Addr::LOCALHOST.into(),
405            ws_port: constants::DEFAULT_WS_RPC_PORT,
406            ws_allowed_origins: None,
407            ws_api: None,
408            ipcdisable: false,
409            ipcpath: constants::DEFAULT_IPC_ENDPOINT.to_string(),
410            ipc_socket_permissions: None,
411            auth_addr: Ipv4Addr::LOCALHOST.into(),
412            auth_port: constants::DEFAULT_AUTH_PORT,
413            auth_jwtsecret: None,
414            auth_ipc: false,
415            auth_ipc_path: constants::DEFAULT_ENGINE_API_IPC_ENDPOINT.to_string(),
416            disable_auth_server: false,
417            rpc_jwtsecret: None,
418            rpc_disable_metrics: false,
419            rpc_max_request_size: RPC_DEFAULT_MAX_REQUEST_SIZE_MB.into(),
420            rpc_max_response_size: RPC_DEFAULT_MAX_RESPONSE_SIZE_MB.into(),
421            rpc_max_subscriptions_per_connection: RPC_DEFAULT_MAX_SUBS_PER_CONN.into(),
422            rpc_max_connections: RPC_DEFAULT_MAX_CONNECTIONS.into(),
423            rpc_max_tracing_requests: constants::default_max_tracing_requests(),
424            rpc_max_blocking_io_requests: constants::DEFAULT_MAX_BLOCKING_IO_REQUEST,
425            rpc_max_trace_filter_blocks: constants::DEFAULT_MAX_TRACE_FILTER_BLOCKS,
426            rpc_max_blocks_per_filter: constants::DEFAULT_MAX_BLOCKS_PER_FILTER.into(),
427            rpc_max_logs_per_response: (constants::DEFAULT_MAX_LOGS_PER_RESPONSE as u64).into(),
428            rpc_gas_cap: constants::gas_oracle::RPC_DEFAULT_GAS_CAP,
429            rpc_evm_memory_limit: (1 << 32) - 1,
430            rpc_tx_fee_cap: constants::DEFAULT_TX_FEE_CAP_WEI,
431            rpc_max_simulate_blocks: constants::DEFAULT_MAX_SIMULATE_BLOCKS,
432            rpc_compute_state_root_for_eth_simulate: false,
433            rpc_eth_proof_window: constants::DEFAULT_ETH_PROOF_WINDOW,
434            rpc_proof_permits: constants::DEFAULT_PROOF_PERMITS,
435            rpc_pending_block: PendingBlockKind::Full,
436            rpc_forwarder: None,
437            builder_disallow: None,
438            rpc_state_cache: RpcStateCacheArgs::default(),
439            gas_price_oracle: GasPriceOracleArgs::default(),
440            rpc_send_raw_transaction_sync_timeout:
441                constants::RPC_DEFAULT_SEND_RAW_TX_SYNC_TIMEOUT_SECS,
442        }
443    }
444}
445
446/// Parameters for configuring the rpc more granularity via CLI
447#[derive(Debug, Clone, Args, PartialEq, Eq)]
448#[command(next_help_heading = "RPC")]
449pub struct RpcServerArgs {
450    /// Enable the HTTP-RPC server
451    #[arg(long, default_value_if("dev", "true", "true"), default_value_t = DefaultRpcServerArgs::get_global().http)]
452    pub http: bool,
453
454    /// Http server address to listen on
455    #[arg(long = "http.addr", default_value_t = DefaultRpcServerArgs::get_global().http_addr)]
456    pub http_addr: IpAddr,
457
458    /// Http server port to listen on
459    #[arg(long = "http.port", default_value_t = DefaultRpcServerArgs::get_global().http_port)]
460    pub http_port: u16,
461
462    /// Disable compression for HTTP responses.
463    ///
464    /// Servers listening on a loopback address never compress responses.
465    #[arg(long = "http.disable-compression", default_value_t = DefaultRpcServerArgs::get_global().http_disable_compression)]
466    pub http_disable_compression: bool,
467
468    /// Comma-separated list of allowed compression algorithms for HTTP responses.
469    ///
470    /// If not specified, all supported algorithms are enabled.
471    ///
472    /// Client `Accept-Encoding` quality values select among allowed algorithms; ties prefer
473    /// zstd > br > gzip > deflate. Omitted quality values default to 1; without an acceptable
474    /// allowed algorithm, the response is uncompressed. List order is ignored.
475    #[arg(
476        long = "http.compression",
477        value_name = "ALGOS",
478        value_delimiter = ',',
479        value_parser = ["zstd", "gzip", "deflate", "br"],
480        default_value = Resettable::from(DefaultRpcServerArgs::get_global().http_compression_algorithms.as_ref().map(|v| v.join(",").into()))
481    )]
482    pub http_compression_algorithms: Option<Vec<String>>,
483
484    /// Comma-separated list of allowed decompression algorithms for HTTP requests.
485    ///
486    /// Request decompression is disabled when not specified.
487    #[arg(
488        long = "http.decompression",
489        value_name = "ALGOS",
490        value_delimiter = ',',
491        value_parser = ["zstd", "gzip", "deflate", "br"],
492        default_value = Resettable::from(DefaultRpcServerArgs::get_global().http_decompression_algorithms.as_ref().map(|v| v.join(",").into()))
493    )]
494    pub http_decompression_algorithms: Option<Vec<String>>,
495
496    /// Rpc Modules to be configured for the HTTP server
497    #[arg(long = "http.api", value_parser = RpcModuleSelectionValueParser::default(), default_value = Resettable::from(DefaultRpcServerArgs::get_global().http_api.as_ref().map(|v| v.to_string().into())))]
498    pub http_api: Option<RpcModuleSelection>,
499
500    /// Http Corsdomain to allow request from
501    #[arg(long = "http.corsdomain", default_value = Resettable::from(DefaultRpcServerArgs::get_global().http_corsdomain.as_ref().map(|v| v.to_string().into())))]
502    pub http_corsdomain: Option<String>,
503
504    /// Enable the WS-RPC server
505    #[arg(long, default_value_t = DefaultRpcServerArgs::get_global().ws)]
506    pub ws: bool,
507
508    /// Ws server address to listen on
509    #[arg(long = "ws.addr", default_value_t = DefaultRpcServerArgs::get_global().ws_addr)]
510    pub ws_addr: IpAddr,
511
512    /// Ws server port to listen on
513    #[arg(long = "ws.port", default_value_t = DefaultRpcServerArgs::get_global().ws_port)]
514    pub ws_port: u16,
515
516    /// Origins from which to accept `WebSocket` requests
517    #[arg(id = "ws.origins", long = "ws.origins", alias = "ws.corsdomain", default_value = Resettable::from(DefaultRpcServerArgs::get_global().ws_allowed_origins.as_ref().map(|v| v.to_string().into())))]
518    pub ws_allowed_origins: Option<String>,
519
520    /// Rpc Modules to be configured for the WS server
521    #[arg(long = "ws.api", value_parser = RpcModuleSelectionValueParser::default(), default_value = Resettable::from(DefaultRpcServerArgs::get_global().ws_api.as_ref().map(|v| v.to_string().into())))]
522    pub ws_api: Option<RpcModuleSelection>,
523
524    /// Disable the IPC-RPC server
525    #[arg(long, default_value_t = DefaultRpcServerArgs::get_global().ipcdisable)]
526    pub ipcdisable: bool,
527
528    /// Filename for IPC socket/pipe within the datadir
529    #[arg(long, default_value_t = DefaultRpcServerArgs::get_global().ipcpath.clone())]
530    pub ipcpath: String,
531
532    /// Set the permissions for the IPC socket file, in octal format.
533    ///
534    /// Accepts modes up to `0o7777`, with an optional `0o` prefix. If not specified, the
535    /// permissions will be set by the system's umask.
536    ///
537    /// Startup fails if the mode is invalid or the platform cannot apply it before binding the
538    /// socket, which includes macOS.
539    #[arg(long = "ipc.permissions", default_value = Resettable::from(DefaultRpcServerArgs::get_global().ipc_socket_permissions.as_ref().map(|v| v.to_string().into())))]
540    pub ipc_socket_permissions: Option<String>,
541
542    /// Auth server address to listen on
543    #[arg(long = "authrpc.addr", default_value_t = DefaultRpcServerArgs::get_global().auth_addr)]
544    pub auth_addr: IpAddr,
545
546    /// Auth server port to listen on
547    #[arg(long = "authrpc.port", default_value_t = DefaultRpcServerArgs::get_global().auth_port)]
548    pub auth_port: u16,
549
550    /// Path to a JWT secret to use for the authenticated engine-API RPC server.
551    ///
552    /// This will enforce JWT authentication for all requests coming from the consensus layer.
553    ///
554    /// If no path is provided, a secret will be generated and stored in the datadir under
555    /// `<DIR>/<CHAIN_ID>/jwt.hex`. For mainnet this would be `~/.local/share/reth/mainnet/jwt.hex`
556    /// by default.
557    #[arg(long = "authrpc.jwtsecret", value_name = "PATH", global = true, required = false, conflicts_with = "auth_jwtsecret_hex", default_value = Resettable::from(DefaultRpcServerArgs::get_global().auth_jwtsecret.as_ref().map(|v| v.to_string_lossy().into())))]
558    pub auth_jwtsecret: Option<PathBuf>,
559
560    /// Hex encoded JWT secret to use for the authenticated engine-API RPC server.
561    ///
562    /// This will enforce JWT authentication for all requests coming from the consensus layer.
563    /// Cannot be used together with `--authrpc.jwtsecret`.
564    #[arg(
565        long = "authrpc.jwtsecret-hex",
566        value_name = "HEX",
567        global = true,
568        required = false,
569        conflicts_with = "auth_jwtsecret"
570    )]
571    pub auth_jwtsecret_hex: Option<JwtSecret>,
572
573    /// Enable auth engine API over IPC
574    #[arg(long, default_value_t = DefaultRpcServerArgs::get_global().auth_ipc)]
575    pub auth_ipc: bool,
576
577    /// Path for the auth IPC socket/pipe. Explicit paths are used unchanged.
578    ///
579    /// If omitted, uses `/tmp/reth_engine_api.ipc`, with `-<instance>` appended when
580    /// `--instance` is set. Applications can override this default via `DefaultRpcServerArgs`.
581    #[arg(long = "auth-ipc.path")]
582    pub auth_ipc_path: Option<String>,
583
584    /// Disable the auth/engine API server.
585    ///
586    /// This will prevent the authenticated engine-API server from starting. Use this if you're
587    /// running a node that doesn't need to serve engine API requests.
588    #[arg(long = "disable-auth-server", alias = "disable-engine-api", default_value_t = DefaultRpcServerArgs::get_global().disable_auth_server)]
589    pub disable_auth_server: bool,
590
591    /// Hex encoded JWT secret to authenticate the regular RPC server(s), see `--http.api` and
592    /// `--ws.api`.
593    ///
594    /// This is __not__ used for the authenticated engine-API RPC server, see
595    /// `--authrpc.jwtsecret`.
596    #[arg(
597        long = "rpc.jwtsecret",
598        value_name = "HEX",
599        global = true,
600        required = false,
601        value_parser = DefaultValueParser {
602            inner: |value: &str| JwtSecret::from_hex(value),
603            default: &DefaultRpcServerArgs::get_global().rpc_jwtsecret,
604        },
605        default_value = Resettable::from(DefaultRpcServerArgs::get_global().rpc_jwtsecret.as_ref().map(|_| "configured".into())),
606        hide_default_value = true,
607    )]
608    pub rpc_jwtsecret: Option<JwtSecret>,
609
610    /// Disable built-in RPC request metrics.
611    #[arg(long = "rpc.disable-metrics", default_value_t = DefaultRpcServerArgs::get_global().rpc_disable_metrics)]
612    pub rpc_disable_metrics: bool,
613
614    /// Set the maximum RPC request payload size for both HTTP and WS in megabytes.
615    ///
616    /// For compressed HTTP requests, this limit applies to both the compressed and decompressed
617    /// payloads.
618    #[arg(long = "rpc.max-request-size", alias = "rpc-max-request-size", default_value_t = DefaultRpcServerArgs::get_global().rpc_max_request_size)]
619    pub rpc_max_request_size: MaxU32,
620
621    /// Set the maximum RPC response payload size for both HTTP and WS in megabytes.
622    #[arg(long = "rpc.max-response-size", alias = "rpc-max-response-size", visible_alias = "rpc.returndata.limit", default_value_t = DefaultRpcServerArgs::get_global().rpc_max_response_size)]
623    pub rpc_max_response_size: MaxU32,
624
625    /// Set the maximum concurrent subscriptions per connection.
626    #[arg(long = "rpc.max-subscriptions-per-connection", alias = "rpc-max-subscriptions-per-connection", default_value_t = DefaultRpcServerArgs::get_global().rpc_max_subscriptions_per_connection)]
627    pub rpc_max_subscriptions_per_connection: MaxU32,
628
629    /// Maximum number of RPC server connections.
630    #[arg(long = "rpc.max-connections", alias = "rpc-max-connections", value_name = "COUNT", default_value_t = DefaultRpcServerArgs::get_global().rpc_max_connections)]
631    pub rpc_max_connections: MaxU32,
632
633    /// Maximum number of concurrent tracing requests.
634    ///
635    /// By default this chooses a sensible value based on the number of available cores.
636    /// Tracing requests are generally CPU bound.
637    /// Choosing a value that is higher than the available CPU cores can have a negative impact on
638    /// the performance of the node and affect the node's ability to maintain sync.
639    #[arg(long = "rpc.max-tracing-requests", alias = "rpc-max-tracing-requests", value_name = "COUNT", value_parser = RangedU64ValueParser::<usize>::new().range(1..), default_value_t = DefaultRpcServerArgs::get_global().rpc_max_tracing_requests)]
640    pub rpc_max_tracing_requests: usize,
641
642    /// Maximum number of concurrent blocking IO requests.
643    ///
644    /// Blocking IO requests include `eth_call`, `eth_estimateGas`, and similar methods that
645    /// require EVM execution. These are spawned as blocking tasks to avoid blocking the async
646    /// runtime.
647    #[arg(long = "rpc.max-blocking-io-requests", alias = "rpc-max-blocking-io-requests", value_name = "COUNT", value_parser = RangedU64ValueParser::<usize>::new().range(1..), default_value_t = DefaultRpcServerArgs::get_global().rpc_max_blocking_io_requests)]
648    pub rpc_max_blocking_io_requests: usize,
649
650    /// Maximum number of blocks for `trace_filter` requests.
651    #[arg(long = "rpc.max-trace-filter-blocks", alias = "rpc-max-trace-filter-blocks", value_name = "COUNT", default_value_t = DefaultRpcServerArgs::get_global().rpc_max_trace_filter_blocks)]
652    pub rpc_max_trace_filter_blocks: u64,
653
654    /// Maximum number of blocks that could be scanned per filter request. (0 = entire chain)
655    #[arg(long = "rpc.max-blocks-per-filter", alias = "rpc-max-blocks-per-filter", value_name = "COUNT", default_value_t = DefaultRpcServerArgs::get_global().rpc_max_blocks_per_filter)]
656    pub rpc_max_blocks_per_filter: ZeroAsNoneU64,
657
658    /// Maximum number of logs that can be returned in a single response. (0 = no limit)
659    #[arg(long = "rpc.max-logs-per-response", alias = "rpc-max-logs-per-response", value_name = "COUNT", default_value_t = DefaultRpcServerArgs::get_global().rpc_max_logs_per_response)]
660    pub rpc_max_logs_per_response: ZeroAsNoneU64,
661
662    /// Maximum gas limit for `eth_call`, `eth_estimateGas`, and call tracing RPC methods.
663    ///
664    /// The calls of an `eth_callMany`, `trace_callMany` or `debug_traceCallMany` request share it.
665    #[arg(
666        long = "rpc.gascap",
667        alias = "rpc-gascap",
668        value_name = "GAS_CAP",
669        value_parser = MaxOr::new(RangedU64ValueParser::<u64>::new().range(1..)),
670        default_value_t = DefaultRpcServerArgs::get_global().rpc_gas_cap
671    )]
672    pub rpc_gas_cap: u64,
673
674    /// Maximum memory the EVM can allocate per RPC request.
675    #[arg(
676        long = "rpc.evm-memory-limit",
677        alias = "rpc-evm-memory-limit",
678        value_name = "MEMORY_LIMIT",
679        value_parser = MaxOr::new(RangedU64ValueParser::<u64>::new().range(1..)),
680        default_value_t = DefaultRpcServerArgs::get_global().rpc_evm_memory_limit
681    )]
682    pub rpc_evm_memory_limit: u64,
683
684    /// Maximum eth transaction fee (in ether) that can be sent via the RPC APIs (0 = no cap)
685    #[arg(
686        long = "rpc.txfeecap",
687        alias = "rpc-txfeecap",
688        value_name = "TX_FEE_CAP",
689        value_parser = parse_ether_value,
690        default_value = format_tx_fee_cap(DefaultRpcServerArgs::get_global().rpc_tx_fee_cap)
691    )]
692    pub rpc_tx_fee_cap: u128,
693
694    /// Maximum number of blocks for `eth_simulateV1` call.
695    #[arg(
696        long = "rpc.max-simulate-blocks",
697        value_name = "BLOCKS_COUNT",
698        default_value_t = DefaultRpcServerArgs::get_global().rpc_max_simulate_blocks
699    )]
700    pub rpc_max_simulate_blocks: u64,
701
702    /// Compute state roots for `eth_simulateV1` responses.
703    #[arg(
704        long = "rpc.compute-state-root-for-eth-simulate",
705        env = "RETH_RPC_COMPUTE_STATE_ROOT_FOR_ETH_SIMULATE",
706        default_value_t = DefaultRpcServerArgs::get_global().rpc_compute_state_root_for_eth_simulate
707    )]
708    pub rpc_compute_state_root_for_eth_simulate: bool,
709
710    /// The maximum proof window for historical proof generation.
711    /// This value allows for generating historical proofs up to
712    /// configured number of blocks from current tip (up to `tip - window`).
713    #[arg(
714        long = "rpc.eth-proof-window",
715        default_value_t = DefaultRpcServerArgs::get_global().rpc_eth_proof_window,
716        value_parser = RangedU64ValueParser::<u64>::new().range(..=constants::MAX_ETH_PROOF_WINDOW)
717    )]
718    pub rpc_eth_proof_window: u64,
719
720    /// Maximum number of concurrent getproof requests.
721    #[arg(long = "rpc.proof-permits", alias = "rpc-proof-permits", value_name = "COUNT", value_parser = RangedU64ValueParser::<usize>::new().range(1..), default_value_t = DefaultRpcServerArgs::get_global().rpc_proof_permits)]
722    pub rpc_proof_permits: usize,
723
724    /// Configures the pending block behavior for RPC responses.
725    ///
726    /// Options: full (include all transactions), empty (header only), none (disable pending
727    /// blocks).
728    #[arg(
729        long = "rpc.pending-block",
730        default_value = match DefaultRpcServerArgs::get_global().rpc_pending_block {
731            PendingBlockKind::Empty => "empty",
732            PendingBlockKind::None => "none",
733            PendingBlockKind::Full => "full",
734        },
735        value_name = "KIND",
736    )]
737    pub rpc_pending_block: PendingBlockKind,
738
739    /// Endpoint to forward transactions to.
740    #[arg(
741        long = "rpc.forwarder",
742        alias = "rpc-forwarder",
743        value_name = "FORWARDER",
744        value_parser = DefaultValueParser {
745            inner: Url::parse,
746            default: &DefaultRpcServerArgs::get_global().rpc_forwarder,
747        },
748        default_value = Resettable::from(DefaultRpcServerArgs::get_global().rpc_forwarder.as_ref().map(|_| "configured".into())),
749        hide_default_value = true,
750    )]
751    pub rpc_forwarder: Option<Url>,
752
753    /// Path to file containing disallowed addresses, json-encoded list of strings. Block
754    /// validation API will reject blocks containing transactions from these addresses.
755    #[arg(
756        long = "builder.disallow",
757        value_name = "PATH",
758        value_parser = DefaultValueParser {
759            inner: reth_cli_util::parsers::read_json_from_file::<AddressSet>,
760            default: &DefaultRpcServerArgs::get_global().builder_disallow,
761        },
762        default_value = Resettable::from(DefaultRpcServerArgs::get_global().builder_disallow.as_ref().map(|_| "configured".into())),
763        hide_default_value = true,
764    )]
765    pub builder_disallow: Option<AddressSet>,
766
767    /// State cache configuration.
768    #[command(flatten)]
769    pub rpc_state_cache: RpcStateCacheArgs,
770
771    /// Gas price oracle configuration.
772    #[command(flatten)]
773    pub gas_price_oracle: GasPriceOracleArgs,
774
775    /// Timeout for `send_raw_transaction_sync` RPC method.
776    #[arg(
777        long = "rpc.send-raw-transaction-sync-timeout",
778        value_name = "SECONDS",
779        default_value = format_duration_as_secs_or_ms(DefaultRpcServerArgs::get_global().rpc_send_raw_transaction_sync_timeout),
780        value_parser = parse_duration_from_secs_or_ms,
781    )]
782    pub rpc_send_raw_transaction_sync_timeout: Duration,
783
784    /// Skip invalid transactions in `testing_buildBlockV1` instead of failing.
785    ///
786    /// When enabled, transactions that fail execution will be skipped, and all subsequent
787    /// transactions from the same sender will also be skipped.
788    #[arg(long = "testing.skip-invalid-transactions", default_value_t = false)]
789    pub testing_skip_invalid_transactions: bool,
790
791    /// Override the gas limit used by `testing_buildBlockV1`.
792    ///
793    /// When set, `testing_buildBlockV1` will use this exact value instead of moving toward the
794    /// payload builder's configured gas limit. Accepts short notation: K for thousand, M for
795    /// million, G for billion (e.g., 1G = 1 billion).
796    #[arg(long = "testing.gas-limit", value_name = "GAS_LIMIT", hide = true)]
797    pub testing_gas_limit: Option<u64>,
798
799    /// Force upcasting EIP-4844 blob sidecars to EIP-7594 format when Osaka is active.
800    ///
801    /// When enabled, blob transactions submitted via `eth_sendRawTransaction` with EIP-4844
802    /// sidecars will be automatically converted to EIP-7594 format if the next block is Osaka.
803    /// By default this is disabled, meaning transactions are submitted as-is.
804    #[arg(long = "rpc.force-blob-sidecar-upcasting", default_value_t = false)]
805    pub rpc_force_blob_sidecar_upcasting: bool,
806}
807
808impl RpcServerArgs {
809    /// Enables the HTTP-RPC server.
810    pub const fn with_http(mut self) -> Self {
811        self.http = true;
812        self
813    }
814
815    /// Configures modules for the HTTP-RPC server.
816    pub fn with_http_api(mut self, http_api: RpcModuleSelection) -> Self {
817        self.http_api = Some(http_api);
818        self
819    }
820
821    /// Enables the WS-RPC server.
822    pub const fn with_ws(mut self) -> Self {
823        self.ws = true;
824        self
825    }
826
827    /// Configures modules for WS-RPC server.
828    pub fn with_ws_api(mut self, ws_api: RpcModuleSelection) -> Self {
829        self.ws_api = Some(ws_api);
830        self
831    }
832
833    /// Enables the Auth IPC
834    pub const fn with_auth_ipc(mut self) -> Self {
835        self.auth_ipc = true;
836        self
837    }
838
839    /// Configures modules for both the HTTP-RPC server and WS-RPC server.
840    ///
841    /// This is the same as calling both [`Self::with_http_api`] and [`Self::with_ws_api`].
842    pub fn with_api(self, api: RpcModuleSelection) -> Self {
843        self.with_http_api(api.clone()).with_ws_api(api)
844    }
845
846    /// Change rpc port numbers based on the instance number, if provided.
847    /// * The `auth_port` is scaled by a factor of `instance * 100`
848    /// * The `http_port` is scaled by a factor of `-instance`
849    /// * The `ws_port` is scaled by a factor of `instance * 2`
850    /// * The `ipcpath` is appended with the instance number: `/tmp/reth.ipc-<instance>`
851    /// * If `auth_ipc_path` is `None`, the default auth IPC path is appended with the instance
852    ///   number: `/tmp/reth_engine_api.ipc-<instance>`. Explicit paths are left unchanged.
853    ///
854    /// # Panics
855    /// Warning: if `instance` is zero in debug mode, this will panic.
856    ///
857    /// This will also panic in debug mode if either:
858    /// * `instance` is greater than `655` (scaling would overflow `u16`)
859    /// * `self.auth_port / 100 + (instance - 1)` would overflow `u16`
860    ///
861    /// In release mode, this will silently wrap around.
862    pub fn adjust_instance_ports(&mut self, instance: Option<u16>) {
863        if let Some(instance) = instance {
864            debug_assert_ne!(instance, 0, "instance must be non-zero");
865            // auth port is scaled by a factor of instance * 100
866            self.auth_port += instance * 100 - 100;
867            // http port is scaled by a factor of -instance
868            self.http_port -= instance - 1;
869            // ws port is scaled by a factor of instance * 2
870            self.ws_port += instance * 2 - 2;
871            // append instance file to ipc path
872            self.ipcpath = format!("{}-{}", self.ipcpath, instance);
873            if self.auth_ipc_path.is_none() {
874                self.auth_ipc_path = Some(format!("{}-{instance}", self.auth_ipc_path()));
875            }
876        }
877    }
878
879    /// Set the http port to zero, to allow the OS to assign a random unused port when the rpc
880    /// server binds to a socket.
881    pub const fn with_http_unused_port(mut self) -> Self {
882        self.http_port = 0;
883        self
884    }
885
886    /// Set the ws port to zero, to allow the OS to assign a random unused port when the rpc
887    /// server binds to a socket.
888    pub const fn with_ws_unused_port(mut self) -> Self {
889        self.ws_port = 0;
890        self
891    }
892
893    /// Set the auth port to zero, to allow the OS to assign a random unused port when the rpc
894    /// server binds to a socket.
895    pub const fn with_auth_unused_port(mut self) -> Self {
896        self.auth_port = 0;
897        self
898    }
899
900    /// Append a random string to the ipc path, to prevent possible collisions when multiple nodes
901    /// are being run on the same machine.
902    pub fn with_ipc_random_path(mut self) -> Self {
903        let random_string: String =
904            rand::rng().sample_iter(rand::distr::Alphanumeric).take(8).map(char::from).collect();
905        self.ipcpath = format!("{}-{}", self.ipcpath, random_string);
906        self
907    }
908
909    /// Configure all ports to be set to a random unused port when bound, and set the IPC path to a
910    /// random path.
911    pub fn with_unused_ports(mut self) -> Self {
912        self = self.with_http_unused_port();
913        self = self.with_ws_unused_port();
914        self = self.with_auth_unused_port();
915        self = self.with_ipc_random_path();
916        self
917    }
918
919    /// Apply a function to the args.
920    pub fn apply<F>(self, f: F) -> Self
921    where
922        F: FnOnce(Self) -> Self,
923    {
924        f(self)
925    }
926
927    /// Configures the timeout for send raw transaction sync.
928    pub const fn with_send_raw_transaction_sync_timeout(mut self, timeout: Duration) -> Self {
929        self.rpc_send_raw_transaction_sync_timeout = timeout;
930        self
931    }
932
933    /// Returns `true` if the given RPC namespace is enabled on any transport.
934    pub fn is_namespace_enabled(&self, ns: RethRpcModule) -> bool {
935        if self.http &&
936            self.http_api.as_ref().unwrap_or(&RpcModuleSelection::Standard).contains(&ns)
937        {
938            return true;
939        }
940        if self.ws && self.ws_api.as_ref().unwrap_or(&RpcModuleSelection::Standard).contains(&ns) {
941            return true;
942        }
943        !self.ipcdisable && RpcModuleSelection::default_ipc_modules().contains(&ns)
944    }
945
946    /// Enables forced blob sidecar upcasting from EIP-4844 to EIP-7594 format.
947    pub const fn with_force_blob_sidecar_upcasting(mut self) -> Self {
948        self.rpc_force_blob_sidecar_upcasting = true;
949        self
950    }
951
952    /// Returns the auth IPC endpoint to bind after instance adjustment.
953    ///
954    /// If no explicit or instance-adjusted path is set, returns the global default.
955    pub fn auth_ipc_path(&self) -> &str {
956        self.auth_ipc_path.as_deref().unwrap_or(&DefaultRpcServerArgs::get_global().auth_ipc_path)
957    }
958}
959
960impl Default for RpcServerArgs {
961    fn default() -> Self {
962        let DefaultRpcServerArgs {
963            http,
964            http_addr,
965            http_port,
966            http_disable_compression,
967            http_compression_algorithms,
968            http_decompression_algorithms,
969            http_api,
970            http_corsdomain,
971            ws,
972            ws_addr,
973            ws_port,
974            ws_allowed_origins,
975            ws_api,
976            ipcdisable,
977            ipcpath,
978            ipc_socket_permissions,
979            auth_addr,
980            auth_port,
981            auth_jwtsecret,
982            auth_ipc,
983            auth_ipc_path: _,
984            disable_auth_server,
985            rpc_jwtsecret,
986            rpc_disable_metrics,
987            rpc_max_request_size,
988            rpc_max_response_size,
989            rpc_max_subscriptions_per_connection,
990            rpc_max_connections,
991            rpc_max_tracing_requests,
992            rpc_max_blocking_io_requests,
993            rpc_max_trace_filter_blocks,
994            rpc_max_blocks_per_filter,
995            rpc_max_logs_per_response,
996            rpc_gas_cap,
997            rpc_evm_memory_limit,
998            rpc_tx_fee_cap,
999            rpc_max_simulate_blocks,
1000            rpc_compute_state_root_for_eth_simulate,
1001            rpc_eth_proof_window,
1002            rpc_proof_permits,
1003            rpc_pending_block,
1004            rpc_forwarder,
1005            builder_disallow,
1006            rpc_state_cache,
1007            gas_price_oracle,
1008            rpc_send_raw_transaction_sync_timeout,
1009        } = DefaultRpcServerArgs::get_global().clone();
1010        Self {
1011            http,
1012            http_addr,
1013            http_port,
1014            http_disable_compression,
1015            http_compression_algorithms,
1016            http_decompression_algorithms,
1017            http_api,
1018            http_corsdomain,
1019            ws,
1020            ws_addr,
1021            ws_port,
1022            ws_allowed_origins,
1023            ws_api,
1024            ipcdisable,
1025            ipcpath,
1026            ipc_socket_permissions,
1027            auth_addr,
1028            auth_port,
1029            auth_jwtsecret,
1030            auth_jwtsecret_hex: None,
1031            auth_ipc,
1032            auth_ipc_path: None,
1033            disable_auth_server,
1034            rpc_jwtsecret,
1035            rpc_disable_metrics,
1036            rpc_max_request_size,
1037            rpc_max_response_size,
1038            rpc_max_subscriptions_per_connection,
1039            rpc_max_connections,
1040            rpc_max_tracing_requests,
1041            rpc_max_blocking_io_requests,
1042            rpc_max_trace_filter_blocks,
1043            rpc_max_blocks_per_filter,
1044            rpc_max_logs_per_response,
1045            rpc_gas_cap,
1046            rpc_evm_memory_limit,
1047            rpc_tx_fee_cap,
1048            rpc_max_simulate_blocks,
1049            rpc_compute_state_root_for_eth_simulate,
1050            rpc_eth_proof_window,
1051            rpc_proof_permits,
1052            rpc_pending_block,
1053            rpc_forwarder,
1054            builder_disallow,
1055            rpc_state_cache,
1056            gas_price_oracle,
1057            rpc_send_raw_transaction_sync_timeout,
1058            testing_skip_invalid_transactions: false,
1059            testing_gas_limit: None,
1060            rpc_force_blob_sidecar_upcasting: false,
1061        }
1062    }
1063}
1064
1065/// Formats a wei amount as the ether string `--rpc.txfeecap` parses, without trailing zeros.
1066fn format_tx_fee_cap(wei: u128) -> String {
1067    format_ether(wei).trim_end_matches('0').trim_end_matches('.').to_string()
1068}
1069
1070/// clap value parser for [`RpcModuleSelection`] with configurable validation.
1071#[derive(Clone, Debug, Default)]
1072#[non_exhaustive]
1073struct RpcModuleSelectionValueParser;
1074
1075impl TypedValueParser for RpcModuleSelectionValueParser {
1076    type Value = RpcModuleSelection;
1077
1078    fn parse_ref(
1079        &self,
1080        _cmd: &Command,
1081        _arg: Option<&Arg>,
1082        value: &OsStr,
1083    ) -> Result<Self::Value, clap::Error> {
1084        let val =
1085            value.to_str().ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8))?;
1086        // This will now accept any module name, creating Other(name) for unknowns
1087        Ok(val
1088            .parse::<RpcModuleSelection>()
1089            .expect("RpcModuleSelection parsing cannot fail with Other variant"))
1090    }
1091
1092    fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
1093        // Only show standard modules in help text (excludes "other")
1094        let values = RethRpcModule::standard_variant_names().map(PossibleValue::new);
1095        Some(Box::new(values))
1096    }
1097}
1098
1099/// Uses a typed default without serializing secrets or treating address sets as file paths.
1100#[derive(Clone)]
1101struct DefaultValueParser<P: TypedValueParser>
1102where
1103    P::Value: 'static,
1104{
1105    inner: P,
1106    default: &'static Option<P::Value>,
1107}
1108
1109impl<P: TypedValueParser> TypedValueParser for DefaultValueParser<P>
1110where
1111    P::Value: 'static,
1112{
1113    type Value = P::Value;
1114
1115    fn parse_ref(
1116        &self,
1117        cmd: &Command,
1118        arg: Option<&Arg>,
1119        value: &OsStr,
1120    ) -> Result<Self::Value, clap::Error> {
1121        self.inner.parse_ref(cmd, arg, value)
1122    }
1123
1124    fn parse_ref_(
1125        &self,
1126        cmd: &Command,
1127        arg: Option<&Arg>,
1128        value: &OsStr,
1129        source: ValueSource,
1130    ) -> Result<Self::Value, clap::Error> {
1131        if source == ValueSource::DefaultValue &&
1132            let Some(default) = self.default
1133        {
1134            Ok(default.clone())
1135        } else {
1136            self.inner.parse_ref_(cmd, arg, value, source)
1137        }
1138    }
1139}
1140
1141#[cfg(test)]
1142mod tests {
1143    use super::*;
1144    use clap::{Args, Parser};
1145
1146    /// A helper type to parse Args more easily
1147    #[derive(Parser)]
1148    struct CommandParser<T: Args> {
1149        #[command(flatten)]
1150        args: T,
1151    }
1152
1153    #[test]
1154    fn parse_bal_cache_modes() {
1155        for (flag, cache_computed, prewarm) in [
1156            ("--rpc-cache.cache-computed-bals", true, None),
1157            ("--rpc-cache.prewarm-bals", false, Some(0)),
1158            ("--rpc-cache.prewarm-bals=10", false, Some(10)),
1159        ] {
1160            let args = CommandParser::<RpcServerArgs>::parse_from(["reth", flag]).args;
1161            assert_eq!(args.rpc_state_cache.cache_computed_bals, cache_computed);
1162            assert_eq!(args.rpc_state_cache.prewarm_bals, prewarm);
1163        }
1164    }
1165
1166    #[test]
1167    fn test_rpc_server_args_parser() {
1168        let args =
1169            CommandParser::<RpcServerArgs>::parse_from(["reth", "--http.api", "eth,admin,debug"])
1170                .args;
1171
1172        let apis = args.http_api.unwrap();
1173        let expected = RpcModuleSelection::try_from_selection(["eth", "admin", "debug"]).unwrap();
1174
1175        assert_eq!(apis, expected);
1176    }
1177
1178    #[test]
1179    fn test_rpc_server_eth_call_bundle_args() {
1180        let args =
1181            CommandParser::<RpcServerArgs>::parse_from(["reth", "--http.api", "eth,admin,debug"])
1182                .args;
1183
1184        let apis = args.http_api.unwrap();
1185        let expected = RpcModuleSelection::try_from_selection(["eth", "admin", "debug"]).unwrap();
1186
1187        assert_eq!(apis, expected);
1188    }
1189
1190    #[test]
1191    fn test_rpc_server_args_parser_none() {
1192        let args = CommandParser::<RpcServerArgs>::parse_from(["reth", "--http.api", "none"]).args;
1193        let apis = args.http_api.unwrap();
1194        let expected = RpcModuleSelection::Selection(Default::default());
1195        assert_eq!(apis, expected);
1196    }
1197
1198    #[test]
1199    fn rpc_concurrency_limits_reject_zero() {
1200        for flag in
1201            ["--rpc.max-tracing-requests", "--rpc.max-blocking-io-requests", "--rpc.proof-permits"]
1202        {
1203            let result = CommandParser::<RpcServerArgs>::try_parse_from(["reth", flag, "0"]);
1204            assert!(result.is_err(), "{flag} should reject zero");
1205        }
1206
1207        let args = CommandParser::<RpcServerArgs>::parse_from([
1208            "reth",
1209            "--rpc.max-tracing-requests",
1210            "1",
1211            "--rpc.max-blocking-io-requests",
1212            "2",
1213            "--rpc.proof-permits",
1214            "3",
1215        ])
1216        .args;
1217        assert_eq!(args.rpc_max_tracing_requests, 1);
1218        assert_eq!(args.rpc_max_blocking_io_requests, 2);
1219        assert_eq!(args.rpc_proof_permits, 3);
1220    }
1221
1222    #[test]
1223    fn http_compression_algorithms_are_optional() {
1224        let args = CommandParser::<RpcServerArgs>::parse_from(["reth"]).args;
1225        assert!(args.http_compression_algorithms.is_none());
1226
1227        let args = CommandParser::<RpcServerArgs>::parse_from([
1228            "reth",
1229            "--http.compression",
1230            "zstd,gzip,deflate,br",
1231        ])
1232        .args;
1233        assert_eq!(
1234            args.http_compression_algorithms.as_deref().unwrap(),
1235            ["zstd", "gzip", "deflate", "br"]
1236        );
1237
1238        let result =
1239            CommandParser::<RpcServerArgs>::try_parse_from(["reth", "--http.compression", "gizp"]);
1240        assert!(result.is_err());
1241    }
1242
1243    #[test]
1244    fn rpc_server_args_default_sanity_test() {
1245        let default_args = RpcServerArgs::default();
1246        let args = CommandParser::<RpcServerArgs>::parse_from(["reth"]).args;
1247
1248        assert_eq!(args, default_args);
1249    }
1250
1251    #[test]
1252    fn http_request_decompression_is_opt_in() {
1253        let args = CommandParser::<RpcServerArgs>::parse_from(["reth"]).args;
1254        assert!(args.http_decompression_algorithms.is_none());
1255
1256        let args = CommandParser::<RpcServerArgs>::parse_from([
1257            "reth",
1258            "--http.decompression",
1259            "zstd,gzip,deflate,br",
1260        ])
1261        .args;
1262        assert_eq!(
1263            args.http_decompression_algorithms.as_deref().unwrap(),
1264            ["zstd", "gzip", "deflate", "br"]
1265        );
1266    }
1267
1268    #[test]
1269    fn invalid_http_request_decompression_algorithm_is_rejected() {
1270        let result = CommandParser::<RpcServerArgs>::try_parse_from([
1271            "reth",
1272            "--http.decompression",
1273            "gizp",
1274        ]);
1275        assert!(result.is_err());
1276    }
1277
1278    #[test]
1279    fn test_rpc_disable_metrics_arg() {
1280        let args = CommandParser::<RpcServerArgs>::parse_from(["reth"]).args;
1281        assert!(!args.rpc_disable_metrics);
1282
1283        let args =
1284            CommandParser::<RpcServerArgs>::parse_from(["reth", "--rpc.disable-metrics"]).args;
1285        assert!(args.rpc_disable_metrics);
1286    }
1287
1288    #[test]
1289    fn test_rpc_tx_fee_cap_parse_integer() {
1290        let args = CommandParser::<RpcServerArgs>::parse_from(["reth", "--rpc.txfeecap", "2"]).args;
1291        let expected = 2_000_000_000_000_000_000u128; // 2 ETH in wei
1292        assert_eq!(args.rpc_tx_fee_cap, expected);
1293    }
1294
1295    #[test]
1296    fn test_rpc_tx_fee_cap_parse_decimal() {
1297        let args =
1298            CommandParser::<RpcServerArgs>::parse_from(["reth", "--rpc.txfeecap", "1.5"]).args;
1299        let expected = 1_500_000_000_000_000_000u128; // 1.5 ETH in wei
1300        assert_eq!(args.rpc_tx_fee_cap, expected);
1301    }
1302
1303    #[test]
1304    fn test_rpc_tx_fee_cap_parse_zero() {
1305        let args = CommandParser::<RpcServerArgs>::parse_from(["reth", "--rpc.txfeecap", "0"]).args;
1306        assert_eq!(args.rpc_tx_fee_cap, 0); // 0 = no cap
1307    }
1308
1309    #[test]
1310    fn test_rpc_tx_fee_cap_parse_none() {
1311        let args = CommandParser::<RpcServerArgs>::parse_from(["reth"]).args;
1312        let expected = 1_000_000_000_000_000_000u128;
1313        assert_eq!(args.rpc_tx_fee_cap, expected); // 1 ETH default cap
1314    }
1315
1316    #[test]
1317    fn test_rpc_server_args() {
1318        let args = RpcServerArgs {
1319            http: true,
1320            http_addr: "127.0.0.1".parse().unwrap(),
1321            http_port: 8545,
1322            http_disable_compression: false,
1323            http_compression_algorithms: None,
1324            http_decompression_algorithms: None,
1325            http_api: Some(RpcModuleSelection::try_from_selection(["eth", "admin"]).unwrap()),
1326            http_corsdomain: Some("*".to_string()),
1327            ws: true,
1328            ws_addr: "127.0.0.1".parse().unwrap(),
1329            ws_port: 8546,
1330            ws_allowed_origins: Some("*".to_string()),
1331            ws_api: Some(RpcModuleSelection::try_from_selection(["eth", "admin"]).unwrap()),
1332            ipcdisable: false,
1333            ipcpath: "reth.ipc".to_string(),
1334            ipc_socket_permissions: Some("0o666".to_string()),
1335            auth_addr: "127.0.0.1".parse().unwrap(),
1336            auth_port: 8551,
1337            auth_jwtsecret: Some(std::path::PathBuf::from("/tmp/jwt.hex")),
1338            auth_jwtsecret_hex: None,
1339            auth_ipc: false,
1340            auth_ipc_path: Some("engine.ipc".to_string()),
1341            disable_auth_server: false,
1342            rpc_jwtsecret: Some(
1343                JwtSecret::from_hex(
1344                    "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
1345                )
1346                .unwrap(),
1347            ),
1348            rpc_disable_metrics: false,
1349            rpc_max_request_size: 15u32.into(),
1350            rpc_max_response_size: 160u32.into(),
1351            rpc_max_subscriptions_per_connection: 1024u32.into(),
1352            rpc_max_connections: 500u32.into(),
1353            rpc_max_tracing_requests: 16,
1354            rpc_max_blocking_io_requests: 256,
1355            rpc_max_trace_filter_blocks: 4000,
1356            rpc_max_blocks_per_filter: 1000u64.into(),
1357            rpc_max_logs_per_response: 10000u64.into(),
1358            rpc_gas_cap: 50_000_000,
1359            rpc_evm_memory_limit: 256,
1360            rpc_tx_fee_cap: 2_000_000_000_000_000_000u128,
1361            rpc_max_simulate_blocks: 256,
1362            rpc_compute_state_root_for_eth_simulate: false,
1363            rpc_eth_proof_window: 100_000,
1364            rpc_proof_permits: 16,
1365            rpc_pending_block: PendingBlockKind::Full,
1366            rpc_forwarder: Some("http://localhost:8545".parse().unwrap()),
1367            builder_disallow: None,
1368            rpc_state_cache: RpcStateCacheArgs {
1369                max_blocks: 5000,
1370                max_receipts: 2000,
1371                max_bals: 1000,
1372                cache_computed_bals: true,
1373                prewarm_bals: Some(0),
1374                max_concurrent_db_requests: 512,
1375                max_cached_tx_hashes: 100_000,
1376                ..Default::default()
1377            },
1378            gas_price_oracle: GasPriceOracleArgs {
1379                blocks: 20,
1380                ignore_price: 2,
1381                max_price: 500_000_000_000,
1382                percentile: 60,
1383                default_suggested_fee: None,
1384            },
1385            rpc_send_raw_transaction_sync_timeout: std::time::Duration::from_secs(30),
1386            testing_skip_invalid_transactions: true,
1387            testing_gas_limit: None,
1388            rpc_force_blob_sidecar_upcasting: false,
1389        };
1390
1391        let parsed_args = CommandParser::<RpcServerArgs>::parse_from([
1392            "reth",
1393            "--http",
1394            "--http.addr",
1395            "127.0.0.1",
1396            "--http.port",
1397            "8545",
1398            "--http.api",
1399            "eth,admin",
1400            "--http.corsdomain",
1401            "*",
1402            "--ws",
1403            "--ws.addr",
1404            "127.0.0.1",
1405            "--ws.port",
1406            "8546",
1407            "--ws.origins",
1408            "*",
1409            "--ws.api",
1410            "eth,admin",
1411            "--ipcpath",
1412            "reth.ipc",
1413            "--ipc.permissions",
1414            "0o666",
1415            "--authrpc.addr",
1416            "127.0.0.1",
1417            "--authrpc.port",
1418            "8551",
1419            "--authrpc.jwtsecret",
1420            "/tmp/jwt.hex",
1421            "--auth-ipc.path",
1422            "engine.ipc",
1423            "--rpc.jwtsecret",
1424            "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
1425            "--rpc.max-request-size",
1426            "15",
1427            "--rpc.max-response-size",
1428            "160",
1429            "--rpc.max-subscriptions-per-connection",
1430            "1024",
1431            "--rpc.max-connections",
1432            "500",
1433            "--rpc.max-tracing-requests",
1434            "16",
1435            "--rpc.max-blocking-io-requests",
1436            "256",
1437            "--rpc.max-trace-filter-blocks",
1438            "4000",
1439            "--rpc.max-blocks-per-filter",
1440            "1000",
1441            "--rpc.max-logs-per-response",
1442            "10000",
1443            "--rpc.gascap",
1444            "50000000",
1445            "--rpc.evm-memory-limit",
1446            "256",
1447            "--rpc.txfeecap",
1448            "2.0",
1449            "--rpc.max-simulate-blocks",
1450            "256",
1451            "--rpc.eth-proof-window",
1452            "100000",
1453            "--rpc.proof-permits",
1454            "16",
1455            "--rpc.pending-block",
1456            "full",
1457            "--rpc.forwarder",
1458            "http://localhost:8545",
1459            "--rpc-cache.max-blocks",
1460            "5000",
1461            "--rpc-cache.max-receipts",
1462            "2000",
1463            "--rpc-cache.max-bals",
1464            "1000",
1465            "--rpc-cache.cache-computed-bals",
1466            "--rpc-cache.prewarm-bals",
1467            "--rpc-cache.max-concurrent-db-requests",
1468            "512",
1469            "--gpo.blocks",
1470            "20",
1471            "--gpo.ignoreprice",
1472            "2",
1473            "--gpo.maxprice",
1474            "500000000000",
1475            "--gpo.percentile",
1476            "60",
1477            "--rpc.send-raw-transaction-sync-timeout",
1478            "30s",
1479            "--testing.skip-invalid-transactions",
1480        ])
1481        .args;
1482
1483        assert_eq!(parsed_args, args);
1484    }
1485
1486    #[test]
1487    fn parse_auth_jwtsecret_hex() {
1488        let hex = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
1489        let args =
1490            CommandParser::<RpcServerArgs>::parse_from(["reth", "--authrpc.jwtsecret-hex", hex])
1491                .args;
1492
1493        let expected = JwtSecret::from_hex(hex).unwrap();
1494        assert_eq!(args.auth_jwtsecret_hex, Some(expected));
1495        assert_eq!(args.auth_jwtsecret, None);
1496    }
1497
1498    #[test]
1499    fn test_auth_jwtsecret_and_hex_are_mutually_exclusive() {
1500        let result = CommandParser::<RpcServerArgs>::try_parse_from([
1501            "reth",
1502            "--authrpc.jwtsecret",
1503            "/tmp/jwt.hex",
1504            "--authrpc.jwtsecret-hex",
1505            "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
1506        ]);
1507
1508        assert!(result.is_err());
1509    }
1510
1511    #[test]
1512    fn test_adjust_instance_ports_suffixes_ipc_paths() {
1513        let paths: Vec<_> = (1..=3u16)
1514            .map(|instance| {
1515                let mut args = RpcServerArgs::default();
1516                let ipcpath = args.ipcpath.clone();
1517                let auth_ipc_path = args.auth_ipc_path().to_string();
1518                args.adjust_instance_ports(Some(instance));
1519                assert_eq!(args.ipcpath, format!("{ipcpath}-{instance}"));
1520                assert_eq!(args.auth_ipc_path(), format!("{auth_ipc_path}-{instance}"));
1521                args.auth_ipc_path().to_string()
1522            })
1523            .collect();
1524
1525        assert_eq!(
1526            paths,
1527            [
1528                "/tmp/reth_engine_api.ipc-1",
1529                "/tmp/reth_engine_api.ipc-2",
1530                "/tmp/reth_engine_api.ipc-3"
1531            ]
1532        );
1533
1534        // Without an instance the paths are left untouched.
1535        let mut args = RpcServerArgs::default();
1536        let auth_ipc_path = args.auth_ipc_path().to_string();
1537        args.adjust_instance_ports(None);
1538        assert_eq!(args.auth_ipc_path(), auth_ipc_path);
1539    }
1540
1541    #[test]
1542    fn test_adjust_instance_ports_preserves_explicit_auth_ipc_path() {
1543        for path in ["engine.ipc", constants::DEFAULT_ENGINE_API_IPC_ENDPOINT] {
1544            let mut args =
1545                CommandParser::<RpcServerArgs>::parse_from(["reth", "--auth-ipc.path", path]).args;
1546            args.adjust_instance_ports(Some(2));
1547            assert_eq!(args.auth_ipc_path(), path);
1548        }
1549    }
1550}