11use bdk_chain:: {
2- bitcoin:: { OutPoint , ScriptBuf , Transaction , Txid } ,
2+ bitcoin:: { OutPoint , ScriptBuf , Txid } ,
3+ collections:: { HashMap , HashSet } ,
34 local_chain:: { self , CheckPoint } ,
4- tx_graph:: { self , TxGraph } ,
5- Anchor , BlockId , ConfirmationHeightAnchor , ConfirmationTimeHeightAnchor ,
6- } ;
7- use electrum_client:: { Client , ElectrumApi , Error , HeaderNotification } ;
8- use std:: {
9- collections:: { BTreeMap , BTreeSet , HashMap , HashSet } ,
10- fmt:: Debug ,
11- str:: FromStr ,
5+ tx_graph:: TxGraph ,
6+ BlockId , ConfirmationHeightAnchor , ConfirmationTimeHeightAnchor ,
127} ;
8+ use electrum_client:: { ElectrumApi , Error , HeaderNotification } ;
9+ use std:: { collections:: BTreeMap , fmt:: Debug , str:: FromStr } ;
1310
1411/// We include a chain suffix of a certain length for the purpose of robustness.
1512const CHAIN_SUFFIX_LENGTH : u32 = 8 ;
1613
17- /// Represents updates fetched from an Electrum server, but excludes full transactions.
18- ///
19- /// To provide a complete update to [`TxGraph`], you'll need to call [`Self::missing_full_txs`] to
20- /// determine the full transactions missing from [`TxGraph`]. Then call [`Self::into_tx_graph`] to
21- /// fetch the full transactions from Electrum and finalize the update.
22- #[ derive( Debug , Default , Clone ) ]
23- pub struct RelevantTxids ( HashMap < Txid , BTreeSet < ConfirmationHeightAnchor > > ) ;
24-
25- impl RelevantTxids {
26- /// Determine the full transactions that are missing from `graph`.
27- ///
28- /// Refer to [`RelevantTxids`] for more details.
29- pub fn missing_full_txs < A : Anchor > ( & self , graph : & TxGraph < A > ) -> Vec < Txid > {
30- self . 0
31- . keys ( )
32- . filter ( move |& & txid| graph. as_ref ( ) . get_tx ( txid) . is_none ( ) )
33- . cloned ( )
34- . collect ( )
35- }
36-
37- /// Finalizes the [`TxGraph`] update by fetching `missing` txids from the `client`.
38- ///
39- /// Refer to [`RelevantTxids`] for more details.
40- pub fn into_tx_graph (
41- self ,
42- client : & Client ,
43- missing : Vec < Txid > ,
44- ) -> Result < TxGraph < ConfirmationHeightAnchor > , Error > {
45- let new_txs = client. batch_transaction_get ( & missing) ?;
46- let mut graph = TxGraph :: < ConfirmationHeightAnchor > :: new ( new_txs) ;
47- for ( txid, anchors) in self . 0 {
48- for anchor in anchors {
49- let _ = graph. insert_anchor ( txid, anchor) ;
50- }
51- }
52- Ok ( graph)
53- }
54-
55- /// Finalizes the update by fetching `missing` txids from the `client`, where the
56- /// resulting [`TxGraph`] has anchors of type [`ConfirmationTimeHeightAnchor`].
57- ///
58- /// Refer to [`RelevantTxids`] for more details.
59- ///
60- /// **Note:** The confirmation time might not be precisely correct if there has been a reorg.
61- // Electrum's API intends that we use the merkle proof API, we should change `bdk_electrum` to
62- // use it.
63- pub fn into_confirmation_time_tx_graph (
64- self ,
65- client : & Client ,
66- missing : Vec < Txid > ,
67- ) -> Result < TxGraph < ConfirmationTimeHeightAnchor > , Error > {
68- let graph = self . into_tx_graph ( client, missing) ?;
69-
70- let relevant_heights = {
71- let mut visited_heights = HashSet :: new ( ) ;
72- graph
73- . all_anchors ( )
74- . iter ( )
75- . map ( |( a, _) | a. confirmation_height_upper_bound ( ) )
76- . filter ( move |& h| visited_heights. insert ( h) )
77- . collect :: < Vec < _ > > ( )
78- } ;
79-
80- let height_to_time = relevant_heights
81- . clone ( )
82- . into_iter ( )
83- . zip (
84- client
85- . batch_block_header ( relevant_heights) ?
86- . into_iter ( )
87- . map ( |bh| bh. time as u64 ) ,
88- )
89- . collect :: < HashMap < u32 , u64 > > ( ) ;
90-
91- let graph_changeset = {
92- let old_changeset = TxGraph :: default ( ) . apply_update ( graph) ;
93- tx_graph:: ChangeSet {
94- txs : old_changeset. txs ,
95- txouts : old_changeset. txouts ,
96- last_seen : old_changeset. last_seen ,
97- anchors : old_changeset
98- . anchors
99- . into_iter ( )
100- . map ( |( height_anchor, txid) | {
101- let confirmation_height = height_anchor. confirmation_height ;
102- let confirmation_time = height_to_time[ & confirmation_height] ;
103- let time_anchor = ConfirmationTimeHeightAnchor {
104- anchor_block : height_anchor. anchor_block ,
105- confirmation_height,
106- confirmation_time,
107- } ;
108- ( time_anchor, txid)
109- } )
110- . collect ( ) ,
111- }
112- } ;
113-
114- let mut new_graph = TxGraph :: default ( ) ;
115- new_graph. apply_changeset ( graph_changeset) ;
116- Ok ( new_graph)
117- }
118- }
119-
12014/// Combination of chain and transactions updates from electrum
12115///
12216/// We have to update the chain and the txids at the same time since we anchor the txids to
@@ -125,11 +19,11 @@ impl RelevantTxids {
12519pub struct ElectrumUpdate {
12620 /// Chain update
12721 pub chain_update : local_chain:: Update ,
128- /// Transaction updates from electrum
129- pub relevant_txids : RelevantTxids ,
22+ /// Tracks electrum updates in TxGraph
23+ pub graph_update : TxGraph < ConfirmationTimeHeightAnchor > ,
13024}
13125
132- /// Trait to extend [`Client`] functionality.
26+ /// Trait to extend [`electrum_client:: Client`] functionality.
13327pub trait ElectrumExt {
13428 /// Full scan the keychain scripts specified with the blockchain (via an Electrum client) and
13529 /// returns updates for [`bdk_chain`] data structures.
@@ -153,7 +47,7 @@ pub trait ElectrumExt {
15347 ///
15448 /// - `prev_tip`: the most recent blockchain tip present locally
15549 /// - `misc_spks`: an iterator of scripts we want to sync transactions for
156- /// - `txids`: transactions for which we want updated [`Anchor`]s
50+ /// - `txids`: transactions for which we want updated [`bdk_chain:: Anchor`]s
15751 /// - `outpoints`: transactions associated with these outpoints (residing, spending) that we
15852 /// want to include in the update
15953 ///
@@ -190,7 +84,7 @@ impl<A: ElectrumApi> ElectrumExt for A {
19084
19185 let ( electrum_update, keychain_update) = loop {
19286 let ( tip, _) = construct_update_tip ( self , prev_tip. clone ( ) ) ?;
193- let mut relevant_txids = RelevantTxids :: default ( ) ;
87+ let mut tx_graph = TxGraph :: < ConfirmationHeightAnchor > :: default ( ) ;
19488 let cps = tip
19589 . iter ( )
19690 . take ( 10 )
@@ -202,7 +96,7 @@ impl<A: ElectrumApi> ElectrumExt for A {
20296 scanned_spks. append ( & mut populate_with_spks (
20397 self ,
20498 & cps,
205- & mut relevant_txids ,
99+ & mut tx_graph ,
206100 & mut scanned_spks
207101 . iter ( )
208102 . map ( |( i, ( spk, _) ) | ( i. clone ( ) , spk. clone ( ) ) ) ,
@@ -215,7 +109,7 @@ impl<A: ElectrumApi> ElectrumExt for A {
215109 populate_with_spks (
216110 self ,
217111 & cps,
218- & mut relevant_txids ,
112+ & mut tx_graph ,
219113 keychain_spks,
220114 stop_gap,
221115 batch_size,
@@ -237,6 +131,8 @@ impl<A: ElectrumApi> ElectrumExt for A {
237131 introduce_older_blocks : true ,
238132 } ;
239133
134+ let graph_update = into_confirmation_time_tx_graph ( self , & tx_graph) ?;
135+
240136 let keychain_update = request_spks
241137 . into_keys ( )
242138 . filter_map ( |k| {
@@ -251,7 +147,7 @@ impl<A: ElectrumApi> ElectrumExt for A {
251147 break (
252148 ElectrumUpdate {
253149 chain_update,
254- relevant_txids ,
150+ graph_update ,
255151 } ,
256152 keychain_update,
257153 ) ;
@@ -287,10 +183,12 @@ impl<A: ElectrumApi> ElectrumExt for A {
287183 . map ( |cp| ( cp. height ( ) , cp) )
288184 . collect :: < BTreeMap < u32 , CheckPoint > > ( ) ;
289185
290- populate_with_txids ( self , & cps, & mut electrum_update. relevant_txids , txids) ?;
291-
292- let _txs =
293- populate_with_outpoints ( self , & cps, & mut electrum_update. relevant_txids , outpoints) ?;
186+ let mut tx_graph = TxGraph :: < ConfirmationHeightAnchor > :: default ( ) ;
187+ populate_with_txids ( self , & cps, & mut tx_graph, txids) ?;
188+ populate_with_outpoints ( self , & cps, & mut tx_graph, outpoints) ?;
189+ let _ = electrum_update
190+ . graph_update
191+ . apply_update ( into_confirmation_time_tx_graph ( self , & tx_graph) ?) ;
294192
295193 Ok ( electrum_update)
296194 }
@@ -414,10 +312,9 @@ fn determine_tx_anchor(
414312fn populate_with_outpoints (
415313 client : & impl ElectrumApi ,
416314 cps : & BTreeMap < u32 , CheckPoint > ,
417- relevant_txids : & mut RelevantTxids ,
315+ tx_graph : & mut TxGraph < ConfirmationHeightAnchor > ,
418316 outpoints : impl IntoIterator < Item = OutPoint > ,
419- ) -> Result < HashMap < Txid , Transaction > , Error > {
420- let mut full_txs = HashMap :: new ( ) ;
317+ ) -> Result < ( ) , Error > {
421318 for outpoint in outpoints {
422319 let txid = outpoint. txid ;
423320 let tx = client. transaction_get ( & txid) ?;
@@ -440,17 +337,19 @@ fn populate_with_outpoints(
440337 continue ;
441338 }
442339 has_residing = true ;
443- full_txs. insert ( res. tx_hash , tx. clone ( ) ) ;
340+ if tx_graph. get_tx ( res. tx_hash ) . is_none ( ) {
341+ let _ = tx_graph. insert_tx ( tx. clone ( ) ) ;
342+ }
444343 } else {
445344 if has_spending {
446345 continue ;
447346 }
448- let res_tx = match full_txs . get ( & res. tx_hash ) {
347+ let res_tx = match tx_graph . get_tx ( res. tx_hash ) {
449348 Some ( tx) => tx,
450349 None => {
451350 let res_tx = client. transaction_get ( & res. tx_hash ) ?;
452- full_txs . insert ( res . tx_hash , res_tx) ;
453- full_txs . get ( & res. tx_hash ) . expect ( "just inserted" )
351+ let _ = tx_graph . insert_tx ( res_tx) ;
352+ tx_graph . get_tx ( res. tx_hash ) . expect ( "just inserted" )
454353 }
455354 } ;
456355 has_spending = res_tx
@@ -462,20 +361,18 @@ fn populate_with_outpoints(
462361 }
463362 } ;
464363
465- let anchor = determine_tx_anchor ( cps, res. height , res. tx_hash ) ;
466- let tx_entry = relevant_txids. 0 . entry ( res. tx_hash ) . or_default ( ) ;
467- if let Some ( anchor) = anchor {
468- tx_entry. insert ( anchor) ;
364+ if let Some ( anchor) = determine_tx_anchor ( cps, res. height , res. tx_hash ) {
365+ let _ = tx_graph. insert_anchor ( res. tx_hash , anchor) ;
469366 }
470367 }
471368 }
472- Ok ( full_txs )
369+ Ok ( ( ) )
473370}
474371
475372fn populate_with_txids (
476373 client : & impl ElectrumApi ,
477374 cps : & BTreeMap < u32 , CheckPoint > ,
478- relevant_txids : & mut RelevantTxids ,
375+ tx_graph : & mut TxGraph < ConfirmationHeightAnchor > ,
479376 txids : impl IntoIterator < Item = Txid > ,
480377) -> Result < ( ) , Error > {
481378 for txid in txids {
@@ -500,9 +397,11 @@ fn populate_with_txids(
500397 None => continue ,
501398 } ;
502399
503- let tx_entry = relevant_txids. 0 . entry ( txid) . or_default ( ) ;
400+ if tx_graph. get_tx ( txid) . is_none ( ) {
401+ let _ = tx_graph. insert_tx ( tx) ;
402+ }
504403 if let Some ( anchor) = anchor {
505- tx_entry . insert ( anchor) ;
404+ let _ = tx_graph . insert_anchor ( txid , anchor) ;
506405 }
507406 }
508407 Ok ( ( ) )
@@ -511,7 +410,7 @@ fn populate_with_txids(
511410fn populate_with_spks < I : Ord + Clone > (
512411 client : & impl ElectrumApi ,
513412 cps : & BTreeMap < u32 , CheckPoint > ,
514- relevant_txids : & mut RelevantTxids ,
413+ tx_graph : & mut TxGraph < ConfirmationHeightAnchor > ,
515414 spks : & mut impl Iterator < Item = ( I , ScriptBuf ) > ,
516415 stop_gap : usize ,
517416 batch_size : usize ,
@@ -544,11 +443,50 @@ fn populate_with_spks<I: Ord + Clone>(
544443 }
545444
546445 for tx in spk_history {
547- let tx_entry = relevant_txids. 0 . entry ( tx. tx_hash ) . or_default ( ) ;
446+ let mut update = TxGraph :: < ConfirmationHeightAnchor > :: default ( ) ;
447+
448+ if tx_graph. get_tx ( tx. tx_hash ) . is_none ( ) {
449+ let full_tx = client. transaction_get ( & tx. tx_hash ) ?;
450+ update = TxGraph :: < ConfirmationHeightAnchor > :: new ( [ full_tx] ) ;
451+ }
452+
548453 if let Some ( anchor) = determine_tx_anchor ( cps, tx. height , tx. tx_hash ) {
549- tx_entry . insert ( anchor) ;
454+ let _ = update . insert_anchor ( tx . tx_hash , anchor) ;
550455 }
456+
457+ let _ = tx_graph. apply_update ( update) ;
551458 }
552459 }
553460 }
554461}
462+
463+ fn into_confirmation_time_tx_graph (
464+ client : & impl ElectrumApi ,
465+ tx_graph : & TxGraph < ConfirmationHeightAnchor > ,
466+ ) -> Result < TxGraph < ConfirmationTimeHeightAnchor > , Error > {
467+ let relevant_heights = tx_graph
468+ . all_anchors ( )
469+ . iter ( )
470+ . map ( |( a, _) | a. confirmation_height )
471+ . collect :: < HashSet < _ > > ( ) ;
472+
473+ let height_to_time = relevant_heights
474+ . clone ( )
475+ . into_iter ( )
476+ . zip (
477+ client
478+ . batch_block_header ( relevant_heights) ?
479+ . into_iter ( )
480+ . map ( |bh| bh. time as u64 ) ,
481+ )
482+ . collect :: < HashMap < u32 , u64 > > ( ) ;
483+
484+ let new_graph = tx_graph
485+ . clone ( )
486+ . map_anchors ( |a| ConfirmationTimeHeightAnchor {
487+ anchor_block : a. anchor_block ,
488+ confirmation_height : a. confirmation_height ,
489+ confirmation_time : height_to_time[ & a. confirmation_height ] ,
490+ } ) ;
491+ Ok ( new_graph)
492+ }
0 commit comments