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// Allow expect usage and enum variant names (comes from thiserror derives)
10#![allow(clippy::expect_used)]
11#![allow(clippy::enum_variant_names)]
12
13//! Bootstrap Cache for the Autonomous Network
14//!
15//! This crate provides a decentralized peer discovery and caching system for the Autonomi Network.
16//! It implements a robust peer management system with the following features:
17//!
18//! - Decentralized Design: No dedicated bootstrap nodes required
19//! - Cross-Platform Support: Works on Linux, macOS, and Windows
20//! - Shared Cache: System-wide cache file accessible by both nodes and clients
21//! - Concurrent Access: File locking for safe multi-process access
22//! - Atomic Operations: Safe cache updates using atomic file operations
23//! - Initial Peer Discovery: Fallback web endpoints for new/stale cache scenarios
24
25#[macro_use]
26extern crate tracing;
27
28pub mod bootstrap;
29pub mod cache_store;
30pub mod config;
31pub mod contacts_fetcher;
32pub mod error;
33
34use ant_protocol::version::{get_network_id_str, get_truncate_version_str};
35use libp2p::{Multiaddr, PeerId, multiaddr::Protocol};
36use thiserror::Error;
37
38pub use bootstrap::Bootstrap;
39pub use cache_store::BootstrapCacheStore;
40pub use config::BootstrapConfig;
41pub use config::InitialPeersConfig;
42pub use contacts_fetcher::ContactsFetcher;
43pub use error::{Error, Result};
44
45/// The name of the environment variable that can be used to pass peers to the node.
46pub const ANT_PEERS_ENV: &str = "ANT_PEERS";
47
48/// Craft a proper address to avoid any ill formed addresses
49///
50/// ignore_peer_id is only used for nat-detection contact list
51pub fn craft_valid_multiaddr(addr: &Multiaddr, ignore_peer_id: bool) -> Option<Multiaddr> {
52    let peer_id = addr
53        .iter()
54        .find(|protocol| matches!(protocol, Protocol::P2p(_)));
55
56    let mut output_address = Multiaddr::empty();
57
58    let ip = addr
59        .iter()
60        .find(|protocol| matches!(protocol, Protocol::Ip4(_)))?;
61    output_address.push(ip);
62
63    let udp = addr
64        .iter()
65        .find(|protocol| matches!(protocol, Protocol::Udp(_)));
66    let tcp = addr
67        .iter()
68        .find(|protocol| matches!(protocol, Protocol::Tcp(_)));
69
70    // UDP or TCP
71    if let Some(udp) = udp {
72        output_address.push(udp);
73        if let Some(quic) = addr
74            .iter()
75            .find(|protocol| matches!(protocol, Protocol::QuicV1))
76        {
77            output_address.push(quic);
78        }
79    } else if let Some(tcp) = tcp {
80        output_address.push(tcp);
81
82        if let Some(ws) = addr
83            .iter()
84            .find(|protocol| matches!(protocol, Protocol::Ws(_)))
85        {
86            output_address.push(ws);
87        }
88    } else {
89        return None;
90    }
91
92    if let Some(peer_id) = peer_id {
93        output_address.push(peer_id);
94    } else if !ignore_peer_id {
95        return None;
96    }
97
98    Some(output_address)
99}
100
101/// ignore_peer_id is only used for nat-detection contact list
102pub fn craft_valid_multiaddr_from_str(addr_str: &str, ignore_peer_id: bool) -> Option<Multiaddr> {
103    let Ok(addr) = addr_str.parse::<Multiaddr>() else {
104        warn!("Failed to parse multiaddr from str {addr_str}");
105        return None;
106    };
107    craft_valid_multiaddr(&addr, ignore_peer_id)
108}
109
110pub fn multiaddr_get_peer_id(addr: &Multiaddr) -> Option<PeerId> {
111    match addr.iter().find(|p| matches!(p, Protocol::P2p(_))) {
112        Some(Protocol::P2p(id)) => Some(id),
113        _ => None,
114    }
115}
116
117pub fn get_network_version() -> String {
118    format!("{}_{}", get_network_id_str(), get_truncate_version_str())
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124    use libp2p::Multiaddr;
125
126    #[test]
127    fn test_transport_protocol_variants() {
128        let variants = [
129            (
130                "/ip4/127.0.0.1/udp/8080/quic-v1/p2p/12D3KooWRBhwfeP2Y4TCx1SM6s9rUoHhR5STiGwxBhgFRcw3UERE",
131                true,
132            ),
133            (
134                "/ip4/127.0.0.1/tcp/8080/ws/p2p/12D3KooWRBhwfeP2Y4TCx1SM6s9rUoHhR5STiGwxBhgFRcw3UERE",
135                true,
136            ),
137            (
138                "/ip4/127.0.0.1/tcp/8080/p2p/12D3KooWRBhwfeP2Y4TCx1SM6s9rUoHhR5STiGwxBhgFRcw3UERE",
139                true,
140            ),
141            ("/ip4/127.0.0.1/tcp/8080", false),
142            (
143                "/ip4/127.0.0.1/p2p/12D3KooWRBhwfeP2Y4TCx1SM6s9rUoHhR5STiGwxBhgFRcw3UERE",
144                false,
145            ),
146            (
147                "/ip4/127.0.0.1/wss/p2p/12D3KooWRBhwfeP2Y4TCx1SM6s9rUoHhR5STiGwxBhgFRcw3UERE",
148                false,
149            ),
150        ];
151
152        for (addr, should_be_valid) in variants {
153            let parsed: Multiaddr = addr.parse().unwrap();
154            let result = craft_valid_multiaddr(&parsed, false);
155
156            if should_be_valid {
157                let crafted = result.unwrap_or_else(|| panic!("Expected valid multiaddr: {addr}"));
158                assert_eq!(crafted.to_string(), parsed.to_string());
159            } else {
160                assert!(result.is_none(), "Expected invalid multiaddr: {addr}");
161            }
162        }
163    }
164
165    #[test]
166    fn test_craft_valid_multiaddr_from_str() {
167        let valid = "/ip4/127.0.0.1/udp/8080/quic-v1/p2p/12D3KooWRBhwfeP2Y4TCx1SM6s9rUoHhR5STiGwxBhgFRcw3UERE";
168        assert!(craft_valid_multiaddr_from_str(valid, false).is_some());
169
170        let invalid = "not a multiaddr";
171        assert!(craft_valid_multiaddr_from_str(invalid, false).is_none());
172
173        let missing_peer = "/ip4/127.0.0.1/tcp/8080";
174        assert!(craft_valid_multiaddr_from_str(missing_peer, false).is_none());
175        assert!(craft_valid_multiaddr_from_str(missing_peer, true).is_some());
176    }
177
178    #[test]
179    fn test_craft_valid_multiaddr_ignore_peer_id() {
180        let addr_without_peer: Multiaddr = "/ip4/127.0.0.1/udp/8080/quic-v1".parse().unwrap();
181        assert!(craft_valid_multiaddr(&addr_without_peer, false).is_none());
182        assert!(craft_valid_multiaddr(&addr_without_peer, true).is_some());
183    }
184
185    #[test]
186    fn test_multiaddr_get_peer_id() {
187        let addr_with_peer: Multiaddr =
188            "/ip4/127.0.0.1/udp/8080/quic-v1/p2p/12D3KooWRBhwfeP2Y4TCx1SM6s9rUoHhR5STiGwxBhgFRcw3UERE"
189                .parse()
190                .unwrap();
191        assert!(multiaddr_get_peer_id(&addr_with_peer).is_some());
192
193        let addr_without_peer: Multiaddr = "/ip4/127.0.0.1/udp/8080/quic-v1".parse().unwrap();
194        assert!(multiaddr_get_peer_id(&addr_without_peer).is_none());
195    }
196}