Skip to main content

zebra_rpc/server/
rpc_call_compatibility.rs

1//! Compatibility fixes for JSON-RPC remote procedure calls.
2//!
3//! These fixes are applied at the JSON-RPC call level,
4//! after the RPC request is parsed and split into calls.
5
6use jsonrpsee::{
7    server::middleware::rpc::{layer::ResponseFuture, RpcServiceT},
8    MethodResponse,
9};
10use jsonrpsee_types::ErrorObject;
11
12/// JSON-RPC [`FixRpcResponseMiddleware`] with compatibility workarounds.
13///
14/// This middleware makes the following changes to JSON-RPC calls:
15///
16/// ## Make RPC framework response codes match `zcashd`
17///
18/// [`jsonrpsee_types`] returns specific error codes while parsing requests:
19/// <https://docs.rs/jsonrpsee-types/latest/jsonrpsee_types/error/enum.ErrorCode.html>
20///
21/// But these codes are different from `zcashd`, and some RPC clients rely on the exact code.
22/// Specifically, the [`jsonrpsee_types::error::INVALID_PARAMS_CODE`] is different:
23/// <https://docs.rs/jsonrpsee-types/latest/jsonrpsee_types/error/constant.INVALID_PARAMS_CODE.html>
24#[derive(Clone)]
25pub struct FixRpcResponseMiddleware<S> {
26    service: S,
27}
28
29impl<S> FixRpcResponseMiddleware<S> {
30    /// Create a new `FixRpcResponseMiddleware` with the given `service`.
31    pub fn new(service: S) -> Self {
32        Self { service }
33    }
34}
35
36impl<'a, S> RpcServiceT<'a> for FixRpcResponseMiddleware<S>
37where
38    S: RpcServiceT<'a> + Send + Sync + Clone + 'static,
39{
40    type Future = ResponseFuture<futures::future::BoxFuture<'a, jsonrpsee::MethodResponse>>;
41
42    fn call(&self, request: jsonrpsee::types::Request<'a>) -> Self::Future {
43        let service = self.service.clone();
44        ResponseFuture::future(Box::pin(async move {
45            let response = service.call(request).await;
46            if response.is_error() {
47                let original_error_code = response
48                    .as_error_code()
49                    .expect("response should have an error code");
50                if original_error_code == jsonrpsee_types::ErrorCode::InvalidParams.code() {
51                    let new_error_code = crate::server::error::LegacyCode::Misc.into();
52                    tracing::debug!(
53                        "Replacing RPC error: {original_error_code} with {new_error_code}"
54                    );
55                    let json: serde_json::Value =
56                        serde_json::from_str(response.into_parts().0.as_str())
57                            .expect("response string should be valid json");
58                    let id = match &json["id"] {
59                        serde_json::Value::Null => Some(jsonrpsee::types::Id::Null),
60                        serde_json::Value::Number(n) => {
61                            n.as_u64().map(jsonrpsee::types::Id::Number)
62                        }
63                        serde_json::Value::String(s) => Some(jsonrpsee::types::Id::Str(s.into())),
64                        _ => None,
65                    }
66                    .expect("response json should have an id");
67
68                    return MethodResponse::error(
69                        id,
70                        ErrorObject::borrowed(
71                            new_error_code,
72                            json.get("error")
73                                .and_then(|v| v.get("message"))
74                                .and_then(|m| m.as_str())
75                                .unwrap_or("Invalid params"),
76                            None,
77                        ),
78                    );
79                }
80            }
81            response
82        }))
83    }
84}