1use 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#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub enum SyncItem<'i, I> {
16 Spk(I, &'i Script),
18 Txid(Txid),
20 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#[derive(Debug, Clone)]
42pub struct SyncProgress {
43 pub spks_consumed: usize,
45 pub spks_remaining: usize,
47 pub txids_consumed: usize,
49 pub txids_remaining: usize,
51 pub outpoints_consumed: usize,
53 pub outpoints_remaining: usize,
55}
56
57impl SyncProgress {
58 pub fn total(&self) -> usize {
60 self.total_spks() + self.total_txids() + self.total_outpoints()
61 }
62
63 pub fn total_spks(&self) -> usize {
65 self.spks_consumed + self.spks_remaining
66 }
67
68 pub fn total_txids(&self) -> usize {
70 self.txids_consumed + self.txids_remaining
71 }
72
73 pub fn total_outpoints(&self) -> usize {
75 self.outpoints_consumed + self.outpoints_remaining
76 }
77
78 pub fn consumed(&self) -> usize {
80 self.spks_consumed + self.txids_consumed + self.outpoints_consumed
81 }
82
83 pub fn remaining(&self) -> usize {
85 self.spks_remaining + self.txids_remaining + self.outpoints_remaining
86 }
87}
88
89#[derive(Debug, Clone)]
91pub struct SpkWithExpectedTxids {
92 pub spk: ScriptBuf,
94
95 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#[must_use]
115pub struct SyncRequestBuilder<I = ()> {
116 inner: SyncRequest<I>,
117}
118
119impl SyncRequestBuilder<()> {
120 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 pub fn chain_tip(mut self, cp: CheckPoint) -> Self {
131 self.inner.chain_tip = Some(cp);
132 self
133 }
134
135 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 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 pub fn txids(mut self, txids: impl IntoIterator<Item = Txid>) -> Self {
195 self.inner.txids.extend(txids);
196 self
197 }
198
199 pub fn outpoints(mut self, outpoints: impl IntoIterator<Item = OutPoint>) -> Self {
201 self.inner.outpoints.extend(outpoints);
202 self
203 }
204
205 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 pub fn build(self) -> SyncRequest<I> {
216 self.inner
217 }
218}
219
220#[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 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 #[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 pub fn start_time(&self) -> u64 {
304 self.start_time
305 }
306
307 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 pub fn chain_tip(&self) -> Option<CheckPoint> {
321 self.chain_tip.clone()
322 }
323
324 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 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 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 pub fn iter_spks_with_expected_txids(
365 &mut self,
366 ) -> impl ExactSizeIterator<Item = SpkWithExpectedTxids> + '_ {
367 SyncIter::<I, SpkWithExpectedTxids>::new(self)
368 }
369
370 pub fn iter_txids(&mut self) -> impl ExactSizeIterator<Item = Txid> + '_ {
372 SyncIter::<I, Txid>::new(self)
373 }
374
375 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#[must_use]
390#[derive(Debug)]
391pub struct SyncResponse<A = ConfirmationBlockTime> {
392 pub tx_update: crate::TxUpdate<A>,
394 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 pub fn is_empty(&self) -> bool {
410 self.tx_update.is_empty() && self.chain_update.is_none()
411 }
412}
413
414#[must_use]
418pub struct FullScanRequestBuilder<K> {
419 inner: FullScanRequest<K>,
420}
421
422impl<K: Ord> FullScanRequestBuilder<K> {
423 pub fn chain_tip(mut self, tip: CheckPoint) -> Self {
427 self.inner.chain_tip = Some(tip);
428 self
429 }
430
431 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 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 pub fn build(self) -> FullScanRequest<K> {
454 self.inner
455 }
456}
457
458#[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 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 #[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 pub fn start_time(&self) -> u64 {
515 self.start_time
516 }
517
518 pub fn chain_tip(&self) -> Option<CheckPoint> {
520 self.chain_tip.clone()
521 }
522
523 pub fn keychains(&self) -> Vec<K> {
525 self.spks_by_keychain.keys().cloned().collect()
526 }
527
528 pub fn next_spk(&mut self, keychain: K) -> Option<Indexed<ScriptBuf>> {
531 self.iter_spks(keychain).next()
532 }
533
534 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#[must_use]
550#[derive(Debug)]
551pub struct FullScanResponse<K, A = ConfirmationBlockTime> {
552 pub tx_update: crate::TxUpdate<A>,
554 pub last_active_indices: BTreeMap<K, u32>,
557 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 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}