Skip to main content

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}