reth_rpc_builder/
config.rs

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
17/// A trait that provides a configured RPC server.
18///
19/// This provides all basic config values for the RPC server and is implemented by the
20/// [`RpcServerArgs`] type.
21pub trait RethRpcServerConfig {
22    /// Returns whether ipc is enabled.
23    fn is_ipc_enabled(&self) -> bool;
24
25    /// Returns the path to the target ipc socket if enabled.
26    fn ipc_path(&self) -> &str;
27
28    /// The configured ethereum RPC settings.
29    fn eth_config(&self) -> EthConfig;
30
31    /// The configured ethereum RPC settings.
32    fn flashbots_config(&self) -> ValidationApiConfig;
33
34    /// Returns state cache configuration.
35    fn state_cache_config(&self) -> EthStateCacheConfig;
36
37    /// Returns the max request size in bytes.
38    fn rpc_max_request_size_bytes(&self) -> u32;
39
40    /// Returns the max response size in bytes.
41    fn rpc_max_response_size_bytes(&self) -> u32;
42
43    /// Extracts the gas price oracle config from the args.
44    fn gas_price_oracle_config(&self) -> GasPriceOracleConfig;
45
46    /// Creates the [`TransportRpcModuleConfig`] from cli args.
47    ///
48    /// This sets all the api modules, and configures additional settings like gas price oracle
49    /// settings in the [`TransportRpcModuleConfig`].
50    fn transport_rpc_module_config(&self) -> TransportRpcModuleConfig;
51
52    /// Returns the default server builder for http/ws
53    fn http_ws_server_builder(&self) -> ServerBuilder<Identity, Identity>;
54
55    /// Returns the default ipc server builder
56    fn ipc_server_builder(&self) -> IpcServerBuilder<Identity, Identity>;
57
58    /// Creates the [`RpcServerConfig`] from cli args.
59    fn rpc_server_config(&self) -> RpcServerConfig;
60
61    /// Creates the [`AuthServerConfig`] from cli args.
62    fn auth_server_config(&self, jwt_secret: JwtSecret) -> Result<AuthServerConfig, RpcError>;
63
64    /// The execution layer and consensus layer clients SHOULD accept a configuration parameter:
65    /// jwt-secret, which designates a file containing the hex-encoded 256 bit secret key to be used
66    /// for verifying/generating JWT tokens.
67    ///
68    /// If such a parameter is given, but the file cannot be read, or does not contain a hex-encoded
69    /// key of 256 bits, the client SHOULD treat this as an error.
70    ///
71    /// If such a parameter is not given, the client SHOULD generate such a token, valid for the
72    /// duration of the execution, and SHOULD store the hex-encoded secret as a jwt.hex file on
73    /// the filesystem. This file can then be used to provision the counterpart client.
74    ///
75    /// The `default_jwt_path` provided as an argument will be used as the default location for the
76    /// jwt secret in case the `auth_jwtsecret` argument is not provided.
77    fn auth_jwt_secret(&self, default_jwt_path: PathBuf) -> Result<JwtSecret, JwtError>;
78
79    /// Returns the configured jwt secret key for the regular rpc servers, if any.
80    ///
81    /// Note: this is not used for the auth server (engine API).
82    fn rpc_secret_key(&self) -> Option<JwtSecret>;
83}
84
85impl RethRpcServerConfig for RpcServerArgs {
86    fn is_ipc_enabled(&self) -> bool {
87        // By default IPC is enabled therefore it is enabled if the `ipcdisable` is false.
88        !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    /// A helper type to parse Args more easily
246    #[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}