ant_bootstrap/
lib.rs

1// Copyright 2024 MaidSafe.net limited.
2//
3// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
4// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
5// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
6// KIND, either express or implied. Please review the Licences for the specific language governing
7// permissions and limitations relating to use of the SAFE Network Software.
8
9//! Bootstrap Cache for the Autonomous Network
10//!
11//! This crate provides a decentralized peer discovery and caching system for the Autonomi Network.
12//! It implements a robust peer management system with the following features:
13//!
14//! - Decentralized Design: No dedicated bootstrap nodes required
15//! - Cross-Platform Support: Works on Linux, macOS, and Windows
16//! - Shared Cache: System-wide cache file accessible by both nodes and clients
17//! - Concurrent Access: File locking for safe multi-process access
18//! - Atomic Operations: Safe cache updates using atomic file operations
19//! - Initial Peer Discovery: Fallback web endpoints for new/stale cache scenarios
20
21#[macro_use]
22extern crate tracing;
23
24mod cache_store;
25pub mod config;
26pub mod contacts;
27pub mod error;
28mod initial_peers;
29
30use ant_protocol::version::{get_network_id_str, get_truncate_version_str};
31use libp2p::{multiaddr::Protocol, Multiaddr, PeerId};
32use serde::{Deserialize, Serialize};
33use std::time::SystemTime;
34use thiserror::Error;
35
36pub use cache_store::BootstrapCacheStore;
37pub use config::BootstrapCacheConfig;
38pub use contacts::ContactsFetcher;
39pub use error::{Error, Result};
40pub use initial_peers::{InitialPeersConfig, ANT_PEERS_ENV};
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
43/// Set of addresses for a particular PeerId
44pub struct BootstrapAddresses(pub Vec<BootstrapAddr>);
45
46impl BootstrapAddresses {
47    pub fn insert_addr(&mut self, addr: &BootstrapAddr) {
48        if let Some(bootstrap_addr) = self.get_addr_mut(&addr.addr) {
49            bootstrap_addr.sync(addr);
50        } else {
51            self.0.push(addr.clone());
52        }
53    }
54
55    pub fn get_addr(&self, addr: &Multiaddr) -> Option<&BootstrapAddr> {
56        self.0
57            .iter()
58            .find(|bootstrap_addr| &bootstrap_addr.addr == addr)
59    }
60
61    pub fn get_addr_mut(&mut self, addr: &Multiaddr) -> Option<&mut BootstrapAddr> {
62        self.0
63            .iter_mut()
64            .find(|bootstrap_addr| &bootstrap_addr.addr == addr)
65    }
66
67    pub fn get_least_faulty(&self) -> Option<&BootstrapAddr> {
68        self.0.iter().min_by_key(|addr| addr.failure_rate() as u64)
69    }
70
71    pub fn remove_addr(&mut self, addr: &Multiaddr) {
72        if let Some(idx) = self
73            .0
74            .iter()
75            .position(|bootstrap_addr| &bootstrap_addr.addr == addr)
76        {
77            let bootstrap_addr = self.0.remove(idx);
78            debug!("Removed {bootstrap_addr:?}");
79        }
80    }
81
82    pub fn sync(&mut self, other: &Self) {
83        for other_addr in other.0.iter() {
84            if let Some(bootstrap_addr) = self.get_addr_mut(&other_addr.addr) {
85                bootstrap_addr.sync(other_addr);
86            } else {
87                trace!(
88                    "Addr {:?} from other not found in self, inserting it.",
89                    other_addr.addr
90                );
91                self.insert_addr(other_addr);
92            }
93        }
94    }
95
96    pub fn update_addr_status(&mut self, addr: &Multiaddr, success: bool) {
97        if let Some(bootstrap_addr) = self.get_addr_mut(addr) {
98            bootstrap_addr.update_status(success);
99        } else {
100            debug!("Addr not found in cache to update, skipping: {addr:?}")
101        }
102    }
103}
104
105/// A addr that can be used for bootstrapping into the network
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct BootstrapAddr {
108    /// The multiaddress of the peer
109    pub addr: Multiaddr,
110    /// The number of successful connections to this address
111    pub success_count: u32,
112    /// The number of failed connection attempts to this address
113    pub failure_count: u32,
114    /// The last time this address was successfully contacted
115    pub last_seen: SystemTime,
116}
117
118impl BootstrapAddr {
119    pub fn new(addr: Multiaddr) -> Self {
120        Self {
121            addr,
122            success_count: 0,
123            failure_count: 0,
124            last_seen: SystemTime::now(),
125        }
126    }
127
128    pub fn peer_id(&self) -> Option<PeerId> {
129        multiaddr_get_peer_id(&self.addr)
130    }
131
132    pub fn update_status(&mut self, success: bool) {
133        if success {
134            if let Some(new_value) = self.success_count.checked_add(1) {
135                self.success_count = new_value;
136            } else {
137                self.success_count = 1;
138                self.failure_count = 0;
139            }
140        }
141        self.last_seen = SystemTime::now();
142        if !success {
143            if let Some(new_value) = self.failure_count.checked_add(1) {
144                self.failure_count = new_value;
145            } else {
146                self.failure_count = 1;
147                self.success_count = 0;
148            }
149        }
150    }
151
152    // An addr is considered reliable if it has more successes than failures
153    pub fn is_reliable(&self) -> bool {
154        self.success_count >= self.failure_count
155    }
156
157    /// Add the values from other into self.
158    pub fn sync(&mut self, other: &Self) {
159        trace!("Syncing our state {self:?} with and other: {other:?}.");
160        if self.last_seen == other.last_seen {
161            return;
162        }
163
164        self.success_count = self.success_count.saturating_add(other.success_count);
165        self.failure_count = self.failure_count.saturating_add(other.failure_count);
166
167        // if at max value, reset to 0
168        if self.success_count == u32::MAX {
169            self.success_count = 1;
170            self.failure_count = 0;
171        } else if self.failure_count == u32::MAX {
172            self.failure_count = 1;
173            self.success_count = 0;
174        }
175        self.last_seen = std::cmp::max(self.last_seen, other.last_seen);
176        trace!("Successfully synced BootstrapAddr: {self:?}");
177    }
178
179    fn failure_rate(&self) -> f64 {
180        if self.success_count + self.failure_count == 0 {
181            0.0
182        } else {
183            self.failure_count as f64 / (self.success_count + self.failure_count) as f64
184        }
185    }
186}
187
188/// Craft a proper address to avoid any ill formed addresses
189///
190/// ignore_peer_id is only used for nat-detection contact list
191pub fn craft_valid_multiaddr(addr: &Multiaddr, ignore_peer_id: bool) -> Option<Multiaddr> {
192    let peer_id = addr
193        .iter()
194        .find(|protocol| matches!(protocol, Protocol::P2p(_)));
195
196    let mut output_address = Multiaddr::empty();
197
198    let ip = addr
199        .iter()
200        .find(|protocol| matches!(protocol, Protocol::Ip4(_)))?;
201    output_address.push(ip);
202
203    let udp = addr
204        .iter()
205        .find(|protocol| matches!(protocol, Protocol::Udp(_)));
206    let tcp = addr
207        .iter()
208        .find(|protocol| matches!(protocol, Protocol::Tcp(_)));
209
210    // UDP or TCP
211    if let Some(udp) = udp {
212        output_address.push(udp);
213        if let Some(quic) = addr
214            .iter()
215            .find(|protocol| matches!(protocol, Protocol::QuicV1))
216        {
217            output_address.push(quic);
218        }
219    } else if let Some(tcp) = tcp {
220        output_address.push(tcp);
221
222        if let Some(ws) = addr
223            .iter()
224            .find(|protocol| matches!(protocol, Protocol::Ws(_)))
225        {
226            output_address.push(ws);
227        }
228    } else {
229        return None;
230    }
231
232    if let Some(peer_id) = peer_id {
233        output_address.push(peer_id);
234    } else if !ignore_peer_id {
235        return None;
236    }
237
238    Some(output_address)
239}
240
241/// ignore_peer_id is only used for nat-detection contact list
242pub fn craft_valid_multiaddr_from_str(addr_str: &str, ignore_peer_id: bool) -> Option<Multiaddr> {
243    let Ok(addr) = addr_str.parse::<Multiaddr>() else {
244        warn!("Failed to parse multiaddr from str {addr_str}");
245        return None;
246    };
247    craft_valid_multiaddr(&addr, ignore_peer_id)
248}
249
250pub fn multiaddr_get_peer_id(addr: &Multiaddr) -> Option<PeerId> {
251    match addr.iter().find(|p| matches!(p, Protocol::P2p(_))) {
252        Some(Protocol::P2p(id)) => Some(id),
253        _ => None,
254    }
255}
256
257pub fn get_network_version() -> String {
258    format!("{}_{}", get_network_id_str(), get_truncate_version_str())
259}