Skip to main content

solana_tpu_client/nonblocking/
tpu_client.rs

1pub use crate::tpu_client::Result;
2use {
3    crate::tpu_client::{MAX_FANOUT_SLOTS, RecentLeaderSlots, TpuClientConfig},
4    futures_util::{future::join_all, stream::StreamExt},
5    log::*,
6    solana_clock::{DEFAULT_MS_PER_SLOT, Slot},
7    solana_commitment_config::CommitmentConfig,
8    solana_connection_cache::{
9        connection_cache::{
10            ConnectionCache, ConnectionManager, ConnectionPool, DEFAULT_CONNECTION_POOL_SIZE,
11            NewConnectionConfig, Protocol,
12        },
13        nonblocking::client_connection::ClientConnection,
14    },
15    solana_epoch_schedule::EpochSchedule,
16    solana_leader_schedule::NUM_CONSECUTIVE_LEADER_SLOTS,
17    solana_pubkey::Pubkey,
18    solana_pubsub_client::nonblocking::pubsub_client::{PubsubClient, PubsubClientError},
19    solana_rpc_client::nonblocking::rpc_client::RpcClient,
20    solana_rpc_client_api::{
21        client_error::{Error as ClientError, ErrorKind, Result as ClientResult},
22        request::RpcError,
23        response::{RpcContactInfo, SlotUpdate},
24    },
25    solana_signer::SignerError,
26    solana_transaction::{Transaction, versioned::VersionedTransaction},
27    solana_transaction_error::{TransportError, TransportResult},
28    std::{
29        collections::{HashMap, HashSet},
30        net::SocketAddr,
31        str::FromStr,
32        sync::{
33            Arc, RwLock,
34            atomic::{AtomicBool, Ordering},
35        },
36    },
37    thiserror::Error,
38    tokio::{
39        task::JoinHandle,
40        time::{Duration, Instant, sleep, timeout},
41    },
42};
43#[cfg(feature = "spinner")]
44use {
45    crate::tpu_client::{SEND_TRANSACTION_INTERVAL, TRANSACTION_RESEND_INTERVAL},
46    futures_util::FutureExt,
47    indicatif::ProgressBar,
48    solana_message::Message,
49    solana_rpc_client::spinner::{self, SendTransactionProgress},
50    solana_rpc_client_api::request::MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS,
51    solana_signer::signers::Signers,
52    solana_transaction_error::TransactionError,
53    std::{future::Future, iter},
54};
55
56#[derive(Error, Debug)]
57pub enum TpuSenderError {
58    #[error("Pubsub error: {0:?}")]
59    PubsubError(#[from] PubsubClientError),
60    #[error("RPC error: {0:?}")]
61    RpcError(#[from] ClientError),
62    #[error("IO error: {0:?}")]
63    IoError(#[from] std::io::Error),
64    #[error("Signer error: {0:?}")]
65    SignerError(#[from] SignerError),
66    #[error("Custom error: {0}")]
67    Custom(String),
68}
69
70struct LeaderTpuCacheUpdateInfo {
71    pub(super) maybe_cluster_nodes: Option<ClientResult<Vec<RpcContactInfo>>>,
72    pub(super) maybe_epoch_schedule: Option<ClientResult<EpochSchedule>>,
73    pub(super) maybe_slot_leaders: Option<ClientResult<Vec<Pubkey>>>,
74    pub(super) first_slot: Slot,
75}
76impl LeaderTpuCacheUpdateInfo {
77    pub fn has_some(&self) -> bool {
78        self.maybe_cluster_nodes.is_some()
79            || self.maybe_epoch_schedule.is_some()
80            || self.maybe_slot_leaders.is_some()
81    }
82}
83
84struct LeaderTpuCache {
85    protocol: Protocol,
86    first_slot: Slot,
87    leaders: Vec<Pubkey>,
88    leader_tpu_map: HashMap<Pubkey, SocketAddr>,
89    slots_in_epoch: Slot,
90    last_slot_in_epoch: Slot,
91}
92
93impl LeaderTpuCache {
94    pub fn new(
95        first_slot: Slot,
96        slots_in_epoch: Slot,
97        last_slot_in_epoch: Slot,
98        leaders: Vec<Pubkey>,
99        cluster_nodes: Vec<RpcContactInfo>,
100        protocol: Protocol,
101    ) -> Self {
102        let leader_tpu_map = Self::extract_cluster_tpu_sockets(protocol, cluster_nodes);
103        Self {
104            protocol,
105            first_slot,
106            leaders,
107            leader_tpu_map,
108            slots_in_epoch,
109            last_slot_in_epoch,
110        }
111    }
112
113    // Last slot that has a cached leader pubkey
114    pub fn last_slot(&self) -> Slot {
115        self.first_slot + self.leaders.len().saturating_sub(1) as u64
116    }
117
118    pub fn slot_info(&self) -> (Slot, Slot, Slot) {
119        (
120            self.last_slot(),
121            self.last_slot_in_epoch,
122            self.slots_in_epoch,
123        )
124    }
125
126    // Get the TPU sockets for the current leader and upcoming *unique* leaders according to fanout size.
127    fn get_unique_leader_sockets(
128        &self,
129        estimated_current_slot: Slot,
130        fanout_slots: u64,
131    ) -> Vec<SocketAddr> {
132        let all_leader_sockets = self.get_leader_sockets(estimated_current_slot, fanout_slots);
133
134        let mut unique_sockets = Vec::new();
135        let mut seen = HashSet::new();
136
137        for socket in all_leader_sockets {
138            if seen.insert(socket) {
139                unique_sockets.push(socket);
140            }
141        }
142
143        unique_sockets
144    }
145
146    // Get the TPU sockets for the current leader and upcoming leaders according to fanout size.
147    fn get_leader_sockets(
148        &self,
149        estimated_current_slot: Slot,
150        fanout_slots: u64,
151    ) -> Vec<SocketAddr> {
152        let mut leader_sockets = Vec::new();
153        // `first_slot` might have been advanced since caller last read the `estimated_current_slot`
154        // value. Take the greater of the two values to ensure we are reading from the latest
155        // leader schedule.
156        let current_slot = std::cmp::max(estimated_current_slot, self.first_slot);
157        for leader_slot in
158            (current_slot..current_slot + fanout_slots).step_by(NUM_CONSECUTIVE_LEADER_SLOTS.get())
159        {
160            if let Some(leader) = self.get_slot_leader(leader_slot) {
161                if let Some(tpu_socket) = self.leader_tpu_map.get(leader) {
162                    leader_sockets.push(*tpu_socket);
163                } else {
164                    // The leader is probably delinquent
165                    trace!("TPU not available for leader {leader}");
166                }
167            } else {
168                // Overran the local leader schedule cache
169                warn!(
170                    "Leader not known for slot {}; cache holds slots [{},{}]",
171                    leader_slot,
172                    self.first_slot,
173                    self.last_slot()
174                );
175            }
176        }
177        leader_sockets
178    }
179
180    pub fn get_slot_leader(&self, slot: Slot) -> Option<&Pubkey> {
181        if slot >= self.first_slot {
182            let index = slot - self.first_slot;
183            self.leaders.get(index as usize)
184        } else {
185            None
186        }
187    }
188
189    fn extract_cluster_tpu_sockets(
190        protocol: Protocol,
191        cluster_contact_info: Vec<RpcContactInfo>,
192    ) -> HashMap<Pubkey, SocketAddr> {
193        cluster_contact_info
194            .into_iter()
195            .filter_map(|contact_info| {
196                let pubkey = Pubkey::from_str(&contact_info.pubkey).ok()?;
197                let socket = match protocol {
198                    Protocol::QUIC => contact_info.tpu_quic,
199                    Protocol::UDP => contact_info.tpu,
200                }?;
201                Some((pubkey, socket))
202            })
203            .collect()
204    }
205
206    pub fn fanout(slots_in_epoch: Slot) -> Slot {
207        (2 * MAX_FANOUT_SLOTS).min(slots_in_epoch)
208    }
209
210    pub fn update_all(&mut self, cache_update_info: LeaderTpuCacheUpdateInfo) -> (bool, bool) {
211        let mut has_error = false;
212        let mut cluster_refreshed = false;
213        if let Some(cluster_nodes) = cache_update_info.maybe_cluster_nodes {
214            match cluster_nodes {
215                Ok(cluster_nodes) => {
216                    self.leader_tpu_map =
217                        Self::extract_cluster_tpu_sockets(self.protocol, cluster_nodes);
218                    cluster_refreshed = true;
219                }
220                Err(err) => {
221                    warn!("Failed to fetch cluster tpu sockets: {err}");
222                    has_error = true;
223                }
224            }
225        }
226
227        if let Some(Ok(epoch_schedule)) = cache_update_info.maybe_epoch_schedule {
228            let epoch = epoch_schedule.get_epoch(cache_update_info.first_slot);
229            self.slots_in_epoch = epoch_schedule.get_slots_in_epoch(epoch);
230            self.last_slot_in_epoch = epoch_schedule.get_last_slot_in_epoch(epoch);
231        }
232
233        if let Some(slot_leaders) = cache_update_info.maybe_slot_leaders {
234            match slot_leaders {
235                Ok(slot_leaders) => {
236                    self.first_slot = cache_update_info.first_slot;
237                    self.leaders = slot_leaders;
238                }
239                Err(err) => {
240                    warn!(
241                        "Failed to fetch slot leaders (first_slot: {}): {err}",
242                        cache_update_info.first_slot
243                    );
244                    has_error = true;
245                }
246            }
247        }
248        (has_error, cluster_refreshed)
249    }
250}
251
252/// Client which sends transactions directly to the current leader's TPU port over UDP.
253/// The client uses RPC to determine the current leader and fetch node contact info
254pub struct TpuClient<
255    P, // ConnectionPool
256    M, // ConnectionManager
257    C, // NewConnectionConfig
258> {
259    fanout_slots: u64,
260    leader_tpu_service: LeaderTpuService,
261    exit: Arc<AtomicBool>,
262    rpc_client: Arc<RpcClient>,
263    connection_cache: Arc<ConnectionCache<P, M, C>>,
264}
265
266/// Helper function which generates futures to all be awaited together for maximum
267/// throughput
268#[cfg(feature = "spinner")]
269fn send_wire_transaction_futures<'a, P, M, C>(
270    progress_bar: &'a ProgressBar,
271    progress: &'a SendTransactionProgress,
272    index: usize,
273    num_transactions: usize,
274    wire_transaction: Vec<u8>,
275    leaders: Vec<SocketAddr>,
276    connection_cache: &'a ConnectionCache<P, M, C>,
277) -> Vec<impl Future<Output = TransportResult<()>> + 'a>
278where
279    P: ConnectionPool<NewConnectionConfig = C>,
280    M: ConnectionManager<ConnectionPool = P, NewConnectionConfig = C>,
281    C: NewConnectionConfig,
282{
283    const SEND_TIMEOUT_INTERVAL: Duration = Duration::from_secs(5);
284    let sleep_duration = SEND_TRANSACTION_INTERVAL.saturating_mul(index as u32);
285    let send_timeout = SEND_TIMEOUT_INTERVAL.saturating_add(sleep_duration);
286    leaders
287        .into_iter()
288        .map(|addr| {
289            timeout_future(
290                send_timeout,
291                sleep_and_send_wire_transaction_to_addr(
292                    sleep_duration,
293                    connection_cache,
294                    addr,
295                    wire_transaction.clone(),
296                ),
297            )
298            .boxed_local() // required to make types work simply
299        })
300        .chain(iter::once(
301            timeout_future(
302                send_timeout,
303                sleep_and_set_message(
304                    sleep_duration,
305                    progress_bar,
306                    progress,
307                    index,
308                    num_transactions,
309                ),
310            )
311            .boxed_local(), // required to make types work simply
312        ))
313        .collect::<Vec<_>>()
314}
315
316// Wrap an existing future with a timeout.
317//
318// Useful for end-users who don't need a persistent connection to each validator,
319// and want to abort more quickly.
320#[cfg(feature = "spinner")]
321async fn timeout_future<Fut: Future<Output = TransportResult<()>>>(
322    timeout_duration: Duration,
323    future: Fut,
324) -> TransportResult<()> {
325    timeout(timeout_duration, future)
326        .await
327        .unwrap_or_else(|_| Err(TransportError::Custom("Timed out".to_string())))
328}
329
330#[cfg(feature = "spinner")]
331async fn sleep_and_set_message(
332    sleep_duration: Duration,
333    progress_bar: &ProgressBar,
334    progress: &SendTransactionProgress,
335    index: usize,
336    num_transactions: usize,
337) -> TransportResult<()> {
338    sleep(sleep_duration).await;
339    progress.set_message_for_confirmed_transactions(
340        progress_bar,
341        &format!("Sending {}/{} transactions", index + 1, num_transactions,),
342    );
343    Ok(())
344}
345
346#[cfg(feature = "spinner")]
347async fn sleep_and_send_wire_transaction_to_addr<P, M, C>(
348    sleep_duration: Duration,
349    connection_cache: &ConnectionCache<P, M, C>,
350    addr: SocketAddr,
351    wire_transaction: Vec<u8>,
352) -> TransportResult<()>
353where
354    P: ConnectionPool<NewConnectionConfig = C>,
355    M: ConnectionManager<ConnectionPool = P, NewConnectionConfig = C>,
356    C: NewConnectionConfig,
357{
358    sleep(sleep_duration).await;
359    send_wire_transaction_to_addr(connection_cache, &addr, wire_transaction).await
360}
361
362async fn send_wire_transaction_to_addr<P, M, C>(
363    connection_cache: &ConnectionCache<P, M, C>,
364    addr: &SocketAddr,
365    wire_transaction: Vec<u8>,
366) -> TransportResult<()>
367where
368    P: ConnectionPool<NewConnectionConfig = C>,
369    M: ConnectionManager<ConnectionPool = P, NewConnectionConfig = C>,
370    C: NewConnectionConfig,
371{
372    let conn = connection_cache.get_nonblocking_connection(addr);
373    conn.send_data(&wire_transaction).await
374}
375
376async fn send_wire_transaction_batch_to_addr<P, M, C>(
377    connection_cache: &ConnectionCache<P, M, C>,
378    addr: &SocketAddr,
379    wire_transactions: &[Vec<u8>],
380) -> TransportResult<()>
381where
382    P: ConnectionPool<NewConnectionConfig = C>,
383    M: ConnectionManager<ConnectionPool = P, NewConnectionConfig = C>,
384    C: NewConnectionConfig,
385{
386    let conn = connection_cache.get_nonblocking_connection(addr);
387    conn.send_data_batch(wire_transactions).await
388}
389
390impl<P, M, C> TpuClient<P, M, C>
391where
392    P: ConnectionPool<NewConnectionConfig = C>,
393    M: ConnectionManager<ConnectionPool = P, NewConnectionConfig = C>,
394    C: NewConnectionConfig,
395{
396    /// Serialize and send transaction to the current and upcoming leader TPUs according to fanout
397    /// size
398    pub async fn send_transaction(&self, transaction: &Transaction) -> bool {
399        let wire_transaction =
400            wincode::serialize(transaction).expect("serialization should succeed");
401        self.send_wire_transaction(wire_transaction).await
402    }
403
404    /// Send a wire transaction to the current and upcoming leader TPUs according to fanout size
405    pub async fn send_wire_transaction(&self, wire_transaction: Vec<u8>) -> bool {
406        self.try_send_wire_transaction(wire_transaction)
407            .await
408            .is_ok()
409    }
410
411    /// Serialize and send transaction to the current and upcoming leader TPUs according to fanout
412    /// size
413    /// Returns the last error if all sends fail
414    pub async fn try_send_transaction(
415        &self,
416        transaction: &VersionedTransaction,
417    ) -> TransportResult<()> {
418        let wire_transaction =
419            wincode::serialize(transaction).expect("serialization should succeed");
420        self.try_send_wire_transaction(wire_transaction).await
421    }
422
423    /// Send a wire transaction to the current and upcoming leader TPUs according to fanout size
424    /// Returns the last error if all sends fail
425    pub async fn try_send_wire_transaction(
426        &self,
427        wire_transaction: Vec<u8>,
428    ) -> TransportResult<()> {
429        let leaders = self
430            .leader_tpu_service
431            .unique_leader_tpu_sockets(self.fanout_slots);
432        let futures = leaders
433            .iter()
434            .map(|addr| {
435                send_wire_transaction_to_addr(
436                    &self.connection_cache,
437                    addr,
438                    wire_transaction.clone(),
439                )
440            })
441            .collect::<Vec<_>>();
442        let results: Vec<TransportResult<()>> = join_all(futures).await;
443
444        let mut last_error: Option<TransportError> = None;
445        let mut some_success = false;
446        for result in results {
447            if let Err(e) = result {
448                if last_error.is_none() {
449                    last_error = Some(e);
450                }
451            } else {
452                some_success = true;
453            }
454        }
455        if !some_success {
456            Err(if let Some(err) = last_error {
457                err
458            } else {
459                std::io::Error::other("No sends attempted").into()
460            })
461        } else {
462            Ok(())
463        }
464    }
465
466    /// Send a batch of wire transactions to the current and upcoming leader TPUs according to
467    /// fanout size
468    /// Returns the last error if all sends fail
469    pub async fn try_send_wire_transaction_batch(
470        &self,
471        wire_transactions: Vec<Vec<u8>>,
472    ) -> TransportResult<()> {
473        let leaders = self
474            .leader_tpu_service
475            .unique_leader_tpu_sockets(self.fanout_slots);
476        let futures = leaders
477            .iter()
478            .map(|addr| {
479                send_wire_transaction_batch_to_addr(
480                    &self.connection_cache,
481                    addr,
482                    &wire_transactions,
483                )
484            })
485            .collect::<Vec<_>>();
486        let results: Vec<TransportResult<()>> = join_all(futures).await;
487
488        let mut last_error: Option<TransportError> = None;
489        let mut some_success = false;
490        for result in results {
491            if let Err(e) = result {
492                if last_error.is_none() {
493                    last_error = Some(e);
494                }
495            } else {
496                some_success = true;
497            }
498        }
499        if !some_success {
500            Err(if let Some(err) = last_error {
501                err
502            } else {
503                std::io::Error::other("No sends attempted").into()
504            })
505        } else {
506            Ok(())
507        }
508    }
509
510    /// Create a new client that disconnects when dropped
511    pub async fn new(
512        name: &'static str,
513        rpc_client: Arc<RpcClient>,
514        websocket_url: &str,
515        config: TpuClientConfig,
516        connection_manager: M,
517    ) -> Result<Self> {
518        let connection_cache = Arc::new(
519            ConnectionCache::new(name, connection_manager, DEFAULT_CONNECTION_POOL_SIZE).unwrap(),
520        ); // TODO: Handle error properly, as the ConnectionCache ctor is now fallible.
521        Self::new_with_connection_cache(rpc_client, websocket_url, config, connection_cache).await
522    }
523
524    /// Create a new client that disconnects when dropped
525    pub async fn new_with_connection_cache(
526        rpc_client: Arc<RpcClient>,
527        websocket_url: &str,
528        config: TpuClientConfig,
529        connection_cache: Arc<ConnectionCache<P, M, C>>,
530    ) -> Result<Self> {
531        let exit = Arc::new(AtomicBool::new(false));
532        let leader_tpu_service =
533            LeaderTpuService::new(rpc_client.clone(), websocket_url, M::PROTOCOL, exit.clone())
534                .await?;
535
536        Ok(Self {
537            fanout_slots: config.fanout_slots.clamp(1, MAX_FANOUT_SLOTS),
538            leader_tpu_service,
539            exit,
540            rpc_client,
541            connection_cache,
542        })
543    }
544
545    #[deprecated(
546        since = "4.3.0",
547        note = "prefer solana_client::send_and_confirm_transactions_in_parallel_v3"
548    )]
549    #[cfg(feature = "spinner")]
550    pub async fn send_and_confirm_messages_with_spinner<T: Signers + ?Sized>(
551        &self,
552        messages: &[Message],
553        signers: &T,
554    ) -> Result<Vec<Option<TransactionError>>> {
555        let mut progress = SendTransactionProgress::default();
556        let progress_bar = spinner::new_progress_bar();
557        progress_bar.set_message("Setting up...");
558
559        let mut transactions = messages
560            .iter()
561            .enumerate()
562            .map(|(i, message)| (i, Transaction::new_unsigned(message.clone())))
563            .collect::<Vec<_>>();
564        progress.total_transactions = transactions.len();
565        let mut transaction_errors = vec![None; transactions.len()];
566        progress.block_height = self.rpc_client.get_block_height().await?;
567        for expired_blockhash_retries in (0..5).rev() {
568            let (blockhash, last_valid_block_height) = self
569                .rpc_client
570                .get_latest_blockhash_with_commitment(self.rpc_client.commitment())
571                .await?;
572            progress.last_valid_block_height = last_valid_block_height;
573
574            let mut pending_transactions = HashMap::new();
575            for (i, mut transaction) in transactions {
576                transaction.try_sign(signers, blockhash)?;
577                pending_transactions.insert(transaction.signatures[0], (i, transaction));
578            }
579
580            let mut last_resend = Instant::now() - TRANSACTION_RESEND_INTERVAL;
581            while progress.block_height <= progress.last_valid_block_height {
582                let num_transactions = pending_transactions.len();
583
584                // Periodically re-send all pending transactions
585                if Instant::now().duration_since(last_resend) > TRANSACTION_RESEND_INTERVAL {
586                    // Prepare futures for all transactions
587                    let mut futures = vec![];
588                    for (index, (_i, transaction)) in pending_transactions.values().enumerate() {
589                        let wire_transaction = wincode::serialize(transaction).unwrap();
590                        let leaders = self
591                            .leader_tpu_service
592                            .unique_leader_tpu_sockets(self.fanout_slots);
593                        futures.extend(send_wire_transaction_futures(
594                            &progress_bar,
595                            &progress,
596                            index,
597                            num_transactions,
598                            wire_transaction,
599                            leaders,
600                            &self.connection_cache,
601                        ));
602                    }
603
604                    // Start the process of sending them all
605                    let results = join_all(futures).await;
606
607                    progress.set_message_for_confirmed_transactions(
608                        &progress_bar,
609                        "Checking sent transactions",
610                    );
611                    for (index, (tx_results, (_i, transaction))) in results
612                        .chunks(self.fanout_slots as usize)
613                        .zip(pending_transactions.values())
614                        .enumerate()
615                    {
616                        // Only report an error if every future in the chunk errored
617                        if tx_results.iter().all(|r| r.is_err()) {
618                            progress.set_message_for_confirmed_transactions(
619                                &progress_bar,
620                                &format!(
621                                    "Resending failed transaction {} of {}",
622                                    index + 1,
623                                    num_transactions,
624                                ),
625                            );
626                            let _result = self.rpc_client.send_transaction(transaction).await.ok();
627                        }
628                    }
629                    last_resend = Instant::now();
630                }
631
632                // Wait for the next block before checking for transaction statuses
633                let mut block_height_refreshes = 10;
634                progress.set_message_for_confirmed_transactions(
635                    &progress_bar,
636                    &format!("Waiting for next block, {num_transactions} transactions pending..."),
637                );
638                let mut new_block_height = progress.block_height;
639                while progress.block_height == new_block_height && block_height_refreshes > 0 {
640                    sleep(Duration::from_millis(500)).await;
641                    new_block_height = self.rpc_client.get_block_height().await?;
642                    block_height_refreshes -= 1;
643                }
644                progress.block_height = new_block_height;
645
646                // Collect statuses for the transactions, drop those that are confirmed
647                let pending_signatures = pending_transactions.keys().cloned().collect::<Vec<_>>();
648                for pending_signatures_chunk in
649                    pending_signatures.chunks(MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS)
650                {
651                    if let Ok(result) = self
652                        .rpc_client
653                        .get_signature_statuses(pending_signatures_chunk)
654                        .await
655                    {
656                        let statuses = result.value;
657                        for (signature, status) in pending_signatures_chunk.iter().zip(statuses) {
658                            if let Some(status) = status
659                                && status.satisfies_commitment(self.rpc_client.commitment())
660                                && let Some((i, _)) = pending_transactions.remove(signature)
661                            {
662                                progress.confirmed_transactions += 1;
663                                if status.err.is_some() {
664                                    progress_bar.println(format!("Failed transaction: {status:?}"));
665                                }
666                                transaction_errors[i] = status.err;
667                            }
668                        }
669                    }
670                    progress.set_message_for_confirmed_transactions(
671                        &progress_bar,
672                        "Checking transaction status...",
673                    );
674                }
675
676                if pending_transactions.is_empty() {
677                    return Ok(transaction_errors);
678                }
679            }
680
681            transactions = pending_transactions.into_values().collect();
682            progress_bar.println(format!(
683                "Blockhash expired. {expired_blockhash_retries} retries remaining"
684            ));
685        }
686        Err(TpuSenderError::Custom("Max retries exceeded".into()))
687    }
688
689    pub fn rpc_client(&self) -> &RpcClient {
690        &self.rpc_client
691    }
692
693    pub async fn shutdown(&mut self) {
694        self.exit.store(true, Ordering::Relaxed);
695        self.leader_tpu_service.join().await;
696    }
697
698    pub fn get_connection_cache(&self) -> &Arc<ConnectionCache<P, M, C>>
699    where
700        P: ConnectionPool<NewConnectionConfig = C>,
701        M: ConnectionManager<ConnectionPool = P, NewConnectionConfig = C>,
702        C: NewConnectionConfig,
703    {
704        &self.connection_cache
705    }
706
707    pub fn get_leader_tpu_service(&self) -> &LeaderTpuService {
708        &self.leader_tpu_service
709    }
710
711    pub fn get_fanout_slots(&self) -> u64 {
712        self.fanout_slots
713    }
714}
715
716impl<P, M, C> Drop for TpuClient<P, M, C> {
717    fn drop(&mut self) {
718        self.exit.store(true, Ordering::Relaxed);
719    }
720}
721
722/// Service that tracks upcoming leaders and maintains an up-to-date mapping
723/// of leader id to TPU socket address.
724pub struct LeaderTpuService {
725    recent_slots: RecentLeaderSlots,
726    leader_tpu_cache: Arc<RwLock<LeaderTpuCache>>,
727    t_leader_tpu_service: Option<JoinHandle<Result<()>>>,
728}
729
730impl LeaderTpuService {
731    pub async fn new(
732        rpc_client: Arc<RpcClient>,
733        websocket_url: &str,
734        protocol: Protocol,
735        exit: Arc<AtomicBool>,
736    ) -> Result<Self> {
737        let epoch_schedule = rpc_client.get_epoch_schedule().await?;
738        let start_slot = rpc_client
739            .get_slot_with_commitment(CommitmentConfig::processed())
740            .await?;
741
742        let recent_slots = RecentLeaderSlots::new(start_slot);
743        let epoch = epoch_schedule.get_epoch(start_slot);
744        let slots_in_epoch = epoch_schedule.get_slots_in_epoch(epoch);
745        let last_slot_in_epoch = epoch_schedule.get_last_slot_in_epoch(epoch);
746
747        // When a cluster is starting, we observe an invalid slot range failure that goes away after a
748        // retry. It seems as if the leader schedule is not available, but it should be. The logic
749        // below retries the RPC call in case of an invalid slot range error.
750        let tpu_leader_service_creation_timeout = Duration::from_secs(20);
751        let retry_interval = Duration::from_secs(1);
752        let leaders = timeout(tpu_leader_service_creation_timeout, async {
753            loop {
754                // TODO: The root cause appears to lie within the `rpc_client.get_slot_leaders()`.
755                // It might be worth debugging further and trying to understand why the RPC
756                // call fails. There may be a bug in the `get_slot_leaders()` logic or in the
757                // RPC implementation
758                match rpc_client
759                    .get_slot_leaders(start_slot, LeaderTpuCache::fanout(slots_in_epoch))
760                    .await
761                {
762                    Ok(leaders) => return Ok(leaders),
763                    Err(client_error) => {
764                        if is_invalid_slot_range_error(&client_error) {
765                            sleep(retry_interval).await;
766                            continue;
767                        } else {
768                            return Err(client_error);
769                        }
770                    }
771                }
772            }
773        })
774        .await
775        .map_err(|_| {
776            TpuSenderError::Custom(format!(
777                "Failed to get slot leaders connecting to: {websocket_url}, timeout: \
778                 {tpu_leader_service_creation_timeout:?}. Invalid slot range"
779            ))
780        })??;
781
782        let cluster_nodes = timeout(tpu_leader_service_creation_timeout, async {
783            loop {
784                let cluster_nodes = rpc_client.get_cluster_nodes().await?;
785                // Stop once we find at least one leader's contact info
786                if cluster_nodes.iter().any(|rpc_contact_info| {
787                    Pubkey::from_str(&rpc_contact_info.pubkey)
788                        .map(|pubkey| leaders.contains(&pubkey))
789                        .unwrap_or(false)
790                }) {
791                    return Ok::<_, ClientError>(cluster_nodes);
792                }
793                sleep(retry_interval).await;
794            }
795        })
796        .await
797        .map_err(|_| {
798            TpuSenderError::Custom(format!(
799                "Failed find any cluster node info for upcoming leaders, timeout: \
800                 {tpu_leader_service_creation_timeout:?}."
801            ))
802        })??;
803        let leader_tpu_cache = Arc::new(RwLock::new(LeaderTpuCache::new(
804            start_slot,
805            slots_in_epoch,
806            last_slot_in_epoch,
807            leaders,
808            cluster_nodes,
809            protocol,
810        )));
811
812        let pubsub_client = if !websocket_url.is_empty() {
813            Some(PubsubClient::new(websocket_url).await?)
814        } else {
815            None
816        };
817
818        let t_leader_tpu_service = Some({
819            let recent_slots = recent_slots.clone();
820            let leader_tpu_cache = leader_tpu_cache.clone();
821            tokio::spawn(Self::run(
822                rpc_client,
823                recent_slots,
824                leader_tpu_cache,
825                pubsub_client,
826                exit,
827            ))
828        });
829
830        Ok(LeaderTpuService {
831            recent_slots,
832            leader_tpu_cache,
833            t_leader_tpu_service,
834        })
835    }
836
837    pub async fn join(&mut self) {
838        if let Some(t_handle) = self.t_leader_tpu_service.take() {
839            t_handle.await.unwrap().unwrap();
840        }
841    }
842
843    pub fn estimated_current_slot(&self) -> Slot {
844        self.recent_slots.estimated_current_slot()
845    }
846
847    pub fn unique_leader_tpu_sockets(&self, fanout_slots: u64) -> Vec<SocketAddr> {
848        let current_slot = self.recent_slots.estimated_current_slot();
849        self.leader_tpu_cache
850            .read()
851            .unwrap()
852            .get_unique_leader_sockets(current_slot, fanout_slots)
853    }
854
855    pub fn leader_tpu_sockets(&self, fanout_slots: u64) -> Vec<SocketAddr> {
856        let current_slot = self.recent_slots.estimated_current_slot();
857        self.leader_tpu_cache
858            .read()
859            .unwrap()
860            .get_leader_sockets(current_slot, fanout_slots)
861    }
862
863    async fn run(
864        rpc_client: Arc<RpcClient>,
865        recent_slots: RecentLeaderSlots,
866        leader_tpu_cache: Arc<RwLock<LeaderTpuCache>>,
867        pubsub_client: Option<PubsubClient>,
868        exit: Arc<AtomicBool>,
869    ) -> Result<()> {
870        tokio::try_join!(
871            Self::run_slot_watcher(recent_slots.clone(), pubsub_client, exit.clone()),
872            Self::run_cache_refresher(rpc_client, recent_slots, leader_tpu_cache, exit),
873        )?;
874
875        Ok(())
876    }
877
878    async fn run_cache_refresher(
879        rpc_client: Arc<RpcClient>,
880        recent_slots: RecentLeaderSlots,
881        leader_tpu_cache: Arc<RwLock<LeaderTpuCache>>,
882        exit: Arc<AtomicBool>,
883    ) -> Result<()> {
884        let mut last_cluster_refresh = Instant::now();
885        let mut sleep_ms = DEFAULT_MS_PER_SLOT;
886
887        while !exit.load(Ordering::Relaxed) {
888            // Sleep a slot before checking if leader cache needs to be refreshed again
889            sleep(Duration::from_millis(sleep_ms)).await;
890            sleep_ms = DEFAULT_MS_PER_SLOT;
891
892            let cache_update_info = maybe_fetch_cache_info(
893                &leader_tpu_cache,
894                last_cluster_refresh,
895                &rpc_client,
896                &recent_slots,
897            )
898            .await;
899
900            if cache_update_info.has_some() {
901                let mut leader_tpu_cache = leader_tpu_cache.write().unwrap();
902                let (has_error, cluster_refreshed) = leader_tpu_cache.update_all(cache_update_info);
903                if has_error {
904                    sleep_ms = 100;
905                }
906                if cluster_refreshed {
907                    last_cluster_refresh = Instant::now();
908                }
909            }
910        }
911
912        Ok(())
913    }
914
915    async fn run_slot_watcher(
916        recent_slots: RecentLeaderSlots,
917        pubsub_client: Option<PubsubClient>,
918        exit: Arc<AtomicBool>,
919    ) -> Result<()> {
920        let Some(pubsub_client) = pubsub_client else {
921            return Ok(());
922        };
923
924        let (mut notifications, unsubscribe) = pubsub_client.slot_updates_subscribe().await?;
925        // Time out slot update notification polling at 10ms.
926        //
927        // Rationale is two-fold:
928        // 1. Notifications are an unbounded stream -- polling them will block indefinitely if not
929        //    interrupted, and the exit condition will never be checked. 10ms ensures negligible
930        //    CPU overhead while keeping notification checking timely.
931        // 2. The timeout must be strictly less than the slot time (DEFAULT_MS_PER_SLOT) to
932        //    avoid timeout never being reached. For example, if notifications are received every
933        //    100ms and the timeout is >= 100ms, notifications may theoretically always be available
934        //    before the timeout is reached, resulting in the exit condition never being checked.
935        const SLOT_UPDATE_TIMEOUT: Duration = Duration::from_millis(10);
936
937        while !exit.load(Ordering::Relaxed) {
938            while let Ok(Some(update)) = timeout(SLOT_UPDATE_TIMEOUT, notifications.next()).await {
939                let current_slot = match update {
940                    // This update indicates that a full slot was received by the connected
941                    // node so we can stop sending transactions to the leader for that slot
942                    SlotUpdate::Completed { slot, .. } => slot.saturating_add(1),
943                    // This update indicates that we have just received the first shred from
944                    // the leader for this slot and they are probably still accepting transactions.
945                    SlotUpdate::FirstShredReceived { slot, .. } => slot,
946                    _ => continue,
947                };
948                recent_slots.record_slot(current_slot);
949            }
950        }
951
952        // `notifications` requires a valid reference to `pubsub_client`, so `notifications` must be
953        // dropped before moving `pubsub_client` via `shutdown()`.
954        drop(notifications);
955        unsubscribe().await;
956        pubsub_client.shutdown().await?;
957
958        Ok(())
959    }
960}
961
962async fn maybe_fetch_cache_info(
963    leader_tpu_cache: &Arc<RwLock<LeaderTpuCache>>,
964    last_cluster_refresh: Instant,
965    rpc_client: &RpcClient,
966    recent_slots: &RecentLeaderSlots,
967) -> LeaderTpuCacheUpdateInfo {
968    // Refresh cluster TPU ports every 5min in case validators restart with new port configuration
969    // or new validators come online
970    let maybe_cluster_nodes = if last_cluster_refresh.elapsed() > Duration::from_secs(5 * 60) {
971        Some(rpc_client.get_cluster_nodes().await)
972    } else {
973        None
974    };
975
976    // Grab information about the slot leaders currently in the cache.
977    let estimated_current_slot = recent_slots.estimated_current_slot();
978    let (last_slot, last_slot_in_epoch, slots_in_epoch) = {
979        let leader_tpu_cache = leader_tpu_cache.read().unwrap();
980        leader_tpu_cache.slot_info()
981    };
982
983    // If we're crossing into a new epoch, fetch the updated epoch schedule.
984    let maybe_epoch_schedule = if estimated_current_slot > last_slot_in_epoch {
985        Some(rpc_client.get_epoch_schedule().await)
986    } else {
987        None
988    };
989
990    // If we are within the fanout range of the last slot in the cache, fetch
991    // more slot leaders. We pull down a big batch at at time to amortize the
992    // cost of the RPC call. We don't want to stall transactions on pulling this
993    // down so we fetch it proactively.
994    let maybe_slot_leaders = if estimated_current_slot >= last_slot.saturating_sub(MAX_FANOUT_SLOTS)
995    {
996        Some(
997            rpc_client
998                .get_slot_leaders(
999                    estimated_current_slot,
1000                    LeaderTpuCache::fanout(slots_in_epoch),
1001                )
1002                .await,
1003        )
1004    } else {
1005        None
1006    };
1007    LeaderTpuCacheUpdateInfo {
1008        maybe_cluster_nodes,
1009        maybe_epoch_schedule,
1010        maybe_slot_leaders,
1011        first_slot: estimated_current_slot,
1012    }
1013}
1014
1015fn is_invalid_slot_range_error(client_error: &ClientError) -> bool {
1016    if let ErrorKind::RpcError(RpcError::RpcResponseError { code, message, .. }) =
1017        client_error.kind()
1018    {
1019        return *code == -32602
1020            && message.contains("Invalid slot range: leader schedule for epoch");
1021    }
1022    false
1023}