1//! Mock [`ChainTip`]s for use in tests.
23use std::sync::Arc;
45use chrono::{DateTime, Utc};
6use futures::{future, FutureExt, TryFutureExt};
7use tokio::sync::watch;
89use crate::{
10 block,
11 chain_tip::{BestTipChanged, ChainTip},
12 parameters::Network,
13 transaction,
14};
1516/// A sender to sets the values read by a [`MockChainTip`].
17//
18// Update `best_tip_changed()` for each new field that is added to MockChainTipSender.
19pub struct MockChainTipSender {
20/// A sender that sets the `best_tip_height` of a [`MockChainTip`].
21best_tip_height: watch::Sender<Option<block::Height>>,
2223/// A sender that sets the `best_tip_hash` of a [`MockChainTip`].
24best_tip_hash: watch::Sender<Option<block::Hash>>,
2526/// A sender that sets the `best_tip_block_time` of a [`MockChainTip`].
27best_tip_block_time: watch::Sender<Option<DateTime<Utc>>>,
2829/// A sender that sets the `estimate_distance_to_network_chain_tip` of a [`MockChainTip`].
30estimated_distance_to_network_chain_tip: watch::Sender<Option<block::HeightDiff>>,
31}
3233/// A mock [`ChainTip`] implementation that allows setting the `best_tip_height` externally.
34#[derive(Clone, Debug)]
35pub struct MockChainTip {
36/// A mocked `best_tip_height` value set by the [`MockChainTipSender`].
37best_tip_height: watch::Receiver<Option<block::Height>>,
3839/// A mocked `best_tip_hash` value set by the [`MockChainTipSender`].
40best_tip_hash: watch::Receiver<Option<block::Hash>>,
4142/// A mocked `best_tip_height` value set by the [`MockChainTipSender`].
43best_tip_block_time: watch::Receiver<Option<DateTime<Utc>>>,
4445/// A mocked `estimate_distance_to_network_chain_tip` value set by the [`MockChainTipSender`].
46estimated_distance_to_network_chain_tip: watch::Receiver<Option<block::HeightDiff>>,
47}
4849impl MockChainTip {
50/// Create a new [`MockChainTip`].
51 ///
52 /// Returns the [`MockChainTip`] instance and the endpoint to modiy the current best tip
53 /// height.
54 ///
55 /// Initially, the best tip height is [`None`].
56pub fn new() -> (Self, MockChainTipSender) {
57let (height_sender, height_receiver) = watch::channel(None);
58let (hash_sender, hash_receiver) = watch::channel(None);
59let (time_sender, time_receiver) = watch::channel(None);
60let (estimated_distance_to_tip_sender, estimated_distance_to_tip_receiver) =
61 watch::channel(None);
6263let mock_chain_tip = MockChainTip {
64 best_tip_height: height_receiver,
65 best_tip_hash: hash_receiver,
66 best_tip_block_time: time_receiver,
67 estimated_distance_to_network_chain_tip: estimated_distance_to_tip_receiver,
68 };
6970let mock_chain_tip_sender = MockChainTipSender {
71 best_tip_height: height_sender,
72 best_tip_hash: hash_sender,
73 best_tip_block_time: time_sender,
74 estimated_distance_to_network_chain_tip: estimated_distance_to_tip_sender,
75 };
7677 (mock_chain_tip, mock_chain_tip_sender)
78 }
79}
8081impl ChainTip for MockChainTip {
82fn best_tip_height(&self) -> Option<block::Height> {
83*self.best_tip_height.borrow()
84 }
8586fn best_tip_hash(&self) -> Option<block::Hash> {
87*self.best_tip_hash.borrow()
88 }
8990fn best_tip_height_and_hash(&self) -> Option<(block::Height, block::Hash)> {
91let height = (*self.best_tip_height.borrow())?;
92let hash = (*self.best_tip_hash.borrow())?;
9394Some((height, hash))
95 }
9697fn best_tip_block_time(&self) -> Option<DateTime<Utc>> {
98*self.best_tip_block_time.borrow()
99 }
100101fn best_tip_height_and_block_time(&self) -> Option<(block::Height, DateTime<Utc>)> {
102let height = (*self.best_tip_height.borrow())?;
103let block_time = (*self.best_tip_block_time.borrow())?;
104105Some((height, block_time))
106 }
107108fn best_tip_mined_transaction_ids(&self) -> Arc<[transaction::Hash]> {
109 Arc::new([])
110 }
111112fn estimate_distance_to_network_chain_tip(
113&self,
114 _network: &Network,
115 ) -> Option<(block::HeightDiff, block::Height)> {
116self.estimated_distance_to_network_chain_tip
117 .borrow()
118 .and_then(|estimated_distance| {
119self.best_tip_height()
120 .map(|tip_height| (estimated_distance, tip_height))
121 })
122 }
123124/// Returns when any sender channel changes.
125 /// Returns an error if any sender was dropped.
126 ///
127 /// Marks the changed channel as seen when the returned future completes.
128//
129 // Update this method when each new mock field is added.
130fn best_tip_changed(&mut self) -> BestTipChanged {
131// A future that returns when the first watch channel has changed
132let select_changed = future::select_all([
133// Erase the differing future types for each channel, and map their error types
134BestTipChanged::new(self.best_tip_height.changed().err_into()),
135 BestTipChanged::new(self.best_tip_hash.changed().err_into()),
136 BestTipChanged::new(self.best_tip_block_time.changed().err_into()),
137 BestTipChanged::new(
138self.estimated_distance_to_network_chain_tip
139 .changed()
140 .err_into(),
141 ),
142 ])
143// Map the select result to the expected type, dropping the unused channels
144.map(|(changed_result, _changed_index, _remaining_futures)| changed_result);
145146 BestTipChanged::new(select_changed)
147 }
148149/// Marks all sender channels as seen.
150fn mark_best_tip_seen(&mut self) {
151self.best_tip_height.borrow_and_update();
152self.best_tip_hash.borrow_and_update();
153self.best_tip_block_time.borrow_and_update();
154self.estimated_distance_to_network_chain_tip
155 .borrow_and_update();
156 }
157}
158159impl MockChainTipSender {
160/// Send a new best tip height to the [`MockChainTip`].
161pub fn send_best_tip_height(&self, height: impl Into<Option<block::Height>>) {
162self.best_tip_height
163 .send(height.into())
164 .expect("attempt to send a best tip height to a dropped `MockChainTip`");
165 }
166167/// Send a new best tip hash to the [`MockChainTip`].
168pub fn send_best_tip_hash(&self, hash: impl Into<Option<block::Hash>>) {
169self.best_tip_hash
170 .send(hash.into())
171 .expect("attempt to send a best tip hash to a dropped `MockChainTip`");
172 }
173174/// Send a new best tip block time to the [`MockChainTip`].
175pub fn send_best_tip_block_time(&self, block_time: impl Into<Option<DateTime<Utc>>>) {
176self.best_tip_block_time
177 .send(block_time.into())
178 .expect("attempt to send a best tip block time to a dropped `MockChainTip`");
179 }
180181/// Send a new estimated distance to network chain tip to the [`MockChainTip`].
182pub fn send_estimated_distance_to_network_chain_tip(
183&self,
184 distance: impl Into<Option<block::HeightDiff>>,
185 ) {
186self.estimated_distance_to_network_chain_tip
187 .send(distance.into())
188 .expect("attempt to send a best tip height to a dropped `MockChainTip`");
189 }
190}