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, RpcService, 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>
24pub struct FixRpcResponseMiddleware {
25    service: RpcService,
26}
27
28impl FixRpcResponseMiddleware {
29    /// Create a new `FixRpcResponseMiddleware` with the given `service`.
30    pub fn new(service: RpcService) -> Self {
31        Self { service }
32    }
33}
34
35impl<'a> RpcServiceT<'a> for FixRpcResponseMiddleware {
36    type Future = ResponseFuture<futures::future::BoxFuture<'a, jsonrpsee::MethodResponse>>;
37
38    fn call(&self, request: jsonrpsee::types::Request<'a>) -> Self::Future {
39        let service = self.service.clone();
40        ResponseFuture::future(Box::pin(async move {
41            let response = service.call(request).await;
42            if response.is_error() {
43                let original_error_code = response
44                    .as_error_code()
45                    .expect("response should have an error code");
46                if original_error_code == jsonrpsee_types::ErrorCode::InvalidParams.code() {
47                    let new_error_code = crate::server::error::LegacyCode::Misc.into();
48                    tracing::debug!(
49                        "Replacing RPC error: {original_error_code} with {new_error_code}"
50                    );
51                    let json: serde_json::Value =
52                        serde_json::from_str(response.into_parts().0.as_str())
53                            .expect("response string should be valid json");
54                    let id = match &json["id"] {
55                        serde_json::Value::Null => Some(jsonrpsee::types::Id::Null),
56                        serde_json::Value::Number(n) => {
57                            n.as_u64().map(jsonrpsee::types::Id::Number)
58                        }
59                        serde_json::Value::String(s) => Some(jsonrpsee::types::Id::Str(s.into())),
60                        _ => None,
61                    }
62                    .expect("response json should have an id");
63
64                    return MethodResponse::error(
65                        id,
66                        ErrorObject::borrowed(new_error_code, "Invalid params", None),
67                    );
68                }
69            }
70            response
71        }))
72    }
73}