//! A channel which holds a list of recent syncer response lengths. use tokio::sync::watch; #[cfg(test)] mod tests; /// A helper type which holds a list of recent syncer response lengths. /// These sync lengths can be used to work out if Zebra has reached the end of the chain. /// /// New lengths are added to the front of the list. /// Old lengths are dropped if the list is longer than `MAX_RECENT_LENGTHS`. // // TODO: disable the mempool if obtain or extend tips return errors? #[derive(Debug)] pub struct RecentSyncLengths { /// A sender for an array of recent sync lengths. sender: watch::Sender>, /// A local copy of the contents of `sender`. // TODO: Replace with calls to `watch::Sender::borrow` once Tokio is updated to 1.0.0 (#2573) recent_lengths: Vec, } impl RecentSyncLengths { /// The maximum number of lengths sent by `RecentSyncLengths`. /// /// Older lengths are dropped. /// /// This length was chosen as a tradeoff between: /// * clearing temporary errors and temporary syncs quickly /// * distinguishing between temporary and sustained syncs/errors /// * activating the syncer shortly after reaching the chain tip pub const MAX_RECENT_LENGTHS: usize = 3; /// Create a new instance of [`RecentSyncLengths`] /// and a [`watch::Receiver`] endpoint for receiving recent sync lengths. pub fn new() -> (Self, watch::Receiver>) { let (sender, receiver) = watch::channel(Vec::new()); ( RecentSyncLengths { sender, recent_lengths: Vec::with_capacity(Self::MAX_RECENT_LENGTHS), }, receiver, ) } // We skip the genesis block download, because it just uses the genesis hash directly, // rather than asking peers for the next blocks in the chain. // (And if genesis downloads kept failing, we could accidentally activate the mempool.) /// Insert a sync length from [`ChainSync::obtain_tips`] at the front of the /// list. /// /// [`ChainSync::obtain_tips`]: super::ChainSync::obtain_tips #[instrument(skip(self), fields(self.recent_lengths))] pub fn push_obtain_tips_length(&mut self, sync_length: usize) { // currently, we treat lengths from obtain and extend tips exactly the same, // but we might want to ignore some obtain tips lengths // // See "Response Lengths During Sync -> Details" in: // https://github.com/ZcashFoundation/zebra/issues/2592#issuecomment-897304684 self.update(sync_length) } /// Insert a sync length from [`ChainSync::extend_tips`] at the front of the /// list. /// /// [`ChainSync::extend_tips`]: super::ChainSync::extend_tips #[instrument(skip(self), fields(self.recent_lengths))] pub fn push_extend_tips_length(&mut self, sync_length: usize) { self.update(sync_length) } /// Sends a list update to listeners. /// /// Prunes recent lengths, if the list is longer than `MAX_RECENT_LENGTHS`. fn update(&mut self, sync_length: usize) { self.recent_lengths.insert(0, sync_length); self.recent_lengths.truncate(Self::MAX_RECENT_LENGTHS); debug!( recent_lengths = ?self.recent_lengths, "sending recent sync lengths" ); // ignore dropped receivers let _ = self.sender.send(self.recent_lengths.clone()); } }