ant_bootstrap/
contacts.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::{cache_store::CacheData, craft_valid_multiaddr_from_str, BootstrapAddr, Error, Result};
10use futures::stream::{self, StreamExt};
11use libp2p::Multiaddr;
12use reqwest::Client;
13use std::time::Duration;
14use url::Url;
15
16pub const MAINNET_CONTACTS: &[&str] = &[
17    "https://sn-testnet.s3.eu-west-2.amazonaws.com/network-contacts",
18    "http://159.89.251.80/bootstrap_cache.json",
19    "http://159.65.210.89/bootstrap_cache.json",
20    "http://159.223.246.45/bootstrap_cache.json",
21    "http://139.59.201.153/bootstrap_cache.json",
22    "http://139.59.200.27/bootstrap_cache.json",
23];
24pub const ALPHANET_CONTACTS: &[&str] = &[
25    "http://188.166.133.208/bootstrap_cache.json",
26    "http://188.166.133.125/bootstrap_cache.json",
27    "http://178.128.137.64/bootstrap_cache.json",
28    "http://159.223.242.7/bootstrap_cache.json",
29    "http://143.244.197.147/bootstrap_cache.json",
30];
31
32/// The client fetch timeout
33const FETCH_TIMEOUT_SECS: u64 = 30;
34/// Maximum number of endpoints to fetch at a time
35const MAX_CONCURRENT_FETCHES: usize = 3;
36/// The max number of retries for an endpoint on failure.
37const MAX_RETRIES_ON_FETCH_FAILURE: usize = 3;
38
39/// Discovers initial peers from a list of endpoints
40pub struct ContactsFetcher {
41    /// The number of addrs to fetch
42    max_addrs: usize,
43    /// The list of endpoints
44    endpoints: Vec<Url>,
45    /// Reqwest Client
46    request_client: Client,
47    /// Ignore PeerId in the multiaddr if not present. This is only useful for fetching nat detection contacts
48    ignore_peer_id: bool,
49}
50
51impl ContactsFetcher {
52    /// Create a new struct with the default endpoint
53    pub fn new() -> Result<Self> {
54        Self::with_endpoints(vec![])
55    }
56
57    /// Create a new struct with the provided endpoints
58    pub fn with_endpoints(endpoints: Vec<Url>) -> Result<Self> {
59        let request_client = Client::builder()
60            .timeout(Duration::from_secs(FETCH_TIMEOUT_SECS))
61            .build()?;
62
63        Ok(Self {
64            max_addrs: usize::MAX,
65            endpoints,
66            request_client,
67            ignore_peer_id: false,
68        })
69    }
70
71    /// Set the number of addrs to fetch
72    pub fn set_max_addrs(&mut self, max_addrs: usize) {
73        self.max_addrs = max_addrs;
74    }
75
76    /// Create a new struct with the mainnet endpoints
77    pub fn with_mainnet_endpoints() -> Result<Self> {
78        let mut fetcher = Self::new()?;
79        let mainnet_contact = MAINNET_CONTACTS
80            .iter()
81            .map(|url| url.parse().expect("Failed to parse static URL"))
82            .collect();
83        fetcher.endpoints = mainnet_contact;
84        Ok(fetcher)
85    }
86
87    /// Create a new struct with the alphanet endpoints
88    pub fn with_alphanet_endpoints() -> Result<Self> {
89        let mut fetcher = Self::new()?;
90        let alphanet_contact = ALPHANET_CONTACTS
91            .iter()
92            .map(|url| url.parse().expect("Failed to parse static URL"))
93            .collect();
94        fetcher.endpoints = alphanet_contact;
95        Ok(fetcher)
96    }
97
98    pub fn insert_endpoint(&mut self, endpoint: Url) {
99        self.endpoints.push(endpoint);
100    }
101
102    pub fn ignore_peer_id(&mut self, ignore_peer_id: bool) {
103        self.ignore_peer_id = ignore_peer_id;
104    }
105
106    /// Fetch the list of bootstrap addresses from all configured endpoints
107    pub async fn fetch_bootstrap_addresses(&self) -> Result<Vec<BootstrapAddr>> {
108        Ok(self
109            .fetch_addrs()
110            .await?
111            .into_iter()
112            .map(BootstrapAddr::new)
113            .collect())
114    }
115
116    /// Fetch the list of multiaddrs from all configured endpoints
117    pub async fn fetch_addrs(&self) -> Result<Vec<Multiaddr>> {
118        info!(
119            "Starting peer fetcher from {} endpoints: {:?}",
120            self.endpoints.len(),
121            self.endpoints
122        );
123        let mut bootstrap_addresses = Vec::new();
124
125        let mut fetches = stream::iter(self.endpoints.clone())
126            .map(|endpoint| async move {
127                info!(
128                    "Attempting to fetch bootstrap addresses from endpoint: {}",
129                    endpoint
130                );
131                (
132                    Self::fetch_from_endpoint(
133                        self.request_client.clone(),
134                        &endpoint,
135                        self.ignore_peer_id,
136                    )
137                    .await,
138                    endpoint,
139                )
140            })
141            .buffer_unordered(MAX_CONCURRENT_FETCHES);
142
143        while let Some((result, endpoint)) = fetches.next().await {
144            match result {
145                Ok(mut endpoing_bootstrap_addresses) => {
146                    info!(
147                        "Successfully fetched {} bootstrap addrs from {}. First few addrs: {:?}",
148                        endpoing_bootstrap_addresses.len(),
149                        endpoint,
150                        endpoing_bootstrap_addresses
151                            .iter()
152                            .take(3)
153                            .collect::<Vec<_>>()
154                    );
155                    bootstrap_addresses.append(&mut endpoing_bootstrap_addresses);
156                    if bootstrap_addresses.len() >= self.max_addrs {
157                        info!(
158                            "Fetched enough bootstrap addresses. Stopping. needed: {} Total fetched: {}",
159                            self.max_addrs,
160                            bootstrap_addresses.len()
161                        );
162                        break;
163                    }
164                }
165                Err(e) => {
166                    warn!("Failed to fetch bootstrap addrs from {}: {}", endpoint, e);
167                }
168            }
169        }
170
171        info!(
172            "Successfully discovered {} total addresses. First few: {:?}",
173            bootstrap_addresses.len(),
174            bootstrap_addresses.iter().take(3).collect::<Vec<_>>()
175        );
176        Ok(bootstrap_addresses)
177    }
178
179    /// Fetch the list of multiaddrs from a single endpoint
180    async fn fetch_from_endpoint(
181        request_client: Client,
182        endpoint: &Url,
183        ignore_peer_id: bool,
184    ) -> Result<Vec<Multiaddr>> {
185        let mut retries = 0;
186
187        let bootstrap_addresses = loop {
188            let response = request_client.get(endpoint.clone()).send().await;
189
190            match response {
191                Ok(response) => {
192                    if response.status().is_success() {
193                        let text = response.text().await?;
194
195                        match Self::try_parse_response(&text, ignore_peer_id) {
196                            Ok(addrs) => break addrs,
197                            Err(err) => {
198                                warn!("Failed to parse response with err: {err:?}");
199                                retries += 1;
200                                if retries >= MAX_RETRIES_ON_FETCH_FAILURE {
201                                    return Err(Error::FailedToObtainAddrsFromUrl(
202                                        endpoint.to_string(),
203                                        MAX_RETRIES_ON_FETCH_FAILURE,
204                                    ));
205                                }
206                            }
207                        }
208                    } else {
209                        retries += 1;
210                        if retries >= MAX_RETRIES_ON_FETCH_FAILURE {
211                            return Err(Error::FailedToObtainAddrsFromUrl(
212                                endpoint.to_string(),
213                                MAX_RETRIES_ON_FETCH_FAILURE,
214                            ));
215                        }
216                    }
217                }
218                Err(err) => {
219                    error!("Failed to get bootstrap addrs from URL {endpoint}: {err:?}");
220                    retries += 1;
221                    if retries >= MAX_RETRIES_ON_FETCH_FAILURE {
222                        return Err(Error::FailedToObtainAddrsFromUrl(
223                            endpoint.to_string(),
224                            MAX_RETRIES_ON_FETCH_FAILURE,
225                        ));
226                    }
227                }
228            }
229            debug!(
230                "Failed to get bootstrap addrs from URL, retrying {retries}/{MAX_RETRIES_ON_FETCH_FAILURE}"
231            );
232
233            tokio::time::sleep(Duration::from_secs(1)).await;
234        };
235
236        Ok(bootstrap_addresses)
237    }
238
239    /// Try to parse a response from an endpoint
240    fn try_parse_response(response: &str, ignore_peer_id: bool) -> Result<Vec<Multiaddr>> {
241        match serde_json::from_str::<CacheData>(response) {
242            Ok(json_endpoints) => {
243                info!(
244                    "Successfully parsed JSON response with {} peers",
245                    json_endpoints.peers.len()
246                );
247                let our_network_version = crate::get_network_version();
248
249                if json_endpoints.network_version != our_network_version {
250                    warn!(
251                        "Network version mismatch. Expected: {our_network_version}, got: {}. Skipping.", json_endpoints.network_version
252                    );
253                    return Ok(vec![]);
254                }
255                let bootstrap_addresses = json_endpoints
256                    .peers
257                    .into_iter()
258                    .filter_map(|(_, addresses)| {
259                        addresses.get_least_faulty().map(|addr| addr.addr.clone())
260                    })
261                    .collect::<Vec<_>>();
262
263                info!(
264                    "Successfully parsed {} valid peers from JSON",
265                    bootstrap_addresses.len()
266                );
267                Ok(bootstrap_addresses)
268            }
269            Err(_err) => {
270                info!("Attempting to parse response as plain text");
271                // Try parsing as plain text with one multiaddr per line
272                // example of contacts file exists in resources/network-contacts-examples
273                let bootstrap_addresses = response
274                    .split('\n')
275                    .filter_map(|str| craft_valid_multiaddr_from_str(str, ignore_peer_id))
276                    .collect::<Vec<_>>();
277
278                info!(
279                    "Successfully parsed {} valid bootstrap addrs from plain text",
280                    bootstrap_addresses.len()
281                );
282                Ok(bootstrap_addresses)
283            }
284        }
285    }
286}
287
288#[cfg(test)]
289mod tests {
290    use super::*;
291    use libp2p::Multiaddr;
292    use wiremock::{
293        matchers::{method, path},
294        Mock, MockServer, ResponseTemplate,
295    };
296
297    #[tokio::test]
298    async fn test_fetch_addrs() {
299        let mock_server = MockServer::start().await;
300
301        Mock::given(method("GET"))
302            .and(path("/"))
303            .respond_with(
304                ResponseTemplate::new(200)
305                    .set_body_string("/ip4/127.0.0.1/tcp/8080/p2p/12D3KooWRBhwfeP2Y4TCx1SM6s9rUoHhR5STiGwxBhgFRcw3UERE\n/ip4/127.0.0.2/tcp/8080/p2p/12D3KooWD2aV1f3qkhggzEFaJ24CEFYkSdZF5RKoMLpU6CwExYV5"),
306            )
307            .mount(&mock_server)
308            .await;
309
310        let mut fetcher = ContactsFetcher::new().unwrap();
311        fetcher.endpoints = vec![mock_server.uri().parse().unwrap()];
312
313        let addrs = fetcher.fetch_bootstrap_addresses().await.unwrap();
314        assert_eq!(addrs.len(), 2);
315
316        let addr1: Multiaddr =
317            "/ip4/127.0.0.1/tcp/8080/p2p/12D3KooWRBhwfeP2Y4TCx1SM6s9rUoHhR5STiGwxBhgFRcw3UERE"
318                .parse()
319                .unwrap();
320        let addr2: Multiaddr =
321            "/ip4/127.0.0.2/tcp/8080/p2p/12D3KooWD2aV1f3qkhggzEFaJ24CEFYkSdZF5RKoMLpU6CwExYV5"
322                .parse()
323                .unwrap();
324        assert!(addrs.iter().any(|p| p.addr == addr1));
325        assert!(addrs.iter().any(|p| p.addr == addr2));
326    }
327
328    #[tokio::test]
329    async fn test_endpoint_failover() {
330        let mock_server1 = MockServer::start().await;
331        let mock_server2 = MockServer::start().await;
332
333        // First endpoint fails
334        Mock::given(method("GET"))
335            .and(path("/"))
336            .respond_with(ResponseTemplate::new(500))
337            .mount(&mock_server1)
338            .await;
339
340        // Second endpoint succeeds
341        Mock::given(method("GET"))
342            .and(path("/"))
343            .respond_with(ResponseTemplate::new(200).set_body_string(
344                "/ip4/127.0.0.1/tcp/8080/p2p/12D3KooWD2aV1f3qkhggzEFaJ24CEFYkSdZF5RKoMLpU6CwExYV5",
345            ))
346            .mount(&mock_server2)
347            .await;
348
349        let mut fetcher = ContactsFetcher::new().unwrap();
350        fetcher.endpoints = vec![
351            mock_server1.uri().parse().unwrap(),
352            mock_server2.uri().parse().unwrap(),
353        ];
354
355        let addrs = fetcher.fetch_bootstrap_addresses().await.unwrap();
356        assert_eq!(addrs.len(), 1);
357
358        let addr: Multiaddr =
359            "/ip4/127.0.0.1/tcp/8080/p2p/12D3KooWD2aV1f3qkhggzEFaJ24CEFYkSdZF5RKoMLpU6CwExYV5"
360                .parse()
361                .unwrap();
362        assert_eq!(addrs[0].addr, addr);
363    }
364
365    #[tokio::test]
366    async fn test_invalid_multiaddr() {
367        let mock_server = MockServer::start().await;
368
369        Mock::given(method("GET"))
370            .and(path("/"))
371            .respond_with(
372                ResponseTemplate::new(200).set_body_string(
373                    "/ip4/127.0.0.1/tcp/8080\n/ip4/127.0.0.2/tcp/8080/p2p/12D3KooWD2aV1f3qkhggzEFaJ24CEFYkSdZF5RKoMLpU6CwExYV5",
374                ),
375            )
376            .mount(&mock_server)
377            .await;
378
379        let mut fetcher = ContactsFetcher::new().unwrap();
380        fetcher.endpoints = vec![mock_server.uri().parse().unwrap()];
381
382        let addrs = fetcher.fetch_bootstrap_addresses().await.unwrap();
383        let valid_addr: Multiaddr =
384            "/ip4/127.0.0.2/tcp/8080/p2p/12D3KooWD2aV1f3qkhggzEFaJ24CEFYkSdZF5RKoMLpU6CwExYV5"
385                .parse()
386                .unwrap();
387        assert_eq!(addrs[0].addr, valid_addr);
388    }
389
390    #[tokio::test]
391    async fn test_whitespace_and_empty_lines() {
392        let mock_server = MockServer::start().await;
393
394        Mock::given(method("GET"))
395            .and(path("/"))
396            .respond_with(
397                ResponseTemplate::new(200).set_body_string("\n  \n/ip4/127.0.0.1/tcp/8080/p2p/12D3KooWD2aV1f3qkhggzEFaJ24CEFYkSdZF5RKoMLpU6CwExYV5\n  \n"),
398            )
399            .mount(&mock_server)
400            .await;
401
402        let mut fetcher = ContactsFetcher::new().unwrap();
403        fetcher.endpoints = vec![mock_server.uri().parse().unwrap()];
404
405        let addrs = fetcher.fetch_bootstrap_addresses().await.unwrap();
406        assert_eq!(addrs.len(), 1);
407
408        let addr: Multiaddr =
409            "/ip4/127.0.0.1/tcp/8080/p2p/12D3KooWD2aV1f3qkhggzEFaJ24CEFYkSdZF5RKoMLpU6CwExYV5"
410                .parse()
411                .unwrap();
412        assert_eq!(addrs[0].addr, addr);
413    }
414
415    #[tokio::test]
416    async fn test_custom_endpoints() {
417        let endpoints = vec!["http://example.com".parse().unwrap()];
418        let fetcher = ContactsFetcher::with_endpoints(endpoints.clone()).unwrap();
419        assert_eq!(fetcher.endpoints, endpoints);
420    }
421}