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