1//! Mock [`ChainTip`]s for use in tests.
23use std::sync::Arc;
45use chrono::{DateTime, Utc};
6use futures::TryFutureExt;
7use tokio::sync::watch;
89use crate::{block, chain_tip::ChainTip, parameters::Network, transaction, BoxError};
1011/// A sender to sets the values read by a [`MockChainTip`].
12//
13// Update `best_tip_changed()` for each new field that is added to MockChainTipSender.
14pub struct MockChainTipSender {
15/// A sender that sets the `best_tip_height` of a [`MockChainTip`].
16best_tip_height: watch::Sender<Option<block::Height>>,
1718/// A sender that sets the `best_tip_hash` of a [`MockChainTip`].
19best_tip_hash: watch::Sender<Option<block::Hash>>,
2021/// A sender that sets the `best_tip_block_time` of a [`MockChainTip`].
22best_tip_block_time: watch::Sender<Option<DateTime<Utc>>>,
2324/// A sender that sets the `estimate_distance_to_network_chain_tip` of a [`MockChainTip`].
25estimated_distance_to_network_chain_tip: watch::Sender<Option<block::HeightDiff>>,
26}
2728/// A mock [`ChainTip`] implementation that allows setting the `best_tip_height` externally.
29#[derive(Clone, Debug)]
30pub struct MockChainTip {
31/// A mocked `best_tip_height` value set by the [`MockChainTipSender`].
32best_tip_height: watch::Receiver<Option<block::Height>>,
3334/// A mocked `best_tip_hash` value set by the [`MockChainTipSender`].
35best_tip_hash: watch::Receiver<Option<block::Hash>>,
3637/// A mocked `best_tip_height` value set by the [`MockChainTipSender`].
38best_tip_block_time: watch::Receiver<Option<DateTime<Utc>>>,
3940/// A mocked `estimate_distance_to_network_chain_tip` value set by the [`MockChainTipSender`].
41estimated_distance_to_network_chain_tip: watch::Receiver<Option<block::HeightDiff>>,
42}
4344impl MockChainTip {
45/// Create a new [`MockChainTip`].
46 ///
47 /// Returns the [`MockChainTip`] instance and the endpoint to modiy the current best tip
48 /// height.
49 ///
50 /// Initially, the best tip height is [`None`].
51pub fn new() -> (Self, MockChainTipSender) {
52let (height_sender, height_receiver) = watch::channel(None);
53let (hash_sender, hash_receiver) = watch::channel(None);
54let (time_sender, time_receiver) = watch::channel(None);
55let (estimated_distance_to_tip_sender, estimated_distance_to_tip_receiver) =
56 watch::channel(None);
5758let mock_chain_tip = MockChainTip {
59 best_tip_height: height_receiver,
60 best_tip_hash: hash_receiver,
61 best_tip_block_time: time_receiver,
62 estimated_distance_to_network_chain_tip: estimated_distance_to_tip_receiver,
63 };
6465let mock_chain_tip_sender = MockChainTipSender {
66 best_tip_height: height_sender,
67 best_tip_hash: hash_sender,
68 best_tip_block_time: time_sender,
69 estimated_distance_to_network_chain_tip: estimated_distance_to_tip_sender,
70 };
7172 (mock_chain_tip, mock_chain_tip_sender)
73 }
74}
7576impl ChainTip for MockChainTip {
77fn best_tip_height(&self) -> Option<block::Height> {
78*self.best_tip_height.borrow()
79 }
8081fn best_tip_hash(&self) -> Option<block::Hash> {
82*self.best_tip_hash.borrow()
83 }
8485fn best_tip_height_and_hash(&self) -> Option<(block::Height, block::Hash)> {
86let height = (*self.best_tip_height.borrow())?;
87let hash = (*self.best_tip_hash.borrow())?;
8889Some((height, hash))
90 }
9192fn best_tip_block_time(&self) -> Option<DateTime<Utc>> {
93*self.best_tip_block_time.borrow()
94 }
9596fn best_tip_height_and_block_time(&self) -> Option<(block::Height, DateTime<Utc>)> {
97let height = (*self.best_tip_height.borrow())?;
98let block_time = (*self.best_tip_block_time.borrow())?;
99100Some((height, block_time))
101 }
102103fn best_tip_mined_transaction_ids(&self) -> Arc<[transaction::Hash]> {
104 Arc::new([])
105 }
106107fn estimate_distance_to_network_chain_tip(
108&self,
109 _network: &Network,
110 ) -> Option<(block::HeightDiff, block::Height)> {
111self.estimated_distance_to_network_chain_tip
112 .borrow()
113 .and_then(|estimated_distance| {
114self.best_tip_height()
115 .map(|tip_height| (estimated_distance, tip_height))
116 })
117 }
118119/// Returns when any sender channel changes.
120 /// Returns an error if any sender was dropped.
121 ///
122 /// Marks the changed channel as seen when the returned future completes.
123//
124 // Update this method when each new mock field is added.
125async fn best_tip_changed(&mut self) -> Result<(), BoxError> {
126// A future that returns when the first watch channel has changed.
127 // Erase the differing future types for each channel, and map their error types, and
128 // map the select result to the expected type, dropping the unused channels
129tokio::select! {
130 result = self.best_tip_height.changed().err_into() => result.map(|_| ()),
131 result = self.best_tip_hash.changed().err_into() => result.map(|_| ()),
132 result = self.best_tip_block_time.changed().err_into() => result.map(|_| ()),
133 result = self.estimated_distance_to_network_chain_tip.changed().err_into() => result.map(|_| ()),
134 }
135 }
136137/// Marks all sender channels as seen.
138fn mark_best_tip_seen(&mut self) {
139self.best_tip_height.borrow_and_update();
140self.best_tip_hash.borrow_and_update();
141self.best_tip_block_time.borrow_and_update();
142self.estimated_distance_to_network_chain_tip
143 .borrow_and_update();
144 }
145}
146147impl MockChainTipSender {
148/// Send a new best tip height to the [`MockChainTip`].
149pub fn send_best_tip_height(&self, height: impl Into<Option<block::Height>>) {
150self.best_tip_height
151 .send(height.into())
152 .expect("attempt to send a best tip height to a dropped `MockChainTip`");
153 }
154155/// Send a new best tip hash to the [`MockChainTip`].
156pub fn send_best_tip_hash(&self, hash: impl Into<Option<block::Hash>>) {
157self.best_tip_hash
158 .send(hash.into())
159 .expect("attempt to send a best tip hash to a dropped `MockChainTip`");
160 }
161162/// Send a new best tip block time to the [`MockChainTip`].
163pub fn send_best_tip_block_time(&self, block_time: impl Into<Option<DateTime<Utc>>>) {
164self.best_tip_block_time
165 .send(block_time.into())
166 .expect("attempt to send a best tip block time to a dropped `MockChainTip`");
167 }
168169/// Send a new estimated distance to network chain tip to the [`MockChainTip`].
170pub fn send_estimated_distance_to_network_chain_tip(
171&self,
172 distance: impl Into<Option<block::HeightDiff>>,
173 ) {
174self.estimated_distance_to_network_chain_tip
175 .send(distance.into())
176 .expect("attempt to send a best tip height to a dropped `MockChainTip`");
177 }
178}