Skip to main content

solana_pubsub_client/nonblocking/
pubsub_client.rs

1//! A client for subscribing to messages from the RPC server.
2//!
3//! The [`PubsubClient`] implements [Solana WebSocket event
4//! subscriptions][spec].
5//!
6//! [spec]: https://solana.com/docs/rpc/websocket
7//!
8//! This is a nonblocking (async) API. For a blocking API use the synchronous
9//! client in [`crate::pubsub_client`].
10//!
11//! A single `PubsubClient` client may be used to subscribe to many events via
12//! subscription methods like [`PubsubClient::account_subscribe`]. These methods
13//! return a [`PubsubClientResult`] of a pair, the first element being a
14//! [`BoxStream`] of subscription-specific [`RpcResponse`]s, the second being an
15//! unsubscribe closure, an asynchronous function that can be called and
16//! `await`ed to unsubscribe.
17//!
18//! Note that `BoxStream` contains an immutable reference to the `PubsubClient`
19//! that created it. This makes `BoxStream` not `Send`, forcing it to stay in
20//! the same task as its `PubsubClient`. `PubsubClient` though is `Send` and
21//! `Sync`, and can be shared between tasks by putting it in an `Arc`. Thus
22//! one viable pattern to creating multiple subscriptions is:
23//!
24//! - create an `Arc<PubsubClient>`
25//! - spawn one task for each subscription, sharing the `PubsubClient`.
26//! - in each task:
27//!   - create a subscription
28//!   - send the `UnsubscribeFn` to another task to handle shutdown
29//!   - loop while receiving messages from the subscription
30//!
31//! This pattern is illustrated in the example below.
32//!
33//! By default the [`block_subscribe`] and [`vote_subscribe`] events are
34//! disabled on RPC nodes. They can be enabled by passing
35//! `--rpc-pubsub-enable-block-subscription` and
36//! `--rpc-pubsub-enable-vote-subscription` to `agave-validator`. When these
37//! methods are disabled, the RPC server will return a "Method not found" error
38//! message.
39//!
40//! [`block_subscribe`]: https://docs.rs/solana-rpc/latest/solana_rpc/rpc_pubsub/trait.RpcSolPubSub.html#tymethod.block_subscribe
41//! [`vote_subscribe`]: https://docs.rs/solana-rpc/latest/solana_rpc/rpc_pubsub/trait.RpcSolPubSub.html#tymethod.vote_subscribe
42//!
43//! # Examples
44//!
45//! Demo two async `PubsubClient` subscriptions with clean shutdown.
46//!
47//! This spawns a task for each subscription type, each of which subscribes and
48//! sends back a ready message and an unsubscribe channel (closure), then loops
49//! on printing messages. The main task then waits for user input before
50//! unsubscribing and waiting on the tasks.
51//!
52//! ```
53//! use anyhow::Result;
54//! use futures_util::StreamExt;
55//! use solana_pubsub_client::nonblocking::pubsub_client::PubsubClient;
56//! use std::sync::Arc;
57//! use tokio::io::AsyncReadExt;
58//! use tokio::sync::mpsc::unbounded_channel;
59//!
60//! pub async fn watch_subscriptions(
61//!     websocket_url: &str,
62//! ) -> Result<()> {
63//!
64//!     // Subscription tasks will send a ready signal when they have subscribed.
65//!     let (ready_sender, mut ready_receiver) = unbounded_channel::<()>();
66//!
67//!     // Channel to receive unsubscribe channels (actually closures).
68//!     // These receive a pair of `(Box<dyn FnOnce() -> BoxFuture<'static, ()> + Send>), &'static str)`,
69//!     // where the first is a closure to call to unsubscribe, the second is the subscription name.
70//!     let (unsubscribe_sender, mut unsubscribe_receiver) = unbounded_channel::<(_, &'static str)>();
71//!
72//!     // The `PubsubClient` must be `Arc`ed to share it across tasks.
73//!     let pubsub_client = Arc::new(PubsubClient::new(websocket_url).await?);
74//!
75//!     let mut join_handles = vec![];
76//!
77//!     join_handles.push(("slot", tokio::spawn({
78//!         // Clone things we need before moving their clones into the `async move` block.
79//!         //
80//!         // The subscriptions have to be made from the tasks that will receive the subscription messages,
81//!         // because the subscription streams hold a reference to the `PubsubClient`.
82//!         // Otherwise we would just subscribe on the main task and send the receivers out to other tasks.
83//!
84//!         let ready_sender = ready_sender.clone();
85//!         let unsubscribe_sender = unsubscribe_sender.clone();
86//!         let pubsub_client = Arc::clone(&pubsub_client);
87//!         async move {
88//!             let (mut slot_notifications, slot_unsubscribe) =
89//!                 pubsub_client.slot_subscribe().await?;
90//!
91//!             // With the subscription started,
92//!             // send a signal back to the main task for synchronization.
93//!             ready_sender.send(()).expect("channel");
94//!
95//!             // Send the unsubscribe closure back to the main task.
96//!             unsubscribe_sender.send((slot_unsubscribe, "slot"))
97//!                 .map_err(|e| format!("{}", e)).expect("channel");
98//!
99//!             // Drop senders so that the channels can close.
100//!             // The main task will receive until channels are closed.
101//!             drop((ready_sender, unsubscribe_sender));
102//!
103//!             // Do something with the subscribed messages.
104//!             // This loop will end once the main task unsubscribes.
105//!             while let Some(slot_info) = slot_notifications.next().await {
106//!                 println!("------------------------------------------------------------");
107//!                 println!("slot pubsub result: {:?}", slot_info);
108//!             }
109//!
110//!             // This type hint is necessary to allow the `async move` block to use `?`.
111//!             Ok::<_, anyhow::Error>(())
112//!         }
113//!     })));
114//!
115//!     join_handles.push(("root", tokio::spawn({
116//!         let ready_sender = ready_sender.clone();
117//!         let unsubscribe_sender = unsubscribe_sender.clone();
118//!         let pubsub_client = Arc::clone(&pubsub_client);
119//!         async move {
120//!             let (mut root_notifications, root_unsubscribe) =
121//!                 pubsub_client.root_subscribe().await?;
122//!
123//!             ready_sender.send(()).expect("channel");
124//!             unsubscribe_sender.send((root_unsubscribe, "root"))
125//!                 .map_err(|e| format!("{}", e)).expect("channel");
126//!             drop((ready_sender, unsubscribe_sender));
127//!
128//!             while let Some(root) = root_notifications.next().await {
129//!                 println!("------------------------------------------------------------");
130//!                 println!("root pubsub result: {:?}", root);
131//!             }
132//!
133//!             Ok::<_, anyhow::Error>(())
134//!         }
135//!     })));
136//!
137//!     // Drop these senders so that the channels can close
138//!     // and their receivers return `None` below.
139//!     drop(ready_sender);
140//!     drop(unsubscribe_sender);
141//!
142//!     // Wait until all subscribers are ready before proceeding with application logic.
143//!     while let Some(_) = ready_receiver.recv().await { }
144//!
145//!     // Do application logic here.
146//!
147//!     // Wait for input or some application-specific shutdown condition.
148//!     tokio::io::stdin().read_u8().await?;
149//!
150//!     // Unsubscribe from everything, which will shutdown all the tasks.
151//!     while let Some((unsubscribe, name)) = unsubscribe_receiver.recv().await {
152//!         println!("unsubscribing from {}", name);
153//!         unsubscribe().await
154//!     }
155//!
156//!     // Wait for the tasks.
157//!     for (name, handle) in join_handles {
158//!         println!("waiting on task {}", name);
159//!         if let Ok(Err(e)) = handle.await {
160//!             println!("task {} failed: {}", name, e);
161//!         }
162//!     }
163//!
164//!     Ok(())
165//! }
166//! # Ok::<(), anyhow::Error>(())
167//! ```
168
169use {
170    futures_util::{
171        future::{BoxFuture, FutureExt, ready},
172        sink::SinkExt,
173        stream::{BoxStream, StreamExt},
174    },
175    log::*,
176    serde::de::DeserializeOwned,
177    serde_json::{Map, Value, json},
178    solana_account_decoder_client_types::UiAccount,
179    solana_clock::Slot,
180    solana_pubkey::Pubkey,
181    solana_rpc_client_types::{
182        config::{
183            RpcAccountInfoConfig, RpcBlockSubscribeConfig, RpcBlockSubscribeFilter,
184            RpcProgramAccountsConfig, RpcSignatureSubscribeConfig, RpcTransactionLogsConfig,
185            RpcTransactionLogsFilter,
186        },
187        error_object::RpcErrorObject,
188        response::{
189            Response as RpcResponse, RpcBlockUpdate, RpcKeyedAccount, RpcLogsResponse,
190            RpcSignatureResult, RpcVote, SlotInfo, SlotUpdate,
191        },
192    },
193    solana_signature::Signature,
194    std::collections::BTreeMap,
195    thiserror::Error,
196    tokio::{
197        net::TcpStream,
198        sync::{mpsc, oneshot},
199        task::JoinHandle,
200        time::{Duration, sleep},
201    },
202    tokio_stream::wrappers::UnboundedReceiverStream,
203    tokio_tungstenite::{
204        MaybeTlsStream, WebSocketStream, connect_async,
205        tungstenite::{
206            Message,
207            protocol::frame::{CloseFrame, coding::CloseCode},
208        },
209    },
210    tungstenite::{
211        Bytes,
212        client::IntoClientRequest,
213        http::{StatusCode, header},
214    },
215};
216
217pub type PubsubClientResult<T = ()> = Result<T, PubsubClientError>;
218
219#[derive(Debug, Error)]
220pub enum PubsubClientError {
221    #[error("url parse error")]
222    UrlParseError(#[from] url::ParseError),
223
224    #[error("unable to connect to server")]
225    ConnectionError(Box<tokio_tungstenite::tungstenite::Error>),
226
227    #[error("websocket error")]
228    WsError(#[from] Box<tokio_tungstenite::tungstenite::Error>),
229
230    #[error("connection closed (({0})")]
231    ConnectionClosed(String),
232
233    #[error("json parse error")]
234    JsonParseError(#[from] serde_json::error::Error),
235
236    #[error("subscribe failed: {reason}")]
237    SubscribeFailed { reason: String, message: String },
238
239    #[error("unexpected message format: {0}")]
240    UnexpectedMessageError(String),
241
242    #[error("request failed: {reason}")]
243    RequestFailed { reason: String, message: String },
244
245    #[error("request error: {0}")]
246    RequestError(String),
247
248    #[error("could not find subscription id: {0}")]
249    UnexpectedSubscriptionResponse(String),
250
251    #[error("could not find node version: {0}")]
252    UnexpectedGetVersionResponse(String),
253}
254
255type UnsubscribeFn = Box<dyn FnOnce() -> BoxFuture<'static, ()> + Send>;
256type SubscribeResponseMsg =
257    Result<(mpsc::UnboundedReceiver<Value>, UnsubscribeFn), PubsubClientError>;
258type SubscribeRequestMsg = (String, Value, oneshot::Sender<SubscribeResponseMsg>);
259type SubscribeResult<'a, T> = PubsubClientResult<(BoxStream<'a, T>, UnsubscribeFn)>;
260type RequestMsg = (
261    String,
262    Value,
263    oneshot::Sender<Result<Value, PubsubClientError>>,
264);
265
266/// A client for subscribing to messages from the RPC server.
267///
268/// See the [module documentation][self].
269#[derive(Debug)]
270pub struct PubsubClient {
271    subscribe_sender: mpsc::UnboundedSender<SubscribeRequestMsg>,
272    _request_sender: mpsc::UnboundedSender<RequestMsg>,
273    shutdown_sender: oneshot::Sender<()>,
274    ws: JoinHandle<PubsubClientResult>,
275}
276
277async fn connect_with_retry<R: IntoClientRequest>(
278    request: R,
279) -> Result<WebSocketStream<MaybeTlsStream<TcpStream>>, Box<tungstenite::Error>> {
280    let mut connection_retries = 5;
281    let client_request = request.into_client_request().map_err(Box::new)?;
282    loop {
283        let result = connect_async(client_request.clone())
284            .await
285            .map(|(socket, _)| socket);
286        if let Err(tungstenite::Error::Http(response)) = &result {
287            if response.status() == StatusCode::TOO_MANY_REQUESTS && connection_retries > 0 {
288                let mut duration = Duration::from_millis(500);
289                if let Some(retry_after) = response.headers().get(header::RETRY_AFTER) {
290                    if let Ok(retry_after) = retry_after.to_str() {
291                        if let Ok(retry_after) = retry_after.parse::<u64>() {
292                            if retry_after < 120 {
293                                duration = Duration::from_secs(retry_after);
294                            }
295                        }
296                    }
297                }
298
299                connection_retries -= 1;
300                debug!(
301                    "Too many requests: server responded with {response:?}, {connection_retries} \
302                     retries left, pausing for {duration:?}"
303                );
304
305                sleep(duration).await;
306                continue;
307            }
308        }
309        return result.map_err(Box::new);
310    }
311}
312
313impl PubsubClient {
314    pub async fn new<R: IntoClientRequest>(request: R) -> PubsubClientResult<Self> {
315        let client_request = request.into_client_request().map_err(Box::new)?;
316        let ws = connect_with_retry(client_request)
317            .await
318            .map_err(PubsubClientError::ConnectionError)?;
319
320        let (subscribe_sender, subscribe_receiver) = mpsc::unbounded_channel();
321        let (_request_sender, request_receiver) = mpsc::unbounded_channel();
322        let (shutdown_sender, shutdown_receiver) = oneshot::channel();
323
324        #[allow(clippy::used_underscore_binding)]
325        Ok(Self {
326            subscribe_sender,
327            _request_sender,
328            shutdown_sender,
329            ws: tokio::spawn(PubsubClient::run_ws(
330                ws,
331                subscribe_receiver,
332                request_receiver,
333                shutdown_receiver,
334            )),
335        })
336    }
337
338    pub async fn shutdown(self) -> PubsubClientResult {
339        let _ = self.shutdown_sender.send(());
340        self.ws.await.unwrap() // WS future should not be cancelled or panicked
341    }
342
343    async fn subscribe<'a, T>(&self, operation: &str, params: Value) -> SubscribeResult<'a, T>
344    where
345        T: DeserializeOwned + Send + 'a,
346    {
347        let (response_sender, response_receiver) = oneshot::channel();
348        self.subscribe_sender
349            .send((operation.to_string(), params, response_sender))
350            .map_err(|err| PubsubClientError::ConnectionClosed(err.to_string()))?;
351
352        let (notifications, unsubscribe) = response_receiver
353            .await
354            .map_err(|err| PubsubClientError::ConnectionClosed(err.to_string()))??;
355        Ok((
356            UnboundedReceiverStream::new(notifications)
357                .filter_map(|value| ready(serde_json::from_value::<T>(value).ok()))
358                .boxed(),
359            unsubscribe,
360        ))
361    }
362
363    /// Subscribe to account events.
364    ///
365    /// Receives messages of type [`UiAccount`] when an account's lamports or data changes.
366    ///
367    /// # RPC Reference
368    ///
369    /// This method corresponds directly to the [`accountSubscribe`] RPC method.
370    ///
371    /// [`accountSubscribe`]: https://solana.com/docs/rpc/websocket#accountsubscribe
372    pub async fn account_subscribe(
373        &self,
374        pubkey: &Pubkey,
375        config: Option<RpcAccountInfoConfig>,
376    ) -> SubscribeResult<'_, RpcResponse<UiAccount>> {
377        let params = json!([pubkey.to_string(), config]);
378        self.subscribe("account", params).await
379    }
380
381    /// Subscribe to block events.
382    ///
383    /// Receives messages of type [`RpcBlockUpdate`] when a block is confirmed or finalized.
384    ///
385    /// This method is disabled by default. It can be enabled by passing
386    /// `--rpc-pubsub-enable-block-subscription` to `agave-validator`.
387    ///
388    /// # RPC Reference
389    ///
390    /// This method corresponds directly to the [`blockSubscribe`] RPC method.
391    ///
392    /// [`blockSubscribe`]: https://solana.com/docs/rpc/websocket#blocksubscribe
393    pub async fn block_subscribe(
394        &self,
395        filter: RpcBlockSubscribeFilter,
396        config: Option<RpcBlockSubscribeConfig>,
397    ) -> SubscribeResult<'_, RpcResponse<RpcBlockUpdate>> {
398        self.subscribe("block", json!([filter, config])).await
399    }
400
401    /// Subscribe to transaction log events.
402    ///
403    /// Receives messages of type [`RpcLogsResponse`] when a transaction is committed.
404    ///
405    /// # RPC Reference
406    ///
407    /// This method corresponds directly to the [`logsSubscribe`] RPC method.
408    ///
409    /// [`logsSubscribe`]: https://solana.com/docs/rpc/websocket#logssubscribe
410    pub async fn logs_subscribe(
411        &self,
412        filter: RpcTransactionLogsFilter,
413        config: RpcTransactionLogsConfig,
414    ) -> SubscribeResult<'_, RpcResponse<RpcLogsResponse>> {
415        self.subscribe("logs", json!([filter, config])).await
416    }
417
418    /// Subscribe to program account events.
419    ///
420    /// Receives messages of type [`RpcKeyedAccount`] when an account owned
421    /// by the given program changes.
422    ///
423    /// # RPC Reference
424    ///
425    /// This method corresponds directly to the [`programSubscribe`] RPC method.
426    ///
427    /// [`programSubscribe`]: https://solana.com/docs/rpc/websocket#programsubscribe
428    pub async fn program_subscribe(
429        &self,
430        pubkey: &Pubkey,
431        config: Option<RpcProgramAccountsConfig>,
432    ) -> SubscribeResult<'_, RpcResponse<RpcKeyedAccount>> {
433        let params = json!([pubkey.to_string(), config]);
434        self.subscribe("program", params).await
435    }
436
437    /// Subscribe to vote events.
438    ///
439    /// Receives messages of type [`RpcVote`] when a new vote is observed. These
440    /// votes are observed prior to confirmation and may never be confirmed.
441    ///
442    /// This method is disabled by default. It can be enabled by passing
443    /// `--rpc-pubsub-enable-vote-subscription` to `agave-validator`.
444    ///
445    /// # RPC Reference
446    ///
447    /// This method corresponds directly to the [`voteSubscribe`] RPC method.
448    ///
449    /// [`voteSubscribe`]: https://solana.com/docs/rpc/websocket#votesubscribe
450    pub async fn vote_subscribe(&self) -> SubscribeResult<'_, RpcVote> {
451        self.subscribe("vote", json!([])).await
452    }
453
454    /// Subscribe to root events.
455    ///
456    /// Receives messages of type [`Slot`] when a new [root] is set by the
457    /// validator.
458    ///
459    /// [root]: https://solana.com/docs/terminology#root
460    ///
461    /// # RPC Reference
462    ///
463    /// This method corresponds directly to the [`rootSubscribe`] RPC method.
464    ///
465    /// [`rootSubscribe`]: https://solana.com/docs/rpc/websocket#rootsubscribe
466    pub async fn root_subscribe(&self) -> SubscribeResult<'_, Slot> {
467        self.subscribe("root", json!([])).await
468    }
469
470    /// Subscribe to transaction confirmation events.
471    ///
472    /// Receives messages of type [`RpcSignatureResult`] when a transaction
473    /// with the given signature is committed.
474    ///
475    /// This is a subscription to a single notification. It is automatically
476    /// cancelled by the server once the notification is sent.
477    ///
478    /// # RPC Reference
479    ///
480    /// This method corresponds directly to the [`signatureSubscribe`] RPC method.
481    ///
482    /// [`signatureSubscribe`]: https://solana.com/docs/rpc/websocket#signaturesubscribe
483    pub async fn signature_subscribe(
484        &self,
485        signature: &Signature,
486        config: Option<RpcSignatureSubscribeConfig>,
487    ) -> SubscribeResult<'_, RpcResponse<RpcSignatureResult>> {
488        let params = json!([signature.to_string(), config]);
489        self.subscribe("signature", params).await
490    }
491
492    /// Subscribe to slot events.
493    ///
494    /// Receives messages of type [`SlotInfo`] when processing of a slot begins.
495    ///
496    /// # RPC Reference
497    ///
498    /// This method corresponds directly to the [`slotSubscribe`] RPC method.
499    ///
500    /// [`slotSubscribe`]: https://solana.com/docs/rpc/websocket#slotsubscribe
501    pub async fn slot_subscribe(&self) -> SubscribeResult<'_, SlotInfo> {
502        self.subscribe("slot", json!([])).await
503    }
504
505    /// Subscribe to slot update events.
506    ///
507    /// Receives messages of type [`SlotUpdate`] when various updates to a slot occur.
508    ///
509    /// Note that this method operates differently than other subscriptions:
510    /// instead of sending the message to a receiver on a channel, it accepts a
511    /// `handler` callback that processes the message directly. This processing
512    /// occurs on another thread.
513    ///
514    /// # RPC Reference
515    ///
516    /// This method corresponds directly to the [`slotUpdatesSubscribe`] RPC method.
517    ///
518    /// [`slotUpdatesSubscribe`]: https://solana.com/docs/rpc/websocket#slotsupdatessubscribe
519    pub async fn slot_updates_subscribe(&self) -> SubscribeResult<'_, SlotUpdate> {
520        self.subscribe("slotsUpdates", json!([])).await
521    }
522
523    async fn run_ws(
524        mut ws: WebSocketStream<MaybeTlsStream<TcpStream>>,
525        mut subscribe_receiver: mpsc::UnboundedReceiver<SubscribeRequestMsg>,
526        mut request_receiver: mpsc::UnboundedReceiver<RequestMsg>,
527        mut shutdown_receiver: oneshot::Receiver<()>,
528    ) -> PubsubClientResult {
529        let mut request_id: u64 = 0;
530
531        let mut requests_subscribe = BTreeMap::new();
532        let mut requests_unsubscribe = BTreeMap::<u64, oneshot::Sender<()>>::new();
533        let mut other_requests = BTreeMap::new();
534        let mut subscriptions = BTreeMap::new();
535        let (unsubscribe_sender, mut unsubscribe_receiver) = mpsc::unbounded_channel();
536
537        loop {
538            tokio::select! {
539                // Send close on shutdown signal
540                _ = (&mut shutdown_receiver) => {
541                    let frame = CloseFrame { code: CloseCode::Normal, reason: "".into() };
542                    ws.send(Message::Close(Some(frame))).await.map_err(Box::new)?;
543                    ws.flush().await.map_err(Box::new)?;
544                    break;
545                },
546                // Send `Message::Ping` each 10s if no any other communication
547                () = sleep(Duration::from_secs(10)) => {
548                    ws.send(Message::Ping(Bytes::new())).await.map_err(Box::new)?;
549                },
550                // Read message for subscribe
551                Some((operation, params, response_sender)) = subscribe_receiver.recv() => {
552                    request_id += 1;
553                    let method = format!("{operation}Subscribe");
554                    let text = json!({"jsonrpc":"2.0","id":request_id,"method":method,"params":params}).to_string();
555                    ws.send(Message::Text(text.into())).await.map_err(Box::new)?;
556                    requests_subscribe.insert(request_id, (operation, response_sender));
557                },
558                // Read message for unsubscribe
559                Some((operation, sid, response_sender)) = unsubscribe_receiver.recv() => {
560                    subscriptions.remove(&sid);
561                    request_id += 1;
562                    let method = format!("{operation}Unsubscribe");
563                    let text = json!({"jsonrpc":"2.0","id":request_id,"method":method,"params":[sid]}).to_string();
564                    ws.send(Message::Text(text.into())).await.map_err(Box::new)?;
565                    requests_unsubscribe.insert(request_id, response_sender);
566                },
567                // Read message for other requests
568                Some((method, params, response_sender)) = request_receiver.recv() => {
569                    request_id += 1;
570                    let text = json!({"jsonrpc":"2.0","id":request_id,"method":method,"params":params}).to_string();
571                    ws.send(Message::Text(text.into())).await.map_err(Box::new)?;
572                    other_requests.insert(request_id, response_sender);
573                }
574                // Read incoming WebSocket message
575                next_msg = ws.next() => {
576                    let msg = match next_msg {
577                        Some(msg) => msg.map_err(Box::new)?,
578                        None => break,
579                    };
580                    trace!("ws.next(): {:?}", &msg);
581
582                    // Get text from the message
583                    let text = match msg {
584                        Message::Text(text) => text,
585                        Message::Binary(_data) => continue, // Ignore
586                        Message::Ping(data) => {
587                            ws.send(Message::Pong(data)).await.map_err(Box::new)?;
588                            continue
589                        },
590                        Message::Pong(_data) => continue,
591                        Message::Close(_frame) => break,
592                        Message::Frame(_frame) => continue,
593                    };
594
595
596                    let mut json: Map<String, Value> = serde_json::from_str(&text)?;
597
598                    // Subscribe/Unsubscribe response, example:
599                    // `{"jsonrpc":"2.0","result":5308752,"id":1}`
600                    if let Some(id) = json.get("id") {
601                        let id = id.as_u64().ok_or_else(|| {
602                            PubsubClientError::SubscribeFailed { reason: "invalid `id` field".into(), message: text.to_string() }
603                        })?;
604
605                        let err = json.get("error").map(|error_object| {
606                            match serde_json::from_value::<RpcErrorObject>(error_object.clone()) {
607                                Ok(rpc_error_object) => {
608                                    format!("{} ({})",  rpc_error_object.message, rpc_error_object.code)
609                                }
610                                Err(err) => format!(
611                                    "Failed to deserialize RPC error response: {} [{}]",
612                                    serde_json::to_string(error_object).unwrap(),
613                                    err
614                                )
615                            }
616                        });
617
618                        if let Some(response_sender) = other_requests.remove(&id) {
619                            match err {
620                                Some(reason) => {
621                                    let _ = response_sender.send(Err(PubsubClientError::RequestFailed { reason, message: text.to_string()}));
622                                },
623                                None => {
624                                    let json_result = json.get("result").ok_or_else(|| {
625                                        PubsubClientError::RequestFailed { reason: "missing `result` field".into(), message: text.to_string() }
626                                    })?;
627                                    if response_sender.send(Ok(json_result.clone())).is_err() {
628                                        break;
629                                    }
630                                }
631                            }
632                        } else if let Some(response_sender) = requests_unsubscribe.remove(&id) {
633                            let _ = response_sender.send(()); // do not care if receiver is closed
634                        } else if let Some((operation, response_sender)) = requests_subscribe.remove(&id) {
635                            match err {
636                                Some(reason) => {
637                                    let _ = response_sender.send(Err(PubsubClientError::SubscribeFailed { reason, message: text.to_string()}));
638                                },
639                                None => {
640                                    // Subscribe Id
641                                    let sid = json.get("result").and_then(Value::as_u64).ok_or_else(|| {
642                                        PubsubClientError::SubscribeFailed { reason: "invalid `result` field".into(), message: text.to_string() }
643                                    })?;
644
645                                    // Create notifications channel and unsubscribe function
646                                    let (notifications_sender, notifications_receiver) = mpsc::unbounded_channel();
647                                    let unsubscribe_sender = unsubscribe_sender.clone();
648                                    let unsubscribe = Box::new(move || async move {
649                                        let (response_sender, response_receiver) = oneshot::channel();
650                                        // do nothing if ws already closed
651                                        if unsubscribe_sender.send((operation, sid, response_sender)).is_ok() {
652                                            let _ = response_receiver.await; // channel can be closed only if ws is closed
653                                        }
654                                    }.boxed());
655
656                                    if response_sender.send(Ok((notifications_receiver, unsubscribe))).is_err() {
657                                        break;
658                                    }
659                                    subscriptions.insert(sid, notifications_sender);
660                                }
661                            }
662                        } else {
663                            error!("Unknown request id: {id}");
664                            break;
665                        }
666                        continue;
667                    }
668
669                    // Notification, example:
670                    // `{"jsonrpc":"2.0","method":"logsNotification","params":{"result":{...},"subscription":3114862}}`
671                    if let Some(Value::Object(params)) = json.get_mut("params") {
672                        if let Some(sid) = params.get("subscription").and_then(Value::as_u64) {
673                            let mut unsubscribe_required = false;
674
675                            if let Some(notifications_sender) = subscriptions.get(&sid) {
676                                if let Some(result) = params.remove("result") {
677                                    if notifications_sender.send(result).is_err() {
678                                        unsubscribe_required = true;
679                                    }
680                                }
681                            } else {
682                                unsubscribe_required = true;
683                            }
684
685                            if unsubscribe_required {
686                                if let Some(Value::String(method)) = json.remove("method") {
687                                    if let Some(operation) = method.strip_suffix("Notification") {
688                                        let (response_sender, _response_receiver) = oneshot::channel();
689                                        let _ = unsubscribe_sender.send((operation.to_string(), sid, response_sender));
690                                    }
691                                }
692                            }
693                        }
694                    }
695                }
696            }
697        }
698
699        Ok(())
700    }
701}
702
703#[cfg(test)]
704mod tests {
705    // see client-test/test/client.rs
706}