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::TryFutureExt;
7use tokio::sync::watch;
8
9use crate::{block, chain_tip::ChainTip, parameters::Network, transaction, BoxError};
10
11/// 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`].
16    best_tip_height: watch::Sender<Option<block::Height>>,
17
18    /// A sender that sets the `best_tip_hash` of a [`MockChainTip`].
19    best_tip_hash: watch::Sender<Option<block::Hash>>,
20
21    /// A sender that sets the `best_tip_block_time` of a [`MockChainTip`].
22    best_tip_block_time: watch::Sender<Option<DateTime<Utc>>>,
23
24    /// A sender that sets the `estimate_distance_to_network_chain_tip` of a [`MockChainTip`].
25    estimated_distance_to_network_chain_tip: watch::Sender<Option<block::HeightDiff>>,
26}
27
28/// 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`].
32    best_tip_height: watch::Receiver<Option<block::Height>>,
33
34    /// A mocked `best_tip_hash` value set by the [`MockChainTipSender`].
35    best_tip_hash: watch::Receiver<Option<block::Hash>>,
36
37    /// A mocked `best_tip_height` value set by the [`MockChainTipSender`].
38    best_tip_block_time: watch::Receiver<Option<DateTime<Utc>>>,
39
40    /// A mocked `estimate_distance_to_network_chain_tip` value set by the [`MockChainTipSender`].
41    estimated_distance_to_network_chain_tip: watch::Receiver<Option<block::HeightDiff>>,
42}
43
44impl 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`].
51    pub fn new() -> (Self, MockChainTipSender) {
52        let (height_sender, height_receiver) = watch::channel(None);
53        let (hash_sender, hash_receiver) = watch::channel(None);
54        let (time_sender, time_receiver) = watch::channel(None);
55        let (estimated_distance_to_tip_sender, estimated_distance_to_tip_receiver) =
56            watch::channel(None);
57
58        let 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        };
64
65        let 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        };
71
72        (mock_chain_tip, mock_chain_tip_sender)
73    }
74}
75
76impl ChainTip for MockChainTip {
77    fn best_tip_height(&self) -> Option<block::Height> {
78        *self.best_tip_height.borrow()
79    }
80
81    fn best_tip_hash(&self) -> Option<block::Hash> {
82        *self.best_tip_hash.borrow()
83    }
84
85    fn best_tip_height_and_hash(&self) -> Option<(block::Height, block::Hash)> {
86        let height = (*self.best_tip_height.borrow())?;
87        let hash = (*self.best_tip_hash.borrow())?;
88
89        Some((height, hash))
90    }
91
92    fn best_tip_block_time(&self) -> Option<DateTime<Utc>> {
93        *self.best_tip_block_time.borrow()
94    }
95
96    fn best_tip_height_and_block_time(&self) -> Option<(block::Height, DateTime<Utc>)> {
97        let height = (*self.best_tip_height.borrow())?;
98        let block_time = (*self.best_tip_block_time.borrow())?;
99
100        Some((height, block_time))
101    }
102
103    fn best_tip_mined_transaction_ids(&self) -> Arc<[transaction::Hash]> {
104        Arc::new([])
105    }
106
107    fn estimate_distance_to_network_chain_tip(
108        &self,
109        _network: &Network,
110    ) -> Option<(block::HeightDiff, block::Height)> {
111        self.estimated_distance_to_network_chain_tip
112            .borrow()
113            .and_then(|estimated_distance| {
114                self.best_tip_height()
115                    .map(|tip_height| (estimated_distance, tip_height))
116            })
117    }
118
119    /// 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.
125    async 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
129        tokio::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    }
136
137    /// Marks all sender channels as seen.
138    fn mark_best_tip_seen(&mut self) {
139        self.best_tip_height.borrow_and_update();
140        self.best_tip_hash.borrow_and_update();
141        self.best_tip_block_time.borrow_and_update();
142        self.estimated_distance_to_network_chain_tip
143            .borrow_and_update();
144    }
145}
146
147impl MockChainTipSender {
148    /// Send a new best tip height to the [`MockChainTip`].
149    pub fn send_best_tip_height(&self, height: impl Into<Option<block::Height>>) {
150        self.best_tip_height
151            .send(height.into())
152            .expect("attempt to send a best tip height to a dropped `MockChainTip`");
153    }
154
155    /// Send a new best tip hash to the [`MockChainTip`].
156    pub fn send_best_tip_hash(&self, hash: impl Into<Option<block::Hash>>) {
157        self.best_tip_hash
158            .send(hash.into())
159            .expect("attempt to send a best tip hash to a dropped `MockChainTip`");
160    }
161
162    /// Send a new best tip block time to the [`MockChainTip`].
163    pub fn send_best_tip_block_time(&self, block_time: impl Into<Option<DateTime<Utc>>>) {
164        self.best_tip_block_time
165            .send(block_time.into())
166            .expect("attempt to send a best tip block time to a dropped `MockChainTip`");
167    }
168
169    /// Send a new estimated distance to network chain tip to the [`MockChainTip`].
170    pub fn send_estimated_distance_to_network_chain_tip(
171        &self,
172        distance: impl Into<Option<block::HeightDiff>>,
173    ) {
174        self.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}