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.evm-memory-limit",
194 alias = "rpc-evm-memory-limit",
195 value_name = "MEMORY_LIMIT",
196 value_parser = MaxOr::new(RangedU64ValueParser::<u64>::new().range(1..)),
197 default_value_t = (1 << 32) - 1
198 )]
199 pub rpc_evm_memory_limit: u64,
200
201 #[arg(
203 long = "rpc.txfeecap",
204 alias = "rpc-txfeecap",
205 value_name = "TX_FEE_CAP",
206 value_parser = parse_ether_value,
207 default_value = "1.0"
208 )]
209 pub rpc_tx_fee_cap: u128,
210
211 #[arg(
213 long = "rpc.max-simulate-blocks",
214 value_name = "BLOCKS_COUNT",
215 default_value_t = constants::DEFAULT_MAX_SIMULATE_BLOCKS
216 )]
217 pub rpc_max_simulate_blocks: u64,
218
219 #[arg(
223 long = "rpc.eth-proof-window",
224 default_value_t = constants::DEFAULT_ETH_PROOF_WINDOW,
225 value_parser = RangedU64ValueParser::<u64>::new().range(..=constants::MAX_ETH_PROOF_WINDOW)
226 )]
227 pub rpc_eth_proof_window: u64,
228
229 #[arg(long = "rpc.proof-permits", alias = "rpc-proof-permits", value_name = "COUNT", default_value_t = constants::DEFAULT_PROOF_PERMITS)]
231 pub rpc_proof_permits: usize,
232
233 #[arg(long = "rpc.pending-block", default_value = "full", value_name = "KIND")]
238 pub rpc_pending_block: PendingBlockKind,
239
240 #[arg(long = "rpc.forwarder", alias = "rpc-forwarder", value_name = "FORWARDER")]
242 pub rpc_forwarder: Option<Url>,
243
244 #[arg(long = "builder.disallow", value_name = "PATH", value_parser = reth_cli_util::parsers::read_json_from_file::<HashSet<Address>>)]
247 pub builder_disallow: Option<HashSet<Address>>,
248
249 #[command(flatten)]
251 pub rpc_state_cache: RpcStateCacheArgs,
252
253 #[command(flatten)]
255 pub gas_price_oracle: GasPriceOracleArgs,
256
257 #[arg(
259 long = "rpc.send-raw-transaction-sync-timeout",
260 value_name = "SECONDS",
261 default_value = "30s",
262 value_parser = parse_duration_from_secs_or_ms,
263 )]
264 pub rpc_send_raw_transaction_sync_timeout: Duration,
265}
266
267impl RpcServerArgs {
268 pub const fn with_http(mut self) -> Self {
270 self.http = true;
271 self
272 }
273
274 pub fn with_http_api(mut self, http_api: RpcModuleSelection) -> Self {
276 self.http_api = Some(http_api);
277 self
278 }
279
280 pub const fn with_ws(mut self) -> Self {
282 self.ws = true;
283 self
284 }
285
286 pub fn with_ws_api(mut self, ws_api: RpcModuleSelection) -> Self {
288 self.ws_api = Some(ws_api);
289 self
290 }
291
292 pub const fn with_auth_ipc(mut self) -> Self {
294 self.auth_ipc = true;
295 self
296 }
297
298 pub fn with_api(self, api: RpcModuleSelection) -> Self {
302 self.with_http_api(api.clone()).with_ws_api(api)
303 }
304
305 pub fn adjust_instance_ports(&mut self, instance: Option<u16>) {
320 if let Some(instance) = instance {
321 debug_assert_ne!(instance, 0, "instance must be non-zero");
322 self.auth_port += instance * 100 - 100;
324 self.http_port -= instance - 1;
326 self.ws_port += instance * 2 - 2;
328 self.ipcpath = format!("{}-{}", self.ipcpath, instance);
330 }
331 }
332
333 pub const fn with_http_unused_port(mut self) -> Self {
336 self.http_port = 0;
337 self
338 }
339
340 pub const fn with_ws_unused_port(mut self) -> Self {
343 self.ws_port = 0;
344 self
345 }
346
347 pub const fn with_auth_unused_port(mut self) -> Self {
350 self.auth_port = 0;
351 self
352 }
353
354 pub fn with_ipc_random_path(mut self) -> Self {
357 let random_string: String =
358 rand::rng().sample_iter(rand::distr::Alphanumeric).take(8).map(char::from).collect();
359 self.ipcpath = format!("{}-{}", self.ipcpath, random_string);
360 self
361 }
362
363 pub fn with_unused_ports(mut self) -> Self {
366 self = self.with_http_unused_port();
367 self = self.with_ws_unused_port();
368 self = self.with_auth_unused_port();
369 self = self.with_ipc_random_path();
370 self
371 }
372
373 pub fn apply<F>(self, f: F) -> Self
375 where
376 F: FnOnce(Self) -> Self,
377 {
378 f(self)
379 }
380
381 pub const fn with_send_raw_transaction_sync_timeout(mut self, timeout: Duration) -> Self {
383 self.rpc_send_raw_transaction_sync_timeout = timeout;
384 self
385 }
386}
387
388impl Default for RpcServerArgs {
389 fn default() -> Self {
390 Self {
391 http: false,
392 http_addr: Ipv4Addr::LOCALHOST.into(),
393 http_port: constants::DEFAULT_HTTP_RPC_PORT,
394 http_disable_compression: false,
395 http_api: None,
396 http_corsdomain: None,
397 ws: false,
398 ws_addr: Ipv4Addr::LOCALHOST.into(),
399 ws_port: constants::DEFAULT_WS_RPC_PORT,
400 ws_allowed_origins: None,
401 ws_api: None,
402 ipcdisable: false,
403 ipcpath: constants::DEFAULT_IPC_ENDPOINT.to_string(),
404 ipc_socket_permissions: None,
405 auth_addr: Ipv4Addr::LOCALHOST.into(),
406 auth_port: constants::DEFAULT_AUTH_PORT,
407 auth_jwtsecret: None,
408 auth_ipc: false,
409 auth_ipc_path: constants::DEFAULT_ENGINE_API_IPC_ENDPOINT.to_string(),
410 disable_auth_server: false,
411 rpc_jwtsecret: None,
412 rpc_max_request_size: RPC_DEFAULT_MAX_REQUEST_SIZE_MB.into(),
413 rpc_max_response_size: RPC_DEFAULT_MAX_RESPONSE_SIZE_MB.into(),
414 rpc_max_subscriptions_per_connection: RPC_DEFAULT_MAX_SUBS_PER_CONN.into(),
415 rpc_max_connections: RPC_DEFAULT_MAX_CONNECTIONS.into(),
416 rpc_max_tracing_requests: constants::default_max_tracing_requests(),
417 rpc_max_trace_filter_blocks: constants::DEFAULT_MAX_TRACE_FILTER_BLOCKS,
418 rpc_max_blocks_per_filter: constants::DEFAULT_MAX_BLOCKS_PER_FILTER.into(),
419 rpc_max_logs_per_response: (constants::DEFAULT_MAX_LOGS_PER_RESPONSE as u64).into(),
420 rpc_gas_cap: constants::gas_oracle::RPC_DEFAULT_GAS_CAP,
421 rpc_evm_memory_limit: (1 << 32) - 1,
422 rpc_tx_fee_cap: constants::DEFAULT_TX_FEE_CAP_WEI,
423 rpc_max_simulate_blocks: constants::DEFAULT_MAX_SIMULATE_BLOCKS,
424 rpc_eth_proof_window: constants::DEFAULT_ETH_PROOF_WINDOW,
425 rpc_pending_block: PendingBlockKind::Full,
426 gas_price_oracle: GasPriceOracleArgs::default(),
427 rpc_state_cache: RpcStateCacheArgs::default(),
428 rpc_proof_permits: constants::DEFAULT_PROOF_PERMITS,
429 rpc_forwarder: None,
430 builder_disallow: Default::default(),
431 rpc_send_raw_transaction_sync_timeout:
432 constants::RPC_DEFAULT_SEND_RAW_TX_SYNC_TIMEOUT_SECS,
433 }
434 }
435}
436
437#[derive(Clone, Debug, Default)]
439#[non_exhaustive]
440struct RpcModuleSelectionValueParser;
441
442impl TypedValueParser for RpcModuleSelectionValueParser {
443 type Value = RpcModuleSelection;
444
445 fn parse_ref(
446 &self,
447 _cmd: &Command,
448 _arg: Option<&Arg>,
449 value: &OsStr,
450 ) -> Result<Self::Value, clap::Error> {
451 let val =
452 value.to_str().ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8))?;
453 Ok(val
455 .parse::<RpcModuleSelection>()
456 .expect("RpcModuleSelection parsing cannot fail with Other variant"))
457 }
458
459 fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
460 let values = RethRpcModule::standard_variant_names().map(PossibleValue::new);
462 Some(Box::new(values))
463 }
464}
465
466#[cfg(test)]
467mod tests {
468 use super::*;
469 use clap::{Args, Parser};
470
471 #[derive(Parser)]
473 struct CommandParser<T: Args> {
474 #[command(flatten)]
475 args: T,
476 }
477
478 #[test]
479 fn test_rpc_server_args_parser() {
480 let args =
481 CommandParser::<RpcServerArgs>::parse_from(["reth", "--http.api", "eth,admin,debug"])
482 .args;
483
484 let apis = args.http_api.unwrap();
485 let expected = RpcModuleSelection::try_from_selection(["eth", "admin", "debug"]).unwrap();
486
487 assert_eq!(apis, expected);
488 }
489
490 #[test]
491 fn test_rpc_server_eth_call_bundle_args() {
492 let args =
493 CommandParser::<RpcServerArgs>::parse_from(["reth", "--http.api", "eth,admin,debug"])
494 .args;
495
496 let apis = args.http_api.unwrap();
497 let expected = RpcModuleSelection::try_from_selection(["eth", "admin", "debug"]).unwrap();
498
499 assert_eq!(apis, expected);
500 }
501
502 #[test]
503 fn test_rpc_server_args_parser_none() {
504 let args = CommandParser::<RpcServerArgs>::parse_from(["reth", "--http.api", "none"]).args;
505 let apis = args.http_api.unwrap();
506 let expected = RpcModuleSelection::Selection(Default::default());
507 assert_eq!(apis, expected);
508 }
509
510 #[test]
511 fn rpc_server_args_default_sanity_test() {
512 let default_args = RpcServerArgs::default();
513 let args = CommandParser::<RpcServerArgs>::parse_from(["reth"]).args;
514
515 assert_eq!(args, default_args);
516 }
517
518 #[test]
519 fn test_rpc_tx_fee_cap_parse_integer() {
520 let args = CommandParser::<RpcServerArgs>::parse_from(["reth", "--rpc.txfeecap", "2"]).args;
521 let expected = 2_000_000_000_000_000_000u128; assert_eq!(args.rpc_tx_fee_cap, expected);
523 }
524
525 #[test]
526 fn test_rpc_tx_fee_cap_parse_decimal() {
527 let args =
528 CommandParser::<RpcServerArgs>::parse_from(["reth", "--rpc.txfeecap", "1.5"]).args;
529 let expected = 1_500_000_000_000_000_000u128; assert_eq!(args.rpc_tx_fee_cap, expected);
531 }
532
533 #[test]
534 fn test_rpc_tx_fee_cap_parse_zero() {
535 let args = CommandParser::<RpcServerArgs>::parse_from(["reth", "--rpc.txfeecap", "0"]).args;
536 assert_eq!(args.rpc_tx_fee_cap, 0); }
538
539 #[test]
540 fn test_rpc_tx_fee_cap_parse_none() {
541 let args = CommandParser::<RpcServerArgs>::parse_from(["reth"]).args;
542 let expected = 1_000_000_000_000_000_000u128;
543 assert_eq!(args.rpc_tx_fee_cap, expected); }
545}