Skip to main content

solana_quic_client/nonblocking/
quic_client.rs

1//! Simple nonblocking client that connects to a given UDP port with the QUIC protocol
2//! and provides an interface for sending data which is restricted by the
3//! server's flow control.
4use {
5    async_lock::Mutex,
6    async_trait::async_trait,
7    futures::future::TryFutureExt,
8    log::*,
9    quinn::{
10        ClientConfig, ClosedStream, ConnectError, Connection, ConnectionError, Endpoint,
11        EndpointConfig, IdleTimeout, TokioRuntime, TransportConfig, WriteError,
12        crypto::rustls::QuicClientConfig,
13    },
14    solana_connection_cache::{
15        client_connection::ClientStats, connection_cache_stats::ConnectionCacheStats,
16        nonblocking::client_connection::ClientConnection,
17    },
18    solana_keypair::Keypair,
19    solana_measure::measure::Measure,
20    solana_net_utils::sockets,
21    solana_rpc_client_api::client_error::ErrorKind as ClientErrorKind,
22    solana_streamer::{nonblocking::quic::ALPN_TPU_PROTOCOL_ID, quic::QUIC_MAX_TIMEOUT},
23    solana_tls_utils::{
24        QuicClientCertificate, new_dummy_x509_certificate, socket_addr_to_quic_server_name,
25        tls_client_config_builder,
26    },
27    solana_transaction_error::TransportResult,
28    std::{
29        net::{SocketAddr, UdpSocket},
30        sync::{Arc, atomic::Ordering},
31        thread,
32        time::Duration,
33    },
34    thiserror::Error,
35    tokio::{sync::OnceCell, time::timeout},
36};
37
38const QUIC_KEEP_ALIVE: Duration = Duration::from_secs(1);
39
40// Based on commonly-used handshake timeouts for various TCP
41// applications. Different applications vary, but most seem to
42// be in the 30-60 second range
43pub const QUIC_CONNECTION_HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(60);
44
45/// A lazy-initialized Quic Endpoint
46pub struct QuicLazyInitializedEndpoint {
47    endpoint: OnceCell<Arc<Endpoint>>,
48    client_certificate: Arc<QuicClientCertificate>,
49    client_endpoint: Option<Endpoint>,
50}
51
52#[derive(Error, Debug)]
53pub enum QuicError {
54    #[error(transparent)]
55    WriteError(#[from] WriteError),
56    #[error(transparent)]
57    ConnectionError(#[from] ConnectionError),
58    #[error(transparent)]
59    ConnectError(#[from] ConnectError),
60    #[error(transparent)]
61    ClosedStream(#[from] ClosedStream),
62}
63
64impl From<QuicError> for ClientErrorKind {
65    fn from(quic_error: QuicError) -> Self {
66        Self::Custom(format!("{quic_error:?}"))
67    }
68}
69
70impl QuicLazyInitializedEndpoint {
71    pub fn new(
72        client_certificate: Arc<QuicClientCertificate>,
73        client_endpoint: Option<Endpoint>,
74    ) -> Self {
75        Self {
76            endpoint: OnceCell::<Arc<Endpoint>>::new(),
77            client_certificate,
78            client_endpoint,
79        }
80    }
81
82    fn create_endpoint(&self) -> Endpoint {
83        let mut endpoint = if let Some(endpoint) = &self.client_endpoint {
84            endpoint.clone()
85        } else {
86            // This will bind to random ports, but VALIDATOR_PORT_RANGE is outside
87            // of the range for CI tests when this is running in CI
88            let client_socket = sockets::bind_in_range_with_config(
89                std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED),
90                solana_net_utils::VALIDATOR_PORT_RANGE,
91                sockets::SocketConfiguration::default(),
92            )
93            .expect("QuicLazyInitializedEndpoint::create_endpoint bind_in_range")
94            .1;
95            info!("Local endpoint is : {client_socket:?}");
96
97            QuicNewConnection::create_endpoint(EndpointConfig::default(), client_socket)
98        };
99
100        let mut crypto = tls_client_config_builder()
101            .with_client_auth_cert(
102                vec![self.client_certificate.certificate.clone()],
103                self.client_certificate.key.clone_key(),
104            )
105            .expect("Failed to set QUIC client certificates");
106        crypto.enable_early_data = true;
107        crypto.alpn_protocols = vec![ALPN_TPU_PROTOCOL_ID.to_vec()];
108
109        let mut config = ClientConfig::new(Arc::new(QuicClientConfig::try_from(crypto).unwrap()));
110        let mut transport_config = TransportConfig::default();
111
112        let timeout = IdleTimeout::try_from(QUIC_MAX_TIMEOUT).unwrap();
113        transport_config.max_idle_timeout(Some(timeout));
114        transport_config.keep_alive_interval(Some(QUIC_KEEP_ALIVE));
115        transport_config.send_fairness(false);
116        config.transport_config(Arc::new(transport_config));
117
118        endpoint.set_default_client_config(config);
119
120        endpoint
121    }
122
123    async fn get_endpoint(&self) -> Arc<Endpoint> {
124        self.endpoint
125            .get_or_init(|| async { Arc::new(self.create_endpoint()) })
126            .await
127            .clone()
128    }
129}
130
131impl Default for QuicLazyInitializedEndpoint {
132    fn default() -> Self {
133        let (cert, priv_key) = new_dummy_x509_certificate(&Keypair::new());
134        Self::new(
135            Arc::new(QuicClientCertificate {
136                certificate: cert,
137                key: priv_key,
138            }),
139            None,
140        )
141    }
142}
143
144/// A wrapper over NewConnection with additional capability to create the endpoint as part
145/// of creating a new connection.
146#[derive(Clone)]
147struct QuicNewConnection {
148    endpoint: Arc<Endpoint>,
149    connection: Arc<Connection>,
150}
151
152impl QuicNewConnection {
153    /// Create a QuicNewConnection given the remote address 'addr'.
154    async fn make_connection(
155        endpoint: Arc<QuicLazyInitializedEndpoint>,
156        addr: SocketAddr,
157        stats: &ClientStats,
158    ) -> Result<Self, QuicError> {
159        let mut make_connection_measure = Measure::start("make_connection_measure");
160        let endpoint = endpoint.get_endpoint().await;
161        let server_name = socket_addr_to_quic_server_name(addr);
162        let connecting = endpoint.connect(addr, &server_name)?;
163        stats.total_connections.fetch_add(1, Ordering::Relaxed);
164        if let Ok(connecting_result) = timeout(QUIC_CONNECTION_HANDSHAKE_TIMEOUT, connecting).await
165        {
166            if connecting_result.is_err() {
167                stats.connection_errors.fetch_add(1, Ordering::Relaxed);
168            }
169            make_connection_measure.stop();
170            stats
171                .make_connection_ms
172                .fetch_add(make_connection_measure.as_ms(), Ordering::Relaxed);
173
174            let connection = connecting_result?;
175
176            Ok(Self {
177                endpoint,
178                connection: Arc::new(connection),
179            })
180        } else {
181            Err(ConnectionError::TimedOut.into())
182        }
183    }
184
185    fn create_endpoint(config: EndpointConfig, client_socket: UdpSocket) -> Endpoint {
186        quinn::Endpoint::new(config, None, client_socket, Arc::new(TokioRuntime))
187            .expect("QuicNewConnection::create_endpoint quinn::Endpoint::new")
188    }
189
190    // Attempts to make a faster connection by taking advantage of pre-existing key material.
191    // Only works if connection to this endpoint was previously established.
192    async fn make_connection_0rtt(
193        &mut self,
194        addr: SocketAddr,
195        stats: &ClientStats,
196    ) -> Result<Arc<Connection>, QuicError> {
197        let server_name = socket_addr_to_quic_server_name(addr);
198        let connecting = self.endpoint.connect(addr, &server_name)?;
199        stats.total_connections.fetch_add(1, Ordering::Relaxed);
200        let connection = match connecting.into_0rtt() {
201            Ok((connection, zero_rtt)) => {
202                if let Ok(zero_rtt) = timeout(QUIC_CONNECTION_HANDSHAKE_TIMEOUT, zero_rtt).await {
203                    if zero_rtt {
204                        stats.zero_rtt_accepts.fetch_add(1, Ordering::Relaxed);
205                    } else {
206                        stats.zero_rtt_rejects.fetch_add(1, Ordering::Relaxed);
207                    }
208                    connection
209                } else {
210                    return Err(ConnectionError::TimedOut.into());
211                }
212            }
213            Err(connecting) => {
214                stats.connection_errors.fetch_add(1, Ordering::Relaxed);
215
216                if let Ok(connecting_result) =
217                    timeout(QUIC_CONNECTION_HANDSHAKE_TIMEOUT, connecting).await
218                {
219                    connecting_result?
220                } else {
221                    return Err(ConnectionError::TimedOut.into());
222                }
223            }
224        };
225        self.connection = Arc::new(connection);
226        Ok(self.connection.clone())
227    }
228}
229
230pub struct QuicClient {
231    endpoint: Arc<QuicLazyInitializedEndpoint>,
232    connection: Arc<Mutex<Option<QuicNewConnection>>>,
233    addr: SocketAddr,
234    stats: Arc<ClientStats>,
235}
236
237const CONNECTION_CLOSE_CODE_APPLICATION_CLOSE: u32 = 0u32;
238const CONNECTION_CLOSE_REASON_APPLICATION_CLOSE: &[u8] = b"dropped";
239
240impl QuicClient {
241    /// Explicitly close the connection. Must be called manually if cleanup is needed.
242    pub async fn close(&self) {
243        let mut conn_guard = self.connection.lock().await;
244        if let Some(conn) = conn_guard.take() {
245            debug!(
246                "Closing connection to {} connection_id: {:?}",
247                self.addr, conn.connection
248            );
249            conn.connection.close(
250                CONNECTION_CLOSE_CODE_APPLICATION_CLOSE.into(),
251                CONNECTION_CLOSE_REASON_APPLICATION_CLOSE,
252            );
253        }
254    }
255}
256
257impl QuicClient {
258    pub fn new(endpoint: Arc<QuicLazyInitializedEndpoint>, addr: SocketAddr) -> Self {
259        Self {
260            endpoint,
261            connection: Arc::new(Mutex::new(None)),
262            addr,
263            stats: Arc::new(ClientStats::default()),
264        }
265    }
266
267    async fn _send_buffer_using_conn(
268        data: &[u8],
269        connection: &Connection,
270    ) -> Result<(), QuicError> {
271        let mut send_stream = connection.open_uni().await?;
272        send_stream.write_all(data).await?;
273        Ok(())
274    }
275
276    // Attempts to send data, connecting/reconnecting as necessary
277    // On success, returns the connection used to successfully send the data
278    async fn _send_buffer(
279        &self,
280        data: &[u8],
281        stats: &ClientStats,
282        connection_stats: Arc<ConnectionCacheStats>,
283    ) -> Result<Arc<Connection>, QuicError> {
284        let mut measure_send_packet = Measure::start("send_packet_us");
285        let mut measure_prepare_connection = Measure::start("prepare_connection");
286        let mut connection_try_count = 0;
287        let mut last_connection_id = 0;
288        let mut last_error = None;
289        while connection_try_count < 2 {
290            let connection = {
291                let mut conn_guard = self.connection.lock().await;
292
293                let maybe_conn = conn_guard.as_mut();
294                match maybe_conn {
295                    Some(conn) => {
296                        if conn.connection.stable_id() == last_connection_id {
297                            // this is the problematic connection we had used before, create a new one
298                            let conn = conn.make_connection_0rtt(self.addr, stats).await;
299                            match conn {
300                                Ok(conn) => {
301                                    info!(
302                                        "Made 0rtt connection to {} with id {} try_count {}, \
303                                         last_connection_id: {}, last_error: {:?}",
304                                        self.addr,
305                                        conn.stable_id(),
306                                        connection_try_count,
307                                        last_connection_id,
308                                        last_error,
309                                    );
310                                    connection_try_count += 1;
311                                    conn
312                                }
313                                Err(err) => {
314                                    info!(
315                                        "Cannot make 0rtt connection to {}, error {:}",
316                                        self.addr, err
317                                    );
318                                    return Err(err);
319                                }
320                            }
321                        } else {
322                            stats.connection_reuse.fetch_add(1, Ordering::Relaxed);
323                            conn.connection.clone()
324                        }
325                    }
326                    None => {
327                        let conn = QuicNewConnection::make_connection(
328                            self.endpoint.clone(),
329                            self.addr,
330                            stats,
331                        )
332                        .await;
333                        match conn {
334                            Ok(conn) => {
335                                *conn_guard = Some(conn.clone());
336                                info!(
337                                    "Made connection to {} id {} try_count {}, from connection \
338                                     cache warming?: {}",
339                                    self.addr,
340                                    conn.connection.stable_id(),
341                                    connection_try_count,
342                                    data.is_empty(),
343                                );
344                                connection_try_count += 1;
345                                conn.connection.clone()
346                            }
347                            Err(err) => {
348                                info!(
349                                    "Cannot make connection to {}, error {:}, from connection \
350                                     cache warming?: {}",
351                                    self.addr,
352                                    err,
353                                    data.is_empty()
354                                );
355                                return Err(err);
356                            }
357                        }
358                    }
359                }
360            };
361
362            let new_stats = connection.stats();
363
364            connection_stats
365                .total_client_stats
366                .congestion_events
367                .update_stat(
368                    &self.stats.congestion_events,
369                    new_stats.path.congestion_events,
370                );
371
372            connection_stats
373                .total_client_stats
374                .streams_blocked_uni
375                .update_stat(
376                    &self.stats.streams_blocked_uni,
377                    new_stats.frame_tx.streams_blocked_uni,
378                );
379
380            connection_stats
381                .total_client_stats
382                .data_blocked
383                .update_stat(&self.stats.data_blocked, new_stats.frame_tx.data_blocked);
384
385            connection_stats
386                .total_client_stats
387                .acks
388                .update_stat(&self.stats.acks, new_stats.frame_tx.acks);
389
390            if data.is_empty() {
391                // no need to send packet as it is only for warming connections
392                return Ok(connection);
393            }
394
395            last_connection_id = connection.stable_id();
396            measure_prepare_connection.stop();
397
398            match Self::_send_buffer_using_conn(data, &connection).await {
399                Ok(()) => {
400                    measure_send_packet.stop();
401                    stats.successful_packets.fetch_add(1, Ordering::Relaxed);
402                    stats
403                        .send_packets_us
404                        .fetch_add(measure_send_packet.as_us(), Ordering::Relaxed);
405                    stats
406                        .prepare_connection_us
407                        .fetch_add(measure_prepare_connection.as_us(), Ordering::Relaxed);
408                    trace!(
409                        "Successfully sent to {} with id {}, thread: {:?}, data len: {}, \
410                         send_packet_us: {} prepare_connection_us: {}",
411                        self.addr,
412                        connection.stable_id(),
413                        thread::current().id(),
414                        data.len(),
415                        measure_send_packet.as_us(),
416                        measure_prepare_connection.as_us(),
417                    );
418
419                    return Ok(connection);
420                }
421                Err(err) => match err {
422                    QuicError::ConnectionError(_) => {
423                        last_error = Some(err);
424                    }
425                    _ => {
426                        info!(
427                            "Error sending to {} with id {}, error {:?} thread: {:?}",
428                            self.addr,
429                            connection.stable_id(),
430                            err,
431                            thread::current().id(),
432                        );
433                        return Err(err);
434                    }
435                },
436            }
437        }
438
439        // if we come here, that means we have exhausted maximum retries, return the error
440        info!(
441            "Ran into an error sending data {:?}, exhausted retries to {}",
442            last_error, self.addr
443        );
444        // If we get here but last_error is None, then we have a logic error
445        // in this function, so panic here with an expect to help debugging
446        Err(last_error.expect("QuicClient::_send_buffer last_error.expect"))
447    }
448
449    pub async fn send_buffer<T>(
450        &self,
451        data: T,
452        stats: &ClientStats,
453        connection_stats: Arc<ConnectionCacheStats>,
454    ) -> Result<(), ClientErrorKind>
455    where
456        T: AsRef<[u8]>,
457    {
458        self._send_buffer(data.as_ref(), stats, connection_stats)
459            .await
460            .map_err(Into::<ClientErrorKind>::into)?;
461        Ok(())
462    }
463
464    pub async fn send_batch<T>(
465        &self,
466        buffers: &[T],
467        stats: &ClientStats,
468        connection_stats: Arc<ConnectionCacheStats>,
469    ) -> Result<(), ClientErrorKind>
470    where
471        T: AsRef<[u8]>,
472    {
473        // Start off by "testing" the connection by sending the first buffer
474        // This will also connect to the server if not already connected
475        // and reconnect and retry if the first send attempt failed
476        // (for example due to a timed out connection), returning an error
477        // or the connection that was used to successfully send the buffer.
478        // We will use the returned connection to send the rest of the buffers in the batch
479        // to avoid touching the mutex in self, and not bother reconnecting if we fail along the way
480        // since testing even in the ideal GCE environment has found no cases
481        // where reconnecting and retrying in the middle of a batch send
482        // (i.e. we encounter a connection error in the middle of a batch send, which presumably cannot
483        // be due to a timed out connection) has succeeded
484        if buffers.is_empty() {
485            return Ok(());
486        }
487        let connection = self
488            ._send_buffer(buffers[0].as_ref(), stats, connection_stats)
489            .await
490            .map_err(Into::<ClientErrorKind>::into)?;
491
492        for data in buffers[1..buffers.len()].iter() {
493            Self::_send_buffer_using_conn(data.as_ref(), &connection).await?;
494        }
495        Ok(())
496    }
497
498    pub fn server_addr(&self) -> &SocketAddr {
499        &self.addr
500    }
501
502    pub fn stats(&self) -> Arc<ClientStats> {
503        self.stats.clone()
504    }
505}
506
507pub struct QuicClientConnection {
508    pub client: Arc<QuicClient>,
509    pub connection_stats: Arc<ConnectionCacheStats>,
510}
511
512impl QuicClientConnection {
513    pub fn base_stats(&self) -> Arc<ClientStats> {
514        self.client.stats()
515    }
516
517    pub fn connection_stats(&self) -> Arc<ConnectionCacheStats> {
518        self.connection_stats.clone()
519    }
520
521    pub fn new(
522        endpoint: Arc<QuicLazyInitializedEndpoint>,
523        addr: SocketAddr,
524        connection_stats: Arc<ConnectionCacheStats>,
525    ) -> Self {
526        let client = Arc::new(QuicClient::new(endpoint, addr));
527        Self::new_with_client(client, connection_stats)
528    }
529
530    pub fn new_with_client(
531        client: Arc<QuicClient>,
532        connection_stats: Arc<ConnectionCacheStats>,
533    ) -> Self {
534        Self {
535            client,
536            connection_stats,
537        }
538    }
539}
540
541#[async_trait]
542impl ClientConnection for QuicClientConnection {
543    fn server_addr(&self) -> &SocketAddr {
544        self.client.server_addr()
545    }
546
547    async fn send_data_batch(&self, buffers: &[Vec<u8>]) -> TransportResult<()> {
548        let stats = ClientStats::default();
549        let len = buffers.len();
550        let res = self
551            .client
552            .send_batch(buffers, &stats, self.connection_stats.clone())
553            .await;
554        self.connection_stats
555            .add_client_stats(&stats, len, res.is_ok());
556        res?;
557        Ok(())
558    }
559
560    async fn send_data(&self, data: &[u8]) -> TransportResult<()> {
561        let stats = Arc::new(ClientStats::default());
562        // When data is empty which is from cache warmer, we are not sending packets actually, do not count it in
563        let num_packets = if data.is_empty() { 0 } else { 1 };
564        self.client
565            .send_buffer(data, &stats, self.connection_stats.clone())
566            .map_ok(|v| {
567                self.connection_stats
568                    .add_client_stats(&stats, num_packets, true);
569                v
570            })
571            .map_err(|e| {
572                warn!(
573                    "Failed to send data async to {}, error: {:?} ",
574                    self.server_addr(),
575                    e
576                );
577                datapoint_warn!("send-wire-async", ("failure", 1, i64),);
578                self.connection_stats
579                    .add_client_stats(&stats, num_packets, false);
580                e.into()
581            })
582            .await
583    }
584}