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