reth_node_core/args/
rpc_server.rs

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