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