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