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

bitcoindevkit / bdk / 8968819480

06 May 2024 12:09PM CUT coverage: 82.273%. Remained the same
8968819480

push

github

evanlinjin
Merge bitcoindevkit/bdk#1427: docs(esplora): fixed `full_scan` and `sync` documentation

<a class=hub.com/bitcoindevkit/bdk/commit/<a class="double-link" href="https://git"><a class=hub.com/bitcoindevkit/bdk/commit/f6dc6890c37752e80c86c68ae3ca3cc4fac6245e">f6dc6890c docs(esplora): fixed `full_scan` and `sync` documentation (Wei Chen)

Pull request description:

  <!-- You can erase any parts of this template not applicable to your Pull Request. -->

  ### Description

  Fixed documentation for `full_scan` and `sync` in `bdk_esplora`.

  ### Changelog notice

  <!-- Notice the release manager should include in the release tag message changelog -->
  <!-- See https://keepachangelog.com/en/1.0.0/ for examples -->
  * Updated documentation for `full_scan` and `sync` in `bdk_esplora`.

  ### Checklists

  #### All Submissions:

  * [x] I've signed all my commits
  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  * [x] I ran `cargo fmt` and `cargo clippy` before committing

ACKs for top commit:
  evanlinjin:
    ACK f6dc6890c37752e80c86c68ae3ca3cc4fac6245e
  storopoli:
    ACK f6dc6890c37752e80c86c68ae3ca3cc4fac6245e

Tree-SHA512: 900fb1a280a72ee1e13c5c70a136b93f332

10396 of 12636 relevant lines covered (82.27%)

17655.35 hits per line

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

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

3
use core::{fmt::Debug, marker::PhantomData, ops::RangeBounds};
4

5
use alloc::{boxed::Box, collections::BTreeMap, vec::Vec};
6
use bitcoin::{OutPoint, Script, ScriptBuf, Txid};
7

8
use crate::{local_chain::CheckPoint, ConfirmationTimeHeightAnchor, TxGraph};
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 {
42✔
31
        Self {
42✔
32
            chain_tip: cp,
42✔
33
            spks: Box::new(core::iter::empty()),
42✔
34
            txids: Box::new(core::iter::empty()),
42✔
35
            outpoints: Box::new(core::iter::empty()),
42✔
36
        }
42✔
37
    }
42✔
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(
2✔
44
        mut self,
2✔
45
        spks: impl IntoIterator<IntoIter = impl ExactSizeIterator<Item = ScriptBuf> + Send + 'static>,
2✔
46
    ) -> Self {
2✔
47
        self.spks = Box::new(spks.into_iter());
2✔
48
        self
2✔
49
    }
2✔
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(
×
82
        mut self,
×
83
        spks: impl IntoIterator<
×
84
            IntoIter = impl ExactSizeIterator<Item = ScriptBuf> + Send + 'static,
×
85
            Item = ScriptBuf,
×
86
        >,
×
87
    ) -> Self {
×
88
        self.spks = Box::new(ExactSizeChain::new(self.spks, spks.into_iter()));
×
89
        self
×
90
    }
×
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 + Debug + Send + Sync>(
×
161
        self,
×
162
        index: &crate::keychain::KeychainTxOutIndex<K>,
×
163
        spk_range: impl RangeBounds<K>,
×
164
    ) -> Self {
×
165
        use alloc::borrow::ToOwned;
×
166
        self.chain_spks(
×
167
            index
×
168
                .revealed_spks(spk_range)
×
169
                .map(|(_, _, spk)| spk.to_owned())
×
170
                .collect::<Vec<_>>(),
×
171
        )
×
172
    }
×
173
}
174

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

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

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

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

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

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

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

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

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

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

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

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

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

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