ant_bootstrap/
contacts.rs1use 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
32const FETCH_TIMEOUT_SECS: u64 = 30;
34const MAX_CONCURRENT_FETCHES: usize = 3;
36const MAX_RETRIES_ON_FETCH_FAILURE: usize = 3;
38
39pub struct ContactsFetcher {
41 max_addrs: usize,
43 endpoints: Vec<Url>,
45 request_client: Client,
47 ignore_peer_id: bool,
49}
50
51impl ContactsFetcher {
52 pub fn new() -> Result<Self> {
54 Self::with_endpoints(vec![])
55 }
56
57 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 pub fn set_max_addrs(&mut self, max_addrs: usize) {
73 self.max_addrs = max_addrs;
74 }
75
76 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 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 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 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 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 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 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 Mock::given(method("GET"))
335 .and(path("/"))
336 .respond_with(ResponseTemplate::new(500))
337 .mount(&mock_server1)
338 .await;
339
340 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}