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
88
89
90
91
92
93
94
95
96
97
98
99
100
//! Syncer chain tip status, based on recent block locator responses from peers.
use tokio::sync::watch;
use zakura_chain::chain_sync_status::ChainSyncStatus;
use super::RecentSyncLengths;
#[cfg(any(test, feature = "proptest-impl"))]
pub mod mock;
#[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>>,
is_regtest: bool,
}
impl SyncStatus {
/// The threshold that determines if the synchronization is at the chain
/// tip.
///
/// This uses a strict less-than check, so 101 means Zebra is close to the
/// tip when the average recent sync length is at most 100 blocks.
const MIN_DIST_FROM_TIP: usize = 101;
/// Create an instance of [`SyncStatus`] for a specific network.
///
/// The status is determined based on the latest counts of synchronized blocks, observed
/// through `latest_sync_length`. In regtest, [`ChainSyncStatus::is_close_to_tip`] always returns `true`.
pub fn new_for_network(
network: &zakura_chain::parameters::Network,
) -> (Self, RecentSyncLengths) {
let (recent_sync_lengths, latest_sync_length) = RecentSyncLengths::new();
let status = SyncStatus {
latest_sync_length,
is_regtest: network.is_regtest(),
};
(status, recent_sync_lengths)
}
/// 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,
is_regtest: false,
};
(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(())
}
}
impl ChainSyncStatus for SyncStatus {
/// Check if the synchronization is likely close to the chain tip.
fn is_close_to_tip(&self) -> bool {
if self.is_regtest {
return true;
}
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
}
}