Skip to main content

solana_tpu_client/
tpu_client.rs

1pub use crate::nonblocking::tpu_client::TpuSenderError;
2use {
3    crate::nonblocking::tpu_client::TpuClient as NonblockingTpuClient,
4    rayon::iter::{IntoParallelIterator, ParallelIterator},
5    solana_client_traits::AsyncClient,
6    solana_clock::Slot,
7    solana_connection_cache::{
8        client_connection::ClientConnection,
9        connection_cache::{
10            ConnectionCache, ConnectionManager, ConnectionPool, NewConnectionConfig,
11        },
12    },
13    solana_rpc_client::rpc_client::RpcClient,
14    solana_signature::Signature,
15    solana_transaction::{Transaction, versioned::VersionedTransaction},
16    solana_transaction_error::{TransportError, TransportResult},
17    std::{
18        collections::VecDeque,
19        sync::{Arc, RwLock},
20    },
21};
22#[cfg(feature = "spinner")]
23use {
24    solana_message::Message, solana_signer::signers::Signers,
25    solana_transaction_error::TransactionError, tokio::time::Duration,
26};
27
28pub const DEFAULT_VOTE_USE_QUIC: bool = false;
29
30/// The default connection count is set to 1 -- it should
31/// be sufficient for most use cases. Validators can use
32/// --tpu-connection-pool-size to override this default value.
33pub const DEFAULT_TPU_CONNECTION_POOL_SIZE: usize = 1;
34
35pub type Result<T> = std::result::Result<T, TpuSenderError>;
36
37/// Send at ~100 TPS
38#[cfg(feature = "spinner")]
39pub(crate) const SEND_TRANSACTION_INTERVAL: Duration = Duration::from_millis(10);
40/// Retry batch send after 4 seconds
41#[cfg(feature = "spinner")]
42pub(crate) const TRANSACTION_RESEND_INTERVAL: Duration = Duration::from_secs(4);
43
44/// Default number of slots used to build TPU socket fanout set
45pub const DEFAULT_FANOUT_SLOTS: u64 = 12;
46
47/// Maximum number of slots used to build TPU socket fanout set
48pub const MAX_FANOUT_SLOTS: u64 = 100;
49
50/// Config params for `TpuClient`
51#[derive(Clone, Debug)]
52pub struct TpuClientConfig {
53    /// The range of upcoming slots to include when determining which
54    /// leaders to send transactions to (min: 1, max: `MAX_FANOUT_SLOTS`)
55    pub fanout_slots: u64,
56}
57
58impl Default for TpuClientConfig {
59    fn default() -> Self {
60        Self {
61            fanout_slots: DEFAULT_FANOUT_SLOTS,
62        }
63    }
64}
65
66/// Client which sends transactions directly to the current leader's TPU port over UDP.
67/// The client uses RPC to determine the current leader and fetch node contact info
68pub struct TpuClient<
69    P, // ConnectionPool
70    M, // ConnectionManager
71    C, // NewConnectionConfig
72> {
73    rpc_client: Arc<RpcClient>,
74    tpu_client: Arc<NonblockingTpuClient<P, M, C>>,
75}
76
77impl<P, M, C> TpuClient<P, M, C>
78where
79    P: ConnectionPool<NewConnectionConfig = C>,
80    M: ConnectionManager<ConnectionPool = P, NewConnectionConfig = C>,
81    C: NewConnectionConfig,
82{
83    /// Serialize and send transaction to the current and upcoming leader TPUs according to fanout
84    /// size
85    pub fn send_transaction(&self, transaction: &Transaction) -> bool {
86        self.invoke(self.tpu_client.send_transaction(transaction))
87    }
88
89    /// Send a wire transaction to the current and upcoming leader TPUs according to fanout size
90    pub fn send_wire_transaction(&self, wire_transaction: Vec<u8>) -> bool {
91        self.invoke(self.tpu_client.send_wire_transaction(wire_transaction))
92    }
93
94    /// Serialize and send transaction to the current and upcoming leader TPUs according to fanout
95    /// size
96    /// Returns the last error if all sends fail
97    pub fn try_send_transaction(&self, transaction: &VersionedTransaction) -> TransportResult<()> {
98        self.invoke(self.tpu_client.try_send_transaction(transaction))
99    }
100
101    /// Serialize and send transaction to the current and upcoming leader TPUs according to fanout.
102    ///
103    /// Returns an error if:
104    /// 1. there are no known tpu sockets to send to
105    /// 2. any of the sends fail, even if other sends succeeded.
106    pub fn send_transaction_to_upcoming_leaders(
107        &self,
108        transaction: &Transaction,
109    ) -> TransportResult<()> {
110        let wire_transaction =
111            Arc::new(wincode::serialize(&transaction).expect("should serialize transaction"));
112
113        let leaders = self
114            .tpu_client
115            .get_leader_tpu_service()
116            .unique_leader_tpu_sockets(self.tpu_client.get_fanout_slots());
117
118        let mut last_error: Option<TransportError> = None;
119        let mut some_success = false;
120        for tpu_address in &leaders {
121            let cache = self.tpu_client.get_connection_cache();
122            let conn = cache.get_connection(tpu_address);
123            if let Err(err) = conn.send_data_async(wire_transaction.clone()) {
124                last_error = Some(err);
125            } else {
126                some_success = true;
127            }
128        }
129
130        if let Some(err) = last_error {
131            Err(err)
132        } else if !some_success {
133            Err(std::io::Error::other("No sends attempted").into())
134        } else {
135            Ok(())
136        }
137    }
138
139    /// Serialize and send a batch of transactions to the current and upcoming leader TPUs according
140    /// to fanout size
141    /// Returns the last error if all sends fail
142    pub fn try_send_transaction_batch(
143        &self,
144        transactions: &[VersionedTransaction],
145    ) -> TransportResult<()> {
146        let wire_transactions = transactions
147            .into_par_iter()
148            .map(|tx| wincode::serialize(&tx).expect("serialize Transaction in send_batch"))
149            .collect::<Vec<_>>();
150        self.invoke(
151            self.tpu_client
152                .try_send_wire_transaction_batch(wire_transactions),
153        )
154    }
155
156    /// Send a wire transaction to the current and upcoming leader TPUs according to fanout size
157    /// Returns the last error if all sends fail
158    pub fn try_send_wire_transaction(&self, wire_transaction: Vec<u8>) -> TransportResult<()> {
159        self.invoke(self.tpu_client.try_send_wire_transaction(wire_transaction))
160    }
161
162    pub fn try_send_wire_transaction_batch(
163        &self,
164        wire_transactions: Vec<Vec<u8>>,
165    ) -> TransportResult<()> {
166        self.invoke(
167            self.tpu_client
168                .try_send_wire_transaction_batch(wire_transactions),
169        )
170    }
171
172    /// Create a new client that disconnects when dropped
173    pub fn new(
174        name: &'static str,
175        rpc_client: Arc<RpcClient>,
176        websocket_url: &str,
177        config: TpuClientConfig,
178        connection_manager: M,
179    ) -> Result<Self> {
180        let create_tpu_client = NonblockingTpuClient::new(
181            name,
182            rpc_client.get_inner_client().clone(),
183            websocket_url,
184            config,
185            connection_manager,
186        );
187        let tpu_client =
188            tokio::task::block_in_place(|| rpc_client.runtime().block_on(create_tpu_client))?;
189
190        Ok(Self {
191            rpc_client,
192            tpu_client: Arc::new(tpu_client),
193        })
194    }
195
196    /// Create a new client that disconnects when dropped
197    pub fn new_with_connection_cache(
198        rpc_client: Arc<RpcClient>,
199        websocket_url: &str,
200        config: TpuClientConfig,
201        connection_cache: Arc<ConnectionCache<P, M, C>>,
202    ) -> Result<Self> {
203        let create_tpu_client = NonblockingTpuClient::new_with_connection_cache(
204            rpc_client.get_inner_client().clone(),
205            websocket_url,
206            config,
207            connection_cache,
208        );
209        let tpu_client =
210            tokio::task::block_in_place(|| rpc_client.runtime().block_on(create_tpu_client))?;
211
212        Ok(Self {
213            rpc_client,
214            tpu_client: Arc::new(tpu_client),
215        })
216    }
217
218    #[cfg(feature = "spinner")]
219    pub fn send_and_confirm_messages_with_spinner<T: Signers + ?Sized>(
220        &self,
221        messages: &[Message],
222        signers: &T,
223    ) -> Result<Vec<Option<TransactionError>>> {
224        self.invoke(
225            self.tpu_client
226                .send_and_confirm_messages_with_spinner(messages, signers),
227        )
228    }
229
230    pub fn rpc_client(&self) -> &RpcClient {
231        &self.rpc_client
232    }
233
234    fn invoke<T, F: std::future::Future<Output = T>>(&self, f: F) -> T {
235        // `block_on()` panics if called within an asynchronous execution context. Whereas
236        // `block_in_place()` only panics if called from a current_thread runtime, which is the
237        // lesser evil.
238        tokio::task::block_in_place(move || self.rpc_client.runtime().block_on(f))
239    }
240}
241
242// Methods below are required for calls to client.async_transfer()
243// where client is of type TpuClient<P, M, C>
244impl<P, M, C> AsyncClient for TpuClient<P, M, C>
245where
246    P: ConnectionPool<NewConnectionConfig = C>,
247    M: ConnectionManager<ConnectionPool = P, NewConnectionConfig = C>,
248    C: NewConnectionConfig,
249{
250    fn async_send_versioned_transaction(
251        &self,
252        transaction: VersionedTransaction,
253    ) -> TransportResult<Signature> {
254        let wire_transaction =
255            wincode::serialize(&transaction).expect("serialize Transaction in send_batch");
256        self.send_wire_transaction(wire_transaction);
257        Ok(transaction.signatures[0])
258    }
259
260    fn async_send_versioned_transaction_batch(
261        &self,
262        batch: Vec<VersionedTransaction>,
263    ) -> TransportResult<()> {
264        let buffers = batch
265            .into_par_iter()
266            .map(|tx| wincode::serialize(&tx).expect("serialize Transaction in send_batch"))
267            .collect::<Vec<_>>();
268        self.try_send_wire_transaction_batch(buffers)?;
269        Ok(())
270    }
271}
272
273// 48 chosen because it's unlikely that 12 leaders in a row will miss their slots
274const MAX_SLOT_SKIP_DISTANCE: u64 = 48;
275
276#[derive(Clone, Debug)]
277pub(crate) struct RecentLeaderSlots(Arc<RwLock<VecDeque<Slot>>>);
278impl RecentLeaderSlots {
279    pub(crate) fn new(current_slot: Slot) -> Self {
280        let mut recent_slots = VecDeque::new();
281        recent_slots.push_back(current_slot);
282        Self(Arc::new(RwLock::new(recent_slots)))
283    }
284
285    pub(crate) fn record_slot(&self, current_slot: Slot) {
286        let mut recent_slots = self.0.write().unwrap();
287        recent_slots.push_back(current_slot);
288        // 12 recent slots should be large enough to avoid a misbehaving
289        // validator from affecting the median recent slot
290        while recent_slots.len() > 12 {
291            recent_slots.pop_front();
292        }
293    }
294
295    // Estimate the current slot from recent slot notifications.
296    pub(crate) fn estimated_current_slot(&self) -> Slot {
297        let mut recent_slots: Vec<Slot> = self.0.read().unwrap().iter().cloned().collect();
298        assert!(!recent_slots.is_empty());
299        recent_slots.sort_unstable();
300
301        // Validators can broadcast invalid blocks that are far in the future
302        // so check if the current slot is in line with the recent progression.
303        let max_index = recent_slots.len() - 1;
304        let median_index = max_index / 2;
305        let median_recent_slot = recent_slots[median_index];
306        let expected_current_slot = median_recent_slot + (max_index - median_index) as u64;
307        let max_reasonable_current_slot = expected_current_slot + MAX_SLOT_SKIP_DISTANCE;
308
309        // Return the highest slot that doesn't exceed what we believe is a
310        // reasonable slot.
311        recent_slots
312            .into_iter()
313            .rev()
314            .find(|slot| *slot <= max_reasonable_current_slot)
315            .unwrap()
316    }
317}
318
319#[cfg(test)]
320impl From<Vec<Slot>> for RecentLeaderSlots {
321    fn from(recent_slots: Vec<Slot>) -> Self {
322        assert!(!recent_slots.is_empty());
323        Self(Arc::new(RwLock::new(recent_slots.into_iter().collect())))
324    }
325}
326
327#[cfg(test)]
328mod tests {
329    use super::*;
330
331    fn assert_slot(recent_slots: RecentLeaderSlots, expected_slot: Slot) {
332        assert_eq!(recent_slots.estimated_current_slot(), expected_slot);
333    }
334
335    #[test]
336    fn test_recent_leader_slots() {
337        assert_slot(RecentLeaderSlots::new(0), 0);
338
339        let mut recent_slots: Vec<Slot> = (1..=12).collect();
340        assert_slot(RecentLeaderSlots::from(recent_slots.clone()), 12);
341
342        recent_slots.reverse();
343        assert_slot(RecentLeaderSlots::from(recent_slots), 12);
344
345        assert_slot(
346            RecentLeaderSlots::from(vec![0, 1 + MAX_SLOT_SKIP_DISTANCE]),
347            1 + MAX_SLOT_SKIP_DISTANCE,
348        );
349        assert_slot(
350            RecentLeaderSlots::from(vec![0, 2 + MAX_SLOT_SKIP_DISTANCE]),
351            0,
352        );
353
354        assert_slot(RecentLeaderSlots::from(vec![1]), 1);
355        assert_slot(RecentLeaderSlots::from(vec![1, 100]), 1);
356        assert_slot(RecentLeaderSlots::from(vec![1, 2, 100]), 2);
357        assert_slot(RecentLeaderSlots::from(vec![1, 2, 3, 100]), 3);
358        assert_slot(RecentLeaderSlots::from(vec![1, 2, 3, 99, 100]), 3);
359    }
360}