• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

bitcoindevkit / bdk / 5119600689

pending completion
5119600689

Pull #976

github

web-flow
Merge 250458ae0 into 9bc7fe855
Pull Request #976: Reimplement `Wallet`, `ElectrumExt` and `Esplora{Async}Ext` with redesigned structures.

909 of 909 new or added lines in 13 files covered. (100.0%)

7569 of 9556 relevant lines covered (79.21%)

5314.36 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.1
/crates/chain/src/chain_data.rs
1
use bitcoin::{hashes::Hash, BlockHash, OutPoint, TxOut, Txid};
2

3
use crate::{Anchor, COINBASE_MATURITY};
4

5
/// Represents the observed position of some chain data.
6
///
7
/// The generic `A` should be a [`Anchor`] implementation.
8
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, core::hash::Hash)]
14✔
9
pub enum ChainPosition<A> {
10
    /// The chain data is seen as confirmed, and in anchored by `A`.
11
    Confirmed(A),
12
    /// The chain data is seen in mempool at this given timestamp.
13
    Unconfirmed(u64),
14
}
15

16
impl<A> ChainPosition<A> {
17
    /// Returns whether [`ChainPosition`] is confirmed or not.
18
    pub fn is_confirmed(&self) -> bool {
×
19
        matches!(self, Self::Confirmed(_))
×
20
    }
×
21
}
22

23
impl<A: Clone> ChainPosition<&A> {
24
    /// Maps a [`ChainPosition<&A>`] into a [`ChainPosition<A>`] by cloning the contents.
25
    pub fn cloned(self) -> ChainPosition<A> {
2,978✔
26
        match self {
2,978✔
27
            ChainPosition::Confirmed(a) => ChainPosition::Confirmed(a.clone()),
2,605✔
28
            ChainPosition::Unconfirmed(last_seen) => ChainPosition::Unconfirmed(last_seen),
373✔
29
        }
30
    }
2,978✔
31
}
32

33
impl<A: Anchor> ChainPosition<A> {
34
    /// Determines the upper bound of the confirmation height.
35
    pub fn confirmation_height_upper_bound(&self) -> Option<u32> {
×
36
        match self {
×
37
            ChainPosition::Confirmed(a) => Some(a.confirmation_height_upper_bound()),
×
38
            ChainPosition::Unconfirmed(_) => None,
×
39
        }
40
    }
×
41
}
42

43
/// Block height and timestamp at which a transaction is confirmed.
44
#[derive(Debug, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)]
103,887✔
45
#[cfg_attr(
46
    feature = "serde",
47
    derive(serde::Deserialize, serde::Serialize),
×
48
    serde(crate = "serde_crate")
49
)]
50
pub enum ConfirmationTime {
51
    /// The confirmed variant.
52
    Confirmed {
53
        /// Confirmation height.
54
        height: u32,
55
        /// Confirmation time in unix seconds.
56
        time: u64,
57
    },
58
    /// The unconfirmed variant.
59
    Unconfirmed {
60
        /// The last-seen timestamp in unix seconds.
61
        last_seen: u64,
62
    },
63
}
64

65
impl ConfirmationTime {
66
    /// Construct an unconfirmed variant using the given `last_seen` time in unix seconds.
67
    pub fn unconfirmed(last_seen: u64) -> Self {
13✔
68
        Self::Unconfirmed { last_seen }
13✔
69
    }
13✔
70

71
    /// Returns whether [`ConfirmationTime`] is the confirmed variant.
72
    pub fn is_confirmed(&self) -> bool {
208✔
73
        matches!(self, Self::Confirmed { .. })
208✔
74
    }
208✔
75
}
76

77
impl From<ChainPosition<ConfirmationTimeAnchor>> for ConfirmationTime {
78
    fn from(observed_as: ChainPosition<ConfirmationTimeAnchor>) -> Self {
4,264✔
79
        match observed_as {
4,264✔
80
            ChainPosition::Confirmed(a) => Self::Confirmed {
3,939✔
81
                height: a.confirmation_height,
3,939✔
82
                time: a.confirmation_time,
3,939✔
83
            },
3,939✔
84
            ChainPosition::Unconfirmed(_) => Self::Unconfirmed { last_seen: 0 },
325✔
85
        }
86
    }
4,264✔
87
}
88

89
/// A reference to a block in the canonical chain.
90
#[derive(Debug, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)]
44✔
91
#[cfg_attr(
92
    feature = "serde",
93
    derive(serde::Deserialize, serde::Serialize),
×
94
    serde(crate = "serde_crate")
95
)]
96
pub struct BlockId {
97
    /// The height of the block.
98
    pub height: u32,
99
    /// The hash of the block.
100
    pub hash: BlockHash,
101
}
102

103
impl Default for BlockId {
104
    fn default() -> Self {
52✔
105
        Self {
52✔
106
            height: Default::default(),
52✔
107
            hash: BlockHash::from_inner([0u8; 32]),
52✔
108
        }
52✔
109
    }
52✔
110
}
111

112
impl From<(u32, BlockHash)> for BlockId {
113
    fn from((height, hash): (u32, BlockHash)) -> Self {
832✔
114
        Self { height, hash }
832✔
115
    }
832✔
116
}
117

118
impl From<BlockId> for (u32, BlockHash) {
119
    fn from(block_id: BlockId) -> Self {
×
120
        (block_id.height, block_id.hash)
×
121
    }
×
122
}
123

124
impl From<(&u32, &BlockHash)> for BlockId {
125
    fn from((height, hash): (&u32, &BlockHash)) -> Self {
×
126
        Self {
×
127
            height: *height,
×
128
            hash: *hash,
×
129
        }
×
130
    }
×
131
}
132

133
/// An [`Anchor`] implementation that also records the exact confirmation height of the transaction.
134
#[derive(Debug, Default, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)]
54✔
135
#[cfg_attr(
136
    feature = "serde",
137
    derive(serde::Deserialize, serde::Serialize),
×
138
    serde(crate = "serde_crate")
139
)]
140
pub struct ConfirmationHeightAnchor {
141
    /// The anchor block.
142
    pub anchor_block: BlockId,
143

144
    /// The exact confirmation height of the transaction.
145
    ///
146
    /// It is assumed that this value is never larger than the height of the anchor block.
147
    pub confirmation_height: u32,
148
}
149

150
impl Anchor for ConfirmationHeightAnchor {
151
    fn anchor_block(&self) -> BlockId {
1,456✔
152
        self.anchor_block
1,456✔
153
    }
1,456✔
154

155
    fn confirmation_height_upper_bound(&self) -> u32 {
169✔
156
        self.confirmation_height
169✔
157
    }
169✔
158
}
159

160
/// An [`Anchor`] implementation that also records the exact confirmation time and height of the
161
/// transaction.
162
#[derive(Debug, Default, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)]
2,836✔
163
#[cfg_attr(
164
    feature = "serde",
165
    derive(serde::Deserialize, serde::Serialize),
×
166
    serde(crate = "serde_crate")
167
)]
168
pub struct ConfirmationTimeAnchor {
169
    /// The anchor block.
170
    pub anchor_block: BlockId,
171
    /// The confirmation height of the chain data being anchored.
172
    pub confirmation_height: u32,
173
    /// The confirmation time of the chain data being anchored.
174
    pub confirmation_time: u64,
175
}
176

177
impl Anchor for ConfirmationTimeAnchor {
178
    fn anchor_block(&self) -> BlockId {
6,851✔
179
        self.anchor_block
6,851✔
180
    }
6,851✔
181

182
    fn confirmation_height_upper_bound(&self) -> u32 {
65✔
183
        self.confirmation_height
65✔
184
    }
65✔
185
}
186
/// A `TxOut` with as much data as we can retrieve about it
187
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
×
188
pub struct FullTxOut<A> {
189
    /// The location of the `TxOut`.
190
    pub outpoint: OutPoint,
191
    /// The `TxOut`.
192
    pub txout: TxOut,
193
    /// The position of the transaction in `outpoint` in the overall chain.
194
    pub chain_position: ChainPosition<A>,
195
    /// The txid and chain position of the transaction (if any) that has spent this output.
196
    pub spent_by: Option<(ChainPosition<A>, Txid)>,
197
    /// Whether this output is on a coinbase transaction.
198
    pub is_on_coinbase: bool,
199
}
200

201
impl<A: Anchor> FullTxOut<A> {
202
    /// Whether the `txout` is considered mature.
203
    ///
204
    /// Depending on the implementation of [`confirmation_height_upper_bound`] in [`Anchor`], this
205
    /// method may return false-negatives. In other words, interpretted confirmation count may be
206
    /// less than the actual value.
207
    ///
208
    /// [`confirmation_height_upper_bound`]: Anchor::confirmation_height_upper_bound
209
    pub fn is_mature(&self, tip: u32) -> bool {
16✔
210
        if self.is_on_coinbase {
16✔
211
            let tx_height = match &self.chain_position {
12✔
212
                ChainPosition::Confirmed(anchor) => anchor.confirmation_height_upper_bound(),
12✔
213
                ChainPosition::Unconfirmed(_) => {
214
                    debug_assert!(false, "coinbase tx can never be unconfirmed");
×
215
                    return false;
×
216
                }
217
            };
218
            let age = tip.saturating_sub(tx_height);
12✔
219
            if age + 1 < COINBASE_MATURITY {
12✔
220
                return false;
10✔
221
            }
2✔
222
        }
4✔
223

224
        true
6✔
225
    }
16✔
226

227
    /// Whether the utxo is/was/will be spendable with chain `tip`.
228
    ///
229
    /// This method does not take into account the locktime.
230
    ///
231
    /// Depending on the implementation of [`confirmation_height_upper_bound`] in [`Anchor`], this
232
    /// method may return false-negatives. In other words, interpretted confirmation count may be
233
    /// less than the actual value.
234
    ///
235
    /// [`confirmation_height_upper_bound`]: Anchor::confirmation_height_upper_bound
236
    pub fn is_confirmed_and_spendable(&self, tip: u32) -> bool {
11✔
237
        if !self.is_mature(tip) {
11✔
238
            return false;
5✔
239
        }
6✔
240

241
        let confirmation_height = match &self.chain_position {
6✔
242
            ChainPosition::Confirmed(anchor) => anchor.confirmation_height_upper_bound(),
6✔
243
            ChainPosition::Unconfirmed(_) => return false,
×
244
        };
245
        if confirmation_height > tip {
6✔
246
            return false;
×
247
        }
6✔
248

249
        // if the spending tx is confirmed within tip height, the txout is no longer spendable
250
        if let Some((ChainPosition::Confirmed(spending_anchor), _)) = &self.spent_by {
6✔
251
            if spending_anchor.anchor_block().height <= tip {
×
252
                return false;
×
253
            }
×
254
        }
6✔
255

256
        true
6✔
257
    }
11✔
258
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc