Skip to content

Commit 86defb3

Browse files
committed
Merge branch 'master' into kwsantiago/1973-simplify-examples
2 parents 097fc2f + 333cc2a commit 86defb3

File tree

2 files changed

+90
-8
lines changed

2 files changed

+90
-8
lines changed

ci/pin-msrv.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,4 @@ cargo update -p tracing-core --precise "0.1.33"
3131
cargo update -p [email protected] --precise "1.0.1"
3232
cargo update -p rayon --precise "1.10.0"
3333
cargo update -p rayon-core --precise "1.12.1"
34+
cargo update -p [email protected] --precise "0.5.10"

crates/electrum/src/bdk_electrum_client.rs

Lines changed: 89 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use bdk_core::{
77
BlockId, CheckPoint, ConfirmationBlockTime, TxUpdate,
88
};
99
use electrum_client::{ElectrumApi, Error, HeaderNotification};
10+
use std::convert::TryInto;
1011
use 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))]
727744
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;
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

Comments
 (0)