ant_core/bootstrap/
mod.rs1pub mod cache;
9pub mod contact;
10pub mod discovery;
11pub mod merge;
12pub mod words;
13
14pub use cache::{BootstrapCache, CacheConfig, CacheError};
15pub use contact::{ContactEntry, QualityMetrics, QualityCalculator};
16pub use discovery::{BootstrapDiscovery, BootstrapConfig, ConfigurableBootstrapDiscovery};
17pub use merge::{MergeCoordinator, MergeResult};
18pub use words::{ThreeWordAddress, WordDictionary, WordEncoder};
19
20use crate::{Result, P2PError, PeerId};
21use std::path::PathBuf;
22use std::time::Duration;
23
24pub const DEFAULT_MAX_CONTACTS: usize = 30_000;
26pub const DEFAULT_CACHE_DIR: &str = ".cache/p2p_foundation";
28pub const DEFAULT_MERGE_INTERVAL: Duration = Duration::from_secs(30);
30pub const DEFAULT_CLEANUP_INTERVAL: Duration = Duration::from_secs(3600);
32pub const DEFAULT_QUALITY_UPDATE_INTERVAL: Duration = Duration::from_secs(300);
34
35pub struct BootstrapManager {
37 cache: BootstrapCache,
38 merge_coordinator: MergeCoordinator,
39 word_encoder: WordEncoder,
40}
41
42impl BootstrapManager {
43 pub async fn new() -> Result<Self> {
45 let cache_dir = home_cache_dir()?;
46 let config = CacheConfig::default();
47
48 let cache = BootstrapCache::new(cache_dir.clone(), config).await?;
49 let merge_coordinator = MergeCoordinator::new(cache_dir)?;
50 let word_encoder = WordEncoder::new();
51
52 Ok(Self {
53 cache,
54 merge_coordinator,
55 word_encoder,
56 })
57 }
58
59 pub async fn with_config(config: CacheConfig) -> Result<Self> {
61 let cache_dir = home_cache_dir()?;
62
63 let cache = BootstrapCache::new(cache_dir.clone(), config).await?;
64 let merge_coordinator = MergeCoordinator::new(cache_dir)?;
65 let word_encoder = WordEncoder::new();
66
67 Ok(Self {
68 cache,
69 merge_coordinator,
70 word_encoder,
71 })
72 }
73
74 pub async fn get_bootstrap_peers(&self, count: usize) -> Result<Vec<ContactEntry>> {
76 self.cache.get_bootstrap_peers(count).await
77 }
78
79 pub async fn add_contact(&mut self, contact: ContactEntry) -> Result<()> {
81 self.cache.add_contact(contact).await
82 }
83
84 pub async fn update_contact_metrics(&mut self, peer_id: &PeerId, metrics: QualityMetrics) -> Result<()> {
86 self.cache.update_contact_metrics(peer_id, metrics).await
87 }
88
89 pub async fn start_background_tasks(&mut self) -> Result<()> {
91 let cache_clone = self.cache.clone();
93 let merge_coordinator = self.merge_coordinator.clone();
94
95 tokio::spawn(async move {
96 let mut interval = tokio::time::interval(DEFAULT_MERGE_INTERVAL);
97 loop {
98 interval.tick().await;
99 if let Err(e) = merge_coordinator.merge_instance_caches(&cache_clone).await {
100 tracing::warn!("Failed to merge instance caches: {}", e);
101 }
102 }
103 });
104
105 let cache_clone = self.cache.clone();
107 tokio::spawn(async move {
108 let mut interval = tokio::time::interval(DEFAULT_QUALITY_UPDATE_INTERVAL);
109 loop {
110 interval.tick().await;
111 if let Err(e) = cache_clone.update_quality_scores().await {
112 tracing::warn!("Failed to update quality scores: {}", e);
113 }
114 }
115 });
116
117 let cache_clone = self.cache.clone();
119 tokio::spawn(async move {
120 let mut interval = tokio::time::interval(DEFAULT_CLEANUP_INTERVAL);
121 loop {
122 interval.tick().await;
123 if let Err(e) = cache_clone.cleanup_stale_entries().await {
124 tracing::warn!("Failed to cleanup stale entries: {}", e);
125 }
126 }
127 });
128
129 Ok(())
130 }
131
132 pub async fn get_stats(&self) -> Result<CacheStats> {
134 self.cache.get_stats().await
135 }
136
137 pub async fn force_merge(&self) -> Result<MergeResult> {
139 self.merge_coordinator.merge_instance_caches(&self.cache).await
140 }
141
142 pub fn encode_address(&self, multiaddr: &crate::Multiaddr) -> Result<ThreeWordAddress> {
144 self.word_encoder.encode_multiaddr(multiaddr)
145 }
146
147 pub fn decode_address(&self, words: &ThreeWordAddress) -> Result<crate::Multiaddr> {
149 self.word_encoder.decode_to_multiaddr(words)
150 }
151
152 pub fn validate_words(&self, words: &ThreeWordAddress) -> Result<()> {
154 words.validate(&self.word_encoder)
155 }
156
157 pub fn word_encoder(&self) -> &WordEncoder {
159 &self.word_encoder
160 }
161
162 pub fn get_well_known_word_addresses(&self) -> Vec<(ThreeWordAddress, crate::Multiaddr)> {
164 let well_known_addrs = vec![
165 "/ip6/2001:4860:4860::8888/udp/9000/quic", "/ip6/2001:4860:4860::8844/udp/9001/quic",
168 "/ip6/2606:4700:4700::1111/udp/9002/quic",
169 ];
170
171 well_known_addrs
172 .into_iter()
173 .filter_map(|addr_str| {
174 if let Ok(multiaddr) = addr_str.parse() {
175 if let Ok(words) = self.encode_address(&multiaddr) {
176 Some((words, multiaddr))
177 } else {
178 None
179 }
180 } else {
181 None
182 }
183 })
184 .collect()
185 }
186}
187
188#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
190pub struct CacheStats {
191 pub total_contacts: usize,
193 pub high_quality_contacts: usize,
195 pub verified_contacts: usize,
197 pub last_merge: chrono::DateTime<chrono::Utc>,
199 pub last_cleanup: chrono::DateTime<chrono::Utc>,
201 pub cache_hit_rate: f64,
203 pub average_quality_score: f64,
205}
206
207fn home_cache_dir() -> Result<PathBuf> {
209 let home = std::env::var("HOME")
210 .or_else(|_| std::env::var("USERPROFILE"))
211 .map_err(|_| P2PError::Bootstrap("Unable to determine home directory".to_string()))?;
212
213 let cache_dir = PathBuf::from(home).join(DEFAULT_CACHE_DIR);
214
215 std::fs::create_dir_all(&cache_dir)
217 .map_err(|e| P2PError::Bootstrap(format!("Failed to create cache directory: {}", e)))?;
218
219 Ok(cache_dir)
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225 use tempfile::TempDir;
226
227 #[tokio::test]
228 async fn test_bootstrap_manager_creation() {
229 let temp_dir = TempDir::new().unwrap();
230 let config = CacheConfig {
231 cache_dir: temp_dir.path().to_path_buf(),
232 max_contacts: 1000,
233 ..CacheConfig::default()
234 };
235
236 let manager = BootstrapManager::with_config(config).await;
237 assert!(manager.is_ok());
238 }
239
240 #[tokio::test]
241 async fn test_home_cache_dir() {
242 let result = home_cache_dir();
243 assert!(result.is_ok());
244
245 let path = result.unwrap();
246 assert!(path.exists());
247 assert!(path.is_dir());
248 }
249}