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