Skip to content

Commit 265700c

Browse files
authored
feat: add configurable RPC middleware to RpcAddOns (#17024)
1 parent 71b33f1 commit 265700c

File tree

5 files changed

+145
-20
lines changed

5 files changed

+145
-20
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ethereum/node/src/node.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use reth_node_builder::{
2525
node::{FullNodeTypes, NodeTypes},
2626
rpc::{
2727
BasicEngineApiBuilder, EngineApiBuilder, EngineValidatorAddOn, EngineValidatorBuilder,
28-
EthApiBuilder, EthApiCtx, RethRpcAddOns, RpcAddOns, RpcHandle,
28+
EthApiBuilder, EthApiCtx, RethRpcAddOns, RpcAddOns, RpcHandle, RpcServiceBuilder,
2929
},
3030
BuilderContext, DebugNode, Node, NodeAdapter, NodeComponentsBuilder, PayloadBuilderConfig,
3131
PayloadTypes,
@@ -174,6 +174,7 @@ where
174174
EthereumEthApiBuilder,
175175
EthereumEngineValidatorBuilder::default(),
176176
BasicEngineApiBuilder::default(),
177+
RpcServiceBuilder::new(),
177178
),
178179
}
179180
}

crates/node/builder/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ serde_json.workspace = true
8282
# tracing
8383
tracing.workspace = true
8484

85+
# tower
86+
tower.workspace = true
87+
8588
[dev-dependencies]
8689
tempfile.workspace = true
8790

crates/node/builder/src/rpc.rs

Lines changed: 134 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
//! Builder support for rpc components.
22
3+
pub use jsonrpsee::server::middleware::rpc::{RpcService, RpcServiceBuilder};
4+
pub use reth_rpc_builder::{Identity, RpcRequestMetricsService};
5+
36
use crate::{BeaconConsensusEngineEvent, BeaconConsensusEngineHandle};
47
use alloy_rpc_types::engine::ClientVersionV1;
58
use alloy_rpc_types_engine::ExecutionData;
@@ -31,6 +34,7 @@ use std::{
3134
future::Future,
3235
ops::{Deref, DerefMut},
3336
};
37+
use tower::Layer;
3438

3539
/// Contains the handles to the spawned RPC servers.
3640
///
@@ -417,6 +421,7 @@ pub struct RpcAddOns<
417421
EthB: EthApiBuilder<Node>,
418422
EV,
419423
EB = BasicEngineApiBuilder<EV>,
424+
RpcMiddleware = Identity,
420425
> {
421426
/// Additional RPC add-ons.
422427
pub hooks: RpcHooks<Node, EthB::EthApi>,
@@ -426,9 +431,14 @@ pub struct RpcAddOns<
426431
engine_validator_builder: EV,
427432
/// Builder for `EngineApi`
428433
engine_api_builder: EB,
434+
/// Configurable RPC middleware stack.
435+
///
436+
/// This middleware is applied to all RPC requests across all transports (HTTP, WS, IPC).
437+
/// See [`RpcAddOns::with_rpc_middleware`] for more details.
438+
rpc_middleware: RpcServiceBuilder<RpcMiddleware>,
429439
}
430440

431-
impl<Node, EthB, EV, EB> Debug for RpcAddOns<Node, EthB, EV, EB>
441+
impl<Node, EthB, EV, EB, RpcMiddleware> Debug for RpcAddOns<Node, EthB, EV, EB, RpcMiddleware>
432442
where
433443
Node: FullNodeComponents,
434444
EthB: EthApiBuilder<Node>,
@@ -441,11 +451,12 @@ where
441451
.field("eth_api_builder", &"...")
442452
.field("engine_validator_builder", &self.engine_validator_builder)
443453
.field("engine_api_builder", &self.engine_api_builder)
454+
.field("rpc_middleware", &"...")
444455
.finish()
445456
}
446457
}
447458

448-
impl<Node, EthB, EV, EB> RpcAddOns<Node, EthB, EV, EB>
459+
impl<Node, EthB, EV, EB, RpcMiddleware> RpcAddOns<Node, EthB, EV, EB, RpcMiddleware>
449460
where
450461
Node: FullNodeComponents,
451462
EthB: EthApiBuilder<Node>,
@@ -455,28 +466,98 @@ where
455466
eth_api_builder: EthB,
456467
engine_validator_builder: EV,
457468
engine_api_builder: EB,
469+
rpc_middleware: RpcServiceBuilder<RpcMiddleware>,
458470
) -> Self {
459471
Self {
460472
hooks: RpcHooks::default(),
461473
eth_api_builder,
462474
engine_validator_builder,
463475
engine_api_builder,
476+
rpc_middleware,
464477
}
465478
}
466479

467480
/// Maps the [`EngineApiBuilder`] builder type.
468-
pub fn with_engine_api<T>(self, engine_api_builder: T) -> RpcAddOns<Node, EthB, EV, T> {
469-
let Self { hooks, eth_api_builder, engine_validator_builder, .. } = self;
470-
RpcAddOns { hooks, eth_api_builder, engine_validator_builder, engine_api_builder }
481+
pub fn with_engine_api<T>(
482+
self,
483+
engine_api_builder: T,
484+
) -> RpcAddOns<Node, EthB, EV, T, RpcMiddleware> {
485+
let Self { hooks, eth_api_builder, engine_validator_builder, rpc_middleware, .. } = self;
486+
RpcAddOns {
487+
hooks,
488+
eth_api_builder,
489+
engine_validator_builder,
490+
engine_api_builder,
491+
rpc_middleware,
492+
}
471493
}
472494

473495
/// Maps the [`EngineValidatorBuilder`] builder type.
474496
pub fn with_engine_validator<T>(
475497
self,
476498
engine_validator_builder: T,
477-
) -> RpcAddOns<Node, EthB, T, EB> {
478-
let Self { hooks, eth_api_builder, engine_api_builder, .. } = self;
479-
RpcAddOns { hooks, eth_api_builder, engine_validator_builder, engine_api_builder }
499+
) -> RpcAddOns<Node, EthB, T, EB, RpcMiddleware> {
500+
let Self { hooks, eth_api_builder, engine_api_builder, rpc_middleware, .. } = self;
501+
RpcAddOns {
502+
hooks,
503+
eth_api_builder,
504+
engine_validator_builder,
505+
engine_api_builder,
506+
rpc_middleware,
507+
}
508+
}
509+
510+
/// Sets the RPC middleware stack for processing RPC requests.
511+
///
512+
/// This method configures a custom middleware stack that will be applied to all RPC requests
513+
/// across HTTP, `WebSocket`, and IPC transports. The middleware is applied to the RPC service
514+
/// layer, allowing you to intercept, modify, or enhance RPC request processing.
515+
///
516+
///
517+
/// # How It Works
518+
///
519+
/// The middleware uses the Tower ecosystem's `Layer` pattern. When an RPC server is started,
520+
/// the configured middleware stack is applied to create a layered service that processes
521+
/// requests in the order the layers were added.
522+
///
523+
/// # Examples
524+
///
525+
/// ```ignore
526+
/// use reth_rpc_builder::{RpcServiceBuilder, RpcRequestMetrics};
527+
/// use tower::Layer;
528+
///
529+
/// // Simple example with metrics
530+
/// let metrics_layer = RpcRequestMetrics::new(metrics_recorder);
531+
/// let with_metrics = rpc_addons.with_rpc_middleware(
532+
/// RpcServiceBuilder::new().layer(metrics_layer)
533+
/// );
534+
///
535+
/// // Composing multiple middleware layers
536+
/// let middleware_stack = RpcServiceBuilder::new()
537+
/// .layer(rate_limit_layer)
538+
/// .layer(logging_layer)
539+
/// .layer(metrics_layer);
540+
/// let with_full_stack = rpc_addons.with_rpc_middleware(middleware_stack);
541+
/// ```
542+
///
543+
/// # Notes
544+
///
545+
/// - Middleware is applied to the RPC service layer, not the HTTP transport layer
546+
/// - The default middleware is `Identity` (no-op), which passes through requests unchanged
547+
/// - Middleware layers are applied in the order they are added via `.layer()`
548+
pub fn with_rpc_middleware<T>(
549+
self,
550+
rpc_middleware: RpcServiceBuilder<T>,
551+
) -> RpcAddOns<Node, EthB, EV, EB, T> {
552+
let Self { hooks, eth_api_builder, engine_validator_builder, engine_api_builder, .. } =
553+
self;
554+
RpcAddOns {
555+
hooks,
556+
eth_api_builder,
557+
engine_validator_builder,
558+
engine_api_builder,
559+
rpc_middleware,
560+
}
480561
}
481562

482563
/// Sets the hook that is run once the rpc server is started.
@@ -500,25 +581,35 @@ where
500581
}
501582
}
502583

503-
impl<Node, EthB, EV, EB> Default for RpcAddOns<Node, EthB, EV, EB>
584+
impl<Node, EthB, EV, EB> Default for RpcAddOns<Node, EthB, EV, EB, Identity>
504585
where
505586
Node: FullNodeComponents,
506587
EthB: EthApiBuilder<Node>,
507588
EV: Default,
508589
EB: Default,
509590
{
510591
fn default() -> Self {
511-
Self::new(EthB::default(), EV::default(), EB::default())
592+
Self::new(EthB::default(), EV::default(), EB::default(), RpcServiceBuilder::new())
512593
}
513594
}
514595

515-
impl<N, EthB, EV, EB> RpcAddOns<N, EthB, EV, EB>
596+
impl<N, EthB, EV, EB, RpcMiddleware> RpcAddOns<N, EthB, EV, EB, RpcMiddleware>
516597
where
517598
N: FullNodeComponents,
518599
N::Provider: ChainSpecProvider<ChainSpec: EthereumHardforks>,
519600
EthB: EthApiBuilder<N>,
520601
EV: EngineValidatorBuilder<N>,
521602
EB: EngineApiBuilder<N>,
603+
RpcMiddleware: Layer<RpcRequestMetricsService<RpcService>> + Clone + Send + 'static,
604+
<RpcMiddleware as Layer<RpcRequestMetricsService<RpcService>>>::Service:
605+
Send
606+
+ Sync
607+
+ 'static
608+
+ jsonrpsee::server::middleware::rpc::RpcServiceT<
609+
MethodResponse = jsonrpsee::MethodResponse,
610+
BatchResponse = jsonrpsee::MethodResponse,
611+
NotificationResponse = jsonrpsee::MethodResponse,
612+
>,
522613
{
523614
/// Launches only the regular RPC server (HTTP/WS/IPC), without the authenticated Engine API
524615
/// server.
@@ -533,6 +624,7 @@ where
533624
where
534625
F: FnOnce(RpcModuleContainer<'_, N, EthB::EthApi>) -> eyre::Result<()>,
535626
{
627+
let rpc_middleware = self.rpc_middleware.clone();
536628
let setup_ctx = self.setup_rpc_components(ctx, ext).await?;
537629
let RpcSetupContext {
538630
node,
@@ -546,7 +638,7 @@ where
546638
engine_handle,
547639
} = setup_ctx;
548640

549-
let server_config = config.rpc.rpc_server_config();
641+
let server_config = config.rpc.rpc_server_config().set_rpc_middleware(rpc_middleware);
550642
let rpc_server_handle = Self::launch_rpc_server_internal(server_config, &modules).await?;
551643

552644
let handles =
@@ -579,6 +671,7 @@ where
579671
where
580672
F: FnOnce(RpcModuleContainer<'_, N, EthB::EthApi>) -> eyre::Result<()>,
581673
{
674+
let rpc_middleware = self.rpc_middleware.clone();
582675
let setup_ctx = self.setup_rpc_components(ctx, ext).await?;
583676
let RpcSetupContext {
584677
node,
@@ -592,7 +685,7 @@ where
592685
engine_handle,
593686
} = setup_ctx;
594687

595-
let server_config = config.rpc.rpc_server_config();
688+
let server_config = config.rpc.rpc_server_config().set_rpc_middleware(rpc_middleware);
596689
let auth_module_clone = auth_module.clone();
597690

598691
// launch servers concurrently
@@ -706,10 +799,22 @@ where
706799
}
707800

708801
/// Helper to launch the RPC server
709-
async fn launch_rpc_server_internal(
710-
server_config: RpcServerConfig,
802+
async fn launch_rpc_server_internal<M>(
803+
server_config: RpcServerConfig<M>,
711804
modules: &TransportRpcModules,
712-
) -> eyre::Result<RpcServerHandle> {
805+
) -> eyre::Result<RpcServerHandle>
806+
where
807+
M: Layer<RpcRequestMetricsService<RpcService>> + Clone + Send + 'static,
808+
for<'a> <M as Layer<RpcRequestMetricsService<RpcService>>>::Service:
809+
Send
810+
+ Sync
811+
+ 'static
812+
+ jsonrpsee::server::middleware::rpc::RpcServiceT<
813+
MethodResponse = jsonrpsee::MethodResponse,
814+
BatchResponse = jsonrpsee::MethodResponse,
815+
NotificationResponse = jsonrpsee::MethodResponse,
816+
>,
817+
{
713818
let handle = server_config.start(modules).await?;
714819

715820
if let Some(path) = handle.ipc_endpoint() {
@@ -760,13 +865,23 @@ where
760865
}
761866
}
762867

763-
impl<N, EthB, EV, EB> NodeAddOns<N> for RpcAddOns<N, EthB, EV, EB>
868+
impl<N, EthB, EV, EB, RpcMiddleware> NodeAddOns<N> for RpcAddOns<N, EthB, EV, EB, RpcMiddleware>
764869
where
765870
N: FullNodeComponents,
766871
<N as FullNodeTypes>::Provider: ChainSpecProvider<ChainSpec: EthereumHardforks>,
767872
EthB: EthApiBuilder<N>,
768873
EV: EngineValidatorBuilder<N>,
769874
EB: EngineApiBuilder<N>,
875+
RpcMiddleware: Layer<RpcRequestMetricsService<RpcService>> + Clone + Send + 'static,
876+
<RpcMiddleware as Layer<RpcRequestMetricsService<RpcService>>>::Service:
877+
Send
878+
+ Sync
879+
+ 'static
880+
+ jsonrpsee::server::middleware::rpc::RpcServiceT<
881+
MethodResponse = jsonrpsee::MethodResponse,
882+
BatchResponse = jsonrpsee::MethodResponse,
883+
NotificationResponse = jsonrpsee::MethodResponse,
884+
>,
770885
{
771886
type Handle = RpcHandle<N, EthB::EthApi>;
772887

@@ -787,7 +902,8 @@ pub trait RethRpcAddOns<N: FullNodeComponents>:
787902
fn hooks_mut(&mut self) -> &mut RpcHooks<N, Self::EthApi>;
788903
}
789904

790-
impl<N: FullNodeComponents, EthB, EV, EB> RethRpcAddOns<N> for RpcAddOns<N, EthB, EV, EB>
905+
impl<N: FullNodeComponents, EthB, EV, EB, RpcMiddleware> RethRpcAddOns<N>
906+
for RpcAddOns<N, EthB, EV, EB, RpcMiddleware>
791907
where
792908
Self: NodeAddOns<N, Handle = RpcHandle<N, EthB::EthApi>>,
793909
EthB: EthApiBuilder<N>,

crates/optimism/node/src/node.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use reth_node_builder::{
2727
node::{FullNodeTypes, NodeTypes},
2828
rpc::{
2929
EngineApiBuilder, EngineValidatorAddOn, EngineValidatorBuilder, EthApiBuilder,
30-
RethRpcAddOns, RethRpcServerHandles, RpcAddOns, RpcContext, RpcHandle,
30+
RethRpcAddOns, RethRpcServerHandles, RpcAddOns, RpcContext, RpcHandle, RpcServiceBuilder,
3131
},
3232
BuilderContext, DebugNode, Node, NodeAdapter, NodeComponentsBuilder,
3333
};
@@ -255,6 +255,9 @@ impl NodeTypes for OpNode {
255255
}
256256

257257
/// Add-ons w.r.t. optimism.
258+
///
259+
/// This type provides optimism-specific addons to the node and exposes the RPC server and engine
260+
/// API.
258261
#[derive(Debug)]
259262
pub struct OpAddOns<N: FullNodeComponents, EthB: EthApiBuilder<N>, EV, EB> {
260263
/// Rpc add-ons responsible for launching the RPC servers and instantiating the RPC handlers
@@ -591,6 +594,7 @@ impl<NetworkT> OpAddOnsBuilder<NetworkT> {
591594
.with_min_suggested_priority_fee(min_suggested_priority_fee),
592595
EV::default(),
593596
EB::default(),
597+
RpcServiceBuilder::new(),
594598
),
595599
da_config: da_config.unwrap_or_default(),
596600
sequencer_url,

0 commit comments

Comments
 (0)