1pub use crate::nonblocking::pubsub_client::PubsubClientError;
90use {
91 crossbeam_channel::{Receiver, Sender, unbounded},
92 log::*,
93 serde::de::DeserializeOwned,
94 serde_json::{
95 Map, Value, json,
96 value::Value::{Number, Object},
97 },
98 solana_account_decoder_client_types::UiAccount,
99 solana_clock::Slot,
100 solana_pubkey::Pubkey,
101 solana_rpc_client_types::{
102 config::{
103 RpcAccountInfoConfig, RpcBlockSubscribeConfig, RpcBlockSubscribeFilter,
104 RpcProgramAccountsConfig, RpcSignatureSubscribeConfig, RpcTransactionLogsConfig,
105 RpcTransactionLogsFilter,
106 },
107 response::{
108 Response as RpcResponse, RpcBlockUpdate, RpcKeyedAccount, RpcLogsResponse,
109 RpcSignatureResult, RpcVote, SlotInfo, SlotUpdate,
110 },
111 },
112 solana_signature::Signature,
113 std::{
114 marker::PhantomData,
115 net::TcpStream,
116 sync::{
117 Arc, RwLock,
118 atomic::{AtomicBool, Ordering},
119 },
120 thread::{JoinHandle, sleep},
121 time::Duration,
122 },
123 tungstenite::{
124 Message, WebSocket,
125 client::IntoClientRequest,
126 connect,
127 http::{StatusCode, header},
128 stream::MaybeTlsStream,
129 },
130};
131
132pub struct PubsubClientSubscription<T>
138where
139 T: DeserializeOwned,
140{
141 message_type: PhantomData<T>,
142 operation: &'static str,
143 socket: Arc<RwLock<WebSocket<MaybeTlsStream<TcpStream>>>>,
144 subscription_id: u64,
145 t_cleanup: Option<JoinHandle<()>>,
146 exit: Arc<AtomicBool>,
147}
148
149impl<T> Drop for PubsubClientSubscription<T>
150where
151 T: DeserializeOwned,
152{
153 fn drop(&mut self) {
154 self.send_unsubscribe()
155 .unwrap_or_else(|_| warn!("unable to unsubscribe from websocket"));
156 self.socket
157 .write()
158 .unwrap()
159 .close(None)
160 .unwrap_or_else(|_| warn!("unable to close websocket"));
161 }
162}
163
164impl<T> PubsubClientSubscription<T>
165where
166 T: DeserializeOwned,
167{
168 fn send_subscribe(
169 writable_socket: &Arc<RwLock<WebSocket<MaybeTlsStream<TcpStream>>>>,
170 body: String,
171 ) -> Result<u64, PubsubClientError> {
172 writable_socket
173 .write()
174 .unwrap()
175 .send(Message::Text(body.into()))
176 .map_err(Box::new)?;
177 let message = writable_socket.write().unwrap().read().map_err(Box::new)?;
178 Self::extract_subscription_id(message)
179 }
180
181 fn extract_subscription_id(message: Message) -> Result<u64, PubsubClientError> {
182 let message_text = &message.into_text().map_err(Box::new)?;
183
184 if let Ok(json_msg) = serde_json::from_str::<Map<String, Value>>(message_text)
185 && let Some(Number(x)) = json_msg.get("result")
186 && let Some(x) = x.as_u64()
187 {
188 return Ok(x);
189 }
190
191 Err(PubsubClientError::UnexpectedSubscriptionResponse(format!(
192 "msg={message_text}"
193 )))
194 }
195
196 pub fn send_unsubscribe(&self) -> Result<(), PubsubClientError> {
205 let method = format!("{}Unsubscribe", self.operation);
206 self.socket
207 .write()
208 .unwrap()
209 .send(Message::Text(
210 json!({
211 "jsonrpc":"2.0","id":1,"method":method,"params":[self.subscription_id]
212 })
213 .to_string()
214 .into(),
215 ))
216 .map_err(Box::new)
217 .map_err(|err| err.into())
218 }
219
220 fn read_message(
221 writable_socket: &Arc<RwLock<WebSocket<MaybeTlsStream<TcpStream>>>>,
222 ) -> Result<Option<T>, PubsubClientError> {
223 let message = writable_socket.write().unwrap().read().map_err(Box::new)?;
224 if message.is_ping() {
225 return Ok(None);
226 }
227 let message_text = &message.into_text().map_err(Box::new)?;
228 if let Ok(json_msg) = serde_json::from_str::<Map<String, Value>>(message_text)
229 && let Some(Object(params)) = json_msg.get("params")
230 && let Some(result) = params.get("result")
231 && let Ok(x) = T::deserialize(result)
232 {
233 return Ok(Some(x));
234 }
235
236 Err(PubsubClientError::UnexpectedMessageError(format!(
237 "msg={message_text}"
238 )))
239 }
240
241 pub fn shutdown(&mut self) -> std::thread::Result<()> {
250 if self.t_cleanup.is_some() {
251 info!("websocket thread - shutting down");
252 self.exit.store(true, Ordering::Relaxed);
253 let x = self.t_cleanup.take().unwrap().join();
254 info!("websocket thread - shut down.");
255 x
256 } else {
257 warn!("websocket thread - already shut down.");
258 Ok(())
259 }
260 }
261}
262
263pub type PubsubLogsClientSubscription = PubsubClientSubscription<RpcResponse<RpcLogsResponse>>;
264pub type LogsSubscription = (
265 PubsubLogsClientSubscription,
266 Receiver<RpcResponse<RpcLogsResponse>>,
267);
268
269pub type PubsubSlotClientSubscription = PubsubClientSubscription<SlotInfo>;
270pub type SlotsSubscription = (PubsubSlotClientSubscription, Receiver<SlotInfo>);
271
272pub type PubsubSignatureClientSubscription =
273 PubsubClientSubscription<RpcResponse<RpcSignatureResult>>;
274pub type SignatureSubscription = (
275 PubsubSignatureClientSubscription,
276 Receiver<RpcResponse<RpcSignatureResult>>,
277);
278
279pub type PubsubBlockClientSubscription = PubsubClientSubscription<RpcResponse<RpcBlockUpdate>>;
280pub type BlockSubscription = (
281 PubsubBlockClientSubscription,
282 Receiver<RpcResponse<RpcBlockUpdate>>,
283);
284
285pub type PubsubProgramClientSubscription = PubsubClientSubscription<RpcResponse<RpcKeyedAccount>>;
286pub type ProgramSubscription = (
287 PubsubProgramClientSubscription,
288 Receiver<RpcResponse<RpcKeyedAccount>>,
289);
290
291pub type PubsubAccountClientSubscription = PubsubClientSubscription<RpcResponse<UiAccount>>;
292pub type AccountSubscription = (
293 PubsubAccountClientSubscription,
294 Receiver<RpcResponse<UiAccount>>,
295);
296
297pub type PubsubVoteClientSubscription = PubsubClientSubscription<RpcVote>;
298pub type VoteSubscription = (PubsubVoteClientSubscription, Receiver<RpcVote>);
299
300pub type PubsubRootClientSubscription = PubsubClientSubscription<Slot>;
301pub type RootSubscription = (PubsubRootClientSubscription, Receiver<Slot>);
302
303pub struct PubsubClient {}
307
308fn connect_with_retry<R: IntoClientRequest>(
309 request: R,
310) -> Result<WebSocket<MaybeTlsStream<TcpStream>>, Box<tungstenite::Error>> {
311 let mut connection_retries = 5;
312 let client_request = request.into_client_request().map_err(Box::new)?;
313 loop {
314 let result = connect(client_request.clone()).map(|(socket, _)| socket);
315 if let Err(tungstenite::Error::Http(response)) = &result
316 && response.status() == StatusCode::TOO_MANY_REQUESTS
317 && connection_retries > 0
318 {
319 let mut duration = Duration::from_millis(500);
320 if let Some(retry_after) = response.headers().get(header::RETRY_AFTER)
321 && let Ok(retry_after) = retry_after.to_str()
322 && let Ok(retry_after) = retry_after.parse::<u64>()
323 && retry_after < 120
324 {
325 duration = Duration::from_secs(retry_after);
326 }
327
328 connection_retries -= 1;
329 debug!(
330 "Too many requests: server responded with {response:?}, {connection_retries} \
331 retries left, pausing for {duration:?}"
332 );
333
334 sleep(duration);
335 continue;
336 }
337 return result.map_err(Box::new);
338 }
339}
340
341impl PubsubClient {
342 pub fn account_subscribe<R: IntoClientRequest>(
352 request: R,
353 pubkey: &Pubkey,
354 config: Option<RpcAccountInfoConfig>,
355 ) -> Result<AccountSubscription, PubsubClientError> {
356 let client_request = request.into_client_request().map_err(Box::new)?;
357 let socket = connect_with_retry(client_request)?;
358 let (sender, receiver) = unbounded();
359
360 let socket = Arc::new(RwLock::new(socket));
361 let socket_clone = socket.clone();
362 let exit = Arc::new(AtomicBool::new(false));
363 let exit_clone = exit.clone();
364 let body = json!({
365 "jsonrpc":"2.0",
366 "id":1,
367 "method":"accountSubscribe",
368 "params":[
369 pubkey.to_string(),
370 config
371 ]
372 })
373 .to_string();
374 let subscription_id = PubsubAccountClientSubscription::send_subscribe(&socket_clone, body)?;
375
376 let t_cleanup = std::thread::spawn(move || {
377 Self::cleanup_with_sender(exit_clone, &socket_clone, sender)
378 });
379
380 let result = PubsubClientSubscription {
381 message_type: PhantomData,
382 operation: "account",
383 socket,
384 subscription_id,
385 t_cleanup: Some(t_cleanup),
386 exit,
387 };
388
389 Ok((result, receiver))
390 }
391
392 pub fn block_subscribe<R: IntoClientRequest>(
405 request: R,
406 filter: RpcBlockSubscribeFilter,
407 config: Option<RpcBlockSubscribeConfig>,
408 ) -> Result<BlockSubscription, PubsubClientError> {
409 let client_request = request.into_client_request().map_err(Box::new)?;
410 let socket = connect_with_retry(client_request)?;
411 let (sender, receiver) = unbounded();
412
413 let socket = Arc::new(RwLock::new(socket));
414 let socket_clone = socket.clone();
415 let exit = Arc::new(AtomicBool::new(false));
416 let exit_clone = exit.clone();
417 let body = json!({
418 "jsonrpc":"2.0",
419 "id":1,
420 "method":"blockSubscribe",
421 "params":[filter, config]
422 })
423 .to_string();
424
425 let subscription_id = PubsubBlockClientSubscription::send_subscribe(&socket_clone, body)?;
426
427 let t_cleanup = std::thread::spawn(move || {
428 Self::cleanup_with_sender(exit_clone, &socket_clone, sender)
429 });
430
431 let result = PubsubClientSubscription {
432 message_type: PhantomData,
433 operation: "block",
434 socket,
435 subscription_id,
436 t_cleanup: Some(t_cleanup),
437 exit,
438 };
439
440 Ok((result, receiver))
441 }
442
443 pub fn logs_subscribe<R: IntoClientRequest>(
453 request: R,
454 filter: RpcTransactionLogsFilter,
455 config: RpcTransactionLogsConfig,
456 ) -> Result<LogsSubscription, PubsubClientError> {
457 let client_request = request.into_client_request().map_err(Box::new)?;
458 let socket = connect_with_retry(client_request)?;
459 let (sender, receiver) = unbounded();
460
461 let socket = Arc::new(RwLock::new(socket));
462 let socket_clone = socket.clone();
463 let exit = Arc::new(AtomicBool::new(false));
464 let exit_clone = exit.clone();
465 let body = json!({
466 "jsonrpc":"2.0",
467 "id":1,
468 "method":"logsSubscribe",
469 "params":[filter, config]
470 })
471 .to_string();
472
473 let subscription_id = PubsubLogsClientSubscription::send_subscribe(&socket_clone, body)?;
474
475 let t_cleanup = std::thread::spawn(move || {
476 Self::cleanup_with_sender(exit_clone, &socket_clone, sender)
477 });
478
479 let result = PubsubClientSubscription {
480 message_type: PhantomData,
481 operation: "logs",
482 socket,
483 subscription_id,
484 t_cleanup: Some(t_cleanup),
485 exit,
486 };
487
488 Ok((result, receiver))
489 }
490
491 pub fn program_subscribe<R: IntoClientRequest>(
502 request: R,
503 pubkey: &Pubkey,
504 config: Option<RpcProgramAccountsConfig>,
505 ) -> Result<ProgramSubscription, PubsubClientError> {
506 let client_request = request.into_client_request().map_err(Box::new)?;
507 let socket = connect_with_retry(client_request)?;
508 let (sender, receiver) = unbounded();
509
510 let socket = Arc::new(RwLock::new(socket));
511 let socket_clone = socket.clone();
512 let exit = Arc::new(AtomicBool::new(false));
513 let exit_clone = exit.clone();
514
515 let body = json!({
516 "jsonrpc":"2.0",
517 "id":1,
518 "method":"programSubscribe",
519 "params":[
520 pubkey.to_string(),
521 config
522 ]
523 })
524 .to_string();
525 let subscription_id = PubsubProgramClientSubscription::send_subscribe(&socket_clone, body)?;
526
527 let t_cleanup = std::thread::spawn(move || {
528 Self::cleanup_with_sender(exit_clone, &socket_clone, sender)
529 });
530
531 let result = PubsubClientSubscription {
532 message_type: PhantomData,
533 operation: "program",
534 socket,
535 subscription_id,
536 t_cleanup: Some(t_cleanup),
537 exit,
538 };
539
540 Ok((result, receiver))
541 }
542
543 pub fn vote_subscribe<R: IntoClientRequest>(
557 request: R,
558 ) -> Result<VoteSubscription, PubsubClientError> {
559 let client_request = request.into_client_request().map_err(Box::new)?;
560 let socket = connect_with_retry(client_request)?;
561 let (sender, receiver) = unbounded();
562
563 let socket = Arc::new(RwLock::new(socket));
564 let socket_clone = socket.clone();
565 let exit = Arc::new(AtomicBool::new(false));
566 let exit_clone = exit.clone();
567 let body = json!({
568 "jsonrpc":"2.0",
569 "id":1,
570 "method":"voteSubscribe",
571 })
572 .to_string();
573 let subscription_id = PubsubVoteClientSubscription::send_subscribe(&socket_clone, body)?;
574
575 let t_cleanup = std::thread::spawn(move || {
576 Self::cleanup_with_sender(exit_clone, &socket_clone, sender)
577 });
578
579 let result = PubsubClientSubscription {
580 message_type: PhantomData,
581 operation: "vote",
582 socket,
583 subscription_id,
584 t_cleanup: Some(t_cleanup),
585 exit,
586 };
587
588 Ok((result, receiver))
589 }
590
591 pub fn root_subscribe<R: IntoClientRequest>(
604 request: R,
605 ) -> Result<RootSubscription, PubsubClientError> {
606 let client_request = request.into_client_request().map_err(Box::new)?;
607 let socket = connect_with_retry(client_request)?;
608 let (sender, receiver) = unbounded();
609
610 let socket = Arc::new(RwLock::new(socket));
611 let socket_clone = socket.clone();
612 let exit = Arc::new(AtomicBool::new(false));
613 let exit_clone = exit.clone();
614 let body = json!({
615 "jsonrpc":"2.0",
616 "id":1,
617 "method":"rootSubscribe",
618 })
619 .to_string();
620 let subscription_id = PubsubRootClientSubscription::send_subscribe(&socket_clone, body)?;
621
622 let t_cleanup = std::thread::spawn(move || {
623 Self::cleanup_with_sender(exit_clone, &socket_clone, sender)
624 });
625
626 let result = PubsubClientSubscription {
627 message_type: PhantomData,
628 operation: "root",
629 socket,
630 subscription_id,
631 t_cleanup: Some(t_cleanup),
632 exit,
633 };
634
635 Ok((result, receiver))
636 }
637
638 pub fn signature_subscribe<R: IntoClientRequest>(
652 request: R,
653 signature: &Signature,
654 config: Option<RpcSignatureSubscribeConfig>,
655 ) -> Result<SignatureSubscription, PubsubClientError> {
656 let client_request = request.into_client_request().map_err(Box::new)?;
657 let socket = connect_with_retry(client_request)?;
658 let (sender, receiver) = unbounded();
659
660 let socket = Arc::new(RwLock::new(socket));
661 let socket_clone = socket.clone();
662 let exit = Arc::new(AtomicBool::new(false));
663 let exit_clone = exit.clone();
664 let body = json!({
665 "jsonrpc":"2.0",
666 "id":1,
667 "method":"signatureSubscribe",
668 "params":[
669 signature.to_string(),
670 config
671 ]
672 })
673 .to_string();
674 let subscription_id =
675 PubsubSignatureClientSubscription::send_subscribe(&socket_clone, body)?;
676
677 let t_cleanup = std::thread::spawn(move || {
678 Self::cleanup_with_sender(exit_clone, &socket_clone, sender)
679 });
680
681 let result = PubsubClientSubscription {
682 message_type: PhantomData,
683 operation: "signature",
684 socket,
685 subscription_id,
686 t_cleanup: Some(t_cleanup),
687 exit,
688 };
689
690 Ok((result, receiver))
691 }
692
693 pub fn slot_subscribe<R: IntoClientRequest>(
703 request: R,
704 ) -> Result<SlotsSubscription, PubsubClientError> {
705 let client_request = request.into_client_request().map_err(Box::new)?;
706 let socket = connect_with_retry(client_request)?;
707 let (sender, receiver) = unbounded::<SlotInfo>();
708
709 let socket = Arc::new(RwLock::new(socket));
710 let socket_clone = socket.clone();
711 let exit = Arc::new(AtomicBool::new(false));
712 let exit_clone = exit.clone();
713 let body = json!({
714 "jsonrpc":"2.0",
715 "id":1,
716 "method":"slotSubscribe",
717 "params":[]
718 })
719 .to_string();
720 let subscription_id = PubsubSlotClientSubscription::send_subscribe(&socket_clone, body)?;
721
722 let t_cleanup = std::thread::spawn(move || {
723 Self::cleanup_with_sender(exit_clone, &socket_clone, sender)
724 });
725
726 let result = PubsubClientSubscription {
727 message_type: PhantomData,
728 operation: "slot",
729 socket,
730 subscription_id,
731 t_cleanup: Some(t_cleanup),
732 exit,
733 };
734
735 Ok((result, receiver))
736 }
737
738 pub fn slot_updates_subscribe<R: IntoClientRequest>(
753 request: R,
754 handler: impl Fn(SlotUpdate) + Send + 'static,
755 ) -> Result<PubsubClientSubscription<SlotUpdate>, PubsubClientError> {
756 let client_request = request.into_client_request().map_err(Box::new)?;
757 let socket = connect_with_retry(client_request)?;
758
759 let socket = Arc::new(RwLock::new(socket));
760 let socket_clone = socket.clone();
761 let exit = Arc::new(AtomicBool::new(false));
762 let exit_clone = exit.clone();
763 let body = json!({
764 "jsonrpc":"2.0",
765 "id":1,
766 "method":"slotsUpdatesSubscribe",
767 "params":[]
768 })
769 .to_string();
770 let subscription_id = PubsubSlotClientSubscription::send_subscribe(&socket, body)?;
771
772 let t_cleanup = std::thread::spawn(move || {
773 Self::cleanup_with_handler(exit_clone, &socket_clone, handler)
774 });
775
776 Ok(PubsubClientSubscription {
777 message_type: PhantomData,
778 operation: "slotsUpdates",
779 socket,
780 subscription_id,
781 t_cleanup: Some(t_cleanup),
782 exit,
783 })
784 }
785
786 fn cleanup_with_sender<T>(
787 exit: Arc<AtomicBool>,
788 socket: &Arc<RwLock<WebSocket<MaybeTlsStream<TcpStream>>>>,
789 sender: Sender<T>,
790 ) where
791 T: DeserializeOwned + Send + 'static,
792 {
793 let handler = move |message| match sender.send(message) {
794 Ok(_) => (),
795 Err(err) => {
796 info!("receive error: {err:?}");
797 }
798 };
799 Self::cleanup_with_handler(exit, socket, handler);
800 }
801
802 fn cleanup_with_handler<T, F>(
803 exit: Arc<AtomicBool>,
804 socket: &Arc<RwLock<WebSocket<MaybeTlsStream<TcpStream>>>>,
805 handler: F,
806 ) where
807 T: DeserializeOwned,
808 F: Fn(T) + Send + 'static,
809 {
810 loop {
811 if exit.load(Ordering::Relaxed) {
812 break;
813 }
814
815 match PubsubClientSubscription::read_message(socket) {
816 Ok(Some(message)) => handler(message),
817 Ok(None) => {
818 }
820 Err(err) => {
821 info!("receive error: {err:?}");
822 break;
823 }
824 }
825 }
826
827 info!("websocket - exited receive loop");
828 }
829}
830
831#[cfg(test)]
832mod tests {
833 }