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 .with_ws_cors(self.ws_allowed_origins.clone());
200 }
201
202 if self.ws {
203 let socket_address = SocketAddr::new(self.ws_addr, self.ws_port);
204 config = config.with_ws_address(socket_address).with_ws(self.http_ws_server_builder());
205 }
206
207 if self.is_ipc_enabled() {
208 config =
209 config.with_ipc(self.ipc_server_builder()).with_ipc_endpoint(self.ipcpath.clone());
210 }
211
212 config
213 }
214
215 fn auth_server_config(&self, jwt_secret: JwtSecret) -> Result<AuthServerConfig, RpcError> {
216 let address = SocketAddr::new(self.auth_addr, self.auth_port);
217
218 let mut builder = AuthServerConfig::builder(jwt_secret).socket_addr(address);
219 if self.auth_ipc {
220 builder = builder
221 .ipc_endpoint(self.auth_ipc_path.clone())
222 .with_ipc_config(self.ipc_server_builder());
223 }
224 Ok(builder.build())
225 }
226
227 fn auth_jwt_secret(&self, default_jwt_path: PathBuf) -> Result<JwtSecret, JwtError> {
228 match self.auth_jwtsecret.as_ref() {
229 Some(fpath) => {
230 debug!(target: "reth::cli", user_path=?fpath, "Reading JWT auth secret file");
231 JwtSecret::from_file(fpath)
232 }
233 None => get_or_create_jwt_secret_from_path(&default_jwt_path),
234 }
235 }
236
237 fn rpc_secret_key(&self) -> Option<JwtSecret> {
238 self.rpc_jwtsecret
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use clap::{Args, Parser};
245 use reth_node_core::args::RpcServerArgs;
246 use reth_rpc_eth_types::RPC_DEFAULT_GAS_CAP;
247 use reth_rpc_server_types::{constants, RethRpcModule, RpcModuleSelection};
248 use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
249
250 use crate::config::RethRpcServerConfig;
251
252 #[derive(Parser)]
254 struct CommandParser<T: Args> {
255 #[command(flatten)]
256 args: T,
257 }
258
259 #[test]
260 fn test_rpc_gas_cap() {
261 let args = CommandParser::<RpcServerArgs>::parse_from(["reth"]).args;
262 let config = args.eth_config();
263 assert_eq!(config.rpc_gas_cap, u64::from(RPC_DEFAULT_GAS_CAP));
264
265 let args =
266 CommandParser::<RpcServerArgs>::parse_from(["reth", "--rpc.gascap", "1000"]).args;
267 let config = args.eth_config();
268 assert_eq!(config.rpc_gas_cap, 1000);
269
270 let args = CommandParser::<RpcServerArgs>::try_parse_from(["reth", "--rpc.gascap", "0"]);
271 assert!(args.is_err());
272 }
273
274 #[test]
275 fn test_transport_rpc_module_config() {
276 let args = CommandParser::<RpcServerArgs>::parse_from([
277 "reth",
278 "--http.api",
279 "eth,admin,debug",
280 "--http",
281 "--ws",
282 ])
283 .args;
284 let config = args.transport_rpc_module_config();
285 let expected = [RethRpcModule::Eth, RethRpcModule::Admin, RethRpcModule::Debug];
286 assert_eq!(config.http().cloned().unwrap().into_selection(), expected.into());
287 assert_eq!(
288 config.ws().cloned().unwrap().into_selection(),
289 RpcModuleSelection::standard_modules()
290 );
291 }
292
293 #[test]
294 fn test_transport_rpc_module_trim_config() {
295 let args = CommandParser::<RpcServerArgs>::parse_from([
296 "reth",
297 "--http.api",
298 " eth, admin, debug",
299 "--http",
300 "--ws",
301 ])
302 .args;
303 let config = args.transport_rpc_module_config();
304 let expected = [RethRpcModule::Eth, RethRpcModule::Admin, RethRpcModule::Debug];
305 assert_eq!(config.http().cloned().unwrap().into_selection(), expected.into());
306 assert_eq!(
307 config.ws().cloned().unwrap().into_selection(),
308 RpcModuleSelection::standard_modules()
309 );
310 }
311
312 #[test]
313 fn test_unique_rpc_modules() {
314 let args = CommandParser::<RpcServerArgs>::parse_from([
315 "reth",
316 "--http.api",
317 " eth, admin, debug, eth,admin",
318 "--http",
319 "--ws",
320 ])
321 .args;
322 let config = args.transport_rpc_module_config();
323 let expected = [RethRpcModule::Eth, RethRpcModule::Admin, RethRpcModule::Debug];
324 assert_eq!(config.http().cloned().unwrap().into_selection(), expected.into());
325 assert_eq!(
326 config.ws().cloned().unwrap().into_selection(),
327 RpcModuleSelection::standard_modules()
328 );
329 }
330
331 #[test]
332 fn test_rpc_server_config() {
333 let args = CommandParser::<RpcServerArgs>::parse_from([
334 "reth",
335 "--http.api",
336 "eth,admin,debug",
337 "--http",
338 "--ws",
339 "--ws.addr",
340 "127.0.0.1",
341 "--ws.port",
342 "8888",
343 ])
344 .args;
345 let config = args.rpc_server_config();
346 assert_eq!(
347 config.http_address().unwrap(),
348 SocketAddr::V4(SocketAddrV4::new(
349 Ipv4Addr::LOCALHOST,
350 constants::DEFAULT_HTTP_RPC_PORT
351 ))
352 );
353 assert_eq!(
354 config.ws_address().unwrap(),
355 SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 8888))
356 );
357 assert_eq!(config.ipc_endpoint().unwrap(), constants::DEFAULT_IPC_ENDPOINT);
358 }
359
360 #[test]
361 fn test_zero_filter_limits() {
362 let args = CommandParser::<RpcServerArgs>::parse_from([
363 "reth",
364 "--rpc-max-blocks-per-filter",
365 "0",
366 "--rpc-max-logs-per-response",
367 "0",
368 ])
369 .args;
370
371 let config = args.eth_config().filter_config();
372 assert_eq!(config.max_blocks_per_filter, Some(u64::MAX));
373 assert_eq!(config.max_logs_per_response, Some(usize::MAX));
374 }
375
376 #[test]
377 fn test_custom_filter_limits() {
378 let args = CommandParser::<RpcServerArgs>::parse_from([
379 "reth",
380 "--rpc-max-blocks-per-filter",
381 "100",
382 "--rpc-max-logs-per-response",
383 "200",
384 ])
385 .args;
386
387 let config = args.eth_config().filter_config();
388 assert_eq!(config.max_blocks_per_filter, Some(100));
389 assert_eq!(config.max_logs_per_response, Some(200));
390 }
391}