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
//! Mock [`ChainTip`]s for use in tests.

use std::sync::Arc;

use chrono::{DateTime, Utc};
use futures::{future, FutureExt, TryFutureExt};
use tokio::sync::watch;

use crate::{
    block,
    chain_tip::{BestTipChanged, ChainTip},
    parameters::Network,
    transaction,
};

/// A sender to sets the values read by a [`MockChainTip`].
//
// Update `best_tip_changed()` for each new field that is added to MockChainTipSender.
pub struct MockChainTipSender {
    /// A sender that sets the `best_tip_height` of a [`MockChainTip`].
    best_tip_height: watch::Sender<Option<block::Height>>,

    /// A sender that sets the `best_tip_hash` of a [`MockChainTip`].
    best_tip_hash: watch::Sender<Option<block::Hash>>,

    /// A sender that sets the `best_tip_block_time` of a [`MockChainTip`].
    best_tip_block_time: watch::Sender<Option<DateTime<Utc>>>,

    /// A sender that sets the `estimate_distance_to_network_chain_tip` of a [`MockChainTip`].
    estimated_distance_to_network_chain_tip: watch::Sender<Option<block::HeightDiff>>,
}

/// A mock [`ChainTip`] implementation that allows setting the `best_tip_height` externally.
#[derive(Clone, Debug)]
pub struct MockChainTip {
    /// A mocked `best_tip_height` value set by the [`MockChainTipSender`].
    best_tip_height: watch::Receiver<Option<block::Height>>,

    /// A mocked `best_tip_hash` value set by the [`MockChainTipSender`].
    best_tip_hash: watch::Receiver<Option<block::Hash>>,

    /// A mocked `best_tip_height` value set by the [`MockChainTipSender`].
    best_tip_block_time: watch::Receiver<Option<DateTime<Utc>>>,

    /// A mocked `estimate_distance_to_network_chain_tip` value set by the [`MockChainTipSender`].
    estimated_distance_to_network_chain_tip: watch::Receiver<Option<block::HeightDiff>>,
}

impl MockChainTip {
    /// Create a new [`MockChainTip`].
    ///
    /// Returns the [`MockChainTip`] instance and the endpoint to modiy the current best tip
    /// height.
    ///
    /// Initially, the best tip height is [`None`].
    pub fn new() -> (Self, MockChainTipSender) {
        let (height_sender, height_receiver) = watch::channel(None);
        let (hash_sender, hash_receiver) = watch::channel(None);
        let (time_sender, time_receiver) = watch::channel(None);
        let (estimated_distance_to_tip_sender, estimated_distance_to_tip_receiver) =
            watch::channel(None);

        let mock_chain_tip = MockChainTip {
            best_tip_height: height_receiver,
            best_tip_hash: hash_receiver,
            best_tip_block_time: time_receiver,
            estimated_distance_to_network_chain_tip: estimated_distance_to_tip_receiver,
        };

        let mock_chain_tip_sender = MockChainTipSender {
            best_tip_height: height_sender,
            best_tip_hash: hash_sender,
            best_tip_block_time: time_sender,
            estimated_distance_to_network_chain_tip: estimated_distance_to_tip_sender,
        };

        (mock_chain_tip, mock_chain_tip_sender)
    }
}

impl ChainTip for MockChainTip {
    fn best_tip_height(&self) -> Option<block::Height> {
        *self.best_tip_height.borrow()
    }

    fn best_tip_hash(&self) -> Option<block::Hash> {
        *self.best_tip_hash.borrow()
    }

    fn best_tip_height_and_hash(&self) -> Option<(block::Height, block::Hash)> {
        let height = (*self.best_tip_height.borrow())?;
        let hash = (*self.best_tip_hash.borrow())?;

        Some((height, hash))
    }

    fn best_tip_block_time(&self) -> Option<DateTime<Utc>> {
        *self.best_tip_block_time.borrow()
    }

    fn best_tip_height_and_block_time(&self) -> Option<(block::Height, DateTime<Utc>)> {
        let height = (*self.best_tip_height.borrow())?;
        let block_time = (*self.best_tip_block_time.borrow())?;

        Some((height, block_time))
    }

    fn best_tip_mined_transaction_ids(&self) -> Arc<[transaction::Hash]> {
        unreachable!("Method not used in tests");
    }

    fn estimate_distance_to_network_chain_tip(
        &self,
        _network: &Network,
    ) -> Option<(block::HeightDiff, block::Height)> {
        self.estimated_distance_to_network_chain_tip
            .borrow()
            .and_then(|estimated_distance| {
                self.best_tip_height()
                    .map(|tip_height| (estimated_distance, tip_height))
            })
    }

    /// Returns when any sender channel changes.
    /// Returns an error if any sender was dropped.
    ///
    /// Marks the changed channel as seen when the returned future completes.
    //
    // Update this method when each new mock field is added.
    fn best_tip_changed(&mut self) -> BestTipChanged {
        // A future that returns when the first watch channel has changed
        let select_changed = future::select_all([
            // Erase the differing future types for each channel, and map their error types
            BestTipChanged::new(self.best_tip_height.changed().err_into()),
            BestTipChanged::new(self.best_tip_hash.changed().err_into()),
            BestTipChanged::new(self.best_tip_block_time.changed().err_into()),
            BestTipChanged::new(
                self.estimated_distance_to_network_chain_tip
                    .changed()
                    .err_into(),
            ),
        ])
        // Map the select result to the expected type, dropping the unused channels
        .map(|(changed_result, _changed_index, _remaining_futures)| changed_result);

        BestTipChanged::new(select_changed)
    }

    /// Marks all sender channels as seen.
    fn mark_best_tip_seen(&mut self) {
        self.best_tip_height.borrow_and_update();
        self.best_tip_hash.borrow_and_update();
        self.best_tip_block_time.borrow_and_update();
        self.estimated_distance_to_network_chain_tip
            .borrow_and_update();
    }
}

impl MockChainTipSender {
    /// Send a new best tip height to the [`MockChainTip`].
    pub fn send_best_tip_height(&self, height: impl Into<Option<block::Height>>) {
        self.best_tip_height
            .send(height.into())
            .expect("attempt to send a best tip height to a dropped `MockChainTip`");
    }

    /// Send a new best tip hash to the [`MockChainTip`].
    pub fn send_best_tip_hash(&self, hash: impl Into<Option<block::Hash>>) {
        self.best_tip_hash
            .send(hash.into())
            .expect("attempt to send a best tip hash to a dropped `MockChainTip`");
    }

    /// Send a new best tip block time to the [`MockChainTip`].
    pub fn send_best_tip_block_time(&self, block_time: impl Into<Option<DateTime<Utc>>>) {
        self.best_tip_block_time
            .send(block_time.into())
            .expect("attempt to send a best tip block time to a dropped `MockChainTip`");
    }

    /// Send a new estimated distance to network chain tip to the [`MockChainTip`].
    pub fn send_estimated_distance_to_network_chain_tip(
        &self,
        distance: impl Into<Option<block::HeightDiff>>,
    ) {
        self.estimated_distance_to_network_chain_tip
            .send(distance.into())
            .expect("attempt to send a best tip height to a dropped `MockChainTip`");
    }
}