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};
6use futures::{future::BoxFuture, Future, FutureExt};
7
8use crate::{block, parameters::Network, transaction, BoxError};
9
10mod network_chain_tip_height_estimator;
11
12#[cfg(any(test, feature = "proptest-impl"))]
13pub mod mock;
14#[cfg(test)]
15mod tests;
16
17pub use network_chain_tip_height_estimator::NetworkChainTipHeightEstimator;
18
19/// An interface for querying the chain tip.
20///
21/// This trait helps avoid dependencies between:
22/// * `zebra-chain` and `tokio`
23/// * `zebra-network` and `zebra-state`
24pub trait ChainTip {
25 /// Returns the height of the best chain tip.
26 ///
27 /// Does not mark the best tip as seen.
28 fn best_tip_height(&self) -> Option<block::Height>;
29
30 /// Returns the block hash of the best chain tip.
31 ///
32 /// Does not mark the best tip as seen.
33 fn best_tip_hash(&self) -> Option<block::Hash>;
34
35 /// Returns the height and the hash of the best chain tip.
36 ///
37 /// Does not mark the best tip as seen.
38 fn best_tip_height_and_hash(&self) -> Option<(block::Height, block::Hash)>;
39
40 /// Returns the block time of the best chain tip.
41 ///
42 /// Does not mark the best tip as seen.
43 fn best_tip_block_time(&self) -> Option<DateTime<Utc>>;
44
45 /// Returns the height and the block time of the best chain tip.
46 /// Returning both values at the same time guarantees that they refer to the same chain tip.
47 ///
48 /// Does not mark the best tip as seen.
49 fn best_tip_height_and_block_time(&self) -> Option<(block::Height, DateTime<Utc>)>;
50
51 /// Returns the mined transaction IDs of the transactions in the best chain tip block.
52 ///
53 /// All transactions with these mined IDs should be rejected from the mempool,
54 /// even if their authorizing data is different.
55 ///
56 /// Does not mark the best tip as seen.
57 fn best_tip_mined_transaction_ids(&self) -> Arc<[transaction::Hash]>;
58
59 /// A future that returns when the best chain tip changes.
60 /// Can return immediately if the latest value in this [`ChainTip`] has not been seen yet.
61 ///
62 /// Marks the best tip as seen.
63 ///
64 /// Returns an error if Zebra is shutting down, or the state has permanently failed.
65 ///
66 /// See [`tokio::watch::Receiver::changed()`](https://docs.rs/tokio/latest/tokio/sync/watch/struct.Receiver.html#method.changed) for details.
67 //
68 // TODO:
69 // Use async_fn_in_trait or return_position_impl_trait_in_trait when one of them stabilises:
70 // https://github.com/rust-lang/rust/issues/91611
71 fn best_tip_changed(&mut self) -> BestTipChanged;
72
73 /// Mark the current best tip as seen.
74 ///
75 /// Later calls to [`ChainTip::best_tip_changed()`] will wait for the next change
76 /// before returning.
77 fn mark_best_tip_seen(&mut self);
78
79 // Provided methods
80 //
81 /// Return an estimate of the network chain tip's height.
82 ///
83 /// The estimate is calculated based on the current local time, the block time of the best tip
84 /// and the height of the best tip.
85 fn estimate_network_chain_tip_height(
86 &self,
87 network: &Network,
88 now: DateTime<Utc>,
89 ) -> Option<block::Height> {
90 let (current_height, current_block_time) = self.best_tip_height_and_block_time()?;
91
92 let estimator =
93 NetworkChainTipHeightEstimator::new(current_block_time, current_height, network);
94
95 Some(estimator.estimate_height_at(now))
96 }
97
98 /// Return an estimate of how many blocks there are ahead of Zebra's best chain tip until the
99 /// network chain tip, and Zebra's best chain tip height.
100 ///
101 /// The first element in the returned tuple is the estimate.
102 /// The second element in the returned tuple is the current best chain tip.
103 ///
104 /// The estimate is calculated based on the current local time, the block time of the best tip
105 /// and the height of the best tip.
106 ///
107 /// This estimate may be negative if the current local time is behind the chain tip block's
108 /// timestamp.
109 ///
110 /// Returns `None` if the state is empty.
111 fn estimate_distance_to_network_chain_tip(
112 &self,
113 network: &Network,
114 ) -> Option<(block::HeightDiff, block::Height)> {
115 let (current_height, current_block_time) = self.best_tip_height_and_block_time()?;
116
117 let estimator =
118 NetworkChainTipHeightEstimator::new(current_block_time, current_height, network);
119
120 let distance_to_tip = estimator.estimate_height_at(Utc::now()) - current_height;
121
122 Some((distance_to_tip, current_height))
123 }
124}
125
126/// A future for the [`ChainTip::best_tip_changed()`] method.
127/// See that method for details.
128pub struct BestTipChanged<'f> {
129 fut: BoxFuture<'f, Result<(), BoxError>>,
130}
131
132impl<'f> BestTipChanged<'f> {
133 /// Returns a new [`BestTipChanged`] containing `fut`.
134 pub fn new<Fut>(fut: Fut) -> Self
135 where
136 Fut: Future<Output = Result<(), BoxError>> + Send + 'f,
137 {
138 Self { fut: Box::pin(fut) }
139 }
140}
141
142impl Future for BestTipChanged<'_> {
143 type Output = Result<(), BoxError>;
144
145 fn poll(
146 mut self: std::pin::Pin<&mut Self>,
147 cx: &mut std::task::Context<'_>,
148 ) -> std::task::Poll<Self::Output> {
149 self.fut.poll_unpin(cx)
150 }
151}
152
153/// A chain tip that is always empty and never changes.
154///
155/// Used in production for isolated network connections,
156/// and as a mock chain tip in tests.
157#[derive(Copy, Clone, Debug, PartialEq, Eq)]
158pub struct NoChainTip;
159
160impl ChainTip for NoChainTip {
161 fn best_tip_height(&self) -> Option<block::Height> {
162 None
163 }
164
165 fn best_tip_hash(&self) -> Option<block::Hash> {
166 None
167 }
168
169 fn best_tip_height_and_hash(&self) -> Option<(block::Height, block::Hash)> {
170 None
171 }
172
173 fn best_tip_block_time(&self) -> Option<DateTime<Utc>> {
174 None
175 }
176
177 fn best_tip_height_and_block_time(&self) -> Option<(block::Height, DateTime<Utc>)> {
178 None
179 }
180
181 fn best_tip_mined_transaction_ids(&self) -> Arc<[transaction::Hash]> {
182 Arc::new([])
183 }
184
185 /// The [`NoChainTip`] best tip never changes, so this never returns.
186 fn best_tip_changed(&mut self) -> BestTipChanged {
187 BestTipChanged::new(future::pending())
188 }
189
190 /// The [`NoChainTip`] best tip never changes, so this does nothing.
191 fn mark_best_tip_seen(&mut self) {}
192}