1use 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
27pub(crate) const RPC_DEFAULT_MAX_SUBS_PER_CONN: u32 = 1024;
29
30pub(crate) const RPC_DEFAULT_MAX_REQUEST_SIZE_MB: u32 = 15;
32
33pub(crate) const RPC_DEFAULT_MAX_RESPONSE_SIZE_MB: u32 = 160;
37
38pub(crate) const RPC_DEFAULT_MAX_CONNECTIONS: u32 = 500;
40
41#[derive(Debug, Clone, Args, PartialEq, Eq)]
43#[command(next_help_heading = "RPC")]
44pub struct RpcServerArgs {
45 #[arg(long, default_value_if("dev", "true", "true"))]
47 pub http: bool,
48
49 #[arg(long = "http.addr", default_value_t = IpAddr::V4(Ipv4Addr::LOCALHOST))]
51 pub http_addr: IpAddr,
52
53 #[arg(long = "http.port", default_value_t = constants::DEFAULT_HTTP_RPC_PORT)]
55 pub http_port: u16,
56
57 #[arg(long = "http.disable-compression", default_value_t = false)]
59 pub http_disable_compression: bool,
60
61 #[arg(long = "http.api", value_parser = RpcModuleSelectionValueParser::default())]
63 pub http_api: Option<RpcModuleSelection>,
64
65 #[arg(long = "http.corsdomain")]
67 pub http_corsdomain: Option<String>,
68
69 #[arg(long)]
71 pub ws: bool,
72
73 #[arg(long = "ws.addr", default_value_t = IpAddr::V4(Ipv4Addr::LOCALHOST))]
75 pub ws_addr: IpAddr,
76
77 #[arg(long = "ws.port", default_value_t = constants::DEFAULT_WS_RPC_PORT)]
79 pub ws_port: u16,
80
81 #[arg(id = "ws.origins", long = "ws.origins", alias = "ws.corsdomain")]
83 pub ws_allowed_origins: Option<String>,
84
85 #[arg(long = "ws.api", value_parser = RpcModuleSelectionValueParser::default())]
87 pub ws_api: Option<RpcModuleSelection>,
88
89 #[arg(long)]
91 pub ipcdisable: bool,
92
93 #[arg(long, default_value_t = constants::DEFAULT_IPC_ENDPOINT.to_string())]
95 pub ipcpath: String,
96
97 #[arg(long = "authrpc.addr", default_value_t = IpAddr::V4(Ipv4Addr::LOCALHOST))]
99 pub auth_addr: IpAddr,
100
101 #[arg(long = "authrpc.port", default_value_t = constants::DEFAULT_AUTH_PORT)]
103 pub auth_port: u16,
104
105 #[arg(long = "authrpc.jwtsecret", value_name = "PATH", global = true, required = false)]
112 pub auth_jwtsecret: Option<PathBuf>,
113
114 #[arg(long)]
116 pub auth_ipc: bool,
117
118 #[arg(long = "auth-ipc.path", default_value_t = constants::DEFAULT_ENGINE_API_IPC_ENDPOINT.to_string())]
120 pub auth_ipc_path: String,
121
122 #[arg(long = "rpc.jwtsecret", value_name = "HEX", global = true, required = false)]
128 pub rpc_jwtsecret: Option<JwtSecret>,
129
130 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[command(flatten)]
216 pub rpc_state_cache: RpcStateCacheArgs,
217
218 #[command(flatten)]
220 pub gas_price_oracle: GasPriceOracleArgs,
221}
222
223impl RpcServerArgs {
224 pub const fn with_http(mut self) -> Self {
226 self.http = true;
227 self
228 }
229
230 pub fn with_http_api(mut self, http_api: RpcModuleSelection) -> Self {
232 self.http_api = Some(http_api);
233 self
234 }
235
236 pub const fn with_ws(mut self) -> Self {
238 self.ws = true;
239 self
240 }
241
242 pub const fn with_auth_ipc(mut self) -> Self {
244 self.auth_ipc = true;
245 self
246 }
247
248 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 self.auth_port += instance * 100 - 100;
267 self.http_port -= instance - 1;
269 self.ws_port += instance * 2 - 2;
271 self.ipcpath = format!("{}-{}", self.ipcpath, instance);
273 }
274 }
275
276 pub const fn with_http_unused_port(mut self) -> Self {
279 self.http_port = 0;
280 self
281 }
282
283 pub const fn with_ws_unused_port(mut self) -> Self {
286 self.ws_port = 0;
287 self
288 }
289
290 pub const fn with_auth_unused_port(mut self) -> Self {
293 self.auth_port = 0;
294 self
295 }
296
297 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 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#[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 #[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; 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; 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); }
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); }
470}