diff --git a/Cargo.lock b/Cargo.lock index 7460cfc0513..e76d5337b2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3670,6 +3670,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "example-custom-rpc-middleware" +version = "0.0.0" +dependencies = [ + "clap", + "jsonrpsee", + "reth-ethereum", + "tower", + "tracing", +] + [[package]] name = "example-db-access" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index 0294a059f8e..0fc548dbf52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -153,6 +153,7 @@ members = [ "examples/custom-node-components/", "examples/custom-payload-builder/", "examples/custom-rlpx-subprotocol", + "examples/custom-rpc-middleware", "examples/custom-node", "examples/db-access", "examples/engine-api-access", diff --git a/examples/custom-rpc-middleware/Cargo.toml b/examples/custom-rpc-middleware/Cargo.toml new file mode 100644 index 00000000000..92b5975e43e --- /dev/null +++ b/examples/custom-rpc-middleware/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "example-custom-rpc-middleware" +version = "0.0.0" +publish = false +edition.workspace = true +license.workspace = true + +[dependencies] +reth-ethereum = { workspace = true, features = ["node", "rpc", "cli"] } + +clap = { workspace = true, features = ["derive"] } +jsonrpsee = { workspace = true, features = ["server", "macros"] } +tracing.workspace = true +tower.workspace = true diff --git a/examples/custom-rpc-middleware/src/main.rs b/examples/custom-rpc-middleware/src/main.rs new file mode 100644 index 00000000000..003111b6712 --- /dev/null +++ b/examples/custom-rpc-middleware/src/main.rs @@ -0,0 +1,117 @@ +//! Example of how to create a node with custom middleware that alters a returned error message from +//! the RPC +//! +//! Run with +//! +//! ```sh +//! cargo run -p example-custom-rpc-middleware node --http --dev --dev.block-time 12s --http.api=debug,eth +//! ``` +//! +//! Then make an RPC request that will result in an error +//! +//! ```sh +//! curl -s -X POST http://localhost:8545 \ +//! -H "Content-Type: application/json" \ +//! -d '{ +//! "jsonrpc": "2.0", +//! "method": "debug_getRawBlock", +//! "params": ["2"], +//! "id": 1 +//! }' | jq +//! ``` + +use clap::Parser; +use jsonrpsee::{ + core::{ + middleware::{Batch, Notification, RpcServiceT}, + server::MethodResponse, + }, + types::{ErrorObjectOwned, Id, Request}, +}; +use reth_ethereum::{ + cli::{chainspec::EthereumChainSpecParser, interface::Cli}, + node::{EthereumAddOns, EthereumNode}, +}; +use tower::Layer; + +fn main() { + Cli::::parse() + .run(|builder, _| async move { + let handle = builder + .with_types::() + .with_components(EthereumNode::components()) + .with_add_ons( + //create ethereum addons with our custom rpc middleware + EthereumAddOns::default().with_rpc_middleware(ResponseMutationLayer), + ) + .launch_with_debug_capabilities() + .await?; + + handle.wait_for_node_exit().await + }) + .unwrap(); +} + +#[derive(Clone)] +pub struct ResponseMutationLayer; + +impl Layer for ResponseMutationLayer { + type Service = ResponseMutationService; + + fn layer(&self, inner: S) -> Self::Service { + ResponseMutationService { service: inner } + } +} + +#[derive(Clone)] +pub struct ResponseMutationService { + service: S, +} + +impl RpcServiceT for ResponseMutationService +where + S: RpcServiceT< + MethodResponse = jsonrpsee::MethodResponse, + BatchResponse = jsonrpsee::MethodResponse, + NotificationResponse = jsonrpsee::MethodResponse, + > + Send + + Sync + + Clone + + 'static, +{ + type MethodResponse = S::MethodResponse; + type NotificationResponse = S::NotificationResponse; + type BatchResponse = S::BatchResponse; + + fn call<'a>(&self, req: Request<'a>) -> impl Future + Send + 'a { + tracing::info!("processed call {:?}", req); + let service = self.service.clone(); + Box::pin(async move { + let resp = service.call(req).await; + + //we can modify the response with our own custom error + if resp.is_error() { + let err = ErrorObjectOwned::owned( + -31404, + "CustomError", + Some("Our very own custom error message"), + ); + return MethodResponse::error(Id::Number(1), err); + } + + //otherwise just return the original response + resp + }) + } + + fn batch<'a>(&self, req: Batch<'a>) -> impl Future + Send + 'a { + self.service.batch(req) + } + + fn notification<'a>( + &self, + n: Notification<'a>, + ) -> impl Future + Send + 'a { + self.service.notification(n) + } +}