11use bdk_chain:: {
2- bitcoin:: { OutPoint , ScriptBuf , Transaction , Txid } ,
2+ bitcoin:: { OutPoint , ScriptBuf , Txid } ,
33 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 ,
4+ tx_graph:: TxGraph ,
5+ BlockId , ConfirmationTimeHeightAnchor ,
126} ;
7+ use electrum_client:: { ElectrumApi , Error , HeaderNotification } ;
8+ use std:: { collections:: BTreeMap , fmt:: Debug , str:: FromStr } ;
139
1410/// We include a chain suffix of a certain length for the purpose of robustness.
1511const CHAIN_SUFFIX_LENGTH : u32 = 8 ;
1612
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-
12013/// Combination of chain and transactions updates from electrum
12114///
12215/// We have to update the chain and the txids at the same time since we anchor the txids to
@@ -125,11 +18,11 @@ impl RelevantTxids {
12518pub struct ElectrumUpdate {
12619 /// Chain update
12720 pub chain_update : local_chain:: Update ,
128- /// Transaction updates from electrum
129- pub relevant_txids : RelevantTxids ,
21+ /// Tracks electrum updates in TxGraph
22+ pub graph_update : TxGraph < ConfirmationTimeHeightAnchor > ,
13023}
13124
132- /// Trait to extend [`Client`] functionality.
25+ /// Trait to extend [`electrum_client:: Client`] functionality.
13326pub trait ElectrumExt {
13427 /// Full scan the keychain scripts specified with the blockchain (via an Electrum client) and
13528 /// returns updates for [`bdk_chain`] data structures.
@@ -153,7 +46,7 @@ pub trait ElectrumExt {
15346 ///
15447 /// - `prev_tip`: the most recent blockchain tip present locally
15548 /// - `misc_spks`: an iterator of scripts we want to sync transactions for
156- /// - `txids`: transactions for which we want updated [`Anchor`]s
49+ /// - `txids`: transactions for which we want updated [`bdk_chain:: Anchor`]s
15750 /// - `outpoints`: transactions associated with these outpoints (residing, spending) that we
15851 /// want to include in the update
15952 ///
@@ -190,7 +83,7 @@ impl<A: ElectrumApi> ElectrumExt for A {
19083
19184 let ( electrum_update, keychain_update) = loop {
19285 let ( tip, _) = construct_update_tip ( self , prev_tip. clone ( ) ) ?;
193- let mut relevant_txids = RelevantTxids :: default ( ) ;
86+ let mut graph_update = TxGraph :: < ConfirmationTimeHeightAnchor > :: default ( ) ;
19487 let cps = tip
19588 . iter ( )
19689 . take ( 10 )
@@ -202,7 +95,7 @@ impl<A: ElectrumApi> ElectrumExt for A {
20295 scanned_spks. append ( & mut populate_with_spks (
20396 self ,
20497 & cps,
205- & mut relevant_txids ,
98+ & mut graph_update ,
20699 & mut scanned_spks
207100 . iter ( )
208101 . map ( |( i, ( spk, _) ) | ( i. clone ( ) , spk. clone ( ) ) ) ,
@@ -215,7 +108,7 @@ impl<A: ElectrumApi> ElectrumExt for A {
215108 populate_with_spks (
216109 self ,
217110 & cps,
218- & mut relevant_txids ,
111+ & mut graph_update ,
219112 keychain_spks,
220113 stop_gap,
221114 batch_size,
@@ -251,7 +144,7 @@ impl<A: ElectrumApi> ElectrumExt for A {
251144 break (
252145 ElectrumUpdate {
253146 chain_update,
254- relevant_txids ,
147+ graph_update ,
255148 } ,
256149 keychain_update,
257150 ) ;
@@ -287,10 +180,8 @@ impl<A: ElectrumApi> ElectrumExt for A {
287180 . map ( |cp| ( cp. height ( ) , cp) )
288181 . collect :: < BTreeMap < u32 , CheckPoint > > ( ) ;
289182
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) ?;
183+ populate_with_txids ( self , & cps, & mut electrum_update. graph_update , txids) ?;
184+ populate_with_outpoints ( self , & cps, & mut electrum_update. graph_update , outpoints) ?;
294185
295186 Ok ( electrum_update)
296187 }
@@ -373,10 +264,16 @@ fn construct_update_tip(
373264///
374265/// [tx status](https://electrumx-spesmilo.readthedocs.io/en/latest/protocol-basics.html#status)
375266fn determine_tx_anchor (
267+ client : & impl ElectrumApi ,
376268 cps : & BTreeMap < u32 , CheckPoint > ,
377269 raw_height : i32 ,
378270 txid : Txid ,
379- ) -> Option < ConfirmationHeightAnchor > {
271+ ) -> Option < ConfirmationTimeHeightAnchor > {
272+ let confirmation_time = client
273+ . block_header ( raw_height as usize )
274+ . expect ( "header must exist" )
275+ . time as u64 ;
276+
380277 // The electrum API has a weird quirk where an unconfirmed transaction is presented with a
381278 // height of 0. To avoid invalid representation in our data structures, we manually set
382279 // transactions residing in the genesis block to have height 0, then interpret a height of 0 as
@@ -386,9 +283,10 @@ fn determine_tx_anchor(
386283 . expect ( "must deserialize genesis coinbase txid" )
387284 {
388285 let anchor_block = cps. values ( ) . next ( ) ?. block_id ( ) ;
389- return Some ( ConfirmationHeightAnchor {
286+ return Some ( ConfirmationTimeHeightAnchor {
390287 anchor_block,
391288 confirmation_height : 0 ,
289+ confirmation_time,
392290 } ) ;
393291 }
394292 match raw_height {
@@ -402,9 +300,10 @@ fn determine_tx_anchor(
402300 if h > anchor_block. height {
403301 None
404302 } else {
405- Some ( ConfirmationHeightAnchor {
303+ Some ( ConfirmationTimeHeightAnchor {
406304 anchor_block,
407305 confirmation_height : h,
306+ confirmation_time,
408307 } )
409308 }
410309 }
@@ -414,10 +313,9 @@ fn determine_tx_anchor(
414313fn populate_with_outpoints (
415314 client : & impl ElectrumApi ,
416315 cps : & BTreeMap < u32 , CheckPoint > ,
417- relevant_txids : & mut RelevantTxids ,
316+ tx_graph : & mut TxGraph < ConfirmationTimeHeightAnchor > ,
418317 outpoints : impl IntoIterator < Item = OutPoint > ,
419- ) -> Result < HashMap < Txid , Transaction > , Error > {
420- let mut full_txs = HashMap :: new ( ) ;
318+ ) -> Result < ( ) , Error > {
421319 for outpoint in outpoints {
422320 let txid = outpoint. txid ;
423321 let tx = client. transaction_get ( & txid) ?;
@@ -431,6 +329,8 @@ fn populate_with_outpoints(
431329 let mut has_residing = false ; // tx in which the outpoint resides
432330 let mut has_spending = false ; // tx that spends the outpoint
433331 for res in client. script_get_history ( & txout. script_pubkey ) ? {
332+ let mut update = TxGraph :: < ConfirmationTimeHeightAnchor > :: default ( ) ;
333+
434334 if has_residing && has_spending {
435335 break ;
436336 }
@@ -440,17 +340,19 @@ fn populate_with_outpoints(
440340 continue ;
441341 }
442342 has_residing = true ;
443- full_txs. insert ( res. tx_hash , tx. clone ( ) ) ;
343+ if tx_graph. get_tx ( res. tx_hash ) . is_none ( ) {
344+ update = TxGraph :: < ConfirmationTimeHeightAnchor > :: new ( [ tx. clone ( ) ] ) ;
345+ }
444346 } else {
445347 if has_spending {
446348 continue ;
447349 }
448- let res_tx = match full_txs . get ( & res. tx_hash ) {
350+ let res_tx = match tx_graph . get_tx ( res. tx_hash ) {
449351 Some ( tx) => tx,
450352 None => {
451353 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" )
354+ update = TxGraph :: < ConfirmationTimeHeightAnchor > :: new ( [ res_tx] ) ;
355+ tx_graph . get_tx ( res. tx_hash ) . expect ( "just inserted" )
454356 }
455357 } ;
456358 has_spending = res_tx
@@ -462,23 +364,25 @@ fn populate_with_outpoints(
462364 }
463365 } ;
464366
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) ;
367+ if let Some ( anchor) = determine_tx_anchor ( client, cps, res. height , res. tx_hash ) {
368+ let _ = update. insert_anchor ( res. tx_hash , anchor) ;
469369 }
370+
371+ let _ = tx_graph. apply_update ( update) ;
470372 }
471373 }
472- Ok ( full_txs )
374+ Ok ( ( ) )
473375}
474376
475377fn populate_with_txids (
476378 client : & impl ElectrumApi ,
477379 cps : & BTreeMap < u32 , CheckPoint > ,
478- relevant_txids : & mut RelevantTxids ,
380+ tx_graph : & mut TxGraph < ConfirmationTimeHeightAnchor > ,
479381 txids : impl IntoIterator < Item = Txid > ,
480382) -> Result < ( ) , Error > {
481383 for txid in txids {
384+ let mut update = TxGraph :: < ConfirmationTimeHeightAnchor > :: default ( ) ;
385+
482386 let tx = match client. transaction_get ( & txid) {
483387 Ok ( tx) => tx,
484388 Err ( electrum_client:: Error :: Protocol ( _) ) => continue ,
@@ -496,22 +400,27 @@ fn populate_with_txids(
496400 . into_iter ( )
497401 . find ( |r| r. tx_hash == txid)
498402 {
499- Some ( r) => determine_tx_anchor ( cps, r. height , txid) ,
403+ Some ( r) => determine_tx_anchor ( client , cps, r. height , txid) ,
500404 None => continue ,
501405 } ;
502406
503- let tx_entry = relevant_txids. 0 . entry ( txid) . or_default ( ) ;
407+ if tx_graph. get_tx ( txid) . is_none ( ) {
408+ update = TxGraph :: < ConfirmationTimeHeightAnchor > :: new ( [ tx] ) ;
409+ }
410+
504411 if let Some ( anchor) = anchor {
505- tx_entry . insert ( anchor) ;
412+ let _ = update . insert_anchor ( txid , anchor) ;
506413 }
414+
415+ let _ = tx_graph. apply_update ( update) ;
507416 }
508417 Ok ( ( ) )
509418}
510419
511420fn populate_with_spks < I : Ord + Clone > (
512421 client : & impl ElectrumApi ,
513422 cps : & BTreeMap < u32 , CheckPoint > ,
514- relevant_txids : & mut RelevantTxids ,
423+ tx_graph : & mut TxGraph < ConfirmationTimeHeightAnchor > ,
515424 spks : & mut impl Iterator < Item = ( I , ScriptBuf ) > ,
516425 stop_gap : usize ,
517426 batch_size : usize ,
@@ -544,10 +453,18 @@ fn populate_with_spks<I: Ord + Clone>(
544453 }
545454
546455 for tx in spk_history {
547- let tx_entry = relevant_txids. 0 . entry ( tx. tx_hash ) . or_default ( ) ;
548- if let Some ( anchor) = determine_tx_anchor ( cps, tx. height , tx. tx_hash ) {
549- tx_entry. insert ( anchor) ;
456+ let mut update = TxGraph :: < ConfirmationTimeHeightAnchor > :: default ( ) ;
457+
458+ if tx_graph. get_tx ( tx. tx_hash ) . is_none ( ) {
459+ let full_tx = client. transaction_get ( & tx. tx_hash ) ?;
460+ update = TxGraph :: < ConfirmationTimeHeightAnchor > :: new ( [ full_tx] ) ;
461+ }
462+
463+ if let Some ( anchor) = determine_tx_anchor ( client, cps, tx. height , tx. tx_hash ) {
464+ let _ = update. insert_anchor ( tx. tx_hash , anchor) ;
550465 }
466+
467+ let _ = tx_graph. apply_update ( update) ;
551468 }
552469 }
553470 }
0 commit comments