From: Vihiga Tyonum Date: Wed, 28 Oct 2026 14:59:46 +0000 (+0100) Subject: fix(tests): wait for electrs to reach bitcoind's tip in online tests X-Git-Url: http://internal-gitweb-vhost/-script/src/example_cli/scripts/struct.CommandString.html?a=commitdiff_plain;h=d2317d2a6f549d22e312d73a120a643eaf69a2af;p=bdk-cli fix(tests): wait for electrs to reach bitcoind's tip in online tests 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. --- diff --git a/tests/common/mod.rs b/tests/common/mod.rs index d38edf8..6832633 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -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:?}" + )) +} diff --git a/tests/integration/online.rs b/tests/integration/online.rs index 4d3f11a..dc2399f 100644 --- a/tests/integration/online.rs +++ b/tests/integration/online.rs @@ -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"])