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_rpc_server_types::{constants, RethRpcModule, RpcModuleSelection};
18
19use crate::args::{
20 types::{MaxU32, ZeroAsNoneU64},
21 GasPriceOracleArgs, RpcStateCacheArgs,
22};
23
24pub(crate) const RPC_DEFAULT_MAX_SUBS_PER_CONN: u32 = 1024;
26
27pub(crate) const RPC_DEFAULT_MAX_REQUEST_SIZE_MB: u32 = 15;
29
30pub(crate) const RPC_DEFAULT_MAX_RESPONSE_SIZE_MB: u32 = 160;
34
35pub(crate) const RPC_DEFAULT_MAX_CONNECTIONS: u32 = 500;
37
38#[derive(Debug, Clone, Args, PartialEq, Eq)]
40#[command(next_help_heading = "RPC")]
41pub struct RpcServerArgs {
42 #[arg(long, default_value_if("dev", "true", "true"))]
44 pub http: bool,
45
46 #[arg(long = "http.addr", default_value_t = IpAddr::V4(Ipv4Addr::LOCALHOST))]
48 pub http_addr: IpAddr,
49
50 #[arg(long = "http.port", default_value_t = constants::DEFAULT_HTTP_RPC_PORT)]
52 pub http_port: u16,
53
54 #[arg(long = "http.api", value_parser = RpcModuleSelectionValueParser::default())]
56 pub http_api: Option<RpcModuleSelection>,
57
58 #[arg(long = "http.corsdomain")]
60 pub http_corsdomain: Option<String>,
61
62 #[arg(long)]
64 pub ws: bool,
65
66 #[arg(long = "ws.addr", default_value_t = IpAddr::V4(Ipv4Addr::LOCALHOST))]
68 pub ws_addr: IpAddr,
69
70 #[arg(long = "ws.port", default_value_t = constants::DEFAULT_WS_RPC_PORT)]
72 pub ws_port: u16,
73
74 #[arg(id = "ws.origins", long = "ws.origins")]
76 pub ws_allowed_origins: Option<String>,
77
78 #[arg(long = "ws.api", value_parser = RpcModuleSelectionValueParser::default())]
80 pub ws_api: Option<RpcModuleSelection>,
81
82 #[arg(long)]
84 pub ipcdisable: bool,
85
86 #[arg(long, default_value_t = constants::DEFAULT_IPC_ENDPOINT.to_string())]
88 pub ipcpath: String,
89
90 #[arg(long = "authrpc.addr", default_value_t = IpAddr::V4(Ipv4Addr::LOCALHOST))]
92 pub auth_addr: IpAddr,
93
94 #[arg(long = "authrpc.port", default_value_t = constants::DEFAULT_AUTH_PORT)]
96 pub auth_port: u16,
97
98 #[arg(long = "authrpc.jwtsecret", value_name = "PATH", global = true, required = false)]
105 pub auth_jwtsecret: Option<PathBuf>,
106
107 #[arg(long)]
109 pub auth_ipc: bool,
110
111 #[arg(long = "auth-ipc.path", default_value_t = constants::DEFAULT_ENGINE_API_IPC_ENDPOINT.to_string())]
113 pub auth_ipc_path: String,
114
115 #[arg(long = "rpc.jwtsecret", value_name = "HEX", global = true, required = false)]
121 pub rpc_jwtsecret: Option<JwtSecret>,
122
123 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[command(flatten)]
199 pub rpc_state_cache: RpcStateCacheArgs,
200
201 #[command(flatten)]
203 pub gas_price_oracle: GasPriceOracleArgs,
204}
205
206impl RpcServerArgs {
207 pub const fn with_http(mut self) -> Self {
209 self.http = true;
210 self
211 }
212
213 pub fn with_http_api(mut self, http_api: RpcModuleSelection) -> Self {
215 self.http_api = Some(http_api);
216 self
217 }
218
219 pub const fn with_ws(mut self) -> Self {
221 self.ws = true;
222 self
223 }
224
225 pub const fn with_auth_ipc(mut self) -> Self {
227 self.auth_ipc = true;
228 self
229 }
230
231 pub fn adjust_instance_ports(&mut self, instance: u16) {
246 debug_assert_ne!(instance, 0, "instance must be non-zero");
247 self.auth_port += instance * 100 - 100;
249 self.http_port -= instance - 1;
251 self.ws_port += instance * 2 - 2;
253
254 if instance > 1 {
256 self.ipcpath = format!("{}-{}", self.ipcpath, instance);
257 }
258 }
259
260 pub const fn with_http_unused_port(mut self) -> Self {
263 self.http_port = 0;
264 self
265 }
266
267 pub const fn with_ws_unused_port(mut self) -> Self {
270 self.ws_port = 0;
271 self
272 }
273
274 pub const fn with_auth_unused_port(mut self) -> Self {
277 self.auth_port = 0;
278 self
279 }
280
281 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 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#[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 #[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}