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

bitcoindevkit / bdk / 9984976482

18 Jul 2024 03:21AM UTC coverage: 81.557% (-1.9%) from 83.434%
9984976482

Pull #1514

github

web-flow
Merge 1d7ddfe8d into d99b3ef4b
Pull Request #1514: Flexible sqlite, rework persistence, refactor changeset, refactor wallet construction

801 of 1118 new or added lines in 12 files covered. (71.65%)

35 existing lines in 8 files now uncovered.

10927 of 13398 relevant lines covered (81.56%)

16606.33 hits per line

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

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

3
use crate::{Anchor, AnchorFromBlockPosition, 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)]
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 not confirmed and last seen in the mempool at this 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> {
5,772✔
26
        match self {
5,772✔
27
            ChainPosition::Confirmed(a) => ChainPosition::Confirmed(a.clone()),
5,368✔
28
            ChainPosition::Unconfirmed(last_seen) => ChainPosition::Unconfirmed(last_seen),
404✔
29
        }
30
    }
5,772✔
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)]
45
#[cfg_attr(
46
    feature = "serde",
47
    derive(serde::Deserialize, serde::Serialize),
×
48
    serde(crate = "serde_crate")
49
)]
50
pub enum ConfirmationTime {
51
    /// The transaction is confirmed
52
    Confirmed {
53
        /// Confirmation height.
54
        height: u32,
55
        /// Confirmation time in unix seconds.
56
        time: u64,
57
    },
58
    /// The transaction is unconfirmed
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 {
22✔
68
        Self::Unconfirmed { last_seen }
22✔
69
    }
22✔
70

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

77
impl From<ChainPosition<ConfirmationBlockTime>> for ConfirmationTime {
78
    fn from(observed_as: ChainPosition<ConfirmationBlockTime>) -> Self {
7,590✔
79
        match observed_as {
7,590✔
80
            ChainPosition::Confirmed(a) => Self::Confirmed {
7,128✔
81
                height: a.block_id.height,
7,128✔
82
                time: a.confirmation_time,
7,128✔
83
            },
7,128✔
84
            ChainPosition::Unconfirmed(last_seen) => Self::Unconfirmed { last_seen },
462✔
85
        }
86
    }
7,590✔
87
}
88

89
/// A reference to a block in the canonical chain.
90
///
91
/// `BlockId` implements [`Anchor`]. When a transaction is anchored to `BlockId`, the confirmation
92
/// block and anchor block are the same block.
93
#[derive(Debug, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)]
94
#[cfg_attr(
95
    feature = "serde",
UNCOV
96
    derive(serde::Deserialize, serde::Serialize),
×
97
    serde(crate = "serde_crate")
98
)]
99
pub struct BlockId {
100
    /// The height of the block.
101
    pub height: u32,
102
    /// The hash of the block.
103
    pub hash: BlockHash,
104
}
105

106
impl Anchor for BlockId {
107
    fn anchor_block(&self) -> Self {
17,182✔
108
        *self
17,182✔
109
    }
17,182✔
110
}
111

112
impl AnchorFromBlockPosition for BlockId {
113
    fn from_block_position(_block: &bitcoin::Block, block_id: BlockId, _tx_pos: usize) -> Self {
4,246✔
114
        block_id
4,246✔
115
    }
4,246✔
116
}
117

118
impl Default for BlockId {
119
    fn default() -> Self {
2✔
120
        Self {
2✔
121
            height: Default::default(),
2✔
122
            hash: BlockHash::all_zeros(),
2✔
123
        }
2✔
124
    }
2✔
125
}
126

127
impl From<(u32, BlockHash)> for BlockId {
128
    fn from((height, hash): (u32, BlockHash)) -> Self {
15,136✔
129
        Self { height, hash }
15,136✔
130
    }
15,136✔
131
}
132

133
impl From<BlockId> for (u32, BlockHash) {
134
    fn from(block_id: BlockId) -> Self {
×
135
        (block_id.height, block_id.hash)
×
136
    }
×
137
}
138

139
impl From<(&u32, &BlockHash)> for BlockId {
140
    fn from((height, hash): (&u32, &BlockHash)) -> Self {
10,692✔
141
        Self {
10,692✔
142
            height: *height,
10,692✔
143
            hash: *hash,
10,692✔
144
        }
10,692✔
145
    }
10,692✔
146
}
147

148
/// An [`Anchor`] implementation that also records the exact confirmation time of the transaction.
149
///
150
/// Refer to [`Anchor`] for more details.
151
#[derive(Debug, Default, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)]
152
#[cfg_attr(
153
    feature = "serde",
UNCOV
154
    derive(serde::Deserialize, serde::Serialize),
×
155
    serde(crate = "serde_crate")
156
)]
157
pub struct ConfirmationBlockTime {
158
    /// The anchor block.
159
    pub block_id: BlockId,
160
    /// The confirmation time of the transaction being anchored.
161
    pub confirmation_time: u64,
162
}
163

164
impl Anchor for ConfirmationBlockTime {
165
    fn anchor_block(&self) -> BlockId {
30,756✔
166
        self.block_id
30,756✔
167
    }
30,756✔
168

169
    fn confirmation_height_upper_bound(&self) -> u32 {
1,298✔
170
        self.block_id.height
1,298✔
171
    }
1,298✔
172
}
173

174
impl AnchorFromBlockPosition for ConfirmationBlockTime {
175
    fn from_block_position(block: &bitcoin::Block, block_id: BlockId, _tx_pos: usize) -> Self {
×
176
        Self {
×
177
            block_id,
×
178
            confirmation_time: block.header.time as _,
×
179
        }
×
180
    }
×
181
}
182

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

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

221
        true
153✔
222
    }
177✔
223

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

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

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

253
        true
153✔
254
    }
165✔
255
}
256

257
#[cfg(test)]
258
mod test {
259
    use super::*;
260

261
    #[test]
262
    fn chain_position_ord() {
1✔
263
        let unconf1 = ChainPosition::<ConfirmationBlockTime>::Unconfirmed(10);
1✔
264
        let unconf2 = ChainPosition::<ConfirmationBlockTime>::Unconfirmed(20);
1✔
265
        let conf1 = ChainPosition::Confirmed(ConfirmationBlockTime {
1✔
266
            confirmation_time: 20,
1✔
267
            block_id: BlockId {
1✔
268
                height: 9,
1✔
269
                ..Default::default()
1✔
270
            },
1✔
271
        });
1✔
272
        let conf2 = ChainPosition::Confirmed(ConfirmationBlockTime {
1✔
273
            confirmation_time: 15,
1✔
274
            block_id: BlockId {
1✔
275
                height: 12,
1✔
276
                ..Default::default()
1✔
277
            },
1✔
278
        });
1✔
279

1✔
280
        assert!(unconf2 > unconf1, "higher last_seen means higher ord");
1✔
281
        assert!(unconf1 > conf1, "unconfirmed is higher ord than confirmed");
1✔
282
        assert!(
1✔
283
            conf2 > conf1,
1✔
284
            "confirmation_height is higher then it should be higher ord"
×
285
        );
286
    }
1✔
287
}
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