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

bitcoindevkit / bdk / 9638796845

24 Jun 2024 02:52AM UTC coverage: 83.122% (+0.1%) from 83.024%
9638796845

Pull #1486

github

web-flow
Merge 95f76e0f3 into 6dab68d35
Pull Request #1486: refact(chain): Use hash of SPK at index 0 as Descriptor unique ID

7 of 9 new or added lines in 2 files covered. (77.78%)

148 existing lines in 3 files now uncovered.

11214 of 13491 relevant lines covered (83.12%)

16554.98 hits per line

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

82.17
/crates/chain/src/keychain/txout_index.rs
1
use crate::{
2
    collections::*,
3
    indexed_tx_graph::Indexer,
4
    miniscript::{Descriptor, DescriptorPublicKey},
5
    spk_iter::BIP32_MAX_INDEX,
6
    DescriptorExt, DescriptorId, SpkIterator, SpkTxOutIndex,
7
};
8
use alloc::{borrow::ToOwned, vec::Vec};
9
use bitcoin::{Amount, OutPoint, Script, SignedAmount, Transaction, TxOut, Txid};
10
use core::{
11
    fmt::Debug,
12
    ops::{Bound, RangeBounds},
13
};
14

15
use super::*;
16
use crate::Append;
17

18
/// The default lookahead for a [`KeychainTxOutIndex`]
19
pub const DEFAULT_LOOKAHEAD: u32 = 25;
20

21
/// [`KeychainTxOutIndex`] controls how script pubkeys are revealed for multiple keychains, and
22
/// indexes [`TxOut`]s with them.
23
///
24
/// A single keychain is a chain of script pubkeys derived from a single [`Descriptor`]. Keychains
25
/// are identified using the `K` generic. Script pubkeys are identified by the keychain that they
26
/// are derived from `K`, as well as the derivation index `u32`.
27
///
28
/// There is a strict 1-to-1 relationship between descriptors and keychains. Each keychain has one
29
/// and only one descriptor and each descriptor has one and only one keychain. The
30
/// [`insert_descriptor`] method will return an error if you try and violate this invariant. This
31
/// rule is a proxy for a stronger rule: no two descriptors should produce the same script pubkey.
32
/// Having two descriptors produce the same script pubkey should cause whichever keychain derives
33
/// the script pubkey first to be the effective owner of it but you should not rely on this
34
/// behaviour. âš  It is up you, the developer, not to violate this invariant.
35
///
36
/// # Revealed script pubkeys
37
///
38
/// Tracking how script pubkeys are revealed is useful for collecting chain data. For example, if
39
/// the user has requested 5 script pubkeys (to receive money with), we only need to use those
40
/// script pubkeys to scan for chain data.
41
///
42
/// Call [`reveal_to_target`] or [`reveal_next_spk`] to reveal more script pubkeys.
43
/// Call [`revealed_keychain_spks`] or [`revealed_spks`] to iterate through revealed script pubkeys.
44
///
45
/// # Lookahead script pubkeys
46
///
47
/// When an user first recovers a wallet (i.e. from a recovery phrase and/or descriptor), we will
48
/// NOT have knowledge of which script pubkeys are revealed. So when we index a transaction or
49
/// txout (using [`index_tx`]/[`index_txout`]) we scan the txouts against script pubkeys derived
50
/// above the last revealed index. These additionally-derived script pubkeys are called the
51
/// lookahead.
52
///
53
/// The [`KeychainTxOutIndex`] is constructed with the `lookahead` and cannot be altered. See
54
/// [`DEFAULT_LOOKAHEAD`] for the value used in the `Default` implementation. Use [`new`] to set a
55
/// custom `lookahead`.
56
///
57
/// # Unbounded script pubkey iterator
58
///
59
/// For script-pubkey-based chain sources (such as Electrum/Esplora), an initial scan is best done
60
/// by iterating though derived script pubkeys one by one and requesting transaction histories for
61
/// each script pubkey. We will stop after x-number of script pubkeys have empty histories. An
62
/// unbounded script pubkey iterator is useful to pass to such a chain source because it doesn't
63
/// require holding a reference to the index.
64
///
65
/// Call [`unbounded_spk_iter`] to get an unbounded script pubkey iterator for a given keychain.
66
/// Call [`all_unbounded_spk_iters`] to get unbounded script pubkey iterators for all keychains.
67
///
68
/// # Change sets
69
///
70
/// Methods that can update the last revealed index or add keychains will return [`ChangeSet`] to report
71
/// these changes. This should be persisted for future recovery.
72
///
73
/// ## Synopsis
74
///
75
/// ```
76
/// use bdk_chain::keychain::KeychainTxOutIndex;
77
/// # use bdk_chain::{ miniscript::{Descriptor, DescriptorPublicKey} };
78
/// # use core::str::FromStr;
79
///
80
/// // imagine our service has internal and external addresses but also addresses for users
81
/// #[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
82
/// enum MyKeychain {
83
///     External,
84
///     Internal,
85
///     MyAppUser {
86
///         user_id: u32
87
///     }
88
/// }
89
///
90
/// let mut txout_index = KeychainTxOutIndex::<MyKeychain>::default();
91
///
92
/// # let secp = bdk_chain::bitcoin::secp256k1::Secp256k1::signing_only();
93
/// # let (external_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/0/*)").unwrap();
94
/// # let (internal_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/1/*)").unwrap();
95
/// # let (descriptor_42, _) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/2/*)").unwrap();
96
/// let _ = txout_index.insert_descriptor(MyKeychain::External, external_descriptor)?;
97
/// let _ = txout_index.insert_descriptor(MyKeychain::Internal, internal_descriptor)?;
98
/// let _ = txout_index.insert_descriptor(MyKeychain::MyAppUser { user_id: 42 }, descriptor_42)?;
99
///
100
/// let new_spk_for_user = txout_index.reveal_next_spk(&MyKeychain::MyAppUser{ user_id: 42 });
101
/// # Ok::<_, bdk_chain::keychain::InsertDescriptorError<_>>(())
102
/// ```
103
///
104
/// [`Ord`]: core::cmp::Ord
105
/// [`SpkTxOutIndex`]: crate::spk_txout_index::SpkTxOutIndex
106
/// [`Descriptor`]: crate::miniscript::Descriptor
107
/// [`reveal_to_target`]: Self::reveal_to_target
108
/// [`reveal_next_spk`]: Self::reveal_next_spk
109
/// [`revealed_keychain_spks`]: Self::revealed_keychain_spks
110
/// [`revealed_spks`]: Self::revealed_spks
111
/// [`index_tx`]: Self::index_tx
112
/// [`index_txout`]: Self::index_txout
113
/// [`new`]: Self::new
114
/// [`unbounded_spk_iter`]: Self::unbounded_spk_iter
115
/// [`all_unbounded_spk_iters`]: Self::all_unbounded_spk_iters
116
/// [`outpoints`]: Self::outpoints
117
/// [`txouts`]: Self::txouts
118
/// [`unused_spks`]: Self::unused_spks
119
/// [`insert_descriptor`]: Self::insert_descriptor
120
#[derive(Clone, Debug)]
121
pub struct KeychainTxOutIndex<K> {
122
    inner: SpkTxOutIndex<(K, u32)>,
123
    keychain_to_descriptor_id: BTreeMap<K, DescriptorId>,
124
    descriptor_id_to_keychain: HashMap<DescriptorId, K>,
125
    descriptors: HashMap<DescriptorId, Descriptor<DescriptorPublicKey>>,
126
    last_revealed: HashMap<DescriptorId, u32>,
127
    lookahead: u32,
128
}
129

130
impl<K> Default for KeychainTxOutIndex<K> {
131
    fn default() -> Self {
1,455✔
132
        Self::new(DEFAULT_LOOKAHEAD)
1,455✔
133
    }
1,455✔
134
}
135

136
impl<K: Clone + Ord + Debug> Indexer for KeychainTxOutIndex<K> {
137
    type ChangeSet = ChangeSet<K>;
138

139
    fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::ChangeSet {
3,587✔
140
        let mut changeset = ChangeSet::default();
3,587✔
141
        if let Some((keychain, index)) = self.inner.scan_txout(outpoint, txout).cloned() {
3,587✔
142
            let did = self
2,433✔
143
                .keychain_to_descriptor_id
2,433✔
144
                .get(&keychain)
2,433✔
145
                .expect("invariant");
2,433✔
146
            if self.last_revealed.get(did) < Some(&index) {
2,433✔
147
                self.last_revealed.insert(*did, index);
1,094✔
148
                changeset.last_revealed.insert(*did, index);
1,094✔
149
                self.replenish_inner_index(*did, &keychain, self.lookahead);
1,094✔
150
            }
1,339✔
151
        }
1,154✔
152
        changeset
3,587✔
153
    }
3,587✔
154

155
    fn index_tx(&mut self, tx: &bitcoin::Transaction) -> Self::ChangeSet {
2,428✔
156
        let mut changeset = ChangeSet::<K>::default();
2,428✔
157
        let txid = tx.compute_txid();
2,428✔
158
        for (op, txout) in tx.output.iter().enumerate() {
3,574✔
159
            changeset.append(self.index_txout(OutPoint::new(txid, op as u32), txout));
3,566✔
160
        }
3,566✔
161
        changeset
2,428✔
162
    }
2,428✔
163

164
    fn initial_changeset(&self) -> Self::ChangeSet {
152✔
165
        ChangeSet {
152✔
166
            keychains_added: self
152✔
167
                .keychains()
152✔
168
                .map(|(k, v)| (k.clone(), v.clone()))
303✔
169
                .collect(),
152✔
170
            last_revealed: self.last_revealed.clone().into_iter().collect(),
152✔
171
        }
152✔
172
    }
152✔
173

174
    fn apply_changeset(&mut self, changeset: Self::ChangeSet) {
96✔
175
        self.apply_changeset(changeset)
96✔
176
    }
96✔
177

178
    fn is_tx_relevant(&self, tx: &bitcoin::Transaction) -> bool {
9✔
179
        self.inner.is_relevant(tx)
9✔
180
    }
9✔
181
}
182

183
impl<K> KeychainTxOutIndex<K> {
184
    /// Construct a [`KeychainTxOutIndex`] with the given `lookahead`.
185
    ///
186
    /// The `lookahead` is the number of script pubkeys to derive and cache from the internal
187
    /// descriptors over and above the last revealed script index. Without a lookahead the index
188
    /// will miss outputs you own when processing transactions whose output script pubkeys lie
189
    /// beyond the last revealed index. In certain situations, such as when performing an initial
190
    /// scan of the blockchain during wallet import, it may be uncertain or unknown what the index
191
    /// of the last revealed script pubkey actually is.
192
    ///
193
    /// Refer to [struct-level docs](KeychainTxOutIndex) for more about `lookahead`.
194
    pub fn new(lookahead: u32) -> Self {
1,472✔
195
        Self {
1,472✔
196
            inner: SpkTxOutIndex::default(),
1,472✔
197
            keychain_to_descriptor_id: Default::default(),
1,472✔
198
            descriptors: Default::default(),
1,472✔
199
            descriptor_id_to_keychain: Default::default(),
1,472✔
200
            last_revealed: Default::default(),
1,472✔
201
            lookahead,
1,472✔
202
        }
1,472✔
203
    }
1,472✔
204
}
205

206
/// Methods that are *re-exposed* from the internal [`SpkTxOutIndex`].
207
impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
208
    /// Return a reference to the internal [`SpkTxOutIndex`].
209
    ///
210
    /// **WARNING**: The internal index will contain lookahead spks. Refer to
211
    /// [struct-level docs](KeychainTxOutIndex) for more about `lookahead`.
212
    pub fn inner(&self) -> &SpkTxOutIndex<(K, u32)> {
29✔
213
        &self.inner
29✔
214
    }
29✔
215

216
    /// Get the set of indexed outpoints, corresponding to tracked keychains.
217
    pub fn outpoints(&self) -> &BTreeSet<KeychainIndexed<K, OutPoint>> {
1,248✔
218
        self.inner.outpoints()
1,248✔
219
    }
1,248✔
220

221
    /// Iterate over known txouts that spend to tracked script pubkeys.
222
    pub fn txouts(
×
223
        &self,
×
224
    ) -> impl DoubleEndedIterator<Item = KeychainIndexed<K, (OutPoint, &TxOut)>> + ExactSizeIterator
×
225
    {
×
226
        self.inner
×
227
            .txouts()
×
228
            .map(|(index, op, txout)| (index.clone(), (op, txout)))
×
229
    }
×
230

231
    /// Finds all txouts on a transaction that has previously been scanned and indexed.
232
    pub fn txouts_in_tx(
×
233
        &self,
×
234
        txid: Txid,
×
235
    ) -> impl DoubleEndedIterator<Item = KeychainIndexed<K, (OutPoint, &TxOut)>> {
×
236
        self.inner
×
237
            .txouts_in_tx(txid)
×
238
            .map(|(index, op, txout)| (index.clone(), (op, txout)))
×
239
    }
×
240

241
    /// Return the [`TxOut`] of `outpoint` if it has been indexed, and if it corresponds to a
242
    /// tracked keychain.
243
    ///
244
    /// The associated keychain and keychain index of the txout's spk is also returned.
245
    ///
246
    /// This calls [`SpkTxOutIndex::txout`] internally.
247
    pub fn txout(&self, outpoint: OutPoint) -> Option<KeychainIndexed<K, &TxOut>> {
72✔
248
        self.inner
72✔
249
            .txout(outpoint)
72✔
250
            .map(|(index, txout)| (index.clone(), txout))
72✔
251
    }
72✔
252

253
    /// Return the script that exists under the given `keychain`'s `index`.
254
    ///
255
    /// This calls [`SpkTxOutIndex::spk_at_index`] internally.
256
    pub fn spk_at_index(&self, keychain: K, index: u32) -> Option<&Script> {
×
257
        self.inner.spk_at_index(&(keychain.clone(), index))
×
258
    }
×
259

260
    /// Returns the keychain and keychain index associated with the spk.
261
    ///
262
    /// This calls [`SpkTxOutIndex::index_of_spk`] internally.
263
    pub fn index_of_spk(&self, script: &Script) -> Option<&(K, u32)> {
7,216✔
264
        self.inner.index_of_spk(script)
7,216✔
265
    }
7,216✔
266

267
    /// Returns whether the spk under the `keychain`'s `index` has been used.
268
    ///
269
    /// Here, "unused" means that after the script pubkey was stored in the index, the index has
270
    /// never scanned a transaction output with it.
271
    ///
272
    /// This calls [`SpkTxOutIndex::is_used`] internally.
273
    pub fn is_used(&self, keychain: K, index: u32) -> bool {
×
274
        self.inner.is_used(&(keychain, index))
×
275
    }
×
276

277
    /// Marks the script pubkey at `index` as used even though the tracker hasn't seen an output
278
    /// with it.
279
    ///
280
    /// This only has an effect when the `index` had been added to `self` already and was unused.
281
    ///
282
    /// Returns whether the spk under the given `keychain` and `index` is successfully
283
    /// marked as used. Returns false either when there is no descriptor under the given
284
    /// keychain, or when the spk is already marked as used.
285
    ///
286
    /// This is useful when you want to reserve a script pubkey for something but don't want to add
287
    /// the transaction output using it to the index yet. Other callers will consider `index` on
288
    /// `keychain` used until you call [`unmark_used`].
289
    ///
290
    /// This calls [`SpkTxOutIndex::mark_used`] internally.
291
    ///
292
    /// [`unmark_used`]: Self::unmark_used
293
    pub fn mark_used(&mut self, keychain: K, index: u32) -> bool {
759✔
294
        self.inner.mark_used(&(keychain, index))
759✔
295
    }
759✔
296

297
    /// Undoes the effect of [`mark_used`]. Returns whether the `index` is inserted back into
298
    /// `unused`.
299
    ///
300
    /// Note that if `self` has scanned an output with this script pubkey, then this will have no
301
    /// effect.
302
    ///
303
    /// This calls [`SpkTxOutIndex::unmark_used`] internally.
304
    ///
305
    /// [`mark_used`]: Self::mark_used
306
    pub fn unmark_used(&mut self, keychain: K, index: u32) -> bool {
32✔
307
        self.inner.unmark_used(&(keychain, index))
32✔
308
    }
32✔
309

310
    /// Computes the total value transfer effect `tx` has on the script pubkeys belonging to the
311
    /// keychains in `range`. Value is *sent* when a script pubkey in the `range` is on an input and
312
    /// *received* when it is on an output. For `sent` to be computed correctly, the output being
313
    /// spent must have already been scanned by the index. Calculating received just uses the
314
    /// [`Transaction`] outputs directly, so it will be correct even if it has not been scanned.
315
    pub fn sent_and_received(
224✔
316
        &self,
224✔
317
        tx: &Transaction,
224✔
318
        range: impl RangeBounds<K>,
224✔
319
    ) -> (Amount, Amount) {
224✔
320
        self.inner
224✔
321
            .sent_and_received(tx, self.map_to_inner_bounds(range))
224✔
322
    }
224✔
323

324
    /// Computes the net value that this transaction gives to the script pubkeys in the index and
325
    /// *takes* from the transaction outputs in the index. Shorthand for calling
326
    /// [`sent_and_received`] and subtracting sent from received.
327
    ///
328
    /// This calls [`SpkTxOutIndex::net_value`] internally.
329
    ///
330
    /// [`sent_and_received`]: Self::sent_and_received
331
    pub fn net_value(&self, tx: &Transaction, range: impl RangeBounds<K>) -> SignedAmount {
2✔
332
        self.inner.net_value(tx, self.map_to_inner_bounds(range))
2✔
333
    }
2✔
334
}
335

336
impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
337
    /// Return all keychains and their corresponding descriptors.
338
    pub fn keychains(
8,871✔
339
        &self,
8,871✔
340
    ) -> impl DoubleEndedIterator<Item = (&K, &Descriptor<DescriptorPublicKey>)> + ExactSizeIterator + '_
8,871✔
341
    {
8,871✔
342
        self.keychain_to_descriptor_id
8,871✔
343
            .iter()
8,871✔
344
            .map(|(k, did)| (k, self.descriptors.get(did).expect("invariant")))
12,367✔
345
    }
8,871✔
346

347
    /// Insert a descriptor with a keychain associated to it.
348
    ///
349
    /// Adding a descriptor means you will be able to derive new script pubkeys under it and the
350
    /// txout index will discover transaction outputs with those script pubkeys (once they've been
351
    /// derived and added to the index).
352
    ///
353
    /// keychain <-> descriptor is a one-to-one mapping that cannot be changed. Attempting to do so
354
    /// will return a [`InsertDescriptorError<K>`].
355
    pub fn insert_descriptor(
3,141✔
356
        &mut self,
3,141✔
357
        keychain: K,
3,141✔
358
        descriptor: Descriptor<DescriptorPublicKey>,
3,141✔
359
    ) -> Result<ChangeSet<K>, InsertDescriptorError<K>> {
3,141✔
360
        let mut changeset = ChangeSet::<K>::default();
3,141✔
361
        let did = descriptor.descriptor_id();
3,141✔
362
        if !self.keychain_to_descriptor_id.contains_key(&keychain)
3,141✔
363
            && !self.descriptor_id_to_keychain.contains_key(&did)
2,945✔
364
        {
2,928✔
365
            self.descriptors.insert(did, descriptor.clone());
2,928✔
366
            self.keychain_to_descriptor_id.insert(keychain.clone(), did);
2,928✔
367
            self.descriptor_id_to_keychain.insert(did, keychain.clone());
2,928✔
368
            self.replenish_inner_index(did, &keychain, self.lookahead);
2,928✔
369
            changeset
2,928✔
370
                .keychains_added
2,928✔
371
                .insert(keychain.clone(), descriptor);
2,928✔
372
        } else {
2,928✔
373
            if let Some(existing_desc_id) = self.keychain_to_descriptor_id.get(&keychain) {
213✔
374
                let descriptor = self.descriptors.get(existing_desc_id).expect("invariant");
196✔
375
                if *existing_desc_id != did {
196✔
376
                    return Err(InsertDescriptorError::KeychainAlreadyAssigned {
3✔
377
                        existing_assignment: descriptor.clone(),
3✔
378
                        keychain,
3✔
379
                    });
3✔
380
                }
193✔
381
            }
17✔
382

383
            if let Some(existing_keychain) = self.descriptor_id_to_keychain.get(&did) {
210✔
384
                let descriptor = self.descriptors.get(&did).expect("invariant").clone();
210✔
385

210✔
386
                if *existing_keychain != keychain {
210✔
387
                    return Err(InsertDescriptorError::DescriptorAlreadyAssigned {
17✔
388
                        existing_assignment: existing_keychain.clone(),
17✔
389
                        descriptor,
17✔
390
                    });
17✔
391
                }
193✔
UNCOV
392
            }
×
393
        }
394

395
        Ok(changeset)
3,121✔
396
    }
3,141✔
397

398
    /// Gets the descriptor associated with the keychain. Returns `None` if the keychain doesn't
399
    /// have a descriptor associated with it.
400
    pub fn get_descriptor(&self, keychain: &K) -> Option<&Descriptor<DescriptorPublicKey>> {
1,240✔
401
        let did = self.keychain_to_descriptor_id.get(keychain)?;
1,240✔
402
        self.descriptors.get(did)
1,240✔
403
    }
1,240✔
404

405
    /// Get the lookahead setting.
406
    ///
407
    /// Refer to [`new`] for more information on the `lookahead`.
408
    ///
409
    /// [`new`]: Self::new
UNCOV
410
    pub fn lookahead(&self) -> u32 {
×
UNCOV
411
        self.lookahead
×
UNCOV
412
    }
×
413

414
    /// Store lookahead scripts until `target_index` (inclusive).
415
    ///
416
    /// This does not change the global `lookahead` setting.
417
    pub fn lookahead_to_target(&mut self, keychain: &K, target_index: u32) {
8✔
418
        if let Some((next_index, _)) = self.next_index(keychain) {
8✔
419
            let temp_lookahead = (target_index + 1)
8✔
420
                .checked_sub(next_index)
8✔
421
                .filter(|&index| index > 0);
8✔
422

423
            if let Some(temp_lookahead) = temp_lookahead {
8✔
424
                self.replenish_inner_index_keychain(keychain, temp_lookahead);
8✔
425
            }
8✔
UNCOV
426
        }
×
427
    }
8✔
428

429
    fn replenish_inner_index_did(&mut self, did: DescriptorId, lookahead: u32) {
16✔
430
        if let Some(keychain) = self.descriptor_id_to_keychain.get(&did).cloned() {
16✔
431
            self.replenish_inner_index(did, &keychain, lookahead);
16✔
432
        }
16✔
433
    }
16✔
434

435
    fn replenish_inner_index_keychain(&mut self, keychain: &K, lookahead: u32) {
8✔
436
        if let Some(did) = self.keychain_to_descriptor_id.get(keychain) {
8✔
437
            self.replenish_inner_index(*did, keychain, lookahead);
8✔
438
        }
8✔
439
    }
8✔
440

441
    /// Syncs the state of the inner spk index after changes to a keychain
442
    fn replenish_inner_index(&mut self, did: DescriptorId, keychain: &K, lookahead: u32) {
5,361✔
443
        let descriptor = self.descriptors.get(&did).expect("invariant");
5,361✔
444
        let next_store_index = self
5,361✔
445
            .inner
5,361✔
446
            .all_spks()
5,361✔
447
            .range(&(keychain.clone(), u32::MIN)..=&(keychain.clone(), u32::MAX))
5,361✔
448
            .last()
5,361✔
449
            .map_or(0, |((_, index), _)| *index + 1);
5,361✔
450
        let next_reveal_index = self.last_revealed.get(&did).map_or(0, |v| *v + 1);
5,361✔
451
        for (new_index, new_spk) in
20,588✔
452
            SpkIterator::new_with_range(descriptor, next_store_index..next_reveal_index + lookahead)
5,361✔
453
        {
454
            let _inserted = self
20,588✔
455
                .inner
20,588✔
456
                .insert_spk((keychain.clone(), new_index), new_spk);
20,588✔
457
            debug_assert!(_inserted, "replenish lookahead: must not have existing spk: keychain={:?}, lookahead={}, next_store_index={}, next_reveal_index={}", keychain, lookahead, next_store_index, next_reveal_index);
20,588✔
458
        }
459
    }
5,361✔
460

461
    /// Get an unbounded spk iterator over a given `keychain`. Returns `None` if the provided
462
    /// keychain doesn't exist
463
    pub fn unbounded_spk_iter(
1,240✔
464
        &self,
1,240✔
465
        keychain: &K,
1,240✔
466
    ) -> Option<SpkIterator<Descriptor<DescriptorPublicKey>>> {
1,240✔
467
        let descriptor = self.get_descriptor(keychain)?.clone();
1,240✔
468
        Some(SpkIterator::new(descriptor))
1,240✔
469
    }
1,240✔
470

471
    /// Get unbounded spk iterators for all keychains.
UNCOV
472
    pub fn all_unbounded_spk_iters(
×
UNCOV
473
        &self,
×
UNCOV
474
    ) -> BTreeMap<K, SpkIterator<Descriptor<DescriptorPublicKey>>> {
×
UNCOV
475
        self.keychain_to_descriptor_id
×
UNCOV
476
            .iter()
×
UNCOV
477
            .map(|(k, did)| {
×
UNCOV
478
                (
×
479
                    k.clone(),
×
480
                    SpkIterator::new(self.descriptors.get(did).expect("invariant").clone()),
×
481
                )
×
482
            })
×
483
            .collect()
×
484
    }
×
485

486
    /// Iterate over revealed spks of keychains in `range`
487
    pub fn revealed_spks(
2✔
488
        &self,
2✔
489
        range: impl RangeBounds<K>,
2✔
490
    ) -> impl Iterator<Item = KeychainIndexed<K, &Script>> {
2✔
491
        let start = range.start_bound();
2✔
492
        let end = range.end_bound();
2✔
493
        let mut iter_last_revealed = self
2✔
494
            .keychain_to_descriptor_id
2✔
495
            .range((start, end))
2✔
496
            .map(|(k, did)| (k, self.last_revealed.get(did).cloned()));
10✔
497
        let mut iter_spks = self
2✔
498
            .inner
2✔
499
            .all_spks()
2✔
500
            .range(self.map_to_inner_bounds((start, end)));
2✔
501
        let mut current_keychain = iter_last_revealed.next();
2✔
502
        // The reason we need a tricky algorithm is because of the "lookahead" feature which means
2✔
503
        // that some of the spks in the SpkTxoutIndex will not have been revealed yet. So we need to
2✔
504
        // filter out those spks that are above the last_revealed for that keychain. To do this we
2✔
505
        // iterate through the last_revealed for each keychain and the spks for each keychain in
2✔
506
        // tandem. This minimizes BTreeMap queries.
2✔
507
        core::iter::from_fn(move || loop {
11✔
508
            let ((keychain, index), spk) = iter_spks.next()?;
11✔
509
            // We need to find the last revealed that matches the current spk we are considering so
510
            // we skip ahead.
511
            while current_keychain?.0 < keychain {
17✔
512
                current_keychain = iter_last_revealed.next();
8✔
513
            }
8✔
514
            let (current_keychain, last_revealed) = current_keychain?;
9✔
515

516
            if current_keychain == keychain && Some(*index) <= last_revealed {
9✔
517
                break Some(((keychain.clone(), *index), spk.as_script()));
9✔
UNCOV
518
            }
×
519
        })
11✔
520
    }
2✔
521

522
    /// Iterate over revealed spks of the given `keychain` with ascending indices.
523
    ///
524
    /// This is a double ended iterator so you can easily reverse it to get an iterator where
525
    /// the script pubkeys that were most recently revealed are first.
526
    pub fn revealed_keychain_spks<'a>(
82✔
527
        &'a self,
82✔
528
        keychain: &'a K,
82✔
529
    ) -> impl DoubleEndedIterator<Item = Indexed<&Script>> + 'a {
82✔
530
        let end = self
82✔
531
            .last_revealed_index(keychain)
82✔
532
            .map(|v| v + 1)
82✔
533
            .unwrap_or(0);
82✔
534
        self.inner
82✔
535
            .all_spks()
82✔
536
            .range((keychain.clone(), 0)..(keychain.clone(), end))
82✔
537
            .map(|((_, index), spk)| (*index, spk.as_script()))
1,136✔
538
    }
82✔
539

540
    /// Iterate over revealed, but unused, spks of all keychains.
UNCOV
541
    pub fn unused_spks(
×
UNCOV
542
        &self,
×
UNCOV
543
    ) -> impl DoubleEndedIterator<Item = KeychainIndexed<K, &Script>> + Clone {
×
UNCOV
544
        self.keychain_to_descriptor_id.keys().flat_map(|keychain| {
×
UNCOV
545
            self.unused_keychain_spks(keychain)
×
UNCOV
546
                .map(|(i, spk)| ((keychain.clone(), i), spk))
×
UNCOV
547
        })
×
548
    }
×
549

550
    /// Iterate over revealed, but unused, spks of the given `keychain`.
551
    /// Returns an empty iterator if the provided keychain doesn't exist.
552
    pub fn unused_keychain_spks(
1,661✔
553
        &self,
1,661✔
554
        keychain: &K,
1,661✔
555
    ) -> impl DoubleEndedIterator<Item = Indexed<&Script>> + Clone {
1,661✔
556
        let end = match self.keychain_to_descriptor_id.get(keychain) {
1,661✔
557
            Some(did) => self.last_revealed.get(did).map(|v| *v + 1).unwrap_or(0),
1,661✔
UNCOV
558
            None => 0,
×
559
        };
560

561
        self.inner
1,661✔
562
            .unused_spks((keychain.clone(), 0)..(keychain.clone(), end))
1,661✔
563
            .map(|((_, i), spk)| (*i, spk))
1,829✔
564
    }
1,661✔
565

566
    /// Get the next derivation index for `keychain`. The next index is the index after the last revealed
567
    /// derivation index.
568
    ///
569
    /// The second field in the returned tuple represents whether the next derivation index is new.
570
    /// There are two scenarios where the next derivation index is reused (not new):
571
    ///
572
    /// 1. The keychain's descriptor has no wildcard, and a script has already been revealed.
573
    /// 2. The number of revealed scripts has already reached 2^31 (refer to BIP-32).
574
    ///
575
    /// Not checking the second field of the tuple may result in address reuse.
576
    ///
577
    /// Returns None if the provided `keychain` doesn't exist.
578
    pub fn next_index(&self, keychain: &K) -> Option<(u32, bool)> {
2,381✔
579
        let did = self.keychain_to_descriptor_id.get(keychain)?;
2,381✔
580
        let last_index = self.last_revealed.get(did).cloned();
2,381✔
581
        let descriptor = self.descriptors.get(did).expect("invariant");
2,381✔
582

2,381✔
583
        // we can only get the next index if the wildcard exists.
2,381✔
584
        let has_wildcard = descriptor.has_wildcard();
2,381✔
585

586
        Some(match last_index {
897✔
587
            // if there is no index, next_index is always 0.
588
            None => (0, true),
792✔
589
            // descriptors without wildcards can only have one index.
590
            Some(_) if !has_wildcard => (0, false),
692✔
591
            // derivation index must be < 2^31 (BIP-32).
592
            Some(index) if index > BIP32_MAX_INDEX => {
897✔
UNCOV
593
                unreachable!("index is out of bounds")
×
594
            }
595
            Some(index) if index == BIP32_MAX_INDEX => (index, false),
897✔
596
            // get the next derivation index.
597
            Some(index) => (index + 1, true),
897✔
598
        })
599
    }
2,381✔
600

601
    /// Get the last derivation index that is revealed for each keychain.
602
    ///
603
    /// Keychains with no revealed indices will not be included in the returned [`BTreeMap`].
604
    pub fn last_revealed_indices(&self) -> BTreeMap<K, u32> {
5✔
605
        self.last_revealed
5✔
606
            .iter()
5✔
607
            .filter_map(|(desc_id, index)| {
6✔
608
                let keychain = self.descriptor_id_to_keychain.get(desc_id)?;
6✔
609
                Some((keychain.clone(), *index))
6✔
610
            })
6✔
611
            .collect()
5✔
612
    }
5✔
613

614
    /// Get the last derivation index revealed for `keychain`. Returns None if the keychain doesn't
615
    /// exist, or if the keychain doesn't have any revealed scripts.
616
    pub fn last_revealed_index(&self, keychain: &K) -> Option<u32> {
168✔
617
        let descriptor_id = self.keychain_to_descriptor_id.get(keychain)?;
168✔
618
        self.last_revealed.get(descriptor_id).cloned()
168✔
619
    }
168✔
620

621
    /// Convenience method to call [`Self::reveal_to_target`] on multiple keychains.
622
    pub fn reveal_to_target_multi(&mut self, keychains: &BTreeMap<K, u32>) -> ChangeSet<K> {
2✔
623
        let mut changeset = ChangeSet::default();
2✔
624

625
        for (keychain, &index) in keychains {
6✔
626
            if let Some((_, new_changeset)) = self.reveal_to_target(keychain, index) {
4✔
627
                changeset.append(new_changeset);
4✔
628
            }
4✔
629
        }
630

631
        changeset
2✔
632
    }
2✔
633

634
    /// Reveals script pubkeys of the `keychain`'s descriptor **up to and including** the
635
    /// `target_index`.
636
    ///
637
    /// If the `target_index` cannot be reached (due to the descriptor having no wildcard and/or
638
    /// the `target_index` is in the hardened index range), this method will make a best-effort and
639
    /// reveal up to the last possible index.
640
    ///
641
    /// This returns list of newly revealed indices (alongside their scripts) and a
642
    /// [`ChangeSet`], which reports updates to the latest revealed index. If no new script
643
    /// pubkeys are revealed, then both of these will be empty.
644
    ///
645
    /// Returns None if the provided `keychain` doesn't exist.
646
    #[must_use]
647
    pub fn reveal_to_target(
47✔
648
        &mut self,
47✔
649
        keychain: &K,
47✔
650
        target_index: u32,
47✔
651
    ) -> Option<(Vec<Indexed<ScriptBuf>>, ChangeSet<K>)> {
47✔
652
        let mut changeset = ChangeSet::default();
47✔
653
        let mut spks: Vec<Indexed<ScriptBuf>> = vec![];
47✔
654
        while let Some((i, new)) = self.next_index(keychain) {
364✔
655
            if !new || i > target_index {
364✔
656
                break;
47✔
657
            }
317✔
658
            match self.reveal_next_spk(keychain) {
317✔
659
                Some(((i, spk), change)) => {
317✔
660
                    spks.push((i, spk));
317✔
661
                    changeset.append(change);
317✔
662
                }
317✔
UNCOV
663
                None => break,
×
664
            }
665
        }
666

667
        Some((spks, changeset))
47✔
668
    }
47✔
669

670
    /// Attempts to reveal the next script pubkey for `keychain`.
671
    ///
672
    /// Returns the derivation index of the revealed script pubkey, the revealed script pubkey and a
673
    /// [`ChangeSet`] which represents changes in the last revealed index (if any).
674
    /// Returns None if the provided keychain doesn't exist.
675
    ///
676
    /// When a new script cannot be revealed, we return the last revealed script and an empty
677
    /// [`ChangeSet`]. There are two scenarios when a new script pubkey cannot be derived:
678
    ///
679
    ///  1. The descriptor has no wildcard and already has one script revealed.
680
    ///  2. The descriptor has already revealed scripts up to the numeric bound.
681
    ///  3. There is no descriptor associated with the given keychain.
682
    pub fn reveal_next_spk(&mut self, keychain: &K) -> Option<(Indexed<ScriptBuf>, ChangeSet<K>)> {
2,005✔
683
        let (next_index, new) = self.next_index(keychain)?;
2,005✔
684
        let mut changeset = ChangeSet::default();
2,005✔
685

2,005✔
686
        if new {
2,005✔
687
            let did = self.keychain_to_descriptor_id.get(keychain)?;
1,315✔
688
            self.last_revealed.insert(*did, next_index);
1,315✔
689
            changeset.last_revealed.insert(*did, next_index);
1,315✔
690
            self.replenish_inner_index(*did, keychain, self.lookahead);
1,315✔
691
        }
690✔
692
        let script = self
2,005✔
693
            .inner
2,005✔
694
            .spk_at_index(&(keychain.clone(), next_index))
2,005✔
695
            .expect("we just inserted it");
2,005✔
696
        Some(((next_index, script.into()), changeset))
2,005✔
697
    }
2,005✔
698

699
    /// Gets the next unused script pubkey in the keychain. I.e., the script pubkey with the lowest
700
    /// index that has not been used yet.
701
    ///
702
    /// This will derive and reveal a new script pubkey if no more unused script pubkeys exist.
703
    ///
704
    /// If the descriptor has no wildcard and already has a used script pubkey or if a descriptor
705
    /// has used all scripts up to the derivation bounds, then the last derived script pubkey will be
706
    /// returned.
707
    ///
708
    /// Returns `None` if there are no script pubkeys that have been used and no new script pubkey
709
    /// could be revealed (see [`reveal_next_spk`] for when this happens).
710
    ///
711
    /// [`reveal_next_spk`]: Self::reveal_next_spk
712
    pub fn next_unused_spk(&mut self, keychain: &K) -> Option<(Indexed<ScriptBuf>, ChangeSet<K>)> {
1,597✔
713
        let next_unused = self
1,597✔
714
            .unused_keychain_spks(keychain)
1,597✔
715
            .next()
1,597✔
716
            .map(|(i, spk)| ((i, spk.to_owned()), ChangeSet::default()));
1,597✔
717

1,597✔
718
        next_unused.or_else(|| self.reveal_next_spk(keychain))
1,597✔
719
    }
1,597✔
720

721
    /// Iterate over all [`OutPoint`]s that have `TxOut`s with script pubkeys derived from
722
    /// `keychain`.
723
    pub fn keychain_outpoints<'a>(
4✔
724
        &'a self,
4✔
725
        keychain: &'a K,
4✔
726
    ) -> impl DoubleEndedIterator<Item = Indexed<OutPoint>> + 'a {
4✔
727
        self.keychain_outpoints_in_range(keychain..=keychain)
4✔
728
            .map(|((_, i), op)| (i, op))
10✔
729
    }
4✔
730

731
    /// Iterate over [`OutPoint`]s that have script pubkeys derived from keychains in `range`.
732
    pub fn keychain_outpoints_in_range<'a>(
4✔
733
        &'a self,
4✔
734
        range: impl RangeBounds<K> + 'a,
4✔
735
    ) -> impl DoubleEndedIterator<Item = KeychainIndexed<K, OutPoint>> + 'a {
4✔
736
        self.inner
4✔
737
            .outputs_in_range(self.map_to_inner_bounds(range))
4✔
738
            .map(|((k, i), op)| ((k.clone(), *i), op))
10✔
739
    }
4✔
740

741
    fn map_to_inner_bounds(&self, bound: impl RangeBounds<K>) -> impl RangeBounds<(K, u32)> {
232✔
742
        let start = match bound.start_bound() {
232✔
743
            Bound::Included(keychain) => Bound::Included((keychain.clone(), u32::MIN)),
8✔
UNCOV
744
            Bound::Excluded(keychain) => Bound::Excluded((keychain.clone(), u32::MAX)),
×
745
            Bound::Unbounded => Bound::Unbounded,
224✔
746
        };
747
        let end = match bound.end_bound() {
232✔
748
            Bound::Included(keychain) => Bound::Included((keychain.clone(), u32::MAX)),
4✔
749
            Bound::Excluded(keychain) => Bound::Excluded((keychain.clone(), u32::MIN)),
4✔
750
            Bound::Unbounded => Bound::Unbounded,
224✔
751
        };
752

753
        (start, end)
232✔
754
    }
232✔
755

756
    /// Returns the highest derivation index of the `keychain` where [`KeychainTxOutIndex`] has
757
    /// found a [`TxOut`] with it's script pubkey.
758
    pub fn last_used_index(&self, keychain: &K) -> Option<u32> {
4✔
759
        self.keychain_outpoints(keychain).last().map(|(i, _)| i)
4✔
760
    }
4✔
761

762
    /// Returns the highest derivation index of each keychain that [`KeychainTxOutIndex`] has found
763
    /// a [`TxOut`] with it's script pubkey.
UNCOV
764
    pub fn last_used_indices(&self) -> BTreeMap<K, u32> {
×
UNCOV
765
        self.keychain_to_descriptor_id
×
UNCOV
766
            .iter()
×
UNCOV
767
            .filter_map(|(keychain, _)| {
×
UNCOV
768
                self.last_used_index(keychain)
×
UNCOV
769
                    .map(|index| (keychain.clone(), index))
×
UNCOV
770
            })
×
771
            .collect()
×
772
    }
×
773

774
    /// Applies the `ChangeSet<K>` to the [`KeychainTxOutIndex<K>`]
775
    ///
776
    /// Keychains added by the `keychains_added` field of `ChangeSet<K>` respect the one-to-one
777
    /// keychain <-> descriptor invariant by silently ignoring attempts to violate it (but will
778
    /// panic if `debug_assertions` are enabled).
779
    pub fn apply_changeset(&mut self, changeset: ChangeSet<K>) {
98✔
780
        let ChangeSet {
98✔
781
            keychains_added,
98✔
782
            last_revealed,
98✔
783
        } = changeset;
98✔
784
        for (keychain, descriptor) in keychains_added {
292✔
785
            let _ignore_invariant_violation = self.insert_descriptor(keychain, descriptor);
194✔
786
        }
194✔
787

788
        for (&desc_id, &index) in &last_revealed {
114✔
789
            let v = self.last_revealed.entry(desc_id).or_default();
16✔
790
            *v = index.max(*v);
16✔
791
        }
16✔
792

793
        for did in last_revealed.keys() {
98✔
794
            self.replenish_inner_index_did(*did, self.lookahead);
16✔
795
        }
16✔
796
    }
98✔
797
}
798

799
#[derive(Clone, Debug, PartialEq)]
800
/// Error returned from [`KeychainTxOutIndex::insert_descriptor`]
801
pub enum InsertDescriptorError<K> {
802
    /// The descriptor has already been assigned to a keychain so you can't assign it to another
803
    DescriptorAlreadyAssigned {
804
        /// The descriptor you have attempted to reassign
805
        descriptor: Descriptor<DescriptorPublicKey>,
806
        /// The keychain that the descriptor is already assigned to
807
        existing_assignment: K,
808
    },
809
    /// The keychain is already assigned to a descriptor so you can't reassign it
810
    KeychainAlreadyAssigned {
811
        /// The keychain that you have attempted to reassign
812
        keychain: K,
813
        /// The descriptor that the keychain is already assigned to
814
        existing_assignment: Descriptor<DescriptorPublicKey>,
815
    },
816
}
817

818
impl<K: core::fmt::Debug> core::fmt::Display for InsertDescriptorError<K> {
UNCOV
819
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
×
UNCOV
820
        match self {
×
821
            InsertDescriptorError::DescriptorAlreadyAssigned {
UNCOV
822
                existing_assignment: existing,
×
UNCOV
823
                descriptor,
×
UNCOV
824
            } => {
×
UNCOV
825
                write!(
×
826
                    f,
×
827
                    "attempt to re-assign descriptor {descriptor:?} already assigned to {existing:?}"
×
UNCOV
828
                )
×
829
            }
830
            InsertDescriptorError::KeychainAlreadyAssigned {
831
                existing_assignment: existing,
×
832
                keychain,
×
833
            } => {
×
834
                write!(
×
835
                    f,
×
UNCOV
836
                    "attempt to re-assign keychain {keychain:?} already assigned to {existing:?}"
×
UNCOV
837
                )
×
838
            }
839
        }
840
    }
×
841
}
842

843
#[cfg(feature = "std")]
844
impl<K: core::fmt::Debug> std::error::Error for InsertDescriptorError<K> {}
845

846
/// Represents updates to the derivation index of a [`KeychainTxOutIndex`].
847
/// It maps each keychain `K` to a descriptor and its last revealed index.
848
///
849
/// It can be applied to [`KeychainTxOutIndex`] with [`apply_changeset`].
850
///
851
/// The `last_revealed` field is monotone in that [`append`] will never decrease it.
852
/// `keychains_added` is *not* monotone, once it is set any attempt to change it is subject to the
853
/// same *one-to-one* keychain <-> descriptor mapping invariant as [`KeychainTxOutIndex`] itself.
854
///
855
/// [`KeychainTxOutIndex`]: crate::keychain::KeychainTxOutIndex
856
/// [`apply_changeset`]: crate::keychain::KeychainTxOutIndex::apply_changeset
857
/// [`append`]: Self::append
858
#[derive(Clone, Debug, PartialEq)]
859
#[cfg_attr(
860
    feature = "serde",
861
    derive(serde::Deserialize, serde::Serialize),
6✔
862
    serde(
863
        crate = "serde_crate",
864
        bound(
865
            deserialize = "K: Ord + serde::Deserialize<'de>",
866
            serialize = "K: Ord + serde::Serialize"
867
        )
868
    )
869
)]
870
#[must_use]
871
pub struct ChangeSet<K> {
872
    /// Contains the keychains that have been added and their respective descriptor
873
    pub keychains_added: BTreeMap<K, Descriptor<DescriptorPublicKey>>,
874
    /// Contains for each descriptor_id the last revealed index of derivation
875
    pub last_revealed: BTreeMap<DescriptorId, u32>,
876
}
877

878
impl<K: Ord> Append for ChangeSet<K> {
879
    /// Merge another [`ChangeSet<K>`] into self.
880
    ///
881
    /// For the `keychains_added` field this method respects the invariants of
882
    /// [`insert_descriptor`]. `last_revealed` always becomes the larger of the two.
883
    ///
884
    /// [`insert_descriptor`]: KeychainTxOutIndex::insert_descriptor
885
    fn append(&mut self, other: Self) {
17,400✔
886
        for (new_keychain, new_descriptor) in other.keychains_added {
17,406✔
887
            // enforce 1-to-1 invariance
888
            if !self.keychains_added.contains_key(&new_keychain)
6✔
889
                // FIXME: very inefficient
890
                && self
6✔
891
                    .keychains_added
6✔
892
                    .values()
6✔
893
                    .all(|descriptor| descriptor != &new_descriptor)
6✔
894
            {
6✔
895
                self.keychains_added.insert(new_keychain, new_descriptor);
6✔
896
            }
6✔
897
        }
898

899
        // for `last_revealed`, entries of `other` will take precedence ONLY if it is greater than
900
        // what was originally in `self`.
901
        for (desc_id, index) in other.last_revealed {
23,058✔
902
            use crate::collections::btree_map::Entry;
903
            match self.last_revealed.entry(desc_id) {
5,658✔
904
                Entry::Vacant(entry) => {
5,150✔
905
                    entry.insert(index);
5,150✔
906
                }
5,150✔
907
                Entry::Occupied(mut entry) => {
508✔
908
                    if *entry.get() < index {
508✔
909
                        entry.insert(index);
507✔
910
                    }
507✔
911
                }
912
            }
913
        }
914
    }
17,400✔
915

916
    /// Returns whether the changeset are empty.
917
    fn is_empty(&self) -> bool {
4✔
918
        self.last_revealed.is_empty() && self.keychains_added.is_empty()
4✔
919
    }
4✔
920
}
921

922
impl<K> Default for ChangeSet<K> {
923
    fn default() -> Self {
28,043✔
924
        Self {
28,043✔
925
            last_revealed: BTreeMap::default(),
28,043✔
926
            keychains_added: BTreeMap::default(),
28,043✔
927
        }
28,043✔
928
    }
28,043✔
929
}
930

931
#[derive(Clone, Debug, Eq, PartialEq)]
932
/// The keychain doesn't exist. Most likley hasn't been inserted with [`KeychainTxOutIndex::insert_descriptor`].
933
pub struct NoSuchKeychain<K>(K);
934

935
impl<K: Debug> core::fmt::Display for NoSuchKeychain<K> {
UNCOV
936
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
×
UNCOV
937
        write!(f, "no such keychain {:?} exists", &self.0)
×
UNCOV
938
    }
×
939
}
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