Skip to main content

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    is_regtest: bool,
20}
21
22impl SyncStatus {
23    /// The threshold that determines if the synchronization is at the chain
24    /// tip.
25    ///
26    /// This is based on the fact that sync lengths are around 2-20 blocks long
27    /// once Zebra reaches the tip.
28    const MIN_DIST_FROM_TIP: usize = 20;
29
30    /// Create an instance of [`SyncStatus`] for a specific network.
31    ///
32    /// The status is determined based on the latest counts of synchronized blocks, observed
33    /// through `latest_sync_length`. In regtest, [`ChainSyncStatus::is_close_to_tip`] always returns `true`.
34    pub fn new_for_network(
35        network: &zebra_chain::parameters::Network,
36    ) -> (Self, RecentSyncLengths) {
37        let (recent_sync_lengths, latest_sync_length) = RecentSyncLengths::new();
38        let status = SyncStatus {
39            latest_sync_length,
40            is_regtest: network.is_regtest(),
41        };
42
43        (status, recent_sync_lengths)
44    }
45
46    /// Create an instance of [`SyncStatus`].
47    ///
48    /// The status is determined based on the latest counts of synchronized blocks, observed
49    /// through `latest_sync_length`.
50    pub fn new() -> (Self, RecentSyncLengths) {
51        let (recent_sync_lengths, latest_sync_length) = RecentSyncLengths::new();
52        let status = SyncStatus {
53            latest_sync_length,
54            is_regtest: false,
55        };
56
57        (status, recent_sync_lengths)
58    }
59
60    /// Wait until the synchronization is likely close to the tip.
61    ///
62    /// Returns an error if communication with the synchronizer is lost.
63    pub async fn wait_until_close_to_tip(&mut self) -> Result<(), watch::error::RecvError> {
64        while !self.is_close_to_tip() {
65            self.latest_sync_length.changed().await?;
66        }
67
68        Ok(())
69    }
70}
71
72impl ChainSyncStatus for SyncStatus {
73    /// Check if the synchronization is likely close to the chain tip.
74    fn is_close_to_tip(&self) -> bool {
75        if self.is_regtest {
76            return true;
77        }
78
79        let sync_lengths = self.latest_sync_length.borrow();
80
81        // Return early if sync_lengths is empty.
82        if sync_lengths.is_empty() {
83            return false;
84        }
85
86        // Compute the sum of the `sync_lengths`.
87        // The sum is computed by saturating addition in order to avoid overflowing.
88        let sum = sync_lengths
89            .iter()
90            .fold(0usize, |sum, rhs| sum.saturating_add(*rhs));
91
92        // Compute the average sync length.
93        // This value effectively represents a simple moving average.
94        let avg = sum / sync_lengths.len();
95
96        // The synchronization process is close to the chain tip once the
97        // average sync length falls below the threshold.
98        avg < Self::MIN_DIST_FROM_TIP
99    }
100}