Skip to content

Commit 5499225

Browse files
committed
fix(electrum): fix stale anchor hash on reorg
1 parent 6292eba commit 5499225

File tree

1 file changed

+63
-6
lines changed

1 file changed

+63
-6
lines changed

crates/electrum/src/bdk_electrum_client.rs

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -501,17 +501,16 @@ impl<E: ElectrumApi> BdkElectrumClient<E> {
501501
if let Some(anchor) = anchor_cache.get(&(txid, hash)) {
502502
results.push((txid, *anchor));
503503
} else {
504-
to_fetch.push((txid, height, hash));
504+
to_fetch.push((txid, height));
505505
}
506506
}
507507
}
508508

509509
// Fetch merkle proofs.
510-
let txids_and_heights = to_fetch.iter().map(|&(txid, height, _)| (txid, height));
511-
let proofs = self.inner.batch_transaction_get_merkle(txids_and_heights)?;
510+
let proofs = self.inner.batch_transaction_get_merkle(to_fetch.iter())?;
512511

513512
// Validate each proof, retrying once for each stale header.
514-
for ((txid, height, hash), proof) in to_fetch.into_iter().zip(proofs.into_iter()) {
513+
for ((txid, height), proof) in to_fetch.into_iter().zip(proofs.into_iter()) {
515514
let mut header = {
516515
let cache = self.block_header_cache.lock().unwrap();
517516
cache
@@ -536,6 +535,7 @@ impl<E: ElectrumApi> BdkElectrumClient<E> {
536535

537536
// Build and cache the anchor if merkle proof is valid.
538537
if valid {
538+
let hash = header.block_hash();
539539
let anchor = ConfirmationBlockTime {
540540
confirmation_time: header.time as u64,
541541
block_id: BlockId {
@@ -679,11 +679,13 @@ fn chain_update(
679679
#[cfg(test)]
680680
#[cfg_attr(coverage_nightly, coverage(off))]
681681
mod test {
682-
use crate::{bdk_electrum_client::TxUpdate, BdkElectrumClient};
682+
use crate::{bdk_electrum_client::TxUpdate, electrum_client::ElectrumApi, BdkElectrumClient};
683+
use bdk_chain::bitcoin::Amount;
683684
use bdk_chain::bitcoin::{constants, Network, OutPoint, ScriptBuf, Transaction, TxIn};
684685
use bdk_chain::{BlockId, CheckPoint};
685686
use bdk_core::{collections::BTreeMap, spk_client::SyncRequest};
686-
use bdk_testenv::{anyhow, utils::new_tx, TestEnv};
687+
use bdk_testenv::{anyhow, bitcoincore_rpc::RpcApi, utils::new_tx, TestEnv};
688+
use core::time::Duration;
687689
use electrum_client::Error as ElectrumError;
688690
use std::sync::Arc;
689691

@@ -747,4 +749,59 @@ mod test {
747749

748750
Ok(())
749751
}
752+
753+
#[cfg(feature = "default")]
754+
#[test]
755+
fn test_batch_fetch_anchors_reorg_uses_new_hash() -> anyhow::Result<()> {
756+
let env = TestEnv::new()?;
757+
let client = electrum_client::Client::new(env.electrsd.electrum_url.as_str()).unwrap();
758+
let electrum_client = BdkElectrumClient::new(client);
759+
760+
env.mine_blocks(101, None)?;
761+
env.wait_until_electrum_sees_block(Duration::from_secs(6))?;
762+
763+
let addr = env
764+
.rpc_client()
765+
.get_new_address(None, None)?
766+
.assume_checked();
767+
let txid = env.send(&addr, Amount::from_sat(50_000))?;
768+
env.wait_until_electrum_sees_txid(txid, Duration::from_secs(6))?;
769+
770+
// Mine block that confirms transaction.
771+
env.mine_blocks(1, None)?;
772+
env.wait_until_electrum_sees_block(Duration::from_secs(6))?;
773+
let height: u32 = env.rpc_client().get_block_count()? as u32;
774+
775+
// Populate header cache.
776+
let header = electrum_client.inner.block_header(height as usize)?;
777+
{
778+
electrum_client
779+
.block_header_cache
780+
.lock()
781+
.unwrap()
782+
.insert(height, header);
783+
}
784+
785+
// Reorg to create a new header and hash.
786+
env.reorg(1)?;
787+
env.wait_until_electrum_sees_block(Duration::from_secs(6))?;
788+
789+
// Calling `batch_fetch_anchors` should fetch new header.
790+
let anchors = electrum_client.batch_fetch_anchors(&[(txid, height as usize)])?;
791+
assert_eq!(anchors.len(), 1);
792+
793+
let new_header = electrum_client.inner.block_header(height as usize)?;
794+
let new_hash = new_header.block_hash();
795+
796+
// Anchor should contain new hash.
797+
let (_, anchor) = anchors[0];
798+
assert_eq!(anchor.block_id.height, height);
799+
assert_eq!(anchor.block_id.hash, new_hash);
800+
801+
// Anchor cache should also contain new hash.
802+
let cache = electrum_client.anchor_cache.lock().unwrap();
803+
assert!(cache.get(&(txid, new_hash)).is_some());
804+
805+
Ok(())
806+
}
750807
}

0 commit comments

Comments
 (0)