@@ -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,22 @@ 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 ;
67+ // .into_iter()
68+ // .map(|(k, s)| (k, s.into_iter()))
69+ // .collect::<BTreeMap<K, _>>();
14870
14971 // We keep track of already-scanned spks just in case a reorg happens and we need to do a
15072 // rescan. We need to keep track of this as iterators in `keychain_spks` are "unbounded" so
@@ -154,8 +76,8 @@ impl<E: ElectrumApi> ElectrumExt for E {
15476 // * val: (script_pubkey, has_tx_history).
15577 let mut scanned_spks = BTreeMap :: < ( K , u32 ) , ( ScriptBuf , bool ) > :: new ( ) ;
15678
157- let ( electrum_update , keychain_update ) = loop {
158- let ( tip, _) = construct_update_tip ( self , prev_tip . clone ( ) ) ?;
79+ let update = loop {
80+ let ( tip, _) = construct_update_tip ( self , request . chain_tip . clone ( ) ) ?;
15981 let mut graph_update = TxGraph :: < ConfirmationHeightAnchor > :: default ( ) ;
16082 let cps = tip
16183 . iter ( )
@@ -168,7 +90,7 @@ impl<E: ElectrumApi> ElectrumExt for E {
16890 scanned_spks. append ( & mut populate_with_spks (
16991 self ,
17092 & cps,
171- tx_cache,
93+ & mut request . tx_cache ,
17294 & mut graph_update,
17395 & mut scanned_spks
17496 . iter ( )
@@ -182,7 +104,7 @@ impl<E: ElectrumApi> ElectrumExt for E {
182104 populate_with_spks (
183105 self ,
184106 & cps,
185- tx_cache,
107+ & mut request . tx_cache ,
186108 & mut graph_update,
187109 keychain_spks,
188110 stop_gap,
@@ -213,55 +135,118 @@ impl<E: ElectrumApi> ElectrumExt for E {
213135 } )
214136 . collect :: < BTreeMap < _ , _ > > ( ) ;
215137
216- break (
217- ElectrumUpdate {
218- chain_update,
219- graph_update,
220- } ,
221- keychain_update,
222- ) ;
138+ break FullScanResult {
139+ graph_update,
140+ chain_update,
141+ last_active_indices : keychain_update,
142+ } ;
223143 } ;
224144
225- Ok ( ( electrum_update , keychain_update ) )
145+ Ok ( update )
226146 }
227147
228148 fn sync (
229149 & 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 > ,
150+ request : SyncRequest ,
235151 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) ?;
152+ ) -> Result < SyncResult < ConfirmationHeightAnchor > , Error > {
153+ let mut tx_cache = request. tx_cache . clone ( ) ;
154+
155+ let full_scan_req = FullScanRequest :: from_chain_tip ( request. chain_tip . clone ( ) )
156+ . cache_txs ( request. tx_cache )
157+ . set_spks_for_keychain ( ( ) , request. spks . enumerate ( ) . map ( |( i, spk) | ( i as u32 , spk) ) ) ;
158+ let full_scan_res = self . full_scan ( full_scan_req, usize:: MAX , batch_size) ?;
159+
160+ let ( tip, _) = construct_update_tip ( self , request. chain_tip ) ?;
251161 let cps = tip
252162 . iter ( )
253163 . take ( 10 )
254164 . map ( |cp| ( cp. height ( ) , cp) )
255165 . collect :: < BTreeMap < u32 , CheckPoint > > ( ) ;
256166
257167 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) ?;
168+ populate_with_txids ( self , & cps, & mut tx_cache, & mut tx_graph, request. txids ) ?;
169+ populate_with_outpoints ( self , & cps, & mut tx_cache, & mut tx_graph, request. outpoints ) ?;
170+
171+ Ok ( SyncResult {
172+ chain_update : full_scan_res. chain_update ,
173+ graph_update : full_scan_res. graph_update ,
174+ } )
175+ }
176+ }
177+
178+ /// Trait that extends [`SyncResult`] and [`FullScanResult`] functionality.
179+ ///
180+ /// Currently, only a single method exists that converts the update [`TxGraph`] to have an anchor
181+ /// type of [`ConfirmationTimeHeightAnchor`].
182+ pub trait ElectrumResultExt {
183+ /// New result type with a [`TxGraph`] that contains the [`ConfirmationTimeHeightAnchor`].
184+ type NewResult ;
185+
186+ /// Convert result type to have an update [`TxGraph`] that contains the [`ConfirmationTimeHeightAnchor`] .
187+ fn try_into_confirmation_time_result (
188+ self ,
189+ client : & impl ElectrumApi ,
190+ ) -> Result < Self :: NewResult , Error > ;
191+ }
192+
193+ impl < K > ElectrumResultExt for FullScanResult < K , ConfirmationHeightAnchor > {
194+ type NewResult = FullScanResult < K , ConfirmationTimeHeightAnchor > ;
195+
196+ fn try_into_confirmation_time_result (
197+ self ,
198+ client : & impl ElectrumApi ,
199+ ) -> Result < Self :: NewResult , Error > {
200+ Ok ( FullScanResult :: < K , ConfirmationTimeHeightAnchor > {
201+ graph_update : try_into_confirmation_time_result ( self . graph_update , client) ?,
202+ chain_update : self . chain_update ,
203+ last_active_indices : self . last_active_indices ,
204+ } )
205+ }
206+ }
207+
208+ impl ElectrumResultExt for SyncResult < ConfirmationHeightAnchor > {
209+ type NewResult = SyncResult < ConfirmationTimeHeightAnchor > ;
260210
261- Ok ( electrum_update)
211+ fn try_into_confirmation_time_result (
212+ self ,
213+ client : & impl ElectrumApi ,
214+ ) -> Result < Self :: NewResult , Error > {
215+ Ok ( SyncResult {
216+ graph_update : try_into_confirmation_time_result ( self . graph_update , client) ?,
217+ chain_update : self . chain_update ,
218+ } )
262219 }
263220}
264221
222+ fn try_into_confirmation_time_result (
223+ graph_update : TxGraph < ConfirmationHeightAnchor > ,
224+ client : & impl ElectrumApi ,
225+ ) -> Result < TxGraph < ConfirmationTimeHeightAnchor > , Error > {
226+ let relevant_heights = graph_update
227+ . all_anchors ( )
228+ . iter ( )
229+ . map ( |( a, _) | a. confirmation_height )
230+ . collect :: < HashSet < _ > > ( ) ;
231+
232+ let height_to_time = relevant_heights
233+ . clone ( )
234+ . into_iter ( )
235+ . zip (
236+ client
237+ . batch_block_header ( relevant_heights) ?
238+ . into_iter ( )
239+ . map ( |bh| bh. time as u64 ) ,
240+ )
241+ . collect :: < HashMap < u32 , u64 > > ( ) ;
242+
243+ Ok ( graph_update. map_anchors ( |a| ConfirmationTimeHeightAnchor {
244+ anchor_block : a. anchor_block ,
245+ confirmation_height : a. confirmation_height ,
246+ confirmation_time : height_to_time[ & a. confirmation_height ] ,
247+ } ) )
248+ }
249+
265250/// Return a [`CheckPoint`] of the latest tip, that connects with `prev_tip`.
266251fn construct_update_tip (
267252 client : & impl ElectrumApi ,
@@ -380,6 +365,7 @@ fn determine_tx_anchor(
380365fn populate_with_outpoints (
381366 client : & impl ElectrumApi ,
382367 cps : & BTreeMap < u32 , CheckPoint > ,
368+ tx_cache : & mut TxCache ,
383369 tx_graph : & mut TxGraph < ConfirmationHeightAnchor > ,
384370 outpoints : impl IntoIterator < Item = OutPoint > ,
385371) -> Result < ( ) , Error > {
@@ -415,9 +401,9 @@ fn populate_with_outpoints(
415401 let res_tx = match tx_graph. get_tx ( res. tx_hash ) {
416402 Some ( tx) => tx,
417403 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" )
404+ let res_tx = fetch_tx ( client , tx_cache , res. tx_hash ) ?;
405+ let _ = tx_graph. insert_tx ( Arc :: clone ( & res_tx) ) ;
406+ res_tx
421407 }
422408 } ;
423409 has_spending = res_tx
0 commit comments