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

bitcoindevkit / bdk / 10348217438

12 Aug 2024 08:10AM CUT coverage: 81.794% (-0.02%) from 81.813%
10348217438

Pull #1535

github

web-flow
Merge 2c0bc45ec into 98c49592d
Pull Request #1535: test(electrum): Test sync in reorg and no-reorg situations

19 of 25 new or added lines in 1 file covered. (76.0%)

1 existing line in 1 file now uncovered.

10908 of 13336 relevant lines covered (81.79%)

12995.15 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, local_chain::CheckPoint, ConfirmationBlockTime, Indexed, TxGraph,
5
};
6
use alloc::boxed::Box;
7
use bitcoin::{OutPoint, Script, ScriptBuf, Txid};
8
use core::marker::PhantomData;
9

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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