Skip to content

Commit 5ddf270

Browse files
committed
Wait for replacement blocks after reorgs
The block helper only compared heights. Replacing invalidated blocks with the same number of new blocks leaves the height unchanged, so it could return while Electrum still exposed the old chain. Wallet syncs then observed stale state and made reorg assertions timing-dependent. Require Electrum's target-height hash to match bitcoind's replacement block before returning. Co-Authored-By: HAL 9000
1 parent 0bb047f commit 5ddf270

2 files changed

Lines changed: 21 additions & 23 deletions

File tree

tests/common/mod.rs

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -753,7 +753,7 @@ pub(crate) async fn generate_blocks_and_wait<E: ElectrumApi>(
753753
let address = bitcoind.new_address().expect("failed to get new address");
754754
// TODO: expect this Result once the WouldBlock issue is resolved upstream.
755755
let _block_hashes_res = bitcoind.generate_to_address(num, &address);
756-
wait_for_block(electrs, cur_height as usize + num).await;
756+
wait_for_block(bitcoind, electrs, cur_height as usize + num).await;
757757
print!(" Done!");
758758
println!("\n");
759759
}
@@ -773,27 +773,25 @@ pub(crate) fn invalidate_blocks(bitcoind: &BitcoindClient, num_blocks: usize) {
773773
assert!(new_cur_height + num_blocks == cur_height);
774774
}
775775

776-
pub(crate) async fn wait_for_block<E: ElectrumApi>(electrs: &E, min_height: usize) {
777-
let mut header = match electrs.block_headers_subscribe() {
778-
Ok(header) => header,
779-
Err(_) => {
780-
// While subscribing should succeed the first time around, we ran into some cases where
781-
// it didn't. Since we can't proceed without subscribing, we try again after a delay
782-
// and panic if it still fails.
783-
tokio::time::sleep(Duration::from_secs(3)).await;
784-
electrs.block_headers_subscribe().expect("failed to subscribe to block headers")
785-
},
786-
};
787-
loop {
788-
if header.height >= min_height {
789-
break;
776+
pub(crate) async fn wait_for_block<E: ElectrumApi>(
777+
bitcoind: &BitcoindClient, electrs: &E, min_height: usize,
778+
) {
779+
let expected_block_hash = exponential_backoff_poll(|| {
780+
let bitcoind_height =
781+
bitcoind.get_blockchain_info().expect("failed to get blockchain info").blocks as usize;
782+
if bitcoind_height < min_height {
783+
return None;
790784
}
791-
header = exponential_backoff_poll(|| {
792-
electrs.ping().expect("failed to ping electrs");
793-
electrs.block_headers_pop().expect("failed to pop block header")
794-
})
795-
.await;
796-
}
785+
bitcoind.get_block_hash(min_height as u64).ok()?.block_hash().ok()
786+
})
787+
.await;
788+
// A height-only wait can return the old header during a same-height reorg. Require the
789+
// replacement hash so callers cannot sync against the stale chain by mistake.
790+
exponential_backoff_poll(|| {
791+
let header = electrs.block_header(min_height).ok()?;
792+
(header.block_hash() == expected_block_hash).then_some(())
793+
})
794+
.await;
797795
}
798796

799797
pub(crate) async fn wait_for_tx<E: ElectrumApi>(electrs: &E, txid: Txid) {

tests/integration_tests_rust.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -807,7 +807,7 @@ async fn reorged_onchain_payment_returns_to_unconfirmed() {
807807
.call("generateblock", &[json!(replacement_address.to_string()), json!([])])
808808
.expect("failed to generate empty block");
809809
}
810-
wait_for_block(&electrsd.client, original_height as usize + 1).await;
810+
wait_for_block(&bitcoind.client, &electrsd.client, original_height as usize + 1).await;
811811

812812
node_a.sync_wallets().unwrap();
813813
node_b.sync_wallets().unwrap();
@@ -2056,7 +2056,7 @@ async fn splice_payment_reorged_to_unconfirmed() {
20562056
.call("generateblock", &[json!(replacement_address.to_string()), json!([])])
20572057
.expect("failed to generate empty block");
20582058
}
2059-
wait_for_block(&electrsd.client, original_height as usize + 1).await;
2059+
wait_for_block(&bitcoind.client, &electrsd.client, original_height as usize + 1).await;
20602060
node_b.sync_wallets().unwrap();
20612061

20622062
// The funding payment returns to `Unconfirmed` and stays `Pending`, exercising the

0 commit comments

Comments
 (0)