@@ -2,87 +2,17 @@ use bdk_chain::{
22 bitcoin:: { OutPoint , ScriptBuf , Transaction , Txid } ,
33 collections:: { BTreeMap , HashMap , HashSet } ,
44 local_chain:: CheckPoint ,
5+ spk_client:: { FullScanRequest , FullScanResult , SyncRequest , SyncResult , TxCache } ,
56 tx_graph:: TxGraph ,
67 BlockId , ConfirmationHeightAnchor , ConfirmationTimeHeightAnchor ,
78} ;
8- use core:: { fmt :: Debug , str:: FromStr } ;
9+ use core:: str:: FromStr ;
910use electrum_client:: { ElectrumApi , Error , HeaderNotification } ;
1011use std:: sync:: Arc ;
1112
1213/// We include a chain suffix of a certain length for the purpose of robustness.
1314const CHAIN_SUFFIX_LENGTH : u32 = 8 ;
1415
15- /// Type that maintains a cache of [`Arc`]-wrapped transactions.
16- pub type TxCache = HashMap < Txid , Arc < Transaction > > ;
17-
18- /// Combination of chain and transactions updates from electrum
19- ///
20- /// We have to update the chain and the txids at the same time since we anchor the txids to
21- /// the same chain tip that we check before and after we gather the txids.
22- #[ derive( Debug ) ]
23- pub struct ElectrumUpdate < A = ConfirmationHeightAnchor > {
24- /// Chain update
25- pub chain_update : CheckPoint ,
26- /// Tracks electrum updates in TxGraph
27- pub graph_update : TxGraph < A > ,
28- }
29-
30- impl < A : Clone + Ord > ElectrumUpdate < A > {
31- /// Transform the [`ElectrumUpdate`] to have [`bdk_chain::Anchor`]s of another type.
32- ///
33- /// Refer to [`TxGraph::map_anchors`].
34- pub fn map_anchors < A2 : Clone + Ord , F > ( self , f : F ) -> ElectrumUpdate < A2 >
35- where
36- F : FnMut ( A ) -> A2 ,
37- {
38- ElectrumUpdate {
39- chain_update : self . chain_update ,
40- graph_update : self . graph_update . map_anchors ( f) ,
41- }
42- }
43- }
44-
45- impl ElectrumUpdate {
46- /// Transforms the [`TxGraph`]'s [`bdk_chain::Anchor`] type to [`ConfirmationTimeHeightAnchor`].
47- pub fn into_confirmation_time_update (
48- self ,
49- client : & impl ElectrumApi ,
50- ) -> Result < ElectrumUpdate < ConfirmationTimeHeightAnchor > , Error > {
51- let relevant_heights = self
52- . graph_update
53- . all_anchors ( )
54- . iter ( )
55- . map ( |( a, _) | a. confirmation_height )
56- . collect :: < HashSet < _ > > ( ) ;
57-
58- let height_to_time = relevant_heights
59- . clone ( )
60- . into_iter ( )
61- . zip (
62- client
63- . batch_block_header ( relevant_heights) ?
64- . into_iter ( )
65- . map ( |bh| bh. time as u64 ) ,
66- )
67- . collect :: < HashMap < u32 , u64 > > ( ) ;
68-
69- let chain_update = self . chain_update ;
70- let graph_update =
71- self . graph_update
72- . clone ( )
73- . map_anchors ( |a| ConfirmationTimeHeightAnchor {
74- anchor_block : a. anchor_block ,
75- confirmation_height : a. confirmation_height ,
76- confirmation_time : height_to_time[ & a. confirmation_height ] ,
77- } ) ;
78-
79- Ok ( ElectrumUpdate {
80- chain_update,
81- graph_update,
82- } )
83- }
84- }
85-
8616/// Trait to extend [`electrum_client::Client`] functionality.
8717pub trait ElectrumExt {
8818 /// Full scan the keychain scripts specified with the blockchain (via an Electrum client) and
@@ -97,12 +27,10 @@ pub trait ElectrumExt {
9727 /// single batch request.
9828 fn full_scan < K : Ord + Clone > (
9929 & self ,
100- tx_cache : & mut TxCache ,
101- prev_tip : CheckPoint ,
102- keychain_spks : BTreeMap < K , impl IntoIterator < Item = ( u32 , ScriptBuf ) > > ,
30+ request : FullScanRequest < K > ,
10331 stop_gap : usize ,
10432 batch_size : usize ,
105- ) -> Result < ( ElectrumUpdate , BTreeMap < K , u32 > ) , Error > ;
33+ ) -> Result < FullScanResult < K , ConfirmationHeightAnchor > , Error > ;
10634
10735 /// Sync a set of scripts with the blockchain (via an Electrum client) for the data specified
10836 /// and returns updates for [`bdk_chain`] data structures.
@@ -123,28 +51,19 @@ pub trait ElectrumExt {
12351 /// [`full_scan`]: ElectrumExt::full_scan
12452 fn sync (
12553 & self ,
126- tx_cache : & mut TxCache ,
127- prev_tip : CheckPoint ,
128- misc_spks : impl IntoIterator < Item = ScriptBuf > ,
129- txids : impl IntoIterator < Item = Txid > ,
130- outpoints : impl IntoIterator < Item = OutPoint > ,
54+ request : SyncRequest ,
13155 batch_size : usize ,
132- ) -> Result < ElectrumUpdate , Error > ;
56+ ) -> Result < SyncResult < ConfirmationHeightAnchor > , Error > ;
13357}
13458
13559impl < E : ElectrumApi > ElectrumExt for E {
13660 fn full_scan < K : Ord + Clone > (
13761 & self ,
138- tx_cache : & mut TxCache ,
139- prev_tip : CheckPoint ,
140- keychain_spks : BTreeMap < K , impl IntoIterator < Item = ( u32 , ScriptBuf ) > > ,
62+ mut request : FullScanRequest < K > ,
14163 stop_gap : usize ,
14264 batch_size : usize ,
143- ) -> Result < ( ElectrumUpdate , BTreeMap < K , u32 > ) , Error > {
144- let mut request_spks = keychain_spks
145- . into_iter ( )
146- . map ( |( k, s) | ( k, s. into_iter ( ) ) )
147- . collect :: < BTreeMap < K , _ > > ( ) ;
65+ ) -> Result < FullScanResult < K , ConfirmationHeightAnchor > , Error > {
66+ let mut request_spks = request. spks_by_keychain ;
14867
14968 // We keep track of already-scanned spks just in case a reorg happens and we need to do a
15069 // rescan. We need to keep track of this as iterators in `keychain_spks` are "unbounded" so
@@ -154,8 +73,8 @@ impl<E: ElectrumApi> ElectrumExt for E {
15473 // * val: (script_pubkey, has_tx_history).
15574 let mut scanned_spks = BTreeMap :: < ( K , u32 ) , ( ScriptBuf , bool ) > :: new ( ) ;
15675
157- let ( electrum_update , keychain_update ) = loop {
158- let ( tip, _) = construct_update_tip ( self , prev_tip . clone ( ) ) ?;
76+ let update = loop {
77+ let ( tip, _) = construct_update_tip ( self , request . chain_tip . clone ( ) ) ?;
15978 let mut graph_update = TxGraph :: < ConfirmationHeightAnchor > :: default ( ) ;
16079 let cps = tip
16180 . iter ( )
@@ -168,7 +87,7 @@ impl<E: ElectrumApi> ElectrumExt for E {
16887 scanned_spks. append ( & mut populate_with_spks (
16988 self ,
17089 & cps,
171- tx_cache,
90+ & mut request . tx_cache ,
17291 & mut graph_update,
17392 & mut scanned_spks
17493 . iter ( )
@@ -182,7 +101,7 @@ impl<E: ElectrumApi> ElectrumExt for E {
182101 populate_with_spks (
183102 self ,
184103 & cps,
185- tx_cache,
104+ & mut request . tx_cache ,
186105 & mut graph_update,
187106 keychain_spks,
188107 stop_gap,
@@ -213,55 +132,118 @@ impl<E: ElectrumApi> ElectrumExt for E {
213132 } )
214133 . collect :: < BTreeMap < _ , _ > > ( ) ;
215134
216- break (
217- ElectrumUpdate {
218- chain_update,
219- graph_update,
220- } ,
221- keychain_update,
222- ) ;
135+ break FullScanResult {
136+ graph_update,
137+ chain_update,
138+ last_active_indices : keychain_update,
139+ } ;
223140 } ;
224141
225- Ok ( ( electrum_update , keychain_update ) )
142+ Ok ( update )
226143 }
227144
228145 fn sync (
229146 & self ,
230- tx_cache : & mut TxCache ,
231- prev_tip : CheckPoint ,
232- misc_spks : impl IntoIterator < Item = ScriptBuf > ,
233- txids : impl IntoIterator < Item = Txid > ,
234- outpoints : impl IntoIterator < Item = OutPoint > ,
147+ request : SyncRequest ,
235148 batch_size : usize ,
236- ) -> Result < ElectrumUpdate , Error > {
237- let spk_iter = misc_spks
238- . into_iter ( )
239- . enumerate ( )
240- . map ( |( i, spk) | ( i as u32 , spk) ) ;
241-
242- let ( electrum_update, _) = self . full_scan (
243- tx_cache,
244- prev_tip. clone ( ) ,
245- [ ( ( ) , spk_iter) ] . into ( ) ,
246- usize:: MAX ,
247- batch_size,
248- ) ?;
249-
250- let ( tip, _) = construct_update_tip ( self , prev_tip) ?;
149+ ) -> Result < SyncResult < ConfirmationHeightAnchor > , Error > {
150+ let mut tx_cache = request. tx_cache . clone ( ) ;
151+
152+ let full_scan_req = FullScanRequest :: from_chain_tip ( request. chain_tip . clone ( ) )
153+ . cache_txs ( request. tx_cache )
154+ . set_spks_for_keychain ( ( ) , request. spks . enumerate ( ) . map ( |( i, spk) | ( i as u32 , spk) ) ) ;
155+ let full_scan_res = self . full_scan ( full_scan_req, usize:: MAX , batch_size) ?;
156+
157+ let ( tip, _) = construct_update_tip ( self , request. chain_tip ) ?;
251158 let cps = tip
252159 . iter ( )
253160 . take ( 10 )
254161 . map ( |cp| ( cp. height ( ) , cp) )
255162 . collect :: < BTreeMap < u32 , CheckPoint > > ( ) ;
256163
257164 let mut tx_graph = TxGraph :: < ConfirmationHeightAnchor > :: default ( ) ;
258- populate_with_txids ( self , & cps, tx_cache, & mut tx_graph, txids) ?;
259- populate_with_outpoints ( self , & cps, & mut tx_graph, outpoints) ?;
165+ populate_with_txids ( self , & cps, & mut tx_cache, & mut tx_graph, request. txids ) ?;
166+ populate_with_outpoints ( self , & cps, & mut tx_cache, & mut tx_graph, request. outpoints ) ?;
167+
168+ Ok ( SyncResult {
169+ chain_update : full_scan_res. chain_update ,
170+ graph_update : full_scan_res. graph_update ,
171+ } )
172+ }
173+ }
174+
175+ /// Trait that extends [`SyncResult`] and [`FullScanResult`] functionality.
176+ ///
177+ /// Currently, only a single method exists that converts the update [`TxGraph`] to have an anchor
178+ /// type of [`ConfirmationTimeHeightAnchor`].
179+ pub trait ElectrumResultExt {
180+ /// New result type with a [`TxGraph`] that contains the [`ConfirmationTimeHeightAnchor`].
181+ type NewResult ;
182+
183+ /// Convert result type to have an update [`TxGraph`] that contains the [`ConfirmationTimeHeightAnchor`] .
184+ fn try_into_confirmation_time_result (
185+ self ,
186+ client : & impl ElectrumApi ,
187+ ) -> Result < Self :: NewResult , Error > ;
188+ }
189+
190+ impl < K > ElectrumResultExt for FullScanResult < K , ConfirmationHeightAnchor > {
191+ type NewResult = FullScanResult < K , ConfirmationTimeHeightAnchor > ;
192+
193+ fn try_into_confirmation_time_result (
194+ self ,
195+ client : & impl ElectrumApi ,
196+ ) -> Result < Self :: NewResult , Error > {
197+ Ok ( FullScanResult :: < K , ConfirmationTimeHeightAnchor > {
198+ graph_update : try_into_confirmation_time_result ( self . graph_update , client) ?,
199+ chain_update : self . chain_update ,
200+ last_active_indices : self . last_active_indices ,
201+ } )
202+ }
203+ }
204+
205+ impl ElectrumResultExt for SyncResult < ConfirmationHeightAnchor > {
206+ type NewResult = SyncResult < ConfirmationTimeHeightAnchor > ;
260207
261- Ok ( electrum_update)
208+ fn try_into_confirmation_time_result (
209+ self ,
210+ client : & impl ElectrumApi ,
211+ ) -> Result < Self :: NewResult , Error > {
212+ Ok ( SyncResult {
213+ graph_update : try_into_confirmation_time_result ( self . graph_update , client) ?,
214+ chain_update : self . chain_update ,
215+ } )
262216 }
263217}
264218
219+ fn try_into_confirmation_time_result (
220+ graph_update : TxGraph < ConfirmationHeightAnchor > ,
221+ client : & impl ElectrumApi ,
222+ ) -> Result < TxGraph < ConfirmationTimeHeightAnchor > , Error > {
223+ let relevant_heights = graph_update
224+ . all_anchors ( )
225+ . iter ( )
226+ . map ( |( a, _) | a. confirmation_height )
227+ . collect :: < HashSet < _ > > ( ) ;
228+
229+ let height_to_time = relevant_heights
230+ . clone ( )
231+ . into_iter ( )
232+ . zip (
233+ client
234+ . batch_block_header ( relevant_heights) ?
235+ . into_iter ( )
236+ . map ( |bh| bh. time as u64 ) ,
237+ )
238+ . collect :: < HashMap < u32 , u64 > > ( ) ;
239+
240+ Ok ( graph_update. map_anchors ( |a| ConfirmationTimeHeightAnchor {
241+ anchor_block : a. anchor_block ,
242+ confirmation_height : a. confirmation_height ,
243+ confirmation_time : height_to_time[ & a. confirmation_height ] ,
244+ } ) )
245+ }
246+
265247/// Return a [`CheckPoint`] of the latest tip, that connects with `prev_tip`.
266248fn construct_update_tip (
267249 client : & impl ElectrumApi ,
@@ -380,6 +362,7 @@ fn determine_tx_anchor(
380362fn populate_with_outpoints (
381363 client : & impl ElectrumApi ,
382364 cps : & BTreeMap < u32 , CheckPoint > ,
365+ tx_cache : & mut TxCache ,
383366 tx_graph : & mut TxGraph < ConfirmationHeightAnchor > ,
384367 outpoints : impl IntoIterator < Item = OutPoint > ,
385368) -> Result < ( ) , Error > {
@@ -415,9 +398,9 @@ fn populate_with_outpoints(
415398 let res_tx = match tx_graph. get_tx ( res. tx_hash ) {
416399 Some ( tx) => tx,
417400 None => {
418- let res_tx = client . transaction_get ( & res. tx_hash ) ?;
419- let _ = tx_graph. insert_tx ( res_tx) ;
420- tx_graph . get_tx ( res . tx_hash ) . expect ( "just inserted" )
401+ let res_tx = fetch_tx ( client , tx_cache , res. tx_hash ) ?;
402+ let _ = tx_graph. insert_tx ( Arc :: clone ( & res_tx) ) ;
403+ res_tx
421404 }
422405 } ;
423406 has_spending = res_tx
0 commit comments