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_concurrent_db_requests: self.rpc_state_cache.max_concurrent_db_requests,
130 max_cached_tx_hashes: self.rpc_state_cache.max_cached_tx_hashes,
131 }
132 }
133
134 fn rpc_max_request_size_bytes(&self) -> u32 {
135 self.rpc_max_request_size.get().saturating_mul(1024 * 1024)
136 }
137
138 fn rpc_max_response_size_bytes(&self) -> u32 {
139 self.rpc_max_response_size.get().saturating_mul(1024 * 1024)
140 }
141
142 fn gas_price_oracle_config(&self) -> GasPriceOracleConfig {
143 self.gas_price_oracle.gas_price_oracle_config()
144 }
145
146 fn transport_rpc_module_config(&self) -> TransportRpcModuleConfig {
147 let mut config = TransportRpcModuleConfig::default()
148 .with_config(RpcModuleConfig::new(self.eth_config()));
149
150 if self.http {
151 config = config.with_http(
152 self.http_api
153 .clone()
154 .unwrap_or_else(|| RpcModuleSelection::standard_modules().into()),
155 );
156 }
157
158 if self.ws {
159 config = config.with_ws(
160 self.ws_api
161 .clone()
162 .unwrap_or_else(|| RpcModuleSelection::standard_modules().into()),
163 );
164 }
165
166 if self.is_ipc_enabled() {
167 config = config.with_ipc(RpcModuleSelection::default_ipc_modules());
168 }
169
170 config
171 }
172
173 fn http_ws_server_builder(&self) -> ServerConfigBuilder {
174 ServerConfigBuilder::new()
175 .max_connections(self.rpc_max_connections.get())
176 .max_request_body_size(self.rpc_max_request_size_bytes())
177 .max_response_body_size(self.rpc_max_response_size_bytes())
178 .max_subscriptions_per_connection(self.rpc_max_subscriptions_per_connection.get())
179 }
180
181 fn ipc_server_builder(&self) -> IpcServerBuilder<Identity, Identity> {
182 IpcServerBuilder::default()
183 .max_subscriptions_per_connection(self.rpc_max_subscriptions_per_connection.get())
184 .max_request_body_size(self.rpc_max_request_size_bytes())
185 .max_response_body_size(self.rpc_max_response_size_bytes())
186 .max_connections(self.rpc_max_connections.get())
187 .set_ipc_socket_permissions(self.ipc_socket_permissions.clone())
188 }
189
190 fn rpc_server_config(&self) -> RpcServerConfig {
191 let mut config = RpcServerConfig::default()
192 .with_jwt_secret(self.rpc_secret_key())
193 .with_rpc_metrics_enabled(self.rpc_metrics_enabled());
194
195 if self.http_api.is_some() && !self.http {
196 warn!(
197 target: "reth::cli",
198 "The --http.api flag is set but --http is not enabled. HTTP RPC API will not be exposed."
199 );
200 }
201
202 if self.ws_api.is_some() && !self.ws {
203 warn!(
204 target: "reth::cli",
205 "The --ws.api flag is set but --ws is not enabled. WS RPC API will not be exposed."
206 );
207 }
208
209 if self.http {
210 let socket_address = SocketAddr::new(self.http_addr, self.http_port);
211 config = config
212 .with_http_address(socket_address)
213 .with_http(self.http_ws_server_builder())
214 .with_http_cors(self.http_corsdomain.clone())
215 .with_http_disable_compression(self.http_disable_compression)
216 .with_http_compression_algorithms(self.http_compression_algorithms.clone())
217 .with_http_decompression(
218 self.http_decompression_algorithms.clone(),
219 self.rpc_max_request_size_bytes(),
220 );
221 }
222
223 if self.ws {
224 let socket_address = SocketAddr::new(self.ws_addr, self.ws_port);
225 config = config
227 .with_ws_address(socket_address)
228 .with_ws(self.http_ws_server_builder())
229 .with_ws_cors(self.ws_allowed_origins.clone());
230 }
231
232 if self.is_ipc_enabled() {
233 config =
234 config.with_ipc(self.ipc_server_builder()).with_ipc_endpoint(self.ipcpath.clone());
235 }
236
237 config
238 }
239
240 fn rpc_metrics_enabled(&self) -> bool {
241 !self.rpc_disable_metrics
242 }
243
244 fn auth_server_config(&self, jwt_secret: JwtSecret) -> Result<AuthServerConfig, RpcError> {
245 let address = SocketAddr::new(self.auth_addr, self.auth_port);
246
247 let mut builder = AuthServerConfig::builder(jwt_secret).socket_addr(address);
248 if self.auth_ipc {
249 builder = builder
250 .ipc_endpoint(self.auth_ipc_path.clone())
251 .with_ipc_config(self.ipc_server_builder());
252 }
253 Ok(builder.build())
254 }
255
256 fn auth_jwt_secret(&self, default_jwt_path: PathBuf) -> Result<JwtSecret, JwtError> {
257 if let Some(secret) = self.auth_jwtsecret_hex {
258 debug!(target: "reth::cli", "Using JWT auth secret from hex");
259 Ok(secret)
260 } else {
261 match self.auth_jwtsecret.as_ref() {
262 Some(fpath) => {
263 debug!(target: "reth::cli", user_path=?fpath, "Reading JWT auth secret file");
264 JwtSecret::from_file(fpath)
265 }
266 None => get_or_create_jwt_secret_from_path(&default_jwt_path),
267 }
268 }
269 }
270
271 fn rpc_secret_key(&self) -> Option<JwtSecret> {
272 self.rpc_jwtsecret
273 }
274}
275
276#[cfg(test)]
277mod tests {
278 use clap::{Args, Parser};
279 use reth_node_core::args::RpcServerArgs;
280 use reth_rpc_eth_types::RPC_DEFAULT_GAS_CAP;
281 use reth_rpc_layer::JwtSecret;
282 use reth_rpc_server_types::{constants, RethRpcModule, RpcModuleSelection};
283 use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
284
285 use crate::config::RethRpcServerConfig;
286
287 #[derive(Parser)]
289 struct CommandParser<T: Args> {
290 #[command(flatten)]
291 args: T,
292 }
293
294 #[test]
295 fn test_rpc_gas_cap() {
296 let args = CommandParser::<RpcServerArgs>::parse_from(["reth"]).args;
297 let config = args.eth_config();
298 assert_eq!(config.rpc_gas_cap, u64::from(RPC_DEFAULT_GAS_CAP));
299
300 let args =
301 CommandParser::<RpcServerArgs>::parse_from(["reth", "--rpc.gascap", "1000"]).args;
302 let config = args.eth_config();
303 assert_eq!(config.rpc_gas_cap, 1000);
304
305 let args = CommandParser::<RpcServerArgs>::try_parse_from(["reth", "--rpc.gascap", "0"]);
306 assert!(args.is_err());
307 }
308
309 #[test]
310 fn test_transport_rpc_module_config() {
311 let args = CommandParser::<RpcServerArgs>::parse_from([
312 "reth",
313 "--http.api",
314 "eth,admin,debug",
315 "--http",
316 "--ws",
317 ])
318 .args;
319 let config = args.transport_rpc_module_config();
320 let expected = [RethRpcModule::Eth, RethRpcModule::Admin, RethRpcModule::Debug];
321 assert_eq!(config.http().cloned().unwrap().into_selection(), expected.into());
322 assert_eq!(
323 config.ws().cloned().unwrap().into_selection(),
324 RpcModuleSelection::standard_modules()
325 );
326 }
327
328 #[test]
329 fn test_transport_rpc_module_trim_config() {
330 let args = CommandParser::<RpcServerArgs>::parse_from([
331 "reth",
332 "--http.api",
333 " eth, admin, debug",
334 "--http",
335 "--ws",
336 ])
337 .args;
338 let config = args.transport_rpc_module_config();
339 let expected = [RethRpcModule::Eth, RethRpcModule::Admin, RethRpcModule::Debug];
340 assert_eq!(config.http().cloned().unwrap().into_selection(), expected.into());
341 assert_eq!(
342 config.ws().cloned().unwrap().into_selection(),
343 RpcModuleSelection::standard_modules()
344 );
345 }
346
347 #[test]
348 fn test_unique_rpc_modules() {
349 let args = CommandParser::<RpcServerArgs>::parse_from([
350 "reth",
351 "--http.api",
352 " eth, admin, debug, eth,admin",
353 "--http",
354 "--ws",
355 ])
356 .args;
357 let config = args.transport_rpc_module_config();
358 let expected = [RethRpcModule::Eth, RethRpcModule::Admin, RethRpcModule::Debug];
359 assert_eq!(config.http().cloned().unwrap().into_selection(), expected.into());
360 assert_eq!(
361 config.ws().cloned().unwrap().into_selection(),
362 RpcModuleSelection::standard_modules()
363 );
364 }
365
366 #[test]
367 fn test_rpc_server_config() {
368 let args = CommandParser::<RpcServerArgs>::parse_from([
369 "reth",
370 "--http.api",
371 "eth,admin,debug",
372 "--http",
373 "--ws",
374 "--ws.addr",
375 "127.0.0.1",
376 "--ws.port",
377 "8888",
378 ])
379 .args;
380 let config = args.rpc_server_config();
381 assert_eq!(
382 config.http_address().unwrap(),
383 SocketAddr::V4(SocketAddrV4::new(
384 Ipv4Addr::LOCALHOST,
385 constants::DEFAULT_HTTP_RPC_PORT
386 ))
387 );
388 assert_eq!(
389 config.ws_address().unwrap(),
390 SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 8888))
391 );
392 assert_eq!(config.ipc_endpoint().unwrap(), constants::DEFAULT_IPC_ENDPOINT);
393 assert!(config.rpc_metrics_enabled());
394 }
395
396 #[test]
397 fn test_rpc_server_config_disable_metrics() {
398 let args =
399 CommandParser::<RpcServerArgs>::parse_from(["reth", "--rpc.disable-metrics"]).args;
400 let config = args.rpc_server_config();
401 assert!(!config.rpc_metrics_enabled());
402 }
403
404 #[test]
405 fn test_zero_filter_limits() {
406 let args = CommandParser::<RpcServerArgs>::parse_from([
407 "reth",
408 "--rpc-max-blocks-per-filter",
409 "0",
410 "--rpc-max-logs-per-response",
411 "0",
412 ])
413 .args;
414
415 let config = args.eth_config().filter_config();
416 assert_eq!(config.max_blocks_per_filter, Some(u64::MAX));
417 assert_eq!(config.max_logs_per_response, Some(usize::MAX));
418 }
419
420 #[test]
421 fn test_custom_filter_limits() {
422 let args = CommandParser::<RpcServerArgs>::parse_from([
423 "reth",
424 "--rpc-max-blocks-per-filter",
425 "100",
426 "--rpc-max-logs-per-response",
427 "200",
428 ])
429 .args;
430
431 let config = args.eth_config().filter_config();
432 assert_eq!(config.max_blocks_per_filter, Some(100));
433 assert_eq!(config.max_logs_per_response, Some(200));
434 }
435
436 #[test]
437 fn test_auth_jwt_secret_from_hex() {
438 let hex = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
439 let args =
440 CommandParser::<RpcServerArgs>::parse_from(["reth", "--authrpc.jwtsecret-hex", hex])
441 .args;
442
443 let secret = args.auth_jwt_secret(std::env::temp_dir().join("unused.jwt")).unwrap();
444 assert_eq!(secret, JwtSecret::from_hex(hex).unwrap());
445 }
446}