1use crate::{
13 DialLimit, Error,
14 engine::Engine,
15 model::{OverlayPeerStatus, OverlayStatus},
16 peer::{PeerSource, SyncPeers},
17 peer_inventory::{placed, selected},
18 peer_protocol::Placement,
19 recorded::{RecordedError, unix_seconds},
20};
21use libp2p::{Multiaddr, PeerId};
22use range::{Range, accept, open};
23use std::{
24 sync::{
25 Arc, Mutex,
26 atomic::{AtomicBool, AtomicU32, AtomicU64, AtomicUsize, Ordering},
27 },
28 time::{Duration, Instant},
29};
30
31mod range;
32
33pub type ConnectedPeers = Arc<dyn Fn() -> u64 + Send + Sync>;
34pub type Listeners = Arc<dyn Fn() -> Vec<Multiaddr> + Send + Sync>;
35
36pub struct OverlayInfo {
38 pub peer_id: String,
39 pub listen: Listeners,
40 pub announce: Vec<String>,
41 pub peer_book: u64,
42 pub connected: ConnectedPeers,
43}
44
45pub struct PeerSync {
47 pub peers: SyncPeers,
48 pub info: OverlayInfo,
49}
50
51struct Peer {
52 label: String,
53 id: String,
54 peer_id: PeerId,
55 source: PeerSource,
56 range: Mutex<Option<Range>>,
57 last_request: Mutex<Option<Instant>>,
58 last_error: Mutex<Option<RecordedError>>,
59 backoff: Mutex<Backoff>,
60 busy_turns: AtomicU32,
61 idle_turns: AtomicU32,
62 last_progress: Mutex<Option<u64>>,
63 connected_since: Mutex<Option<u64>>,
65}
66
67struct Backoff {
70 failures: u32,
71 until: Option<u64>,
72}
73
74impl Peer {
75 fn backoff_until(&self, now: u64) -> Result<Option<u64>, Error> {
76 let backoff = self.backoff.lock().map_err(|_| poisoned())?;
77 Ok(backoff.until.filter(|until| *until > now))
78 }
79
80 fn last_progress(&self) -> Result<Option<u64>, Error> {
81 Ok(*self.last_progress.lock().map_err(|_| poisoned())?)
82 }
83
84 fn hold_off(&self, now: u64, seconds: u64) -> Result<(), Error> {
87 let until = now.checked_add(seconds).unwrap_or(u64::MAX);
88 let mut backoff = self.backoff.lock().map_err(|_| poisoned())?;
89 backoff.until = Some(backoff.until.map_or(until, |current| current.max(until)));
90 Ok(())
91 }
92
93 fn wait_closed(&self, stop: &AtomicBool, deadline: Instant) -> bool {
96 let until = deadline.min(Instant::now() + Scheduler::EVICT_WAIT);
97 while self.source.connected() {
98 if stop.load(Ordering::Acquire) || Instant::now() >= until {
99 return false;
100 }
101 std::thread::sleep(Duration::from_millis(25));
102 }
103 true
104 }
105}
106
107enum Pause {
110 Done,
111 Abandon,
112 Error(Error),
113 Blame(Error),
114}
115
116enum Turn {
119 Quiet,
120 Busy(Error),
121 Retry(Error),
122 Blamed(Error),
123}
124
125fn blame(error: Error) -> Pause {
128 match error {
129 Error::Busy(_)
130 | Error::PeerBusy
131 | Error::Transport(_)
132 | Error::DialDenied(_)
133 | Error::Stale => Pause::Error(error),
134 other => Pause::Blame(other),
135 }
136}
137
138impl From<Error> for Pause {
139 fn from(error: Error) -> Self {
140 Pause::Error(error)
141 }
142}
143
144struct Round<'a> {
148 stop: &'a AtomicBool,
149 deadline: Instant,
150 fetched: usize,
151 requested_page: bool,
152 contacted: bool,
153 progressed: bool,
154}
155
156impl Round<'_> {
157 fn open(&self) -> Result<(), Pause> {
158 if self.stop.load(Ordering::Acquire) || Instant::now() >= self.deadline {
159 return Err(Pause::Done);
160 }
161 Ok(())
162 }
163}
164
165pub(crate) struct Scheduler {
166 offered: SyncPeers,
167 peers: Mutex<Vec<Arc<Peer>>>,
168 turn: AtomicUsize,
169 configured_turn: AtomicBool,
170 configured_next: AtomicUsize,
171 info: OverlayInfo,
172 admitted: AtomicU64,
173 rejected: AtomicU64,
174 penalized: Mutex<Vec<PeerId>>,
175 steps: AtomicU64,
176}
177
178fn poisoned() -> Error {
179 Error::Invalid("peer synchronization state poisoned".into())
180}
181
182fn rotate(
184 peers: &[Arc<Peer>],
185 turn: &AtomicUsize,
186 omitting: &[String],
187 now: u64,
188) -> Result<Option<Arc<Peer>>, Error> {
189 for _ in 0..peers.len() {
190 let peer = &peers[turn.fetch_add(1, Ordering::Relaxed) % peers.len()];
191 if !omitting.contains(&peer.id) && peer.backoff_until(now)?.is_none() {
192 return Ok(Some(Arc::clone(peer)));
193 }
194 }
195 Ok(None)
196}
197
198fn configured(peer: &Arc<Peer>) -> bool {
199 peer.source.configured()
200}
201
202fn evictable(peer: &Peer, step: u64, now: u64) -> Result<bool, Error> {
206 let settled = peer
207 .connected_since
208 .lock()
209 .map_err(|_| poisoned())?
210 .is_some_and(|since| step >= since.saturating_add(2));
211 let idle = peer
212 .last_progress()?
213 .is_none_or(|at| now >= at.saturating_add(Scheduler::IDLE_BACKOFF));
214 Ok(settled && idle && peer.source.outbound())
215}
216
217fn inventory_peer(label: String, source: PeerSource) -> Result<Peer, Error> {
218 let peer_id = match label.parse::<PeerId>() {
219 Ok(id) => id,
220 Err(error) => {
221 return Err(Error::Invalid(format!("inventory peer label: {error}")));
222 }
223 };
224 Ok(Peer {
225 label,
226 id: peer_id.to_string(),
227 peer_id,
228 source,
229 range: Mutex::new(None),
230 last_request: Mutex::new(None),
231 last_error: Mutex::new(None),
232 backoff: Mutex::new(Backoff {
233 failures: 0,
234 until: None,
235 }),
236 busy_turns: AtomicU32::new(0),
237 idle_turns: AtomicU32::new(0),
238 last_progress: Mutex::new(None),
239 connected_since: Mutex::new(None),
240 })
241}
242
243impl Scheduler {
244 pub const RANGE_HEIGHTS: u64 = 4096;
246 pub const PAGE_ENTRIES: u32 = 512;
248 pub const ROUND_FETCHES: usize = 16;
250 pub const ROUND_TIME: Duration = Duration::from_secs(20);
252 pub const REQUEST_SPACING: Duration = Duration::from_millis(250);
254 pub const BACKOFF_SECONDS: u64 = 30;
256 pub const BACKOFF_CAP: u64 = 86_400;
257 pub const BUSY_TURNS: u32 = 10;
259 pub const MISSING_ANSWERS: u32 = 2;
261 pub const IDLE_TURNS: u32 = 3;
266 pub const IDLE_BACKOFF: u64 = 60;
267 pub const EVICT_WAIT: Duration = Duration::from_secs(2);
269 const MAX_PEERS: usize = 1024;
270
271 pub(crate) fn new(sync: PeerSync) -> Result<Self, Error> {
273 let scheduler = Self {
274 offered: sync.peers,
275 peers: Mutex::new(Vec::new()),
276 turn: AtomicUsize::new(0),
277 configured_turn: AtomicBool::new(false),
278 configured_next: AtomicUsize::new(0),
279 info: sync.info,
280 admitted: AtomicU64::new(0),
281 rejected: AtomicU64::new(0),
282 penalized: Mutex::new(Vec::new()),
283 steps: AtomicU64::new(0),
284 };
285 scheduler.refresh(scheduler.offered.sources(&[]), unix_seconds()?)?;
286 Ok(scheduler)
287 }
288
289 pub(crate) fn step(&self, engine: &Engine, stop: &AtomicBool) -> Result<(), Error> {
293 let omitting = engine.omitting_peers()?;
294 let now = unix_seconds()?;
295 self.refresh(self.offered.sources(&omitting), now)?;
296 let step = self.steps.fetch_add(1, Ordering::Relaxed);
297 self.observe_connections(step)?;
298 let Some(peer) = self.next_peer(&omitting, now)? else {
299 return Ok(());
300 };
301 let deadline = Instant::now() + Self::ROUND_TIME;
302 let denied = self.turn(&peer, engine, stop, deadline, now)?;
303 if denied && self.free_slot_for(&peer, step, stop, deadline, now)? {
304 self.turn(&peer, engine, stop, deadline, now)?;
305 }
306 Ok(())
307 }
308
309 fn turn(
312 &self,
313 peer: &Peer,
314 engine: &Engine,
315 stop: &AtomicBool,
316 deadline: Instant,
317 now: u64,
318 ) -> Result<bool, Error> {
319 let mut round = Round {
320 stop,
321 deadline,
322 fetched: 0,
323 requested_page: false,
324 contacted: false,
325 progressed: false,
326 };
327 let outcome = self.advance(peer, engine, &mut round);
328 if stop.load(Ordering::Acquire) {
329 return Ok(false);
330 }
331 self.track_progress(peer, &outcome, &round, now)?;
332 let denied = matches!(
333 &outcome,
334 Ok(Turn::Retry(Error::DialDenied(DialLimit::Established)))
335 );
336 let result = self.judge(peer, outcome, now)?;
337 *peer.last_error.lock().map_err(|_| poisoned())? = RecordedError::from_result(&result);
338 Ok(denied)
339 }
340
341 fn observe_connections(&self, step: u64) -> Result<(), Error> {
343 for peer in self.snapshot()? {
344 let mut since = peer.connected_since.lock().map_err(|_| poisoned())?;
345 *since = match (peer.source.connected(), *since) {
346 (true, Some(first)) => Some(first),
347 (true, None) => Some(step),
348 (false, _gone) => None,
349 };
350 }
351 Ok(())
352 }
353
354 pub(crate) fn backed_off(&self, now: u64) -> Result<Vec<String>, Error> {
356 let mut skipped = Vec::new();
357 for peer in self.snapshot()? {
358 if peer.backoff_until(now)?.is_some() {
359 skipped.push(peer.id.clone());
360 }
361 }
362 Ok(skipped)
363 }
364
365 fn track_progress(
370 &self,
371 peer: &Peer,
372 outcome: &Result<Turn, Error>,
373 round: &Round,
374 now: u64,
375 ) -> Result<(), Error> {
376 if round.progressed {
377 peer.idle_turns.store(0, Ordering::Relaxed);
378 *peer.last_progress.lock().map_err(|_| poisoned())? = Some(now);
379 return Ok(());
380 }
381 if !round.contacted || !matches!(outcome, Ok(Turn::Quiet)) {
382 return Ok(());
383 }
384 let idle = peer.idle_turns.fetch_add(1, Ordering::Relaxed) + 1;
385 if idle < Self::IDLE_TURNS {
386 return Ok(());
387 }
388 peer.idle_turns.store(0, Ordering::Relaxed);
389 peer.source.disconnect();
390 peer.hold_off(now, Self::IDLE_BACKOFF)
391 }
392
393 fn free_slot_for(
403 &self,
404 starved: &Peer,
405 step: u64,
406 stop: &AtomicBool,
407 deadline: Instant,
408 now: u64,
409 ) -> Result<bool, Error> {
410 let mut oldest: Option<(Arc<Peer>, Option<u64>)> = None;
411 for peer in self.snapshot()? {
412 if peer.id == starved.id || !evictable(&peer, step, now)? {
413 continue;
414 }
415 let progress = peer.last_progress()?;
416 if oldest.as_ref().is_none_or(|(_, best)| progress < *best) {
417 oldest = Some((peer, progress));
418 }
419 }
420 let Some((victim, _progress)) = oldest else {
421 return Ok(false);
422 };
423 victim.source.disconnect();
424 victim.hold_off(now, Self::IDLE_BACKOFF)?;
425 Ok(victim.wait_closed(stop, deadline))
426 }
427
428 fn judge(
432 &self,
433 peer: &Peer,
434 outcome: Result<Turn, Error>,
435 now: u64,
436 ) -> Result<Result<(), Error>, Error> {
437 match outcome {
438 Ok(Turn::Quiet) => {
439 peer.busy_turns.store(0, Ordering::Relaxed);
440 peer.backoff.lock().map_err(|_| poisoned())?.failures = 0;
441 Ok(Ok(()))
442 }
443 Ok(Turn::Busy(error)) => {
444 let streak = peer.busy_turns.fetch_add(1, Ordering::Relaxed) + 1;
445 if streak < Self::BUSY_TURNS {
446 return Ok(Err(error));
447 }
448 peer.busy_turns.store(0, Ordering::Relaxed);
449 peer.source.disconnect();
450 Ok(Err(Error::Busy(format!(
451 "{error}; busy for {streak} turns, disconnected"
452 ))))
453 }
454 Ok(Turn::Blamed(error)) => {
455 peer.busy_turns.store(0, Ordering::Relaxed);
456 self.penalize(peer, now)?;
457 Ok(Err(error))
458 }
459 Ok(Turn::Retry(error)) | Err(error) => Ok(Err(error)),
460 }
461 }
462
463 fn penalize(&self, peer: &Peer, now: u64) -> Result<(), Error> {
464 peer.source.disconnect();
465 let mut backoff = peer.backoff.lock().map_err(|_| poisoned())?;
466 backoff.failures = backoff.failures.checked_add(1).unwrap_or(u32::MAX);
467 let delay = 2u64
468 .checked_pow(backoff.failures - 1)
469 .and_then(|factor| factor.checked_mul(Self::BACKOFF_SECONDS))
470 .map_or(Self::BACKOFF_CAP, |delay| delay.min(Self::BACKOFF_CAP));
471 backoff.until = Some(now.checked_add(delay).unwrap_or(u64::MAX));
472 drop(backoff);
473 let mut penalized = self.penalized.lock().map_err(|_| poisoned())?;
474 if !penalized.contains(&peer.peer_id) {
475 penalized.push(peer.peer_id);
476 }
477 Ok(())
478 }
479
480 pub(crate) fn drain_penalized(&self) -> Result<Vec<PeerId>, Error> {
482 let mut penalized = self.penalized.lock().map_err(|_| poisoned())?;
483 Ok(std::mem::take(&mut *penalized))
484 }
485
486 fn refresh(&self, offered: Vec<(String, PeerSource)>, now: u64) -> Result<(), Error> {
491 if offered.len() > Self::MAX_PEERS {
492 return Err(Error::Invalid("too many inventory peers".into()));
493 }
494 let mut peers = self.peers.lock().map_err(|_| poisoned())?;
495 let mut next: Vec<Arc<Peer>> = Vec::with_capacity(offered.len());
496 for (label, source) in offered {
497 let candidate = inventory_peer(label, source)?;
498 match peers.iter().find(|known| known.id == candidate.id) {
499 Some(known) => next.push(Arc::clone(known)),
500 None => next.push(Arc::new(candidate)),
501 }
502 }
503 let mut held = Vec::new();
504 for known in peers.iter() {
505 if !next.iter().any(|kept| kept.id == known.id) && known.backoff_until(now)?.is_some() {
506 held.push(Arc::clone(known));
507 }
508 }
509 let room = Self::MAX_PEERS.saturating_sub(next.len());
511 next.extend(held.split_off(held.len().saturating_sub(room)));
512 *peers = next;
513 Ok(())
514 }
515
516 fn snapshot(&self) -> Result<Vec<Arc<Peer>>, Error> {
517 Ok(self.peers.lock().map_err(|_| poisoned())?.clone())
518 }
519
520 fn next_peer(&self, omitting: &[String], now: u64) -> Result<Option<Arc<Peer>>, Error> {
523 let peers = self.snapshot()?;
524 let (configured, live): (Vec<_>, Vec<_>) = peers.into_iter().partition(configured);
525 let mut groups = [(&configured, &self.configured_next), (&live, &self.turn)];
526 if self.configured_turn.fetch_xor(true, Ordering::Relaxed) {
527 groups.reverse();
528 }
529 for (peers, turn) in groups {
530 if let Some(peer) = rotate(peers, turn, omitting, now)? {
531 return Ok(Some(peer));
532 }
533 }
534 Ok(None)
535 }
536
537 fn advance(&self, peer: &Peer, engine: &Engine, round: &mut Round) -> Result<Turn, Error> {
538 let mut slot = peer.range.lock().map_err(|_| poisoned())?;
539 if slot.is_none() {
540 *slot = open(&peer.id, engine)?;
541 round.progressed |= slot.is_none();
544 }
545 match self.walk(peer, engine, &mut slot, round) {
548 Ok(()) | Err(Pause::Done) => Ok(Turn::Quiet),
549 Err(Pause::Abandon) | Err(Pause::Error(Error::Stale)) => {
550 *slot = None;
551 Ok(Turn::Quiet)
552 }
553 Err(Pause::Error(Error::PeerBusy)) => Ok(Turn::Busy(Error::PeerBusy)),
554 Err(Pause::Error(
555 retry @ (Error::Busy(_) | Error::Transport(_) | Error::DialDenied(_)),
556 )) => Ok(Turn::Retry(retry)),
557 Err(Pause::Blame(error)) => {
558 *slot = None;
559 Ok(Turn::Blamed(error))
560 }
561 Err(Pause::Error(error)) => {
562 *slot = None;
563 Err(error)
564 }
565 }
566 }
567
568 fn walk(
569 &self,
570 peer: &Peer,
571 engine: &Engine,
572 slot: &mut Option<Range>,
573 round: &mut Round,
574 ) -> Result<(), Pause> {
575 while let Some(range) = slot.as_mut() {
576 round.open()?;
577 if let Some(&entry) = range.entries.get(range.next) {
578 if !selected(engine, &entry)? {
579 return Err(Pause::Abandon);
580 }
581 if engine.record(entry.txid)?.is_none() {
582 if round.fetched == Self::ROUND_FETCHES {
583 return Err(Pause::Done);
584 }
585 round.fetched += 1;
586 pace(peer, round)?;
587 let missing = &mut range.missing;
588 round.progressed |=
589 self.acquire(peer, engine, &entry, missing, round.deadline)?;
590 } else {
591 round.progressed = true;
592 }
593 range.next += 1;
594 } else if range.more {
595 if round.requested_page {
596 return Err(Pause::Done);
597 }
598 round.requested_page = true;
599 pace(peer, round)?;
600 let Some(page) = peer
601 .source
602 .inventory_until(
603 (range.from, range.to),
604 range.after,
605 Self::PAGE_ENTRIES,
606 round.stop,
607 round.deadline,
608 )
609 .map_err(blame)?
610 else {
611 return Err(Pause::Done);
612 };
613 if !accept(range, page).map_err(Pause::Blame)? {
614 *slot = None;
615 }
616 } else {
617 let through = range.to.min(range.scanned_through);
618 engine.record_peer_coverage(&peer.id, range.from, through, range.generation)?;
619 round.progressed = true;
620 *slot = None;
621 }
622 }
623 Ok(())
624 }
625
626 fn acquire(
632 &self,
633 peer: &Peer,
634 engine: &Engine,
635 entry: &Placement,
636 missing: &mut u32,
637 deadline: Instant,
638 ) -> Result<bool, Pause> {
639 let fetched = peer
640 .source
641 .fetch_until(entry.txid, deadline)
642 .map_err(blame)?;
643 let Some(package) = fetched else {
644 *missing += 1;
645 if *missing < Self::MISSING_ANSWERS {
646 return Ok(false);
647 }
648 return Err(Pause::Blame(Error::Invalid(
649 "peer reported advertised records missing".into(),
650 )));
651 };
652 *missing = 0;
653 let receipt = match engine.admit_record(package) {
654 Ok(receipt) => receipt,
655 Err(invalid @ Error::Invalid(_)) => {
656 self.rejected.fetch_add(1, Ordering::Relaxed);
657 return Err(Pause::Blame(invalid));
658 }
659 Err(local) => return Err(Pause::Error(local)),
660 };
661 self.admitted.fetch_add(1, Ordering::Relaxed);
662 if !placed(&receipt, entry) {
663 return Err(Pause::Blame(Error::Invalid(
664 "peer inventory placement disagrees with its proven record".into(),
665 )));
666 }
667 Ok(true)
668 }
669
670 pub(crate) fn status(&self, engine: &Engine) -> Result<OverlayStatus, Error> {
671 let start = engine.status()?.application_start.height;
672 let now = unix_seconds()?;
673 let known = self.snapshot()?;
674 let mut peers = Vec::with_capacity(known.len());
675 for peer in &known {
676 let cursor = engine.peer_cursor(&peer.id)?;
677 let synced_through = cursor.checked_sub(1).filter(|height| *height >= start);
678 let recorded = peer.last_error.lock().map_err(|_| poisoned())?.clone();
679 let (last_error, last_error_at) = RecordedError::split(recorded);
680 let mut status = OverlayPeerStatus::default();
681 status.label = peer.label.clone();
682 status.cursor = cursor;
683 status.synced_through = synced_through;
684 status.last_error = last_error;
685 status.last_error_at = last_error_at;
686 status.backoff_until = peer.backoff_until(now)?;
687 status.last_progress_at = peer.last_progress()?;
688 peers.push(status);
689 }
690 let mut status = OverlayStatus::default();
691 status.peer_id = self.info.peer_id.clone();
692 status.listen = (self.info.listen)()
693 .iter()
694 .map(Multiaddr::to_string)
695 .collect();
696 status.announce = self.info.announce.clone();
697 status.peer_book = self.info.peer_book;
698 status.connected = (self.info.connected)();
699 status.admitted = self.admitted.load(Ordering::Relaxed);
700 status.rejected = self.rejected.load(Ordering::Relaxed);
701 status.peers = peers;
702 Ok(status)
703 }
704}
705
706fn pace(peer: &Peer, round: &mut Round) -> Result<(), Pause> {
710 let mut last = peer.last_request.lock().map_err(|_| poisoned())?;
711 if let Some(previous) = *last {
712 let ready = previous + Scheduler::REQUEST_SPACING;
713 while Instant::now() < ready {
714 round.open()?;
715 let wait = ready.saturating_duration_since(Instant::now());
716 std::thread::sleep(wait.min(Duration::from_millis(50)));
717 }
718 }
719 round.open()?;
720 *last = Some(Instant::now());
721 round.contacted = true;
722 Ok(())
723}