1use crate::address::NetworkAddress;
23use crate::bootstrap::{FourWordAddress, WordEncoder};
24use anyhow::Result;
25use std::collections::HashMap;
26use std::net::SocketAddr;
27use tracing::{debug, info, warn};
28
29#[derive(Debug, Clone)]
31pub struct BootstrapDiscovery {
32 word_encoder: WordEncoder,
33 hardcoded_nodes: HashMap<String, NetworkAddress>,
34 custom_nodes: Vec<NetworkAddress>,
35}
36
37impl BootstrapDiscovery {
38 pub fn new() -> Self {
40 let mut hardcoded_nodes = HashMap::new();
41
42 hardcoded_nodes.insert(
44 "foundation.main.bootstrap".to_string(),
45 NetworkAddress::from_ipv4(std::net::Ipv4Addr::new(147, 182, 203, 123), 9000),
46 );
47
48 hardcoded_nodes.insert(
49 "foundation.backup.lighthouse".to_string(),
50 NetworkAddress::from_ipv4(std::net::Ipv4Addr::new(147, 182, 203, 124), 9000),
51 );
52
53 hardcoded_nodes.insert(
55 "global.fast.eagle".to_string(),
56 NetworkAddress::from_ipv6(
57 "2604:a880:400:d1:0:2:40d7:9001"
58 .parse()
59 .unwrap_or(std::net::Ipv6Addr::LOCALHOST),
60 9000,
61 ),
62 );
63
64 hardcoded_nodes.insert(
66 "reliable.sturdy.anchor".to_string(),
67 NetworkAddress::from_ipv4(std::net::Ipv4Addr::new(8, 8, 8, 8), 9000),
68 );
69
70 Self {
71 word_encoder: WordEncoder::new(),
72 hardcoded_nodes,
73 custom_nodes: Vec::new(),
74 }
75 }
76
77 pub fn add_bootstrap(&mut self, addr: NetworkAddress) {
79 self.custom_nodes.push(addr);
80 }
81
82 pub fn resolve_four_words(&self, four_words: &str) -> Result<std::net::SocketAddr> {
84 if let Some(addr) = self.hardcoded_nodes.get(four_words) {
86 debug!(
87 "Resolved hardcoded four-word address: {} -> {}",
88 four_words, addr
89 );
90 return Ok(addr.socket_addr());
92 }
93
94 let word_address = FourWordAddress::from_string(four_words)
96 .map_err(|e| anyhow::anyhow!("Invalid four-word address format: {}", e))?;
97
98 let socket_addr = self
100 .word_encoder
101 .decode_to_socket_addr(&word_address)
102 .map_err(|e| anyhow::anyhow!("Failed to decode four-word address: {}", e))?;
103
104 Ok(socket_addr)
105 }
106
107 pub fn get_bootstrap_addresses(&self) -> Vec<NetworkAddress> {
109 let mut addresses = Vec::new();
110
111 addresses.extend(self.hardcoded_nodes.values().cloned());
113
114 addresses.extend(self.custom_nodes.clone());
116
117 addresses
118 }
119
120 pub fn get_well_known_four_words(&self) -> Vec<String> {
122 self.hardcoded_nodes.keys().cloned().collect()
123 }
124
125 pub async fn discover_bootstraps(&self) -> Result<Vec<NetworkAddress>> {
127 let mut discovered = Vec::new();
128
129 info!("๐ Discovering bootstrap nodes...");
130
131 let hardcoded = self.get_bootstrap_addresses();
133 info!("๐ Found {} hardcoded bootstrap nodes", hardcoded.len());
134 discovered.extend(hardcoded);
135
136 if discovered.is_empty() {
141 warn!("โ ๏ธ No bootstrap nodes discovered, network may be unreachable");
142 } else {
143 info!("โ
Discovered {} total bootstrap nodes", discovered.len());
144 }
145
146 Ok(discovered)
147 }
148
149 pub async fn test_bootstrap_connectivity(&self) -> Result<Vec<(NetworkAddress, bool)>> {
151 let bootstraps = self.get_bootstrap_addresses();
152 let mut results = Vec::new();
153
154 info!(
155 "๐งช Testing connectivity to {} bootstrap nodes",
156 bootstraps.len()
157 );
158
159 for addr in bootstraps {
160 let reachable = self.test_single_bootstrap(&addr).await;
161 results.push((addr.clone(), reachable));
162
163 if reachable {
164 debug!("โ
Bootstrap node reachable: {}", addr);
165 } else {
166 warn!("โ Bootstrap node unreachable: {}", addr);
167 }
168 }
169
170 let reachable_count = results.iter().filter(|(_, reachable)| *reachable).count();
171 info!(
172 "๐ Bootstrap connectivity: {}/{} nodes reachable",
173 reachable_count,
174 results.len()
175 );
176
177 Ok(results)
178 }
179
180 async fn test_single_bootstrap(&self, _addr: &NetworkAddress) -> bool {
182 true
186 }
187
188 pub fn update_hardcoded_bootstraps(&mut self, new_bootstraps: HashMap<String, NetworkAddress>) {
190 info!(
191 "๐ Updating hardcoded bootstrap list with {} entries",
192 new_bootstraps.len()
193 );
194 self.hardcoded_nodes = new_bootstraps;
195 }
196}
197
198impl Default for BootstrapDiscovery {
199 fn default() -> Self {
200 Self::new()
201 }
202}
203
204#[derive(Debug, Clone)]
206pub struct BootstrapConfig {
207 pub enable_hardcoded: bool,
209 pub enable_four_words: bool,
211 pub enable_dns: bool,
213 pub custom_bootstraps: Vec<NetworkAddress>,
215 pub fallback_behavior: FallbackBehavior,
217}
218
219#[derive(Debug, Clone)]
220pub enum FallbackBehavior {
221 ContinueWithoutBootstrap,
223 RetryAfterDelay(std::time::Duration),
225 FailIfUnavailable,
227}
228
229impl Default for BootstrapConfig {
230 fn default() -> Self {
231 Self {
232 enable_hardcoded: true,
233 enable_four_words: true,
234 enable_dns: true,
235 custom_bootstraps: Vec::new(),
236 fallback_behavior: FallbackBehavior::RetryAfterDelay(std::time::Duration::from_secs(
237 30,
238 )),
239 }
240 }
241}
242
243pub struct ConfigurableBootstrapDiscovery {
245 discovery: BootstrapDiscovery,
246 config: BootstrapConfig,
247}
248
249impl ConfigurableBootstrapDiscovery {
250 pub fn new(config: BootstrapConfig) -> Self {
252 let mut discovery = BootstrapDiscovery::new();
253
254 for addr in &config.custom_bootstraps {
256 discovery.add_bootstrap(addr.clone());
257 }
258
259 Self { discovery, config }
260 }
261
262 pub async fn discover(&self) -> Result<Vec<NetworkAddress>> {
264 self.discover_internal(0).await
265 }
266
267 async fn discover_internal(&self, retry_count: u32) -> Result<Vec<NetworkAddress>> {
269 let mut addresses = Vec::new();
270
271 if self.config.enable_hardcoded {
272 let hardcoded = self.discovery.get_bootstrap_addresses();
273 addresses.extend(hardcoded);
274 }
275
276 addresses.extend(self.config.custom_bootstraps.clone());
278
279 if addresses.is_empty() && retry_count < 3 {
280 match &self.config.fallback_behavior {
281 FallbackBehavior::ContinueWithoutBootstrap => {
282 warn!("โ ๏ธ No bootstrap nodes available, continuing without bootstrap");
283 }
284 FallbackBehavior::RetryAfterDelay(duration) => {
285 warn!(
286 "โ ๏ธ No bootstrap nodes available, retrying after {:?} (attempt {})",
287 duration,
288 retry_count + 1
289 );
290 tokio::time::sleep(*duration).await;
291 return Box::pin(self.discover_internal(retry_count + 1)).await;
292 }
293 FallbackBehavior::FailIfUnavailable => {
294 return Err(anyhow::anyhow!(
295 "No bootstrap nodes available and fallback disabled"
296 ));
297 }
298 }
299 }
300
301 Ok(addresses)
302 }
303
304 pub fn resolve_four_words(&self, four_words: &str) -> Result<SocketAddr> {
306 if !self.config.enable_four_words {
307 return Err(anyhow::anyhow!("Four-word address resolution disabled"));
308 }
309
310 self.discovery.resolve_four_words(four_words)
311 }
312}
313
314#[cfg(test)]
315mod tests {
316 use super::*;
317
318 #[test]
319 fn test_bootstrap_discovery_creation() {
320 let discovery = BootstrapDiscovery::new();
321 let addresses = discovery.get_bootstrap_addresses();
322 assert!(
323 !addresses.is_empty(),
324 "Should have hardcoded bootstrap addresses"
325 );
326 }
327
328 #[test]
329 fn test_four_word_resolution() {
330 let discovery = BootstrapDiscovery::new();
331
332 let result = discovery.resolve_four_words("foundation.main.bootstrap");
334 assert!(result.is_ok(), "Should resolve hardcoded four-word address");
335 }
336
337 #[test]
338 fn test_custom_bootstrap_addition() {
339 let mut discovery = BootstrapDiscovery::new();
340 let custom_addr =
341 NetworkAddress::from_ipv4(std::net::Ipv4Addr::new(192, 168, 1, 100), 9000);
342
343 let initial_count = discovery.get_bootstrap_addresses().len();
344 discovery.add_bootstrap(custom_addr.clone());
345 let final_count = discovery.get_bootstrap_addresses().len();
346
347 assert_eq!(
348 final_count,
349 initial_count + 1,
350 "Should add custom bootstrap"
351 );
352 assert!(discovery.get_bootstrap_addresses().contains(&custom_addr));
353 }
354
355 #[tokio::test]
356 async fn test_configurable_discovery() {
357 let config = BootstrapConfig::default();
358 let discovery = ConfigurableBootstrapDiscovery::new(config);
359
360 let addresses = discovery
361 .discover()
362 .await
363 .expect("valid discovery operation");
364 assert!(!addresses.is_empty(), "Should discover bootstrap addresses");
365 }
366
367 #[test]
368 fn test_well_known_addresses() {
369 let discovery = BootstrapDiscovery::new();
370 let four_words = discovery.get_well_known_four_words();
371
372 assert!(four_words.contains(&"foundation.main.bootstrap".to_string()));
373 assert!(four_words.contains(&"global.fast.eagle".to_string()));
374 }
375}