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 auth_server_config(&self, jwt_secret: JwtSecret) -> Result<AuthServerConfig, RpcError>;
62
63 fn auth_jwt_secret(&self, default_jwt_path: PathBuf) -> Result<JwtSecret, JwtError>;
77
78 fn rpc_secret_key(&self) -> Option<JwtSecret>;
82}
83
84impl RethRpcServerConfig for RpcServerArgs {
85 fn is_ipc_enabled(&self) -> bool {
86 !self.ipcdisable
88 }
89
90 fn ipc_path(&self) -> &str {
91 self.ipcpath.as_str()
92 }
93
94 fn eth_config(&self) -> EthConfig {
95 EthConfig::default()
96 .max_tracing_requests(self.rpc_max_tracing_requests)
97 .max_trace_filter_blocks(self.rpc_max_trace_filter_blocks)
98 .max_blocks_per_filter(self.rpc_max_blocks_per_filter.unwrap_or_max())
99 .max_logs_per_response(self.rpc_max_logs_per_response.unwrap_or_max() as usize)
100 .eth_proof_window(self.rpc_eth_proof_window)
101 .rpc_gas_cap(self.rpc_gas_cap)
102 .rpc_max_simulate_blocks(self.rpc_max_simulate_blocks)
103 .state_cache(self.state_cache_config())
104 .gpo_config(self.gas_price_oracle_config())
105 .proof_permits(self.rpc_proof_permits)
106 .pending_block_kind(self.rpc_pending_block)
107 .raw_tx_forwarder(self.rpc_forwarder.clone())
108 }
109
110 fn flashbots_config(&self) -> ValidationApiConfig {
111 ValidationApiConfig {
112 disallow: self.builder_disallow.clone().unwrap_or_default(),
113 validation_window: self.rpc_eth_proof_window,
114 }
115 }
116
117 fn state_cache_config(&self) -> EthStateCacheConfig {
118 EthStateCacheConfig {
119 max_blocks: self.rpc_state_cache.max_blocks,
120 max_receipts: self.rpc_state_cache.max_receipts,
121 max_headers: self.rpc_state_cache.max_headers,
122 max_concurrent_db_requests: self.rpc_state_cache.max_concurrent_db_requests,
123 }
124 }
125
126 fn rpc_max_request_size_bytes(&self) -> u32 {
127 self.rpc_max_request_size.get().saturating_mul(1024 * 1024)
128 }
129
130 fn rpc_max_response_size_bytes(&self) -> u32 {
131 self.rpc_max_response_size.get().saturating_mul(1024 * 1024)
132 }
133
134 fn gas_price_oracle_config(&self) -> GasPriceOracleConfig {
135 self.gas_price_oracle.gas_price_oracle_config()
136 }
137
138 fn transport_rpc_module_config(&self) -> TransportRpcModuleConfig {
139 let mut config = TransportRpcModuleConfig::default()
140 .with_config(RpcModuleConfig::new(self.eth_config(), self.flashbots_config()));
141
142 if self.http {
143 config = config.with_http(
144 self.http_api
145 .clone()
146 .unwrap_or_else(|| RpcModuleSelection::standard_modules().into()),
147 );
148 }
149
150 if self.ws {
151 config = config.with_ws(
152 self.ws_api
153 .clone()
154 .unwrap_or_else(|| RpcModuleSelection::standard_modules().into()),
155 );
156 }
157
158 if self.is_ipc_enabled() {
159 config = config.with_ipc(RpcModuleSelection::default_ipc_modules());
160 }
161
162 config
163 }
164
165 fn http_ws_server_builder(&self) -> ServerConfigBuilder {
166 ServerConfigBuilder::new()
167 .max_connections(self.rpc_max_connections.get())
168 .max_request_body_size(self.rpc_max_request_size_bytes())
169 .max_response_body_size(self.rpc_max_response_size_bytes())
170 .max_subscriptions_per_connection(self.rpc_max_subscriptions_per_connection.get())
171 }
172
173 fn ipc_server_builder(&self) -> IpcServerBuilder<Identity, Identity> {
174 IpcServerBuilder::default()
175 .max_subscriptions_per_connection(self.rpc_max_subscriptions_per_connection.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_connections(self.rpc_max_connections.get())
179 .set_ipc_socket_permissions(self.ipc_socket_permissions.clone())
180 }
181
182 fn rpc_server_config(&self) -> RpcServerConfig {
183 let mut config = RpcServerConfig::default().with_jwt_secret(self.rpc_secret_key());
184
185 if self.http_api.is_some() && !self.http {
186 warn!(
187 target: "reth::cli",
188 "The --http.api flag is set but --http is not enabled. HTTP RPC API will not be exposed."
189 );
190 }
191
192 if self.http {
193 let socket_address = SocketAddr::new(self.http_addr, self.http_port);
194 config = config
195 .with_http_address(socket_address)
196 .with_http(self.http_ws_server_builder())
197 .with_http_cors(self.http_corsdomain.clone())
198 .with_http_disable_compression(self.http_disable_compression);
199 }
200
201 if self.ws {
202 let socket_address = SocketAddr::new(self.ws_addr, self.ws_port);
203 config = config
205 .with_ws_address(socket_address)
206 .with_ws(self.http_ws_server_builder())
207 .with_ws_cors(self.ws_allowed_origins.clone());
208 }
209
210 if self.is_ipc_enabled() {
211 config =
212 config.with_ipc(self.ipc_server_builder()).with_ipc_endpoint(self.ipcpath.clone());
213 }
214
215 config
216 }
217
218 fn auth_server_config(&self, jwt_secret: JwtSecret) -> Result<AuthServerConfig, RpcError> {
219 let address = SocketAddr::new(self.auth_addr, self.auth_port);
220
221 let mut builder = AuthServerConfig::builder(jwt_secret).socket_addr(address);
222 if self.auth_ipc {
223 builder = builder
224 .ipc_endpoint(self.auth_ipc_path.clone())
225 .with_ipc_config(self.ipc_server_builder());
226 }
227 Ok(builder.build())
228 }
229
230 fn auth_jwt_secret(&self, default_jwt_path: PathBuf) -> Result<JwtSecret, JwtError> {
231 match self.auth_jwtsecret.as_ref() {
232 Some(fpath) => {
233 debug!(target: "reth::cli", user_path=?fpath, "Reading JWT auth secret file");
234 JwtSecret::from_file(fpath)
235 }
236 None => get_or_create_jwt_secret_from_path(&default_jwt_path),
237 }
238 }
239
240 fn rpc_secret_key(&self) -> Option<JwtSecret> {
241 self.rpc_jwtsecret
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use clap::{Args, Parser};
248 use reth_node_core::args::RpcServerArgs;
249 use reth_rpc_eth_types::RPC_DEFAULT_GAS_CAP;
250 use reth_rpc_server_types::{constants, RethRpcModule, RpcModuleSelection};
251 use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
252
253 use crate::config::RethRpcServerConfig;
254
255 #[derive(Parser)]
257 struct CommandParser<T: Args> {
258 #[command(flatten)]
259 args: T,
260 }
261
262 #[test]
263 fn test_rpc_gas_cap() {
264 let args = CommandParser::<RpcServerArgs>::parse_from(["reth"]).args;
265 let config = args.eth_config();
266 assert_eq!(config.rpc_gas_cap, u64::from(RPC_DEFAULT_GAS_CAP));
267
268 let args =
269 CommandParser::<RpcServerArgs>::parse_from(["reth", "--rpc.gascap", "1000"]).args;
270 let config = args.eth_config();
271 assert_eq!(config.rpc_gas_cap, 1000);
272
273 let args = CommandParser::<RpcServerArgs>::try_parse_from(["reth", "--rpc.gascap", "0"]);
274 assert!(args.is_err());
275 }
276
277 #[test]
278 fn test_transport_rpc_module_config() {
279 let args = CommandParser::<RpcServerArgs>::parse_from([
280 "reth",
281 "--http.api",
282 "eth,admin,debug",
283 "--http",
284 "--ws",
285 ])
286 .args;
287 let config = args.transport_rpc_module_config();
288 let expected = [RethRpcModule::Eth, RethRpcModule::Admin, RethRpcModule::Debug];
289 assert_eq!(config.http().cloned().unwrap().into_selection(), expected.into());
290 assert_eq!(
291 config.ws().cloned().unwrap().into_selection(),
292 RpcModuleSelection::standard_modules()
293 );
294 }
295
296 #[test]
297 fn test_transport_rpc_module_trim_config() {
298 let args = CommandParser::<RpcServerArgs>::parse_from([
299 "reth",
300 "--http.api",
301 " eth, admin, debug",
302 "--http",
303 "--ws",
304 ])
305 .args;
306 let config = args.transport_rpc_module_config();
307 let expected = [RethRpcModule::Eth, RethRpcModule::Admin, RethRpcModule::Debug];
308 assert_eq!(config.http().cloned().unwrap().into_selection(), expected.into());
309 assert_eq!(
310 config.ws().cloned().unwrap().into_selection(),
311 RpcModuleSelection::standard_modules()
312 );
313 }
314
315 #[test]
316 fn test_unique_rpc_modules() {
317 let args = CommandParser::<RpcServerArgs>::parse_from([
318 "reth",
319 "--http.api",
320 " eth, admin, debug, eth,admin",
321 "--http",
322 "--ws",
323 ])
324 .args;
325 let config = args.transport_rpc_module_config();
326 let expected = [RethRpcModule::Eth, RethRpcModule::Admin, RethRpcModule::Debug];
327 assert_eq!(config.http().cloned().unwrap().into_selection(), expected.into());
328 assert_eq!(
329 config.ws().cloned().unwrap().into_selection(),
330 RpcModuleSelection::standard_modules()
331 );
332 }
333
334 #[test]
335 fn test_rpc_server_config() {
336 let args = CommandParser::<RpcServerArgs>::parse_from([
337 "reth",
338 "--http.api",
339 "eth,admin,debug",
340 "--http",
341 "--ws",
342 "--ws.addr",
343 "127.0.0.1",
344 "--ws.port",
345 "8888",
346 ])
347 .args;
348 let config = args.rpc_server_config();
349 assert_eq!(
350 config.http_address().unwrap(),
351 SocketAddr::V4(SocketAddrV4::new(
352 Ipv4Addr::LOCALHOST,
353 constants::DEFAULT_HTTP_RPC_PORT
354 ))
355 );
356 assert_eq!(
357 config.ws_address().unwrap(),
358 SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 8888))
359 );
360 assert_eq!(config.ipc_endpoint().unwrap(), constants::DEFAULT_IPC_ENDPOINT);
361 }
362
363 #[test]
364 fn test_zero_filter_limits() {
365 let args = CommandParser::<RpcServerArgs>::parse_from([
366 "reth",
367 "--rpc-max-blocks-per-filter",
368 "0",
369 "--rpc-max-logs-per-response",
370 "0",
371 ])
372 .args;
373
374 let config = args.eth_config().filter_config();
375 assert_eq!(config.max_blocks_per_filter, Some(u64::MAX));
376 assert_eq!(config.max_logs_per_response, Some(usize::MAX));
377 }
378
379 #[test]
380 fn test_custom_filter_limits() {
381 let args = CommandParser::<RpcServerArgs>::parse_from([
382 "reth",
383 "--rpc-max-blocks-per-filter",
384 "100",
385 "--rpc-max-logs-per-response",
386 "200",
387 ])
388 .args;
389
390 let config = args.eth_config().filter_config();
391 assert_eq!(config.max_blocks_per_filter, Some(100));
392 assert_eq!(config.max_logs_per_response, Some(200));
393 }
394}