Skip to main content

p2p_foundation/bootstrap/
discovery.rs

1//! Bootstrap Discovery Module
2//! 
3//! Provides multiple mechanisms for discovering bootstrap nodes:
4//! 1. Hardcoded well-known bootstrap nodes
5//! 2. Three-word address resolution
6//! 3. DNS-based discovery (future)
7//! 4. Peer exchange from connected nodes
8
9use crate::bootstrap::{WordEncoder, ThreeWordAddress};
10use crate::Multiaddr;
11use anyhow::{Result, Context};
12use std::collections::HashMap;
13use tracing::{info, warn, debug};
14
15/// Well-known bootstrap nodes for the P2P Foundation network
16#[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    /// Create a new bootstrap discovery instance with default well-known nodes
25    pub fn new() -> Self {
26        let mut hardcoded_nodes = HashMap::new();
27        
28        // Digital Ocean bootstrap nodes (will be updated with real addresses)
29        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        // IPv6 primary bootstrap (Digital Ocean IPv6)
40        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        // IPv4 fallback bootstrap
46        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    /// Add a custom bootstrap node
59    pub fn add_bootstrap(&mut self, addr: Multiaddr) {
60        self.custom_nodes.push(addr);
61    }
62
63    /// Resolve a three-word address to a multiaddr
64    pub fn resolve_three_words(&self, three_words: &str) -> Result<Multiaddr> {
65        // First check if it's a hardcoded well-known address
66        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        // Try to decode as a generated three-word address
72        // For now, parse as ThreeWordAddress and attempt resolution
73        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    /// Get all available bootstrap addresses
79    pub fn get_bootstrap_addresses(&self) -> Vec<Multiaddr> {
80        let mut addresses = Vec::new();
81        
82        // Add hardcoded nodes
83        addresses.extend(self.hardcoded_nodes.values().cloned());
84        
85        // Add custom nodes
86        addresses.extend(self.custom_nodes.clone());
87        
88        addresses
89    }
90
91    /// Get well-known three-word addresses
92    pub fn get_well_known_three_words(&self) -> Vec<String> {
93        self.hardcoded_nodes.keys().cloned().collect()
94    }
95
96    /// Discover bootstrap nodes using multiple methods
97    pub async fn discover_bootstraps(&self) -> Result<Vec<Multiaddr>> {
98        let mut discovered = Vec::new();
99        
100        info!("๐Ÿ” Discovering bootstrap nodes...");
101        
102        // Start with hardcoded nodes
103        let hardcoded = self.get_bootstrap_addresses();
104        info!("๐Ÿ“ Found {} hardcoded bootstrap nodes", hardcoded.len());
105        discovered.extend(hardcoded);
106        
107        // TODO: Add DNS-based discovery
108        // TODO: Add peer exchange discovery
109        // TODO: Add DHT-based discovery
110        
111        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    /// Test connectivity to bootstrap nodes
121    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    /// Test connectivity to a single bootstrap node
145    async fn test_single_bootstrap(&self, _addr: &Multiaddr) -> bool {
146        // TODO: Implement actual connectivity test
147        // This would attempt to establish a connection to the bootstrap node
148        // For now, return true as a placeholder
149        true
150    }
151
152    /// Update the hardcoded bootstrap list (for dynamic updates)
153    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/// Bootstrap configuration for different deployment scenarios
166#[derive(Debug, Clone)]
167pub struct BootstrapConfig {
168    /// Enable hardcoded bootstrap discovery
169    pub enable_hardcoded: bool,
170    /// Enable three-word address resolution
171    pub enable_three_words: bool,
172    /// Enable DNS-based discovery
173    pub enable_dns: bool,
174    /// Custom bootstrap addresses
175    pub custom_bootstraps: Vec<Multiaddr>,
176    /// Fallback behavior when no bootstraps are available
177    pub fallback_behavior: FallbackBehavior,
178}
179
180#[derive(Debug, Clone)]
181pub enum FallbackBehavior {
182    /// Continue without bootstrap (may have limited connectivity)
183    ContinueWithoutBootstrap,
184    /// Retry discovery after a delay
185    RetryAfterDelay(std::time::Duration),
186    /// Fail if no bootstraps available
187    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
204/// Enhanced bootstrap discovery with configuration
205pub struct ConfigurableBootstrapDiscovery {
206    discovery: BootstrapDiscovery,
207    config: BootstrapConfig,
208}
209
210impl ConfigurableBootstrapDiscovery {
211    /// Create a new configurable bootstrap discovery
212    pub fn new(config: BootstrapConfig) -> Self {
213        let mut discovery = BootstrapDiscovery::new();
214        
215        // Add custom bootstrap nodes
216        for addr in &config.custom_bootstraps {
217            discovery.add_bootstrap(addr.clone());
218        }
219        
220        Self { discovery, config }
221    }
222
223    /// Discover bootstrap nodes with configuration options
224    pub async fn discover(&self) -> Result<Vec<Multiaddr>> {
225        self.discover_internal(0).await
226    }
227
228    /// Internal discovery with retry limit to prevent infinite recursion
229    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        // Add custom bootstraps
238        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    /// Resolve three-word address if enabled
260    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        // Test hardcoded three-word addresses
285        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}