reth_rpc_builder/
config.rs

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
16/// A trait that provides a configured RPC server.
17///
18/// This provides all basic config values for the RPC server and is implemented by the
19/// [`RpcServerArgs`] type.
20pub trait RethRpcServerConfig {
21    /// Returns whether ipc is enabled.
22    fn is_ipc_enabled(&self) -> bool;
23
24    /// Returns the path to the target ipc socket if enabled.
25    fn ipc_path(&self) -> &str;
26
27    /// The configured ethereum RPC settings.
28    fn eth_config(&self) -> EthConfig;
29
30    /// The configured ethereum RPC settings.
31    fn flashbots_config(&self) -> ValidationApiConfig;
32
33    /// Returns state cache configuration.
34    fn state_cache_config(&self) -> EthStateCacheConfig;
35
36    /// Returns the max request size in bytes.
37    fn rpc_max_request_size_bytes(&self) -> u32;
38
39    /// Returns the max response size in bytes.
40    fn rpc_max_response_size_bytes(&self) -> u32;
41
42    /// Extracts the gas price oracle config from the args.
43    fn gas_price_oracle_config(&self) -> GasPriceOracleConfig;
44
45    /// Creates the [`TransportRpcModuleConfig`] from cli args.
46    ///
47    /// This sets all the api modules, and configures additional settings like gas price oracle
48    /// settings in the [`TransportRpcModuleConfig`].
49    fn transport_rpc_module_config(&self) -> TransportRpcModuleConfig;
50
51    /// Returns the default server config for http/ws
52    fn http_ws_server_builder(&self) -> ServerConfigBuilder;
53
54    /// Returns the default ipc server builder
55    fn ipc_server_builder(&self) -> IpcServerBuilder<Identity, Identity>;
56
57    /// Creates the [`RpcServerConfig`] from cli args.
58    fn rpc_server_config(&self) -> RpcServerConfig;
59
60    /// Creates the [`AuthServerConfig`] from cli args.
61    fn auth_server_config(&self, jwt_secret: JwtSecret) -> Result<AuthServerConfig, RpcError>;
62
63    /// The execution layer and consensus layer clients SHOULD accept a configuration parameter:
64    /// jwt-secret, which designates a file containing the hex-encoded 256 bit secret key to be used
65    /// for verifying/generating JWT tokens.
66    ///
67    /// If such a parameter is given, but the file cannot be read, or does not contain a hex-encoded
68    /// key of 256 bits, the client SHOULD treat this as an error.
69    ///
70    /// If such a parameter is not given, the client SHOULD generate such a token, valid for the
71    /// duration of the execution, and SHOULD store the hex-encoded secret as a jwt.hex file on
72    /// the filesystem. This file can then be used to provision the counterpart client.
73    ///
74    /// The `default_jwt_path` provided as an argument will be used as the default location for the
75    /// jwt secret in case the `auth_jwtsecret` argument is not provided.
76    fn auth_jwt_secret(&self, default_jwt_path: PathBuf) -> Result<JwtSecret, JwtError>;
77
78    /// Returns the configured jwt secret key for the regular rpc servers, if any.
79    ///
80    /// Note: this is not used for the auth server (engine API).
81    fn rpc_secret_key(&self) -> Option<JwtSecret>;
82}
83
84impl RethRpcServerConfig for RpcServerArgs {
85    fn is_ipc_enabled(&self) -> bool {
86        // By default IPC is enabled therefore it is enabled if the `ipcdisable` is false.
87        !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_blocking_io_requests(self.rpc_max_blocking_io_requests)
98            .max_trace_filter_blocks(self.rpc_max_trace_filter_blocks)
99            .max_blocks_per_filter(self.rpc_max_blocks_per_filter.unwrap_or_max())
100            .max_logs_per_response(self.rpc_max_logs_per_response.unwrap_or_max() as usize)
101            .eth_proof_window(self.rpc_eth_proof_window)
102            .rpc_gas_cap(self.rpc_gas_cap)
103            .rpc_max_simulate_blocks(self.rpc_max_simulate_blocks)
104            .state_cache(self.state_cache_config())
105            .gpo_config(self.gas_price_oracle_config())
106            .proof_permits(self.rpc_proof_permits)
107            .pending_block_kind(self.rpc_pending_block)
108            .raw_tx_forwarder(self.rpc_forwarder.clone())
109            .rpc_evm_memory_limit(self.rpc_evm_memory_limit)
110    }
111
112    fn flashbots_config(&self) -> ValidationApiConfig {
113        ValidationApiConfig {
114            disallow: self.builder_disallow.clone().unwrap_or_default(),
115            validation_window: self.rpc_eth_proof_window,
116        }
117    }
118
119    fn state_cache_config(&self) -> EthStateCacheConfig {
120        EthStateCacheConfig {
121            max_blocks: self.rpc_state_cache.max_blocks,
122            max_receipts: self.rpc_state_cache.max_receipts,
123            max_headers: self.rpc_state_cache.max_headers,
124            max_concurrent_db_requests: self.rpc_state_cache.max_concurrent_db_requests,
125        }
126    }
127
128    fn rpc_max_request_size_bytes(&self) -> u32 {
129        self.rpc_max_request_size.get().saturating_mul(1024 * 1024)
130    }
131
132    fn rpc_max_response_size_bytes(&self) -> u32 {
133        self.rpc_max_response_size.get().saturating_mul(1024 * 1024)
134    }
135
136    fn gas_price_oracle_config(&self) -> GasPriceOracleConfig {
137        self.gas_price_oracle.gas_price_oracle_config()
138    }
139
140    fn transport_rpc_module_config(&self) -> TransportRpcModuleConfig {
141        let mut config = TransportRpcModuleConfig::default()
142            .with_config(RpcModuleConfig::new(self.eth_config(), self.flashbots_config()));
143
144        if self.http {
145            config = config.with_http(
146                self.http_api
147                    .clone()
148                    .unwrap_or_else(|| RpcModuleSelection::standard_modules().into()),
149            );
150        }
151
152        if self.ws {
153            config = config.with_ws(
154                self.ws_api
155                    .clone()
156                    .unwrap_or_else(|| RpcModuleSelection::standard_modules().into()),
157            );
158        }
159
160        if self.is_ipc_enabled() {
161            config = config.with_ipc(RpcModuleSelection::default_ipc_modules());
162        }
163
164        config
165    }
166
167    fn http_ws_server_builder(&self) -> ServerConfigBuilder {
168        ServerConfigBuilder::new()
169            .max_connections(self.rpc_max_connections.get())
170            .max_request_body_size(self.rpc_max_request_size_bytes())
171            .max_response_body_size(self.rpc_max_response_size_bytes())
172            .max_subscriptions_per_connection(self.rpc_max_subscriptions_per_connection.get())
173    }
174
175    fn ipc_server_builder(&self) -> IpcServerBuilder<Identity, Identity> {
176        IpcServerBuilder::default()
177            .max_subscriptions_per_connection(self.rpc_max_subscriptions_per_connection.get())
178            .max_request_body_size(self.rpc_max_request_size_bytes())
179            .max_response_body_size(self.rpc_max_response_size_bytes())
180            .max_connections(self.rpc_max_connections.get())
181            .set_ipc_socket_permissions(self.ipc_socket_permissions.clone())
182    }
183
184    fn rpc_server_config(&self) -> RpcServerConfig {
185        let mut config = RpcServerConfig::default().with_jwt_secret(self.rpc_secret_key());
186
187        if self.http_api.is_some() && !self.http {
188            warn!(
189                target: "reth::cli",
190                "The --http.api flag is set but --http is not enabled. HTTP RPC API will not be exposed."
191            );
192        }
193
194        if self.ws_api.is_some() && !self.ws {
195            warn!(
196                target: "reth::cli",
197                "The --ws.api flag is set but --ws is not enabled. WS RPC API will not be exposed."
198            );
199        }
200
201        if self.http {
202            let socket_address = SocketAddr::new(self.http_addr, self.http_port);
203            config = config
204                .with_http_address(socket_address)
205                .with_http(self.http_ws_server_builder())
206                .with_http_cors(self.http_corsdomain.clone())
207                .with_http_disable_compression(self.http_disable_compression);
208        }
209
210        if self.ws {
211            let socket_address = SocketAddr::new(self.ws_addr, self.ws_port);
212            // Ensure WS CORS is applied regardless of HTTP being enabled
213            config = config
214                .with_ws_address(socket_address)
215                .with_ws(self.http_ws_server_builder())
216                .with_ws_cors(self.ws_allowed_origins.clone());
217        }
218
219        if self.is_ipc_enabled() {
220            config =
221                config.with_ipc(self.ipc_server_builder()).with_ipc_endpoint(self.ipcpath.clone());
222        }
223
224        config
225    }
226
227    fn auth_server_config(&self, jwt_secret: JwtSecret) -> Result<AuthServerConfig, RpcError> {
228        let address = SocketAddr::new(self.auth_addr, self.auth_port);
229
230        let mut builder = AuthServerConfig::builder(jwt_secret).socket_addr(address);
231        if self.auth_ipc {
232            builder = builder
233                .ipc_endpoint(self.auth_ipc_path.clone())
234                .with_ipc_config(self.ipc_server_builder());
235        }
236        Ok(builder.build())
237    }
238
239    fn auth_jwt_secret(&self, default_jwt_path: PathBuf) -> Result<JwtSecret, JwtError> {
240        match self.auth_jwtsecret.as_ref() {
241            Some(fpath) => {
242                debug!(target: "reth::cli", user_path=?fpath, "Reading JWT auth secret file");
243                JwtSecret::from_file(fpath)
244            }
245            None => get_or_create_jwt_secret_from_path(&default_jwt_path),
246        }
247    }
248
249    fn rpc_secret_key(&self) -> Option<JwtSecret> {
250        self.rpc_jwtsecret
251    }
252}
253
254#[cfg(test)]
255mod tests {
256    use clap::{Args, Parser};
257    use reth_node_core::args::RpcServerArgs;
258    use reth_rpc_eth_types::RPC_DEFAULT_GAS_CAP;
259    use reth_rpc_server_types::{constants, RethRpcModule, RpcModuleSelection};
260    use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
261
262    use crate::config::RethRpcServerConfig;
263
264    /// A helper type to parse Args more easily
265    #[derive(Parser)]
266    struct CommandParser<T: Args> {
267        #[command(flatten)]
268        args: T,
269    }
270
271    #[test]
272    fn test_rpc_gas_cap() {
273        let args = CommandParser::<RpcServerArgs>::parse_from(["reth"]).args;
274        let config = args.eth_config();
275        assert_eq!(config.rpc_gas_cap, u64::from(RPC_DEFAULT_GAS_CAP));
276
277        let args =
278            CommandParser::<RpcServerArgs>::parse_from(["reth", "--rpc.gascap", "1000"]).args;
279        let config = args.eth_config();
280        assert_eq!(config.rpc_gas_cap, 1000);
281
282        let args = CommandParser::<RpcServerArgs>::try_parse_from(["reth", "--rpc.gascap", "0"]);
283        assert!(args.is_err());
284    }
285
286    #[test]
287    fn test_transport_rpc_module_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_transport_rpc_module_trim_config() {
307        let args = CommandParser::<RpcServerArgs>::parse_from([
308            "reth",
309            "--http.api",
310            " eth, admin, debug",
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_unique_rpc_modules() {
326        let args = CommandParser::<RpcServerArgs>::parse_from([
327            "reth",
328            "--http.api",
329            " eth, admin, debug, eth,admin",
330            "--http",
331            "--ws",
332        ])
333        .args;
334        let config = args.transport_rpc_module_config();
335        let expected = [RethRpcModule::Eth, RethRpcModule::Admin, RethRpcModule::Debug];
336        assert_eq!(config.http().cloned().unwrap().into_selection(), expected.into());
337        assert_eq!(
338            config.ws().cloned().unwrap().into_selection(),
339            RpcModuleSelection::standard_modules()
340        );
341    }
342
343    #[test]
344    fn test_rpc_server_config() {
345        let args = CommandParser::<RpcServerArgs>::parse_from([
346            "reth",
347            "--http.api",
348            "eth,admin,debug",
349            "--http",
350            "--ws",
351            "--ws.addr",
352            "127.0.0.1",
353            "--ws.port",
354            "8888",
355        ])
356        .args;
357        let config = args.rpc_server_config();
358        assert_eq!(
359            config.http_address().unwrap(),
360            SocketAddr::V4(SocketAddrV4::new(
361                Ipv4Addr::LOCALHOST,
362                constants::DEFAULT_HTTP_RPC_PORT
363            ))
364        );
365        assert_eq!(
366            config.ws_address().unwrap(),
367            SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 8888))
368        );
369        assert_eq!(config.ipc_endpoint().unwrap(), constants::DEFAULT_IPC_ENDPOINT);
370    }
371
372    #[test]
373    fn test_zero_filter_limits() {
374        let args = CommandParser::<RpcServerArgs>::parse_from([
375            "reth",
376            "--rpc-max-blocks-per-filter",
377            "0",
378            "--rpc-max-logs-per-response",
379            "0",
380        ])
381        .args;
382
383        let config = args.eth_config().filter_config();
384        assert_eq!(config.max_blocks_per_filter, Some(u64::MAX));
385        assert_eq!(config.max_logs_per_response, Some(usize::MAX));
386    }
387
388    #[test]
389    fn test_custom_filter_limits() {
390        let args = CommandParser::<RpcServerArgs>::parse_from([
391            "reth",
392            "--rpc-max-blocks-per-filter",
393            "100",
394            "--rpc-max-logs-per-response",
395            "200",
396        ])
397        .args;
398
399        let config = args.eth_config().filter_config();
400        assert_eq!(config.max_blocks_per_filter, Some(100));
401        assert_eq!(config.max_logs_per_response, Some(200));
402    }
403}