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    craft_valid_multiaddr, craft_valid_multiaddr_from_str,
11    error::{Error, Result},
12    BootstrapCacheConfig, BootstrapCacheStore, ContactsFetcher,
13};
14use ant_protocol::version::{get_network_id, ALPHANET_ID, MAINNET_ID};
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 not load the bootstrap addresses from the local cache.
61    #[clap(long, default_value = "false")]
62    pub ignore_cache: bool,
63    /// The directory to load and store the bootstrap cache. If not provided, the default path will be used.
64    ///
65    /// The JSON filename will be derived automatically from the network ID
66    ///
67    /// The default location is platform specific:
68    ///  - Linux: $HOME/.local/share/autonomi/bootstrap_cache/bootstrap_cache_<network_id>.json
69    ///  - macOS: $HOME/Library/Application Support/autonomi/bootstrap_cache/bootstrap_cache_<network_id>.json
70    ///  - Windows: C:\Users\<username>\AppData\Roaming\autonomi\bootstrap_cache\bootstrap_cache_<network_id>.json
71    #[clap(long)]
72    pub bootstrap_cache_dir: Option<PathBuf>,
73}
74
75impl InitialPeersConfig {
76    /// Get bootstrap peers
77    pub async fn get_bootstrap_addr(
78        &self,
79        config: Option<BootstrapCacheConfig>,
80        count: Option<usize>,
81    ) -> Result<Vec<Multiaddr>> {
82        // If this is the first node, return an empty list
83        if self.first {
84            info!("First node in network, no initial bootstrap peers");
85            return Ok(vec![]);
86        }
87
88        let mut bootstrap_addresses = vec![];
89
90        // Read from ANT_PEERS environment variable if present
91        bootstrap_addresses.extend(Self::read_bootstrap_addr_from_env());
92
93        if !bootstrap_addresses.is_empty() {
94            info!(
95                "Found {} bootstrap addresses from environment variable",
96                bootstrap_addresses.len()
97            );
98            return Ok(bootstrap_addresses);
99        }
100
101        // Add addrs from arguments if present
102        for addr in &self.addrs {
103            if let Some(addr) = craft_valid_multiaddr(addr, false) {
104                info!("Adding addr from arguments: {addr}");
105                bootstrap_addresses.push(addr);
106            } else {
107                warn!("Invalid multiaddress format from arguments: {addr}");
108            }
109        }
110
111        if let Some(count) = count {
112            if bootstrap_addresses.len() >= count {
113                bootstrap_addresses.truncate(count);
114                info!(
115                    "Found {} bootstrap addresses. Returning early.",
116                    bootstrap_addresses.len()
117                );
118                return Ok(bootstrap_addresses);
119            }
120        }
121
122        // load from cache if present
123        if !self.ignore_cache {
124            let cfg = if let Some(config) = config {
125                Some(config)
126            } else {
127                BootstrapCacheConfig::new(self.local)
128                    .inspect_err(|err| {
129                        error!("Failed to create cache config: {err}",);
130                    })
131                    .ok()
132            };
133            if let Some(cfg) = cfg {
134                if let Ok(data) = BootstrapCacheStore::load_cache_data(&cfg) {
135                    bootstrap_addresses.extend(data.get_all_addrs().cloned());
136
137                    if let Some(count) = count {
138                        if bootstrap_addresses.len() >= count {
139                            bootstrap_addresses.truncate(count);
140                            info!(
141                                "Found {} bootstrap addresses. Returning early.",
142                                bootstrap_addresses.len()
143                            );
144                            return Ok(bootstrap_addresses);
145                        }
146                    }
147                }
148            }
149        } else {
150            info!("Ignoring cache, not loading bootstrap addresses from cache");
151        }
152
153        // If we have a network contacts URL, fetch addrs from there.
154        if !self.local && !self.network_contacts_url.is_empty() {
155            info!(
156                "Fetching bootstrap address from network contacts URLs: {:?}",
157                self.network_contacts_url
158            );
159            let addrs = self
160                .network_contacts_url
161                .iter()
162                .map(|url| url.parse::<Url>().map_err(|_| Error::FailedToParseUrl))
163                .collect::<Result<Vec<Url>>>()?;
164            let mut contacts_fetcher = ContactsFetcher::with_endpoints(addrs)?;
165            if let Some(count) = count {
166                contacts_fetcher.set_max_addrs(count);
167            }
168            let addrs = contacts_fetcher.fetch_bootstrap_addresses().await?;
169            bootstrap_addresses.extend(addrs);
170
171            if let Some(count) = count {
172                if bootstrap_addresses.len() >= count {
173                    bootstrap_addresses.truncate(count);
174                    info!(
175                        "Found {} bootstrap addresses. Returning early.",
176                        bootstrap_addresses.len()
177                    );
178                    return Ok(bootstrap_addresses);
179                }
180            }
181        }
182
183        if !self.local && get_network_id() == MAINNET_ID {
184            let mut contacts_fetcher = ContactsFetcher::with_mainnet_endpoints()?;
185            if let Some(count) = count {
186                contacts_fetcher.set_max_addrs(count);
187            }
188            info!("Fetching bootstrap address from mainnet contacts");
189            let addrs = contacts_fetcher.fetch_bootstrap_addresses().await?;
190            bootstrap_addresses.extend(addrs);
191        } else if !self.local && get_network_id() == ALPHANET_ID {
192            let mut contacts_fetcher = ContactsFetcher::with_alphanet_endpoints()?;
193            if let Some(count) = count {
194                contacts_fetcher.set_max_addrs(count);
195            }
196            info!("Fetching bootstrap address from alphanet contacts");
197            let addrs = contacts_fetcher.fetch_bootstrap_addresses().await?;
198            bootstrap_addresses.extend(addrs);
199        }
200
201        if !bootstrap_addresses.is_empty() {
202            if let Some(count) = count {
203                bootstrap_addresses.truncate(count);
204            }
205            info!(
206                "Found {} bootstrap addresses. Returning early.",
207                bootstrap_addresses.len()
208            );
209            Ok(bootstrap_addresses)
210        } else {
211            error!("No initial bootstrap peers found through any means");
212            Err(Error::NoBootstrapPeersFound)
213        }
214    }
215
216    pub fn read_bootstrap_addr_from_env() -> Vec<Multiaddr> {
217        let mut bootstrap_addresses = Vec::new();
218        // Read from ANT_PEERS environment variable if present
219        if let Ok(addrs) = std::env::var(ANT_PEERS_ENV) {
220            for addr_str in addrs.split(',') {
221                if let Some(addr) = craft_valid_multiaddr_from_str(addr_str, false) {
222                    info!("Adding addr from environment variable: {addr}");
223                    bootstrap_addresses.push(addr);
224                } else {
225                    warn!("Invalid multiaddress format from environment variable: {addr_str}");
226                }
227            }
228        }
229        bootstrap_addresses
230    }
231}