Skip to main content

zebra_chain/
chain_tip.rs

1//! Zebra interfaces for access to chain tip information.
2
3use std::{future, sync::Arc};
4
5use chrono::{DateTime, Utc};
6
7use crate::{block, parameters::Network, transaction, BoxError};
8
9mod network_chain_tip_height_estimator;
10
11#[cfg(any(test, feature = "proptest-impl"))]
12pub mod mock;
13#[cfg(test)]
14mod tests;
15
16pub use network_chain_tip_height_estimator::NetworkChainTipHeightEstimator;
17
18/// The maximum estimated distance to the network chain tip that is considered "at or near tip".
19///
20/// Allows for normal block-time variance and propagation delay. Considering the 75 second target
21/// for the time between blocks on mainnet, the node is considered far from the tip once the
22/// estimated distance exceeds this threshold, after approximately 20 hours and 51 minutes without
23/// a new block.
24///
25/// Since block production is approximately a Poisson process, this larger tolerance avoids
26/// enabling peer stall detection during normal long gaps between blocks.
27pub const AT_OR_NEAR_TIP_THRESHOLD: block::HeightDiff = 1_000;
28
29/// An interface for querying the chain tip.
30///
31/// This trait helps avoid dependencies between:
32/// * `zebra-chain` and `tokio`
33/// * `zebra-network` and `zebra-state`
34pub trait ChainTip {
35    /// Returns the height of the best chain tip.
36    ///
37    /// Does not mark the best tip as seen.
38    fn best_tip_height(&self) -> Option<block::Height>;
39
40    /// Returns the block hash of the best chain tip.
41    ///
42    /// Does not mark the best tip as seen.
43    fn best_tip_hash(&self) -> Option<block::Hash>;
44
45    /// Returns the height and the hash of the best chain tip.
46    ///
47    /// Does not mark the best tip as seen.
48    fn best_tip_height_and_hash(&self) -> Option<(block::Height, block::Hash)>;
49
50    /// Returns the block time of the best chain tip.
51    ///
52    /// Does not mark the best tip as seen.
53    fn best_tip_block_time(&self) -> Option<DateTime<Utc>>;
54
55    /// Returns the height and the block time of the best chain tip.
56    /// Returning both values at the same time guarantees that they refer to the same chain tip.
57    ///
58    /// Does not mark the best tip as seen.
59    fn best_tip_height_and_block_time(&self) -> Option<(block::Height, DateTime<Utc>)>;
60
61    /// Returns the mined transaction IDs of the transactions in the best chain tip block.
62    ///
63    /// All transactions with these mined IDs should be rejected from the mempool,
64    /// even if their authorizing data is different.
65    ///
66    /// Does not mark the best tip as seen.
67    fn best_tip_mined_transaction_ids(&self) -> Arc<[transaction::Hash]>;
68
69    /// A future that returns when the best chain tip changes.
70    /// Can return immediately if the latest value in this [`ChainTip`] has not been seen yet.
71    ///
72    /// Marks the best tip as seen.
73    ///
74    /// Returns an error if Zebra is shutting down, or the state has permanently failed.
75    ///
76    /// See [`tokio::watch::Receiver::changed()`](https://docs.rs/tokio/latest/tokio/sync/watch/struct.Receiver.html#method.changed) for details.
77    fn best_tip_changed(
78        &mut self,
79    ) -> impl std::future::Future<Output = Result<(), BoxError>> + Send;
80
81    /// Mark the current best tip as seen.
82    ///
83    /// Later calls to [`ChainTip::best_tip_changed()`] will wait for the next change
84    /// before returning.
85    fn mark_best_tip_seen(&mut self);
86
87    // Provided methods
88    //
89    /// Return an estimate of the network chain tip's height.
90    ///
91    /// The estimate is calculated based on the current local time, the block time of the best tip
92    /// and the height of the best tip.
93    fn estimate_network_chain_tip_height(
94        &self,
95        network: &Network,
96        now: DateTime<Utc>,
97    ) -> Option<block::Height> {
98        let (current_height, current_block_time) = self.best_tip_height_and_block_time()?;
99
100        let estimator =
101            NetworkChainTipHeightEstimator::new(current_block_time, current_height, network);
102
103        Some(estimator.estimate_height_at(now))
104    }
105
106    /// Return an estimate of how many blocks there are ahead of Zebra's best chain tip until the
107    /// network chain tip, and Zebra's best chain tip height.
108    ///
109    /// The first element in the returned tuple is the estimate.
110    /// The second element in the returned tuple is the current best chain tip.
111    ///
112    /// The estimate is calculated based on the current local time, the block time of the best tip
113    /// and the height of the best tip.
114    ///
115    /// This estimate may be negative if the current local time is behind the chain tip block's
116    /// timestamp.
117    ///
118    /// Returns `None` if the state is empty.
119    fn estimate_distance_to_network_chain_tip(
120        &self,
121        network: &Network,
122    ) -> Option<(block::HeightDiff, block::Height)> {
123        let (current_height, current_block_time) = self.best_tip_height_and_block_time()?;
124
125        let estimator =
126            NetworkChainTipHeightEstimator::new(current_block_time, current_height, network);
127
128        let distance_to_tip = estimator.estimate_height_at(Utc::now()) - current_height;
129
130        Some((distance_to_tip, current_height))
131    }
132
133    /// Returns `true` if the node is at or near the network chain tip.
134    ///
135    /// Returns `false` if the chain is empty or the node is more than
136    /// [`AT_OR_NEAR_TIP_THRESHOLD`] blocks behind the estimated network tip,
137    /// meaning stall detection should remain active.
138    fn is_at_or_near_network_tip(&self, network: &Network) -> bool {
139        match self.estimate_distance_to_network_chain_tip(network) {
140            None => false,
141            Some((distance, _height)) => distance <= AT_OR_NEAR_TIP_THRESHOLD,
142        }
143    }
144}
145
146/// A chain tip that is always empty and never changes.
147///
148/// Used in production for isolated network connections,
149/// and as a mock chain tip in tests.
150#[derive(Copy, Clone, Debug, PartialEq, Eq)]
151pub struct NoChainTip;
152
153impl ChainTip for NoChainTip {
154    fn best_tip_height(&self) -> Option<block::Height> {
155        None
156    }
157
158    fn best_tip_hash(&self) -> Option<block::Hash> {
159        None
160    }
161
162    fn best_tip_height_and_hash(&self) -> Option<(block::Height, block::Hash)> {
163        None
164    }
165
166    fn best_tip_block_time(&self) -> Option<DateTime<Utc>> {
167        None
168    }
169
170    fn best_tip_height_and_block_time(&self) -> Option<(block::Height, DateTime<Utc>)> {
171        None
172    }
173
174    fn best_tip_mined_transaction_ids(&self) -> Arc<[transaction::Hash]> {
175        Arc::new([])
176    }
177
178    /// The [`NoChainTip`] best tip never changes, so this never returns.
179    async fn best_tip_changed(&mut self) -> Result<(), BoxError> {
180        future::pending().await
181    }
182
183    /// The [`NoChainTip`] best tip never changes, so this does nothing.
184    fn mark_best_tip_seen(&mut self) {}
185}