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    #[cfg(feature = "spinner")]
546    pub async fn send_and_confirm_messages_with_spinner<T: Signers + ?Sized>(
547        &self,
548        messages: &[Message],
549        signers: &T,
550    ) -> Result<Vec<Option<TransactionError>>> {
551        let mut progress = SendTransactionProgress::default();
552        let progress_bar = spinner::new_progress_bar();
553        progress_bar.set_message("Setting up...");
554
555        let mut transactions = messages
556            .iter()
557            .enumerate()
558            .map(|(i, message)| (i, Transaction::new_unsigned(message.clone())))
559            .collect::<Vec<_>>();
560        progress.total_transactions = transactions.len();
561        let mut transaction_errors = vec![None; transactions.len()];
562        progress.block_height = self.rpc_client.get_block_height().await?;
563        for expired_blockhash_retries in (0..5).rev() {
564            let (blockhash, last_valid_block_height) = self
565                .rpc_client
566                .get_latest_blockhash_with_commitment(self.rpc_client.commitment())
567                .await?;
568            progress.last_valid_block_height = last_valid_block_height;
569
570            let mut pending_transactions = HashMap::new();
571            for (i, mut transaction) in transactions {
572                transaction.try_sign(signers, blockhash)?;
573                pending_transactions.insert(transaction.signatures[0], (i, transaction));
574            }
575
576            let mut last_resend = Instant::now() - TRANSACTION_RESEND_INTERVAL;
577            while progress.block_height <= progress.last_valid_block_height {
578                let num_transactions = pending_transactions.len();
579
580                // Periodically re-send all pending transactions
581                if Instant::now().duration_since(last_resend) > TRANSACTION_RESEND_INTERVAL {
582                    // Prepare futures for all transactions
583                    let mut futures = vec![];
584                    for (index, (_i, transaction)) in pending_transactions.values().enumerate() {
585                        let wire_transaction = wincode::serialize(transaction).unwrap();
586                        let leaders = self
587                            .leader_tpu_service
588                            .unique_leader_tpu_sockets(self.fanout_slots);
589                        futures.extend(send_wire_transaction_futures(
590                            &progress_bar,
591                            &progress,
592                            index,
593                            num_transactions,
594                            wire_transaction,
595                            leaders,
596                            &self.connection_cache,
597                        ));
598                    }
599
600                    // Start the process of sending them all
601                    let results = join_all(futures).await;
602
603                    progress.set_message_for_confirmed_transactions(
604                        &progress_bar,
605                        "Checking sent transactions",
606                    );
607                    for (index, (tx_results, (_i, transaction))) in results
608                        .chunks(self.fanout_slots as usize)
609                        .zip(pending_transactions.values())
610                        .enumerate()
611                    {
612                        // Only report an error if every future in the chunk errored
613                        if tx_results.iter().all(|r| r.is_err()) {
614                            progress.set_message_for_confirmed_transactions(
615                                &progress_bar,
616                                &format!(
617                                    "Resending failed transaction {} of {}",
618                                    index + 1,
619                                    num_transactions,
620                                ),
621                            );
622                            let _result = self.rpc_client.send_transaction(transaction).await.ok();
623                        }
624                    }
625                    last_resend = Instant::now();
626                }
627
628                // Wait for the next block before checking for transaction statuses
629                let mut block_height_refreshes = 10;
630                progress.set_message_for_confirmed_transactions(
631                    &progress_bar,
632                    &format!("Waiting for next block, {num_transactions} transactions pending..."),
633                );
634                let mut new_block_height = progress.block_height;
635                while progress.block_height == new_block_height && block_height_refreshes > 0 {
636                    sleep(Duration::from_millis(500)).await;
637                    new_block_height = self.rpc_client.get_block_height().await?;
638                    block_height_refreshes -= 1;
639                }
640                progress.block_height = new_block_height;
641
642                // Collect statuses for the transactions, drop those that are confirmed
643                let pending_signatures = pending_transactions.keys().cloned().collect::<Vec<_>>();
644                for pending_signatures_chunk in
645                    pending_signatures.chunks(MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS)
646                {
647                    if let Ok(result) = self
648                        .rpc_client
649                        .get_signature_statuses(pending_signatures_chunk)
650                        .await
651                    {
652                        let statuses = result.value;
653                        for (signature, status) in pending_signatures_chunk.iter().zip(statuses) {
654                            if let Some(status) = status
655                                && status.satisfies_commitment(self.rpc_client.commitment())
656                                && let Some((i, _)) = pending_transactions.remove(signature)
657                            {
658                                progress.confirmed_transactions += 1;
659                                if status.err.is_some() {
660                                    progress_bar.println(format!("Failed transaction: {status:?}"));
661                                }
662                                transaction_errors[i] = status.err;
663                            }
664                        }
665                    }
666                    progress.set_message_for_confirmed_transactions(
667                        &progress_bar,
668                        "Checking transaction status...",
669                    );
670                }
671
672                if pending_transactions.is_empty() {
673                    return Ok(transaction_errors);
674                }
675            }
676
677            transactions = pending_transactions.into_values().collect();
678            progress_bar.println(format!(
679                "Blockhash expired. {expired_blockhash_retries} retries remaining"
680            ));
681        }
682        Err(TpuSenderError::Custom("Max retries exceeded".into()))
683    }
684
685    pub fn rpc_client(&self) -> &RpcClient {
686        &self.rpc_client
687    }
688
689    pub async fn shutdown(&mut self) {
690        self.exit.store(true, Ordering::Relaxed);
691        self.leader_tpu_service.join().await;
692    }
693
694    pub fn get_connection_cache(&self) -> &Arc<ConnectionCache<P, M, C>>
695    where
696        P: ConnectionPool<NewConnectionConfig = C>,
697        M: ConnectionManager<ConnectionPool = P, NewConnectionConfig = C>,
698        C: NewConnectionConfig,
699    {
700        &self.connection_cache
701    }
702
703    pub fn get_leader_tpu_service(&self) -> &LeaderTpuService {
704        &self.leader_tpu_service
705    }
706
707    pub fn get_fanout_slots(&self) -> u64 {
708        self.fanout_slots
709    }
710}
711
712impl<P, M, C> Drop for TpuClient<P, M, C> {
713    fn drop(&mut self) {
714        self.exit.store(true, Ordering::Relaxed);
715    }
716}
717
718/// Service that tracks upcoming leaders and maintains an up-to-date mapping
719/// of leader id to TPU socket address.
720pub struct LeaderTpuService {
721    recent_slots: RecentLeaderSlots,
722    leader_tpu_cache: Arc<RwLock<LeaderTpuCache>>,
723    t_leader_tpu_service: Option<JoinHandle<Result<()>>>,
724}
725
726impl LeaderTpuService {
727    pub async fn new(
728        rpc_client: Arc<RpcClient>,
729        websocket_url: &str,
730        protocol: Protocol,
731        exit: Arc<AtomicBool>,
732    ) -> Result<Self> {
733        let epoch_schedule = rpc_client.get_epoch_schedule().await?;
734        let start_slot = rpc_client
735            .get_slot_with_commitment(CommitmentConfig::processed())
736            .await?;
737
738        let recent_slots = RecentLeaderSlots::new(start_slot);
739        let epoch = epoch_schedule.get_epoch(start_slot);
740        let slots_in_epoch = epoch_schedule.get_slots_in_epoch(epoch);
741        let last_slot_in_epoch = epoch_schedule.get_last_slot_in_epoch(epoch);
742
743        // When a cluster is starting, we observe an invalid slot range failure that goes away after a
744        // retry. It seems as if the leader schedule is not available, but it should be. The logic
745        // below retries the RPC call in case of an invalid slot range error.
746        let tpu_leader_service_creation_timeout = Duration::from_secs(20);
747        let retry_interval = Duration::from_secs(1);
748        let leaders = timeout(tpu_leader_service_creation_timeout, async {
749            loop {
750                // TODO: The root cause appears to lie within the `rpc_client.get_slot_leaders()`.
751                // It might be worth debugging further and trying to understand why the RPC
752                // call fails. There may be a bug in the `get_slot_leaders()` logic or in the
753                // RPC implementation
754                match rpc_client
755                    .get_slot_leaders(start_slot, LeaderTpuCache::fanout(slots_in_epoch))
756                    .await
757                {
758                    Ok(leaders) => return Ok(leaders),
759                    Err(client_error) => {
760                        if is_invalid_slot_range_error(&client_error) {
761                            sleep(retry_interval).await;
762                            continue;
763                        } else {
764                            return Err(client_error);
765                        }
766                    }
767                }
768            }
769        })
770        .await
771        .map_err(|_| {
772            TpuSenderError::Custom(format!(
773                "Failed to get slot leaders connecting to: {websocket_url}, timeout: \
774                 {tpu_leader_service_creation_timeout:?}. Invalid slot range"
775            ))
776        })??;
777
778        let cluster_nodes = timeout(tpu_leader_service_creation_timeout, async {
779            loop {
780                let cluster_nodes = rpc_client.get_cluster_nodes().await?;
781                // Stop once we find at least one leader's contact info
782                if cluster_nodes.iter().any(|rpc_contact_info| {
783                    Pubkey::from_str(&rpc_contact_info.pubkey)
784                        .map(|pubkey| leaders.contains(&pubkey))
785                        .unwrap_or(false)
786                }) {
787                    return Ok::<_, ClientError>(cluster_nodes);
788                }
789                sleep(retry_interval).await;
790            }
791        })
792        .await
793        .map_err(|_| {
794            TpuSenderError::Custom(format!(
795                "Failed find any cluster node info for upcoming leaders, timeout: \
796                 {tpu_leader_service_creation_timeout:?}."
797            ))
798        })??;
799        let leader_tpu_cache = Arc::new(RwLock::new(LeaderTpuCache::new(
800            start_slot,
801            slots_in_epoch,
802            last_slot_in_epoch,
803            leaders,
804            cluster_nodes,
805            protocol,
806        )));
807
808        let pubsub_client = if !websocket_url.is_empty() {
809            Some(PubsubClient::new(websocket_url).await?)
810        } else {
811            None
812        };
813
814        let t_leader_tpu_service = Some({
815            let recent_slots = recent_slots.clone();
816            let leader_tpu_cache = leader_tpu_cache.clone();
817            tokio::spawn(Self::run(
818                rpc_client,
819                recent_slots,
820                leader_tpu_cache,
821                pubsub_client,
822                exit,
823            ))
824        });
825
826        Ok(LeaderTpuService {
827            recent_slots,
828            leader_tpu_cache,
829            t_leader_tpu_service,
830        })
831    }
832
833    pub async fn join(&mut self) {
834        if let Some(t_handle) = self.t_leader_tpu_service.take() {
835            t_handle.await.unwrap().unwrap();
836        }
837    }
838
839    pub fn estimated_current_slot(&self) -> Slot {
840        self.recent_slots.estimated_current_slot()
841    }
842
843    pub fn unique_leader_tpu_sockets(&self, fanout_slots: u64) -> Vec<SocketAddr> {
844        let current_slot = self.recent_slots.estimated_current_slot();
845        self.leader_tpu_cache
846            .read()
847            .unwrap()
848            .get_unique_leader_sockets(current_slot, fanout_slots)
849    }
850
851    pub fn leader_tpu_sockets(&self, fanout_slots: u64) -> Vec<SocketAddr> {
852        let current_slot = self.recent_slots.estimated_current_slot();
853        self.leader_tpu_cache
854            .read()
855            .unwrap()
856            .get_leader_sockets(current_slot, fanout_slots)
857    }
858
859    async fn run(
860        rpc_client: Arc<RpcClient>,
861        recent_slots: RecentLeaderSlots,
862        leader_tpu_cache: Arc<RwLock<LeaderTpuCache>>,
863        pubsub_client: Option<PubsubClient>,
864        exit: Arc<AtomicBool>,
865    ) -> Result<()> {
866        tokio::try_join!(
867            Self::run_slot_watcher(recent_slots.clone(), pubsub_client, exit.clone()),
868            Self::run_cache_refresher(rpc_client, recent_slots, leader_tpu_cache, exit),
869        )?;
870
871        Ok(())
872    }
873
874    async fn run_cache_refresher(
875        rpc_client: Arc<RpcClient>,
876        recent_slots: RecentLeaderSlots,
877        leader_tpu_cache: Arc<RwLock<LeaderTpuCache>>,
878        exit: Arc<AtomicBool>,
879    ) -> Result<()> {
880        let mut last_cluster_refresh = Instant::now();
881        let mut sleep_ms = DEFAULT_MS_PER_SLOT;
882
883        while !exit.load(Ordering::Relaxed) {
884            // Sleep a slot before checking if leader cache needs to be refreshed again
885            sleep(Duration::from_millis(sleep_ms)).await;
886            sleep_ms = DEFAULT_MS_PER_SLOT;
887
888            let cache_update_info = maybe_fetch_cache_info(
889                &leader_tpu_cache,
890                last_cluster_refresh,
891                &rpc_client,
892                &recent_slots,
893            )
894            .await;
895
896            if cache_update_info.has_some() {
897                let mut leader_tpu_cache = leader_tpu_cache.write().unwrap();
898                let (has_error, cluster_refreshed) = leader_tpu_cache.update_all(cache_update_info);
899                if has_error {
900                    sleep_ms = 100;
901                }
902                if cluster_refreshed {
903                    last_cluster_refresh = Instant::now();
904                }
905            }
906        }
907
908        Ok(())
909    }
910
911    async fn run_slot_watcher(
912        recent_slots: RecentLeaderSlots,
913        pubsub_client: Option<PubsubClient>,
914        exit: Arc<AtomicBool>,
915    ) -> Result<()> {
916        let Some(pubsub_client) = pubsub_client else {
917            return Ok(());
918        };
919
920        let (mut notifications, unsubscribe) = pubsub_client.slot_updates_subscribe().await?;
921        // Time out slot update notification polling at 10ms.
922        //
923        // Rationale is two-fold:
924        // 1. Notifications are an unbounded stream -- polling them will block indefinitely if not
925        //    interrupted, and the exit condition will never be checked. 10ms ensures negligible
926        //    CPU overhead while keeping notification checking timely.
927        // 2. The timeout must be strictly less than the slot time (DEFAULT_MS_PER_SLOT) to
928        //    avoid timeout never being reached. For example, if notifications are received every
929        //    100ms and the timeout is >= 100ms, notifications may theoretically always be available
930        //    before the timeout is reached, resulting in the exit condition never being checked.
931        const SLOT_UPDATE_TIMEOUT: Duration = Duration::from_millis(10);
932
933        while !exit.load(Ordering::Relaxed) {
934            while let Ok(Some(update)) = timeout(SLOT_UPDATE_TIMEOUT, notifications.next()).await {
935                let current_slot = match update {
936                    // This update indicates that a full slot was received by the connected
937                    // node so we can stop sending transactions to the leader for that slot
938                    SlotUpdate::Completed { slot, .. } => slot.saturating_add(1),
939                    // This update indicates that we have just received the first shred from
940                    // the leader for this slot and they are probably still accepting transactions.
941                    SlotUpdate::FirstShredReceived { slot, .. } => slot,
942                    _ => continue,
943                };
944                recent_slots.record_slot(current_slot);
945            }
946        }
947
948        // `notifications` requires a valid reference to `pubsub_client`, so `notifications` must be
949        // dropped before moving `pubsub_client` via `shutdown()`.
950        drop(notifications);
951        unsubscribe().await;
952        pubsub_client.shutdown().await?;
953
954        Ok(())
955    }
956}
957
958async fn maybe_fetch_cache_info(
959    leader_tpu_cache: &Arc<RwLock<LeaderTpuCache>>,
960    last_cluster_refresh: Instant,
961    rpc_client: &RpcClient,
962    recent_slots: &RecentLeaderSlots,
963) -> LeaderTpuCacheUpdateInfo {
964    // Refresh cluster TPU ports every 5min in case validators restart with new port configuration
965    // or new validators come online
966    let maybe_cluster_nodes = if last_cluster_refresh.elapsed() > Duration::from_secs(5 * 60) {
967        Some(rpc_client.get_cluster_nodes().await)
968    } else {
969        None
970    };
971
972    // Grab information about the slot leaders currently in the cache.
973    let estimated_current_slot = recent_slots.estimated_current_slot();
974    let (last_slot, last_slot_in_epoch, slots_in_epoch) = {
975        let leader_tpu_cache = leader_tpu_cache.read().unwrap();
976        leader_tpu_cache.slot_info()
977    };
978
979    // If we're crossing into a new epoch, fetch the updated epoch schedule.
980    let maybe_epoch_schedule = if estimated_current_slot > last_slot_in_epoch {
981        Some(rpc_client.get_epoch_schedule().await)
982    } else {
983        None
984    };
985
986    // If we are within the fanout range of the last slot in the cache, fetch
987    // more slot leaders. We pull down a big batch at at time to amortize the
988    // cost of the RPC call. We don't want to stall transactions on pulling this
989    // down so we fetch it proactively.
990    let maybe_slot_leaders = if estimated_current_slot >= last_slot.saturating_sub(MAX_FANOUT_SLOTS)
991    {
992        Some(
993            rpc_client
994                .get_slot_leaders(
995                    estimated_current_slot,
996                    LeaderTpuCache::fanout(slots_in_epoch),
997                )
998                .await,
999        )
1000    } else {
1001        None
1002    };
1003    LeaderTpuCacheUpdateInfo {
1004        maybe_cluster_nodes,
1005        maybe_epoch_schedule,
1006        maybe_slot_leaders,
1007        first_slot: estimated_current_slot,
1008    }
1009}
1010
1011fn is_invalid_slot_range_error(client_error: &ClientError) -> bool {
1012    if let ErrorKind::RpcError(RpcError::RpcResponseError { code, message, .. }) =
1013        client_error.kind()
1014    {
1015        return *code == -32602
1016            && message.contains("Invalid slot range: leader schedule for epoch");
1017    }
1018    false
1019}