ant_core/data/network.rs
1//! Network layer wrapping ant-node's P2P node.
2//!
3//! Provides peer discovery, message sending, and DHT operations
4//! for the client library.
5
6use crate::data::error::{Error, Result};
7use ant_protocol::transport::{
8 CoreNodeConfig, IPDiversityConfig, MultiAddr, NodeMode, P2PNode, PeerId,
9};
10use ant_protocol::MAX_WIRE_MESSAGE_SIZE;
11use std::net::SocketAddr;
12use std::sync::Arc;
13
14/// Network abstraction for the Autonomi client.
15///
16/// Wraps a `P2PNode` providing high-level operations for
17/// peer discovery and message routing.
18pub struct Network {
19 node: Arc<P2PNode>,
20}
21
22impl Network {
23 /// Create a new network connection with the given bootstrap peers.
24 ///
25 /// `allow_loopback` controls the saorsa-transport `local` flag on the
26 /// underlying `CoreNodeConfig`. Set it to `true` only for devnet / local
27 /// testing. Public Autonomi network peers reject the QUIC handshake
28 /// variant produced when `local = true`, so production callers must pass
29 /// `false` (this is what `ant-cli` does by default — see
30 /// `ant-cli/src/main.rs::create_client_node_raw`, which builds a similar
31 /// `CoreNodeConfig` directly, with `ipv6` toggled by the `--ipv4-only`
32 /// flag).
33 ///
34 /// `ipv6` controls whether the node binds a dual-stack IPv6 socket
35 /// (`true`) or an IPv4-only socket (`false`). The default for library
36 /// callers should be `true` to match the CLI default; set it to `false`
37 /// only when running on hosts without a working IPv6 stack, to avoid
38 /// advertising unreachable v6 addresses to the DHT.
39 ///
40 /// # Errors
41 ///
42 /// Returns an error if the P2P node cannot be created or bootstrapping fails.
43 pub async fn new(
44 bootstrap_peers: &[SocketAddr],
45 allow_loopback: bool,
46 ipv6: bool,
47 ) -> Result<Self> {
48 let mut core_config = CoreNodeConfig::builder()
49 .port(0)
50 .ipv6(ipv6)
51 .local(allow_loopback)
52 .mode(NodeMode::Client)
53 .max_message_size(MAX_WIRE_MESSAGE_SIZE)
54 .build()
55 .map_err(|e| Error::Network(format!("Failed to create core config: {e}")))?;
56
57 // Clients never enforce IP-diversity limits: they don't host data and
58 // their routing table exists only to find peers, not to be defended
59 // against Sybil clustering. Strict per-IP / per-subnet caps would
60 // silently drop legitimate testnet peers that share an IP or /24.
61 core_config.diversity_config = Some(IPDiversityConfig::permissive());
62
63 core_config.bootstrap_peers = bootstrap_peers
64 .iter()
65 .map(|addr| MultiAddr::quic(*addr))
66 .collect();
67
68 let node = P2PNode::new(core_config)
69 .await
70 .map_err(|e| Error::Network(format!("Failed to create P2P node: {e}")))?;
71
72 node.start()
73 .await
74 .map_err(|e| Error::Network(format!("Failed to start P2P node: {e}")))?;
75
76 Ok(Self {
77 node: Arc::new(node),
78 })
79 }
80
81 /// Create a network from an existing P2P node.
82 #[must_use]
83 pub fn from_node(node: Arc<P2PNode>) -> Self {
84 Self { node }
85 }
86
87 /// Get a reference to the underlying P2P node.
88 #[must_use]
89 pub fn node(&self) -> &Arc<P2PNode> {
90 &self.node
91 }
92
93 /// Get the local peer ID.
94 #[must_use]
95 pub fn peer_id(&self) -> &PeerId {
96 self.node.peer_id()
97 }
98
99 /// Find the closest peers to a target address.
100 ///
101 /// Returns each peer paired with its known network addresses, enabling
102 /// callers to pass addresses to `send_and_await_chunk_response` for
103 /// faster connection establishment.
104 ///
105 /// # Errors
106 ///
107 /// Returns an error if the DHT lookup fails.
108 pub async fn find_closest_peers(
109 &self,
110 target: &[u8; 32],
111 count: usize,
112 ) -> Result<Vec<(PeerId, Vec<MultiAddr>)>> {
113 let local_peer_id = self.node.peer_id();
114
115 // Request one extra to account for filtering out our own peer ID
116 let closest_nodes = self
117 .node
118 .dht()
119 .find_closest_nodes(target, count + 1)
120 .await
121 .map_err(|e| Error::Network(format!("DHT closest-nodes lookup failed: {e}")))?;
122
123 Ok(closest_nodes
124 .into_iter()
125 .filter(|n| n.peer_id != *local_peer_id)
126 .take(count)
127 .map(|n| {
128 let addrs = n.addresses_by_priority();
129 (n.peer_id, addrs)
130 })
131 .collect())
132 }
133
134 /// Get all currently connected peers.
135 pub async fn connected_peers(&self) -> Vec<PeerId> {
136 self.node.connected_peers().await
137 }
138}