1use std::sync::Arc;
6
7use futures::FutureExt;
8use tokio::{
9 sync::mpsc,
10 task::JoinSet,
11 time::{sleep, Duration},
12};
13use tower::{buffer::Buffer, util::BoxCloneService, Service, ServiceExt};
14use tracing::{instrument, Instrument, Span};
15
16use cuprate_p2p_core::{
17 client::Connector,
18 client::PeerSyncCallback,
19 services::{AddressBookRequest, AddressBookResponse},
20 CoreSyncSvc, NetworkZone, ProtocolRequestHandlerMaker, Transport,
21};
22
23pub mod block_downloader;
24mod broadcast;
25pub mod config;
26pub mod connection_maintainer;
27pub mod constants;
28mod inbound_server;
29mod peer_set;
30
31use block_downloader::{
32 BlockDownloaderConfig, BlockDownloaderHandle, ChainSvcRequest, ChainSvcResponse,
33};
34pub use broadcast::{BroadcastRequest, BroadcastSvc};
35pub use config::{AddressBookConfig, P2PConfig, TransportConfig};
36use connection_maintainer::MakeConnectionRequest;
37use peer_set::PeerSet;
38pub use peer_set::{ClientDropGuard, PeerSetRequest, PeerSetResponse};
39
40const INBOUND_CONNECTION_MONITOR_INTERVAL: Duration = Duration::from_secs(3600);
42
43#[expect(clippy::infinite_loop)]
48async fn inbound_connection_monitor(
49 inbound_semaphore: Arc<tokio::sync::Semaphore>,
50 max_inbound_connections: usize,
51 p2p_port: u16,
52) {
53 if max_inbound_connections == 0 {
55 return;
56 }
57
58 loop {
59 sleep(INBOUND_CONNECTION_MONITOR_INTERVAL).await;
61
62 let available_permits = inbound_semaphore.available_permits();
65 if available_permits == max_inbound_connections {
66 tracing::warn!(
67 "No incoming connections - check firewalls/routers allow port {}",
68 p2p_port
69 );
70 }
71 }
72}
73
74#[instrument(level = "error", name = "net", skip_all, fields(zone = Z::NAME))]
85pub async fn initialize_network<Z, T, PR, CS>(
86 protocol_request_handler_maker: PR,
87 core_sync_svc: CS,
88 config: P2PConfig<Z>,
89 transport_config: TransportConfig<Z, T>,
90 peer_sync_callback: Option<PeerSyncCallback>,
91) -> Result<NetworkInterface<Z>, tower::BoxError>
92where
93 Z: NetworkZone,
94 T: Transport<Z>,
95 Z::Addr: borsh::BorshDeserialize + borsh::BorshSerialize,
96 PR: ProtocolRequestHandlerMaker<Z> + Clone,
97 CS: CoreSyncSvc + Clone,
98{
99 let max_connections = config
100 .max_inbound_connections
101 .checked_add(config.outbound_connections)
102 .unwrap()
103 .max(1);
104
105 let address_book = Buffer::new(
106 cuprate_address_book::init_address_book(config.address_book_config.clone()).await?,
107 max_connections,
108 );
109
110 let (broadcast_svc, outbound_mkr, inbound_mkr) =
113 broadcast::init_broadcast_channels(broadcast::BroadcastConfig::default());
114
115 let (new_connection_tx, new_connection_rx) = mpsc::channel(max_connections);
116 let (make_connection_tx, make_connection_rx) = mpsc::channel(3);
117
118 let peer_set = PeerSet::new(new_connection_rx);
119
120 if config.offline {
121 tracing::warn!("Offline mode enabled, not connecting to or listening for peers.");
122
123 return Ok(NetworkInterface {
124 peer_set: Buffer::new(peer_set, 10).boxed_clone(),
125 broadcast_svc,
126 make_connection_tx,
127 address_book: address_book.boxed_clone(),
128 _background_tasks: Arc::new(JoinSet::new()),
129 });
130 }
131
132 let mut basic_node_data = config.basic_node_data();
133
134 if !Z::CHECK_NODE_ID {
135 basic_node_data.peer_id = 1;
136 }
137
138 let mut outbound_handshaker_builder =
139 cuprate_p2p_core::client::HandshakerBuilder::<Z, T, _, _, _, _>::new(
140 basic_node_data,
141 transport_config.client_config,
142 )
143 .with_address_book(address_book.clone())
144 .with_core_sync_svc(core_sync_svc)
145 .with_protocol_request_handler_maker(protocol_request_handler_maker)
146 .with_broadcast_stream_maker(outbound_mkr)
147 .with_connection_parent_span(Span::current());
148
149 if let Some(ref cb) = peer_sync_callback {
150 outbound_handshaker_builder =
151 outbound_handshaker_builder.with_peer_sync_callback(cb.clone());
152 }
153
154 let inbound_handshaker = outbound_handshaker_builder
155 .clone()
156 .with_broadcast_stream_maker(inbound_mkr)
157 .build();
158
159 let outbound_handshaker = outbound_handshaker_builder.build();
160
161 let outbound_connector = Connector::new(outbound_handshaker);
162 let outbound_connection_maintainer = connection_maintainer::OutboundConnectionKeeper::new(
163 config.clone(),
164 new_connection_tx.clone(),
165 make_connection_rx,
166 address_book.clone(),
167 outbound_connector,
168 peer_sync_callback.clone(),
169 );
170
171 let inbound_listener = if let Some(config) = transport_config.server_config {
172 Some(T::incoming_connection_listener(config).await?)
173 } else {
174 tracing::warn!("No inbound server config provided, not listening for inbound connections.");
175 None
176 };
177
178 let inbound_semaphore = Arc::new(tokio::sync::Semaphore::new(config.max_inbound_connections));
180
181 let mut background_tasks = JoinSet::new();
182
183 background_tasks.spawn(
184 outbound_connection_maintainer
185 .run()
186 .instrument(Span::current()),
187 );
188
189 if inbound_listener.is_some() {
191 background_tasks.spawn(
192 inbound_connection_monitor(
193 Arc::clone(&inbound_semaphore),
194 config.max_inbound_connections,
195 config.p2p_port,
196 )
197 .instrument(tracing::info_span!("inbound_connection_monitor")),
198 );
199 }
200
201 background_tasks.spawn(
202 inbound_server::inbound_server(
203 new_connection_tx,
204 inbound_handshaker,
205 address_book.clone(),
206 config,
207 inbound_listener,
208 inbound_semaphore,
209 peer_sync_callback,
210 )
211 .map(|res| {
212 if let Err(e) = res {
213 tracing::error!("Error in inbound connection listener: {e}");
214 }
215
216 tracing::info!("Inbound connection listener shutdown");
217 })
218 .instrument(Span::current()),
219 );
220
221 Ok(NetworkInterface {
222 peer_set: Buffer::new(peer_set, 10).boxed_clone(),
223 broadcast_svc,
224 make_connection_tx,
225 address_book: address_book.boxed_clone(),
226 _background_tasks: Arc::new(background_tasks),
227 })
228}
229
230#[derive(Clone)]
232pub struct NetworkInterface<N: NetworkZone> {
233 peer_set: BoxCloneService<PeerSetRequest, PeerSetResponse<N>, tower::BoxError>,
235 broadcast_svc: BroadcastSvc<N>,
237 #[expect(dead_code, reason = "will be used eventually")]
239 make_connection_tx: mpsc::Sender<MakeConnectionRequest>,
240 address_book: BoxCloneService<AddressBookRequest<N>, AddressBookResponse<N>, tower::BoxError>,
242 _background_tasks: Arc<JoinSet<()>>,
244}
245
246impl<N: NetworkZone> NetworkInterface<N> {
247 pub fn broadcast_svc(&self) -> BroadcastSvc<N> {
249 self.broadcast_svc.clone()
250 }
251
252 pub fn block_downloader<C>(
256 &self,
257 our_chain_service: C,
258 config: BlockDownloaderConfig,
259 ) -> BlockDownloaderHandle
260 where
261 C: Service<ChainSvcRequest<N>, Response = ChainSvcResponse<N>, Error = tower::BoxError>
262 + Send
263 + 'static,
264 C::Future: Send + 'static,
265 {
266 block_downloader::download_blocks(self.peer_set.clone(), our_chain_service, config)
267 }
268
269 pub fn address_book(
271 &self,
272 ) -> BoxCloneService<AddressBookRequest<N>, AddressBookResponse<N>, tower::BoxError> {
273 self.address_book.clone()
274 }
275
276 pub fn peer_set(
278 &mut self,
279 ) -> &mut BoxCloneService<PeerSetRequest, PeerSetResponse<N>, tower::BoxError> {
280 &mut self.peer_set
281 }
282}