1use dashmap::DashSet;
4use futures::StreamExt;
5use libp2p::{
6 autonat,
7 core::Transport as _,
8 dcutr, identify, identity, kad, mdns, noise, ping, relay,
9 swarm::{NetworkBehaviour, SwarmEvent},
10 Multiaddr, PeerId, Swarm,
11};
12use parking_lot::RwLock;
13use std::collections::HashMap;
14use std::fs;
15use std::path::{Path, PathBuf};
16use std::sync::Arc;
17use std::time::Duration;
18use tokio::sync::{mpsc, oneshot, Mutex};
19use tracing::{debug, info, warn};
20
21type IpfrsResult<T> = ipfrs_core::error::Result<T>;
23
24type ProviderWaiters = Arc<Mutex<HashMap<String, Vec<oneshot::Sender<Vec<PeerId>>>>>>;
26
27pub type InferenceWaiters =
33 Arc<Mutex<HashMap<String, Vec<oneshot::Sender<ipfrs_tensorlogic::InferenceResponse>>>>>;
34
35#[derive(Debug, Clone)]
37pub struct KademliaConfig {
38 pub query_timeout_secs: u64,
40 pub replication_factor: usize,
42 pub alpha: usize,
44 pub kbucket_size: usize,
46}
47
48impl Default for KademliaConfig {
49 fn default() -> Self {
50 Self {
51 query_timeout_secs: 60,
53 replication_factor: 20,
55 alpha: 3,
57 kbucket_size: 20,
59 }
60 }
61}
62
63#[derive(Debug, Clone)]
65pub struct NetworkConfig {
66 pub listen_addrs: Vec<String>,
68 pub bootstrap_peers: Vec<String>,
70 pub enable_quic: bool,
72 pub data_dir: PathBuf,
74 pub enable_mdns: bool,
76 pub enable_nat_traversal: bool,
78 pub relay_servers: Vec<String>,
80 pub kademlia: KademliaConfig,
82 pub max_connections: Option<usize>,
84 pub max_inbound_connections: Option<usize>,
86 pub max_outbound_connections: Option<usize>,
88 pub connection_buffer_size: usize,
90 pub low_memory_mode: bool,
92 pub dcutr_enabled: bool,
99 pub relay_v2_enabled: bool,
105 pub hole_punch_timeout: Duration,
107}
108
109impl Default for NetworkConfig {
110 fn default() -> Self {
111 Self {
112 listen_addrs: vec![
113 "/ip4/0.0.0.0/udp/0/quic-v1".to_string(),
114 "/ip6/::/udp/0/quic-v1".to_string(),
115 ],
116 bootstrap_peers: vec![],
117 enable_quic: true,
118 enable_mdns: false,
119 enable_nat_traversal: true,
120 relay_servers: vec![],
121 data_dir: PathBuf::from(".ipfrs"),
122 kademlia: KademliaConfig::default(),
123 max_connections: None,
124 max_inbound_connections: None,
125 max_outbound_connections: None,
126 connection_buffer_size: 64 * 1024, low_memory_mode: false,
128 dcutr_enabled: true,
130 relay_v2_enabled: true,
131 hole_punch_timeout: Duration::from_secs(30),
132 }
133 }
134}
135
136impl NetworkConfig {
137 pub fn low_memory() -> Self {
148 Self {
149 listen_addrs: vec!["/ip4/0.0.0.0/udp/0/quic-v1".to_string()],
150 bootstrap_peers: vec![],
151 enable_quic: true,
152 enable_mdns: false, enable_nat_traversal: false, relay_servers: vec![],
155 data_dir: PathBuf::from(".ipfrs"),
156 kademlia: KademliaConfig {
157 query_timeout_secs: 30, replication_factor: 10, alpha: 2, kbucket_size: 10, },
162 max_connections: Some(16), max_inbound_connections: Some(8),
164 max_outbound_connections: Some(8),
165 connection_buffer_size: 8 * 1024, low_memory_mode: true,
167 dcutr_enabled: false,
169 relay_v2_enabled: false,
170 hole_punch_timeout: Duration::from_secs(30),
171 }
172 }
173
174 pub fn iot() -> Self {
185 Self {
186 listen_addrs: vec!["/ip4/0.0.0.0/udp/0/quic-v1".to_string()],
187 bootstrap_peers: vec![],
188 enable_quic: true,
189 enable_mdns: true, enable_nat_traversal: true,
191 relay_servers: vec![],
192 data_dir: PathBuf::from(".ipfrs"),
193 kademlia: KademliaConfig {
194 query_timeout_secs: 45,
195 replication_factor: 15,
196 alpha: 2,
197 kbucket_size: 15,
198 },
199 max_connections: Some(32),
200 max_inbound_connections: Some(16),
201 max_outbound_connections: Some(16),
202 connection_buffer_size: 16 * 1024, low_memory_mode: false,
204 dcutr_enabled: true,
205 relay_v2_enabled: true,
206 hole_punch_timeout: Duration::from_secs(30),
207 }
208 }
209
210 pub fn mobile() -> Self {
221 Self {
222 listen_addrs: vec!["/ip4/0.0.0.0/udp/0/quic-v1".to_string()],
223 bootstrap_peers: vec![],
224 enable_quic: true,
225 enable_mdns: false, enable_nat_traversal: true,
227 relay_servers: vec![],
228 data_dir: PathBuf::from(".ipfrs"),
229 kademlia: KademliaConfig {
230 query_timeout_secs: 60,
231 replication_factor: 20,
232 alpha: 3,
233 kbucket_size: 20,
234 },
235 dcutr_enabled: true,
236 relay_v2_enabled: true,
237 hole_punch_timeout: Duration::from_secs(30),
238 max_connections: Some(64),
239 max_inbound_connections: Some(32),
240 max_outbound_connections: Some(32),
241 connection_buffer_size: 32 * 1024, low_memory_mode: false,
243 }
244 }
245
246 pub fn high_performance() -> Self {
256 Self {
257 listen_addrs: vec![
258 "/ip4/0.0.0.0/udp/0/quic-v1".to_string(),
259 "/ip6/::/udp/0/quic-v1".to_string(),
260 ],
261 bootstrap_peers: vec![],
262 enable_quic: true,
263 enable_mdns: true,
264 enable_nat_traversal: true,
265 relay_servers: vec![],
266 data_dir: PathBuf::from(".ipfrs"),
267 kademlia: KademliaConfig {
268 query_timeout_secs: 60,
269 replication_factor: 20,
270 alpha: 3,
271 kbucket_size: 20,
272 },
273 max_connections: None, max_inbound_connections: None,
275 max_outbound_connections: None,
276 connection_buffer_size: 128 * 1024, low_memory_mode: false,
278 dcutr_enabled: true,
279 relay_v2_enabled: true,
280 hole_punch_timeout: Duration::from_secs(30),
281 }
282 }
283}
284
285#[derive(NetworkBehaviour)]
287#[behaviour(to_swarm = "IpfrsBehaviourEvent")]
288pub struct IpfrsBehaviour {
289 pub kademlia: kad::Behaviour<kad::store::MemoryStore>,
291 pub identify: identify::Behaviour,
293 pub ping: ping::Behaviour,
295 pub autonat: autonat::Behaviour,
297 pub dcutr: dcutr::Behaviour,
299 pub mdns: mdns::tokio::Behaviour,
301 pub relay_client: relay::client::Behaviour,
303}
304
305#[derive(Debug)]
307pub enum IpfrsBehaviourEvent {
308 Kademlia(kad::Event),
309 Identify(Box<identify::Event>),
310 Ping(ping::Event),
311 Autonat(autonat::Event),
312 Dcutr(dcutr::Event),
313 Mdns(mdns::Event),
314 RelayClient(relay::client::Event),
315}
316
317impl From<kad::Event> for IpfrsBehaviourEvent {
318 fn from(event: kad::Event) -> Self {
319 IpfrsBehaviourEvent::Kademlia(event)
320 }
321}
322
323impl From<identify::Event> for IpfrsBehaviourEvent {
324 fn from(event: identify::Event) -> Self {
325 IpfrsBehaviourEvent::Identify(Box::new(event))
326 }
327}
328
329impl From<ping::Event> for IpfrsBehaviourEvent {
330 fn from(event: ping::Event) -> Self {
331 IpfrsBehaviourEvent::Ping(event)
332 }
333}
334
335impl From<autonat::Event> for IpfrsBehaviourEvent {
336 fn from(event: autonat::Event) -> Self {
337 IpfrsBehaviourEvent::Autonat(event)
338 }
339}
340
341impl From<dcutr::Event> for IpfrsBehaviourEvent {
342 fn from(event: dcutr::Event) -> Self {
343 IpfrsBehaviourEvent::Dcutr(event)
344 }
345}
346
347impl From<mdns::Event> for IpfrsBehaviourEvent {
348 fn from(event: mdns::Event) -> Self {
349 IpfrsBehaviourEvent::Mdns(event)
350 }
351}
352
353impl From<relay::client::Event> for IpfrsBehaviourEvent {
354 fn from(event: relay::client::Event) -> Self {
355 IpfrsBehaviourEvent::RelayClient(event)
356 }
357}
358
359enum SwarmCommand {
365 Dial(Multiaddr),
367 Disconnect(PeerId),
369 Provide(cid::Cid),
371 GetProviders(cid::Cid),
373 Bootstrap,
375 AddPeerAddress(PeerId, Multiaddr),
377}
378
379#[derive(Debug, Clone)]
384pub struct RelayConfig {
385 pub relay_v2_enabled: bool,
387 pub max_reservations: usize,
389 pub reservation_duration_secs: u64,
391}
392
393impl Default for RelayConfig {
394 fn default() -> Self {
395 Self {
396 relay_v2_enabled: true,
397 max_reservations: 4,
398 reservation_duration_secs: 3600,
399 }
400 }
401}
402
403pub struct NetworkNode {
405 config: NetworkConfig,
406 peer_id: PeerId,
407 swarm: Option<Swarm<IpfrsBehaviour>>,
408 shutdown_tx: Option<mpsc::Sender<()>>,
409 swarm_cmd_tx: Option<mpsc::Sender<SwarmCommand>>,
411 event_tx: mpsc::Sender<NetworkEvent>,
412 event_rx: Option<mpsc::Receiver<NetworkEvent>>,
413 external_addrs: Arc<parking_lot::RwLock<Vec<Multiaddr>>>,
415 connected_peers: Arc<DashSet<PeerId>>,
417 bandwidth_stats: Arc<parking_lot::RwLock<BandwidthStats>>,
419 provider_waiters: ProviderWaiters,
421 nat_metrics: Arc<parking_lot::RwLock<NatTraversalMetrics>>,
423 pub gossipsub: Arc<crate::gossipsub::GossipSubManager>,
429 pub inference_waiters: InferenceWaiters,
431 pub active_relay_reservations: Arc<parking_lot::RwLock<HashMap<PeerId, std::time::Instant>>>,
436 pub relay_config: RelayConfig,
438}
439
440#[derive(Debug, Clone, Default)]
442struct BandwidthStats {
443 bytes_sent: u64,
444 bytes_received: u64,
445}
446
447#[derive(Debug, Clone)]
449pub enum NetworkEvent {
450 PeerConnected {
452 peer_id: PeerId,
453 endpoint: ConnectionEndpoint,
454 established_in: std::time::Duration,
455 },
456 PeerDisconnected {
458 peer_id: PeerId,
459 cause: Option<String>,
460 },
461 ContentFound { cid: String, providers: Vec<PeerId> },
463 PeerDiscovered {
465 peer_id: PeerId,
466 addrs: Vec<Multiaddr>,
467 },
468 ListeningOn { address: Multiaddr },
470 ConnectionError {
472 peer_id: Option<PeerId>,
473 error: String,
474 },
475 DhtBootstrapCompleted,
477 NatStatusChanged {
479 old_status: String,
480 new_status: String,
481 },
482}
483
484#[derive(Debug, Clone)]
486pub enum ConnectionEndpoint {
487 Dialer { address: Multiaddr },
489 Listener {
491 local_addr: Multiaddr,
492 send_back_addr: Multiaddr,
493 },
494}
495
496const KEYPAIR_FILENAME: &str = "identity.key";
498
499impl NetworkNode {
500 pub fn new(config: NetworkConfig) -> IpfrsResult<Self> {
502 info!("Creating network node with libp2p");
503
504 let keypair = Self::load_or_generate_keypair(&config.data_dir)?;
506 let peer_id = keypair.public().to_peer_id();
507
508 info!("Local peer ID: {}", peer_id);
509
510 let (event_tx, event_rx) = mpsc::channel(1024);
512
513 let swarm = Self::build_swarm(keypair, &config)?;
515
516 let gossipsub = {
518 use crate::gossipsub::{GossipSubConfig, GossipSubManager};
519 let mgr = GossipSubManager::new(GossipSubConfig::default());
520 let _ = mgr.subscribe_inference_topics();
522 Arc::new(mgr)
523 };
524
525 Ok(Self {
526 config,
527 peer_id,
528 swarm: Some(swarm),
529 shutdown_tx: None,
530 swarm_cmd_tx: None,
531 event_tx,
532 event_rx: Some(event_rx),
533 external_addrs: Arc::new(RwLock::new(Vec::new())),
534 connected_peers: Arc::new(DashSet::new()),
535 bandwidth_stats: Arc::new(RwLock::new(BandwidthStats::default())),
536 provider_waiters: Arc::new(Mutex::new(HashMap::new())),
537 nat_metrics: Arc::new(RwLock::new(NatTraversalMetrics::default())),
538 gossipsub,
539 inference_waiters: Arc::new(Mutex::new(HashMap::new())),
540 active_relay_reservations: Arc::new(RwLock::new(HashMap::new())),
541 relay_config: RelayConfig::default(),
542 })
543 }
544
545 fn load_or_generate_keypair(data_dir: &Path) -> IpfrsResult<identity::Keypair> {
547 let key_path = data_dir.join(KEYPAIR_FILENAME);
548
549 if key_path.exists() {
550 info!("Loading existing identity from {:?}", key_path);
551 Self::load_keypair(&key_path)
552 } else {
553 info!("Generating new identity");
554 let keypair = identity::Keypair::generate_ed25519();
555
556 if !data_dir.exists() {
558 fs::create_dir_all(data_dir).map_err(ipfrs_core::error::Error::Io)?;
559 }
560
561 Self::save_keypair(&keypair, &key_path)?;
563 info!("Saved new identity to {:?}", key_path);
564
565 Ok(keypair)
566 }
567 }
568
569 fn load_keypair(path: &Path) -> IpfrsResult<identity::Keypair> {
571 let bytes = fs::read(path).map_err(ipfrs_core::error::Error::Io)?;
572
573 identity::Keypair::from_protobuf_encoding(&bytes).map_err(|e| {
574 ipfrs_core::error::Error::Network(format!("Failed to decode keypair: {}", e))
575 })
576 }
577
578 fn save_keypair(keypair: &identity::Keypair, path: &Path) -> IpfrsResult<()> {
580 let bytes = keypair.to_protobuf_encoding().map_err(|e| {
581 ipfrs_core::error::Error::Network(format!("Failed to encode keypair: {}", e))
582 })?;
583
584 fs::write(path, bytes).map_err(ipfrs_core::error::Error::Io)?;
585
586 #[cfg(unix)]
588 {
589 use std::os::unix::fs::PermissionsExt;
590 let permissions = fs::Permissions::from_mode(0o600);
591 fs::set_permissions(path, permissions).map_err(ipfrs_core::error::Error::Io)?;
592 }
593
594 Ok(())
595 }
596
597 #[allow(clippy::too_many_lines)]
599 fn build_swarm(
600 keypair: identity::Keypair,
601 config: &NetworkConfig,
602 ) -> IpfrsResult<Swarm<IpfrsBehaviour>> {
603 let peer_id = keypair.public().to_peer_id();
604
605 let (relay_transport, relay_client) = relay::client::new(peer_id);
611
612 let relay_transport = relay_transport
614 .upgrade(libp2p::core::upgrade::Version::V1)
615 .authenticate(noise::Config::new(&keypair).map_err(std::io::Error::other)?)
616 .multiplex(libp2p::yamux::Config::default())
617 .map(|(peer_id, muxer), _| (peer_id, libp2p::core::muxing::StreamMuxerBox::new(muxer)));
618
619 let tcp_transport = libp2p::tcp::tokio::Transport::default()
621 .upgrade(libp2p::core::upgrade::Version::V1)
622 .authenticate(noise::Config::new(&keypair).map_err(std::io::Error::other)?)
623 .multiplex(libp2p::yamux::Config::default())
624 .map(|(peer_id, muxer), _| (peer_id, libp2p::core::muxing::StreamMuxerBox::new(muxer)));
625
626 let quic_transport = libp2p::quic::tokio::Transport::new(libp2p::quic::Config::new(
628 &keypair,
629 ))
630 .map(|(peer_id, muxer), _| (peer_id, libp2p::core::muxing::StreamMuxerBox::new(muxer)));
631
632 let transport = if config.enable_quic {
637 relay_transport
638 .or_transport(quic_transport)
639 .map(|either, _| either.into_inner())
640 .or_transport(tcp_transport)
641 .map(|either, _| either.into_inner())
642 .boxed()
643 } else {
644 relay_transport
645 .or_transport(tcp_transport)
646 .map(|either, _| either.into_inner())
647 .boxed()
648 };
649
650 let store = kad::store::MemoryStore::new(peer_id);
652 let mut kad_config = kad::Config::default();
653
654 kad_config.set_query_timeout(Duration::from_secs(config.kademlia.query_timeout_secs));
656 kad_config.set_replication_factor(
657 std::num::NonZeroUsize::new(config.kademlia.replication_factor)
658 .expect("Replication factor must be > 0"),
659 );
660 kad_config.set_parallelism(
661 std::num::NonZeroUsize::new(config.kademlia.alpha).expect("Alpha must be > 0"),
662 );
663 kad_config.set_kbucket_inserts(kad::BucketInserts::OnConnected);
664
665 let kademlia = kad::Behaviour::with_config(peer_id, store, kad_config);
669
670 let identify = identify::Behaviour::new(
672 identify::Config::new("/ipfrs/1.0.0".to_string(), keypair.public())
673 .with_agent_version(format!("ipfrs/{}", env!("CARGO_PKG_VERSION"))),
674 );
675
676 let ping = ping::Behaviour::new(ping::Config::new().with_interval(Duration::from_secs(15)));
678
679 let autonat = autonat::Behaviour::new(
681 peer_id,
682 autonat::Config {
683 only_global_ips: false,
684 ..Default::default()
685 },
686 );
687
688 let dcutr = dcutr::Behaviour::new(peer_id);
690
691 let mdns = if config.enable_mdns {
693 mdns::tokio::Behaviour::new(mdns::Config::default(), peer_id)
694 .map_err(|e| ipfrs_core::error::Error::Network(e.to_string()))?
695 } else {
696 mdns::tokio::Behaviour::new(
698 mdns::Config {
699 ttl: Duration::from_secs(1),
700 query_interval: Duration::from_secs(3600), enable_ipv6: false,
702 },
703 peer_id,
704 )
705 .map_err(|e| ipfrs_core::error::Error::Network(e.to_string()))?
706 };
707
708 let behaviour = IpfrsBehaviour {
710 kademlia,
711 identify,
712 ping,
713 autonat,
714 dcutr,
715 mdns,
716 relay_client,
717 };
718
719 let mut swarm_config = libp2p::swarm::Config::with_executor(|fut| {
721 tokio::spawn(fut);
722 });
723 swarm_config = swarm_config.with_idle_connection_timeout(Duration::from_secs(60));
724
725 let swarm = Swarm::new(transport, behaviour, peer_id, swarm_config);
726
727 Ok(swarm)
728 }
729
730 pub async fn start(&mut self) -> IpfrsResult<()> {
732 info!("🚀 IPFRS Network Node Starting");
733 info!(" Peer ID: {}", self.peer_id);
734 info!(" QUIC enabled: {}", self.config.enable_quic);
735
736 let mut swarm = self.swarm.take().ok_or_else(|| {
737 ipfrs_core::error::Error::Network("Swarm already started".to_string())
738 })?;
739
740 for addr_str in &self.config.listen_addrs {
742 let addr: Multiaddr = addr_str.parse().map_err(|e| {
743 ipfrs_core::error::Error::Network(format!("Invalid multiaddr: {}", e))
744 })?;
745
746 swarm
747 .listen_on(addr.clone())
748 .map_err(|e| ipfrs_core::error::Error::Network(e.to_string()))?;
749
750 info!(" Listening on: {}", addr);
751 }
752
753 for peer_str in &self.config.bootstrap_peers {
755 match peer_str.parse::<Multiaddr>() {
756 Ok(addr) => {
757 if let Err(e) = swarm.dial(addr.clone()) {
758 warn!("Failed to dial bootstrap peer {}: {}", addr, e);
759 } else {
760 info!(" Dialing bootstrap peer: {}", addr);
761 }
762 }
763 Err(e) => {
764 warn!("Invalid bootstrap peer address {}: {}", peer_str, e);
765 }
766 }
767 }
768
769 swarm
771 .behaviour_mut()
772 .kademlia
773 .set_mode(Some(kad::Mode::Server));
774
775 if let Err(e) = swarm.behaviour_mut().kademlia.bootstrap() {
777 warn!("DHT bootstrap failed: {}", e);
778 }
779
780 let (shutdown_tx, mut shutdown_rx) = mpsc::channel::<()>(1);
782 self.shutdown_tx = Some(shutdown_tx);
783
784 let (swarm_cmd_tx, mut swarm_cmd_rx) = mpsc::channel::<SwarmCommand>(256);
787 self.swarm_cmd_tx = Some(swarm_cmd_tx);
788
789 let event_tx = self.event_tx.clone();
790 let external_addrs = Arc::clone(&self.external_addrs);
791 let connected_peers = Arc::clone(&self.connected_peers);
792 let provider_waiters = Arc::clone(&self.provider_waiters);
793 let nat_metrics = Arc::clone(&self.nat_metrics);
794
795 info!("✅ Network node ready");
796 info!(
797 " Transport: {}",
798 if self.config.enable_quic {
799 "QUIC"
800 } else {
801 "TCP"
802 }
803 );
804 info!(" DHT mode: Server");
805
806 tokio::spawn(async move {
808 loop {
809 tokio::select! {
810 event = swarm.select_next_some() => {
811 Self::handle_swarm_event(event, &event_tx, swarm.behaviour_mut(), &external_addrs, &connected_peers, &provider_waiters, &nat_metrics).await;
812 }
813 Some(cmd) = swarm_cmd_rx.recv() => {
814 Self::handle_swarm_command(cmd, &mut swarm, &provider_waiters).await;
815 }
816 _ = shutdown_rx.recv() => {
817 info!("Shutting down network node");
818 break;
819 }
820 }
821 }
822 });
823
824 Ok(())
825 }
826
827 async fn handle_swarm_event(
829 event: SwarmEvent<IpfrsBehaviourEvent>,
830 event_tx: &mpsc::Sender<NetworkEvent>,
831 _behaviour: &mut IpfrsBehaviour,
832 external_addrs: &Arc<RwLock<Vec<Multiaddr>>>,
833 connected_peers: &Arc<DashSet<PeerId>>,
834 provider_waiters: &ProviderWaiters,
835 nat_metrics: &Arc<RwLock<NatTraversalMetrics>>,
836 ) {
837 match event {
838 SwarmEvent::NewListenAddr { address, .. } => {
839 info!("Listening on {}", address);
840 let _ = event_tx
841 .send(NetworkEvent::ListeningOn {
842 address: address.clone(),
843 })
844 .await;
845 }
846 SwarmEvent::Behaviour(IpfrsBehaviourEvent::Identify(ev)) => {
847 if let identify::Event::Received { peer_id, info, .. } = *ev {
848 debug!("Identified peer {}: {:?}", peer_id, info);
849 let _ = event_tx
850 .send(NetworkEvent::PeerDiscovered {
851 peer_id,
852 addrs: info.listen_addrs,
853 })
854 .await;
855 }
856 }
857 SwarmEvent::Behaviour(IpfrsBehaviourEvent::Kademlia(
858 kad::Event::OutboundQueryProgressed { result, .. },
859 )) => match result {
860 kad::QueryResult::GetProviders(Ok(kad::GetProvidersOk::FoundProviders {
861 key,
862 providers,
863 })) => {
864 let cid = String::from_utf8_lossy(key.as_ref()).to_string();
865 let provider_list: Vec<PeerId> = providers.into_iter().collect();
866 debug!("Found {} providers for {}", provider_list.len(), cid);
867
868 {
870 let mut waiters = provider_waiters.lock().await;
871 if let Some(senders) = waiters.remove(&cid) {
872 for tx in senders {
873 let _ = tx.send(provider_list.clone());
875 }
876 }
877 }
878
879 let _ = event_tx
880 .send(NetworkEvent::ContentFound {
881 cid,
882 providers: provider_list,
883 })
884 .await;
885 }
886 kad::QueryResult::GetProviders(Err(e)) => {
887 debug!("GetProviders query failed: {:?}", e);
888 }
889 kad::QueryResult::Bootstrap(Ok(_)) => {
890 info!("DHT bootstrap completed");
891 let _ = event_tx.send(NetworkEvent::DhtBootstrapCompleted).await;
892 }
893 kad::QueryResult::Bootstrap(Err(e)) => {
894 warn!("DHT bootstrap failed: {:?}", e);
895 }
896 _ => {}
897 },
898 SwarmEvent::ConnectionEstablished {
899 peer_id,
900 endpoint,
901 established_in,
902 ..
903 } => {
904 info!("Connected to peer: {} in {:?}", peer_id, established_in);
905
906 connected_peers.insert(peer_id);
908
909 let conn_endpoint = if endpoint.is_dialer() {
910 ConnectionEndpoint::Dialer {
911 address: endpoint.get_remote_address().clone(),
912 }
913 } else {
914 ConnectionEndpoint::Listener {
915 local_addr: endpoint.get_remote_address().clone(),
916 send_back_addr: endpoint.get_remote_address().clone(),
917 }
918 };
919
920 let _ = event_tx
921 .send(NetworkEvent::PeerConnected {
922 peer_id,
923 endpoint: conn_endpoint,
924 established_in,
925 })
926 .await;
927 }
928 SwarmEvent::ConnectionClosed {
929 peer_id,
930 cause,
931 num_established,
932 ..
933 } => {
934 info!("Disconnected from peer {}: {:?}", peer_id, cause);
935
936 if num_established == 0 {
938 connected_peers.remove(&peer_id);
939 }
940
941 let _ = event_tx
942 .send(NetworkEvent::PeerDisconnected {
943 peer_id,
944 cause: cause.map(|c| format!("{:?}", c)),
945 })
946 .await;
947 }
948 SwarmEvent::IncomingConnection { .. } => {
949 debug!("Incoming connection");
950 }
951 SwarmEvent::IncomingConnectionError { error, .. } => {
952 debug!("Incoming connection error: {}", error);
953 let _ = event_tx
954 .send(NetworkEvent::ConnectionError {
955 peer_id: None,
956 error: error.to_string(),
957 })
958 .await;
959 }
960 SwarmEvent::OutgoingConnectionError { peer_id, error, .. } => {
961 warn!("Outgoing connection error to {:?}: {}", peer_id, error);
962 let _ = event_tx
963 .send(NetworkEvent::ConnectionError {
964 peer_id,
965 error: error.to_string(),
966 })
967 .await;
968 }
969 SwarmEvent::Behaviour(IpfrsBehaviourEvent::Autonat(autonat_event)) => {
970 match autonat_event {
971 autonat::Event::InboundProbe(_) => {
972 debug!("AutoNAT inbound probe");
973 }
974 autonat::Event::OutboundProbe(_) => {
975 debug!("AutoNAT outbound probe");
976 }
977 autonat::Event::StatusChanged { old, new } => {
978 info!("AutoNAT status changed from {:?} to {:?}", old, new);
979
980 let old_status = format!("{:?}", old);
981 let new_status = format!("{:?}", new);
982
983 let _ = event_tx
984 .send(NetworkEvent::NatStatusChanged {
985 old_status,
986 new_status,
987 })
988 .await;
989
990 match new {
991 autonat::NatStatus::Public(addr) => {
992 info!("Public address confirmed: {}", addr);
993 let mut addrs = external_addrs.write();
995 if !addrs.contains(&addr) {
996 addrs.push(addr);
997 }
998 }
999 autonat::NatStatus::Private => {
1000 info!("Node is behind NAT");
1001 external_addrs.write().clear();
1003 }
1004 autonat::NatStatus::Unknown => {
1005 debug!("NAT status unknown");
1006 }
1007 }
1008 }
1009 }
1010 }
1011 SwarmEvent::Behaviour(IpfrsBehaviourEvent::Dcutr(dcutr_event)) => {
1012 debug!("DCUtR event: {:?}", dcutr_event);
1013 match dcutr_event {
1014 dcutr::Event { result: Ok(_), .. } => {
1015 let mut m = nat_metrics.write();
1016 m.hole_punch_attempts = m.hole_punch_attempts.saturating_add(1);
1017 m.hole_punch_successes = m.hole_punch_successes.saturating_add(1);
1018 info!("DCUtR hole-punch succeeded");
1019 }
1020 dcutr::Event {
1021 result: Err(ref e), ..
1022 } => {
1023 let mut m = nat_metrics.write();
1024 m.hole_punch_attempts = m.hole_punch_attempts.saturating_add(1);
1025 m.hole_punch_failures = m.hole_punch_failures.saturating_add(1);
1026 warn!("DCUtR hole-punch failed: {}", e);
1027 }
1028 }
1029 }
1030 SwarmEvent::Behaviour(IpfrsBehaviourEvent::Mdns(mdns_event)) => match mdns_event {
1031 mdns::Event::Discovered(peers) => {
1032 for (peer_id, addr) in peers {
1033 info!("mDNS discovered peer {} at {}", peer_id, addr);
1034 let _ = event_tx
1035 .send(NetworkEvent::PeerDiscovered {
1036 peer_id,
1037 addrs: vec![addr],
1038 })
1039 .await;
1040 }
1041 }
1042 mdns::Event::Expired(peers) => {
1043 for (peer_id, addr) in peers {
1044 debug!("mDNS peer expired: {} at {}", peer_id, addr);
1045 }
1046 }
1047 },
1048 SwarmEvent::Behaviour(IpfrsBehaviourEvent::RelayClient(relay_event)) => {
1049 debug!("Relay client event: {:?}", relay_event);
1050 match &relay_event {
1051 relay::client::Event::ReservationReqAccepted { .. } => {
1052 let mut m = nat_metrics.write();
1053 m.relay_connections = m.relay_connections.saturating_add(1);
1054 info!("Relay reservation accepted");
1055 }
1056 relay::client::Event::OutboundCircuitEstablished { .. } => {
1057 let mut m = nat_metrics.write();
1058 m.relay_connections = m.relay_connections.saturating_add(1);
1059 debug!("Outbound relay circuit established");
1060 }
1061 _ => {}
1062 }
1063 }
1064 SwarmEvent::Behaviour(IpfrsBehaviourEvent::Ping(ping_event)) => {
1065 if let Ok(rtt) = ping_event.result {
1066 debug!("Ping to {:?}: RTT = {:?}", ping_event.peer, rtt);
1067 }
1068 }
1069 _ => {}
1070 }
1071 }
1072
1073 async fn handle_swarm_command(
1078 cmd: SwarmCommand,
1079 swarm: &mut Swarm<IpfrsBehaviour>,
1080 provider_waiters: &ProviderWaiters,
1081 ) {
1082 match cmd {
1083 SwarmCommand::Dial(addr) => match swarm.dial(addr.clone()) {
1084 Ok(()) => info!("Dialing peer: {}", addr),
1085 Err(e) => warn!("Dial error for {}: {}", addr, e),
1086 },
1087 SwarmCommand::Disconnect(peer_id) => {
1088 let _ = swarm.disconnect_peer_id(peer_id);
1089 info!("Disconnecting from peer: {}", peer_id);
1090 }
1091 SwarmCommand::Provide(cid) => {
1092 let key = kad::RecordKey::new(&cid.to_bytes());
1093 match swarm.behaviour_mut().kademlia.start_providing(key) {
1094 Ok(_) => debug!("Announcing content: {}", cid),
1095 Err(e) => warn!("Failed to announce {}: {}", cid, e),
1096 }
1097 }
1098 SwarmCommand::GetProviders(cid) => {
1099 let cid_str = String::from_utf8_lossy(&cid.to_bytes()).to_string();
1100 let key = kad::RecordKey::new(&cid.to_bytes());
1101 swarm.behaviour_mut().kademlia.get_providers(key);
1102 debug!("Querying DHT providers for: {}", cid_str);
1103 }
1104 SwarmCommand::Bootstrap => match swarm.behaviour_mut().kademlia.bootstrap() {
1105 Ok(_) => info!("DHT bootstrap initiated"),
1106 Err(e) => warn!("DHT bootstrap failed: {}", e),
1107 },
1108 SwarmCommand::AddPeerAddress(peer_id, addr) => {
1109 swarm
1110 .behaviour_mut()
1111 .kademlia
1112 .add_address(&peer_id, addr.clone());
1113 debug!("Added address {} for peer {}", addr, peer_id);
1114 if !swarm.is_connected(&peer_id) {
1117 if let Err(e) = swarm.dial(addr.clone()) {
1118 debug!("Auto-dial for routing table peer {}: {}", peer_id, e);
1119 }
1120 }
1121 }
1122 }
1123 let _ = provider_waiters;
1126 }
1127
1128 pub async fn stop(&mut self) -> IpfrsResult<()> {
1130 if let Some(tx) = self.shutdown_tx.take() {
1131 let _ = tx.send(()).await;
1132 }
1133 self.swarm_cmd_tx = None;
1134 Ok(())
1135 }
1136
1137 pub fn peer_id(&self) -> PeerId {
1139 self.peer_id
1140 }
1141
1142 pub fn listeners(&self) -> Vec<String> {
1144 self.config.listen_addrs.clone()
1145 }
1146
1147 pub fn connected_peers(&self) -> Vec<PeerId> {
1149 self.connected_peers
1150 .iter()
1151 .map(|entry| *entry.key())
1152 .collect()
1153 }
1154
1155 fn send_swarm_cmd(&self, cmd: SwarmCommand) -> IpfrsResult<()> {
1160 match &self.swarm_cmd_tx {
1161 Some(tx) => tx.try_send(cmd).map_err(|e| {
1162 ipfrs_core::error::Error::Network(format!("Swarm command channel error: {}", e))
1163 }),
1164 None => {
1165 Ok(())
1167 }
1168 }
1169 }
1170
1171 pub async fn connect(&mut self, addr: Multiaddr) -> IpfrsResult<()> {
1173 if let Some(swarm) = &mut self.swarm {
1174 swarm
1176 .dial(addr.clone())
1177 .map_err(|e| ipfrs_core::error::Error::Network(e.to_string()))?;
1178 info!("Dialing peer: {}", addr);
1179 } else {
1180 self.send_swarm_cmd(SwarmCommand::Dial(addr))?;
1182 }
1183 Ok(())
1184 }
1185
1186 pub async fn disconnect(&mut self, peer_id: PeerId) -> IpfrsResult<()> {
1188 if let Some(swarm) = &mut self.swarm {
1189 let _ = swarm.disconnect_peer_id(peer_id);
1190 info!("Disconnecting from peer: {}", peer_id);
1191 } else {
1192 self.send_swarm_cmd(SwarmCommand::Disconnect(peer_id))?;
1193 }
1194 Ok(())
1195 }
1196
1197 pub async fn provide(&mut self, cid: &cid::Cid) -> IpfrsResult<()> {
1199 if let Some(swarm) = &mut self.swarm {
1200 let key = kad::RecordKey::new(&cid.to_bytes());
1201 swarm
1202 .behaviour_mut()
1203 .kademlia
1204 .start_providing(key)
1205 .map_err(|e| ipfrs_core::error::Error::Network(e.to_string()))?;
1206 debug!("Announcing content: {}", cid);
1207 } else {
1208 self.send_swarm_cmd(SwarmCommand::Provide(*cid))?;
1209 }
1210 Ok(())
1211 }
1212
1213 pub async fn find_providers(&mut self, cid: &cid::Cid) -> IpfrsResult<()> {
1215 if let Some(swarm) = &mut self.swarm {
1216 let key = kad::RecordKey::new(&cid.to_bytes());
1217 swarm.behaviour_mut().kademlia.get_providers(key);
1218 debug!("Searching for providers of: {}", cid);
1219 } else {
1220 self.send_swarm_cmd(SwarmCommand::GetProviders(*cid))?;
1221 }
1222 Ok(())
1223 }
1224
1225 pub async fn find_providers_await(
1230 &mut self,
1231 cid: &cid::Cid,
1232 timeout: Duration,
1233 ) -> IpfrsResult<Vec<PeerId>> {
1234 let cid_str = String::from_utf8_lossy(&cid.to_bytes()).to_string();
1235
1236 let (tx, rx) = oneshot::channel::<Vec<PeerId>>();
1238 {
1239 let mut waiters = self.provider_waiters.lock().await;
1240 waiters.entry(cid_str.clone()).or_default().push(tx);
1241 }
1242
1243 if let Some(swarm) = &mut self.swarm {
1245 let key = kad::RecordKey::new(&cid.to_bytes());
1246 swarm.behaviour_mut().kademlia.get_providers(key);
1247 debug!(
1248 "Querying DHT providers for: {} (with timeout {:?})",
1249 cid, timeout
1250 );
1251 } else {
1252 match self.send_swarm_cmd(SwarmCommand::GetProviders(*cid)) {
1253 Ok(()) => {
1254 debug!(
1255 "Querying DHT providers for: {} (with timeout {:?})",
1256 cid, timeout
1257 );
1258 }
1259 Err(_) => {
1260 let mut waiters = self.provider_waiters.lock().await;
1262 if let Some(senders) = waiters.get_mut(&cid_str) {
1263 senders.retain(|_| false);
1264 }
1265 return Ok(Vec::new());
1266 }
1267 }
1268 }
1269
1270 match tokio::time::timeout(timeout, rx).await {
1272 Ok(Ok(providers)) => {
1273 debug!("Received {} providers for {}", providers.len(), cid);
1274 Ok(providers)
1275 }
1276 Ok(Err(_)) => {
1277 debug!("Provider query for {} completed with no results", cid);
1279 Ok(Vec::new())
1280 }
1281 Err(_) => {
1282 debug!("Provider query for {} timed out after {:?}", cid, timeout);
1284 let mut waiters = self.provider_waiters.lock().await;
1285 if let Some(senders) = waiters.get_mut(&cid_str) {
1286 senders.retain(|_| false);
1287 }
1288 Ok(Vec::new())
1289 }
1290 }
1291 }
1292
1293 pub async fn fetch_block_from_peer(
1299 &mut self,
1300 peer: &PeerId,
1301 cid: &cid::Cid,
1302 ) -> IpfrsResult<ipfrs_core::Block> {
1303 if !self.connected_peers.contains(peer) {
1305 return Err(ipfrs_core::error::Error::Network(format!(
1306 "Peer {} is not connected; cannot fetch block {}",
1307 peer, cid
1308 )));
1309 }
1310
1311 Err(ipfrs_core::error::Error::NotFound(format!(
1315 "Block {} not yet retrievable from peer {} (Bitswap exchange pending Task E)",
1316 cid, peer
1317 )))
1318 }
1319
1320 pub async fn find_node(&mut self, peer_id: PeerId) -> IpfrsResult<()> {
1322 if let Some(swarm) = &mut self.swarm {
1323 swarm.behaviour_mut().kademlia.get_closest_peers(peer_id);
1324 debug!("Finding closest peers to: {}", peer_id);
1325 }
1326 Ok(())
1327 }
1328
1329 pub async fn get_closest_local_peers(&mut self) -> IpfrsResult<Vec<PeerId>> {
1331 if let Some(swarm) = &mut self.swarm {
1332 let mut closest_peers = Vec::new();
1333
1334 for bucket in swarm.behaviour_mut().kademlia.kbuckets() {
1336 for entry in bucket.iter() {
1337 closest_peers.push(*entry.node.key.preimage());
1338 }
1339 }
1340
1341 debug!("Found {} peers in routing table", closest_peers.len());
1342 Ok(closest_peers)
1343 } else {
1344 Ok(Vec::new())
1345 }
1346 }
1347
1348 pub async fn bootstrap_dht(&mut self) -> IpfrsResult<()> {
1350 if let Some(swarm) = &mut self.swarm {
1351 swarm
1352 .behaviour_mut()
1353 .kademlia
1354 .bootstrap()
1355 .map_err(|e| ipfrs_core::error::Error::Network(e.to_string()))?;
1356 info!("DHT bootstrap initiated");
1357 } else {
1358 self.send_swarm_cmd(SwarmCommand::Bootstrap)?;
1359 }
1360 Ok(())
1361 }
1362
1363 pub fn add_peer_address(&mut self, peer_id: PeerId, addr: Multiaddr) -> IpfrsResult<()> {
1365 if let Some(swarm) = &mut self.swarm {
1366 swarm
1367 .behaviour_mut()
1368 .kademlia
1369 .add_address(&peer_id, addr.clone());
1370 debug!("Added address {} for peer {}", addr, peer_id);
1371 } else {
1372 self.send_swarm_cmd(SwarmCommand::AddPeerAddress(peer_id, addr))?;
1373 }
1374 Ok(())
1375 }
1376
1377 pub fn get_routing_table_info(&mut self) -> IpfrsResult<RoutingTableInfo> {
1379 if let Some(swarm) = &mut self.swarm {
1380 let mut total_peers = 0;
1381 let mut buckets_info = Vec::new();
1382
1383 for (index, bucket) in swarm.behaviour_mut().kademlia.kbuckets().enumerate() {
1384 let num_entries = bucket.iter().count();
1385 total_peers += num_entries;
1386 buckets_info.push(BucketInfo { index, num_entries });
1387 }
1388
1389 Ok(RoutingTableInfo {
1390 total_peers,
1391 num_buckets: buckets_info.len(),
1392 buckets: buckets_info,
1393 })
1394 } else {
1395 Ok(RoutingTableInfo {
1396 total_peers: 0,
1397 num_buckets: 0,
1398 buckets: Vec::new(),
1399 })
1400 }
1401 }
1402
1403 pub fn stats(&self) -> NetworkStats {
1405 let bandwidth = self.bandwidth_stats.read();
1406 NetworkStats {
1407 peer_id: self.peer_id.to_string(),
1408 listen_addrs: self.config.listen_addrs.clone(),
1409 connected_peers: self.connected_peers.len(),
1410 quic_enabled: self.config.enable_quic,
1411 bytes_received: bandwidth.bytes_received,
1412 bytes_sent: bandwidth.bytes_sent,
1413 bootstrap_peers: self.config.bootstrap_peers.clone(),
1414 }
1415 }
1416
1417 pub fn take_event_receiver(&mut self) -> Option<mpsc::Receiver<NetworkEvent>> {
1419 self.event_rx.take()
1420 }
1421
1422 pub fn get_external_addresses(&self) -> Vec<Multiaddr> {
1424 self.external_addrs.read().clone()
1425 }
1426
1427 pub fn is_publicly_reachable(&self) -> bool {
1429 !self.external_addrs.read().is_empty()
1430 }
1431
1432 pub fn is_connected_to(&self, peer_id: &PeerId) -> bool {
1434 self.connected_peers.contains(peer_id)
1435 }
1436
1437 pub fn get_peer_count(&self) -> usize {
1439 self.connected_peers.len()
1440 }
1441
1442 pub async fn connect_to_peers(&mut self, addrs: Vec<Multiaddr>) -> Vec<IpfrsResult<()>> {
1444 let mut results = Vec::with_capacity(addrs.len());
1445
1446 for addr in addrs {
1447 let result = self.connect(addr).await;
1448 results.push(result);
1449 }
1450
1451 results
1452 }
1453
1454 pub async fn disconnect_all(&mut self) -> IpfrsResult<()> {
1456 let peers: Vec<PeerId> = self.connected_peers().clone();
1457
1458 for peer in peers {
1459 let _ = self.disconnect(peer).await;
1460 }
1461
1462 Ok(())
1463 }
1464
1465 pub fn update_bandwidth(&self, bytes_sent: u64, bytes_received: u64) {
1467 let mut stats = self.bandwidth_stats.write();
1468 stats.bytes_sent += bytes_sent;
1469 stats.bytes_received += bytes_received;
1470 }
1471
1472 pub fn get_bytes_sent(&self) -> u64 {
1474 self.bandwidth_stats.read().bytes_sent
1475 }
1476
1477 pub fn get_bytes_received(&self) -> u64 {
1479 self.bandwidth_stats.read().bytes_received
1480 }
1481
1482 pub fn reset_bandwidth_stats(&self) {
1484 let mut stats = self.bandwidth_stats.write();
1485 stats.bytes_sent = 0;
1486 stats.bytes_received = 0;
1487 }
1488
1489 pub fn get_network_health(&self) -> NetworkHealthSummary {
1491 let peer_count = self.get_peer_count();
1492 let is_public = self.is_publicly_reachable();
1493 let has_external_addrs = !self.external_addrs.read().is_empty();
1494
1495 let status = if peer_count >= 10 && is_public {
1497 NetworkHealthLevel::Healthy
1498 } else if peer_count >= 3 || has_external_addrs {
1499 NetworkHealthLevel::Degraded
1500 } else if peer_count > 0 {
1501 NetworkHealthLevel::Limited
1502 } else {
1503 NetworkHealthLevel::Disconnected
1504 };
1505
1506 NetworkHealthSummary {
1507 status,
1508 connected_peers: peer_count,
1509 is_publicly_reachable: is_public,
1510 external_addresses: self.get_external_addresses().len(),
1511 }
1512 }
1513
1514 pub fn is_healthy(&self) -> bool {
1516 matches!(
1517 self.get_network_health().status,
1518 NetworkHealthLevel::Healthy
1519 )
1520 }
1521
1522 pub fn nat_traversal_metrics(&self) -> NatTraversalMetrics {
1524 self.nat_metrics.read().clone()
1525 }
1526
1527 pub fn publish_inference_request(
1540 &self,
1541 request: &ipfrs_tensorlogic::InferenceRequest,
1542 ) -> IpfrsResult<()> {
1543 let json = serde_json::to_vec(request).map_err(|e| {
1544 ipfrs_core::error::Error::Network(format!("Failed to serialize InferenceRequest: {e}"))
1545 })?;
1546 let peer_id_str = self.peer_id.to_string();
1547 self.gossipsub
1548 .publish_inference_request(&json, &peer_id_str)
1549 .map_err(|e| {
1550 ipfrs_core::error::Error::Network(format!(
1551 "GossipSub publish_inference_request failed: {e}"
1552 ))
1553 })
1554 }
1555
1556 pub async fn register_inference_waiter(
1563 &self,
1564 request_id: String,
1565 ) -> oneshot::Receiver<ipfrs_tensorlogic::InferenceResponse> {
1566 let (tx, rx) = oneshot::channel();
1567 let mut waiters = self.inference_waiters.lock().await;
1568 waiters.entry(request_id).or_default().push(tx);
1569 rx
1570 }
1571
1572 pub async fn deliver_inference_response(&self, response: ipfrs_tensorlogic::InferenceResponse) {
1579 let mut waiters = self.inference_waiters.lock().await;
1580 if let Some(senders) = waiters.remove(&response.request_id) {
1581 for tx in senders {
1582 let _ = tx.send(response.clone());
1584 }
1585 }
1586 }
1587
1588 pub fn publish_inference_response(
1594 &self,
1595 response: &ipfrs_tensorlogic::InferenceResponse,
1596 ) -> IpfrsResult<()> {
1597 let json = serde_json::to_vec(response).map_err(|e| {
1598 ipfrs_core::error::Error::Network(format!("Failed to serialize InferenceResponse: {e}"))
1599 })?;
1600 let peer_id_str = self.peer_id.to_string();
1601 self.gossipsub
1602 .publish_inference_result(&json, &peer_id_str)
1603 .map_err(|e| {
1604 ipfrs_core::error::Error::Network(format!(
1605 "GossipSub publish_inference_result failed: {e}"
1606 ))
1607 })
1608 }
1609}
1610
1611#[derive(Debug, Clone, Default, serde::Serialize)]
1616pub struct NatTraversalMetrics {
1617 pub hole_punch_attempts: u64,
1619 pub hole_punch_successes: u64,
1621 pub hole_punch_failures: u64,
1623 pub relay_connections: u64,
1625}
1626
1627impl NatTraversalMetrics {
1628 pub fn success_rate(&self) -> f32 {
1630 if self.hole_punch_attempts == 0 {
1631 return 0.0;
1632 }
1633 self.hole_punch_successes as f32 / self.hole_punch_attempts as f32
1634 }
1635}
1636
1637#[derive(Debug, Clone, serde::Serialize)]
1639pub struct NetworkStats {
1640 pub peer_id: String,
1641 pub listen_addrs: Vec<String>,
1642 pub connected_peers: usize,
1643 pub quic_enabled: bool,
1644 pub bytes_received: u64,
1646 pub bytes_sent: u64,
1648 pub bootstrap_peers: Vec<String>,
1650}
1651
1652#[derive(Debug, Clone, serde::Serialize)]
1654pub struct BucketInfo {
1655 pub index: usize,
1657 pub num_entries: usize,
1659}
1660
1661#[derive(Debug, Clone, serde::Serialize)]
1663pub struct RoutingTableInfo {
1664 pub total_peers: usize,
1666 pub num_buckets: usize,
1668 pub buckets: Vec<BucketInfo>,
1670}
1671
1672#[derive(Debug, Clone, serde::Serialize)]
1674pub struct NetworkHealthSummary {
1675 pub status: NetworkHealthLevel,
1677 pub connected_peers: usize,
1679 pub is_publicly_reachable: bool,
1681 pub external_addresses: usize,
1683}
1684
1685#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
1687pub enum NetworkHealthLevel {
1688 Healthy,
1690 Degraded,
1692 Limited,
1694 Disconnected,
1696}
1697
1698impl NetworkNode {
1703 pub async fn reserve_relay(&mut self, relay_peer: PeerId) -> IpfrsResult<()> {
1719 if !self.config.relay_v2_enabled || !self.relay_config.relay_v2_enabled {
1720 return Err(ipfrs_core::error::Error::Network(
1721 "Circuit Relay v2 is disabled".to_string(),
1722 ));
1723 }
1724
1725 {
1727 let reservations = self.active_relay_reservations.read();
1728 if reservations.len() >= self.relay_config.max_reservations {
1729 return Err(ipfrs_core::error::Error::Network(format!(
1730 "Maximum relay reservations ({}) already reached",
1731 self.relay_config.max_reservations
1732 )));
1733 }
1734 }
1735
1736 let relay_addr: Multiaddr =
1739 format!("/p2p/{}/p2p-circuit", relay_peer)
1740 .parse()
1741 .map_err(|e| {
1742 ipfrs_core::error::Error::Network(format!(
1743 "Invalid relay address for peer {}: {}",
1744 relay_peer, e
1745 ))
1746 })?;
1747
1748 debug!(
1749 relay_peer = %relay_peer,
1750 addr = %relay_addr,
1751 "Requesting Circuit Relay v2 reservation"
1752 );
1753
1754 if let Some(ref cmd_tx) = self.swarm_cmd_tx {
1758 cmd_tx
1759 .send(SwarmCommand::Dial(relay_addr))
1760 .await
1761 .map_err(|_| {
1762 ipfrs_core::error::Error::Network("Swarm command channel closed".to_string())
1763 })?;
1764 } else {
1765 warn!(
1768 relay_peer = %relay_peer,
1769 "reserve_relay called before node.start(); \
1770 reservation recorded but dial not sent"
1771 );
1772 }
1773
1774 {
1776 let mut reservations = self.active_relay_reservations.write();
1777 reservations.insert(relay_peer, std::time::Instant::now());
1778 }
1779
1780 info!(
1781 relay_peer = %relay_peer,
1782 "Circuit Relay v2 reservation recorded"
1783 );
1784
1785 Ok(())
1786 }
1787
1788 pub fn relay_reservations(&self) -> HashMap<PeerId, std::time::Instant> {
1793 self.active_relay_reservations.read().clone()
1794 }
1795
1796 pub fn remove_relay_reservation(&mut self, relay_peer: &PeerId) {
1798 self.active_relay_reservations.write().remove(relay_peer);
1799 }
1800
1801 pub fn prune_expired_relay_reservations(&mut self, max_age: std::time::Duration) {
1803 let now = std::time::Instant::now();
1804 self.active_relay_reservations
1805 .write()
1806 .retain(|_, instant| now.duration_since(*instant) < max_age);
1807 }
1808}