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