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}