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>;
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            // the proof window defaults to 0, which would reject any submission whose parent is
121            // behind the tip, so it can only widen the default window
122            validation_window: self
123                .rpc_eth_proof_window
124                .max(ValidationApiConfig::DEFAULT_VALIDATION_WINDOW),
125        }
126    }
127
128    fn state_cache_config(&self) -> EthStateCacheConfig {
129        EthStateCacheConfig {
130            max_blocks: self.rpc_state_cache.max_blocks,
131            max_receipts: self.rpc_state_cache.max_receipts,
132            max_bals: self.rpc_state_cache.max_bals,
133            max_blocks_bytes: self.rpc_state_cache.max_blocks_bytes,
134            max_receipts_bytes: self.rpc_state_cache.max_receipts_bytes,
135            max_bals_bytes: self.rpc_state_cache.max_bals_bytes,
136            idle_timeout: self.rpc_state_cache.idle_timeout,
137            cache_computed_bals: self.rpc_state_cache.cache_computed_bals,
138            prewarm_bals: self.rpc_state_cache.prewarm_bals,
139            max_concurrent_db_requests: self.rpc_state_cache.max_concurrent_db_requests,
140            max_cached_tx_hashes: self.rpc_state_cache.max_cached_tx_hashes,
141        }
142    }
143
144    fn rpc_max_request_size_bytes(&self) -> u32 {
145        self.rpc_max_request_size.get().saturating_mul(1024 * 1024)
146    }
147
148    fn rpc_max_response_size_bytes(&self) -> u32 {
149        self.rpc_max_response_size.get().saturating_mul(1024 * 1024)
150    }
151
152    fn gas_price_oracle_config(&self) -> GasPriceOracleConfig {
153        self.gas_price_oracle.gas_price_oracle_config()
154    }
155
156    fn transport_rpc_module_config(&self) -> TransportRpcModuleConfig {
157        let mut config = TransportRpcModuleConfig::default()
158            .with_config(RpcModuleConfig::new(self.eth_config()));
159
160        if self.http {
161            config = config.with_http(
162                self.http_api
163                    .clone()
164                    .unwrap_or_else(|| RpcModuleSelection::standard_modules().into()),
165            );
166        }
167
168        if self.ws {
169            config = config.with_ws(
170                self.ws_api
171                    .clone()
172                    .unwrap_or_else(|| RpcModuleSelection::standard_modules().into()),
173            );
174        }
175
176        if self.is_ipc_enabled() {
177            config = config.with_ipc(RpcModuleSelection::default_ipc_modules());
178        }
179
180        config
181    }
182
183    fn http_ws_server_builder(&self) -> ServerConfigBuilder {
184        ServerConfigBuilder::new()
185            .max_connections(self.rpc_max_connections.get())
186            .max_request_body_size(self.rpc_max_request_size_bytes())
187            .max_response_body_size(self.rpc_max_response_size_bytes())
188            .max_subscriptions_per_connection(self.rpc_max_subscriptions_per_connection.get())
189    }
190
191    fn ipc_server_builder(&self) -> IpcServerBuilder<Identity> {
192        IpcServerBuilder::default()
193            .max_subscriptions_per_connection(self.rpc_max_subscriptions_per_connection.get())
194            .max_request_body_size(self.rpc_max_request_size_bytes())
195            .max_response_body_size(self.rpc_max_response_size_bytes())
196            .max_connections(self.rpc_max_connections.get())
197            .set_ipc_socket_permissions(self.ipc_socket_permissions.clone())
198    }
199
200    fn rpc_server_config(&self) -> RpcServerConfig {
201        let mut config = RpcServerConfig::default()
202            .with_jwt_secret(self.rpc_secret_key())
203            .with_rpc_metrics_enabled(self.rpc_metrics_enabled());
204
205        if self.http_api.is_some() && !self.http {
206            warn!(
207                target: "reth::cli",
208                "The --http.api flag is set but --http is not enabled. HTTP RPC API will not be exposed."
209            );
210        }
211
212        if self.ws_api.is_some() && !self.ws {
213            warn!(
214                target: "reth::cli",
215                "The --ws.api flag is set but --ws is not enabled. WS RPC API will not be exposed."
216            );
217        }
218
219        if self.http {
220            let socket_address = SocketAddr::new(self.http_addr, self.http_port);
221            config = config
222                .with_http_address(socket_address)
223                .with_http(self.http_ws_server_builder())
224                .with_http_cors(self.http_corsdomain.clone())
225                .with_http_disable_compression(self.http_disable_compression)
226                .with_http_compression_algorithms(self.http_compression_algorithms.clone())
227                .with_http_decompression(
228                    self.http_decompression_algorithms.clone(),
229                    self.rpc_max_request_size_bytes(),
230                );
231        }
232
233        if self.ws {
234            let socket_address = SocketAddr::new(self.ws_addr, self.ws_port);
235            // Ensure WS CORS is applied regardless of HTTP being enabled
236            config = config
237                .with_ws_address(socket_address)
238                .with_ws(self.http_ws_server_builder())
239                .with_ws_cors(self.ws_allowed_origins.clone());
240        }
241
242        if self.is_ipc_enabled() {
243            config =
244                config.with_ipc(self.ipc_server_builder()).with_ipc_endpoint(self.ipcpath.clone());
245        }
246
247        config
248    }
249
250    fn rpc_metrics_enabled(&self) -> bool {
251        !self.rpc_disable_metrics
252    }
253
254    fn auth_server_config(&self, jwt_secret: JwtSecret) -> Result<AuthServerConfig, RpcError> {
255        let address = SocketAddr::new(self.auth_addr, self.auth_port);
256
257        let mut builder = AuthServerConfig::builder(jwt_secret).socket_addr(address);
258        if self.auth_ipc {
259            builder = builder
260                .ipc_endpoint(self.auth_ipc_path().to_string())
261                .with_ipc_config(self.ipc_server_builder());
262        }
263        Ok(builder.build())
264    }
265
266    fn auth_jwt_secret(&self, default_jwt_path: PathBuf) -> Result<JwtSecret, JwtError> {
267        if let Some(secret) = self.auth_jwtsecret_hex {
268            debug!(target: "reth::cli", "Using JWT auth secret from hex");
269            Ok(secret)
270        } else {
271            match self.auth_jwtsecret.as_ref() {
272                Some(fpath) => {
273                    debug!(target: "reth::cli", user_path=?fpath, "Reading JWT auth secret file");
274                    JwtSecret::from_file(fpath)
275                }
276                None => get_or_create_jwt_secret_from_path(&default_jwt_path),
277            }
278        }
279    }
280
281    fn rpc_secret_key(&self) -> Option<JwtSecret> {
282        self.rpc_jwtsecret
283    }
284}
285
286#[cfg(test)]
287mod tests {
288    use clap::{Args, Parser};
289    use reth_node_core::args::RpcServerArgs;
290    use reth_rpc::ValidationApiConfig;
291    use reth_rpc_eth_types::{EthStateCacheConfig, RPC_DEFAULT_GAS_CAP};
292    use reth_rpc_layer::JwtSecret;
293    use reth_rpc_server_types::{constants, RethRpcModule, RpcModuleSelection};
294    use std::{
295        net::{Ipv4Addr, SocketAddr, SocketAddrV4},
296        time::Duration,
297    };
298
299    use crate::config::RethRpcServerConfig;
300
301    /// A helper type to parse Args more easily
302    #[derive(Parser)]
303    struct CommandParser<T: Args> {
304        #[command(flatten)]
305        args: T,
306    }
307
308    #[test]
309    fn rpc_cache_limits_reach_eth_config() {
310        let args = CommandParser::<RpcServerArgs>::parse_from(["reth"]).args;
311        assert_eq!(args.eth_config().cache, EthStateCacheConfig::default());
312
313        for (timeout, expected_timeout) in
314            [("0s", Duration::ZERO), ("2500ms", Duration::from_millis(2500))]
315        {
316            let args = CommandParser::<RpcServerArgs>::parse_from([
317                "reth",
318                "--rpc-cache.max-blocks",
319                "10",
320                "--rpc-cache.max-receipts",
321                "20",
322                "--rpc-cache.max-bals",
323                "30",
324                "--rpc-cache.max-blocks-bytes",
325                "1024",
326                "--rpc-cache.max-receipts-bytes",
327                "0",
328                "--rpc-cache.max-bals-bytes",
329                "4096",
330                "--rpc-cache.idle-timeout",
331                timeout,
332            ])
333            .args;
334            assert_eq!(
335                args.eth_config().cache,
336                EthStateCacheConfig {
337                    max_blocks: 10,
338                    max_receipts: 20,
339                    max_bals: 30,
340                    max_blocks_bytes: 1024,
341                    max_receipts_bytes: 0,
342                    max_bals_bytes: 4096,
343                    idle_timeout: expected_timeout,
344                    ..Default::default()
345                }
346            );
347        }
348    }
349
350    #[test]
351    fn flashbots_validation_window() {
352        let args = CommandParser::<RpcServerArgs>::parse_from(["reth"]).args;
353        assert_eq!(
354            args.flashbots_config().validation_window,
355            ValidationApiConfig::DEFAULT_VALIDATION_WINDOW
356        );
357
358        let args =
359            CommandParser::<RpcServerArgs>::parse_from(["reth", "--rpc.eth-proof-window", "1"])
360                .args;
361        assert_eq!(
362            args.flashbots_config().validation_window,
363            ValidationApiConfig::DEFAULT_VALIDATION_WINDOW
364        );
365
366        let args =
367            CommandParser::<RpcServerArgs>::parse_from(["reth", "--rpc.eth-proof-window", "100"])
368                .args;
369        assert_eq!(args.flashbots_config().validation_window, 100);
370    }
371
372    #[test]
373    fn test_rpc_gas_cap() {
374        let args = CommandParser::<RpcServerArgs>::parse_from(["reth"]).args;
375        let config = args.eth_config();
376        assert_eq!(config.rpc_gas_cap, u64::from(RPC_DEFAULT_GAS_CAP));
377
378        let args =
379            CommandParser::<RpcServerArgs>::parse_from(["reth", "--rpc.gascap", "1000"]).args;
380        let config = args.eth_config();
381        assert_eq!(config.rpc_gas_cap, 1000);
382
383        let args = CommandParser::<RpcServerArgs>::try_parse_from(["reth", "--rpc.gascap", "0"]);
384        assert!(args.is_err());
385    }
386
387    #[test]
388    fn test_transport_rpc_module_config() {
389        let args = CommandParser::<RpcServerArgs>::parse_from([
390            "reth",
391            "--http.api",
392            "eth,admin,debug",
393            "--http",
394            "--ws",
395        ])
396        .args;
397        let config = args.transport_rpc_module_config();
398        let expected = [RethRpcModule::Eth, RethRpcModule::Admin, RethRpcModule::Debug];
399        assert_eq!(config.http().cloned().unwrap().into_selection(), expected.into());
400        assert_eq!(
401            config.ws().cloned().unwrap().into_selection(),
402            RpcModuleSelection::standard_modules()
403        );
404    }
405
406    #[test]
407    fn test_testing_namespace_requires_explicit_selection() {
408        let args = CommandParser::<RpcServerArgs>::parse_from([
409            "reth",
410            "--http",
411            "--http.api",
412            "all",
413            "--ws",
414            "--ws.api",
415            "all",
416        ])
417        .args;
418        let config = args.transport_rpc_module_config();
419        assert!(!config.contains_http(&RethRpcModule::Testing));
420        assert!(!config.contains_ws(&RethRpcModule::Testing));
421        assert!(!config.contains_ipc(&RethRpcModule::Testing));
422
423        let args = CommandParser::<RpcServerArgs>::parse_from([
424            "reth",
425            "--http",
426            "--http.api",
427            "eth,testing",
428            "--ws",
429            "--ws.api",
430            "eth,testing",
431        ])
432        .args;
433        let config = args.transport_rpc_module_config();
434        assert!(config.contains_http(&RethRpcModule::Testing));
435        assert!(config.contains_ws(&RethRpcModule::Testing));
436    }
437
438    #[test]
439    fn test_transport_rpc_module_trim_config() {
440        let args = CommandParser::<RpcServerArgs>::parse_from([
441            "reth",
442            "--http.api",
443            " eth, admin, debug",
444            "--http",
445            "--ws",
446        ])
447        .args;
448        let config = args.transport_rpc_module_config();
449        let expected = [RethRpcModule::Eth, RethRpcModule::Admin, RethRpcModule::Debug];
450        assert_eq!(config.http().cloned().unwrap().into_selection(), expected.into());
451        assert_eq!(
452            config.ws().cloned().unwrap().into_selection(),
453            RpcModuleSelection::standard_modules()
454        );
455    }
456
457    #[test]
458    fn test_unique_rpc_modules() {
459        let args = CommandParser::<RpcServerArgs>::parse_from([
460            "reth",
461            "--http.api",
462            " eth, admin, debug, eth,admin",
463            "--http",
464            "--ws",
465        ])
466        .args;
467        let config = args.transport_rpc_module_config();
468        let expected = [RethRpcModule::Eth, RethRpcModule::Admin, RethRpcModule::Debug];
469        assert_eq!(config.http().cloned().unwrap().into_selection(), expected.into());
470        assert_eq!(
471            config.ws().cloned().unwrap().into_selection(),
472            RpcModuleSelection::standard_modules()
473        );
474    }
475
476    #[test]
477    fn test_rpc_server_config() {
478        let args = CommandParser::<RpcServerArgs>::parse_from([
479            "reth",
480            "--http.api",
481            "eth,admin,debug",
482            "--http",
483            "--ws",
484            "--ws.addr",
485            "127.0.0.1",
486            "--ws.port",
487            "8888",
488        ])
489        .args;
490        let config = args.rpc_server_config();
491        assert_eq!(
492            config.http_address().unwrap(),
493            SocketAddr::V4(SocketAddrV4::new(
494                Ipv4Addr::LOCALHOST,
495                constants::DEFAULT_HTTP_RPC_PORT
496            ))
497        );
498        assert_eq!(
499            config.ws_address().unwrap(),
500            SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 8888))
501        );
502        assert_eq!(config.ipc_endpoint().unwrap(), constants::DEFAULT_IPC_ENDPOINT);
503        assert!(config.rpc_metrics_enabled());
504    }
505
506    #[test]
507    fn test_rpc_server_config_disable_metrics() {
508        let args =
509            CommandParser::<RpcServerArgs>::parse_from(["reth", "--rpc.disable-metrics"]).args;
510        let config = args.rpc_server_config();
511        assert!(!config.rpc_metrics_enabled());
512    }
513
514    #[test]
515    fn test_zero_filter_limits() {
516        let args = CommandParser::<RpcServerArgs>::parse_from([
517            "reth",
518            "--rpc-max-blocks-per-filter",
519            "0",
520            "--rpc-max-logs-per-response",
521            "0",
522        ])
523        .args;
524
525        let config = args.eth_config().filter_config();
526        assert_eq!(config.max_blocks_per_filter, Some(u64::MAX));
527        assert_eq!(config.max_logs_per_response, Some(usize::MAX));
528    }
529
530    #[test]
531    fn test_custom_filter_limits() {
532        let args = CommandParser::<RpcServerArgs>::parse_from([
533            "reth",
534            "--rpc-max-blocks-per-filter",
535            "100",
536            "--rpc-max-logs-per-response",
537            "200",
538        ])
539        .args;
540
541        let config = args.eth_config().filter_config();
542        assert_eq!(config.max_blocks_per_filter, Some(100));
543        assert_eq!(config.max_logs_per_response, Some(200));
544    }
545
546    #[test]
547    fn test_auth_jwt_secret_from_hex() {
548        let hex = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
549        let args =
550            CommandParser::<RpcServerArgs>::parse_from(["reth", "--authrpc.jwtsecret-hex", hex])
551                .args;
552
553        let secret = args.auth_jwt_secret(std::env::temp_dir().join("unused.jwt")).unwrap();
554        assert_eq!(secret, JwtSecret::from_hex(hex).unwrap());
555    }
556}