diff --git a/zebra-rpc/src/server.rs b/zebra-rpc/src/server.rs index c759e32c..dec2ee3f 100644 --- a/zebra-rpc/src/server.rs +++ b/zebra-rpc/src/server.rs @@ -7,7 +7,7 @@ //! See the full list of //! [Differences between JSON-RPC 1.0 and 2.0.](https://www.simple-is-better.org/rpc/#differences-between-1-0-and-2-0) -use jsonrpc_core; +use jsonrpc_core::{Compatibility, MetaIoHandler}; use jsonrpc_http_server::ServerBuilder; use tokio::task::JoinHandle; use tower::{buffer::Buffer, Service}; @@ -20,10 +20,11 @@ use zebra_node_services::{mempool, BoxError}; use crate::{ config::Config, methods::{Rpc, RpcImpl}, - server::compatibility::FixHttpRequestMiddleware, + server::{compatibility::FixHttpRequestMiddleware, tracing_middleware::TracingMiddleware}, }; pub mod compatibility; +mod tracing_middleware; /// Zebra RPC Server #[derive(Clone, Debug)] @@ -63,8 +64,8 @@ impl RpcServer { RpcImpl::new(app_version, mempool, state, latest_chain_tip, network); // Create handler compatible with V1 and V2 RPC protocols - let mut io = - jsonrpc_core::IoHandler::with_compatibility(jsonrpc_core::Compatibility::Both); + let mut io: MetaIoHandler<(), _> = + MetaIoHandler::new(Compatibility::Both, TracingMiddleware); io.extend_with(rpc_impl.to_delegate()); let server = ServerBuilder::new(io) diff --git a/zebra-rpc/src/server/tracing_middleware.rs b/zebra-rpc/src/server/tracing_middleware.rs new file mode 100644 index 00000000..c4342f6b --- /dev/null +++ b/zebra-rpc/src/server/tracing_middleware.rs @@ -0,0 +1,71 @@ +//! A custom middleware to trace unrecognized RPC requests. + +use std::future::Future; + +use futures::future::{Either, FutureExt}; +use jsonrpc_core::{ + middleware::Middleware, + types::{Call, Failure, Output, Response}, + BoxFuture, Error, ErrorCode, Metadata, MethodCall, Notification, +}; + +/// A custom RPC middleware that logs unrecognized RPC requests. +pub struct TracingMiddleware; + +impl Middleware for TracingMiddleware { + type Future = BoxFuture>; + type CallFuture = BoxFuture>; + + fn on_call( + &self, + call: Call, + meta: M, + next: Next, + ) -> Either + where + Next: Fn(Call, M) -> NextFuture + Send + Sync, + NextFuture: Future> + Send + 'static, + { + Either::Left( + next(call.clone(), meta) + .then(move |output| Self::log_error_if_method_not_found(output, call)) + .boxed(), + ) + } +} + +impl TracingMiddleware { + /// Obtain a description string for a received request. + /// + /// Prints out only the method name and the received parameters. + fn call_description(call: &Call) -> String { + match call { + Call::MethodCall(MethodCall { method, params, .. }) => { + format!(r#"method = {method:?}, params = {params:?}"#) + } + Call::Notification(Notification { method, params, .. }) => { + format!(r#"notification = {method:?}, params = {params:?}"#) + } + Call::Invalid { .. } => "invalid request".to_owned(), + } + } + + /// Check an RPC output and log an error if it indicates the method was not found. + async fn log_error_if_method_not_found(output: Option, call: Call) -> Option { + let call_description = Self::call_description(&call); + + if let Some(Output::Failure(Failure { + error: + Error { + code: ErrorCode::MethodNotFound, + .. + }, + .. + })) = output + { + tracing::warn!("Received unrecognized RPC request: {call_description}"); + } + + output + } +}