ant_bootstrap/
initial_peers.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
9use crate::{
10    config::cache_file_name,
11    craft_valid_multiaddr, craft_valid_multiaddr_from_str,
12    error::{Error, Result},
13    BootstrapAddr, BootstrapCacheConfig, BootstrapCacheStore, ContactsFetcher,
14};
15use clap::Args;
16use libp2p::Multiaddr;
17use serde::{Deserialize, Serialize};
18use std::path::PathBuf;
19use url::Url;
20
21/// The name of the environment variable that can be used to pass peers to the node.
22pub const ANT_PEERS_ENV: &str = "ANT_PEERS";
23
24/// Configurations to fetch the initial peers which is used to bootstrap the network.
25/// This could optionally also be used as a command line argument struct.
26#[derive(Args, Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
27pub struct InitialPeersConfig {
28    /// Set to indicate this is the first node in a new network
29    ///
30    /// If this argument is used, any others will be ignored because they do not apply to the first
31    /// node.
32    #[clap(long, default_value = "false")]
33    pub first: bool,
34    /// Addr(s) to use for bootstrap, in a 'multiaddr' format containing the peer ID.
35    ///
36    /// A multiaddr looks like
37    /// '/ip4/1.2.3.4/tcp/1200/tcp/p2p/12D3KooWRi6wF7yxWLuPSNskXc6kQ5cJ6eaymeMbCRdTnMesPgFx' where
38    /// `1.2.3.4` is the IP, `1200` is the port and the (optional) last part is the peer ID.
39    ///
40    /// This argument can be provided multiple times to connect to multiple peers.
41    ///
42    /// Alternatively, the `ANT_PEERS` environment variable can provide a comma-separated peer
43    /// list.
44    #[clap(
45        long = "peer",
46        value_name = "multiaddr",
47        value_delimiter = ',',
48        conflicts_with = "first"
49    )]
50    pub addrs: Vec<Multiaddr>,
51    /// Specify the URL to fetch the network contacts from.
52    ///
53    /// The URL can point to a text file containing Multiaddresses separated by newline character, or
54    /// a bootstrap cache JSON file.
55    #[clap(long, conflicts_with = "first", value_delimiter = ',')]
56    pub network_contacts_url: Vec<String>,
57    /// Set to indicate this is a local network.
58    #[clap(long, conflicts_with = "network_contacts_url", default_value = "false")]
59    pub local: bool,
60    /// Set to indicate this is a testnet.
61    ///
62    /// This disables fetching peers from the mainnet network contacts.
63    #[clap(name = "testnet", long)]
64    pub disable_mainnet_contacts: bool,
65    /// Set to not load the bootstrap addresses from the local cache.
66    #[clap(long, default_value = "false")]
67    pub ignore_cache: bool,
68    /// The directory to load and store the bootstrap cache. If not provided, the default path will be used.
69    ///
70    /// The JSON filename will be derived automatically from the network ID
71    ///
72    /// The default location is platform specific:
73    ///  - Linux: $HOME/.local/share/autonomi/bootstrap_cache/bootstrap_cache_<network_id>.json
74    ///  - macOS: $HOME/Library/Application Support/autonomi/bootstrap_cache/bootstrap_cache_<network_id>.json
75    ///  - Windows: C:\Users\<username>\AppData\Roaming\autonomi\bootstrap_cache\bootstrap_cache_<network_id>.json
76    #[clap(long)]
77    pub bootstrap_cache_dir: Option<PathBuf>,
78}
79
80impl InitialPeersConfig {
81    /// Get bootstrap peers sorted by the failure rate. The peer with the lowest failure rate will be
82    /// the first in the list.
83    pub async fn get_addrs(
84        &self,
85        config: Option<BootstrapCacheConfig>,
86        count: Option<usize>,
87    ) -> Result<Vec<Multiaddr>> {
88        Ok(self
89            .get_bootstrap_addr(config, count)
90            .await?
91            .into_iter()
92            .map(|addr| addr.addr)
93            .collect())
94    }
95
96    /// Get bootstrap peers sorted by the failure rate. The peer with the lowest failure rate will be
97    /// the first in the list.
98    pub async fn get_bootstrap_addr(
99        &self,
100        config: Option<BootstrapCacheConfig>,
101        count: Option<usize>,
102    ) -> Result<Vec<BootstrapAddr>> {
103        // If this is the first node, return an empty list
104        if self.first {
105            info!("First node in network, no initial bootstrap peers");
106            return Ok(vec![]);
107        }
108
109        let mut bootstrap_addresses = vec![];
110
111        // Read from ANT_PEERS environment variable if present
112        bootstrap_addresses.extend(Self::read_bootstrap_addr_from_env());
113
114        if !bootstrap_addresses.is_empty() {
115            return Ok(bootstrap_addresses);
116        }
117
118        // Add addrs from arguments if present
119        for addr in &self.addrs {
120            if let Some(addr) = craft_valid_multiaddr(addr, false) {
121                info!("Adding addr from arguments: {addr}");
122                bootstrap_addresses.push(BootstrapAddr::new(addr));
123            } else {
124                warn!("Invalid multiaddress format from arguments: {addr}");
125            }
126        }
127
128        if let Some(count) = count {
129            if bootstrap_addresses.len() >= count {
130                bootstrap_addresses.sort_by_key(|addr| addr.failure_rate() as u64);
131                bootstrap_addresses.truncate(count);
132                info!("Returning early as enough bootstrap addresses are found");
133                return Ok(bootstrap_addresses);
134            }
135        }
136
137        // load from cache if present
138        if !self.ignore_cache {
139            let cfg = if let Some(config) = config {
140                Some(config)
141            } else {
142                BootstrapCacheConfig::default_config(self.local).ok()
143            };
144            if let Some(mut cfg) = cfg {
145                if let Some(file_path) = self.get_bootstrap_cache_path()? {
146                    cfg.cache_file_path = file_path;
147                }
148                info!("Loading bootstrap addresses from cache");
149                if let Ok(data) = BootstrapCacheStore::load_cache_data(&cfg) {
150                    let from_cache = data.peers.into_iter().filter_map(|(_, addrs)| {
151                        addrs
152                            .0
153                            .into_iter()
154                            .min_by_key(|addr| addr.failure_rate() as u64)
155                    });
156                    bootstrap_addresses.extend(from_cache);
157
158                    if let Some(count) = count {
159                        if bootstrap_addresses.len() >= count {
160                            bootstrap_addresses.sort_by_key(|addr| addr.failure_rate() as u64);
161                            bootstrap_addresses.truncate(count);
162                            info!("Returning early as enough bootstrap addresses are found");
163                            return Ok(bootstrap_addresses);
164                        }
165                    }
166                }
167            }
168        } else {
169            info!("Ignoring cache, not loading bootstrap addresses from cache");
170        }
171
172        // If we have a network contacts URL, fetch addrs from there.
173        if !self.local && !self.network_contacts_url.is_empty() {
174            info!(
175                "Fetching bootstrap address from network contacts URLs: {:?}",
176                self.network_contacts_url
177            );
178            let addrs = self
179                .network_contacts_url
180                .iter()
181                .map(|url| url.parse::<Url>().map_err(|_| Error::FailedToParseUrl))
182                .collect::<Result<Vec<Url>>>()?;
183            let mut contacts_fetcher = ContactsFetcher::with_endpoints(addrs)?;
184            if let Some(count) = count {
185                contacts_fetcher.set_max_addrs(count);
186            }
187            let addrs = contacts_fetcher.fetch_bootstrap_addresses().await?;
188            bootstrap_addresses.extend(addrs);
189
190            if let Some(count) = count {
191                if bootstrap_addresses.len() >= count {
192                    bootstrap_addresses.sort_by_key(|addr| addr.failure_rate() as u64);
193                    bootstrap_addresses.truncate(count);
194                    info!("Returning early as enough bootstrap addresses are found");
195                    return Ok(bootstrap_addresses);
196                }
197            }
198        }
199
200        if !self.local && !self.disable_mainnet_contacts {
201            let mut contacts_fetcher = ContactsFetcher::with_mainnet_endpoints()?;
202            if let Some(count) = count {
203                contacts_fetcher.set_max_addrs(count);
204            }
205            let addrs = contacts_fetcher.fetch_bootstrap_addresses().await?;
206            bootstrap_addresses.extend(addrs);
207        }
208
209        if !bootstrap_addresses.is_empty() {
210            bootstrap_addresses.sort_by_key(|addr| addr.failure_rate() as u64);
211            if let Some(count) = count {
212                bootstrap_addresses.truncate(count);
213            }
214            Ok(bootstrap_addresses)
215        } else {
216            error!("No initial bootstrap peers found through any means");
217            Err(Error::NoBootstrapPeersFound)
218        }
219    }
220
221    pub fn read_addr_from_env() -> Vec<Multiaddr> {
222        Self::read_bootstrap_addr_from_env()
223            .into_iter()
224            .map(|addr| addr.addr)
225            .collect()
226    }
227
228    pub fn read_bootstrap_addr_from_env() -> Vec<BootstrapAddr> {
229        let mut bootstrap_addresses = Vec::new();
230        // Read from ANT_PEERS environment variable if present
231        if let Ok(addrs) = std::env::var(ANT_PEERS_ENV) {
232            for addr_str in addrs.split(',') {
233                if let Some(addr) = craft_valid_multiaddr_from_str(addr_str, false) {
234                    info!("Adding addr from environment variable: {addr}");
235                    bootstrap_addresses.push(BootstrapAddr::new(addr));
236                } else {
237                    warn!("Invalid multiaddress format from environment variable: {addr_str}");
238                }
239            }
240        }
241        bootstrap_addresses
242    }
243
244    /// Get the path to the bootstrap cache JSON file if `Self::bootstrap_cache_dir` is set
245    pub fn get_bootstrap_cache_path(&self) -> Result<Option<PathBuf>> {
246        if let Some(dir) = &self.bootstrap_cache_dir {
247            if dir.is_file() {
248                return Err(Error::InvalidBootstrapCacheDir);
249            }
250
251            if !dir.exists() {
252                std::fs::create_dir_all(dir)?;
253            }
254
255            let path = dir.join(cache_file_name());
256            Ok(Some(path))
257        } else {
258            Ok(None)
259        }
260    }
261}