reth_node_core/args/
rpc_server.rs

1//! clap [Args](clap::Args) for RPC related arguments.
2
3use crate::args::{
4    types::{MaxU32, ZeroAsNoneU64},
5    GasPriceOracleArgs, RpcStateCacheArgs,
6};
7use alloy_primitives::Address;
8use alloy_rpc_types_engine::JwtSecret;
9use clap::{
10    builder::{PossibleValue, RangedU64ValueParser, TypedValueParser},
11    Arg, Args, Command,
12};
13use rand::Rng;
14use reth_cli_util::{parse_duration_from_secs_or_ms, parse_ether_value};
15use reth_rpc_eth_types::builder::config::PendingBlockKind;
16use reth_rpc_server_types::{constants, RethRpcModule, RpcModuleSelection};
17use std::{
18    collections::HashSet,
19    ffi::OsStr,
20    net::{IpAddr, Ipv4Addr},
21    path::PathBuf,
22    time::Duration,
23};
24use url::Url;
25
26use super::types::MaxOr;
27
28/// Default max number of subscriptions per connection.
29pub(crate) const RPC_DEFAULT_MAX_SUBS_PER_CONN: u32 = 1024;
30
31/// Default max request size in MB.
32pub(crate) const RPC_DEFAULT_MAX_REQUEST_SIZE_MB: u32 = 15;
33
34/// Default max response size in MB.
35///
36/// This is only relevant for very large trace responses.
37pub(crate) const RPC_DEFAULT_MAX_RESPONSE_SIZE_MB: u32 = 160;
38
39/// Default number of incoming connections.
40pub(crate) const RPC_DEFAULT_MAX_CONNECTIONS: u32 = 500;
41
42/// Parameters for configuring the rpc more granularity via CLI
43#[derive(Debug, Clone, Args, PartialEq, Eq)]
44#[command(next_help_heading = "RPC")]
45pub struct RpcServerArgs {
46    /// Enable the HTTP-RPC server
47    #[arg(long, default_value_if("dev", "true", "true"))]
48    pub http: bool,
49
50    /// Http server address to listen on
51    #[arg(long = "http.addr", default_value_t = IpAddr::V4(Ipv4Addr::LOCALHOST))]
52    pub http_addr: IpAddr,
53
54    /// Http server port to listen on
55    #[arg(long = "http.port", default_value_t = constants::DEFAULT_HTTP_RPC_PORT)]
56    pub http_port: u16,
57
58    /// Disable compression for HTTP responses
59    #[arg(long = "http.disable-compression", default_value_t = false)]
60    pub http_disable_compression: bool,
61
62    /// Rpc Modules to be configured for the HTTP server
63    #[arg(long = "http.api", value_parser = RpcModuleSelectionValueParser::default())]
64    pub http_api: Option<RpcModuleSelection>,
65
66    /// Http Corsdomain to allow request from
67    #[arg(long = "http.corsdomain")]
68    pub http_corsdomain: Option<String>,
69
70    /// Enable the WS-RPC server
71    #[arg(long)]
72    pub ws: bool,
73
74    /// Ws server address to listen on
75    #[arg(long = "ws.addr", default_value_t = IpAddr::V4(Ipv4Addr::LOCALHOST))]
76    pub ws_addr: IpAddr,
77
78    /// Ws server port to listen on
79    #[arg(long = "ws.port", default_value_t = constants::DEFAULT_WS_RPC_PORT)]
80    pub ws_port: u16,
81
82    /// Origins from which to accept `WebSocket` requests
83    #[arg(id = "ws.origins", long = "ws.origins", alias = "ws.corsdomain")]
84    pub ws_allowed_origins: Option<String>,
85
86    /// Rpc Modules to be configured for the WS server
87    #[arg(long = "ws.api", value_parser = RpcModuleSelectionValueParser::default())]
88    pub ws_api: Option<RpcModuleSelection>,
89
90    /// Disable the IPC-RPC server
91    #[arg(long)]
92    pub ipcdisable: bool,
93
94    /// Filename for IPC socket/pipe within the datadir
95    #[arg(long, default_value_t = constants::DEFAULT_IPC_ENDPOINT.to_string())]
96    pub ipcpath: String,
97
98    /// Set the permissions for the IPC socket file, in octal format.
99    ///
100    /// If not specified, the permissions will be set by the system's umask.
101    #[arg(long = "ipc.permissions")]
102    pub ipc_socket_permissions: Option<String>,
103
104    /// Auth server address to listen on
105    #[arg(long = "authrpc.addr", default_value_t = IpAddr::V4(Ipv4Addr::LOCALHOST))]
106    pub auth_addr: IpAddr,
107
108    /// Auth server port to listen on
109    #[arg(long = "authrpc.port", default_value_t = constants::DEFAULT_AUTH_PORT)]
110    pub auth_port: u16,
111
112    /// Path to a JWT secret to use for the authenticated engine-API RPC server.
113    ///
114    /// This will enforce JWT authentication for all requests coming from the consensus layer.
115    ///
116    /// If no path is provided, a secret will be generated and stored in the datadir under
117    /// `<DIR>/<CHAIN_ID>/jwt.hex`. For mainnet this would be `~/.reth/mainnet/jwt.hex` by default.
118    #[arg(long = "authrpc.jwtsecret", value_name = "PATH", global = true, required = false)]
119    pub auth_jwtsecret: Option<PathBuf>,
120
121    /// Enable auth engine API over IPC
122    #[arg(long)]
123    pub auth_ipc: bool,
124
125    /// Filename for auth IPC socket/pipe within the datadir
126    #[arg(long = "auth-ipc.path", default_value_t = constants::DEFAULT_ENGINE_API_IPC_ENDPOINT.to_string())]
127    pub auth_ipc_path: String,
128
129    /// Disable the auth/engine API server.
130    ///
131    /// This will prevent the authenticated engine-API server from starting. Use this if you're
132    /// running a node that doesn't need to serve engine API requests.
133    #[arg(long = "disable-auth-server", alias = "disable-engine-api")]
134    pub disable_auth_server: bool,
135
136    /// Hex encoded JWT secret to authenticate the regular RPC server(s), see `--http.api` and
137    /// `--ws.api`.
138    ///
139    /// This is __not__ used for the authenticated engine-API RPC server, see
140    /// `--authrpc.jwtsecret`.
141    #[arg(long = "rpc.jwtsecret", value_name = "HEX", global = true, required = false)]
142    pub rpc_jwtsecret: Option<JwtSecret>,
143
144    /// Set the maximum RPC request payload size for both HTTP and WS in megabytes.
145    #[arg(long = "rpc.max-request-size", alias = "rpc-max-request-size", default_value_t = RPC_DEFAULT_MAX_REQUEST_SIZE_MB.into())]
146    pub rpc_max_request_size: MaxU32,
147
148    /// Set the maximum RPC response payload size for both HTTP and WS in megabytes.
149    #[arg(long = "rpc.max-response-size", alias = "rpc-max-response-size", visible_alias = "rpc.returndata.limit", default_value_t = RPC_DEFAULT_MAX_RESPONSE_SIZE_MB.into())]
150    pub rpc_max_response_size: MaxU32,
151
152    /// Set the maximum concurrent subscriptions per connection.
153    #[arg(long = "rpc.max-subscriptions-per-connection", alias = "rpc-max-subscriptions-per-connection", default_value_t = RPC_DEFAULT_MAX_SUBS_PER_CONN.into())]
154    pub rpc_max_subscriptions_per_connection: MaxU32,
155
156    /// Maximum number of RPC server connections.
157    #[arg(long = "rpc.max-connections", alias = "rpc-max-connections", value_name = "COUNT", default_value_t = RPC_DEFAULT_MAX_CONNECTIONS.into())]
158    pub rpc_max_connections: MaxU32,
159
160    /// Maximum number of concurrent tracing requests.
161    ///
162    /// By default this chooses a sensible value based on the number of available cores.
163    /// Tracing requests are generally CPU bound.
164    /// Choosing a value that is higher than the available CPU cores can have a negative impact on
165    /// the performance of the node and affect the node's ability to maintain sync.
166    #[arg(long = "rpc.max-tracing-requests", alias = "rpc-max-tracing-requests", value_name = "COUNT", default_value_t = constants::default_max_tracing_requests())]
167    pub rpc_max_tracing_requests: usize,
168
169    /// Maximum number of blocks for `trace_filter` requests.
170    #[arg(long = "rpc.max-trace-filter-blocks", alias = "rpc-max-trace-filter-blocks", value_name = "COUNT", default_value_t = constants::DEFAULT_MAX_TRACE_FILTER_BLOCKS)]
171    pub rpc_max_trace_filter_blocks: u64,
172
173    /// Maximum number of blocks that could be scanned per filter request. (0 = entire chain)
174    #[arg(long = "rpc.max-blocks-per-filter", alias = "rpc-max-blocks-per-filter", value_name = "COUNT", default_value_t = ZeroAsNoneU64::new(constants::DEFAULT_MAX_BLOCKS_PER_FILTER))]
175    pub rpc_max_blocks_per_filter: ZeroAsNoneU64,
176
177    /// Maximum number of logs that can be returned in a single response. (0 = no limit)
178    #[arg(long = "rpc.max-logs-per-response", alias = "rpc-max-logs-per-response", value_name = "COUNT", default_value_t = ZeroAsNoneU64::new(constants::DEFAULT_MAX_LOGS_PER_RESPONSE as u64))]
179    pub rpc_max_logs_per_response: ZeroAsNoneU64,
180
181    /// Maximum gas limit for `eth_call` and call tracing RPC methods.
182    #[arg(
183        long = "rpc.gascap",
184        alias = "rpc-gascap",
185        value_name = "GAS_CAP",
186        value_parser = MaxOr::new(RangedU64ValueParser::<u64>::new().range(1..)),
187        default_value_t = constants::gas_oracle::RPC_DEFAULT_GAS_CAP
188    )]
189    pub rpc_gas_cap: u64,
190
191    /// Maximum eth transaction fee (in ether) that can be sent via the RPC APIs (0 = no cap)
192    #[arg(
193        long = "rpc.txfeecap",
194        alias = "rpc-txfeecap",
195        value_name = "TX_FEE_CAP",
196        value_parser = parse_ether_value,
197        default_value = "1.0"
198    )]
199    pub rpc_tx_fee_cap: u128,
200
201    /// Maximum number of blocks for `eth_simulateV1` call.
202    #[arg(
203        long = "rpc.max-simulate-blocks",
204        value_name = "BLOCKS_COUNT",
205        default_value_t = constants::DEFAULT_MAX_SIMULATE_BLOCKS
206    )]
207    pub rpc_max_simulate_blocks: u64,
208
209    /// The maximum proof window for historical proof generation.
210    /// This value allows for generating historical proofs up to
211    /// configured number of blocks from current tip (up to `tip - window`).
212    #[arg(
213        long = "rpc.eth-proof-window",
214        default_value_t = constants::DEFAULT_ETH_PROOF_WINDOW,
215        value_parser = RangedU64ValueParser::<u64>::new().range(..=constants::MAX_ETH_PROOF_WINDOW)
216    )]
217    pub rpc_eth_proof_window: u64,
218
219    /// Maximum number of concurrent getproof requests.
220    #[arg(long = "rpc.proof-permits", alias = "rpc-proof-permits", value_name = "COUNT", default_value_t = constants::DEFAULT_PROOF_PERMITS)]
221    pub rpc_proof_permits: usize,
222
223    /// Configures the pending block behavior for RPC responses.
224    ///
225    /// Options: full (include all transactions), empty (header only), none (disable pending
226    /// blocks).
227    #[arg(long = "rpc.pending-block", default_value = "full", value_name = "KIND")]
228    pub rpc_pending_block: PendingBlockKind,
229
230    /// Endpoint to forward transactions to.
231    #[arg(long = "rpc.forwarder", alias = "rpc-forwarder", value_name = "FORWARDER")]
232    pub rpc_forwarder: Option<Url>,
233
234    /// Path to file containing disallowed addresses, json-encoded list of strings. Block
235    /// validation API will reject blocks containing transactions from these addresses.
236    #[arg(long = "builder.disallow", value_name = "PATH", value_parser = reth_cli_util::parsers::read_json_from_file::<HashSet<Address>>)]
237    pub builder_disallow: Option<HashSet<Address>>,
238
239    /// State cache configuration.
240    #[command(flatten)]
241    pub rpc_state_cache: RpcStateCacheArgs,
242
243    /// Gas price oracle configuration.
244    #[command(flatten)]
245    pub gas_price_oracle: GasPriceOracleArgs,
246
247    /// Timeout for `send_raw_transaction_sync` RPC method.
248    #[arg(
249        long = "rpc.send-raw-transaction-sync-timeout",
250        value_name = "SECONDS",
251        default_value = "30s",
252        value_parser = parse_duration_from_secs_or_ms,
253    )]
254    pub rpc_send_raw_transaction_sync_timeout: Duration,
255}
256
257impl RpcServerArgs {
258    /// Enables the HTTP-RPC server.
259    pub const fn with_http(mut self) -> Self {
260        self.http = true;
261        self
262    }
263
264    /// Configures modules for the HTTP-RPC server.
265    pub fn with_http_api(mut self, http_api: RpcModuleSelection) -> Self {
266        self.http_api = Some(http_api);
267        self
268    }
269
270    /// Enables the WS-RPC server.
271    pub const fn with_ws(mut self) -> Self {
272        self.ws = true;
273        self
274    }
275
276    /// Configures modules for WS-RPC server.
277    pub fn with_ws_api(mut self, ws_api: RpcModuleSelection) -> Self {
278        self.ws_api = Some(ws_api);
279        self
280    }
281
282    /// Enables the Auth IPC
283    pub const fn with_auth_ipc(mut self) -> Self {
284        self.auth_ipc = true;
285        self
286    }
287
288    /// Configures modules for both the HTTP-RPC server and WS-RPC server.
289    ///
290    /// This is the same as calling both [`Self::with_http_api`] and [`Self::with_ws_api`].
291    pub fn with_api(self, api: RpcModuleSelection) -> Self {
292        self.with_http_api(api.clone()).with_ws_api(api)
293    }
294
295    /// Change rpc port numbers based on the instance number, if provided.
296    /// * The `auth_port` is scaled by a factor of `instance * 100`
297    /// * The `http_port` is scaled by a factor of `-instance`
298    /// * The `ws_port` is scaled by a factor of `instance * 2`
299    /// * The `ipcpath` is appended with the instance number: `/tmp/reth.ipc-<instance>`
300    ///
301    /// # Panics
302    /// Warning: if `instance` is zero in debug mode, this will panic.
303    ///
304    /// This will also panic in debug mode if either:
305    /// * `instance` is greater than `655` (scaling would overflow `u16`)
306    /// * `self.auth_port / 100 + (instance - 1)` would overflow `u16`
307    ///
308    /// In release mode, this will silently wrap around.
309    pub fn adjust_instance_ports(&mut self, instance: Option<u16>) {
310        if let Some(instance) = instance {
311            debug_assert_ne!(instance, 0, "instance must be non-zero");
312            // auth port is scaled by a factor of instance * 100
313            self.auth_port += instance * 100 - 100;
314            // http port is scaled by a factor of -instance
315            self.http_port -= instance - 1;
316            // ws port is scaled by a factor of instance * 2
317            self.ws_port += instance * 2 - 2;
318            // append instance file to ipc path
319            self.ipcpath = format!("{}-{}", self.ipcpath, instance);
320        }
321    }
322
323    /// Set the http port to zero, to allow the OS to assign a random unused port when the rpc
324    /// server binds to a socket.
325    pub const fn with_http_unused_port(mut self) -> Self {
326        self.http_port = 0;
327        self
328    }
329
330    /// Set the ws port to zero, to allow the OS to assign a random unused port when the rpc
331    /// server binds to a socket.
332    pub const fn with_ws_unused_port(mut self) -> Self {
333        self.ws_port = 0;
334        self
335    }
336
337    /// Set the auth port to zero, to allow the OS to assign a random unused port when the rpc
338    /// server binds to a socket.
339    pub const fn with_auth_unused_port(mut self) -> Self {
340        self.auth_port = 0;
341        self
342    }
343
344    /// Append a random string to the ipc path, to prevent possible collisions when multiple nodes
345    /// are being run on the same machine.
346    pub fn with_ipc_random_path(mut self) -> Self {
347        let random_string: String =
348            rand::rng().sample_iter(rand::distr::Alphanumeric).take(8).map(char::from).collect();
349        self.ipcpath = format!("{}-{}", self.ipcpath, random_string);
350        self
351    }
352
353    /// Configure all ports to be set to a random unused port when bound, and set the IPC path to a
354    /// random path.
355    pub fn with_unused_ports(mut self) -> Self {
356        self = self.with_http_unused_port();
357        self = self.with_ws_unused_port();
358        self = self.with_auth_unused_port();
359        self = self.with_ipc_random_path();
360        self
361    }
362
363    /// Apply a function to the args.
364    pub fn apply<F>(self, f: F) -> Self
365    where
366        F: FnOnce(Self) -> Self,
367    {
368        f(self)
369    }
370
371    /// Configures the timeout for send raw transaction sync.
372    pub const fn with_send_raw_transaction_sync_timeout(mut self, timeout: Duration) -> Self {
373        self.rpc_send_raw_transaction_sync_timeout = timeout;
374        self
375    }
376}
377
378impl Default for RpcServerArgs {
379    fn default() -> Self {
380        Self {
381            http: false,
382            http_addr: Ipv4Addr::LOCALHOST.into(),
383            http_port: constants::DEFAULT_HTTP_RPC_PORT,
384            http_disable_compression: false,
385            http_api: None,
386            http_corsdomain: None,
387            ws: false,
388            ws_addr: Ipv4Addr::LOCALHOST.into(),
389            ws_port: constants::DEFAULT_WS_RPC_PORT,
390            ws_allowed_origins: None,
391            ws_api: None,
392            ipcdisable: false,
393            ipcpath: constants::DEFAULT_IPC_ENDPOINT.to_string(),
394            ipc_socket_permissions: None,
395            auth_addr: Ipv4Addr::LOCALHOST.into(),
396            auth_port: constants::DEFAULT_AUTH_PORT,
397            auth_jwtsecret: None,
398            auth_ipc: false,
399            auth_ipc_path: constants::DEFAULT_ENGINE_API_IPC_ENDPOINT.to_string(),
400            disable_auth_server: false,
401            rpc_jwtsecret: None,
402            rpc_max_request_size: RPC_DEFAULT_MAX_REQUEST_SIZE_MB.into(),
403            rpc_max_response_size: RPC_DEFAULT_MAX_RESPONSE_SIZE_MB.into(),
404            rpc_max_subscriptions_per_connection: RPC_DEFAULT_MAX_SUBS_PER_CONN.into(),
405            rpc_max_connections: RPC_DEFAULT_MAX_CONNECTIONS.into(),
406            rpc_max_tracing_requests: constants::default_max_tracing_requests(),
407            rpc_max_trace_filter_blocks: constants::DEFAULT_MAX_TRACE_FILTER_BLOCKS,
408            rpc_max_blocks_per_filter: constants::DEFAULT_MAX_BLOCKS_PER_FILTER.into(),
409            rpc_max_logs_per_response: (constants::DEFAULT_MAX_LOGS_PER_RESPONSE as u64).into(),
410            rpc_gas_cap: constants::gas_oracle::RPC_DEFAULT_GAS_CAP,
411            rpc_tx_fee_cap: constants::DEFAULT_TX_FEE_CAP_WEI,
412            rpc_max_simulate_blocks: constants::DEFAULT_MAX_SIMULATE_BLOCKS,
413            rpc_eth_proof_window: constants::DEFAULT_ETH_PROOF_WINDOW,
414            rpc_pending_block: PendingBlockKind::Full,
415            gas_price_oracle: GasPriceOracleArgs::default(),
416            rpc_state_cache: RpcStateCacheArgs::default(),
417            rpc_proof_permits: constants::DEFAULT_PROOF_PERMITS,
418            rpc_forwarder: None,
419            builder_disallow: Default::default(),
420            rpc_send_raw_transaction_sync_timeout:
421                constants::RPC_DEFAULT_SEND_RAW_TX_SYNC_TIMEOUT_SECS,
422        }
423    }
424}
425
426/// clap value parser for [`RpcModuleSelection`] with configurable validation.
427#[derive(Clone, Debug, Default)]
428#[non_exhaustive]
429struct RpcModuleSelectionValueParser;
430
431impl TypedValueParser for RpcModuleSelectionValueParser {
432    type Value = RpcModuleSelection;
433
434    fn parse_ref(
435        &self,
436        _cmd: &Command,
437        _arg: Option<&Arg>,
438        value: &OsStr,
439    ) -> Result<Self::Value, clap::Error> {
440        let val =
441            value.to_str().ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8))?;
442        // This will now accept any module name, creating Other(name) for unknowns
443        Ok(val
444            .parse::<RpcModuleSelection>()
445            .expect("RpcModuleSelection parsing cannot fail with Other variant"))
446    }
447
448    fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
449        // Only show standard modules in help text (excludes "other")
450        let values = RethRpcModule::standard_variant_names().map(PossibleValue::new);
451        Some(Box::new(values))
452    }
453}
454
455#[cfg(test)]
456mod tests {
457    use super::*;
458    use clap::{Args, Parser};
459
460    /// A helper type to parse Args more easily
461    #[derive(Parser)]
462    struct CommandParser<T: Args> {
463        #[command(flatten)]
464        args: T,
465    }
466
467    #[test]
468    fn test_rpc_server_args_parser() {
469        let args =
470            CommandParser::<RpcServerArgs>::parse_from(["reth", "--http.api", "eth,admin,debug"])
471                .args;
472
473        let apis = args.http_api.unwrap();
474        let expected = RpcModuleSelection::try_from_selection(["eth", "admin", "debug"]).unwrap();
475
476        assert_eq!(apis, expected);
477    }
478
479    #[test]
480    fn test_rpc_server_eth_call_bundle_args() {
481        let args =
482            CommandParser::<RpcServerArgs>::parse_from(["reth", "--http.api", "eth,admin,debug"])
483                .args;
484
485        let apis = args.http_api.unwrap();
486        let expected = RpcModuleSelection::try_from_selection(["eth", "admin", "debug"]).unwrap();
487
488        assert_eq!(apis, expected);
489    }
490
491    #[test]
492    fn test_rpc_server_args_parser_none() {
493        let args = CommandParser::<RpcServerArgs>::parse_from(["reth", "--http.api", "none"]).args;
494        let apis = args.http_api.unwrap();
495        let expected = RpcModuleSelection::Selection(Default::default());
496        assert_eq!(apis, expected);
497    }
498
499    #[test]
500    fn rpc_server_args_default_sanity_test() {
501        let default_args = RpcServerArgs::default();
502        let args = CommandParser::<RpcServerArgs>::parse_from(["reth"]).args;
503
504        assert_eq!(args, default_args);
505    }
506
507    #[test]
508    fn test_rpc_tx_fee_cap_parse_integer() {
509        let args = CommandParser::<RpcServerArgs>::parse_from(["reth", "--rpc.txfeecap", "2"]).args;
510        let expected = 2_000_000_000_000_000_000u128; // 2 ETH in wei
511        assert_eq!(args.rpc_tx_fee_cap, expected);
512    }
513
514    #[test]
515    fn test_rpc_tx_fee_cap_parse_decimal() {
516        let args =
517            CommandParser::<RpcServerArgs>::parse_from(["reth", "--rpc.txfeecap", "1.5"]).args;
518        let expected = 1_500_000_000_000_000_000u128; // 1.5 ETH in wei
519        assert_eq!(args.rpc_tx_fee_cap, expected);
520    }
521
522    #[test]
523    fn test_rpc_tx_fee_cap_parse_zero() {
524        let args = CommandParser::<RpcServerArgs>::parse_from(["reth", "--rpc.txfeecap", "0"]).args;
525        assert_eq!(args.rpc_tx_fee_cap, 0); // 0 = no cap
526    }
527
528    #[test]
529    fn test_rpc_tx_fee_cap_parse_none() {
530        let args = CommandParser::<RpcServerArgs>::parse_from(["reth"]).args;
531        let expected = 1_000_000_000_000_000_000u128;
532        assert_eq!(args.rpc_tx_fee_cap, expected); // 1 ETH default cap
533    }
534}