]> Untitled Git - bdk-cli/commitdiff
fix(tests): wait for electrs to reach bitcoind's tip in online tests
authorVihiga Tyonum <withtvpeter@gmail.com>
Wed, 28 Oct 2026 14:59:46 +0000 (15:59 +0100)
committerVihiga Tyonum <withtvpeter@gmail.com>
Wed, 28 Oct 2026 14:59:46 +0000 (15:59 +0100)
bdk_testenv 0.13.1's `wait_until_electrum_sees_block` returns on the
first queued header notification, which can be a stale one left over
from an earlier `mine_blocks` call. A following `full_scan` could then
run before electrs had indexed the confirmation blocks, intermittently
failing tests such as `test_create_tx_multiple_recipients` with a
confirmed balance of 0.

Add a `wait_for_electrum_tip` helper to `tests/common` that reads
bitcoind's current block height and drains header notifications until
electrs reports that height, and use it in place of all calls to
`wait_until_electrum_sees_block` in the electrum and esplora online
tests.

The same bug is fixed upstream in bdk (a98cf7c4) but not yet released;
the helper can be dropped once a new bdk_testenv is published.

tests/common/mod.rs
tests/integration/online.rs

index d38edf8b4a1d24ced7908726fdc72c708ee77d65..6832633b12981a50a34924d85f30eb39dd0c673d 100644 (file)
@@ -11,7 +11,9 @@
 //! This module provides the necessary helper methods for tests
 //!
 use assert_cmd::Command;
+use bdk_testenv::{TestEnv, anyhow, bitcoincore_rpc::RpcApi, electrum_client::ElectrumApi};
 use std::path::PathBuf;
+use std::time::{Duration, Instant};
 
 /// The bdk-cli command struct
 #[allow(dead_code)]
@@ -84,3 +86,31 @@ impl BdkCli {
         cmd
     }
 }
+
+/// Waits until electrs has indexed up to bitcoind's current tip.
+///
+/// `TestEnv::wait_until_electrum_sees_block` returns on the first queued header
+/// notification, which may be a stale one left over from an earlier `mine_blocks`
+/// call, so a following scan can run before the latest blocks are indexed.
+#[allow(unused)]
+pub fn wait_for_electrum_tip(env: &TestEnv, timeout: Duration) -> anyhow::Result<()> {
+    let tip = env.rpc_client().get_block_count()? as usize;
+    let client = env.electrum_client();
+    client.block_headers_subscribe()?;
+    let start = Instant::now();
+
+    while start.elapsed() < timeout {
+        env.electrsd.trigger()?;
+        client.ping()?;
+        while let Some(notif) = client.block_headers_pop()? {
+            if notif.height >= tip {
+                return Ok(());
+            }
+        }
+        std::thread::sleep(Duration::from_millis(200));
+    }
+
+    Err(anyhow::anyhow!(
+        "electrs did not reach tip {tip} within {timeout:?}"
+    ))
+}
index 4d3f11afd44b75200b1ae75b3a5e1ac6326feabb..dc2399f2ab548d28012a501252320e1e2c06c39b 100644 (file)
@@ -1,6 +1,6 @@
 #[cfg(feature = "electrum")]
 mod test_online {
-    use crate::common::BdkCli;
+    use crate::common::{BdkCli, wait_for_electrum_tip};
     use assert_cmd::Command;
     use bdk_testenv::{TestEnv, bitcoincore_rpc::RpcApi};
     use bdk_wallet::bitcoin::{Address, Amount, Network, Txid};
@@ -60,7 +60,7 @@ mod test_online {
             .mine_blocks(101, Some(node_address))
             .expect("Failed to mine initial blocks");
         assert_eq!(mined_blocks.len(), 101, "expected exactly 101 blocks mined");
-        env.wait_until_electrum_sees_block(Duration::from_secs(10))
+        wait_for_electrum_tip(env, Duration::from_secs(10))
             .expect("Electrum did not catch up to initial blocks");
 
         let txid = env
@@ -71,7 +71,7 @@ mod test_online {
 
         env.mine_blocks(3, None)
             .expect("Failed to confirm funding tx");
-        env.wait_until_electrum_sees_block(Duration::from_secs(10))
+        wait_for_electrum_tip(env, Duration::from_secs(10))
             .expect("Electrum did not catch up to confirmation blocks");
 
         cli_full_scan(cli);
@@ -263,7 +263,7 @@ mod test_online {
 
         env.mine_blocks(1, None)
             .expect("Failed to mine drain confirmation block");
-        env.wait_until_electrum_sees_block(Duration::from_secs(10))
+        wait_for_electrum_tip(&env, Duration::from_secs(10))
             .expect("Electrum did not catch up to drain confirmation");
 
         cli_sync(&cli);
@@ -297,7 +297,7 @@ mod test_online {
         // Confirm the spend and verify the final balance
         env.mine_blocks(1, None)
             .expect("Failed to mine spend confirmation block");
-        env.wait_until_electrum_sees_block(Duration::from_secs(10))
+        wait_for_electrum_tip(&env, Duration::from_secs(10))
             .expect("Electrum did not catch up to spend confirmation");
 
         cli_sync(&cli);
@@ -446,7 +446,7 @@ mod test_online {
 
         env.mine_blocks(1, None)
             .expect("Failed to confirm bumped tx");
-        env.wait_until_electrum_sees_block(Duration::from_secs(10))
+        wait_for_electrum_tip(&env, Duration::from_secs(10))
             .expect("Electrum did not catch up after bump confirmation");
         cli_sync(&cli);
 
@@ -566,8 +566,7 @@ mod test_online {
             .unwrap()
             .assume_checked();
         env.mine_blocks(101, Some(node_addr)).unwrap();
-        env.wait_until_electrum_sees_block(Duration::from_secs(10))
-            .unwrap();
+        wait_for_electrum_tip(&env, Duration::from_secs(10)).unwrap();
         let a1 = cli_new_address(&cli);
         let t1 = env.send(&a1, Amount::from_btc(0.3).unwrap()).unwrap();
         env.wait_until_electrum_sees_txid(t1, Duration::from_secs(10))
@@ -577,8 +576,7 @@ mod test_online {
         env.wait_until_electrum_sees_txid(t2, Duration::from_secs(10))
             .unwrap();
         env.mine_blocks(3, None).unwrap();
-        env.wait_until_electrum_sees_block(Duration::from_secs(10))
-            .unwrap();
+        wait_for_electrum_tip(&env, Duration::from_secs(10)).unwrap();
         cli_full_scan(&cli);
 
         let unspent = run_wallet_json(&cli, &["unspent"]);
@@ -779,14 +777,12 @@ mod test_online {
                 .unwrap()
                 .assume_checked();
             env.mine_blocks(101, Some(node_address)).unwrap();
-            env.wait_until_electrum_sees_block(Duration::from_secs(10))
-                .unwrap();
+            wait_for_electrum_tip(&env, Duration::from_secs(10)).unwrap();
             let txid = env.send(&addr, Amount::from_btc(0.5).unwrap()).unwrap();
             env.wait_until_electrum_sees_txid(txid, Duration::from_secs(10))
                 .unwrap();
             env.mine_blocks(3, None).unwrap();
-            env.wait_until_electrum_sees_block(Duration::from_secs(10))
-                .unwrap();
+            wait_for_electrum_tip(&env, Duration::from_secs(10)).unwrap();
             cli.wallet_cmd(&["--wallet", WALLET, "full_scan"])
                 .assert()
                 .success();
@@ -874,14 +870,12 @@ mod test_online {
             .unwrap()
             .assume_checked();
         env.mine_blocks(101, Some(node_addr)).unwrap();
-        env.wait_until_electrum_sees_block(Duration::from_secs(10))
-            .unwrap();
+        wait_for_electrum_tip(&env, Duration::from_secs(10)).unwrap();
         let txid = env.send(&address, Amount::from_sat(5_000)).unwrap();
         env.wait_until_electrum_sees_txid(txid, Duration::from_secs(10))
             .unwrap();
         env.mine_blocks(3, None).unwrap();
-        env.wait_until_electrum_sees_block(Duration::from_secs(10))
-            .unwrap();
+        wait_for_electrum_tip(&env, Duration::from_secs(10)).unwrap();
         cli_full_scan(&cli);
 
         // The funded outpoint to prove control of.
@@ -1072,7 +1066,7 @@ mod test_online {
 
 #[cfg(feature = "esplora")]
 mod test_esplora {
-    use crate::common::BdkCli;
+    use crate::common::{BdkCli, wait_for_electrum_tip};
     use bdk_testenv::{TestEnv, bitcoincore_rpc::RpcApi};
     use bdk_wallet::bitcoin::{Address, Amount, Network};
     use serde_json::Value;
@@ -1132,14 +1126,12 @@ mod test_esplora {
             .unwrap()
             .assume_checked();
         env.mine_blocks(101, Some(node_addr)).unwrap();
-        env.wait_until_electrum_sees_block(Duration::from_secs(10))
-            .unwrap();
+        wait_for_electrum_tip(&env, Duration::from_secs(10)).unwrap();
         let txid = env.send(&address, Amount::from_btc(0.5).unwrap()).unwrap();
         env.wait_until_electrum_sees_txid(txid, Duration::from_secs(10))
             .unwrap();
         env.mine_blocks(3, None).unwrap();
-        env.wait_until_electrum_sees_block(Duration::from_secs(10))
-            .unwrap();
+        wait_for_electrum_tip(&env, Duration::from_secs(10)).unwrap();
 
         let scan = cli
             .wallet_cmd(&["--wallet", WALLET_NAME, "full_scan"])