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