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}