1use 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
28pub(crate) const RPC_DEFAULT_MAX_SUBS_PER_CONN: u32 = 1024;
30
31pub(crate) const RPC_DEFAULT_MAX_REQUEST_SIZE_MB: u32 = 15;
33
34pub(crate) const RPC_DEFAULT_MAX_RESPONSE_SIZE_MB: u32 = 160;
38
39pub(crate) const RPC_DEFAULT_MAX_CONNECTIONS: u32 = 500;
41
42#[derive(Debug, Clone, Args, PartialEq, Eq)]
44#[command(next_help_heading = "RPC")]
45pub struct RpcServerArgs {
46 #[arg(long, default_value_if("dev", "true", "true"))]
48 pub http: bool,
49
50 #[arg(long = "http.addr", default_value_t = IpAddr::V4(Ipv4Addr::LOCALHOST))]
52 pub http_addr: IpAddr,
53
54 #[arg(long = "http.port", default_value_t = constants::DEFAULT_HTTP_RPC_PORT)]
56 pub http_port: u16,
57
58 #[arg(long = "http.disable-compression", default_value_t = false)]
60 pub http_disable_compression: bool,
61
62 #[arg(long = "http.api", value_parser = RpcModuleSelectionValueParser::default())]
64 pub http_api: Option<RpcModuleSelection>,
65
66 #[arg(long = "http.corsdomain")]
68 pub http_corsdomain: Option<String>,
69
70 #[arg(long)]
72 pub ws: bool,
73
74 #[arg(long = "ws.addr", default_value_t = IpAddr::V4(Ipv4Addr::LOCALHOST))]
76 pub ws_addr: IpAddr,
77
78 #[arg(long = "ws.port", default_value_t = constants::DEFAULT_WS_RPC_PORT)]
80 pub ws_port: u16,
81
82 #[arg(id = "ws.origins", long = "ws.origins", alias = "ws.corsdomain")]
84 pub ws_allowed_origins: Option<String>,
85
86 #[arg(long = "ws.api", value_parser = RpcModuleSelectionValueParser::default())]
88 pub ws_api: Option<RpcModuleSelection>,
89
90 #[arg(long)]
92 pub ipcdisable: bool,
93
94 #[arg(long, default_value_t = constants::DEFAULT_IPC_ENDPOINT.to_string())]
96 pub ipcpath: String,
97
98 #[arg(long = "ipc.permissions")]
102 pub ipc_socket_permissions: Option<String>,
103
104 #[arg(long = "authrpc.addr", default_value_t = IpAddr::V4(Ipv4Addr::LOCALHOST))]
106 pub auth_addr: IpAddr,
107
108 #[arg(long = "authrpc.port", default_value_t = constants::DEFAULT_AUTH_PORT)]
110 pub auth_port: u16,
111
112 #[arg(long = "authrpc.jwtsecret", value_name = "PATH", global = true, required = false)]
119 pub auth_jwtsecret: Option<PathBuf>,
120
121 #[arg(long)]
123 pub auth_ipc: bool,
124
125 #[arg(long = "auth-ipc.path", default_value_t = constants::DEFAULT_ENGINE_API_IPC_ENDPOINT.to_string())]
127 pub auth_ipc_path: String,
128
129 #[arg(long = "disable-auth-server", alias = "disable-engine-api")]
134 pub disable_auth_server: bool,
135
136 #[arg(long = "rpc.jwtsecret", value_name = "HEX", global = true, required = false)]
142 pub rpc_jwtsecret: Option<JwtSecret>,
143
144 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[arg(long = "rpc.pending-block", default_value = "full", value_name = "KIND")]
228 pub rpc_pending_block: PendingBlockKind,
229
230 #[arg(long = "rpc.forwarder", alias = "rpc-forwarder", value_name = "FORWARDER")]
232 pub rpc_forwarder: Option<Url>,
233
234 #[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 #[command(flatten)]
241 pub rpc_state_cache: RpcStateCacheArgs,
242
243 #[command(flatten)]
245 pub gas_price_oracle: GasPriceOracleArgs,
246
247 #[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 pub const fn with_http(mut self) -> Self {
260 self.http = true;
261 self
262 }
263
264 pub fn with_http_api(mut self, http_api: RpcModuleSelection) -> Self {
266 self.http_api = Some(http_api);
267 self
268 }
269
270 pub const fn with_ws(mut self) -> Self {
272 self.ws = true;
273 self
274 }
275
276 pub fn with_ws_api(mut self, ws_api: RpcModuleSelection) -> Self {
278 self.ws_api = Some(ws_api);
279 self
280 }
281
282 pub const fn with_auth_ipc(mut self) -> Self {
284 self.auth_ipc = true;
285 self
286 }
287
288 pub fn with_api(self, api: RpcModuleSelection) -> Self {
292 self.with_http_api(api.clone()).with_ws_api(api)
293 }
294
295 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 self.auth_port += instance * 100 - 100;
314 self.http_port -= instance - 1;
316 self.ws_port += instance * 2 - 2;
318 self.ipcpath = format!("{}-{}", self.ipcpath, instance);
320 }
321 }
322
323 pub const fn with_http_unused_port(mut self) -> Self {
326 self.http_port = 0;
327 self
328 }
329
330 pub const fn with_ws_unused_port(mut self) -> Self {
333 self.ws_port = 0;
334 self
335 }
336
337 pub const fn with_auth_unused_port(mut self) -> Self {
340 self.auth_port = 0;
341 self
342 }
343
344 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 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 pub fn apply<F>(self, f: F) -> Self
365 where
366 F: FnOnce(Self) -> Self,
367 {
368 f(self)
369 }
370
371 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#[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 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 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 #[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; 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; 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); }
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); }
534}