bdk_core/
spk_client.rs

1//! Helper types for spk-based blockchain clients.
2use crate::{
3    alloc::{boxed::Box, collections::VecDeque, vec::Vec},
4    collections::{BTreeMap, HashMap, HashSet},
5    CheckPoint, ConfirmationBlockTime, Indexed,
6};
7use bitcoin::{OutPoint, Script, ScriptBuf, Txid};
8
9type InspectSync<I> = dyn FnMut(SyncItem<I>, SyncProgress) + Send + 'static;
10
11type InspectFullScan<K> = dyn FnMut(K, u32, &Script) + Send + 'static;
12
13/// An item reported to the [`inspect`](SyncRequestBuilder::inspect) closure of [`SyncRequest`].
14#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub enum SyncItem<'i, I> {
16    /// Script pubkey sync item.
17    Spk(I, &'i Script),
18    /// Txid sync item.
19    Txid(Txid),
20    /// Outpoint sync item.
21    OutPoint(OutPoint),
22}
23
24impl<I: core::fmt::Debug + core::any::Any> core::fmt::Display for SyncItem<'_, I> {
25    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
26        match self {
27            SyncItem::Spk(i, spk) => {
28                if (i as &dyn core::any::Any).is::<()>() {
29                    write!(f, "script '{spk}'")
30                } else {
31                    write!(f, "script {i:?} '{spk}'")
32                }
33            }
34            SyncItem::Txid(txid) => write!(f, "txid '{txid}'"),
35            SyncItem::OutPoint(op) => write!(f, "outpoint '{op}'"),
36        }
37    }
38}
39
40/// The progress of [`SyncRequest`].
41#[derive(Debug, Clone)]
42pub struct SyncProgress {
43    /// Script pubkeys consumed by the request.
44    pub spks_consumed: usize,
45    /// Script pubkeys remaining in the request.
46    pub spks_remaining: usize,
47    /// Txids consumed by the request.
48    pub txids_consumed: usize,
49    /// Txids remaining in the request.
50    pub txids_remaining: usize,
51    /// Outpoints consumed by the request.
52    pub outpoints_consumed: usize,
53    /// Outpoints remaining in the request.
54    pub outpoints_remaining: usize,
55}
56
57impl SyncProgress {
58    /// Total items, consumed and remaining, of the request.
59    pub fn total(&self) -> usize {
60        self.total_spks() + self.total_txids() + self.total_outpoints()
61    }
62
63    /// Total script pubkeys, consumed and remaining, of the request.
64    pub fn total_spks(&self) -> usize {
65        self.spks_consumed + self.spks_remaining
66    }
67
68    /// Total txids, consumed and remaining, of the request.
69    pub fn total_txids(&self) -> usize {
70        self.txids_consumed + self.txids_remaining
71    }
72
73    /// Total outpoints, consumed and remaining, of the request.
74    pub fn total_outpoints(&self) -> usize {
75        self.outpoints_consumed + self.outpoints_remaining
76    }
77
78    /// Total consumed items of the request.
79    pub fn consumed(&self) -> usize {
80        self.spks_consumed + self.txids_consumed + self.outpoints_consumed
81    }
82
83    /// Total remaining items of the request.
84    pub fn remaining(&self) -> usize {
85        self.spks_remaining + self.txids_remaining + self.outpoints_remaining
86    }
87}
88
89/// [`Script`] with expected [`Txid`] histories.
90#[derive(Debug, Clone)]
91pub struct SpkWithExpectedTxids {
92    /// Script pubkey.
93    pub spk: ScriptBuf,
94
95    /// [`Txid`]s that we expect to appear in the chain source's spk history response.
96    ///
97    /// Any transaction listed here that is missing from the spk history response should be
98    /// considered evicted from the mempool.
99    pub expected_txids: HashSet<Txid>,
100}
101
102impl From<ScriptBuf> for SpkWithExpectedTxids {
103    fn from(spk: ScriptBuf) -> Self {
104        Self {
105            spk,
106            expected_txids: HashSet::new(),
107        }
108    }
109}
110
111/// Builds a [`SyncRequest`].
112///
113/// Construct with [`SyncRequest::builder`].
114#[must_use]
115pub struct SyncRequestBuilder<I = ()> {
116    inner: SyncRequest<I>,
117}
118
119impl SyncRequestBuilder<()> {
120    /// Add [`Script`]s that will be synced against.
121    pub fn spks(self, spks: impl IntoIterator<Item = ScriptBuf>) -> Self {
122        self.spks_with_indexes(spks.into_iter().map(|spk| ((), spk)))
123    }
124}
125
126impl<I> SyncRequestBuilder<I> {
127    /// Set the initial chain tip for the sync request.
128    ///
129    /// This is used to update [`LocalChain`](../../bdk_chain/local_chain/struct.LocalChain.html).
130    pub fn chain_tip(mut self, cp: CheckPoint) -> Self {
131        self.inner.chain_tip = Some(cp);
132        self
133    }
134
135    /// Add [`Script`]s coupled with associated indexes that will be synced against.
136    ///
137    /// # Example
138    ///
139    /// Sync revealed script pubkeys obtained from a
140    /// [`KeychainTxOutIndex`](../../bdk_chain/indexer/keychain_txout/struct.KeychainTxOutIndex.
141    /// html).
142    ///
143    /// ```rust
144    /// # use bdk_chain::spk_client::SyncRequest;
145    /// # use bdk_chain::indexer::keychain_txout::KeychainTxOutIndex;
146    /// # use bdk_chain::miniscript::{Descriptor, DescriptorPublicKey};
147    /// # let secp = bdk_chain::bitcoin::secp256k1::Secp256k1::signing_only();
148    /// # let (descriptor_a,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/0/*)").unwrap();
149    /// # let (descriptor_b,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/1/*)").unwrap();
150    /// let mut indexer = KeychainTxOutIndex::<&'static str>::default();
151    /// indexer.insert_descriptor("descriptor_a", descriptor_a)?;
152    /// indexer.insert_descriptor("descriptor_b", descriptor_b)?;
153    ///
154    /// /* Assume that the caller does more mutations to the `indexer` here... */
155    ///
156    /// // Reveal spks for "descriptor_a", then build a sync request. Each spk will be indexed with
157    /// // `u32`, which represents the derivation index of the associated spk from "descriptor_a".
158    /// let (newly_revealed_spks, _changeset) = indexer
159    ///     .reveal_to_target("descriptor_a", 21)
160    ///     .expect("keychain must exist");
161    /// let _request = SyncRequest::builder()
162    ///     .spks_with_indexes(newly_revealed_spks)
163    ///     .build();
164    ///
165    /// // Sync all revealed spks in the indexer. This time, spks may be derived from different
166    /// // keychains. Each spk will be indexed with `(&'static str, u32)` where `&'static str` is
167    /// // the keychain identifier and `u32` is the derivation index.
168    /// let all_revealed_spks = indexer.revealed_spks(..);
169    /// let _request = SyncRequest::builder()
170    ///     .spks_with_indexes(all_revealed_spks)
171    ///     .build();
172    /// # Ok::<_, bdk_chain::keychain_txout::InsertDescriptorError<_>>(())
173    /// ```
174    pub fn spks_with_indexes(mut self, spks: impl IntoIterator<Item = (I, ScriptBuf)>) -> Self {
175        self.inner.spks.extend(spks);
176        self
177    }
178
179    /// Add transactions that are expected to exist under the given spks.
180    ///
181    /// This is useful for detecting a malicious replacement of an incoming transaction.
182    pub fn expected_spk_txids(mut self, txs: impl IntoIterator<Item = (ScriptBuf, Txid)>) -> Self {
183        for (spk, txid) in txs {
184            self.inner
185                .spk_expected_txids
186                .entry(spk)
187                .or_default()
188                .insert(txid);
189        }
190        self
191    }
192
193    /// Add [`Txid`]s that will be synced against.
194    pub fn txids(mut self, txids: impl IntoIterator<Item = Txid>) -> Self {
195        self.inner.txids.extend(txids);
196        self
197    }
198
199    /// Add [`OutPoint`]s that will be synced against.
200    pub fn outpoints(mut self, outpoints: impl IntoIterator<Item = OutPoint>) -> Self {
201        self.inner.outpoints.extend(outpoints);
202        self
203    }
204
205    /// Set the closure that will inspect every sync item visited.
206    pub fn inspect<F>(mut self, inspect: F) -> Self
207    where
208        F: FnMut(SyncItem<I>, SyncProgress) + Send + 'static,
209    {
210        self.inner.inspect = Box::new(inspect);
211        self
212    }
213
214    /// Build the [`SyncRequest`].
215    pub fn build(self) -> SyncRequest<I> {
216        self.inner
217    }
218}
219
220/// Data required to perform a spk-based blockchain client sync.
221///
222/// A client sync fetches relevant chain data for a known list of scripts, transaction ids and
223/// outpoints. The sync process also updates the chain from the given
224/// [`chain_tip`](SyncRequestBuilder::chain_tip) (if provided).
225///
226/// ```rust
227/// # use bdk_chain::{bitcoin::{hashes::Hash, ScriptBuf}, local_chain::LocalChain};
228/// # use bdk_chain::spk_client::SyncRequest;
229/// # let (local_chain, _) = LocalChain::from_genesis_hash(Hash::all_zeros());
230/// # let scripts = [ScriptBuf::default(), ScriptBuf::default()];
231/// // Construct a sync request.
232/// let sync_request = SyncRequest::builder()
233///     // Provide chain tip of the local wallet.
234///     .chain_tip(local_chain.tip())
235///     // Provide list of scripts to scan for transactions against.
236///     .spks(scripts)
237///     // This is called for every synced item.
238///     .inspect(|item, progress| println!("{} (remaining: {})", item, progress.remaining()))
239///     // Finish constructing the sync request.
240///     .build();
241/// ```
242#[must_use]
243pub struct SyncRequest<I = ()> {
244    start_time: u64,
245    chain_tip: Option<CheckPoint>,
246    spks: VecDeque<(I, ScriptBuf)>,
247    spks_consumed: usize,
248    spk_expected_txids: HashMap<ScriptBuf, HashSet<Txid>>,
249    txids: VecDeque<Txid>,
250    txids_consumed: usize,
251    outpoints: VecDeque<OutPoint>,
252    outpoints_consumed: usize,
253    inspect: Box<InspectSync<I>>,
254}
255
256impl<I> From<SyncRequestBuilder<I>> for SyncRequest<I> {
257    fn from(builder: SyncRequestBuilder<I>) -> Self {
258        builder.inner
259    }
260}
261
262impl<I> SyncRequest<I> {
263    /// Start building [`SyncRequest`] with a given `start_time`.
264    ///
265    /// `start_time` specifies the start time of sync. Chain sources can use this value to set
266    /// [`TxUpdate::seen_ats`](crate::TxUpdate::seen_ats) for mempool transactions. A transaction
267    /// without any `seen_ats` is assumed to be unseen in the mempool.
268    ///
269    /// Use [`SyncRequest::builder`] to use the current timestamp as `start_time` (this requires
270    /// `feature = "std"`).
271    pub fn builder_at(start_time: u64) -> SyncRequestBuilder<I> {
272        SyncRequestBuilder {
273            inner: Self {
274                start_time,
275                chain_tip: None,
276                spks: VecDeque::new(),
277                spks_consumed: 0,
278                spk_expected_txids: HashMap::new(),
279                txids: VecDeque::new(),
280                txids_consumed: 0,
281                outpoints: VecDeque::new(),
282                outpoints_consumed: 0,
283                inspect: Box::new(|_, _| ()),
284            },
285        }
286    }
287
288    /// Start building [`SyncRequest`] with the current timestamp as the `start_time`.
289    ///
290    /// Use [`SyncRequest::builder_at`] to manually set the `start_time`, or if `feature = "std"`
291    /// is not available.
292    #[cfg(feature = "std")]
293    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
294    pub fn builder() -> SyncRequestBuilder<I> {
295        let start_time = std::time::UNIX_EPOCH
296            .elapsed()
297            .expect("failed to get current timestamp")
298            .as_secs();
299        Self::builder_at(start_time)
300    }
301
302    /// When the sync-request was initiated.
303    pub fn start_time(&self) -> u64 {
304        self.start_time
305    }
306
307    /// Get the [`SyncProgress`] of this request.
308    pub fn progress(&self) -> SyncProgress {
309        SyncProgress {
310            spks_consumed: self.spks_consumed,
311            spks_remaining: self.spks.len(),
312            txids_consumed: self.txids_consumed,
313            txids_remaining: self.txids.len(),
314            outpoints_consumed: self.outpoints_consumed,
315            outpoints_remaining: self.outpoints.len(),
316        }
317    }
318
319    /// Get the chain tip [`CheckPoint`] of this request (if any).
320    pub fn chain_tip(&self) -> Option<CheckPoint> {
321        self.chain_tip.clone()
322    }
323
324    /// Advances the sync request and returns the next [`ScriptBuf`] with corresponding [`Txid`]
325    /// history.
326    ///
327    /// Returns [`None`] when there are no more scripts remaining in the request.
328    pub fn next_spk_with_expected_txids(&mut self) -> Option<SpkWithExpectedTxids> {
329        let (i, next_spk) = self.spks.pop_front()?;
330        self.spks_consumed += 1;
331        self._call_inspect(SyncItem::Spk(i, next_spk.as_script()));
332        let spk_history = self
333            .spk_expected_txids
334            .get(&next_spk)
335            .cloned()
336            .unwrap_or_default();
337        Some(SpkWithExpectedTxids {
338            spk: next_spk,
339            expected_txids: spk_history,
340        })
341    }
342
343    /// Advances the sync request and returns the next [`Txid`].
344    ///
345    /// Returns [`None`] when there are no more txids remaining in the request.
346    pub fn next_txid(&mut self) -> Option<Txid> {
347        let txid = self.txids.pop_front()?;
348        self.txids_consumed += 1;
349        self._call_inspect(SyncItem::Txid(txid));
350        Some(txid)
351    }
352
353    /// Advances the sync request and returns the next [`OutPoint`].
354    ///
355    /// Returns [`None`] when there are no more outpoints in the request.
356    pub fn next_outpoint(&mut self) -> Option<OutPoint> {
357        let outpoint = self.outpoints.pop_front()?;
358        self.outpoints_consumed += 1;
359        self._call_inspect(SyncItem::OutPoint(outpoint));
360        Some(outpoint)
361    }
362
363    /// Iterate over [`ScriptBuf`]s with corresponding [`Txid`] histories contained in this request.
364    pub fn iter_spks_with_expected_txids(
365        &mut self,
366    ) -> impl ExactSizeIterator<Item = SpkWithExpectedTxids> + '_ {
367        SyncIter::<I, SpkWithExpectedTxids>::new(self)
368    }
369
370    /// Iterate over [`Txid`]s contained in this request.
371    pub fn iter_txids(&mut self) -> impl ExactSizeIterator<Item = Txid> + '_ {
372        SyncIter::<I, Txid>::new(self)
373    }
374
375    /// Iterate over [`OutPoint`]s contained in this request.
376    pub fn iter_outpoints(&mut self) -> impl ExactSizeIterator<Item = OutPoint> + '_ {
377        SyncIter::<I, OutPoint>::new(self)
378    }
379
380    fn _call_inspect(&mut self, item: SyncItem<I>) {
381        let progress = self.progress();
382        (*self.inspect)(item, progress);
383    }
384}
385
386/// Data returned from a spk-based blockchain client sync.
387///
388/// See also [`SyncRequest`].
389#[must_use]
390#[derive(Debug)]
391pub struct SyncResponse<A = ConfirmationBlockTime> {
392    /// Relevant transaction data discovered during the scan.
393    pub tx_update: crate::TxUpdate<A>,
394    /// Changes to the chain discovered during the scan.
395    pub chain_update: Option<CheckPoint>,
396}
397
398impl<A> Default for SyncResponse<A> {
399    fn default() -> Self {
400        Self {
401            tx_update: Default::default(),
402            chain_update: Default::default(),
403        }
404    }
405}
406
407impl<A> SyncResponse<A> {
408    /// Returns true if the `SyncResponse` is empty.
409    pub fn is_empty(&self) -> bool {
410        self.tx_update.is_empty() && self.chain_update.is_none()
411    }
412}
413
414/// Builds a [`FullScanRequest`].
415///
416/// Construct with [`FullScanRequest::builder`].
417#[must_use]
418pub struct FullScanRequestBuilder<K> {
419    inner: FullScanRequest<K>,
420}
421
422impl<K: Ord> FullScanRequestBuilder<K> {
423    /// Set the initial chain tip for the full scan request.
424    ///
425    /// This is used to update [`LocalChain`](../../bdk_chain/local_chain/struct.LocalChain.html).
426    pub fn chain_tip(mut self, tip: CheckPoint) -> Self {
427        self.inner.chain_tip = Some(tip);
428        self
429    }
430
431    /// Set the spk iterator for a given `keychain`.
432    pub fn spks_for_keychain(
433        mut self,
434        keychain: K,
435        spks: impl IntoIterator<IntoIter = impl Iterator<Item = Indexed<ScriptBuf>> + Send + 'static>,
436    ) -> Self {
437        self.inner
438            .spks_by_keychain
439            .insert(keychain, Box::new(spks.into_iter()));
440        self
441    }
442
443    /// Set the closure that will inspect every sync item visited.
444    pub fn inspect<F>(mut self, inspect: F) -> Self
445    where
446        F: FnMut(K, u32, &Script) + Send + 'static,
447    {
448        self.inner.inspect = Box::new(inspect);
449        self
450    }
451
452    /// Build the [`FullScanRequest`].
453    pub fn build(self) -> FullScanRequest<K> {
454        self.inner
455    }
456}
457
458/// Data required to perform a spk-based blockchain client full scan.
459///
460/// A client full scan iterates through all the scripts for the given keychains, fetching relevant
461/// data until some stop gap number of scripts is found that have no data. This operation is
462/// generally only used when importing or restoring previously used keychains in which the list of
463/// used scripts is not known. The full scan process also updates the chain from the given
464/// [`chain_tip`](FullScanRequestBuilder::chain_tip) (if provided).
465#[must_use]
466pub struct FullScanRequest<K> {
467    start_time: u64,
468    chain_tip: Option<CheckPoint>,
469    spks_by_keychain: BTreeMap<K, Box<dyn Iterator<Item = Indexed<ScriptBuf>> + Send>>,
470    inspect: Box<InspectFullScan<K>>,
471}
472
473impl<K> From<FullScanRequestBuilder<K>> for FullScanRequest<K> {
474    fn from(builder: FullScanRequestBuilder<K>) -> Self {
475        builder.inner
476    }
477}
478
479impl<K: Ord + Clone> FullScanRequest<K> {
480    /// Start building a [`FullScanRequest`] with a given `start_time`.
481    ///
482    /// `start_time` specifies the start time of sync. Chain sources can use this value to set
483    /// [`TxUpdate::seen_ats`](crate::TxUpdate::seen_ats) for mempool transactions. A transaction
484    /// without any `seen_ats` is assumed to be unseen in the mempool.
485    ///
486    /// Use [`FullScanRequest::builder`] to use the current timestamp as `start_time` (this
487    /// requires `feature = "std`).
488    pub fn builder_at(start_time: u64) -> FullScanRequestBuilder<K> {
489        FullScanRequestBuilder {
490            inner: Self {
491                start_time,
492                chain_tip: None,
493                spks_by_keychain: BTreeMap::new(),
494                inspect: Box::new(|_, _, _| ()),
495            },
496        }
497    }
498
499    /// Start building a [`FullScanRequest`] with the current timestamp as the `start_time`.
500    ///
501    /// Use [`FullScanRequest::builder_at`] to manually set the `start_time`, or if `feature =
502    /// "std"` is not available.
503    #[cfg(feature = "std")]
504    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
505    pub fn builder() -> FullScanRequestBuilder<K> {
506        let start_time = std::time::UNIX_EPOCH
507            .elapsed()
508            .expect("failed to get current timestamp")
509            .as_secs();
510        Self::builder_at(start_time)
511    }
512
513    /// When the full-scan-request was initiated.
514    pub fn start_time(&self) -> u64 {
515        self.start_time
516    }
517
518    /// Get the chain tip [`CheckPoint`] of this request (if any).
519    pub fn chain_tip(&self) -> Option<CheckPoint> {
520        self.chain_tip.clone()
521    }
522
523    /// List all keychains contained in this request.
524    pub fn keychains(&self) -> Vec<K> {
525        self.spks_by_keychain.keys().cloned().collect()
526    }
527
528    /// Advances the full scan request and returns the next indexed [`ScriptBuf`] of the given
529    /// `keychain`.
530    pub fn next_spk(&mut self, keychain: K) -> Option<Indexed<ScriptBuf>> {
531        self.iter_spks(keychain).next()
532    }
533
534    /// Iterate over indexed [`ScriptBuf`]s contained in this request of the given `keychain`.
535    pub fn iter_spks(&mut self, keychain: K) -> impl Iterator<Item = Indexed<ScriptBuf>> + '_ {
536        let spks = self.spks_by_keychain.get_mut(&keychain);
537        let inspect = &mut self.inspect;
538        KeychainSpkIter {
539            keychain,
540            spks,
541            inspect,
542        }
543    }
544}
545
546/// Data returned from a spk-based blockchain client full scan.
547///
548/// See also [`FullScanRequest`].
549#[must_use]
550#[derive(Debug)]
551pub struct FullScanResponse<K, A = ConfirmationBlockTime> {
552    /// Relevant transaction data discovered during the scan.
553    pub tx_update: crate::TxUpdate<A>,
554    /// Last active indices for the corresponding keychains (`K`). An index is active if it had a
555    /// transaction associated with the script pubkey at that index.
556    pub last_active_indices: BTreeMap<K, u32>,
557    /// Changes to the chain discovered during the scan.
558    pub chain_update: Option<CheckPoint>,
559}
560
561impl<K, A> Default for FullScanResponse<K, A> {
562    fn default() -> Self {
563        Self {
564            tx_update: Default::default(),
565            chain_update: Default::default(),
566            last_active_indices: Default::default(),
567        }
568    }
569}
570
571impl<K, A> FullScanResponse<K, A> {
572    /// Returns true if the `FullScanResponse` is empty.
573    pub fn is_empty(&self) -> bool {
574        self.tx_update.is_empty()
575            && self.last_active_indices.is_empty()
576            && self.chain_update.is_none()
577    }
578}
579
580struct KeychainSpkIter<'r, K> {
581    keychain: K,
582    spks: Option<&'r mut Box<dyn Iterator<Item = Indexed<ScriptBuf>> + Send>>,
583    inspect: &'r mut Box<InspectFullScan<K>>,
584}
585
586impl<K: Ord + Clone> Iterator for KeychainSpkIter<'_, K> {
587    type Item = Indexed<ScriptBuf>;
588
589    fn next(&mut self) -> Option<Self::Item> {
590        let (i, spk) = self.spks.as_mut()?.next()?;
591        (*self.inspect)(self.keychain.clone(), i, &spk);
592        Some((i, spk))
593    }
594}
595
596struct SyncIter<'r, I, Item> {
597    request: &'r mut SyncRequest<I>,
598    marker: core::marker::PhantomData<Item>,
599}
600
601impl<'r, I, Item> SyncIter<'r, I, Item> {
602    fn new(request: &'r mut SyncRequest<I>) -> Self {
603        Self {
604            request,
605            marker: core::marker::PhantomData,
606        }
607    }
608}
609
610impl<'r, I, Item> ExactSizeIterator for SyncIter<'r, I, Item> where SyncIter<'r, I, Item>: Iterator {}
611
612impl<I> Iterator for SyncIter<'_, I, SpkWithExpectedTxids> {
613    type Item = SpkWithExpectedTxids;
614
615    fn next(&mut self) -> Option<Self::Item> {
616        self.request.next_spk_with_expected_txids()
617    }
618
619    fn size_hint(&self) -> (usize, Option<usize>) {
620        let remaining = self.request.spks.len();
621        (remaining, Some(remaining))
622    }
623}
624
625impl<I> Iterator for SyncIter<'_, I, Txid> {
626    type Item = Txid;
627
628    fn next(&mut self) -> Option<Self::Item> {
629        self.request.next_txid()
630    }
631
632    fn size_hint(&self) -> (usize, Option<usize>) {
633        let remaining = self.request.txids.len();
634        (remaining, Some(remaining))
635    }
636}
637
638impl<I> Iterator for SyncIter<'_, I, OutPoint> {
639    type Item = OutPoint;
640
641    fn next(&mut self) -> Option<Self::Item> {
642        self.request.next_outpoint()
643    }
644
645    fn size_hint(&self) -> (usize, Option<usize>) {
646        let remaining = self.request.outpoints.len();
647        (remaining, Some(remaining))
648    }
649}