reth_rpc_builder/
auth.rs

1use crate::error::{RpcError, ServerKind};
2use http::header::AUTHORIZATION;
3use jsonrpsee::{
4    core::{client::SubscriptionClientT, RegisterMethodError},
5    http_client::HeaderMap,
6    server::{AlreadyStoppedError, RpcModule},
7    Methods,
8};
9use reth_rpc_api::servers::*;
10use reth_rpc_eth_types::EthSubscriptionIdProvider;
11use reth_rpc_layer::{
12    secret_to_bearer_header, AuthClientLayer, AuthLayer, JwtAuthValidator, JwtSecret,
13};
14use reth_rpc_server_types::constants;
15use std::net::{IpAddr, Ipv4Addr, SocketAddr};
16use tower::layer::util::Identity;
17
18pub use jsonrpsee::server::ServerBuilder;
19use jsonrpsee::server::{ServerConfig, ServerConfigBuilder};
20pub use reth_ipc::server::Builder as IpcServerBuilder;
21
22/// Server configuration for the auth server.
23#[derive(Debug)]
24pub struct AuthServerConfig {
25    /// Where the server should listen.
26    pub(crate) socket_addr: SocketAddr,
27    /// The secret for the auth layer of the server.
28    pub(crate) secret: JwtSecret,
29    /// Configs for JSON-RPC Http.
30    pub(crate) server_config: ServerConfigBuilder,
31    /// Configs for IPC server
32    pub(crate) ipc_server_config: Option<IpcServerBuilder<Identity, Identity>>,
33    /// IPC endpoint
34    pub(crate) ipc_endpoint: Option<String>,
35}
36
37// === impl AuthServerConfig ===
38
39impl AuthServerConfig {
40    /// Convenience function to create a new `AuthServerConfig`.
41    pub const fn builder(secret: JwtSecret) -> AuthServerConfigBuilder {
42        AuthServerConfigBuilder::new(secret)
43    }
44
45    /// Returns the address the server will listen on.
46    pub const fn address(&self) -> SocketAddr {
47        self.socket_addr
48    }
49
50    /// Convenience function to start a server in one step.
51    pub async fn start(self, module: AuthRpcModule) -> Result<AuthServerHandle, RpcError> {
52        let Self { socket_addr, secret, server_config, ipc_server_config, ipc_endpoint } = self;
53
54        // Create auth middleware.
55        let middleware =
56            tower::ServiceBuilder::new().layer(AuthLayer::new(JwtAuthValidator::new(secret)));
57
58        // By default, both http and ws are enabled.
59        let server = ServerBuilder::new()
60            .set_config(server_config.build())
61            .set_http_middleware(middleware)
62            .build(socket_addr)
63            .await
64            .map_err(|err| RpcError::server_error(err, ServerKind::Auth(socket_addr)))?;
65
66        let local_addr = server
67            .local_addr()
68            .map_err(|err| RpcError::server_error(err, ServerKind::Auth(socket_addr)))?;
69
70        let handle = server.start(module.inner.clone());
71        let mut ipc_handle: Option<jsonrpsee::server::ServerHandle> = None;
72
73        if let Some(ipc_server_config) = ipc_server_config {
74            let ipc_endpoint_str = ipc_endpoint
75                .clone()
76                .unwrap_or_else(|| constants::DEFAULT_ENGINE_API_IPC_ENDPOINT.to_string());
77            let ipc_server = ipc_server_config.build(ipc_endpoint_str);
78            let res = ipc_server.start(module.inner).await?;
79            ipc_handle = Some(res);
80        }
81
82        Ok(AuthServerHandle { handle: Some(handle), local_addr, secret, ipc_endpoint, ipc_handle })
83    }
84}
85
86/// Builder type for configuring an `AuthServerConfig`.
87#[derive(Debug)]
88pub struct AuthServerConfigBuilder {
89    socket_addr: Option<SocketAddr>,
90    secret: JwtSecret,
91    server_config: Option<ServerConfigBuilder>,
92    ipc_server_config: Option<IpcServerBuilder<Identity, Identity>>,
93    ipc_endpoint: Option<String>,
94}
95
96// === impl AuthServerConfigBuilder ===
97
98impl AuthServerConfigBuilder {
99    /// Create a new `AuthServerConfigBuilder` with the given `secret`.
100    pub const fn new(secret: JwtSecret) -> Self {
101        Self {
102            socket_addr: None,
103            secret,
104            server_config: None,
105            ipc_server_config: None,
106            ipc_endpoint: None,
107        }
108    }
109
110    /// Set the socket address for the server.
111    pub const fn socket_addr(mut self, socket_addr: SocketAddr) -> Self {
112        self.socket_addr = Some(socket_addr);
113        self
114    }
115
116    /// Set the socket address for the server.
117    pub const fn maybe_socket_addr(mut self, socket_addr: Option<SocketAddr>) -> Self {
118        self.socket_addr = socket_addr;
119        self
120    }
121
122    /// Set the secret for the server.
123    pub const fn secret(mut self, secret: JwtSecret) -> Self {
124        self.secret = secret;
125        self
126    }
127
128    /// Configures the JSON-RPC server
129    ///
130    /// Note: this always configures an [`EthSubscriptionIdProvider`]
131    /// [`IdProvider`](jsonrpsee::server::IdProvider) for convenience.
132    pub fn with_server_config(mut self, config: ServerConfigBuilder) -> Self {
133        self.server_config = Some(config.set_id_provider(EthSubscriptionIdProvider::default()));
134        self
135    }
136
137    /// Set the ipc endpoint for the server.
138    pub fn ipc_endpoint(mut self, ipc_endpoint: String) -> Self {
139        self.ipc_endpoint = Some(ipc_endpoint);
140        self
141    }
142
143    /// Configures the IPC server
144    ///
145    /// Note: this always configures an [`EthSubscriptionIdProvider`]
146    pub fn with_ipc_config(mut self, config: IpcServerBuilder<Identity, Identity>) -> Self {
147        self.ipc_server_config = Some(config.set_id_provider(EthSubscriptionIdProvider::default()));
148        self
149    }
150
151    /// Build the `AuthServerConfig`.
152    pub fn build(self) -> AuthServerConfig {
153        AuthServerConfig {
154            socket_addr: self.socket_addr.unwrap_or_else(|| {
155                SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), constants::DEFAULT_AUTH_PORT)
156            }),
157            secret: self.secret,
158            server_config: self.server_config.unwrap_or_else(|| {
159                ServerConfig::builder()
160                    // This needs to large enough to handle large eth_getLogs responses and
161                    // maximum payload bodies limit for
162                    // `engine_getPayloadBodiesByRangeV` ~750MB per
163                    // response should be enough
164                    .max_response_body_size(750 * 1024 * 1024)
165                    // Connections to this server are always authenticated, hence this only
166                    // affects connections from the CL or any other
167                    // client that uses JWT, this should be
168                    // more than enough so that the CL (or multiple CL nodes) will never get
169                    // rate limited
170                    .max_connections(500)
171                    // bump the default request size slightly, there aren't any methods exposed
172                    // with dynamic request params that can exceed this
173                    .max_request_body_size(128 * 1024 * 1024)
174                    .set_id_provider(EthSubscriptionIdProvider::default())
175            }),
176            ipc_server_config: self.ipc_server_config.map(|ipc_server_config| {
177                ipc_server_config
178                    .max_response_body_size(750 * 1024 * 1024)
179                    .max_connections(500)
180                    .max_request_body_size(128 * 1024 * 1024)
181                    .set_id_provider(EthSubscriptionIdProvider::default())
182            }),
183            ipc_endpoint: self.ipc_endpoint,
184        }
185    }
186}
187
188/// Holds installed modules for the auth server.
189#[derive(Debug, Clone)]
190pub struct AuthRpcModule {
191    pub(crate) inner: RpcModule<()>,
192}
193
194impl AuthRpcModule {
195    /// Create a new `AuthRpcModule` with the given `engine_api`.
196    pub fn new(engine: impl IntoEngineApiRpcModule) -> Self {
197        Self { inner: engine.into_rpc_module() }
198    }
199
200    /// Get a reference to the inner `RpcModule`.
201    pub const fn module_mut(&mut self) -> &mut RpcModule<()> {
202        &mut self.inner
203    }
204
205    /// Merge the given [Methods] in the configured authenticated methods.
206    ///
207    /// Fails if any of the methods in other is present already.
208    pub fn merge_auth_methods(
209        &mut self,
210        other: impl Into<Methods>,
211    ) -> Result<bool, RegisterMethodError> {
212        self.module_mut().merge(other.into()).map(|_| true)
213    }
214
215    /// Removes the method with the given name from the configured authenticated methods.
216    ///
217    /// Returns `true` if the method was found and removed, `false` otherwise.
218    pub fn remove_auth_method(&mut self, method_name: &'static str) -> bool {
219        self.module_mut().remove_method(method_name).is_some()
220    }
221
222    /// Removes the given methods from the configured authenticated methods.
223    pub fn remove_auth_methods(&mut self, methods: impl IntoIterator<Item = &'static str>) {
224        for name in methods {
225            self.remove_auth_method(name);
226        }
227    }
228
229    /// Replace the given [Methods] in the configured authenticated methods.
230    pub fn replace_auth_methods(
231        &mut self,
232        other: impl Into<Methods>,
233    ) -> Result<bool, RegisterMethodError> {
234        let other = other.into();
235        self.remove_auth_methods(other.method_names());
236        self.merge_auth_methods(other)
237    }
238
239    /// Convenience function for starting a server
240    pub async fn start_server(
241        self,
242        config: AuthServerConfig,
243    ) -> Result<AuthServerHandle, RpcError> {
244        config.start(self).await
245    }
246}
247
248/// A handle to the spawned auth server.
249///
250/// When this type is dropped or [`AuthServerHandle::stop`] has been called the server will be
251/// stopped.
252#[derive(Clone, Debug)]
253#[must_use = "Server stops if dropped"]
254pub struct AuthServerHandle {
255    local_addr: SocketAddr,
256    handle: Option<jsonrpsee::server::ServerHandle>,
257    secret: JwtSecret,
258    ipc_endpoint: Option<String>,
259    ipc_handle: Option<jsonrpsee::server::ServerHandle>,
260}
261
262// === impl AuthServerHandle ===
263
264impl AuthServerHandle {
265    /// Creates a new handle that isn't connected to any server.
266    ///
267    /// This can be used to satisfy types that require an engine API.
268    pub fn noop() -> Self {
269        Self {
270            local_addr: SocketAddr::new(
271                IpAddr::V4(Ipv4Addr::LOCALHOST),
272                constants::DEFAULT_AUTH_PORT,
273            ),
274            handle: None,
275            secret: JwtSecret::random(),
276            ipc_endpoint: None,
277            ipc_handle: None,
278        }
279    }
280
281    /// Returns the [`SocketAddr`] of the http server if started.
282    pub const fn local_addr(&self) -> SocketAddr {
283        self.local_addr
284    }
285
286    /// Tell the server to stop without waiting for the server to stop.
287    pub fn stop(self) -> Result<(), AlreadyStoppedError> {
288        let Some(handle) = self.handle else { return Ok(()) };
289        handle.stop()
290    }
291
292    /// Returns the url to the http server
293    pub fn http_url(&self) -> String {
294        format!("http://{}", self.local_addr)
295    }
296
297    /// Returns the url to the ws server
298    pub fn ws_url(&self) -> String {
299        format!("ws://{}", self.local_addr)
300    }
301
302    /// Returns a http client connected to the server.
303    ///
304    /// This client uses the JWT token to authenticate requests.
305    pub fn http_client(&self) -> impl SubscriptionClientT + Clone + Send + Sync + Unpin + 'static {
306        // Create a middleware that adds a new JWT token to every request.
307        let secret_layer = AuthClientLayer::new(self.secret);
308        let middleware = tower::ServiceBuilder::default().layer(secret_layer);
309        jsonrpsee::http_client::HttpClientBuilder::default()
310            .set_http_middleware(middleware)
311            .build(self.http_url())
312            .expect("Failed to create http client")
313    }
314
315    /// Returns a ws client connected to the server. Note that the connection can only be
316    /// be established within 1 minute due to the JWT token expiration.
317    pub async fn ws_client(&self) -> jsonrpsee::ws_client::WsClient {
318        jsonrpsee::ws_client::WsClientBuilder::default()
319            .set_headers(HeaderMap::from_iter([(
320                AUTHORIZATION,
321                secret_to_bearer_header(&self.secret),
322            )]))
323            .build(self.ws_url())
324            .await
325            .expect("Failed to create ws client")
326    }
327
328    /// Returns an ipc client connected to the server.
329    #[cfg(unix)]
330    pub async fn ipc_client(&self) -> Option<jsonrpsee::async_client::Client> {
331        use reth_ipc::client::IpcClientBuilder;
332
333        if let Some(ipc_endpoint) = &self.ipc_endpoint {
334            return Some(
335                IpcClientBuilder::default()
336                    .build(ipc_endpoint)
337                    .await
338                    .expect("Failed to create ipc client"),
339            )
340        }
341        None
342    }
343
344    /// Returns an ipc handle
345    pub fn ipc_handle(&self) -> Option<jsonrpsee::server::ServerHandle> {
346        self.ipc_handle.clone()
347    }
348
349    /// Return an ipc endpoint
350    pub fn ipc_endpoint(&self) -> Option<String> {
351        self.ipc_endpoint.clone()
352    }
353}