1use crate::client::peer_tracker::peer_tracker::{PeerTracker, SharedPeerTracker};
20use crate::protocol::peer::Peer;
21use crate::tools::{config, tools};
22use crate::tools::tools::LeadingAgreementBits;
23use crate::tools::types::Id;
24use log::warn;
25use std::collections::HashSet;
26
27pub struct PeerIterator {
30 tracker: SharedPeerTracker,
31 bucket_location_id: Id,
32 max_iterations_since_high_watermark: usize,
33 peers_already_queried: HashSet<Id>,
34 high_watermark: LeadingAgreementBits,
35 iterations_since_high_watermark: usize,
36 cache_radius: Option<LeadingAgreementBits>,
37}
38
39impl PeerIterator {
40 pub fn new(tracker: SharedPeerTracker, bucket_location_id: Id, max_iterations_since_high_watermark: usize, cache_radius: Option<LeadingAgreementBits>) -> Self {
41 Self {
42 tracker,
43 bucket_location_id,
44 max_iterations_since_high_watermark,
45 peers_already_queried: HashSet::new(),
46 high_watermark: 0,
47 iterations_since_high_watermark: 0,
48 cache_radius,
49 }
50 }
51 pub fn next_peer(&mut self) -> Option<(Peer, LeadingAgreementBits)> {
52 loop {
53 let nearest_peer = self
54 .tracker
55 .read()
56 .peers()
57 .iter()
58 .filter(|peer| !self.peers_already_queried.contains(&peer.id))
59 .map(|peer| (peer, tools::leading_agreement_bits_xor(&self.bucket_location_id.0, &peer.id.0)))
60 .filter(|(_, lab)| self.cache_radius.is_none_or(|r| *lab < r))
61 .max_by_key(|peer| peer.1)
62 .map(|(peer, lab)| (peer.clone(), lab));
63
64 match nearest_peer {
65 Some(nearest_peer) => {
66 self.peers_already_queried.insert(nearest_peer.0.id);
67
68 if nearest_peer.1 > self.high_watermark {
69 self.high_watermark = nearest_peer.1;
70 self.iterations_since_high_watermark = 0;
71 }
72 else {
73 self.iterations_since_high_watermark += 1;
74 if self.iterations_since_high_watermark > self.max_iterations_since_high_watermark {
75 return None;
76 }
77 }
78
79 if let Some(r) = &mut self.cache_radius {
81 *r = (*r + 1).min(256);
82 }
83
84 return Some(nearest_peer);
85 }
86 None => {
87 let any_unvisited = self.tracker.read().peers().iter().any(|p| !self.peers_already_queried.contains(&p.id));
90 if !any_unvisited {
91 return None;
92 }
93 let r = self.cache_radius.as_mut()?;
102 *r = (*r + 1).min(256);
103 }
104 }
105 }
106 }
107
108 pub fn iterations_since_high_watermark(&self) -> usize {
109 self.iterations_since_high_watermark
110 }
111
112 pub fn add_peers(&mut self, peers: Vec<Peer>) {
113 if peers.len() > config::CLIENT_MAX_PEERS_CONSIDERED_PER_RESPONSE {
114 warn!("ignoring {} of {} suggested peers (cap is {})", peers.len() - config::CLIENT_MAX_PEERS_CONSIDERED_PER_RESPONSE, peers.len(), config::CLIENT_MAX_PEERS_CONSIDERED_PER_RESPONSE);
115 }
116
117 let (candidate_peers, current_time_millis) = {
119 let tracker = self.tracker.read();
120 let candidate_peers: Vec<Peer> = peers.into_iter().take(config::CLIENT_MAX_PEERS_CONSIDERED_PER_RESPONSE).filter(|peer| tracker.needs_verification(peer)).collect();
121 (candidate_peers, tracker.current_time_millis())
122 };
123
124 let verified_peers: Vec<Peer> = candidate_peers
125 .into_iter()
126 .filter(|peer| match PeerTracker::verify_candidate(peer, current_time_millis) {
127 Ok(()) => true,
128 Err(e) => {
129 warn!("not adding invalid peer: {}", e);
130 false
131 }
132 })
133 .collect();
134
135 if !verified_peers.is_empty() {
136 let mut tracker = self.tracker.write();
137 for peer in verified_peers {
138 tracker.insert_verified(peer);
139 }
140 }
141 }
142 pub fn remove_peer(&mut self, peer: &Peer) {
143 self.tracker.write().remove_peer(peer);
144 }
145}
146
147pub struct ConvergeToLocationVisitResult {
148 pub done: bool, pub peer_unavailable: bool, pub peers_discovered: Vec<Peer>, }
152
153#[async_trait::async_trait]
154pub trait ConvergeToLocationVisitor: Send + Sync {
155 async fn on_peer(&mut self, peer: &Peer) -> anyhow::Result<ConvergeToLocationVisitResult>;
156}
157
158#[cfg(test)]
159mod tests {
160 use crate::client::client_storage::mem_client_storage::MemClientStorage;
161 use crate::client::peer_tracker::peer_tracker::{PeerTracker, SharedPeerTracker};
162 use std::sync::Arc;
163 use crate::tools::buckets::{BUCKET_DURATIONS, BucketLocation, BucketType, generate_bucket_location};
164 use crate::tools::config;
165 use crate::tools::runtime_services::RuntimeServices;
166 use crate::tools::server_id::ServerId;
167 use crate::tools::time::{DurationMillis, TimeMillis};
168 use crate::tools::types::Id;
169
170 #[tokio::test]
171 async fn converge_basics_test() -> anyhow::Result<()> {
172 let runtime_services = RuntimeServices::default_for_testing();
173 let client_storage = MemClientStorage::new().await?;
174 let peer_tracker: SharedPeerTracker = Arc::new(parking_lot::RwLock::new(PeerTracker::new(runtime_services.clone(), client_storage.clone()).await?));
175
176 const NUM_PEERS: usize = 100;
177
178 {
179 for _ in 0..NUM_PEERS {
180 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?;
181 let peer = server_id.to_peer(runtime_services.time_provider.as_ref())?;
182 peer_tracker.write().add_peer(peer)?;
183 }
184 assert_eq!(NUM_PEERS, peer_tracker.read().len());
185 }
186
187 {
189 let bucket_location = generate_bucket_location(BucketType::User, Id::random(), BUCKET_DURATIONS[0], runtime_services.time_provider.current_time_millis())?;
190 let mut count = 0;
191 let mut peer_iter = PeerTracker::iterate_to_location(&peer_tracker, bucket_location.location_id, usize::MAX, None).await?;
192 while let Some(_peer) = peer_iter.next_peer() {
193 count += 1;
194 }
195 assert_eq!(NUM_PEERS, count);
196 };
197
198 {
200 let bucket_location = generate_bucket_location(BucketType::User, Id::random(), BUCKET_DURATIONS[0], runtime_services.time_provider.current_time_millis())?;
201 let mut count = 0;
202 let mut peer_iter = PeerTracker::iterate_to_location(&peer_tracker, bucket_location.location_id, usize::MAX, None).await?;
203 if peer_iter.next_peer().is_some() {
204 count += 1;
205 }
206 assert_eq!(1, count);
207 };
208
209 {
211 let bucket_location = generate_bucket_location(BucketType::User, Id::random(), BUCKET_DURATIONS[0], runtime_services.time_provider.current_time_millis())?;
212 let mut count = 0;
213 let mut peer_iter = PeerTracker::iterate_to_location(&peer_tracker, bucket_location.location_id, usize::MAX, None).await?;
214 while let Some((peer, _)) = peer_iter.next_peer() {
215 count += 1;
216 if 0 == count % 2 {
217 peer_iter.remove_peer(&peer);
218 }
219 }
220 assert_eq!(NUM_PEERS, count);
221 assert_eq!(NUM_PEERS / 2, peer_tracker.read().len());
222 }
223
224 Ok(())
225 }
226
227 #[tokio::test]
228 async fn converge_termination_test() -> anyhow::Result<()> {
229 let runtime_services = RuntimeServices::default_for_testing();
230 let client_storage = MemClientStorage::new().await?;
231 let peer_tracker: SharedPeerTracker = Arc::new(parking_lot::RwLock::new(PeerTracker::new(runtime_services.clone(), client_storage.clone()).await?));
232
233 const NUM_PEERS: usize = 100;
234
235 {
236 for _ in 0..NUM_PEERS {
237 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?;
238 let peer = server_id.to_peer(runtime_services.time_provider.as_ref())?;
239 peer_tracker.write().add_peer(peer)?;
240 }
241 assert_eq!(NUM_PEERS, peer_tracker.read().len());
242 }
243
244 {
245 let bucket_location = generate_bucket_location(BucketType::User, Id::random(), BUCKET_DURATIONS[0], runtime_services.time_provider.current_time_millis())?;
246 let mut count = 0;
247 let mut peer_iter = PeerTracker::iterate_to_location(&peer_tracker, bucket_location.location_id, 3, None).await?;
248 while let Some(_peer) = peer_iter.next_peer() {
249 count += 1;
250 }
251 assert_eq!(3 + 1, count);
252 }
253
254 Ok(())
255 }
256
257 #[tokio::test]
258 async fn converge_insertions_test() -> anyhow::Result<()> {
259 let runtime_services = RuntimeServices::default_for_testing();
260 let client_storage = MemClientStorage::new().await?;
261 let peer_tracker: SharedPeerTracker = Arc::new(parking_lot::RwLock::new(PeerTracker::new(runtime_services.clone(), client_storage.clone()).await?));
262
263 const NUM_PEERS: usize = 100;
264
265 {
266 for _ in 0..NUM_PEERS {
267 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?;
268 let peer = server_id.to_peer(runtime_services.time_provider.as_ref())?;
269 peer_tracker.write().add_peer(peer)?;
270 }
271 assert_eq!(NUM_PEERS, peer_tracker.read().len());
272 }
273
274 {
276 let bucket_location = generate_bucket_location(BucketType::User, Id::random(), BUCKET_DURATIONS[0], runtime_services.time_provider.current_time_millis())?;
277 let mut count = 0;
278 let mut peer_iter = PeerTracker::iterate_to_location(&peer_tracker, bucket_location.location_id, usize::MAX, None).await?;
279 while let Some(_peer) = peer_iter.next_peer() {
280 count += 1;
281
282 if 0 == count % 10 {
283 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?;
284 let peer = server_id.to_peer(runtime_services.time_provider.as_ref())?;
285 peer_iter.add_peers(vec![peer]);
286 }
287
288 if 50 == count {
289 break;
290 }
291 }
292
293 assert_eq!(50, count);
294 assert_eq!(NUM_PEERS + 5, peer_tracker.read().len());
295 }
296
297 Ok(())
298 }
299
300 #[tokio::test]
301 async fn converge_targeting_test() -> anyhow::Result<()> {
302 let runtime_services = RuntimeServices::default_for_testing();
303 let client_storage = MemClientStorage::new().await?;
304 let peer_tracker: SharedPeerTracker = Arc::new(parking_lot::RwLock::new(PeerTracker::new(runtime_services.clone(), client_storage.clone()).await?));
305
306 const NUM_PEERS: usize = 100;
307
308 {
309 for _ in 0..NUM_PEERS {
310 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?;
311 let peer = server_id.to_peer(runtime_services.time_provider.as_ref())?;
312 peer_tracker.write().add_peer(peer)?;
313 }
314 assert_eq!(NUM_PEERS, peer_tracker.read().len());
315 }
316
317 let target_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?;
319 let target_peer = target_server_id.to_peer(runtime_services.time_provider.as_ref())?;
320
321 {
322 const PEER_DISCOVERY_I: usize = 37usize;
323 const PEER_DISCOVERY_I_PLUS_1: usize = PEER_DISCOVERY_I + 1;
324
325 let bucket_location = {
326 let mut location_id = target_peer.id;
327 for i in 10..31 {
328 location_id.0[i] = 0u8;
329 }
330 BucketLocation {
331 bucket_type: BucketType::User,
332 base_id: location_id,
333 duration: DurationMillis::zero(),
334 bucket_time_millis: TimeMillis::zero(),
335 location_id,
336 }
337 };
338
339 let mut count = 0;
340 let mut peer_iter = PeerTracker::iterate_to_location(&peer_tracker, bucket_location.location_id, usize::MAX, None).await?;
341 while let Some((peer, _)) = peer_iter.next_peer() {
342 count += 1;
343 match count {
344 PEER_DISCOVERY_I => {
345 peer_iter.add_peers(vec![target_peer.clone()]);
346 }
347 PEER_DISCOVERY_I_PLUS_1 => {
348 if peer.id != target_peer.id {
349 anyhow::bail!("peer is not the one we expected");
350 }
351 break;
352 }
353 _ => {}
354 }
355 }
356
357 assert_eq!(PEER_DISCOVERY_I_PLUS_1, count);
358 assert_eq!(NUM_PEERS + 1, peer_tracker.read().len());
359 }
360
361 Ok(())
362 }
363
364 #[tokio::test]
367 async fn converge_cache_radius_test() -> anyhow::Result<()> {
368 let runtime_services = RuntimeServices::default_for_testing();
369 let client_storage = MemClientStorage::new().await?;
370 let peer_tracker: SharedPeerTracker = Arc::new(parking_lot::RwLock::new(PeerTracker::new(runtime_services.clone(), client_storage.clone()).await?));
371
372 let location_id = Id::zero();
373
374 const NUM_PEERS: usize = 100;
375 let mut labs_added: Vec<crate::tools::tools::LeadingAgreementBits> = Vec::new();
376 for _ in 0..NUM_PEERS {
377 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?;
378 let peer = server_id.to_peer(runtime_services.time_provider.as_ref())?;
379 let lab = crate::tools::tools::leading_agreement_bits_xor(&location_id.0, &peer.id.0);
380 labs_added.push(lab);
381 peer_tracker.write().add_peer(peer)?;
382 }
383 assert_eq!(NUM_PEERS, peer_tracker.read().len());
384
385 let mut sorted_labs = labs_added.clone();
386 sorted_labs.sort();
387 let cache_radius = sorted_labs[NUM_PEERS / 2];
388
389 let mut labs_visited: Vec<crate::tools::tools::LeadingAgreementBits> = Vec::new();
390 let mut peer_iter = PeerTracker::iterate_to_location(&peer_tracker, location_id, usize::MAX, Some(cache_radius)).await?;
391 while let Some((_, lab)) = peer_iter.next_peer() {
392 labs_visited.push(lab);
393 }
394
395 assert_eq!(NUM_PEERS, labs_visited.len(), "all peers should be visited");
396
397 let has_outside_peers = labs_added.iter().any(|&lab| lab < cache_radius);
398 if has_outside_peers {
399 assert!(labs_visited[0] < cache_radius, "first peer should be outside the initial cache zone, got lab={} cache_radius={}", labs_visited[0], cache_radius);
400 }
401
402 Ok(())
403 }
404
405 #[tokio::test]
407 async fn two_iterators_interleave() -> anyhow::Result<()> {
408 let runtime_services = RuntimeServices::default_for_testing();
409 let client_storage = MemClientStorage::new().await?;
410 let peer_tracker: SharedPeerTracker = Arc::new(parking_lot::RwLock::new(PeerTracker::new(runtime_services.clone(), client_storage.clone()).await?));
411
412 const NUM_PEERS: usize = 20;
413 for _ in 0..NUM_PEERS {
414 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?;
415 peer_tracker.write().add_peer(server_id.to_peer(runtime_services.time_provider.as_ref())?)?;
416 }
417
418 let mut peer_iter_a = PeerTracker::iterate_to_location(&peer_tracker, Id::random(), usize::MAX, None).await?;
419 let mut peer_iter_b = PeerTracker::iterate_to_location(&peer_tracker, Id::random(), usize::MAX, None).await?;
420
421 let mut visited_by_a = 0;
423 let mut added_by_b = 0;
424 let mut a_done = false;
425 let mut b_done = false;
426 while !(a_done && b_done) {
427 if !a_done {
428 match peer_iter_a.next_peer() {
429 Some((peer, _)) => {
430 visited_by_a += 1;
431 peer_iter_a.remove_peer(&peer);
432 }
433 None => a_done = true,
434 }
435 }
436 if !b_done {
437 match peer_iter_b.next_peer() {
438 Some(_) if added_by_b < 5 => {
439 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?;
440 peer_iter_b.add_peers(vec![server_id.to_peer(runtime_services.time_provider.as_ref())?]);
441 added_by_b += 1;
442 }
443 Some(_) => {}
444 None => b_done = true,
445 }
446 }
447 }
448
449 assert!(visited_by_a >= NUM_PEERS, "A must visit at least the initial peers, visited {}", visited_by_a);
451 assert_eq!(NUM_PEERS + added_by_b - visited_by_a, peer_tracker.read().len());
452 Ok(())
453 }
454
455 #[tokio::test]
457 async fn tracker_unlocked_while_iterator_alive() -> anyhow::Result<()> {
458 let runtime_services = RuntimeServices::default_for_testing();
459 let client_storage = MemClientStorage::new().await?;
460 let peer_tracker: SharedPeerTracker = Arc::new(parking_lot::RwLock::new(PeerTracker::new(runtime_services.clone(), client_storage.clone()).await?));
461 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?;
462 peer_tracker.write().add_peer(server_id.to_peer(runtime_services.time_provider.as_ref())?)?;
463
464 let mut peer_iter = PeerTracker::iterate_to_location(&peer_tracker, Id::random(), usize::MAX, None).await?;
465 assert!(peer_tracker.try_write().is_some(), "no lock may be held after construction");
466 assert!(peer_iter.next_peer().is_some());
467 assert!(peer_tracker.try_write().is_some(), "no lock may be held between steps");
468 Ok(())
469 }
470}