Skip to main content

solana_client/
connection_cache.rs

1pub use solana_connection_cache::connection_cache::{DEFAULT_MAX_CONNECTIONS, Protocol};
2use {
3    solana_connection_cache::{
4        client_connection::ClientConnection,
5        connection_cache::{
6            BaseClientConnection, ConnectionCache as BackendConnectionCache, ConnectionPool,
7            NewConnectionConfig,
8        },
9    },
10    solana_keypair::Keypair,
11    solana_pubkey::Pubkey,
12    solana_quic_client::{QuicConfig, QuicConnectionManager, QuicPool},
13    solana_streamer::streamer::StakedNodes,
14    solana_tls_utils::NotifyKeyUpdate,
15    solana_transaction_error::TransportResult,
16    solana_udp_client::{UdpConfig, UdpConnectionManager, UdpPool},
17    std::{
18        net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
19        sync::{Arc, RwLock},
20    },
21};
22
23const DEFAULT_CONNECTION_POOL_SIZE: usize = 4;
24const DEFAULT_CONNECTION_CACHE_USE_QUIC: bool = true;
25
26/// A thin wrapper over connection-cache/ConnectionCache to ease
27/// construction of the ConnectionCache for code dealing both with udp and quic.
28/// For the scenario only using udp or quic, use connection-cache/ConnectionCache directly.
29pub enum ConnectionCache {
30    Quic(Arc<BackendConnectionCache<QuicPool, QuicConnectionManager, QuicConfig>>),
31    Udp(Arc<BackendConnectionCache<UdpPool, UdpConnectionManager, UdpConfig>>),
32}
33
34type QuicBaseClientConnection = <QuicPool as ConnectionPool>::BaseClientConnection;
35type UdpBaseClientConnection = <UdpPool as ConnectionPool>::BaseClientConnection;
36
37pub enum BlockingClientConnection {
38    Quic(Arc<<QuicBaseClientConnection as BaseClientConnection>::BlockingClientConnection>),
39    Udp(Arc<<UdpBaseClientConnection as BaseClientConnection>::BlockingClientConnection>),
40}
41
42pub enum NonblockingClientConnection {
43    Quic(Arc<<QuicBaseClientConnection as BaseClientConnection>::NonblockingClientConnection>),
44    Udp(Arc<<UdpBaseClientConnection as BaseClientConnection>::NonblockingClientConnection>),
45}
46
47impl NotifyKeyUpdate for ConnectionCache {
48    fn update_key(&self, key: &Keypair) -> Result<(), Box<dyn std::error::Error>> {
49        match self {
50            Self::Udp(_) => Ok(()),
51            Self::Quic(backend) => backend.update_key(key),
52        }
53    }
54}
55
56impl ConnectionCache {
57    pub fn new(name: &'static str) -> Self {
58        if DEFAULT_CONNECTION_CACHE_USE_QUIC {
59            let cert_info = (&Keypair::new(), IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)));
60            ConnectionCache::new_with_client_options(
61                name,
62                DEFAULT_CONNECTION_POOL_SIZE,
63                None, // client_endpoint
64                Some(cert_info),
65                None, // stake_info
66            )
67        } else {
68            ConnectionCache::with_udp(name, DEFAULT_CONNECTION_POOL_SIZE)
69        }
70    }
71
72    /// Create a quic connection_cache
73    pub fn new_quic(name: &'static str, connection_pool_size: usize) -> Self {
74        Self::new_with_client_options(name, connection_pool_size, None, None, None)
75    }
76
77    #[cfg(feature = "dev-context-only-utils")]
78    pub fn new_quic_for_tests(name: &'static str, connection_pool_size: usize) -> Self {
79        Self::new_with_client_options(
80            name,
81            connection_pool_size,
82            Some(solana_net_utils::sockets::bind_to_localhost_unique().unwrap()),
83            None,
84            None,
85        )
86    }
87
88    /// Create a quic connection_cache with more client options
89    pub fn new_with_client_options(
90        name: &'static str,
91        connection_pool_size: usize,
92        client_socket: Option<UdpSocket>,
93        cert_info: Option<(&Keypair, IpAddr)>,
94        stake_info: Option<(&Arc<RwLock<StakedNodes>>, &Pubkey)>,
95    ) -> Self {
96        Self::new_with_max_connections(
97            name,
98            connection_pool_size,
99            DEFAULT_MAX_CONNECTIONS,
100            client_socket,
101            cert_info,
102            stake_info,
103        )
104    }
105
106    /// Create a quic connection_cache with configurable max connections
107    pub fn new_with_max_connections(
108        name: &'static str,
109        connection_pool_size: usize,
110        max_connections: usize,
111        client_socket: Option<UdpSocket>,
112        cert_info: Option<(&Keypair, IpAddr)>,
113        stake_info: Option<(&Arc<RwLock<StakedNodes>>, &Pubkey)>,
114    ) -> Self {
115        // The minimum pool size is 1.
116        let connection_pool_size = 1.max(connection_pool_size);
117        let mut config = QuicConfig::new().unwrap();
118        if let Some(cert_info) = cert_info {
119            config.update_client_certificate(cert_info.0, cert_info.1);
120        }
121        if let Some(client_socket) = client_socket {
122            config.update_client_endpoint(client_socket);
123        }
124        if let Some(stake_info) = stake_info {
125            config.set_staked_nodes(stake_info.0, stake_info.1);
126        }
127        let connection_manager = QuicConnectionManager::new_with_connection_config(config);
128        let cache = BackendConnectionCache::new_with_max_connections(
129            name,
130            connection_manager,
131            connection_pool_size,
132            max_connections,
133        )
134        .unwrap();
135        Self::Quic(Arc::new(cache))
136    }
137
138    #[inline]
139    pub fn protocol(&self) -> Protocol {
140        match self {
141            Self::Quic(_) => Protocol::QUIC,
142            Self::Udp(_) => Protocol::UDP,
143        }
144    }
145
146    pub fn with_udp(name: &'static str, connection_pool_size: usize) -> Self {
147        // The minimum pool size is 1.
148        let connection_pool_size = 1.max(connection_pool_size);
149        let connection_manager = UdpConnectionManager::default();
150        let cache =
151            BackendConnectionCache::new(name, connection_manager, connection_pool_size).unwrap();
152        Self::Udp(Arc::new(cache))
153    }
154
155    pub fn use_quic(&self) -> bool {
156        matches!(self, Self::Quic(_))
157    }
158
159    pub fn get_connection(&self, addr: &SocketAddr) -> BlockingClientConnection {
160        match self {
161            Self::Quic(cache) => BlockingClientConnection::Quic(cache.get_connection(addr)),
162            Self::Udp(cache) => BlockingClientConnection::Udp(cache.get_connection(addr)),
163        }
164    }
165
166    pub fn get_nonblocking_connection(&self, addr: &SocketAddr) -> NonblockingClientConnection {
167        match self {
168            Self::Quic(cache) => {
169                NonblockingClientConnection::Quic(cache.get_nonblocking_connection(addr))
170            }
171            Self::Udp(cache) => {
172                NonblockingClientConnection::Udp(cache.get_nonblocking_connection(addr))
173            }
174        }
175    }
176}
177
178macro_rules! dispatch {
179    ($(#[$meta:meta])* $vis:vis fn $name:ident$(<$($t:ident: $cons:ident + ?Sized),*>)?(&self $(, $arg:ident: $ty:ty)*) $(-> $out:ty)?) => {
180        #[inline]
181        $(#[$meta])*
182        $vis fn $name$(<$($t: $cons + ?Sized),*>)?(&self $(, $arg:$ty)*) $(-> $out)? {
183            match self {
184                Self::Quic(this) => this.$name($($arg, )*),
185                Self::Udp(this) => this.$name($($arg, )*),
186            }
187        }
188    };
189}
190
191impl ClientConnection for BlockingClientConnection {
192    dispatch!(fn server_addr(&self) -> &SocketAddr);
193    dispatch!(fn send_data(&self, buffer: &[u8]) -> TransportResult<()>);
194    dispatch!(fn send_data_async(&self, buffer: Arc<Vec<u8>>) -> TransportResult<()>);
195    dispatch!(fn send_data_batch(&self, buffers: &[Vec<u8>]) -> TransportResult<()>);
196    dispatch!(fn send_data_batch_async(&self, buffers: Vec<Vec<u8>>) -> TransportResult<()>);
197}
198
199#[async_trait::async_trait]
200impl solana_connection_cache::nonblocking::client_connection::ClientConnection
201    for NonblockingClientConnection
202{
203    dispatch!(fn server_addr(&self) -> &SocketAddr);
204
205    async fn send_data(&self, buffer: &[u8]) -> TransportResult<()> {
206        match self {
207            Self::Quic(cache) => Ok(cache.send_data(buffer).await?),
208            Self::Udp(cache) => Ok(cache.send_data(buffer).await?),
209        }
210    }
211
212    async fn send_data_batch(&self, buffers: &[Vec<u8>]) -> TransportResult<()> {
213        match self {
214            Self::Quic(cache) => Ok(cache.send_data_batch(buffers).await?),
215            Self::Udp(cache) => Ok(cache.send_data_batch(buffers).await?),
216        }
217    }
218}
219
220#[cfg(test)]
221mod tests {
222    use {
223        super::*,
224        crate::connection_cache::ConnectionCache,
225        solana_net_utils::sockets::{bind_to, localhost_port_range_for_tests},
226        std::net::{IpAddr, Ipv4Addr, SocketAddr},
227    };
228
229    #[test]
230    fn test_connection_with_specified_client_endpoint() {
231        let port_range = localhost_port_range_for_tests();
232        let mut port_range = port_range.0..port_range.1;
233        let client_socket =
234            bind_to(IpAddr::V4(Ipv4Addr::LOCALHOST), port_range.next().unwrap()).unwrap();
235        let connection_cache = ConnectionCache::new_with_client_options(
236            "connection_cache_test",
237            1,                   // connection_pool_size
238            Some(client_socket), // client_endpoint
239            None,                // cert_info
240            None,                // stake_info
241        );
242
243        // server port 1:
244        let port1 = port_range.next().unwrap();
245        let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port1);
246        let conn = connection_cache.get_connection(&addr);
247        assert_eq!(conn.server_addr().port(), port1);
248
249        // server port 2:
250        let port2 = port_range.next().unwrap();
251        let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port2);
252        let conn = connection_cache.get_connection(&addr);
253        assert_eq!(conn.server_addr().port(), port2);
254    }
255}