zebra_chain/chain_tip/
mock.rs

1//! Mock [`ChainTip`]s for use in tests.
2
3use std::sync::Arc;
4
5use chrono::{DateTime, Utc};
6use futures::{future, FutureExt, TryFutureExt};
7use tokio::sync::watch;
8
9use crate::{
10    block,
11    chain_tip::{BestTipChanged, ChainTip},
12    parameters::Network,
13    transaction,
14};
15
16/// 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`].
21    best_tip_height: watch::Sender<Option<block::Height>>,
22
23    /// A sender that sets the `best_tip_hash` of a [`MockChainTip`].
24    best_tip_hash: watch::Sender<Option<block::Hash>>,
25
26    /// A sender that sets the `best_tip_block_time` of a [`MockChainTip`].
27    best_tip_block_time: watch::Sender<Option<DateTime<Utc>>>,
28
29    /// A sender that sets the `estimate_distance_to_network_chain_tip` of a [`MockChainTip`].
30    estimated_distance_to_network_chain_tip: watch::Sender<Option<block::HeightDiff>>,
31}
32
33/// 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`].
37    best_tip_height: watch::Receiver<Option<block::Height>>,
38
39    /// A mocked `best_tip_hash` value set by the [`MockChainTipSender`].
40    best_tip_hash: watch::Receiver<Option<block::Hash>>,
41
42    /// A mocked `best_tip_height` value set by the [`MockChainTipSender`].
43    best_tip_block_time: watch::Receiver<Option<DateTime<Utc>>>,
44
45    /// A mocked `estimate_distance_to_network_chain_tip` value set by the [`MockChainTipSender`].
46    estimated_distance_to_network_chain_tip: watch::Receiver<Option<block::HeightDiff>>,
47}
48
49impl 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`].
56    pub fn new() -> (Self, MockChainTipSender) {
57        let (height_sender, height_receiver) = watch::channel(None);
58        let (hash_sender, hash_receiver) = watch::channel(None);
59        let (time_sender, time_receiver) = watch::channel(None);
60        let (estimated_distance_to_tip_sender, estimated_distance_to_tip_receiver) =
61            watch::channel(None);
62
63        let 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        };
69
70        let 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        };
76
77        (mock_chain_tip, mock_chain_tip_sender)
78    }
79}
80
81impl ChainTip for MockChainTip {
82    fn best_tip_height(&self) -> Option<block::Height> {
83        *self.best_tip_height.borrow()
84    }
85
86    fn best_tip_hash(&self) -> Option<block::Hash> {
87        *self.best_tip_hash.borrow()
88    }
89
90    fn best_tip_height_and_hash(&self) -> Option<(block::Height, block::Hash)> {
91        let height = (*self.best_tip_height.borrow())?;
92        let hash = (*self.best_tip_hash.borrow())?;
93
94        Some((height, hash))
95    }
96
97    fn best_tip_block_time(&self) -> Option<DateTime<Utc>> {
98        *self.best_tip_block_time.borrow()
99    }
100
101    fn best_tip_height_and_block_time(&self) -> Option<(block::Height, DateTime<Utc>)> {
102        let height = (*self.best_tip_height.borrow())?;
103        let block_time = (*self.best_tip_block_time.borrow())?;
104
105        Some((height, block_time))
106    }
107
108    fn best_tip_mined_transaction_ids(&self) -> Arc<[transaction::Hash]> {
109        Arc::new([])
110    }
111
112    fn estimate_distance_to_network_chain_tip(
113        &self,
114        _network: &Network,
115    ) -> Option<(block::HeightDiff, block::Height)> {
116        self.estimated_distance_to_network_chain_tip
117            .borrow()
118            .and_then(|estimated_distance| {
119                self.best_tip_height()
120                    .map(|tip_height| (estimated_distance, tip_height))
121            })
122    }
123
124    /// 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.
130    fn best_tip_changed(&mut self) -> BestTipChanged {
131        // A future that returns when the first watch channel has changed
132        let select_changed = future::select_all([
133            // Erase the differing future types for each channel, and map their error types
134            BestTipChanged::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(
138                self.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);
145
146        BestTipChanged::new(select_changed)
147    }
148
149    /// Marks all sender channels as seen.
150    fn mark_best_tip_seen(&mut self) {
151        self.best_tip_height.borrow_and_update();
152        self.best_tip_hash.borrow_and_update();
153        self.best_tip_block_time.borrow_and_update();
154        self.estimated_distance_to_network_chain_tip
155            .borrow_and_update();
156    }
157}
158
159impl MockChainTipSender {
160    /// Send a new best tip height to the [`MockChainTip`].
161    pub fn send_best_tip_height(&self, height: impl Into<Option<block::Height>>) {
162        self.best_tip_height
163            .send(height.into())
164            .expect("attempt to send a best tip height to a dropped `MockChainTip`");
165    }
166
167    /// Send a new best tip hash to the [`MockChainTip`].
168    pub fn send_best_tip_hash(&self, hash: impl Into<Option<block::Hash>>) {
169        self.best_tip_hash
170            .send(hash.into())
171            .expect("attempt to send a best tip hash to a dropped `MockChainTip`");
172    }
173
174    /// Send a new best tip block time to the [`MockChainTip`].
175    pub fn send_best_tip_block_time(&self, block_time: impl Into<Option<DateTime<Utc>>>) {
176        self.best_tip_block_time
177            .send(block_time.into())
178            .expect("attempt to send a best tip block time to a dropped `MockChainTip`");
179    }
180
181    /// Send a new estimated distance to network chain tip to the [`MockChainTip`].
182    pub fn send_estimated_distance_to_network_chain_tip(
183        &self,
184        distance: impl Into<Option<block::HeightDiff>>,
185    ) {
186        self.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}