@@ -26,7 +26,7 @@ pub struct BdkElectrumClient<E> {
26
26
/// The header cache
27
27
block_header_cache : Mutex < HashMap < u32 , Header > > ,
28
28
/// The Merkle proof cache
29
- merkle_cache : Mutex < HashMap < ( Txid , u32 ) , GetMerkleRes > > ,
29
+ merkle_cache : Mutex < HashMap < ( Txid , BlockHash ) , GetMerkleRes > > ,
30
30
}
31
31
32
32
impl < E : ElectrumApi > BdkElectrumClient < E > {
@@ -315,7 +315,7 @@ impl<E: ElectrumApi> BdkElectrumClient<E> {
315
315
}
316
316
}
317
317
318
- // Batch validate all collected transactions
318
+ // Batch validate all collected transactions.
319
319
if !txs_to_validate. is_empty ( ) {
320
320
let proofs = self . batch_fetch_merkle_proofs ( & txs_to_validate) ?;
321
321
self . batch_validate_merkle_proofs ( tx_update, proofs) ?;
@@ -345,7 +345,7 @@ impl<E: ElectrumApi> BdkElectrumClient<E> {
345
345
} ;
346
346
debug_assert_eq ! ( op_tx. compute_txid( ) , op_txid) ;
347
347
348
- // attempt to find the following transactions (alongside their chain positions), and
348
+ // Attempt to find the following transactions (alongside their chain positions), and
349
349
// add to our sparsechain `update`:
350
350
let mut has_residing = false ; // tx in which the outpoint resides
351
351
let mut has_spending = false ; // tx that spends the outpoint
@@ -370,7 +370,7 @@ impl<E: ElectrumApi> BdkElectrumClient<E> {
370
370
371
371
if !has_spending && res. tx_hash != op_txid {
372
372
let res_tx = self . fetch_tx ( res. tx_hash ) ?;
373
- // we exclude txs/anchors that do not spend our specified outpoint(s)
373
+ // We exclude txs/anchors that do not spend our specified outpoint(s).
374
374
has_spending = res_tx
375
375
. input
376
376
. iter ( )
@@ -392,7 +392,7 @@ impl<E: ElectrumApi> BdkElectrumClient<E> {
392
392
}
393
393
}
394
394
395
- // Batch validate all collected transactions
395
+ // Batch validate all collected transactions.
396
396
if !txs_to_validate. is_empty ( ) {
397
397
let proofs = self . batch_fetch_merkle_proofs ( & txs_to_validate) ?;
398
398
self . batch_validate_merkle_proofs ( tx_update, proofs) ?;
@@ -423,8 +423,8 @@ impl<E: ElectrumApi> BdkElectrumClient<E> {
423
423
. map ( |txo| & txo. script_pubkey )
424
424
. expect ( "tx must have an output" ) ;
425
425
426
- // because of restrictions of the Electrum API, we have to use the `script_get_history`
427
- // call to get confirmation status of our transaction
426
+ // Because of restrictions of the Electrum API, we have to use the `script_get_history`
427
+ // call to get confirmation status of our transaction.
428
428
if let Some ( r) = self
429
429
. inner
430
430
. script_get_history ( spk) ?
@@ -445,7 +445,7 @@ impl<E: ElectrumApi> BdkElectrumClient<E> {
445
445
tx_update. txs . push ( tx) ;
446
446
}
447
447
448
- // Batch validate all collected transactions
448
+ // Batch validate all collected transactions.
449
449
if !txs_to_validate. is_empty ( ) {
450
450
let proofs = self . batch_fetch_merkle_proofs ( & txs_to_validate) ?;
451
451
self . batch_validate_merkle_proofs ( tx_update, proofs) ?;
@@ -454,47 +454,65 @@ impl<E: ElectrumApi> BdkElectrumClient<E> {
454
454
Ok ( ( ) )
455
455
}
456
456
457
- /// Batch fetch Merkle proofs for multiple transactions
457
+ /// Batch fetch Merkle proofs for multiple transactions.
458
458
fn batch_fetch_merkle_proofs (
459
459
& self ,
460
460
txs_with_heights : & [ ( Txid , usize ) ] ,
461
461
) -> Result < Vec < ( Txid , GetMerkleRes ) > , Error > {
462
+ // Evict proofs whose block hash is no longer on our current chain.
463
+ self . clear_stale_proofs ( ) ?;
464
+
462
465
let mut results = Vec :: with_capacity ( txs_with_heights. len ( ) ) ;
463
466
let mut to_fetch = Vec :: new ( ) ;
464
467
465
- // Check cache first
468
+ // Build a map for height to block hash conversions. This is for obtaining block hash data
469
+ // with minimum `fetch_header` calls.
470
+ let mut height_to_hash: HashMap < u32 , BlockHash > = HashMap :: new ( ) ;
471
+ for & ( _, height) in txs_with_heights {
472
+ let h = height as u32 ;
473
+ if !height_to_hash. contains_key ( & h) {
474
+ // Try to obtain hash from the header cache, or fetch the header if absent.
475
+ let hash = self . fetch_header ( h) ?. block_hash ( ) ;
476
+ height_to_hash. insert ( h, hash) ;
477
+ }
478
+ }
479
+
480
+ // Check cache.
466
481
{
467
482
let merkle_cache = self . merkle_cache . lock ( ) . unwrap ( ) ;
468
483
for & ( txid, height) in txs_with_heights {
469
- if let Some ( proof) = merkle_cache. get ( & ( txid, height as u32 ) ) {
484
+ let h = height as u32 ;
485
+ let hash = height_to_hash[ & h] ;
486
+ if let Some ( proof) = merkle_cache. get ( & ( txid, hash) ) {
470
487
results. push ( ( txid, proof. clone ( ) ) ) ;
471
488
} else {
472
- to_fetch. push ( ( txid, height) ) ;
489
+ to_fetch. push ( ( txid, height, hash ) ) ;
473
490
}
474
491
}
475
492
}
476
493
477
- // Fetch missing proofs in batches
494
+ // Fetch missing proofs in batches.
478
495
for chunk in to_fetch. chunks ( MAX_MERKLE_BATCH_SIZE ) {
479
- for & ( txid, height) in chunk {
480
- if let Ok ( merkle_res) = self . inner . transaction_get_merkle ( & txid, height) {
481
- let mut cache = self . merkle_cache . lock ( ) . unwrap ( ) ;
482
- cache. insert ( ( txid, height as u32 ) , merkle_res. clone ( ) ) ;
483
- results. push ( ( txid, merkle_res) ) ;
484
- }
496
+ for & ( txid, height, hash) in chunk {
497
+ let merkle_res = self . inner . transaction_get_merkle ( & txid, height) ?;
498
+ self . merkle_cache
499
+ . lock ( )
500
+ . unwrap ( )
501
+ . insert ( ( txid, hash) , merkle_res. clone ( ) ) ;
502
+ results. push ( ( txid, merkle_res) ) ;
485
503
}
486
504
}
487
505
488
506
Ok ( results)
489
507
}
490
508
491
- /// Batch validate Merkle proofs
509
+ /// Batch validate Merkle proofs.
492
510
fn batch_validate_merkle_proofs (
493
511
& self ,
494
512
tx_update : & mut TxUpdate < ConfirmationBlockTime > ,
495
513
proofs : Vec < ( Txid , GetMerkleRes ) > ,
496
514
) -> Result < ( ) , Error > {
497
- // Pre-fetch all required headers
515
+ // Pre-fetch all required headers.
498
516
let heights: HashSet < u32 > = proofs
499
517
. iter ( )
500
518
. map ( |( _, proof) | proof. block_height as u32 )
@@ -505,7 +523,7 @@ impl<E: ElectrumApi> BdkElectrumClient<E> {
505
523
headers. insert ( height, self . fetch_header ( height) ?) ;
506
524
}
507
525
508
- // Validate proofs
526
+ // Validate proofs.
509
527
for ( txid, merkle_res) in proofs {
510
528
let height = merkle_res. block_height as u32 ;
511
529
let header = headers. get ( & height) . unwrap ( ) ;
@@ -516,7 +534,7 @@ impl<E: ElectrumApi> BdkElectrumClient<E> {
516
534
& merkle_res,
517
535
) ;
518
536
519
- // Retry with updated header if validation fails
537
+ // Retry with updated header if validation fails.
520
538
if !is_confirmed_tx {
521
539
let updated_header = self . update_header ( height) ?;
522
540
headers. insert ( height, updated_header) ;
@@ -545,15 +563,43 @@ impl<E: ElectrumApi> BdkElectrumClient<E> {
545
563
Ok ( ( ) )
546
564
}
547
565
548
- // Replace the old validate_merkle_for_anchor with optimized batch version
566
+ /// Remove any proofs for blocks that may have been re-orged out.
567
+ ///
568
+ /// Checks if the latest cached block hash matches the current chain tip. If not, evicts proofs
569
+ /// for blocks that were re-orged out, stopping at the fork point.
570
+ fn clear_stale_proofs ( & self ) -> Result < ( ) , Error > {
571
+ let mut cache = self . merkle_cache . lock ( ) . unwrap ( ) ;
572
+
573
+ // Collect one (height, old_hash) pair per proof.
574
+ let mut entries: Vec < ( u32 , BlockHash ) > = cache
575
+ . iter ( )
576
+ . map ( |( ( _, old_hash) , res) | ( res. block_height as u32 , * old_hash) )
577
+ . collect ( ) ;
578
+
579
+ // Sort descending and dedup so we only check each height once.
580
+ entries. sort_unstable_by ( |a, b| b. 0 . cmp ( & a. 0 ) ) ;
581
+ entries. dedup ( ) ;
582
+
583
+ // Evict any stale proofs until fork point is found.
584
+ for ( height, old_hash) in entries {
585
+ let current_hash = self . fetch_header ( height) ?. block_hash ( ) ;
586
+ if current_hash == old_hash {
587
+ break ;
588
+ }
589
+ cache. retain ( |& ( _txid, bh) , _| bh != old_hash) ;
590
+ }
591
+ Ok ( ( ) )
592
+ }
593
+
594
+ /// Validate the Merkle proof for a single transaction using the batch APIs.
549
595
#[ allow( dead_code) ]
550
596
fn validate_merkle_for_anchor (
551
597
& self ,
552
598
tx_update : & mut TxUpdate < ConfirmationBlockTime > ,
553
599
txid : Txid ,
554
600
confirmation_height : usize ,
555
601
) -> Result < ( ) , Error > {
556
- // Use the batch processing functions even for single tx
602
+ // Use the batch processing functions even for single tx.
557
603
let proofs = self . batch_fetch_merkle_proofs ( & [ ( txid, confirmation_height) ] ) ?;
558
604
self . batch_validate_merkle_proofs ( tx_update, proofs)
559
605
}
0 commit comments