zebrad/components/sync/recent_sync_lengths.rs
1//! A channel which holds a list of recent syncer response lengths.
2
3use tokio::sync::watch;
4
5#[cfg(test)]
6mod tests;
7
8/// A helper type which holds a list of recent syncer response lengths.
9/// These sync lengths can be used to work out if Zebra has reached the end of the chain.
10///
11/// New lengths are added to the front of the list.
12/// Old lengths are dropped if the list is longer than `MAX_RECENT_LENGTHS`.
13//
14// TODO: disable the mempool if obtain or extend tips return errors?
15#[derive(Debug)]
16pub struct RecentSyncLengths {
17 /// A sender for an array of recent sync lengths.
18 sender: watch::Sender<Vec<usize>>,
19
20 /// A local copy of the contents of `sender`.
21 // TODO: Replace with calls to `watch::Sender::borrow` once Tokio is updated to 1.0.0 (#2573)
22 recent_lengths: Vec<usize>,
23}
24
25impl RecentSyncLengths {
26 /// The maximum number of lengths sent by `RecentSyncLengths`.
27 ///
28 /// Older lengths are dropped.
29 ///
30 /// This length was chosen as a tradeoff between:
31 /// * clearing temporary errors and temporary syncs quickly
32 /// * distinguishing between temporary and sustained syncs/errors
33 /// * activating the syncer shortly after reaching the chain tip
34 pub const MAX_RECENT_LENGTHS: usize = 3;
35
36 /// Create a new instance of [`RecentSyncLengths`]
37 /// and a [`watch::Receiver`] endpoint for receiving recent sync lengths.
38 pub fn new() -> (Self, watch::Receiver<Vec<usize>>) {
39 let (sender, receiver) = watch::channel(Vec::new());
40
41 (
42 RecentSyncLengths {
43 sender,
44 recent_lengths: Vec::with_capacity(Self::MAX_RECENT_LENGTHS),
45 },
46 receiver,
47 )
48 }
49
50 // We skip the genesis block download, because it just uses the genesis hash directly,
51 // rather than asking peers for the next blocks in the chain.
52 // (And if genesis downloads kept failing, we could accidentally activate the mempool.)
53
54 /// Insert a sync length from [`ChainSync::obtain_tips`] at the front of the
55 /// list.
56 ///
57 /// [`ChainSync::obtain_tips`]: super::ChainSync::obtain_tips
58 #[instrument(skip(self), fields(self.recent_lengths))]
59 pub fn push_obtain_tips_length(&mut self, sync_length: usize) {
60 // currently, we treat lengths from obtain and extend tips exactly the same,
61 // but we might want to ignore some obtain tips lengths
62 //
63 // See "Response Lengths During Sync -> Details" in:
64 // https://github.com/ZcashFoundation/zebra/issues/2592#issuecomment-897304684
65 self.update(sync_length)
66 }
67
68 /// Insert a sync length from [`ChainSync::extend_tips`] at the front of the
69 /// list.
70 ///
71 /// [`ChainSync::extend_tips`]: super::ChainSync::extend_tips
72 #[instrument(skip(self), fields(self.recent_lengths))]
73 pub fn push_extend_tips_length(&mut self, sync_length: usize) {
74 self.update(sync_length)
75 }
76
77 /// Sends a list update to listeners.
78 ///
79 /// Prunes recent lengths, if the list is longer than `MAX_RECENT_LENGTHS`.
80 fn update(&mut self, sync_length: usize) {
81 self.recent_lengths.insert(0, sync_length);
82
83 self.recent_lengths.truncate(Self::MAX_RECENT_LENGTHS);
84
85 debug!(
86 recent_lengths = ?self.recent_lengths,
87 "sending recent sync lengths"
88 );
89
90 // ignore dropped receivers
91 let _ = self.sender.send(self.recent_lengths.clone());
92 }
93}