@@ -7,6 +7,7 @@ use bdk_core::{
77 BlockId , CheckPoint , ConfirmationBlockTime , TxUpdate ,
88} ;
99use electrum_client:: { ElectrumApi , Error , HeaderNotification } ;
10+ use std:: convert:: TryInto ;
1011use std:: sync:: { Arc , Mutex } ;
1112
1213/// We include a chain suffix of a certain length for the purpose of robustness.
@@ -46,8 +47,24 @@ impl<E: ElectrumApi> BdkElectrumClient<E> {
4647 }
4748 }
4849
49- /// Inserts transactions into the transaction cache so that the client will not fetch these
50- /// transactions.
50+ /// Insert anchors into the anchor cache so that the client will not re-fetch them.
51+ ///
52+ /// Typically used to pre-populate the cache from an existing `TxGraph`.
53+ pub fn populate_anchor_cache (
54+ & self ,
55+ tx_anchors : impl IntoIterator < Item = ( Txid , impl IntoIterator < Item = ConfirmationBlockTime > ) > ,
56+ ) {
57+ let mut cache = self . anchor_cache . lock ( ) . unwrap ( ) ;
58+ for ( txid, anchors) in tx_anchors {
59+ for anchor in anchors {
60+ cache. insert ( ( txid, anchor. block_id . hash ) , anchor) ;
61+ }
62+ }
63+ }
64+
65+ /// Insert transactions into the transaction cache so that the client will not re-fetch them.
66+ ///
67+ /// Typically used to pre-populate the cache from an existing `TxGraph`.
5168 pub fn populate_tx_cache ( & self , txs : impl IntoIterator < Item = impl Into < Arc < Transaction > > > ) {
5269 let mut tx_cache = self . tx_cache . lock ( ) . unwrap ( ) ;
5370 for tx in txs {
@@ -547,17 +564,16 @@ impl<E: ElectrumApi> BdkElectrumClient<E> {
547564 if let Some ( anchor) = anchor_cache. get ( & ( txid, hash) ) {
548565 results. push ( ( txid, * anchor) ) ;
549566 } else {
550- to_fetch. push ( ( txid, height, hash ) ) ;
567+ to_fetch. push ( ( txid, height) ) ;
551568 }
552569 }
553570 }
554571
555572 // Fetch merkle proofs.
556- let txids_and_heights = to_fetch. iter ( ) . map ( |& ( txid, height, _) | ( txid, height) ) ;
557- let proofs = self . inner . batch_transaction_get_merkle ( txids_and_heights) ?;
573+ let proofs = self . inner . batch_transaction_get_merkle ( to_fetch. iter ( ) ) ?;
558574
559575 // Validate each proof, retrying once for each stale header.
560- for ( ( txid, height, hash ) , proof) in to_fetch. into_iter ( ) . zip ( proofs. into_iter ( ) ) {
576+ for ( ( txid, height) , proof) in to_fetch. into_iter ( ) . zip ( proofs. into_iter ( ) ) {
561577 let mut header = {
562578 let cache = self . block_header_cache . lock ( ) . unwrap ( ) ;
563579 cache
@@ -582,6 +598,7 @@ impl<E: ElectrumApi> BdkElectrumClient<E> {
582598
583599 // Build and cache the anchor if merkle proof is valid.
584600 if valid {
601+ let hash = header. block_hash ( ) ;
585602 let anchor = ConfirmationBlockTime {
586603 confirmation_time : header. time as u64 ,
587604 block_id : BlockId {
@@ -725,11 +742,13 @@ fn chain_update(
725742#[ cfg( test) ]
726743#[ cfg_attr( coverage_nightly, coverage( off) ) ]
727744mod test {
728- use crate :: { bdk_electrum_client:: TxUpdate , BdkElectrumClient } ;
745+ use crate :: { bdk_electrum_client:: TxUpdate , electrum_client:: ElectrumApi , BdkElectrumClient } ;
746+ use bdk_chain:: bitcoin:: Amount ;
729747 use bdk_chain:: bitcoin:: { constants, Network , OutPoint , ScriptBuf , Transaction , TxIn } ;
730748 use bdk_chain:: { BlockId , CheckPoint } ;
731749 use bdk_core:: { collections:: BTreeMap , spk_client:: SyncRequest } ;
732- use bdk_testenv:: { anyhow, utils:: new_tx, TestEnv } ;
750+ use bdk_testenv:: { anyhow, bitcoincore_rpc:: RpcApi , utils:: new_tx, TestEnv } ;
751+ use core:: time:: Duration ;
733752 use electrum_client:: Error as ElectrumError ;
734753 use std:: sync:: Arc ;
735754
@@ -793,4 +812,66 @@ mod test {
793812
794813 Ok ( ( ) )
795814 }
815+
816+ /// This test checks that when a transaction is reorged into a different block
817+ /// at the same height, `batch_fetch_anchors()` updates its anchor correctly:
818+ ///
819+ /// 1. A transaction is confirmed in a block, and that block header is cached.
820+ /// 2. A reorg happens, replacing that block with a new one at the same height.
821+ /// 3. When we call `batch_fetch_anchors()`, it should fetch the new block header and recreate
822+ /// the transaction’s anchor using the new block hash.
823+ ///
824+ /// Reorgs should cause the anchor to point to the new block instead of the stale one.
825+ #[ cfg( feature = "default" ) ]
826+ #[ test]
827+ fn test_batch_fetch_anchors_reorg_uses_new_hash ( ) -> anyhow:: Result < ( ) > {
828+ let env = TestEnv :: new ( ) ?;
829+ let client = electrum_client:: Client :: new ( env. electrsd . electrum_url . as_str ( ) ) . unwrap ( ) ;
830+ let electrum_client = BdkElectrumClient :: new ( client) ;
831+
832+ env. mine_blocks ( 101 , None ) ?;
833+
834+ let addr = env
835+ . rpc_client ( )
836+ . get_new_address ( None , None ) ?
837+ . assume_checked ( ) ;
838+ let txid = env. send ( & addr, Amount :: from_sat ( 50_000 ) ) ?;
839+
840+ // Mine block that confirms transaction.
841+ env. mine_blocks ( 1 , None ) ?;
842+ env. wait_until_electrum_sees_block ( Duration :: from_secs ( 6 ) ) ?;
843+ let height: u32 = env. rpc_client ( ) . get_block_count ( ) ? as u32 ;
844+
845+ // Add the pre-reorg block that the tx is confirmed in to the header cache.
846+ let header = electrum_client. inner . block_header ( height as usize ) ?;
847+ {
848+ electrum_client
849+ . block_header_cache
850+ . lock ( )
851+ . unwrap ( )
852+ . insert ( height, header) ;
853+ }
854+
855+ // Reorg to create a new header and hash.
856+ env. reorg ( 1 ) ?;
857+ env. wait_until_electrum_sees_block ( Duration :: from_secs ( 6 ) ) ?;
858+
859+ // Calling `batch_fetch_anchors` should fetch new header, replacing the pre-reorg header.
860+ let anchors = electrum_client. batch_fetch_anchors ( & [ ( txid, height as usize ) ] ) ?;
861+ assert_eq ! ( anchors. len( ) , 1 ) ;
862+
863+ let new_header = electrum_client. inner . block_header ( height as usize ) ?;
864+ let new_hash = new_header. block_hash ( ) ;
865+
866+ // Anchor should contain new hash.
867+ let ( _, anchor) = anchors[ 0 ] ;
868+ assert_eq ! ( anchor. block_id. height, height) ;
869+ assert_eq ! ( anchor. block_id. hash, new_hash) ;
870+
871+ // Anchor cache should also contain new hash.
872+ let cache = electrum_client. anchor_cache . lock ( ) . unwrap ( ) ;
873+ assert ! ( cache. get( & ( txid, new_hash) ) . is_some( ) ) ;
874+
875+ Ok ( ( ) )
876+ }
796877}
0 commit comments