Skip to main content

reth_ipc/server/
ipc.rs

1//! IPC request handling adapted from [`jsonrpsee`] http request handling
2
3use futures::{stream::FuturesOrdered, FutureExt, StreamExt};
4use jsonrpsee::{
5    batch_response_error,
6    core::{server::helpers::prepare_error, JsonRawValue},
7    server::middleware::rpc::RpcServiceT,
8    types::{
9        error::{reject_too_big_request, ErrorCode},
10        ErrorObject, Id, InvalidRequest, Notification, Request,
11    },
12    BatchResponseBuilder, MethodResponse,
13};
14use std::{future::Future, panic::AssertUnwindSafe, sync::Arc};
15use tokio::sync::OwnedSemaphorePermit;
16use tokio_util::either::Either;
17use tracing::instrument;
18
19type Notif<'a> = Notification<'a, Option<&'a JsonRawValue>>;
20
21#[derive(Debug, Clone)]
22pub(crate) struct Batch<S> {
23    data: Vec<u8>,
24    rpc_service: S,
25}
26
27// Batch responses must be sent back as a single message so we read the results from each
28// request in the batch and read the results off of a new channel, `rx_batch`, and then send the
29// complete batch response back to the client over `tx`.
30#[instrument(name = "batch", skip(b))]
31pub(crate) async fn process_batch_request<S>(
32    b: Batch<S>,
33    max_response_body_size: usize,
34) -> Option<String>
35where
36    S: RpcServiceT<MethodResponse = MethodResponse> + Send,
37{
38    let Batch { data, rpc_service } = b;
39
40    if let Ok(batch) = serde_json::from_slice::<Vec<&JsonRawValue>>(&data) {
41        let mut got_notif = false;
42        let mut batch_response = BatchResponseBuilder::new_with_limit(max_response_body_size);
43
44        let mut pending_calls: FuturesOrdered<_> = batch
45            .into_iter()
46            .filter_map(|v| {
47                if let Ok(req) = serde_json::from_str::<Request<'_>>(v.get()) {
48                    Some(Either::Right(catch_call_panic(req.id(), rpc_service.call(req))))
49                } else if let Ok(_notif) = serde_json::from_str::<Notif<'_>>(v.get()) {
50                    // notifications should not be answered.
51                    got_notif = true;
52                    None
53                } else {
54                    // valid JSON but could be not parsable as `InvalidRequest`
55                    let id = match serde_json::from_str::<InvalidRequest<'_>>(v.get()) {
56                        Ok(err) => err.id,
57                        Err(_) => Id::Null,
58                    };
59
60                    Some(Either::Left(async {
61                        MethodResponse::error(id, ErrorObject::from(ErrorCode::InvalidRequest))
62                    }))
63                }
64            })
65            .collect();
66
67        while let Some(response) = pending_calls.next().await {
68            if let Err(too_large) = batch_response.append(response) {
69                return Some(raw_response_into_string(too_large.into_json()))
70            }
71        }
72
73        if got_notif && batch_response.is_empty() {
74            None
75        } else {
76            let batch_resp = batch_response.finish();
77            Some(raw_response_into_string(MethodResponse::from_batch(batch_resp).into_json()))
78        }
79    } else {
80        Some(raw_response_into_string(batch_response_error(
81            Id::Null,
82            ErrorObject::from(ErrorCode::ParseError),
83        )))
84    }
85}
86
87pub(crate) async fn process_single_request<S>(
88    data: Vec<u8>,
89    rpc_service: &S,
90) -> Option<MethodResponse>
91where
92    S: RpcServiceT<MethodResponse = MethodResponse> + Send,
93{
94    if let Ok(req) = serde_json::from_slice::<Request<'_>>(&data) {
95        Some(execute_call_with_tracing(req, rpc_service).await)
96    } else if serde_json::from_slice::<Notif<'_>>(&data).is_ok() {
97        None
98    } else {
99        let (id, code) = prepare_error(&data);
100        Some(MethodResponse::error(id, ErrorObject::from(code)))
101    }
102}
103
104#[instrument(name = "method_call", fields(method = req.method.as_ref()), skip(req, rpc_service))]
105pub(crate) async fn execute_call_with_tracing<'a, S>(
106    req: Request<'a>,
107    rpc_service: &S,
108) -> MethodResponse
109where
110    S: RpcServiceT<MethodResponse = MethodResponse> + Send,
111{
112    catch_call_panic(req.id(), rpc_service.call(req)).await
113}
114
115/// Answers a panicking call with an internal error, otherwise the panic would only surface as a
116/// failed call task and the client would never receive a response for `id`.
117async fn catch_call_panic(
118    id: Id<'_>,
119    call: impl Future<Output = MethodResponse>,
120) -> MethodResponse {
121    AssertUnwindSafe(call)
122        .catch_unwind()
123        .await
124        .unwrap_or_else(|_| MethodResponse::error(id, ErrorObject::from(ErrorCode::InternalError)))
125}
126
127pub(crate) async fn call_with_service<S>(
128    request: String,
129    rpc_service: S,
130    max_response_body_size: usize,
131    max_request_body_size: usize,
132    conn: Arc<OwnedSemaphorePermit>,
133) -> Option<String>
134where
135    S: RpcServiceT<MethodResponse = MethodResponse> + Send,
136{
137    enum Kind {
138        Single,
139        Batch,
140    }
141
142    let request_kind = request
143        .chars()
144        .find_map(|c| match c {
145            '{' => Some(Kind::Single),
146            '[' => Some(Kind::Batch),
147            _ => None,
148        })
149        .unwrap_or(Kind::Single);
150
151    let data = request.into_bytes();
152    if data.len() > max_request_body_size {
153        return Some(raw_response_into_string(batch_response_error(
154            Id::Null,
155            reject_too_big_request(max_request_body_size as u32),
156        )))
157    }
158
159    // Single request or notification
160    let res = if matches!(request_kind, Kind::Single) {
161        let response = process_single_request(data, &rpc_service).await;
162        match response {
163            Some(response) if response.is_method_call() => {
164                Some(raw_response_into_string(response.into_json()))
165            }
166            _ => {
167                // subscription responses are sent directly over the sink, return a response here
168                // would lead to duplicate responses for the subscription response
169                None
170            }
171        }
172    } else {
173        process_batch_request(Batch { data, rpc_service }, max_response_body_size).await
174    };
175
176    drop(conn);
177
178    res
179}
180
181/// Converts an already serialized JSON-RPC response into the `String` the connection writes.
182///
183/// `Box<RawValue>` is a `Box<str>` under the hood, so this reuses the existing allocation instead
184/// of copying the response body.
185fn raw_response_into_string(raw: Box<JsonRawValue>) -> String {
186    String::from(Box::<str>::from(raw))
187}