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

bitcoindevkit / bdk / 9519613010

14 Jun 2024 04:52PM CUT coverage: 83.312% (+0.2%) from 83.064%
9519613010

Pull #1473

github

web-flow
Merge feb27df18 into bc420923c
Pull Request #1473: Remove `persist` submodule

58 of 64 new or added lines in 5 files covered. (90.63%)

1 existing line in 1 file now uncovered.

11128 of 13357 relevant lines covered (83.31%)

17626.96 hits per line

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

32.14
/crates/chain/src/spk_client.rs
1
//! Helper types for spk-based blockchain clients.
2

3
use crate::{
4
    collections::BTreeMap, keychain::Indexed, local_chain::CheckPoint,
5
    ConfirmationTimeHeightAnchor, TxGraph,
6
};
7
use alloc::boxed::Box;
8
use bitcoin::{OutPoint, Script, ScriptBuf, Txid};
9
use core::marker::PhantomData;
10

11
/// Data required to perform a spk-based blockchain client sync.
12
///
13
/// A client sync fetches relevant chain data for a known list of scripts, transaction ids and
14
/// outpoints. The sync process also updates the chain from the given [`CheckPoint`].
15
pub struct SyncRequest {
16
    /// A checkpoint for the current chain [`LocalChain::tip`].
17
    /// The sync process will return a new chain update that extends this tip.
18
    ///
19
    /// [`LocalChain::tip`]: crate::local_chain::LocalChain::tip
20
    pub chain_tip: CheckPoint,
21
    /// Transactions that spend from or to these indexed script pubkeys.
22
    pub spks: Box<dyn ExactSizeIterator<Item = ScriptBuf> + Send>,
23
    /// Transactions with these txids.
24
    pub txids: Box<dyn ExactSizeIterator<Item = Txid> + Send>,
25
    /// Transactions with these outpoints or spent from these outpoints.
26
    pub outpoints: Box<dyn ExactSizeIterator<Item = OutPoint> + Send>,
27
}
28

29
impl SyncRequest {
30
    /// Construct a new [`SyncRequest`] from a given `cp` tip.
31
    pub fn from_chain_tip(cp: CheckPoint) -> Self {
264✔
32
        Self {
264✔
33
            chain_tip: cp,
264✔
34
            spks: Box::new(core::iter::empty()),
264✔
35
            txids: Box::new(core::iter::empty()),
264✔
36
            outpoints: Box::new(core::iter::empty()),
264✔
37
        }
264✔
38
    }
264✔
39

40
    /// Set the [`Script`]s that will be synced against.
41
    ///
42
    /// This consumes the [`SyncRequest`] and returns the updated one.
43
    #[must_use]
44
    pub fn set_spks(
2✔
45
        mut self,
2✔
46
        spks: impl IntoIterator<IntoIter = impl ExactSizeIterator<Item = ScriptBuf> + Send + 'static>,
2✔
47
    ) -> Self {
2✔
48
        self.spks = Box::new(spks.into_iter());
2✔
49
        self
2✔
50
    }
2✔
51

52
    /// Set the [`Txid`]s that will be synced against.
53
    ///
54
    /// This consumes the [`SyncRequest`] and returns the updated one.
55
    #[must_use]
56
    pub fn set_txids(
×
57
        mut self,
×
58
        txids: impl IntoIterator<IntoIter = impl ExactSizeIterator<Item = Txid> + Send + 'static>,
×
59
    ) -> Self {
×
60
        self.txids = Box::new(txids.into_iter());
×
61
        self
×
62
    }
×
63

64
    /// Set the [`OutPoint`]s that will be synced against.
65
    ///
66
    /// This consumes the [`SyncRequest`] and returns the updated one.
67
    #[must_use]
68
    pub fn set_outpoints(
×
69
        mut self,
×
70
        outpoints: impl IntoIterator<
×
71
            IntoIter = impl ExactSizeIterator<Item = OutPoint> + Send + 'static,
×
72
        >,
×
73
    ) -> Self {
×
74
        self.outpoints = Box::new(outpoints.into_iter());
×
75
        self
×
76
    }
×
77

78
    /// Chain on additional [`Script`]s that will be synced against.
79
    ///
80
    /// This consumes the [`SyncRequest`] and returns the updated one.
81
    #[must_use]
82
    pub fn chain_spks(
10✔
83
        mut self,
10✔
84
        spks: impl IntoIterator<
10✔
85
            IntoIter = impl ExactSizeIterator<Item = ScriptBuf> + Send + 'static,
10✔
86
            Item = ScriptBuf,
10✔
87
        >,
10✔
88
    ) -> Self {
10✔
89
        self.spks = Box::new(ExactSizeChain::new(self.spks, spks.into_iter()));
10✔
90
        self
10✔
91
    }
10✔
92

93
    /// Chain on additional [`Txid`]s that will be synced against.
94
    ///
95
    /// This consumes the [`SyncRequest`] and returns the updated one.
96
    #[must_use]
97
    pub fn chain_txids(
×
98
        mut self,
×
99
        txids: impl IntoIterator<
×
100
            IntoIter = impl ExactSizeIterator<Item = Txid> + Send + 'static,
×
101
            Item = Txid,
×
102
        >,
×
103
    ) -> Self {
×
104
        self.txids = Box::new(ExactSizeChain::new(self.txids, txids.into_iter()));
×
105
        self
×
106
    }
×
107

108
    /// Chain on additional [`OutPoint`]s that will be synced against.
109
    ///
110
    /// This consumes the [`SyncRequest`] and returns the updated one.
111
    #[must_use]
112
    pub fn chain_outpoints(
×
113
        mut self,
×
114
        outpoints: impl IntoIterator<
×
115
            IntoIter = impl ExactSizeIterator<Item = OutPoint> + Send + 'static,
×
116
            Item = OutPoint,
×
117
        >,
×
118
    ) -> Self {
×
119
        self.outpoints = Box::new(ExactSizeChain::new(self.outpoints, outpoints.into_iter()));
×
120
        self
×
121
    }
×
122

123
    /// Add a closure that will be called for [`Script`]s previously added to this request.
124
    ///
125
    /// This consumes the [`SyncRequest`] and returns the updated one.
126
    #[must_use]
127
    pub fn inspect_spks(
×
128
        mut self,
×
129
        mut inspect: impl FnMut(&Script) + Send + Sync + 'static,
×
130
    ) -> Self {
×
131
        self.spks = Box::new(self.spks.inspect(move |spk| inspect(spk)));
×
132
        self
×
133
    }
×
134

135
    /// Add a closure that will be called for [`Txid`]s previously added to this request.
136
    ///
137
    /// This consumes the [`SyncRequest`] and returns the updated one.
138
    #[must_use]
139
    pub fn inspect_txids(mut self, mut inspect: impl FnMut(&Txid) + Send + Sync + 'static) -> Self {
×
140
        self.txids = Box::new(self.txids.inspect(move |txid| inspect(txid)));
×
141
        self
×
142
    }
×
143

144
    /// Add a closure that will be called for [`OutPoint`]s previously added to this request.
145
    ///
146
    /// This consumes the [`SyncRequest`] and returns the updated one.
147
    #[must_use]
148
    pub fn inspect_outpoints(
×
149
        mut self,
×
150
        mut inspect: impl FnMut(&OutPoint) + Send + Sync + 'static,
×
151
    ) -> Self {
×
152
        self.outpoints = Box::new(self.outpoints.inspect(move |op| inspect(op)));
×
153
        self
×
154
    }
×
155

156
    /// Populate the request with revealed script pubkeys from `index` with the given `spk_range`.
157
    ///
158
    /// This consumes the [`SyncRequest`] and returns the updated one.
159
    #[cfg(feature = "miniscript")]
160
    #[must_use]
161
    pub fn populate_with_revealed_spks<K: Clone + Ord + core::fmt::Debug + Send + Sync>(
×
162
        self,
×
163
        index: &crate::keychain::KeychainTxOutIndex<K>,
×
164
        spk_range: impl core::ops::RangeBounds<K>,
×
165
    ) -> Self {
×
166
        use alloc::borrow::ToOwned;
×
167
        use alloc::vec::Vec;
×
168
        self.chain_spks(
×
169
            index
×
170
                .revealed_spks(spk_range)
×
171
                .map(|(_, spk)| spk.to_owned())
×
172
                .collect::<Vec<_>>(),
×
173
        )
×
174
    }
×
175
}
176

177
/// Data returned from a spk-based blockchain client sync.
178
///
179
/// See also [`SyncRequest`].
180
pub struct SyncResult<A = ConfirmationTimeHeightAnchor> {
181
    /// The update to apply to the receiving [`TxGraph`].
182
    pub graph_update: TxGraph<A>,
183
    /// The update to apply to the receiving [`LocalChain`](crate::local_chain::LocalChain).
184
    pub chain_update: CheckPoint,
185
}
186

187
/// Data required to perform a spk-based blockchain client full scan.
188
///
189
/// A client full scan iterates through all the scripts for the given keychains, fetching relevant
190
/// data until some stop gap number of scripts is found that have no data. This operation is
191
/// generally only used when importing or restoring previously used keychains in which the list of
192
/// used scripts is not known. The full scan process also updates the chain from the given [`CheckPoint`].
193
pub struct FullScanRequest<K> {
194
    /// A checkpoint for the current [`LocalChain::tip`].
195
    /// The full scan process will return a new chain update that extends this tip.
196
    ///
197
    /// [`LocalChain::tip`]: crate::local_chain::LocalChain::tip
198
    pub chain_tip: CheckPoint,
199
    /// Iterators of script pubkeys indexed by the keychain index.
200
    pub spks_by_keychain: BTreeMap<K, Box<dyn Iterator<Item = Indexed<ScriptBuf>> + Send>>,
201
}
202

203
impl<K: Ord + Clone> FullScanRequest<K> {
204
    /// Construct a new [`FullScanRequest`] from a given `chain_tip`.
205
    #[must_use]
206
    pub fn from_chain_tip(chain_tip: CheckPoint) -> Self {
18✔
207
        Self {
18✔
208
            chain_tip,
18✔
209
            spks_by_keychain: BTreeMap::new(),
18✔
210
        }
18✔
211
    }
18✔
212

213
    /// Construct a new [`FullScanRequest`] from a given `chain_tip` and `index`.
214
    ///
215
    /// Unbounded script pubkey iterators for each keychain (`K`) are extracted using
216
    /// [`KeychainTxOutIndex::all_unbounded_spk_iters`] and is used to populate the
217
    /// [`FullScanRequest`].
218
    ///
219
    /// [`KeychainTxOutIndex::all_unbounded_spk_iters`]: crate::keychain::KeychainTxOutIndex::all_unbounded_spk_iters
220
    #[cfg(feature = "miniscript")]
221
    #[must_use]
222
    pub fn from_keychain_txout_index(
×
223
        chain_tip: CheckPoint,
×
224
        index: &crate::keychain::KeychainTxOutIndex<K>,
×
225
    ) -> Self
×
226
    where
×
227
        K: core::fmt::Debug,
×
228
    {
×
229
        let mut req = Self::from_chain_tip(chain_tip);
×
230
        for (keychain, spks) in index.all_unbounded_spk_iters() {
×
231
            req = req.set_spks_for_keychain(keychain, spks);
×
232
        }
×
233
        req
×
234
    }
×
235

236
    /// Set the [`Script`]s for a given `keychain`.
237
    ///
238
    /// This consumes the [`FullScanRequest`] and returns the updated one.
239
    #[must_use]
240
    pub fn set_spks_for_keychain(
18✔
241
        mut self,
18✔
242
        keychain: K,
18✔
243
        spks: impl IntoIterator<IntoIter = impl Iterator<Item = Indexed<ScriptBuf>> + Send + 'static>,
18✔
244
    ) -> Self {
18✔
245
        self.spks_by_keychain
18✔
246
            .insert(keychain, Box::new(spks.into_iter()));
18✔
247
        self
18✔
248
    }
18✔
249

250
    /// Chain on additional [`Script`]s that will be synced against.
251
    ///
252
    /// This consumes the [`FullScanRequest`] and returns the updated one.
253
    #[must_use]
254
    pub fn chain_spks_for_keychain(
×
255
        mut self,
×
256
        keychain: K,
×
257
        spks: impl IntoIterator<IntoIter = impl Iterator<Item = Indexed<ScriptBuf>> + Send + 'static>,
×
258
    ) -> Self {
×
259
        match self.spks_by_keychain.remove(&keychain) {
×
260
            // clippy here suggests to remove `into_iter` from `spks.into_iter()`, but doing so
261
            // results in a compilation error
262
            #[allow(clippy::useless_conversion)]
263
            Some(keychain_spks) => self
×
264
                .spks_by_keychain
×
265
                .insert(keychain, Box::new(keychain_spks.chain(spks.into_iter()))),
×
266
            None => self
×
267
                .spks_by_keychain
×
268
                .insert(keychain, Box::new(spks.into_iter())),
×
269
        };
270
        self
×
271
    }
×
272

273
    /// Add a closure that will be called for every [`Script`] previously added to any keychain in
274
    /// this request.
275
    ///
276
    /// This consumes the [`SyncRequest`] and returns the updated one.
277
    #[must_use]
278
    pub fn inspect_spks_for_all_keychains(
×
279
        mut self,
×
280
        inspect: impl FnMut(K, u32, &Script) + Send + Sync + Clone + 'static,
×
281
    ) -> Self
×
282
    where
×
283
        K: Send + 'static,
×
284
    {
×
285
        for (keychain, spks) in core::mem::take(&mut self.spks_by_keychain) {
×
286
            let mut inspect = inspect.clone();
×
287
            self.spks_by_keychain.insert(
×
288
                keychain.clone(),
×
289
                Box::new(spks.inspect(move |(i, spk)| inspect(keychain.clone(), *i, spk))),
×
290
            );
×
291
        }
×
292
        self
×
293
    }
×
294

295
    /// Add a closure that will be called for every [`Script`] previously added to a given
296
    /// `keychain` in this request.
297
    ///
298
    /// This consumes the [`SyncRequest`] and returns the updated one.
299
    #[must_use]
300
    pub fn inspect_spks_for_keychain(
×
301
        mut self,
×
302
        keychain: K,
×
303
        mut inspect: impl FnMut(u32, &Script) + Send + Sync + 'static,
×
304
    ) -> Self
×
305
    where
×
306
        K: Send + 'static,
×
307
    {
×
308
        if let Some(spks) = self.spks_by_keychain.remove(&keychain) {
×
309
            self.spks_by_keychain.insert(
×
310
                keychain,
×
311
                Box::new(spks.inspect(move |(i, spk)| inspect(*i, spk))),
×
312
            );
×
313
        }
×
314
        self
×
315
    }
×
316
}
317

318
/// Data returned from a spk-based blockchain client full scan.
319
///
320
/// See also [`FullScanRequest`].
321
pub struct FullScanResult<K, A = ConfirmationTimeHeightAnchor> {
322
    /// The update to apply to the receiving [`LocalChain`](crate::local_chain::LocalChain).
323
    pub graph_update: TxGraph<A>,
324
    /// The update to apply to the receiving [`TxGraph`].
325
    pub chain_update: CheckPoint,
326
    /// Last active indices for the corresponding keychains (`K`).
327
    pub last_active_indices: BTreeMap<K, u32>,
328
}
329

330
/// A version of [`core::iter::Chain`] which can combine two [`ExactSizeIterator`]s to form a new
331
/// [`ExactSizeIterator`].
332
///
333
/// The danger of this is explained in [the `ExactSizeIterator` docs]
334
/// (https://doc.rust-lang.org/core/iter/trait.ExactSizeIterator.html#when-shouldnt-an-adapter-be-exactsizeiterator).
335
/// This does not apply here since it would be impossible to scan an item count that overflows
336
/// `usize` anyway.
337
struct ExactSizeChain<A, B, I> {
338
    a: Option<A>,
339
    b: Option<B>,
340
    i: PhantomData<I>,
341
}
342

343
impl<A, B, I> ExactSizeChain<A, B, I> {
344
    fn new(a: A, b: B) -> Self {
10✔
345
        ExactSizeChain {
10✔
346
            a: Some(a),
10✔
347
            b: Some(b),
10✔
348
            i: PhantomData,
10✔
349
        }
10✔
350
    }
10✔
351
}
352

353
impl<A, B, I> Iterator for ExactSizeChain<A, B, I>
354
where
355
    A: Iterator<Item = I>,
356
    B: Iterator<Item = I>,
357
{
358
    type Item = I;
359

360
    fn next(&mut self) -> Option<Self::Item> {
30✔
361
        if let Some(a) = &mut self.a {
30✔
362
            let item = a.next();
10✔
363
            if item.is_some() {
10✔
364
                return item;
×
365
            }
10✔
366
            self.a = None;
10✔
367
        }
20✔
368
        if let Some(b) = &mut self.b {
30✔
369
            let item = b.next();
20✔
370
            if item.is_some() {
20✔
371
                return item;
10✔
372
            }
10✔
373
            self.b = None;
10✔
374
        }
10✔
375
        None
20✔
376
    }
30✔
377
}
378

379
impl<A, B, I> ExactSizeIterator for ExactSizeChain<A, B, I>
380
where
381
    A: ExactSizeIterator<Item = I>,
382
    B: ExactSizeIterator<Item = I>,
383
{
384
    fn len(&self) -> usize {
×
385
        let a_len = self.a.as_ref().map(|a| a.len()).unwrap_or(0);
×
386
        let b_len = self.b.as_ref().map(|a| a.len()).unwrap_or(0);
×
387
        a_len + b_len
×
388
    }
×
389
}
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