1use crate::anyhow_assert_eq;
27use crate::client::client_storage::client_storage;
28use crate::client::client_storage::client_storage::{ClientStorage, BUCKET_PEER};
29use crate::client::peer_tracker::peer_iterator::PeerIterator;
30use crate::protocol::payload::payload::{BootstrapResponseV1, BootstrapV1, PayloadRequestKind, PayloadResponseKind};
31use crate::protocol::peer::Peer;
32use crate::protocol::rpc;
33use crate::tools::runtime_services::RuntimeServices;
34use crate::tools::time::TimeMillis;
35use crate::tools::tools::LeadingAgreementBits;
36use crate::tools::types::Id;
37use crate::tools::{config, json, tools};
38use log::{info, trace, warn};
39use std::sync::Arc;
40
41pub type SharedPeerTracker = Arc<parking_lot::RwLock<PeerTracker>>;
43
44pub struct PeerTracker {
59 runtime_services: Arc<RuntimeServices>,
60 client_storage: Arc<dyn ClientStorage>,
61 peers_need_flush: bool,
62 peers: Vec<Peer>,
63 max_peers: usize,
64 flush_serialiser: Arc<tokio::sync::Mutex<()>>,
66}
67
68impl PeerTracker {
69 pub async fn new(runtime_services: Arc<RuntimeServices>, client_storage: Arc<dyn ClientStorage>) -> anyhow::Result<Self> {
70 Self::new_with_max_peers(runtime_services, client_storage, config::CLIENT_PEER_TRACKER_MAX_PEERS).await
71 }
72
73 pub async fn new_with_max_peers(runtime_services: Arc<RuntimeServices>, client_storage: Arc<dyn ClientStorage>, max_peers: usize) -> anyhow::Result<Self> {
74 let peers: anyhow::Result<Vec<Peer>> = try {
75 let peers = client_storage::get_struct::<Vec<Peer>>(client_storage.as_ref(), BUCKET_PEER, "peers", runtime_services.time_provider.current_time_millis()).await?;
76 match peers {
77 Some(peers) => {
78 info!("PeerTracker is starting with {} peers", peers.len());
79 trace!("{:?}", peers);
80 peers
81 }
82 None => Vec::new(),
83 }
84 };
85
86 let peers = peers.unwrap_or_else(|e| {
87 warn!("Failed to load peers from storage: {}", e);
88 Vec::new()
89 });
90
91 let current_time_millis = runtime_services.time_provider.current_time_millis();
92 let (peers, peers_were_dropped) = Self::sanitise_loaded_peers(peers, current_time_millis, max_peers);
93
94 Ok(Self {
95 runtime_services,
96 client_storage,
97 peers_need_flush: peers_were_dropped,
98 peers,
99 max_peers,
100 flush_serialiser: Arc::new(tokio::sync::Mutex::new(())),
101 })
102 }
103
104 fn sanitise_loaded_peers(peers: Vec<Peer>, current_time_millis: TimeMillis, max_peers: usize) -> (Vec<Peer>, bool) {
107 let loaded_peer_count = peers.len();
108 let mut peers: Vec<Peer> = peers
109 .into_iter()
110 .filter(|peer| match peer.verify_timestamps(current_time_millis) {
111 Ok(()) => true,
112 Err(e) => {
113 warn!("dropping stored peer: {}", e);
114 false
115 }
116 })
117 .collect();
118
119 if peers.len() > max_peers {
120 peers.sort_by(|a, b| b.score(current_time_millis).total_cmp(&a.score(current_time_millis)));
121 peers.truncate(max_peers);
122 }
123 peers.sort_by_key(|peer| peer.id);
124
125 let peers_were_dropped = peers.len() != loaded_peer_count;
126 (peers, peers_were_dropped)
127 }
128
129 pub fn take_flush_snapshot(&mut self) -> Option<Vec<Peer>> {
131 if !self.peers_need_flush {
132 return None;
133 }
134 self.peers_need_flush = false;
135 Some(self.peers.clone())
136 }
137
138 pub fn mark_needs_flush(&mut self) {
139 self.peers_need_flush = true;
140 }
141
142 pub fn current_time_millis(&self) -> TimeMillis {
143 self.runtime_services.time_provider.current_time_millis()
144 }
145
146 pub fn add_peer(&mut self, peer: Peer) -> anyhow::Result<()> {
149 if !self.needs_verification(&peer) {
150 return Ok(());
151 }
152 Self::verify_candidate(&peer, self.current_time_millis())?;
153 self.insert_verified(peer);
154 Ok(())
155 }
156
157 pub fn needs_verification(&self, peer: &Peer) -> bool {
160 match self.peers.binary_search_by_key(&peer.id, |peer| peer.id) {
161 Ok(i) => peer.timestamp > self.peers[i].timestamp,
162 Err(_) => true,
163 }
164 }
165
166 pub fn verify_candidate(peer: &Peer, current_time_millis: TimeMillis) -> anyhow::Result<()> {
168 if let Err(e) = peer.verify() {
170 anyhow::bail!("peer verification error: {}", e);
171 }
172
173 if peer.pow_initial.pow < config::SERVER_KEY_POW_MIN {
175 anyhow::bail!("peer peer.pow_initial.pow={} < {}", peer.pow_initial.pow, config::SERVER_KEY_POW_MIN);
176 }
177
178 peer.verify_timestamps(current_time_millis)?;
180
181 Ok(())
182 }
183
184 pub fn insert_verified(&mut self, peer: Peer) {
187 match self.peers.binary_search_by_key(&peer.id, |peer| peer.id) {
188 Ok(i) => {
189 if peer.timestamp <= self.peers[i].timestamp {
190 return;
191 }
192 self.peers[i] = peer;
193 }
194 Err(i) => {
195 if self.peers.len() < self.max_peers {
196 self.peers.insert(i, peer);
197 }
198 else {
199 let current_time_millis = self.current_time_millis();
201 let weakest_peer = self.peers.iter().enumerate().map(|(index, existing_peer)| (index, existing_peer.score(current_time_millis))).min_by(|a, b| a.1.total_cmp(&b.1));
202 let Some((weakest_peer_index, weakest_peer_score)) = weakest_peer else { return };
203 if peer.score(current_time_millis) <= weakest_peer_score {
204 return;
205 }
206 self.peers.remove(weakest_peer_index);
207 let insert_index = self.peers.binary_search_by_key(&peer.id, |peer| peer.id).unwrap_or_else(|insert_index| insert_index);
208 self.peers.insert(insert_index, peer);
209 }
210 }
211 }
212
213 self.peers_need_flush = true;
214 }
215
216 pub fn remove_peer(&mut self, peer: &Peer) {
217 if let Ok(i) = self.peers.binary_search_by_key(&peer.id, |peer| peer.id) {
218 self.peers.remove(i);
219 self.peers_need_flush = true;
220 }
221 }
222
223 pub fn is_empty(&self) -> bool {
224 self.peers.is_empty()
225 }
226 pub fn needs_flush(&self) -> bool {
227 self.peers_need_flush
228 }
229 pub fn len(&self) -> usize {
230 self.peers.len()
231 }
232
233 pub fn peers(&self) -> &Vec<Peer> {
234 &self.peers
235 }
236
237 pub async fn flush(peer_tracker: &SharedPeerTracker) -> anyhow::Result<()> {
239 let flush_serialiser = peer_tracker.read().flush_serialiser.clone();
240 let _flush_guard = flush_serialiser.lock().await;
241
242 let (client_storage, current_time_millis, peers) = {
243 let mut peer_tracker = peer_tracker.write();
244 let Some(peers) = peer_tracker.take_flush_snapshot() else { return Ok(()) };
245 (peer_tracker.client_storage.clone(), peer_tracker.current_time_millis(), peers)
246 };
247
248 trace!("Flushing peers to storage");
249 let result = client_storage::put_struct(client_storage.as_ref(), BUCKET_PEER, "peers", &peers, current_time_millis).await;
250 if result.is_err() {
251 peer_tracker.write().mark_needs_flush();
252 }
253 result
254 }
255
256 pub async fn iterate_to_location(peer_tracker: &SharedPeerTracker, bucket_location_id: Id, max_iterations_since_high_watermark: usize, cache_radius: Option<LeadingAgreementBits>) -> anyhow::Result<PeerIterator> {
258 Self::bootstrap(peer_tracker).await?;
259
260 Ok(PeerIterator::new(peer_tracker.clone(), bucket_location_id, max_iterations_since_high_watermark, cache_radius))
261 }
262
263 pub async fn bootstrap(peer_tracker: &SharedPeerTracker) -> anyhow::Result<()> {
266 let runtime_services = {
268 let peer_tracker = peer_tracker.read();
269 if !peer_tracker.is_empty() {
270 return Ok(());
271 }
272 peer_tracker.runtime_services.clone()
273 };
274
275 info!("bootstrapping PeerTracker");
276
277 let mut bootstrap_addresses = runtime_services.transport_factory.get_bootstrap_addresses().await;
279 tools::shuffle(&mut bootstrap_addresses);
280
281 for bootstrap_address in bootstrap_addresses {
283 let try_result: anyhow::Result<()> = try {
284 info!("bootstrapping {}", bootstrap_address);
285
286 let request = json::struct_to_bytes(&BootstrapV1 {})?;
287 let response = rpc::rpc::rpc_server_unknown(&runtime_services, &Id::zero(), &bootstrap_address, PayloadRequestKind::BootstrapV1, request).await?;
288 anyhow_assert_eq!(&PayloadResponseKind::BootstrapResponseV1, &response.response_request_kind);
289 let response = json::bytes_to_struct::<BootstrapResponseV1>(&response.bytes)?;
290 if response.peers_random.len() > config::CLIENT_MAX_PEERS_CONSIDERED_PER_RESPONSE {
291 warn!("ignoring {} of {} bootstrapped peers from {} (cap is {})", response.peers_random.len() - config::CLIENT_MAX_PEERS_CONSIDERED_PER_RESPONSE, response.peers_random.len(), bootstrap_address, config::CLIENT_MAX_PEERS_CONSIDERED_PER_RESPONSE);
292 }
293
294 let current_time_millis = runtime_services.time_provider.current_time_millis();
295 let verified_peers: Vec<Peer> = response
296 .peers_random
297 .into_iter()
298 .take(config::CLIENT_MAX_PEERS_CONSIDERED_PER_RESPONSE)
299 .filter(|peer| match Self::verify_candidate(peer, current_time_millis) {
300 Ok(()) => true,
301 Err(e) => {
302 warn!("problem while adding bootstrapped peer: {}", e);
303 false
304 }
305 })
306 .collect();
307
308 let mut peer_tracker = peer_tracker.write();
309 for peer in verified_peers {
310 peer_tracker.insert_verified(peer);
311 }
312 };
313
314 if let Err(e) = try_result {
315 warn!("problem bootstrapping peer {}: {}", bootstrap_address, e);
316 }
317
318 if !peer_tracker.read().is_empty() {
320 break;
321 }
322 }
323
324 Ok(())
325 }
326}
327
328#[cfg(test)]
329mod tests {
330 use crate::client::client_storage::mem_client_storage::MemClientStorage;
331 use crate::client::peer_tracker::peer_tracker::{PeerTracker, SharedPeerTracker};
332 use crate::tools::config;
333 use crate::tools::runtime_services::RuntimeServices;
334 use crate::tools::server_id::ServerId;
335 use crate::tools::types::Pow;
336
337 #[tokio::test]
338 async fn general_tests() -> anyhow::Result<()> {
339 let runtime_services = RuntimeServices::default_for_testing();
340 let client_storage = MemClientStorage::new().await?;
341 let mut peer_tracker = PeerTracker::new(runtime_services.clone(), client_storage.clone()).await?;
342
343 assert!(peer_tracker.is_empty());
344 assert_eq!(0, peer_tracker.len());
345
346 {
348 loop {
349 let server_id = ServerId::new("own_pow", runtime_services.time_provider.as_ref(), Pow(config::SERVER_KEY_POW_MIN.0 / 2), true, runtime_services.pow_generator.as_ref()).await?;
350 if server_id.pow >= config::SERVER_KEY_POW_MIN {
351 continue;
352 }
353 let peer = server_id.to_peer(runtime_services.time_provider.as_ref())?;
354 let result = peer_tracker.add_peer(peer);
355 assert!(result.is_err());
356 assert_eq!(0, peer_tracker.len());
357 break;
358 }
359 }
360
361 {
363 let server_id = ServerId::new("own_pow", runtime_services.time_provider.as_ref(), config::SERVER_KEY_POW_MIN, true, runtime_services.pow_generator.as_ref()).await?;
364 let peer = server_id.to_peer(runtime_services.time_provider.as_ref())?;
365 let result = peer_tracker.add_peer(peer);
366 assert!(result.is_ok());
367 assert_eq!(1, peer_tracker.len());
368 }
369
370 {
372 let server_id = ServerId::new("own_pow", runtime_services.time_provider.as_ref(), config::SERVER_KEY_POW_MIN, true, runtime_services.pow_generator.as_ref()).await?;
373 let peer = server_id.to_peer(runtime_services.time_provider.as_ref())?;
374 let result = peer_tracker.add_peer(peer.clone());
375 assert!(result.is_ok());
376 assert_eq!(2, peer_tracker.len());
377 let result = peer_tracker.add_peer(peer.clone());
378 assert!(result.is_ok());
379 assert_eq!(2, peer_tracker.len());
380 }
381
382 {
384 let server_id = ServerId::new("own_pow", runtime_services.time_provider.as_ref(), config::SERVER_KEY_POW_MIN, true, runtime_services.pow_generator.as_ref()).await?;
385 let peer = server_id.to_peer(runtime_services.time_provider.as_ref())?;
386 let result = peer_tracker.add_peer(peer.clone());
387 assert!(result.is_ok());
388 assert_eq!(3, peer_tracker.len());
389 peer_tracker.remove_peer(&peer);
390 assert_eq!(2, peer_tracker.len());
391 }
392
393 {
395 let server_id = ServerId::new("own_pow", runtime_services.time_provider.as_ref(), config::SERVER_KEY_POW_MIN, true, runtime_services.pow_generator.as_ref()).await?;
396 let peer = server_id.to_peer(runtime_services.time_provider.as_ref())?;
397 peer_tracker.remove_peer(&peer);
398 assert_eq!(2, peer_tracker.len());
399 }
400
401 Ok(())
402 }
403
404 #[tokio::test]
407 async fn add_peer_rejects_tampered_pow_initial() -> anyhow::Result<()> {
408 let runtime_services = RuntimeServices::default_for_testing();
409 let client_storage = MemClientStorage::new().await?;
410 let mut peer_tracker = PeerTracker::new(runtime_services.clone(), client_storage.clone()).await?;
411
412 let server_id = ServerId::new("own_pow", runtime_services.time_provider.as_ref(), config::SERVER_KEY_POW_MIN, true, runtime_services.pow_generator.as_ref()).await?;
413 let mut peer = server_id.to_peer(runtime_services.time_provider.as_ref())?;
414
415 peer.pow_initial.salt = crate::tools::types::Salt::zero();
416
417 let result = peer_tracker.add_peer(peer);
418 assert!(result.is_err(), "tampered peer should be rejected");
419 assert_eq!(0, peer_tracker.len());
420
421 Ok(())
422 }
423
424 #[cfg(not(target_arch = "wasm32"))]
426 mod bounded_tracker {
427 use super::*;
428
429 use crate::client::client_storage::client_storage::{self, BUCKET_PEER};
430 use crate::client::peer_tracker::peer_iterator::PeerIterator;
431 use crate::protocol::peer::Peer;
432 use crate::tools::time::{TimeMillis, MILLIS_IN_DAY};
433 use crate::tools::time_provider::manual_time_provider::ManualTimeProvider;
434 use crate::tools::types::Id;
435 use crate::tools::pow_generator::single_threaded_pow_generator::SingleThreadedPowGenerator;
436 use crate::transport::mem_transport::MemTransportFactory;
437 use std::sync::Arc;
438
439 const START_TIME_MILLIS: TimeMillis = TimeMillis(1_800_000_000_000);
440
441 fn manual_runtime_services() -> (Arc<RuntimeServices>, Arc<ManualTimeProvider>) {
442 let manual_time_provider = Arc::new(ManualTimeProvider::new(START_TIME_MILLIS));
443 let runtime_services = Arc::new(RuntimeServices {
444 time_provider: manual_time_provider.clone(),
445 transport_factory: MemTransportFactory::default(),
446 pow_generator: Arc::new(SingleThreadedPowGenerator::new()),
447 });
448 (runtime_services, manual_time_provider)
449 }
450
451 async fn make_peer(runtime_services: &RuntimeServices) -> anyhow::Result<(ServerId, Peer)> {
453 let server_id = ServerId::new("own_pow", runtime_services.time_provider.as_ref(), config::SERVER_KEY_POW_MIN, true, runtime_services.pow_generator.as_ref()).await?;
454 let peer = server_id.to_peer(runtime_services.time_provider.as_ref())?;
455 Ok((server_id, peer))
456 }
457
458 fn peer_ids(peer_tracker: &PeerTracker) -> Vec<Id> {
459 peer_tracker.peers().iter().map(|peer| peer.id).collect()
460 }
461
462 #[tokio::test]
463 async fn add_peer_never_exceeds_max_peers() -> anyhow::Result<()> {
464 let runtime_services = RuntimeServices::default_for_testing();
465 let client_storage = MemClientStorage::new().await?;
466 const MAX_PEERS: usize = 4;
467 let mut peer_tracker = PeerTracker::new_with_max_peers(runtime_services.clone(), client_storage, MAX_PEERS).await?;
468
469 for _ in 0..MAX_PEERS + 4 {
470 let (_, peer) = make_peer(&runtime_services).await?;
471 peer_tracker.add_peer(peer)?;
472 assert!(peer_tracker.len() <= MAX_PEERS);
473 }
474 assert_eq!(MAX_PEERS, peer_tracker.len());
475 for window in peer_tracker.peers().windows(2) {
476 assert!(window[0].id < window[1].id, "peers must stay sorted by id");
477 }
478 Ok(())
479 }
480
481 #[tokio::test]
482 async fn full_tracker_evicts_weakest_peer_only_for_a_stronger_newcomer() -> anyhow::Result<()> {
483 let (runtime_services, manual_time_provider) = manual_runtime_services();
484 let client_storage = MemClientStorage::new().await?;
485 let mut peer_tracker = PeerTracker::new_with_max_peers(runtime_services.clone(), client_storage, 2).await?;
486
487 let (_, stale_peer_1) = make_peer(&runtime_services).await?;
489 let (_, stale_peer_2) = make_peer(&runtime_services).await?;
490 manual_time_provider.set_time(START_TIME_MILLIS + MILLIS_IN_DAY.const_mul(90));
492 let (_, fresh_peer) = make_peer(&runtime_services).await?;
493 let (_, other_fresh_peer) = make_peer(&runtime_services).await?;
494 let now = runtime_services.time_provider.current_time_millis();
495 let weakest_fresh_score = fresh_peer.score(now).min(other_fresh_peer.score(now));
496 let strongest_stale_score = stale_peer_1.score(now).max(stale_peer_2.score(now));
497 assert!(weakest_fresh_score > strongest_stale_score, "test setup: fresh peers must out-score stale peers");
498
499 peer_tracker.add_peer(stale_peer_1.clone())?;
501 peer_tracker.add_peer(fresh_peer.clone())?;
502 peer_tracker.add_peer(other_fresh_peer.clone())?;
503 let mut expected_ids = vec![fresh_peer.id, other_fresh_peer.id];
504 expected_ids.sort();
505 assert_eq!(expected_ids, peer_ids(&peer_tracker), "the weakest peer must be evicted for a stronger newcomer");
506
507 peer_tracker.add_peer(stale_peer_2.clone())?;
509 assert_eq!(expected_ids, peer_ids(&peer_tracker), "a weaker newcomer must be dropped");
510 Ok(())
511 }
512
513 #[tokio::test]
514 async fn add_peer_rejects_far_future_timestamp() -> anyhow::Result<()> {
515 let (runtime_services, _) = manual_runtime_services();
516 let client_storage = MemClientStorage::new().await?;
517 let mut peer_tracker = PeerTracker::new(runtime_services.clone(), client_storage).await?;
518
519 let (server_id, mut peer) = make_peer(&runtime_services).await?;
520 let future_time_provider = ManualTimeProvider::new(START_TIME_MILLIS + config::PEER_MAX_FUTURE_TIMESTAMP_MILLIS.const_mul(2));
521 peer.sign(&future_time_provider, &server_id.keys.signature_key)?;
522 assert!(peer.verify().is_ok(), "test setup: the record itself is validly signed");
523
524 assert!(peer_tracker.add_peer(peer).is_err(), "far-future peer must be rejected");
525 assert_eq!(0, peer_tracker.len());
526 Ok(())
527 }
528
529 #[tokio::test]
530 async fn add_peer_skips_verify_for_known_peer_that_is_not_newer() -> anyhow::Result<()> {
531 let runtime_services = RuntimeServices::default_for_testing();
532 let client_storage = MemClientStorage::new().await?;
533 let mut peer_tracker = PeerTracker::new(runtime_services.clone(), client_storage).await?;
534
535 let (_, peer) = make_peer(&runtime_services).await?;
536 peer_tracker.add_peer(peer.clone())?;
537 assert!(peer_tracker.take_flush_snapshot().is_some());
538 assert!(!peer_tracker.needs_flush());
539
540 let mut tampered_same_timestamp_peer = peer.clone();
542 tampered_same_timestamp_peer.pow_initial.salt = crate::tools::types::Salt::zero();
543 assert!(peer_tracker.add_peer(tampered_same_timestamp_peer).is_ok());
544 assert!(!peer_tracker.needs_flush(), "a non-newer copy must not trigger a flush");
545 assert_eq!(peer, peer_tracker.peers()[0]);
546
547 let mut tampered_newer_peer = peer.clone();
549 tampered_newer_peer.timestamp = TimeMillis(peer.timestamp.0 + 1);
550 assert!(peer_tracker.add_peer(tampered_newer_peer).is_err());
551 assert_eq!(peer, peer_tracker.peers()[0]);
552 Ok(())
553 }
554
555 #[tokio::test]
556 async fn load_drops_far_future_peers_and_trims_to_max_peers() -> anyhow::Result<()> {
557 let (runtime_services, _) = manual_runtime_services();
558 let client_storage = MemClientStorage::new().await?;
559 const MAX_PEERS: usize = 3;
560
561 let mut stored_peers = Vec::new();
562 for _ in 0..MAX_PEERS + 2 {
563 let (_, peer) = make_peer(&runtime_services).await?;
564 stored_peers.push(peer);
565 }
566 let (far_future_server_id, mut far_future_peer) = make_peer(&runtime_services).await?;
567 let future_time_provider = ManualTimeProvider::new(START_TIME_MILLIS + config::PEER_MAX_FUTURE_TIMESTAMP_MILLIS.const_mul(2));
568 far_future_peer.sign(&future_time_provider, &far_future_server_id.keys.signature_key)?;
569 stored_peers.push(far_future_peer.clone());
570
571 let now = runtime_services.time_provider.current_time_millis();
572 client_storage::put_struct(client_storage.as_ref(), BUCKET_PEER, "peers", &stored_peers, now).await?;
573
574 let peer_tracker = PeerTracker::new_with_max_peers(runtime_services.clone(), client_storage, MAX_PEERS).await?;
575 assert_eq!(MAX_PEERS, peer_tracker.len());
576 assert!(!peer_ids(&peer_tracker).contains(&far_future_peer.id), "far-future stored peer must be dropped");
577 assert!(peer_tracker.needs_flush(), "trimmed set must be persisted");
578 for window in peer_tracker.peers().windows(2) {
579 assert!(window[0].id < window[1].id, "loaded peers must be sorted by id");
580 }
581 Ok(())
582 }
583
584 #[tokio::test]
585 async fn add_peers_considers_at_most_the_per_response_cap() -> anyhow::Result<()> {
586 let runtime_services = RuntimeServices::default_for_testing();
587 let client_storage = MemClientStorage::new().await?;
588 let peer_tracker: SharedPeerTracker = Arc::new(parking_lot::RwLock::new(PeerTracker::new(runtime_services.clone(), client_storage).await?));
589
590 let mut response_peers = Vec::new();
591 for _ in 0..config::CLIENT_MAX_PEERS_CONSIDERED_PER_RESPONSE + 3 {
592 let (_, peer) = make_peer(&runtime_services).await?;
593 response_peers.push(peer);
594 }
595
596 let mut peer_iterator = PeerIterator::new(peer_tracker.clone(), Id::random(), 1, None);
597 peer_iterator.add_peers(response_peers);
598 assert_eq!(config::CLIENT_MAX_PEERS_CONSIDERED_PER_RESPONSE, peer_tracker.read().len());
599 Ok(())
600 }
601
602 #[tokio::test]
603 async fn insert_verified_rechecks_timestamp() -> anyhow::Result<()> {
604 let (runtime_services, manual_time_provider) = manual_runtime_services();
605 let client_storage = MemClientStorage::new().await?;
606 let mut peer_tracker = PeerTracker::new(runtime_services.clone(), client_storage).await?;
607
608 let (server_id, older_peer) = make_peer(&runtime_services).await?;
609 manual_time_provider.set_time(START_TIME_MILLIS + MILLIS_IN_DAY);
610 let mut newer_peer = older_peer.clone();
611 newer_peer.sign(runtime_services.time_provider.as_ref(), &server_id.keys.signature_key)?;
612 assert!(newer_peer.timestamp > older_peer.timestamp, "test setup: the re-signed copy must be newer");
613
614 peer_tracker.insert_verified(newer_peer.clone());
616 peer_tracker.insert_verified(older_peer);
617 assert_eq!(vec![newer_peer], *peer_tracker.peers());
618 Ok(())
619 }
620
621 #[tokio::test]
622 async fn needs_verification_skips_known_peers_that_are_not_newer() -> anyhow::Result<()> {
623 let (runtime_services, manual_time_provider) = manual_runtime_services();
624 let client_storage = MemClientStorage::new().await?;
625 let mut peer_tracker = PeerTracker::new(runtime_services.clone(), client_storage).await?;
626
627 let (server_id, peer) = make_peer(&runtime_services).await?;
628 assert!(peer_tracker.needs_verification(&peer), "an unknown peer needs verifying");
629 peer_tracker.add_peer(peer.clone())?;
630 assert!(!peer_tracker.needs_verification(&peer), "the same copy does not");
631
632 manual_time_provider.set_time(START_TIME_MILLIS + MILLIS_IN_DAY);
633 let mut newer_peer = peer.clone();
634 newer_peer.sign(runtime_services.time_provider.as_ref(), &server_id.keys.signature_key)?;
635 assert!(peer_tracker.needs_verification(&newer_peer), "a newer copy does");
636 Ok(())
637 }
638
639 #[tokio::test]
640 async fn flush_snapshot_clears_and_mark_restores_flag() -> anyhow::Result<()> {
641 let runtime_services = RuntimeServices::default_for_testing();
642 let client_storage = MemClientStorage::new().await?;
643 let mut peer_tracker = PeerTracker::new(runtime_services.clone(), client_storage).await?;
644 assert!(peer_tracker.take_flush_snapshot().is_none(), "nothing to flush yet");
645
646 let (_, peer) = make_peer(&runtime_services).await?;
647 peer_tracker.add_peer(peer.clone())?;
648 assert_eq!(Some(vec![peer]), peer_tracker.take_flush_snapshot());
649 assert!(!peer_tracker.needs_flush());
650 assert!(peer_tracker.take_flush_snapshot().is_none());
651
652 peer_tracker.mark_needs_flush();
653 assert!(peer_tracker.needs_flush());
654 Ok(())
655 }
656
657 #[tokio::test]
658 async fn shared_flush_persists_peers() -> anyhow::Result<()> {
659 let runtime_services = RuntimeServices::default_for_testing();
660 let client_storage = MemClientStorage::new().await?;
661 let peer_tracker: SharedPeerTracker = Arc::new(parking_lot::RwLock::new(PeerTracker::new(runtime_services.clone(), client_storage.clone()).await?));
662
663 for _ in 0..3 {
664 let (_, peer) = make_peer(&runtime_services).await?;
665 peer_tracker.write().add_peer(peer)?;
666 }
667 PeerTracker::flush(&peer_tracker).await?;
668 assert!(!peer_tracker.read().needs_flush());
669
670 let reloaded_peer_tracker = PeerTracker::new(runtime_services.clone(), client_storage).await?;
671 assert_eq!(*peer_tracker.read().peers(), *reloaded_peer_tracker.peers());
672 Ok(())
673 }
674 }
675
676 #[tokio::test]
679 async fn add_peer_keeps_peers_sorted_by_id() -> anyhow::Result<()> {
680 let runtime_services = RuntimeServices::default_for_testing();
681 let client_storage = MemClientStorage::new().await?;
682 let mut peer_tracker = PeerTracker::new(runtime_services.clone(), client_storage.clone()).await?;
683
684 const NUM_PEERS: usize = 30;
685 for _ in 0..NUM_PEERS {
686 let server_id = ServerId::new("own_pow", runtime_services.time_provider.as_ref(), config::SERVER_KEY_POW_MIN, true, runtime_services.pow_generator.as_ref()).await?;
687 let peer = server_id.to_peer(runtime_services.time_provider.as_ref())?;
688 peer_tracker.add_peer(peer)?;
689 }
690
691 let peers = peer_tracker.peers();
692 assert_eq!(NUM_PEERS, peers.len());
693 for window in peers.windows(2) {
694 assert!(window[0].id < window[1].id, "peers must be sorted by id; got {} then {}", window[0].id, window[1].id);
695 }
696
697 Ok(())
698 }
699}