Skip to main content

alloy_rpc_client/
call.rs

1use alloy_json_rpc::{
2    transform_response, try_deserialize_ok, Request, RequestMeta, RequestPacket, ResponsePacket,
3    RpcRecv, RpcResult, RpcSend,
4};
5use alloy_transport::{
6    BoxTransport, IntoBoxTransport, RpcFut, TransportError, TransportErrorKind, TransportResult,
7};
8use futures::FutureExt;
9use serde_json::value::RawValue;
10use std::{
11    fmt,
12    future::Future,
13    marker::PhantomData,
14    pin::Pin,
15    task::{self, ready, Poll::Ready},
16};
17use tower::Service;
18
19/// The states of the [`RpcCall`] future.
20#[must_use = "futures do nothing unless you `.await` or poll them"]
21#[pin_project::pin_project(project = CallStateProj)]
22enum CallState<Params>
23where
24    Params: RpcSend,
25{
26    Prepared {
27        request: Option<Request<Params>>,
28        connection: BoxTransport,
29    },
30    AwaitingResponse {
31        #[pin]
32        fut: <BoxTransport as Service<RequestPacket>>::Future,
33    },
34    Complete,
35}
36
37impl<Params> Clone for CallState<Params>
38where
39    Params: RpcSend,
40{
41    fn clone(&self) -> Self {
42        match self {
43            Self::Prepared { request, connection } => {
44                Self::Prepared { request: request.clone(), connection: connection.clone() }
45            }
46            _ => panic!("cloned after dispatch"),
47        }
48    }
49}
50
51impl<Params> fmt::Debug for CallState<Params>
52where
53    Params: RpcSend,
54{
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        f.write_str(match self {
57            Self::Prepared { .. } => "Prepared",
58            Self::AwaitingResponse { .. } => "AwaitingResponse",
59            Self::Complete => "Complete",
60        })
61    }
62}
63
64impl<Params> Future for CallState<Params>
65where
66    Params: RpcSend,
67{
68    type Output = TransportResult<Box<RawValue>>;
69
70    fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<Self::Output> {
71        loop {
72            match self.as_mut().project() {
73                CallStateProj::Prepared { connection, request } => {
74                    if let Err(e) =
75                        task::ready!(Service::<RequestPacket>::poll_ready(connection, cx))
76                    {
77                        self.set(Self::Complete);
78                        return Ready(RpcResult::Err(e));
79                    }
80
81                    let request = request.take().expect("no request");
82                    if tracing::enabled!(tracing::Level::TRACE) {
83                        trace!(?request, "sending request");
84                    } else {
85                        debug!(method=%request.meta.method, id=%request.meta.id, "sending request");
86                    }
87                    let request = request.serialize();
88                    let fut = match request {
89                        Ok(request) => {
90                            trace!(request=%request.serialized(), "serialized request");
91                            connection.call(request.into())
92                        }
93                        Err(err) => {
94                            trace!(?err, "failed to serialize request");
95                            self.set(Self::Complete);
96                            return Ready(RpcResult::Err(TransportError::ser_err(err)));
97                        }
98                    };
99                    self.set(Self::AwaitingResponse { fut });
100                }
101                CallStateProj::AwaitingResponse { fut } => {
102                    let res = match task::ready!(fut.poll(cx)) {
103                        Ok(ResponsePacket::Single(res)) => Ready(transform_response(res)),
104                        Err(e) => Ready(RpcResult::Err(e)),
105                        Ok(ResponsePacket::Batch(_)) => {
106                            Ready(RpcResult::Err(TransportErrorKind::custom_str(
107                                "received batch response from single request",
108                            )))
109                        }
110                    };
111                    self.set(Self::Complete);
112                    return res;
113                }
114                CallStateProj::Complete => {
115                    panic!("Polled after completion");
116                }
117            }
118        }
119    }
120}
121
122/// A prepared, but unsent, RPC call.
123///
124/// This is a future that will send the request when polled. It contains a
125/// [`Request`], a [`BoxTransport`], and knowledge of its expected response
126/// type. Upon awaiting, it will send the request and wait for the response. It
127/// will then deserialize the response into the expected type.
128///
129/// Errors are captured in the [`RpcResult`] type. Rpc Calls will result in
130/// either a successful response of the `Resp` type, an error response, or a
131/// transport error.
132///
133/// ### Note
134///
135/// Serializing the request is done lazily. The request is not serialized until
136/// the future is polled. This differs from the behavior of
137/// [`crate::BatchRequest`], which serializes greedily. This is because the
138/// batch request must immediately erase the `Param` type to allow batching of
139/// requests with different `Param` types, while the `RpcCall` may do so lazily.
140///
141/// ### Cloning and cancellation
142///
143/// A call can be cloned only while it is still prepared, normally before its
144/// first poll. Each clone keeps the same JSON-RPC ID, and polling multiple
145/// clones sends the request multiple times with that ID. Cloning after
146/// dispatch panics.
147///
148/// Dropping an unpolled call leaves it unsent. Dropping a dispatched call stops
149/// waiting locally, but does not guarantee that the remote operation is
150/// cancelled.
151#[must_use = "futures do nothing unless you `.await` or poll them"]
152#[pin_project::pin_project]
153#[derive(Clone)]
154pub struct RpcCall<Params, Resp, Output = Resp, Map = fn(Resp) -> Output>
155where
156    Params: RpcSend,
157    Map: FnOnce(Resp) -> Output,
158{
159    #[pin]
160    state: CallState<Params>,
161    map: Option<Map>,
162    _pd: core::marker::PhantomData<fn() -> (Resp, Output)>,
163}
164
165impl<Params, Resp, Output, Map> core::fmt::Debug for RpcCall<Params, Resp, Output, Map>
166where
167    Params: RpcSend,
168    Map: FnOnce(Resp) -> Output,
169{
170    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
171        f.debug_struct("RpcCall").field("state", &self.state).finish()
172    }
173}
174
175impl<Params, Resp> RpcCall<Params, Resp>
176where
177    Params: RpcSend,
178{
179    #[doc(hidden)]
180    pub fn new(req: Request<Params>, connection: impl IntoBoxTransport) -> Self {
181        Self {
182            state: CallState::Prepared {
183                request: Some(req),
184                connection: connection.into_box_transport(),
185            },
186            map: Some(std::convert::identity),
187            _pd: PhantomData,
188        }
189    }
190}
191
192impl<Params, Resp, Output, Map> RpcCall<Params, Resp, Output, Map>
193where
194    Params: RpcSend,
195    Map: FnOnce(Resp) -> Output,
196{
197    /// Map the response to a different type. This is usable for converting
198    /// the response to a more usable type, e.g. changing `U64` to `u64`.
199    ///
200    /// ## Note
201    ///
202    /// Carefully review the rust documentation on [fn pointers] before passing
203    /// them to this function. Unless the pointer is specifically coerced to a
204    /// `fn(_) -> _`, the `NewMap` will be inferred as that function's unique
205    /// type. This can lead to confusing error messages.
206    ///
207    /// [fn pointers]: https://doc.rust-lang.org/std/primitive.fn.html#creating-function-pointers
208    pub fn map_resp<NewOutput, NewMap>(
209        self,
210        map: NewMap,
211    ) -> RpcCall<Params, Resp, NewOutput, NewMap>
212    where
213        NewMap: FnOnce(Resp) -> NewOutput,
214    {
215        RpcCall { state: self.state, map: Some(map), _pd: PhantomData }
216    }
217
218    /// Returns `true` if the request is a subscription.
219    ///
220    /// # Panics
221    ///
222    /// Panics if called after the request has been sent.
223    pub fn is_subscription(&self) -> bool {
224        self.request().meta.is_subscription()
225    }
226
227    /// Set the request to be a non-standard subscription (i.e. not
228    /// "eth_subscribe").
229    ///
230    /// # Panics
231    ///
232    /// Panics if called after the request has been sent.
233    pub fn set_is_subscription(&mut self) {
234        self.request_mut().meta.set_is_subscription();
235    }
236
237    /// Set the subscription status of the request.
238    pub fn set_subscription_status(&mut self, status: bool) {
239        self.request_mut().meta.set_subscription_status(status);
240    }
241
242    /// Get a mutable reference to the params of the request.
243    ///
244    /// This is useful for modifying the params after the request has been
245    /// prepared.
246    ///
247    /// # Panics
248    ///
249    /// Panics if called after the request has been sent.
250    pub fn params(&mut self) -> &mut Params {
251        &mut self.request_mut().params
252    }
253
254    /// Returns a reference to the request.
255    ///
256    /// # Panics
257    ///
258    /// Panics if called after the request has been sent.
259    pub fn request(&self) -> &Request<Params> {
260        let CallState::Prepared { request, .. } = &self.state else {
261            panic!("Cannot get request after request has been sent");
262        };
263        request.as_ref().expect("no request in prepared")
264    }
265
266    /// Returns the RPC method
267    pub fn method(&self) -> &str {
268        &self.request().meta.method
269    }
270
271    /// Returns a mutable reference to the request.
272    ///
273    /// # Panics
274    ///
275    /// Panics if called after the request has been sent.
276    pub fn request_mut(&mut self) -> &mut Request<Params> {
277        let CallState::Prepared { request, .. } = &mut self.state else {
278            panic!("Cannot get request after request has been sent");
279        };
280        request.as_mut().expect("no request in prepared")
281    }
282
283    /// Map the params of the request into a new type.
284    pub fn map_params<NewParams: RpcSend>(
285        self,
286        map: impl Fn(Params) -> NewParams,
287    ) -> RpcCall<NewParams, Resp, Output, Map> {
288        let CallState::Prepared { request, connection } = self.state else {
289            panic!("Cannot get request after request has been sent");
290        };
291        let request = request.expect("no request in prepared").map_params(map);
292        RpcCall {
293            state: CallState::Prepared { request: Some(request), connection },
294            map: self.map,
295            _pd: PhantomData,
296        }
297    }
298
299    /// Maps the metadata of the request using the provided function.
300    pub fn map_meta(self, f: impl FnOnce(RequestMeta) -> RequestMeta) -> Self {
301        let CallState::Prepared { request, connection } = self.state else {
302            panic!("Cannot get request after request has been sent");
303        };
304        let request = request.expect("no request in prepared").map_meta(f);
305        Self {
306            state: CallState::Prepared { request: Some(request), connection },
307            map: self.map,
308            _pd: PhantomData,
309        }
310    }
311}
312
313impl<Params, Resp, Output, Map> RpcCall<&Params, Resp, Output, Map>
314where
315    Params: RpcSend + ToOwned,
316    Params::Owned: RpcSend,
317    Map: FnOnce(Resp) -> Output,
318{
319    /// Convert this call into one with owned params, by cloning the params.
320    ///
321    /// # Panics
322    ///
323    /// Panics if called after the request has been polled.
324    pub fn into_owned_params(self) -> RpcCall<Params::Owned, Resp, Output, Map> {
325        let CallState::Prepared { request, connection } = self.state else {
326            panic!("Cannot get params after request has been sent");
327        };
328        let request = request.expect("no request in prepared").into_owned_params();
329
330        RpcCall {
331            state: CallState::Prepared { request: Some(request), connection },
332            map: self.map,
333            _pd: PhantomData,
334        }
335    }
336}
337
338impl<'a, Params, Resp, Output, Map> RpcCall<Params, Resp, Output, Map>
339where
340    Params: RpcSend + 'a,
341    Resp: RpcRecv,
342    Output: 'static,
343    Map: FnOnce(Resp) -> Output + Send + 'a,
344{
345    /// Convert this future into a boxed, pinned future, erasing its type.
346    pub fn boxed(self) -> RpcFut<'a, Output> {
347        Box::pin(self)
348    }
349}
350
351impl<Params, Resp, Output, Map> Future for RpcCall<Params, Resp, Output, Map>
352where
353    Params: RpcSend,
354    Resp: RpcRecv,
355    Output: 'static,
356    Map: FnOnce(Resp) -> Output,
357{
358    type Output = TransportResult<Output>;
359
360    fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<Self::Output> {
361        let this = self.get_mut();
362        let resp = try_deserialize_ok(ready!(this.state.poll_unpin(cx)));
363        Ready(resp.map(this.map.take().expect("polled after completion")))
364    }
365}