p2p_foundation/bootstrap/
discovery.rs1use crate::bootstrap::{WordEncoder, ThreeWordAddress};
10use crate::Multiaddr;
11use anyhow::{Result, Context};
12use std::collections::HashMap;
13use tracing::{info, warn, debug};
14
15#[derive(Debug, Clone)]
17pub struct BootstrapDiscovery {
18 word_encoder: WordEncoder,
19 hardcoded_nodes: HashMap<String, Multiaddr>,
20 custom_nodes: Vec<Multiaddr>,
21}
22
23impl BootstrapDiscovery {
24 pub fn new() -> Self {
26 let mut hardcoded_nodes = HashMap::new();
27
28 hardcoded_nodes.insert(
30 "foundation.main.bootstrap".to_string(),
31 "/dns4/bootstrap.p2pfoundation.org/udp/9000/quic".parse().unwrap()
32 );
33
34 hardcoded_nodes.insert(
35 "foundation.backup.lighthouse".to_string(),
36 "/dns4/bootstrap2.p2pfoundation.org/udp/9000/quic".parse().unwrap()
37 );
38
39 hardcoded_nodes.insert(
41 "global.fast.eagle".to_string(),
42 "/ip6/2604:a880:400:d1:0:2:40d7:9001/udp/9000/quic".parse().unwrap()
43 );
44
45 hardcoded_nodes.insert(
47 "reliable.sturdy.anchor".to_string(),
48 "/ip4/147.182.203.123/udp/9000/quic".parse().unwrap()
49 );
50
51 Self {
52 word_encoder: WordEncoder::new(),
53 hardcoded_nodes,
54 custom_nodes: Vec::new(),
55 }
56 }
57
58 pub fn add_bootstrap(&mut self, addr: Multiaddr) {
60 self.custom_nodes.push(addr);
61 }
62
63 pub fn resolve_three_words(&self, three_words: &str) -> Result<Multiaddr> {
65 if let Some(addr) = self.hardcoded_nodes.get(three_words) {
67 debug!("Resolved hardcoded three-word address: {} -> {}", three_words, addr);
68 return Ok(addr.clone());
69 }
70
71 let word_address = ThreeWordAddress::from_string(three_words)?;
74 self.word_encoder.decode_to_multiaddr(&word_address)
75 .with_context(|| format!("Failed to resolve three-word address: {}", three_words))
76 }
77
78 pub fn get_bootstrap_addresses(&self) -> Vec<Multiaddr> {
80 let mut addresses = Vec::new();
81
82 addresses.extend(self.hardcoded_nodes.values().cloned());
84
85 addresses.extend(self.custom_nodes.clone());
87
88 addresses
89 }
90
91 pub fn get_well_known_three_words(&self) -> Vec<String> {
93 self.hardcoded_nodes.keys().cloned().collect()
94 }
95
96 pub async fn discover_bootstraps(&self) -> Result<Vec<Multiaddr>> {
98 let mut discovered = Vec::new();
99
100 info!("๐ Discovering bootstrap nodes...");
101
102 let hardcoded = self.get_bootstrap_addresses();
104 info!("๐ Found {} hardcoded bootstrap nodes", hardcoded.len());
105 discovered.extend(hardcoded);
106
107 if discovered.is_empty() {
112 warn!("โ ๏ธ No bootstrap nodes discovered, network may be unreachable");
113 } else {
114 info!("โ
Discovered {} total bootstrap nodes", discovered.len());
115 }
116
117 Ok(discovered)
118 }
119
120 pub async fn test_bootstrap_connectivity(&self) -> Result<Vec<(Multiaddr, bool)>> {
122 let bootstraps = self.get_bootstrap_addresses();
123 let mut results = Vec::new();
124
125 info!("๐งช Testing connectivity to {} bootstrap nodes", bootstraps.len());
126
127 for addr in bootstraps {
128 let reachable = self.test_single_bootstrap(&addr).await;
129 results.push((addr.clone(), reachable));
130
131 if reachable {
132 debug!("โ
Bootstrap node reachable: {}", addr);
133 } else {
134 warn!("โ Bootstrap node unreachable: {}", addr);
135 }
136 }
137
138 let reachable_count = results.iter().filter(|(_, reachable)| *reachable).count();
139 info!("๐ Bootstrap connectivity: {}/{} nodes reachable", reachable_count, results.len());
140
141 Ok(results)
142 }
143
144 async fn test_single_bootstrap(&self, _addr: &Multiaddr) -> bool {
146 true
150 }
151
152 pub fn update_hardcoded_bootstraps(&mut self, new_bootstraps: HashMap<String, Multiaddr>) {
154 info!("๐ Updating hardcoded bootstrap list with {} entries", new_bootstraps.len());
155 self.hardcoded_nodes = new_bootstraps;
156 }
157}
158
159impl Default for BootstrapDiscovery {
160 fn default() -> Self {
161 Self::new()
162 }
163}
164
165#[derive(Debug, Clone)]
167pub struct BootstrapConfig {
168 pub enable_hardcoded: bool,
170 pub enable_three_words: bool,
172 pub enable_dns: bool,
174 pub custom_bootstraps: Vec<Multiaddr>,
176 pub fallback_behavior: FallbackBehavior,
178}
179
180#[derive(Debug, Clone)]
181pub enum FallbackBehavior {
182 ContinueWithoutBootstrap,
184 RetryAfterDelay(std::time::Duration),
186 FailIfUnavailable,
188}
189
190impl Default for BootstrapConfig {
191 fn default() -> Self {
192 Self {
193 enable_hardcoded: true,
194 enable_three_words: true,
195 enable_dns: true,
196 custom_bootstraps: Vec::new(),
197 fallback_behavior: FallbackBehavior::RetryAfterDelay(
198 std::time::Duration::from_secs(30)
199 ),
200 }
201 }
202}
203
204pub struct ConfigurableBootstrapDiscovery {
206 discovery: BootstrapDiscovery,
207 config: BootstrapConfig,
208}
209
210impl ConfigurableBootstrapDiscovery {
211 pub fn new(config: BootstrapConfig) -> Self {
213 let mut discovery = BootstrapDiscovery::new();
214
215 for addr in &config.custom_bootstraps {
217 discovery.add_bootstrap(addr.clone());
218 }
219
220 Self { discovery, config }
221 }
222
223 pub async fn discover(&self) -> Result<Vec<Multiaddr>> {
225 self.discover_internal(0).await
226 }
227
228 async fn discover_internal(&self, retry_count: u32) -> Result<Vec<Multiaddr>> {
230 let mut addresses = Vec::new();
231
232 if self.config.enable_hardcoded {
233 let hardcoded = self.discovery.get_bootstrap_addresses();
234 addresses.extend(hardcoded);
235 }
236
237 addresses.extend(self.config.custom_bootstraps.clone());
239
240 if addresses.is_empty() && retry_count < 3 {
241 match &self.config.fallback_behavior {
242 FallbackBehavior::ContinueWithoutBootstrap => {
243 warn!("โ ๏ธ No bootstrap nodes available, continuing without bootstrap");
244 }
245 FallbackBehavior::RetryAfterDelay(duration) => {
246 warn!("โ ๏ธ No bootstrap nodes available, retrying after {:?} (attempt {})", duration, retry_count + 1);
247 tokio::time::sleep(*duration).await;
248 return Box::pin(self.discover_internal(retry_count + 1)).await;
249 }
250 FallbackBehavior::FailIfUnavailable => {
251 return Err(anyhow::anyhow!("No bootstrap nodes available and fallback disabled"));
252 }
253 }
254 }
255
256 Ok(addresses)
257 }
258
259 pub fn resolve_three_words(&self, three_words: &str) -> Result<Multiaddr> {
261 if !self.config.enable_three_words {
262 return Err(anyhow::anyhow!("Three-word address resolution disabled"));
263 }
264
265 self.discovery.resolve_three_words(three_words)
266 }
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272
273 #[test]
274 fn test_bootstrap_discovery_creation() {
275 let discovery = BootstrapDiscovery::new();
276 let addresses = discovery.get_bootstrap_addresses();
277 assert!(!addresses.is_empty(), "Should have hardcoded bootstrap addresses");
278 }
279
280 #[test]
281 fn test_three_word_resolution() {
282 let discovery = BootstrapDiscovery::new();
283
284 let result = discovery.resolve_three_words("foundation.main.bootstrap");
286 assert!(result.is_ok(), "Should resolve hardcoded three-word address");
287 }
288
289 #[test]
290 fn test_custom_bootstrap_addition() {
291 let mut discovery = BootstrapDiscovery::new();
292 let custom_addr: Multiaddr = "/ip4/192.168.1.100/udp/9000/quic".parse().unwrap();
293
294 let initial_count = discovery.get_bootstrap_addresses().len();
295 discovery.add_bootstrap(custom_addr.clone());
296 let final_count = discovery.get_bootstrap_addresses().len();
297
298 assert_eq!(final_count, initial_count + 1, "Should add custom bootstrap");
299 assert!(discovery.get_bootstrap_addresses().contains(&custom_addr));
300 }
301
302 #[tokio::test]
303 async fn test_configurable_discovery() {
304 let config = BootstrapConfig::default();
305 let discovery = ConfigurableBootstrapDiscovery::new(config);
306
307 let addresses = discovery.discover().await.unwrap();
308 assert!(!addresses.is_empty(), "Should discover bootstrap addresses");
309 }
310
311 #[test]
312 fn test_well_known_addresses() {
313 let discovery = BootstrapDiscovery::new();
314 let three_words = discovery.get_well_known_three_words();
315
316 assert!(three_words.contains(&"foundation.main.bootstrap".to_string()));
317 assert!(three_words.contains(&"global.fast.eagle".to_string()));
318 }
319}