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 if let Some(Number(x)) = json_msg.get("result") {
186 if let Some(x) = x.as_u64() {
187 return Ok(x);
188 }
189 }
190 }
191
192 Err(PubsubClientError::UnexpectedSubscriptionResponse(format!(
193 "msg={message_text}"
194 )))
195 }
196
197 pub fn send_unsubscribe(&self) -> Result<(), PubsubClientError> {
206 let method = format!("{}Unsubscribe", self.operation);
207 self.socket
208 .write()
209 .unwrap()
210 .send(Message::Text(
211 json!({
212 "jsonrpc":"2.0","id":1,"method":method,"params":[self.subscription_id]
213 })
214 .to_string()
215 .into(),
216 ))
217 .map_err(Box::new)
218 .map_err(|err| err.into())
219 }
220
221 fn read_message(
222 writable_socket: &Arc<RwLock<WebSocket<MaybeTlsStream<TcpStream>>>>,
223 ) -> Result<Option<T>, PubsubClientError> {
224 let message = writable_socket.write().unwrap().read().map_err(Box::new)?;
225 if message.is_ping() {
226 return Ok(None);
227 }
228 let message_text = &message.into_text().map_err(Box::new)?;
229 if let Ok(json_msg) = serde_json::from_str::<Map<String, Value>>(message_text) {
230 if let Some(Object(params)) = json_msg.get("params") {
231 if let Some(result) = params.get("result") {
232 if let Ok(x) = T::deserialize(result) {
233 return Ok(Some(x));
234 }
235 }
236 }
237 }
238
239 Err(PubsubClientError::UnexpectedMessageError(format!(
240 "msg={message_text}"
241 )))
242 }
243
244 pub fn shutdown(&mut self) -> std::thread::Result<()> {
253 if self.t_cleanup.is_some() {
254 info!("websocket thread - shutting down");
255 self.exit.store(true, Ordering::Relaxed);
256 let x = self.t_cleanup.take().unwrap().join();
257 info!("websocket thread - shut down.");
258 x
259 } else {
260 warn!("websocket thread - already shut down.");
261 Ok(())
262 }
263 }
264}
265
266pub type PubsubLogsClientSubscription = PubsubClientSubscription<RpcResponse<RpcLogsResponse>>;
267pub type LogsSubscription = (
268 PubsubLogsClientSubscription,
269 Receiver<RpcResponse<RpcLogsResponse>>,
270);
271
272pub type PubsubSlotClientSubscription = PubsubClientSubscription<SlotInfo>;
273pub type SlotsSubscription = (PubsubSlotClientSubscription, Receiver<SlotInfo>);
274
275pub type PubsubSignatureClientSubscription =
276 PubsubClientSubscription<RpcResponse<RpcSignatureResult>>;
277pub type SignatureSubscription = (
278 PubsubSignatureClientSubscription,
279 Receiver<RpcResponse<RpcSignatureResult>>,
280);
281
282pub type PubsubBlockClientSubscription = PubsubClientSubscription<RpcResponse<RpcBlockUpdate>>;
283pub type BlockSubscription = (
284 PubsubBlockClientSubscription,
285 Receiver<RpcResponse<RpcBlockUpdate>>,
286);
287
288pub type PubsubProgramClientSubscription = PubsubClientSubscription<RpcResponse<RpcKeyedAccount>>;
289pub type ProgramSubscription = (
290 PubsubProgramClientSubscription,
291 Receiver<RpcResponse<RpcKeyedAccount>>,
292);
293
294pub type PubsubAccountClientSubscription = PubsubClientSubscription<RpcResponse<UiAccount>>;
295pub type AccountSubscription = (
296 PubsubAccountClientSubscription,
297 Receiver<RpcResponse<UiAccount>>,
298);
299
300pub type PubsubVoteClientSubscription = PubsubClientSubscription<RpcVote>;
301pub type VoteSubscription = (PubsubVoteClientSubscription, Receiver<RpcVote>);
302
303pub type PubsubRootClientSubscription = PubsubClientSubscription<Slot>;
304pub type RootSubscription = (PubsubRootClientSubscription, Receiver<Slot>);
305
306pub struct PubsubClient {}
310
311fn connect_with_retry<R: IntoClientRequest>(
312 request: R,
313) -> Result<WebSocket<MaybeTlsStream<TcpStream>>, Box<tungstenite::Error>> {
314 let mut connection_retries = 5;
315 let client_request = request.into_client_request().map_err(Box::new)?;
316 loop {
317 let result = connect(client_request.clone()).map(|(socket, _)| socket);
318 if let Err(tungstenite::Error::Http(response)) = &result {
319 if response.status() == StatusCode::TOO_MANY_REQUESTS && connection_retries > 0 {
320 let mut duration = Duration::from_millis(500);
321 if let Some(retry_after) = response.headers().get(header::RETRY_AFTER) {
322 if let Ok(retry_after) = retry_after.to_str() {
323 if let Ok(retry_after) = retry_after.parse::<u64>() {
324 if retry_after < 120 {
325 duration = Duration::from_secs(retry_after);
326 }
327 }
328 }
329 }
330
331 connection_retries -= 1;
332 debug!(
333 "Too many requests: server responded with {response:?}, {connection_retries} \
334 retries left, pausing for {duration:?}"
335 );
336
337 sleep(duration);
338 continue;
339 }
340 }
341 return result.map_err(Box::new);
342 }
343}
344
345impl PubsubClient {
346 pub fn account_subscribe<R: IntoClientRequest>(
356 request: R,
357 pubkey: &Pubkey,
358 config: Option<RpcAccountInfoConfig>,
359 ) -> Result<AccountSubscription, PubsubClientError> {
360 let client_request = request.into_client_request().map_err(Box::new)?;
361 let socket = connect_with_retry(client_request)?;
362 let (sender, receiver) = unbounded();
363
364 let socket = Arc::new(RwLock::new(socket));
365 let socket_clone = socket.clone();
366 let exit = Arc::new(AtomicBool::new(false));
367 let exit_clone = exit.clone();
368 let body = json!({
369 "jsonrpc":"2.0",
370 "id":1,
371 "method":"accountSubscribe",
372 "params":[
373 pubkey.to_string(),
374 config
375 ]
376 })
377 .to_string();
378 let subscription_id = PubsubAccountClientSubscription::send_subscribe(&socket_clone, body)?;
379
380 let t_cleanup = std::thread::spawn(move || {
381 Self::cleanup_with_sender(exit_clone, &socket_clone, sender)
382 });
383
384 let result = PubsubClientSubscription {
385 message_type: PhantomData,
386 operation: "account",
387 socket,
388 subscription_id,
389 t_cleanup: Some(t_cleanup),
390 exit,
391 };
392
393 Ok((result, receiver))
394 }
395
396 pub fn block_subscribe<R: IntoClientRequest>(
409 request: R,
410 filter: RpcBlockSubscribeFilter,
411 config: Option<RpcBlockSubscribeConfig>,
412 ) -> Result<BlockSubscription, PubsubClientError> {
413 let client_request = request.into_client_request().map_err(Box::new)?;
414 let socket = connect_with_retry(client_request)?;
415 let (sender, receiver) = unbounded();
416
417 let socket = Arc::new(RwLock::new(socket));
418 let socket_clone = socket.clone();
419 let exit = Arc::new(AtomicBool::new(false));
420 let exit_clone = exit.clone();
421 let body = json!({
422 "jsonrpc":"2.0",
423 "id":1,
424 "method":"blockSubscribe",
425 "params":[filter, config]
426 })
427 .to_string();
428
429 let subscription_id = PubsubBlockClientSubscription::send_subscribe(&socket_clone, body)?;
430
431 let t_cleanup = std::thread::spawn(move || {
432 Self::cleanup_with_sender(exit_clone, &socket_clone, sender)
433 });
434
435 let result = PubsubClientSubscription {
436 message_type: PhantomData,
437 operation: "block",
438 socket,
439 subscription_id,
440 t_cleanup: Some(t_cleanup),
441 exit,
442 };
443
444 Ok((result, receiver))
445 }
446
447 pub fn logs_subscribe<R: IntoClientRequest>(
457 request: R,
458 filter: RpcTransactionLogsFilter,
459 config: RpcTransactionLogsConfig,
460 ) -> Result<LogsSubscription, PubsubClientError> {
461 let client_request = request.into_client_request().map_err(Box::new)?;
462 let socket = connect_with_retry(client_request)?;
463 let (sender, receiver) = unbounded();
464
465 let socket = Arc::new(RwLock::new(socket));
466 let socket_clone = socket.clone();
467 let exit = Arc::new(AtomicBool::new(false));
468 let exit_clone = exit.clone();
469 let body = json!({
470 "jsonrpc":"2.0",
471 "id":1,
472 "method":"logsSubscribe",
473 "params":[filter, config]
474 })
475 .to_string();
476
477 let subscription_id = PubsubLogsClientSubscription::send_subscribe(&socket_clone, body)?;
478
479 let t_cleanup = std::thread::spawn(move || {
480 Self::cleanup_with_sender(exit_clone, &socket_clone, sender)
481 });
482
483 let result = PubsubClientSubscription {
484 message_type: PhantomData,
485 operation: "logs",
486 socket,
487 subscription_id,
488 t_cleanup: Some(t_cleanup),
489 exit,
490 };
491
492 Ok((result, receiver))
493 }
494
495 pub fn program_subscribe<R: IntoClientRequest>(
506 request: R,
507 pubkey: &Pubkey,
508 config: Option<RpcProgramAccountsConfig>,
509 ) -> Result<ProgramSubscription, PubsubClientError> {
510 let client_request = request.into_client_request().map_err(Box::new)?;
511 let socket = connect_with_retry(client_request)?;
512 let (sender, receiver) = unbounded();
513
514 let socket = Arc::new(RwLock::new(socket));
515 let socket_clone = socket.clone();
516 let exit = Arc::new(AtomicBool::new(false));
517 let exit_clone = exit.clone();
518
519 let body = json!({
520 "jsonrpc":"2.0",
521 "id":1,
522 "method":"programSubscribe",
523 "params":[
524 pubkey.to_string(),
525 config
526 ]
527 })
528 .to_string();
529 let subscription_id = PubsubProgramClientSubscription::send_subscribe(&socket_clone, body)?;
530
531 let t_cleanup = std::thread::spawn(move || {
532 Self::cleanup_with_sender(exit_clone, &socket_clone, sender)
533 });
534
535 let result = PubsubClientSubscription {
536 message_type: PhantomData,
537 operation: "program",
538 socket,
539 subscription_id,
540 t_cleanup: Some(t_cleanup),
541 exit,
542 };
543
544 Ok((result, receiver))
545 }
546
547 pub fn vote_subscribe<R: IntoClientRequest>(
561 request: R,
562 ) -> Result<VoteSubscription, PubsubClientError> {
563 let client_request = request.into_client_request().map_err(Box::new)?;
564 let socket = connect_with_retry(client_request)?;
565 let (sender, receiver) = unbounded();
566
567 let socket = Arc::new(RwLock::new(socket));
568 let socket_clone = socket.clone();
569 let exit = Arc::new(AtomicBool::new(false));
570 let exit_clone = exit.clone();
571 let body = json!({
572 "jsonrpc":"2.0",
573 "id":1,
574 "method":"voteSubscribe",
575 })
576 .to_string();
577 let subscription_id = PubsubVoteClientSubscription::send_subscribe(&socket_clone, body)?;
578
579 let t_cleanup = std::thread::spawn(move || {
580 Self::cleanup_with_sender(exit_clone, &socket_clone, sender)
581 });
582
583 let result = PubsubClientSubscription {
584 message_type: PhantomData,
585 operation: "vote",
586 socket,
587 subscription_id,
588 t_cleanup: Some(t_cleanup),
589 exit,
590 };
591
592 Ok((result, receiver))
593 }
594
595 pub fn root_subscribe<R: IntoClientRequest>(
608 request: R,
609 ) -> Result<RootSubscription, PubsubClientError> {
610 let client_request = request.into_client_request().map_err(Box::new)?;
611 let socket = connect_with_retry(client_request)?;
612 let (sender, receiver) = unbounded();
613
614 let socket = Arc::new(RwLock::new(socket));
615 let socket_clone = socket.clone();
616 let exit = Arc::new(AtomicBool::new(false));
617 let exit_clone = exit.clone();
618 let body = json!({
619 "jsonrpc":"2.0",
620 "id":1,
621 "method":"rootSubscribe",
622 })
623 .to_string();
624 let subscription_id = PubsubRootClientSubscription::send_subscribe(&socket_clone, body)?;
625
626 let t_cleanup = std::thread::spawn(move || {
627 Self::cleanup_with_sender(exit_clone, &socket_clone, sender)
628 });
629
630 let result = PubsubClientSubscription {
631 message_type: PhantomData,
632 operation: "root",
633 socket,
634 subscription_id,
635 t_cleanup: Some(t_cleanup),
636 exit,
637 };
638
639 Ok((result, receiver))
640 }
641
642 pub fn signature_subscribe<R: IntoClientRequest>(
656 request: R,
657 signature: &Signature,
658 config: Option<RpcSignatureSubscribeConfig>,
659 ) -> Result<SignatureSubscription, PubsubClientError> {
660 let client_request = request.into_client_request().map_err(Box::new)?;
661 let socket = connect_with_retry(client_request)?;
662 let (sender, receiver) = unbounded();
663
664 let socket = Arc::new(RwLock::new(socket));
665 let socket_clone = socket.clone();
666 let exit = Arc::new(AtomicBool::new(false));
667 let exit_clone = exit.clone();
668 let body = json!({
669 "jsonrpc":"2.0",
670 "id":1,
671 "method":"signatureSubscribe",
672 "params":[
673 signature.to_string(),
674 config
675 ]
676 })
677 .to_string();
678 let subscription_id =
679 PubsubSignatureClientSubscription::send_subscribe(&socket_clone, body)?;
680
681 let t_cleanup = std::thread::spawn(move || {
682 Self::cleanup_with_sender(exit_clone, &socket_clone, sender)
683 });
684
685 let result = PubsubClientSubscription {
686 message_type: PhantomData,
687 operation: "signature",
688 socket,
689 subscription_id,
690 t_cleanup: Some(t_cleanup),
691 exit,
692 };
693
694 Ok((result, receiver))
695 }
696
697 pub fn slot_subscribe<R: IntoClientRequest>(
707 request: R,
708 ) -> Result<SlotsSubscription, PubsubClientError> {
709 let client_request = request.into_client_request().map_err(Box::new)?;
710 let socket = connect_with_retry(client_request)?;
711 let (sender, receiver) = unbounded::<SlotInfo>();
712
713 let socket = Arc::new(RwLock::new(socket));
714 let socket_clone = socket.clone();
715 let exit = Arc::new(AtomicBool::new(false));
716 let exit_clone = exit.clone();
717 let body = json!({
718 "jsonrpc":"2.0",
719 "id":1,
720 "method":"slotSubscribe",
721 "params":[]
722 })
723 .to_string();
724 let subscription_id = PubsubSlotClientSubscription::send_subscribe(&socket_clone, body)?;
725
726 let t_cleanup = std::thread::spawn(move || {
727 Self::cleanup_with_sender(exit_clone, &socket_clone, sender)
728 });
729
730 let result = PubsubClientSubscription {
731 message_type: PhantomData,
732 operation: "slot",
733 socket,
734 subscription_id,
735 t_cleanup: Some(t_cleanup),
736 exit,
737 };
738
739 Ok((result, receiver))
740 }
741
742 pub fn slot_updates_subscribe<R: IntoClientRequest>(
757 request: R,
758 handler: impl Fn(SlotUpdate) + Send + 'static,
759 ) -> Result<PubsubClientSubscription<SlotUpdate>, PubsubClientError> {
760 let client_request = request.into_client_request().map_err(Box::new)?;
761 let socket = connect_with_retry(client_request)?;
762
763 let socket = Arc::new(RwLock::new(socket));
764 let socket_clone = socket.clone();
765 let exit = Arc::new(AtomicBool::new(false));
766 let exit_clone = exit.clone();
767 let body = json!({
768 "jsonrpc":"2.0",
769 "id":1,
770 "method":"slotsUpdatesSubscribe",
771 "params":[]
772 })
773 .to_string();
774 let subscription_id = PubsubSlotClientSubscription::send_subscribe(&socket, body)?;
775
776 let t_cleanup = std::thread::spawn(move || {
777 Self::cleanup_with_handler(exit_clone, &socket_clone, handler)
778 });
779
780 Ok(PubsubClientSubscription {
781 message_type: PhantomData,
782 operation: "slotsUpdates",
783 socket,
784 subscription_id,
785 t_cleanup: Some(t_cleanup),
786 exit,
787 })
788 }
789
790 fn cleanup_with_sender<T>(
791 exit: Arc<AtomicBool>,
792 socket: &Arc<RwLock<WebSocket<MaybeTlsStream<TcpStream>>>>,
793 sender: Sender<T>,
794 ) where
795 T: DeserializeOwned + Send + 'static,
796 {
797 let handler = move |message| match sender.send(message) {
798 Ok(_) => (),
799 Err(err) => {
800 info!("receive error: {err:?}");
801 }
802 };
803 Self::cleanup_with_handler(exit, socket, handler);
804 }
805
806 fn cleanup_with_handler<T, F>(
807 exit: Arc<AtomicBool>,
808 socket: &Arc<RwLock<WebSocket<MaybeTlsStream<TcpStream>>>>,
809 handler: F,
810 ) where
811 T: DeserializeOwned,
812 F: Fn(T) + Send + 'static,
813 {
814 loop {
815 if exit.load(Ordering::Relaxed) {
816 break;
817 }
818
819 match PubsubClientSubscription::read_message(socket) {
820 Ok(Some(message)) => handler(message),
821 Ok(None) => {
822 }
824 Err(err) => {
825 info!("receive error: {err:?}");
826 break;
827 }
828 }
829 }
830
831 info!("websocket - exited receive loop");
832 }
833}
834
835#[cfg(test)]
836mod tests {
837 }