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