zebrad/components/sync/status.rs
1//! Syncer chain tip status, based on recent block locator responses from peers.
2
3use tokio::sync::watch;
4use zebra_chain::chain_sync_status::ChainSyncStatus;
5
6use super::RecentSyncLengths;
7
8#[cfg(any(test, feature = "proptest-impl"))]
9pub mod mock;
10#[cfg(test)]
11mod tests;
12
13/// A helper type to determine if the synchronizer has likely reached the chain tip.
14///
15/// This type can be used as a handle, so cloning it is cheap.
16#[derive(Clone, Debug)]
17pub struct SyncStatus {
18 latest_sync_length: watch::Receiver<Vec<usize>>,
19}
20
21impl SyncStatus {
22 /// The threshold that determines if the synchronization is at the chain
23 /// tip.
24 ///
25 /// This is based on the fact that sync lengths are around 2-20 blocks long
26 /// once Zebra reaches the tip.
27 const MIN_DIST_FROM_TIP: usize = 20;
28
29 /// Create an instance of [`SyncStatus`].
30 ///
31 /// The status is determined based on the latest counts of synchronized blocks, observed
32 /// through `latest_sync_length`.
33 pub fn new() -> (Self, RecentSyncLengths) {
34 let (recent_sync_lengths, latest_sync_length) = RecentSyncLengths::new();
35 let status = SyncStatus { latest_sync_length };
36
37 (status, recent_sync_lengths)
38 }
39
40 /// Wait until the synchronization is likely close to the tip.
41 ///
42 /// Returns an error if communication with the synchronizer is lost.
43 pub async fn wait_until_close_to_tip(&mut self) -> Result<(), watch::error::RecvError> {
44 while !self.is_close_to_tip() {
45 self.latest_sync_length.changed().await?;
46 }
47
48 Ok(())
49 }
50}
51
52impl ChainSyncStatus for SyncStatus {
53 /// Check if the synchronization is likely close to the chain tip.
54 fn is_close_to_tip(&self) -> bool {
55 let sync_lengths = self.latest_sync_length.borrow();
56
57 // Return early if sync_lengths is empty.
58 if sync_lengths.is_empty() {
59 return false;
60 }
61
62 // Compute the sum of the `sync_lengths`.
63 // The sum is computed by saturating addition in order to avoid overflowing.
64 let sum = sync_lengths
65 .iter()
66 .fold(0usize, |sum, rhs| sum.saturating_add(*rhs));
67
68 // Compute the average sync length.
69 // This value effectively represents a simple moving average.
70 let avg = sum / sync_lengths.len();
71
72 // The synchronization process is close to the chain tip once the
73 // average sync length falls below the threshold.
74 avg < Self::MIN_DIST_FROM_TIP
75 }
76}