@@ -7,6 +7,7 @@ use bdk_core::{
7
7
BlockId , CheckPoint , ConfirmationBlockTime , TxUpdate ,
8
8
} ;
9
9
use electrum_client:: { ElectrumApi , Error , HeaderNotification } ;
10
+ use std:: convert:: TryInto ;
10
11
use std:: sync:: { Arc , Mutex } ;
11
12
12
13
/// We include a chain suffix of a certain length for the purpose of robustness.
@@ -46,8 +47,24 @@ impl<E: ElectrumApi> BdkElectrumClient<E> {
46
47
}
47
48
}
48
49
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`.
51
68
pub fn populate_tx_cache ( & self , txs : impl IntoIterator < Item = impl Into < Arc < Transaction > > > ) {
52
69
let mut tx_cache = self . tx_cache . lock ( ) . unwrap ( ) ;
53
70
for tx in txs {
@@ -547,17 +564,16 @@ impl<E: ElectrumApi> BdkElectrumClient<E> {
547
564
if let Some ( anchor) = anchor_cache. get ( & ( txid, hash) ) {
548
565
results. push ( ( txid, * anchor) ) ;
549
566
} else {
550
- to_fetch. push ( ( txid, height, hash ) ) ;
567
+ to_fetch. push ( ( txid, height) ) ;
551
568
}
552
569
}
553
570
}
554
571
555
572
// 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 ( ) ) ?;
558
574
559
575
// 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 ( ) ) {
561
577
let mut header = {
562
578
let cache = self . block_header_cache . lock ( ) . unwrap ( ) ;
563
579
cache
@@ -582,6 +598,7 @@ impl<E: ElectrumApi> BdkElectrumClient<E> {
582
598
583
599
// Build and cache the anchor if merkle proof is valid.
584
600
if valid {
601
+ let hash = header. block_hash ( ) ;
585
602
let anchor = ConfirmationBlockTime {
586
603
confirmation_time : header. time as u64 ,
587
604
block_id : BlockId {
@@ -725,11 +742,13 @@ fn chain_update(
725
742
#[ cfg( test) ]
726
743
#[ cfg_attr( coverage_nightly, coverage( off) ) ]
727
744
mod 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 ;
729
747
use bdk_chain:: bitcoin:: { constants, Network , OutPoint , ScriptBuf , Transaction , TxIn } ;
730
748
use bdk_chain:: { BlockId , CheckPoint } ;
731
749
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 ;
733
752
use electrum_client:: Error as ElectrumError ;
734
753
use std:: sync:: Arc ;
735
754
@@ -793,4 +812,66 @@ mod test {
793
812
794
813
Ok ( ( ) )
795
814
}
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
+ }
796
877
}
0 commit comments