p2p_foundation/bootstrap/
contact.rs1use crate::PeerId;
7use std::time::Duration;
8use std::collections::HashMap;
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
13pub struct ContactEntry {
14 pub peer_id: PeerId,
16 pub addresses: Vec<String>,
18 pub last_seen: chrono::DateTime<chrono::Utc>,
20 pub quality_metrics: QualityMetrics,
22 pub capabilities: Vec<String>,
24 pub ipv6_identity_verified: bool,
26 pub reputation_score: f64,
28 pub connection_history: ConnectionHistory,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
34pub struct QualityMetrics {
35 pub success_rate: f64,
37 pub avg_latency_ms: f64,
39 pub quality_score: f64,
41 pub last_connection_attempt: chrono::DateTime<chrono::Utc>,
43 pub last_successful_connection: chrono::DateTime<chrono::Utc>,
45 pub uptime_score: f64,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
51pub struct ConnectionHistory {
52 pub total_attempts: u64,
54 pub successful_connections: u64,
56 pub failed_connections: u64,
58 pub total_session_time: Duration,
60 pub recent_latencies: Vec<u64>,
62 pub connection_failures: HashMap<String, u64>,
64}
65
66impl ContactEntry {
67 pub fn new(peer_id: PeerId, addresses: Vec<String>) -> Self {
69 let now = chrono::Utc::now();
70
71 Self {
72 peer_id,
73 addresses,
74 last_seen: now,
75 quality_metrics: QualityMetrics::new(),
76 capabilities: Vec::new(),
77 ipv6_identity_verified: false,
78 reputation_score: 0.5, connection_history: ConnectionHistory::new(),
80 }
81 }
82
83 pub fn update_connection_result(&mut self, success: bool, latency_ms: Option<u64>, error: Option<String>) {
85 let now = chrono::Utc::now();
86 self.last_seen = now;
87 self.quality_metrics.last_connection_attempt = now;
88 self.connection_history.total_attempts += 1;
89
90 if success {
91 self.connection_history.successful_connections += 1;
92 self.quality_metrics.last_successful_connection = now;
93
94 if let Some(latency) = latency_ms {
95 self.add_latency_measurement(latency);
96 }
97 } else {
98 self.connection_history.failed_connections += 1;
99
100 if let Some(err) = error {
101 *self.connection_history.connection_failures.entry(err).or_insert(0) += 1;
102 }
103 }
104
105 self.update_success_rate();
106 self.update_latency_average();
107 self.recalculate_quality_score();
108 }
109
110 fn add_latency_measurement(&mut self, latency_ms: u64) {
112 self.connection_history.recent_latencies.push(latency_ms);
113
114 if self.connection_history.recent_latencies.len() > 10 {
116 self.connection_history.recent_latencies.remove(0);
117 }
118 }
119
120 pub fn update_success_rate(&mut self) {
122 if self.connection_history.total_attempts > 0 {
123 self.quality_metrics.success_rate =
124 self.connection_history.successful_connections as f64 /
125 self.connection_history.total_attempts as f64;
126 }
127 }
128
129 fn update_latency_average(&mut self) {
131 if !self.connection_history.recent_latencies.is_empty() {
132 let sum: u64 = self.connection_history.recent_latencies.iter().sum();
133 self.quality_metrics.avg_latency_ms = sum as f64 / self.connection_history.recent_latencies.len() as f64;
134 }
135 }
136
137 pub fn recalculate_quality_score(&mut self) {
139 let quality_calculator = QualityCalculator::new();
140 self.quality_metrics.quality_score = quality_calculator.calculate_quality(self);
141 }
142
143 pub fn update_capabilities(&mut self, capabilities: Vec<String>) {
145 self.capabilities = capabilities;
146 self.recalculate_quality_score();
147 }
148
149 pub fn update_reputation(&mut self, reputation: f64) {
151 self.reputation_score = reputation.clamp(0.0, 1.0);
152 self.recalculate_quality_score();
153 }
154
155 pub fn mark_ipv6_verified(&mut self) {
157 self.ipv6_identity_verified = true;
158 self.recalculate_quality_score();
159 }
160
161 pub fn is_stale(&self, max_age: Duration) -> bool {
163 let now = chrono::Utc::now();
164 let age = now.signed_duration_since(self.last_seen);
165 age.to_std().unwrap_or(Duration::MAX) > max_age
166 }
167
168 pub fn age_seconds(&self) -> u64 {
170 let now = chrono::Utc::now();
171 let age = now.signed_duration_since(self.last_seen);
172 age.num_seconds().max(0) as u64
173 }
174
175 pub fn has_capability(&self, capability: &str) -> bool {
177 self.capabilities.contains(&capability.to_string())
178 }
179
180 pub fn summary(&self) -> String {
182 format!(
183 "Peer {} (Quality: {:.2}, Success: {:.1}%, Latency: {:.0}ms, Verified: {})",
184 self.peer_id.to_string().chars().take(8).collect::<String>(),
185 self.quality_metrics.quality_score,
186 self.quality_metrics.success_rate * 100.0,
187 self.quality_metrics.avg_latency_ms,
188 self.ipv6_identity_verified
189 )
190 }
191}
192
193impl QualityMetrics {
194 pub fn new() -> Self {
196 let now = chrono::Utc::now();
197
198 Self {
199 success_rate: 0.0,
200 avg_latency_ms: 0.0,
201 quality_score: 0.0,
202 last_connection_attempt: now,
203 last_successful_connection: now,
204 uptime_score: 0.5, }
206 }
207
208 pub fn apply_age_decay(&mut self, decay_factor: f64) {
210 self.quality_score *= decay_factor;
212 self.uptime_score *= decay_factor;
213 }
214}
215
216impl ConnectionHistory {
217 pub fn new() -> Self {
219 Self {
220 total_attempts: 0,
221 successful_connections: 0,
222 failed_connections: 0,
223 total_session_time: Duration::from_secs(0),
224 recent_latencies: Vec::new(),
225 connection_failures: HashMap::new(),
226 }
227 }
228
229 pub fn add_session_time(&mut self, duration: Duration) {
231 self.total_session_time = self.total_session_time.saturating_add(duration);
232 }
233
234 pub fn get_failure_rate(&self, error_type: &str) -> f64 {
236 let failures = self.connection_failures.get(error_type).copied().unwrap_or(0);
237 if self.total_attempts > 0 {
238 failures as f64 / self.total_attempts as f64
239 } else {
240 0.0
241 }
242 }
243}
244
245pub struct QualityCalculator {
247 success_weight: f64,
248 latency_weight: f64,
249 recency_weight: f64,
250 reputation_weight: f64,
251 verification_bonus: f64,
252 capability_bonus: f64,
253}
254
255impl QualityCalculator {
256 pub fn new() -> Self {
258 Self {
259 success_weight: 0.40, latency_weight: 0.30, recency_weight: 0.20, reputation_weight: 0.10, verification_bonus: 0.05, capability_bonus: 0.02, }
266 }
267
268 pub fn calculate_quality(&self, contact: &ContactEntry) -> f64 {
270 let mut score = 0.0;
271
272 let success_component = contact.quality_metrics.success_rate * self.success_weight;
274 score += success_component;
275
276 let latency_component = if contact.quality_metrics.avg_latency_ms > 0.0 {
278 let normalized_latency = (1000.0 / (contact.quality_metrics.avg_latency_ms + 100.0)).min(1.0);
279 normalized_latency * self.latency_weight
280 } else {
281 0.0
282 };
283 score += latency_component;
284
285 let age_seconds = contact.age_seconds() as f64;
287 let recency_component = (-age_seconds / 86400.0).exp() * self.recency_weight; score += recency_component;
289
290 let reputation_component = contact.reputation_score * self.reputation_weight;
292 score += reputation_component;
293
294 if contact.ipv6_identity_verified {
296 score += self.verification_bonus;
297 }
298
299 let important_capabilities = ["dht", "mcp", "relay"];
301 let capability_count = important_capabilities.iter()
302 .filter(|&cap| contact.has_capability(cap))
303 .count();
304 score += capability_count as f64 * self.capability_bonus;
305
306 score.clamp(0.0, 1.0)
308 }
309
310 pub fn calculate_with_weights(
312 &self,
313 contact: &ContactEntry,
314 success_weight: f64,
315 latency_weight: f64,
316 recency_weight: f64,
317 reputation_weight: f64,
318 ) -> f64 {
319 let mut calculator = self.clone();
320 calculator.success_weight = success_weight;
321 calculator.latency_weight = latency_weight;
322 calculator.recency_weight = recency_weight;
323 calculator.reputation_weight = reputation_weight;
324
325 calculator.calculate_quality(contact)
326 }
327}
328
329impl Clone for QualityCalculator {
330 fn clone(&self) -> Self {
331 Self {
332 success_weight: self.success_weight,
333 latency_weight: self.latency_weight,
334 recency_weight: self.recency_weight,
335 reputation_weight: self.reputation_weight,
336 verification_bonus: self.verification_bonus,
337 capability_bonus: self.capability_bonus,
338 }
339 }
340}
341
342impl Default for QualityCalculator {
343 fn default() -> Self {
344 Self::new()
345 }
346}
347
348#[cfg(test)]
349mod tests {
350 use super::*;
351
352 #[test]
353 fn test_contact_entry_creation() {
354 let peer_id = PeerId::from("test-peer");
355 let addresses = vec!["/ip4/127.0.0.1/tcp/9000".to_string()];
356
357 let contact = ContactEntry::new(peer_id.clone(), addresses.clone());
358
359 assert_eq!(contact.peer_id, peer_id);
360 assert_eq!(contact.addresses, addresses);
361 assert_eq!(contact.quality_metrics.success_rate, 0.0);
362 assert!(!contact.ipv6_identity_verified);
363 }
364
365 #[test]
366 fn test_quality_calculation() {
367 let mut contact = ContactEntry::new(
368 PeerId::from("test-peer"),
369 vec!["/ip4/127.0.0.1/tcp/9000".to_string()]
370 );
371
372 contact.update_connection_result(true, Some(50), None);
374 contact.update_connection_result(true, Some(60), None);
375 contact.update_connection_result(false, None, Some("timeout".to_string()));
376
377 assert!(contact.quality_metrics.success_rate > 0.5);
378 assert!(contact.quality_metrics.avg_latency_ms > 0.0);
379 assert!(contact.quality_metrics.quality_score > 0.0);
380 }
381
382 #[test]
383 fn test_capability_bonus() {
384 let mut contact = ContactEntry::new(
385 PeerId::from("test-peer"),
386 vec!["/ip4/127.0.0.1/tcp/9000".to_string()]
387 );
388
389 let initial_score = contact.quality_metrics.quality_score;
390
391 contact.update_capabilities(vec!["dht".to_string(), "mcp".to_string()]);
392
393 assert!(contact.quality_metrics.quality_score > initial_score);
394 }
395
396 #[test]
397 fn test_stale_detection() {
398 let mut contact = ContactEntry::new(
399 PeerId::from("test-peer"),
400 vec!["/ip4/127.0.0.1/tcp/9000".to_string()]
401 );
402
403 contact.last_seen = chrono::Utc::now() - chrono::Duration::hours(2);
405
406 assert!(contact.is_stale(Duration::from_secs(3600))); assert!(!contact.is_stale(Duration::from_secs(10800))); }
409
410 #[test]
411 fn test_quality_decay() {
412 let mut metrics = QualityMetrics::new();
413 metrics.quality_score = 0.8;
414 metrics.uptime_score = 0.9;
415
416 metrics.apply_age_decay(0.9);
417
418 assert!(metrics.quality_score < 0.8);
419 assert!(metrics.uptime_score < 0.9);
420 }
421}