1use std::sync::Arc;
7
8use rand::{distributions::Bernoulli, prelude::*};
9use tokio::{
10 sync::{mpsc, OwnedSemaphorePermit, Semaphore},
11 task::JoinSet,
12 time::{sleep, timeout},
13};
14use tower::{Service, ServiceExt};
15use tracing::{instrument, Instrument, Span};
16
17use cuprate_p2p_core::{
18 client::{Client, ConnectRequest, HandshakeError, PeerSyncCallback},
19 services::{AddressBookRequest, AddressBookResponse},
20 AddressBook, NetworkZone,
21};
22
23use crate::{
24 config::P2PConfig,
25 constants::{HANDSHAKE_TIMEOUT, MAX_SEED_CONNECTIONS, OUTBOUND_CONNECTION_ATTEMPT_TIMEOUT},
26};
27
28enum OutboundConnectorError {
29 MaxConnections,
30 FailedToConnectToSeeds,
31 NoAvailablePeers,
32}
33
34pub struct MakeConnectionRequest {
39 block_needed: Option<usize>,
41}
42
43pub struct OutboundConnectionKeeper<Z: NetworkZone, A, C> {
47 pub new_peers_tx: mpsc::Sender<Client<Z>>,
49 pub make_connection_rx: mpsc::Receiver<MakeConnectionRequest>,
51 pub address_book_svc: A,
53 pub connector_svc: C,
55 pub outbound_semaphore: Arc<Semaphore>,
57 pub extra_peers: usize,
61 pub config: P2PConfig<Z>,
63 pub peer_type_gen: Bernoulli,
68 pub peer_sync_callback: Option<PeerSyncCallback>,
70}
71
72impl<Z, A, C> OutboundConnectionKeeper<Z, A, C>
73where
74 Z: NetworkZone,
75 A: AddressBook<Z>,
76 C: Service<ConnectRequest<Z>, Response = Client<Z>, Error = HandshakeError>,
77 C::Future: Send + 'static,
78{
79 pub fn new(
80 config: P2PConfig<Z>,
81 new_peers_tx: mpsc::Sender<Client<Z>>,
82 make_connection_rx: mpsc::Receiver<MakeConnectionRequest>,
83 address_book_svc: A,
84 connector_svc: C,
85 peer_sync_callback: Option<PeerSyncCallback>,
86 ) -> Self {
87 let peer_type_gen = Bernoulli::new(config.gray_peers_percent)
88 .expect("Gray peer percent is incorrect should be 0..=1");
89
90 Self {
91 new_peers_tx,
92 make_connection_rx,
93 address_book_svc,
94 connector_svc,
95 outbound_semaphore: Arc::new(Semaphore::new(config.outbound_connections)),
96 extra_peers: 0,
97 config,
98 peer_type_gen,
99 peer_sync_callback,
100 }
101 }
102
103 #[instrument(level = "info", skip(self))]
105 async fn connect_to_random_seeds(&mut self) -> Result<(), OutboundConnectorError> {
106 let seeds = self
107 .config
108 .seeds
109 .choose_multiple(&mut thread_rng(), MAX_SEED_CONNECTIONS);
110
111 assert_ne!(seeds.len(), 0, "No seed nodes available to get peers from");
112
113 let mut allowed_errors = seeds.len();
114
115 let mut handshake_futs = JoinSet::new();
116
117 for seed in seeds {
118 tracing::info!("Getting peers from seed node: {}", seed);
119
120 let addr = *seed;
121 let fut = timeout(
122 HANDSHAKE_TIMEOUT,
123 self.connector_svc
124 .ready()
125 .await
126 .expect("Connector had an error in `poll_ready`")
127 .call(ConnectRequest { addr, permit: None }),
128 );
129 handshake_futs.spawn(
131 async move {
132 match fut.await {
133 Err(_) => {
134 tracing::warn!("Timed out connecting to seed node: {addr}");
135 false
136 }
137 Ok(Err(e)) => {
138 tracing::warn!("Failed to connect to seed node {addr}: {e}");
139 false
140 }
141 Ok(Ok(_)) => true,
142 }
143 }
144 .instrument(Span::current()),
145 );
146 }
147
148 while let Some(res) = handshake_futs.join_next().await {
149 if !res.unwrap_or(false) {
150 allowed_errors -= 1;
151 }
152 }
153
154 if allowed_errors == 0 {
155 Err(OutboundConnectorError::FailedToConnectToSeeds)
156 } else {
157 Ok(())
158 }
159 }
160
161 #[instrument(level = "info", skip_all)]
163 async fn connect_to_outbound_peer(&mut self, permit: OwnedSemaphorePermit, addr: Z::Addr) {
164 let new_peers_tx = self.new_peers_tx.clone();
165 let peer_sync_callback = self.peer_sync_callback.clone();
166 let connection_fut = self
167 .connector_svc
168 .ready()
169 .await
170 .expect("Connector had an error in `poll_ready`")
171 .call(ConnectRequest {
172 addr,
173 permit: Some(permit),
174 });
175
176 tokio::spawn(
177 async move {
178 if let Ok(Ok(peer)) = timeout(HANDSHAKE_TIMEOUT, connection_fut).await {
179 let csd = peer.info.core_sync_data.lock().unwrap().clone();
180 if new_peers_tx.send(peer).await.is_ok() {
181 if let Some(ref peer_sync_callback) = peer_sync_callback {
182 peer_sync_callback.call(&csd);
183 }
184 }
185 }
186 }
187 .instrument(Span::current()),
188 );
189 }
190
191 #[expect(
193 clippy::significant_drop_tightening,
194 reason = "we need to hold onto a permit"
195 )]
196 async fn handle_peer_request(
197 &mut self,
198 req: &MakeConnectionRequest,
199 ) -> Result<(), OutboundConnectorError> {
200 let permit = Arc::clone(&self.outbound_semaphore)
202 .try_acquire_owned()
203 .or_else(|_| {
204 if self.extra_peers >= self.config.extra_outbound_connections {
206 Err(OutboundConnectorError::MaxConnections)
208 } else {
209 self.outbound_semaphore.add_permits(1);
210 self.extra_peers += 1;
211 Ok(Arc::clone(&self.outbound_semaphore)
212 .try_acquire_owned()
213 .unwrap())
214 }
215 })?;
216
217 let peer = self
219 .address_book_svc
220 .ready()
221 .await
222 .expect("Error in address book!")
223 .call(AddressBookRequest::TakeRandomPeer {
224 height: req.block_needed,
225 })
226 .await;
227
228 match peer {
229 Err(_) => {
230 tracing::warn!("No peers in address book which are available and have the data we need. Getting peers from seed nodes.");
232
233 self.connect_to_random_seeds().await?;
234 Err(OutboundConnectorError::NoAvailablePeers)
235 }
236
237 Ok(AddressBookResponse::Peer(peer)) => {
238 self.connect_to_outbound_peer(permit, peer.adr).await;
239 Ok(())
240 }
241 Ok(_) => panic!("peer list sent incorrect response!"),
242 }
243 }
244
245 #[instrument(level = "debug", skip(self, permit))]
248 async fn handle_free_permit(
249 &mut self,
250 permit: OwnedSemaphorePermit,
251 ) -> Result<(), OutboundConnectorError> {
252 if self.extra_peers > 0 {
253 tracing::debug!(
254 "Permit available but we are over the minimum number of peers, forgetting permit."
255 );
256 permit.forget();
257 self.extra_peers -= 1;
258 return Ok(());
259 }
260
261 tracing::debug!("Permit available, making outbound connection.");
262
263 let req = if self.peer_type_gen.sample(&mut thread_rng()) {
264 AddressBookRequest::TakeRandomGrayPeer { height: None }
265 } else {
266 AddressBookRequest::TakeRandomPeer { height: None }
268 };
269
270 let Ok(AddressBookResponse::Peer(peer)) = self
271 .address_book_svc
272 .ready()
273 .await
274 .expect("Error in address book!")
275 .call(req)
276 .await
277 else {
278 tracing::warn!("No peers in peer list to make connection to.");
279 self.connect_to_random_seeds().await?;
280 return Err(OutboundConnectorError::NoAvailablePeers);
281 };
282
283 self.connect_to_outbound_peer(permit, peer.adr).await;
284 Ok(())
285 }
286
287 pub async fn run(mut self) {
289 tracing::info!(
290 "Starting outbound connection maintainer, target outbound connections: {}",
291 self.config.outbound_connections
292 );
293
294 loop {
295 tokio::select! {
296 biased;
297 peer_req = self.make_connection_rx.recv() => {
298 let Some(peer_req) = peer_req else {
299 tracing::info!("Shutting down outbound connector, make connection channel closed.");
300 return;
301 };
302 #[expect(clippy::let_underscore_must_use, reason = "We can't really do much about errors in this function.")]
303 let _ = self.handle_peer_request(&peer_req).await;
304 },
305 Ok(permit) = Arc::clone(&self.outbound_semaphore).acquire_owned() => {
308 if self.handle_free_permit(permit).await.is_err() {
309 sleep(OUTBOUND_CONNECTION_ATTEMPT_TIMEOUT).await;
312 }
313 }
314 }
315 }
316 }
317}