1use jsonrpsee::server::ServerConfigBuilder;
2use reth_node_core::{args::RpcServerArgs, utils::get_or_create_jwt_secret_from_path};
3use reth_rpc::ValidationApiConfig;
4use reth_rpc_eth_types::{EthConfig, EthStateCacheConfig, GasPriceOracleConfig};
5use reth_rpc_layer::{JwtError, JwtSecret};
6use reth_rpc_server_types::RpcModuleSelection;
7use std::{net::SocketAddr, path::PathBuf};
8use tower::layer::util::Identity;
9use tracing::{debug, warn};
10
11use crate::{
12 auth::AuthServerConfig, error::RpcError, IpcServerBuilder, RpcModuleConfig, RpcServerConfig,
13 TransportRpcModuleConfig,
14};
15
16pub trait RethRpcServerConfig {
21 fn is_ipc_enabled(&self) -> bool;
23
24 fn ipc_path(&self) -> &str;
26
27 fn eth_config(&self) -> EthConfig;
29
30 fn flashbots_config(&self) -> ValidationApiConfig;
32
33 fn state_cache_config(&self) -> EthStateCacheConfig;
35
36 fn rpc_max_request_size_bytes(&self) -> u32;
38
39 fn rpc_max_response_size_bytes(&self) -> u32;
41
42 fn gas_price_oracle_config(&self) -> GasPriceOracleConfig;
44
45 fn transport_rpc_module_config(&self) -> TransportRpcModuleConfig;
50
51 fn http_ws_server_builder(&self) -> ServerConfigBuilder;
53
54 fn ipc_server_builder(&self) -> IpcServerBuilder<Identity, Identity>;
56
57 fn rpc_server_config(&self) -> RpcServerConfig;
59
60 fn rpc_metrics_enabled(&self) -> bool;
62
63 fn auth_server_config(&self, jwt_secret: JwtSecret) -> Result<AuthServerConfig, RpcError>;
65
66 fn auth_jwt_secret(&self, default_jwt_path: PathBuf) -> Result<JwtSecret, JwtError>;
80
81 fn rpc_secret_key(&self) -> Option<JwtSecret>;
85}
86
87impl RethRpcServerConfig for RpcServerArgs {
88 fn is_ipc_enabled(&self) -> bool {
89 !self.ipcdisable
91 }
92
93 fn ipc_path(&self) -> &str {
94 self.ipcpath.as_str()
95 }
96
97 fn eth_config(&self) -> EthConfig {
98 EthConfig::default()
99 .max_tracing_requests(self.rpc_max_tracing_requests)
100 .max_blocking_io_requests(self.rpc_max_blocking_io_requests)
101 .max_trace_filter_blocks(self.rpc_max_trace_filter_blocks)
102 .max_blocks_per_filter(self.rpc_max_blocks_per_filter.unwrap_or_max())
103 .max_logs_per_response(self.rpc_max_logs_per_response.unwrap_or_max() as usize)
104 .eth_proof_window(self.rpc_eth_proof_window)
105 .rpc_gas_cap(self.rpc_gas_cap)
106 .rpc_max_simulate_blocks(self.rpc_max_simulate_blocks)
107 .compute_state_root_for_eth_simulate(self.rpc_compute_state_root_for_eth_simulate)
108 .state_cache(self.state_cache_config())
109 .gpo_config(self.gas_price_oracle_config())
110 .proof_permits(self.rpc_proof_permits)
111 .pending_block_kind(self.rpc_pending_block)
112 .raw_tx_forwarder(self.rpc_forwarder.clone())
113 .rpc_evm_memory_limit(self.rpc_evm_memory_limit)
114 .force_blob_sidecar_upcasting(self.rpc_force_blob_sidecar_upcasting)
115 }
116
117 fn flashbots_config(&self) -> ValidationApiConfig {
118 ValidationApiConfig {
119 disallow: self.builder_disallow.clone().unwrap_or_default(),
120 validation_window: self.rpc_eth_proof_window,
121 }
122 }
123
124 fn state_cache_config(&self) -> EthStateCacheConfig {
125 EthStateCacheConfig {
126 max_blocks: self.rpc_state_cache.max_blocks,
127 max_receipts: self.rpc_state_cache.max_receipts,
128 max_bals: self.rpc_state_cache.max_bals,
129 max_blocks_bytes: self.rpc_state_cache.max_blocks_bytes,
130 max_receipts_bytes: self.rpc_state_cache.max_receipts_bytes,
131 max_bals_bytes: self.rpc_state_cache.max_bals_bytes,
132 idle_timeout: self.rpc_state_cache.idle_timeout,
133 cache_computed_bals: self.rpc_state_cache.cache_computed_bals,
134 prewarm_bals: self.rpc_state_cache.prewarm_bals,
135 max_concurrent_db_requests: self.rpc_state_cache.max_concurrent_db_requests,
136 max_cached_tx_hashes: self.rpc_state_cache.max_cached_tx_hashes,
137 }
138 }
139
140 fn rpc_max_request_size_bytes(&self) -> u32 {
141 self.rpc_max_request_size.get().saturating_mul(1024 * 1024)
142 }
143
144 fn rpc_max_response_size_bytes(&self) -> u32 {
145 self.rpc_max_response_size.get().saturating_mul(1024 * 1024)
146 }
147
148 fn gas_price_oracle_config(&self) -> GasPriceOracleConfig {
149 self.gas_price_oracle.gas_price_oracle_config()
150 }
151
152 fn transport_rpc_module_config(&self) -> TransportRpcModuleConfig {
153 let mut config = TransportRpcModuleConfig::default()
154 .with_config(RpcModuleConfig::new(self.eth_config()));
155
156 if self.http {
157 config = config.with_http(
158 self.http_api
159 .clone()
160 .unwrap_or_else(|| RpcModuleSelection::standard_modules().into()),
161 );
162 }
163
164 if self.ws {
165 config = config.with_ws(
166 self.ws_api
167 .clone()
168 .unwrap_or_else(|| RpcModuleSelection::standard_modules().into()),
169 );
170 }
171
172 if self.is_ipc_enabled() {
173 config = config.with_ipc(RpcModuleSelection::default_ipc_modules());
174 }
175
176 config
177 }
178
179 fn http_ws_server_builder(&self) -> ServerConfigBuilder {
180 ServerConfigBuilder::new()
181 .max_connections(self.rpc_max_connections.get())
182 .max_request_body_size(self.rpc_max_request_size_bytes())
183 .max_response_body_size(self.rpc_max_response_size_bytes())
184 .max_subscriptions_per_connection(self.rpc_max_subscriptions_per_connection.get())
185 }
186
187 fn ipc_server_builder(&self) -> IpcServerBuilder<Identity, Identity> {
188 IpcServerBuilder::default()
189 .max_subscriptions_per_connection(self.rpc_max_subscriptions_per_connection.get())
190 .max_request_body_size(self.rpc_max_request_size_bytes())
191 .max_response_body_size(self.rpc_max_response_size_bytes())
192 .max_connections(self.rpc_max_connections.get())
193 .set_ipc_socket_permissions(self.ipc_socket_permissions.clone())
194 }
195
196 fn rpc_server_config(&self) -> RpcServerConfig {
197 let mut config = RpcServerConfig::default()
198 .with_jwt_secret(self.rpc_secret_key())
199 .with_rpc_metrics_enabled(self.rpc_metrics_enabled());
200
201 if self.http_api.is_some() && !self.http {
202 warn!(
203 target: "reth::cli",
204 "The --http.api flag is set but --http is not enabled. HTTP RPC API will not be exposed."
205 );
206 }
207
208 if self.ws_api.is_some() && !self.ws {
209 warn!(
210 target: "reth::cli",
211 "The --ws.api flag is set but --ws is not enabled. WS RPC API will not be exposed."
212 );
213 }
214
215 if self.http {
216 let socket_address = SocketAddr::new(self.http_addr, self.http_port);
217 config = config
218 .with_http_address(socket_address)
219 .with_http(self.http_ws_server_builder())
220 .with_http_cors(self.http_corsdomain.clone())
221 .with_http_disable_compression(self.http_disable_compression)
222 .with_http_compression_algorithms(self.http_compression_algorithms.clone())
223 .with_http_decompression(
224 self.http_decompression_algorithms.clone(),
225 self.rpc_max_request_size_bytes(),
226 );
227 }
228
229 if self.ws {
230 let socket_address = SocketAddr::new(self.ws_addr, self.ws_port);
231 config = config
233 .with_ws_address(socket_address)
234 .with_ws(self.http_ws_server_builder())
235 .with_ws_cors(self.ws_allowed_origins.clone());
236 }
237
238 if self.is_ipc_enabled() {
239 config =
240 config.with_ipc(self.ipc_server_builder()).with_ipc_endpoint(self.ipcpath.clone());
241 }
242
243 config
244 }
245
246 fn rpc_metrics_enabled(&self) -> bool {
247 !self.rpc_disable_metrics
248 }
249
250 fn auth_server_config(&self, jwt_secret: JwtSecret) -> Result<AuthServerConfig, RpcError> {
251 let address = SocketAddr::new(self.auth_addr, self.auth_port);
252
253 let mut builder = AuthServerConfig::builder(jwt_secret).socket_addr(address);
254 if self.auth_ipc {
255 builder = builder
256 .ipc_endpoint(self.auth_ipc_path.clone())
257 .with_ipc_config(self.ipc_server_builder());
258 }
259 Ok(builder.build())
260 }
261
262 fn auth_jwt_secret(&self, default_jwt_path: PathBuf) -> Result<JwtSecret, JwtError> {
263 if let Some(secret) = self.auth_jwtsecret_hex {
264 debug!(target: "reth::cli", "Using JWT auth secret from hex");
265 Ok(secret)
266 } else {
267 match self.auth_jwtsecret.as_ref() {
268 Some(fpath) => {
269 debug!(target: "reth::cli", user_path=?fpath, "Reading JWT auth secret file");
270 JwtSecret::from_file(fpath)
271 }
272 None => get_or_create_jwt_secret_from_path(&default_jwt_path),
273 }
274 }
275 }
276
277 fn rpc_secret_key(&self) -> Option<JwtSecret> {
278 self.rpc_jwtsecret
279 }
280}
281
282#[cfg(test)]
283mod tests {
284 use clap::{Args, Parser};
285 use reth_node_core::args::RpcServerArgs;
286 use reth_rpc_eth_types::{EthStateCacheConfig, RPC_DEFAULT_GAS_CAP};
287 use reth_rpc_layer::JwtSecret;
288 use reth_rpc_server_types::{constants, RethRpcModule, RpcModuleSelection};
289 use std::{
290 net::{Ipv4Addr, SocketAddr, SocketAddrV4},
291 time::Duration,
292 };
293
294 use crate::config::RethRpcServerConfig;
295
296 #[derive(Parser)]
298 struct CommandParser<T: Args> {
299 #[command(flatten)]
300 args: T,
301 }
302
303 #[test]
304 fn rpc_cache_limits_reach_eth_config() {
305 let args = CommandParser::<RpcServerArgs>::parse_from(["reth"]).args;
306 assert_eq!(args.eth_config().cache, EthStateCacheConfig::default());
307
308 for (timeout, expected_timeout) in
309 [("0s", Duration::ZERO), ("2500ms", Duration::from_millis(2500))]
310 {
311 let args = CommandParser::<RpcServerArgs>::parse_from([
312 "reth",
313 "--rpc-cache.max-blocks",
314 "10",
315 "--rpc-cache.max-receipts",
316 "20",
317 "--rpc-cache.max-bals",
318 "30",
319 "--rpc-cache.max-blocks-bytes",
320 "1024",
321 "--rpc-cache.max-receipts-bytes",
322 "0",
323 "--rpc-cache.max-bals-bytes",
324 "4096",
325 "--rpc-cache.idle-timeout",
326 timeout,
327 ])
328 .args;
329 assert_eq!(
330 args.eth_config().cache,
331 EthStateCacheConfig {
332 max_blocks: 10,
333 max_receipts: 20,
334 max_bals: 30,
335 max_blocks_bytes: 1024,
336 max_receipts_bytes: 0,
337 max_bals_bytes: 4096,
338 idle_timeout: expected_timeout,
339 ..Default::default()
340 }
341 );
342 }
343 }
344
345 #[test]
346 fn test_rpc_gas_cap() {
347 let args = CommandParser::<RpcServerArgs>::parse_from(["reth"]).args;
348 let config = args.eth_config();
349 assert_eq!(config.rpc_gas_cap, u64::from(RPC_DEFAULT_GAS_CAP));
350
351 let args =
352 CommandParser::<RpcServerArgs>::parse_from(["reth", "--rpc.gascap", "1000"]).args;
353 let config = args.eth_config();
354 assert_eq!(config.rpc_gas_cap, 1000);
355
356 let args = CommandParser::<RpcServerArgs>::try_parse_from(["reth", "--rpc.gascap", "0"]);
357 assert!(args.is_err());
358 }
359
360 #[test]
361 fn test_transport_rpc_module_config() {
362 let args = CommandParser::<RpcServerArgs>::parse_from([
363 "reth",
364 "--http.api",
365 "eth,admin,debug",
366 "--http",
367 "--ws",
368 ])
369 .args;
370 let config = args.transport_rpc_module_config();
371 let expected = [RethRpcModule::Eth, RethRpcModule::Admin, RethRpcModule::Debug];
372 assert_eq!(config.http().cloned().unwrap().into_selection(), expected.into());
373 assert_eq!(
374 config.ws().cloned().unwrap().into_selection(),
375 RpcModuleSelection::standard_modules()
376 );
377 }
378
379 #[test]
380 fn test_testing_namespace_requires_explicit_selection() {
381 let args = CommandParser::<RpcServerArgs>::parse_from([
382 "reth",
383 "--http",
384 "--http.api",
385 "all",
386 "--ws",
387 "--ws.api",
388 "all",
389 ])
390 .args;
391 let config = args.transport_rpc_module_config();
392 assert!(!config.contains_http(&RethRpcModule::Testing));
393 assert!(!config.contains_ws(&RethRpcModule::Testing));
394 assert!(!config.contains_ipc(&RethRpcModule::Testing));
395
396 let args = CommandParser::<RpcServerArgs>::parse_from([
397 "reth",
398 "--http",
399 "--http.api",
400 "eth,testing",
401 "--ws",
402 "--ws.api",
403 "eth,testing",
404 ])
405 .args;
406 let config = args.transport_rpc_module_config();
407 assert!(config.contains_http(&RethRpcModule::Testing));
408 assert!(config.contains_ws(&RethRpcModule::Testing));
409 }
410
411 #[test]
412 fn test_transport_rpc_module_trim_config() {
413 let args = CommandParser::<RpcServerArgs>::parse_from([
414 "reth",
415 "--http.api",
416 " eth, admin, debug",
417 "--http",
418 "--ws",
419 ])
420 .args;
421 let config = args.transport_rpc_module_config();
422 let expected = [RethRpcModule::Eth, RethRpcModule::Admin, RethRpcModule::Debug];
423 assert_eq!(config.http().cloned().unwrap().into_selection(), expected.into());
424 assert_eq!(
425 config.ws().cloned().unwrap().into_selection(),
426 RpcModuleSelection::standard_modules()
427 );
428 }
429
430 #[test]
431 fn test_unique_rpc_modules() {
432 let args = CommandParser::<RpcServerArgs>::parse_from([
433 "reth",
434 "--http.api",
435 " eth, admin, debug, eth,admin",
436 "--http",
437 "--ws",
438 ])
439 .args;
440 let config = args.transport_rpc_module_config();
441 let expected = [RethRpcModule::Eth, RethRpcModule::Admin, RethRpcModule::Debug];
442 assert_eq!(config.http().cloned().unwrap().into_selection(), expected.into());
443 assert_eq!(
444 config.ws().cloned().unwrap().into_selection(),
445 RpcModuleSelection::standard_modules()
446 );
447 }
448
449 #[test]
450 fn test_rpc_server_config() {
451 let args = CommandParser::<RpcServerArgs>::parse_from([
452 "reth",
453 "--http.api",
454 "eth,admin,debug",
455 "--http",
456 "--ws",
457 "--ws.addr",
458 "127.0.0.1",
459 "--ws.port",
460 "8888",
461 ])
462 .args;
463 let config = args.rpc_server_config();
464 assert_eq!(
465 config.http_address().unwrap(),
466 SocketAddr::V4(SocketAddrV4::new(
467 Ipv4Addr::LOCALHOST,
468 constants::DEFAULT_HTTP_RPC_PORT
469 ))
470 );
471 assert_eq!(
472 config.ws_address().unwrap(),
473 SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 8888))
474 );
475 assert_eq!(config.ipc_endpoint().unwrap(), constants::DEFAULT_IPC_ENDPOINT);
476 assert!(config.rpc_metrics_enabled());
477 }
478
479 #[test]
480 fn test_rpc_server_config_disable_metrics() {
481 let args =
482 CommandParser::<RpcServerArgs>::parse_from(["reth", "--rpc.disable-metrics"]).args;
483 let config = args.rpc_server_config();
484 assert!(!config.rpc_metrics_enabled());
485 }
486
487 #[test]
488 fn test_zero_filter_limits() {
489 let args = CommandParser::<RpcServerArgs>::parse_from([
490 "reth",
491 "--rpc-max-blocks-per-filter",
492 "0",
493 "--rpc-max-logs-per-response",
494 "0",
495 ])
496 .args;
497
498 let config = args.eth_config().filter_config();
499 assert_eq!(config.max_blocks_per_filter, Some(u64::MAX));
500 assert_eq!(config.max_logs_per_response, Some(usize::MAX));
501 }
502
503 #[test]
504 fn test_custom_filter_limits() {
505 let args = CommandParser::<RpcServerArgs>::parse_from([
506 "reth",
507 "--rpc-max-blocks-per-filter",
508 "100",
509 "--rpc-max-logs-per-response",
510 "200",
511 ])
512 .args;
513
514 let config = args.eth_config().filter_config();
515 assert_eq!(config.max_blocks_per_filter, Some(100));
516 assert_eq!(config.max_logs_per_response, Some(200));
517 }
518
519 #[test]
520 fn test_auth_jwt_secret_from_hex() {
521 let hex = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
522 let args =
523 CommandParser::<RpcServerArgs>::parse_from(["reth", "--authrpc.jwtsecret-hex", hex])
524 .args;
525
526 let secret = args.auth_jwt_secret(std::env::temp_dir().join("unused.jwt")).unwrap();
527 assert_eq!(secret, JwtSecret::from_hex(hex).unwrap());
528 }
529}