22use crate :: {
33 alloc:: { boxed:: Box , collections:: VecDeque , vec:: Vec } ,
44 collections:: { BTreeMap , HashMap , HashSet } ,
5- CheckPoint , ConfirmationBlockTime , Indexed ,
5+ CheckPoint , ConfirmationBlockTime , Indexed , TxUpdate , TxUpdateCursor ,
66} ;
77use bitcoin:: { BlockHash , OutPoint , Script , ScriptBuf , Txid } ;
88
9- type InspectSync < I > = dyn FnMut ( SyncItem < I > , SyncProgress ) + Send + ' static ;
10-
11- type InspectFullScan < K > = dyn FnMut ( K , u32 , & Script ) + Send + ' static ;
9+ type OnSyncEvent < I , A > = dyn for < ' a > FnMut ( SyncRequestEvent < ' a , I , A > ) + Send + ' static ;
10+ type OnFullScanEvent < K , A > = dyn for < ' a > FnMut ( FullScanRequestEvent < ' a , K , A > ) + Send + ' static ;
1211
1312/// An item reported to the [`inspect`](SyncRequestBuilder::inspect) closure of [`SyncRequest`].
1413#[ derive( Clone , Copy , Debug , PartialEq , Eq , PartialOrd , Ord , Hash ) ]
@@ -86,6 +85,117 @@ impl SyncProgress {
8685 }
8786}
8887
88+ /// Progress of a [`FullScanRequest`] (total unknown until `stop_gap` terminates the scan).
89+ #[ derive( Debug , Clone , Default ) ]
90+ pub struct FullScanProgress {
91+ /// Script pubkeys consumed across all keychains.
92+ pub keychain_spks_consumed : usize ,
93+ /// Consecutive unused script pubkeys past the last-revealed index.
94+ pub consecutive_unused : usize ,
95+ /// Transactions discovered so far.
96+ pub txs_discovered : usize ,
97+ }
98+
99+ /// Events emitted during [`SyncRequest`] execution.
100+ #[ derive( Debug ) ]
101+ pub enum SyncRequestEvent < ' a , I , A = ConfirmationBlockTime > {
102+ /// A sync item is about to be fetched from the chain source.
103+ ItemStarted ( SyncItem < ' a , I > , SyncProgress ) ,
104+ /// Incremental transaction data safe to pass to
105+ /// [`TxGraph::apply_update`](../../bdk_chain/tx_graph/struct.TxGraph.html#method.
106+ /// apply_update).
107+ ///
108+ /// On Electrum, confirmed transactions may lack anchors until [`AnchorsResolved`].
109+ PartialUpdate ( TxUpdate < A > ) ,
110+ /// Anchor-only update after Electrum resolves confirmation proofs.
111+ AnchorsResolved ( TxUpdate < A > ) ,
112+ }
113+
114+ /// Events emitted during [`FullScanRequest`] execution.
115+ #[ derive( Debug ) ]
116+ pub enum FullScanRequestEvent < ' a , K , A = ConfirmationBlockTime > {
117+ /// A script pubkey is about to be scanned.
118+ SpkStarted {
119+ /// Keychain being scanned.
120+ keychain : K ,
121+ /// Derivation index of the script pubkey.
122+ index : u32 ,
123+ /// Script pubkey.
124+ script : & ' a Script ,
125+ /// Scan progress so far.
126+ progress : FullScanProgress ,
127+ } ,
128+ /// Incremental transaction data safe to pass to
129+ /// [`TxGraph::apply_update`](../../bdk_chain/tx_graph/struct.TxGraph.html#method.
130+ /// apply_update).
131+ ///
132+ /// On Electrum, confirmed transactions may lack anchors until [`AnchorsResolved`].
133+ PartialUpdate ( TxUpdate < A > ) ,
134+ /// Anchor-only update after Electrum resolves confirmation proofs.
135+ AnchorsResolved ( TxUpdate < A > ) ,
136+ }
137+
138+ /// Backend-facing sink for emitting [`SyncRequestEvent`]s.
139+ ///
140+ /// Constructed via [`SyncRequest::event_sink`]. Intended for official chain sources.
141+ pub struct SyncRequestEventSink < ' a , I , D = BlockHash > {
142+ request : & ' a mut SyncRequest < I , D > ,
143+ }
144+
145+ impl < ' a , I , D > SyncRequestEventSink < ' a , I , D > {
146+ /// Emit a [`SyncRequestEvent::PartialUpdate`] if `update` is non-empty.
147+ pub fn emit_partial_update ( & mut self , update : TxUpdate < ConfirmationBlockTime > ) {
148+ if !update. is_empty ( ) {
149+ ( self . request . on_event ) ( SyncRequestEvent :: PartialUpdate ( update) ) ;
150+ }
151+ }
152+
153+ /// Emit a [`SyncRequestEvent::AnchorsResolved`] if `update` is non-empty.
154+ pub fn emit_anchors_resolved ( & mut self , update : TxUpdate < ConfirmationBlockTime > ) {
155+ if !update. is_empty ( ) {
156+ ( self . request . on_event ) ( SyncRequestEvent :: AnchorsResolved ( update) ) ;
157+ }
158+ }
159+ }
160+
161+ /// Backend-facing sink for emitting [`FullScanRequestEvent`]s.
162+ ///
163+ /// Constructed via [`FullScanRequest::event_sink`]. Intended for official chain sources.
164+ pub struct FullScanRequestEventSink < ' a , K , D = BlockHash > {
165+ request : & ' a mut FullScanRequest < K , D > ,
166+ }
167+
168+ impl < ' a , K : Ord + Clone , D > FullScanRequestEventSink < ' a , K , D > {
169+ /// Emit a [`FullScanRequestEvent::PartialUpdate`] if `update` is non-empty.
170+ pub fn emit_partial_update ( & mut self , update : TxUpdate < ConfirmationBlockTime > ) {
171+ if !update. is_empty ( ) {
172+ ( self . request . on_event ) ( FullScanRequestEvent :: PartialUpdate ( update) ) ;
173+ }
174+ }
175+
176+ /// Emit a [`FullScanRequestEvent::AnchorsResolved`] if `update` is non-empty.
177+ pub fn emit_anchors_resolved ( & mut self , update : TxUpdate < ConfirmationBlockTime > ) {
178+ if !update. is_empty ( ) {
179+ ( self . request . on_event ) ( FullScanRequestEvent :: AnchorsResolved ( update) ) ;
180+ }
181+ }
182+
183+ /// Update full-scan progress counters (called by chain sources between batches).
184+ pub fn set_full_scan_progress ( & mut self , progress : FullScanProgress ) {
185+ self . request . full_scan_progress = progress;
186+ }
187+
188+ /// Access the current full-scan progress.
189+ pub fn full_scan_progress ( & self ) -> & FullScanProgress {
190+ & self . request . full_scan_progress
191+ }
192+
193+ /// Mutable access to full-scan progress.
194+ pub fn full_scan_progress_mut ( & mut self ) -> & mut FullScanProgress {
195+ & mut self . request . full_scan_progress
196+ }
197+ }
198+
89199/// [`Script`] with expected [`Txid`] histories.
90200#[ derive( Debug , Clone ) ]
91201pub struct SpkWithExpectedTxids {
@@ -202,13 +312,31 @@ impl<I, D> SyncRequestBuilder<I, D> {
202312 self
203313 }
204314
315+ /// Register a callback for sync events including progress and partial transaction updates.
316+ ///
317+ /// When partial updates are applied during sync, [`SyncResponse::tx_update`] at the end
318+ /// contains only undrained remainder (empty when everything was streamed).
319+ pub fn on_event < F > ( mut self , on_event : F ) -> Self
320+ where
321+ F : for < ' a > FnMut ( SyncRequestEvent < ' a , I > ) + Send + ' static ,
322+ {
323+ self . inner . emit_partial_updates = true ;
324+ self . inner . on_event = Box :: new ( on_event) ;
325+ self
326+ }
327+
205328 /// Set the closure that will inspect every sync item visited.
206- pub fn inspect < F > ( mut self , inspect : F ) -> Self
329+ ///
330+ /// This is sugar over [`Self::on_event`] for progress-only callbacks.
331+ pub fn inspect < F > ( self , mut inspect : F ) -> Self
207332 where
208333 F : FnMut ( SyncItem < I > , SyncProgress ) + Send + ' static ,
209334 {
210- self . inner . inspect = Box :: new ( inspect) ;
211- self
335+ self . on_event ( move |event| {
336+ if let SyncRequestEvent :: ItemStarted ( item, progress) = event {
337+ inspect ( item, progress) ;
338+ }
339+ } )
212340 }
213341
214342 /// Build the [`SyncRequest`].
@@ -266,7 +394,10 @@ pub struct SyncRequest<I = (), D = BlockHash> {
266394 txids_consumed : usize ,
267395 outpoints : VecDeque < OutPoint > ,
268396 outpoints_consumed : usize ,
269- inspect : Box < InspectSync < I > > ,
397+ on_event : Box < OnSyncEvent < I , ConfirmationBlockTime > > ,
398+ /// Whether to emit partial [`TxUpdate`]s during sync (set by
399+ /// [`SyncRequestBuilder::on_event`]).
400+ pub emit_partial_updates : bool ,
270401}
271402
272403impl < I , D > From < SyncRequestBuilder < I , D > > for SyncRequest < I , D > {
@@ -296,7 +427,8 @@ impl<I, D> SyncRequest<I, D> {
296427 txids_consumed : 0 ,
297428 outpoints : VecDeque :: new ( ) ,
298429 outpoints_consumed : 0 ,
299- inspect : Box :: new ( |_, _| ( ) ) ,
430+ on_event : Box :: new ( |_| ( ) ) ,
431+ emit_partial_updates : false ,
300432 } ,
301433 }
302434 }
@@ -393,9 +525,46 @@ impl<I, D> SyncRequest<I, D> {
393525 SyncIter :: < I , D , OutPoint > :: new ( self )
394526 }
395527
528+ /// Returns a sink for emitting sync events from chain sources.
529+ pub fn event_sink ( & mut self ) -> SyncRequestEventSink < ' _ , I , D > {
530+ SyncRequestEventSink { request : self }
531+ }
532+
533+ /// Emit a partial [`TxUpdate`] if [`Self::emit_partial_updates`] is enabled.
534+ pub fn try_emit_partial_update (
535+ & mut self ,
536+ cursor : & mut Option < TxUpdateCursor < ConfirmationBlockTime > > ,
537+ tx_update : & mut TxUpdate < ConfirmationBlockTime > ,
538+ ) {
539+ if !self . emit_partial_updates {
540+ return ;
541+ }
542+ let cursor = cursor. get_or_insert_with ( TxUpdateCursor :: new) ;
543+ let delta = tx_update. drain_since ( cursor) ;
544+ if !delta. is_empty ( ) {
545+ ( self . on_event ) ( SyncRequestEvent :: PartialUpdate ( delta) ) ;
546+ }
547+ }
548+
549+ /// Emit an anchor-only [`TxUpdate`] if [`Self::emit_partial_updates`] is enabled.
550+ pub fn try_emit_anchors_resolved (
551+ & mut self ,
552+ cursor : & mut Option < TxUpdateCursor < ConfirmationBlockTime > > ,
553+ tx_update : & mut TxUpdate < ConfirmationBlockTime > ,
554+ ) {
555+ if !self . emit_partial_updates {
556+ return ;
557+ }
558+ let cursor = cursor. get_or_insert_with ( TxUpdateCursor :: new) ;
559+ let delta = tx_update. drain_since ( cursor) ;
560+ if !delta. is_empty ( ) {
561+ ( self . on_event ) ( SyncRequestEvent :: AnchorsResolved ( delta) ) ;
562+ }
563+ }
564+
396565 fn _call_inspect ( & mut self , item : SyncItem < I > ) {
397566 let progress = self . progress ( ) ;
398- ( * self . inspect ) ( item, progress) ;
567+ ( self . on_event ) ( SyncRequestEvent :: ItemStarted ( item, progress) ) ;
399568 }
400569}
401570
@@ -468,13 +637,37 @@ impl<K: Ord, D> FullScanRequestBuilder<K, D> {
468637 self
469638 }
470639
640+ /// Register a callback for full-scan events including progress and partial transaction updates.
641+ ///
642+ /// When partial updates are applied during the scan, [`FullScanResponse::tx_update`] at the end
643+ /// contains only undrained remainder (empty when everything was streamed).
644+ pub fn on_event < F > ( mut self , on_event : F ) -> Self
645+ where
646+ F : for < ' a > FnMut ( FullScanRequestEvent < ' a , K > ) + Send + ' static ,
647+ {
648+ self . inner . emit_partial_updates = true ;
649+ self . inner . on_event = Box :: new ( on_event) ;
650+ self
651+ }
652+
471653 /// Set the closure that will inspect every sync item visited.
472- pub fn inspect < F > ( mut self , inspect : F ) -> Self
654+ ///
655+ /// This is sugar over [`Self::on_event`] for progress-only callbacks.
656+ pub fn inspect < F > ( self , mut inspect : F ) -> Self
473657 where
474658 F : FnMut ( K , u32 , & Script ) + Send + ' static ,
475659 {
476- self . inner . inspect = Box :: new ( inspect) ;
477- self
660+ self . on_event ( move |event| {
661+ if let FullScanRequestEvent :: SpkStarted {
662+ keychain,
663+ index,
664+ script,
665+ ..
666+ } = event
667+ {
668+ inspect ( keychain, index, script) ;
669+ }
670+ } )
478671 }
479672
480673 /// Build the [`FullScanRequest`].
@@ -497,7 +690,11 @@ pub struct FullScanRequest<K, D = BlockHash> {
497690 chain_tip : Option < CheckPoint < D > > ,
498691 spks_by_keychain : BTreeMap < K , Box < dyn Iterator < Item = Indexed < ScriptBuf > > + Send > > ,
499692 last_revealed : BTreeMap < K , u32 > ,
500- inspect : Box < InspectFullScan < K > > ,
693+ full_scan_progress : FullScanProgress ,
694+ on_event : Box < OnFullScanEvent < K , ConfirmationBlockTime > > ,
695+ /// Whether to emit partial [`TxUpdate`]s during full scan (set by
696+ /// [`FullScanRequestBuilder::on_event`]).
697+ pub emit_partial_updates : bool ,
501698}
502699
503700impl < K , D > From < FullScanRequestBuilder < K , D > > for FullScanRequest < K , D > {
@@ -522,7 +719,9 @@ impl<K: Ord + Clone, D> FullScanRequest<K, D> {
522719 chain_tip : None ,
523720 spks_by_keychain : BTreeMap :: new ( ) ,
524721 last_revealed : BTreeMap :: new ( ) ,
525- inspect : Box :: new ( |_, _, _| ( ) ) ,
722+ full_scan_progress : FullScanProgress :: default ( ) ,
723+ on_event : Box :: new ( |_| ( ) ) ,
724+ emit_partial_updates : false ,
526725 } ,
527726 }
528727 }
@@ -572,12 +771,48 @@ impl<K: Ord + Clone, D> FullScanRequest<K, D> {
572771
573772 /// Iterate over indexed [`ScriptBuf`]s contained in this request of the given `keychain`.
574773 pub fn iter_spks ( & mut self , keychain : K ) -> impl Iterator < Item = Indexed < ScriptBuf > > + ' _ {
575- let spks = self . spks_by_keychain . get_mut ( & keychain) ;
576- let inspect = & mut self . inspect ;
577774 KeychainSpkIter {
578- keychain,
579- spks,
580- inspect,
775+ keychain : keychain. clone ( ) ,
776+ spks : self . spks_by_keychain . get_mut ( & keychain) ,
777+ full_scan_progress : & mut self . full_scan_progress ,
778+ on_event : & mut self . on_event ,
779+ }
780+ }
781+
782+ /// Returns a sink for emitting full-scan events from chain sources.
783+ pub fn event_sink ( & mut self ) -> FullScanRequestEventSink < ' _ , K , D > {
784+ FullScanRequestEventSink { request : self }
785+ }
786+
787+ /// Emit a partial [`TxUpdate`] if [`Self::emit_partial_updates`] is enabled.
788+ pub fn try_emit_partial_update (
789+ & mut self ,
790+ cursor : & mut Option < TxUpdateCursor < ConfirmationBlockTime > > ,
791+ tx_update : & mut TxUpdate < ConfirmationBlockTime > ,
792+ ) {
793+ if !self . emit_partial_updates {
794+ return ;
795+ }
796+ let cursor = cursor. get_or_insert_with ( TxUpdateCursor :: new) ;
797+ let delta = tx_update. drain_since ( cursor) ;
798+ if !delta. is_empty ( ) {
799+ ( self . on_event ) ( FullScanRequestEvent :: PartialUpdate ( delta) ) ;
800+ }
801+ }
802+
803+ /// Emit an anchor-only [`TxUpdate`] if [`Self::emit_partial_updates`] is enabled.
804+ pub fn try_emit_anchors_resolved (
805+ & mut self ,
806+ cursor : & mut Option < TxUpdateCursor < ConfirmationBlockTime > > ,
807+ tx_update : & mut TxUpdate < ConfirmationBlockTime > ,
808+ ) {
809+ if !self . emit_partial_updates {
810+ return ;
811+ }
812+ let cursor = cursor. get_or_insert_with ( TxUpdateCursor :: new) ;
813+ let delta = tx_update. drain_since ( cursor) ;
814+ if !delta. is_empty ( ) {
815+ ( self . on_event ) ( FullScanRequestEvent :: AnchorsResolved ( delta) ) ;
581816 }
582817 }
583818}
@@ -619,15 +854,26 @@ impl<K, A> FullScanResponse<K, A> {
619854struct KeychainSpkIter < ' r , K > {
620855 keychain : K ,
621856 spks : Option < & ' r mut Box < dyn Iterator < Item = Indexed < ScriptBuf > > + Send > > ,
622- inspect : & ' r mut Box < InspectFullScan < K > > ,
857+ full_scan_progress : & ' r mut FullScanProgress ,
858+ on_event : & ' r mut Box < OnFullScanEvent < K , ConfirmationBlockTime > > ,
623859}
624860
625861impl < K : Ord + Clone > Iterator for KeychainSpkIter < ' _ , K > {
626862 type Item = Indexed < ScriptBuf > ;
627863
628864 fn next ( & mut self ) -> Option < Self :: Item > {
629865 let ( i, spk) = self . spks . as_mut ( ) ?. next ( ) ?;
630- ( * self . inspect ) ( self . keychain . clone ( ) , i, & spk) ;
866+ self . full_scan_progress . keychain_spks_consumed = self
867+ . full_scan_progress
868+ . keychain_spks_consumed
869+ . saturating_add ( 1 ) ;
870+ let progress = self . full_scan_progress . clone ( ) ;
871+ ( self . on_event ) ( FullScanRequestEvent :: SpkStarted {
872+ keychain : self . keychain . clone ( ) ,
873+ index : i,
874+ script : spk. as_script ( ) ,
875+ progress,
876+ } ) ;
631877 Some ( ( i, spk) )
632878 }
633879}
0 commit comments