Skip to main content

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    /// Returns whether built-in RPC request metrics are enabled.
61    fn rpc_metrics_enabled(&self) -> bool;
62
63    /// Creates the [`AuthServerConfig`] from cli args.
64    fn auth_server_config(&self, jwt_secret: JwtSecret) -> Result<AuthServerConfig, RpcError>;
65
66    /// The execution layer and consensus layer clients SHOULD accept a configuration parameter:
67    /// jwt-secret, which designates a file containing the hex-encoded 256 bit secret key to be used
68    /// for verifying/generating JWT tokens.
69    ///
70    /// If such a parameter is given, but the file cannot be read, or does not contain a hex-encoded
71    /// key of 256 bits, the client SHOULD treat this as an error.
72    ///
73    /// If such a parameter is not given, the client SHOULD generate such a token, valid for the
74    /// duration of the execution, and SHOULD store the hex-encoded secret as a jwt.hex file on
75    /// the filesystem. This file can then be used to provision the counterpart client.
76    ///
77    /// The `default_jwt_path` provided as an argument will be used as the default location for the
78    /// jwt secret in case neither `--authrpc.jwtsecret` nor `--authrpc.jwtsecret-hex` is provided.
79    fn auth_jwt_secret(&self, default_jwt_path: PathBuf) -> Result<JwtSecret, JwtError>;
80
81    /// Returns the configured jwt secret key for the regular rpc servers, if any.
82    ///
83    /// Note: this is not used for the auth server (engine API).
84    fn rpc_secret_key(&self) -> Option<JwtSecret>;
85}
86
87impl RethRpcServerConfig for RpcServerArgs {
88    fn is_ipc_enabled(&self) -> bool {
89        // By default IPC is enabled therefore it is enabled if the `ipcdisable` is false.
90        !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            // Ensure WS CORS is applied regardless of HTTP being enabled
226            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    /// A helper type to parse Args more easily
288    #[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}