Skip to content

Fix flaky reorg tests#972

Open
tnull wants to merge 4 commits into
lightningdevkit:mainfrom
tnull:2026-07-fix-flaky-reorg-tests
Open

Fix flaky reorg tests#972
tnull wants to merge 4 commits into
lightningdevkit:mainfrom
tnull:2026-07-fix-flaky-reorg-tests

Conversation

@tnull

@tnull tnull commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Previously, the reorg tests could be somewhat flaky. Here we attempt to stabilize them.

tnull added 4 commits July 8, 2026 08:51
Use available listener ports and tolerate intermediate force-close

balances so the property test exercises reorg behavior instead of

setup timing.

Co-Authored-By: HAL 9000
Wait for the replacement chain to confirm close and sweep outputs

before checking final balances after the reorg.

Co-Authored-By: HAL 9000
Stop all reorg test nodes on helper threads before leaving each

property case so shutdown does not park the Tokio worker.

Co-Authored-By: HAL 9000
Wait for bitcoind to reach the generated height before polling

electrs for the corresponding block header.

Co-Authored-By: HAL 9000
@tnull tnull requested a review from joostjager July 8, 2026 07:37
@ldk-reviews-bot

ldk-reviews-bot commented Jul 8, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @joostjager as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@joostjager

Copy link
Copy Markdown
Contributor

Here we attempt to stabilize them

Were you able to reliably repro the flakes and see that this PR fixed them?

Comment thread tests/common/mod.rs

pub(crate) fn generate_listening_addresses() -> Vec<SocketAddress> {
let port = NEXT_PORT.fetch_add(2, Ordering::Relaxed);
let listener_a = TcpListener::bind(("127.0.0.1", 0)).expect("available listener port");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR doesn't give more info on the nature of the flakes. Was it port collisions again?

Comment thread tests/common/mod.rs
let listener_a = TcpListener::bind(("127.0.0.1", 0)).expect("available listener port");
let listener_b = TcpListener::bind(("127.0.0.1", 0)).expect("available listener port");
let port_a = listener_a.local_addr().expect("listener address").port();
let port_b = listener_b.local_addr().expect("listener address").port();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem here is also again that the listener is dropped before the node binds, leaving a window where it can be re-allocated in another thread.

Comment thread tests/reorg_test.rs
} => {
let cur_height = node.status().current_best_block.height;
let blocks_to_go = confirmation_height - cur_height;
if force_close {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This commit seems to combine two unrelated fixes.

Comment thread tests/reorg_test.rs
};
found_claimable_balance = true;

let cur_height = node.status().current_best_block.height;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems quite a few things are changing in this diff hunk. Is it really minimal without unnecessary restructuring?

Comment thread tests/reorg_test.rs
assert_eq!(node.next_event(), None);
});

stop_nodes_concurrently(nodes).await;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] tests/reorg_test.rs:240 only stops nodes on the success path. All assertions and waits above it can still panic or time out, and then nodes is dropped during unwinding before stop_nodes_concurrently(nodes).await runs. Since Node::drop calls stop(), failing proptest cases can still hit the exact shutdown-on-runtime path this commit is trying to avoid. That matters especially for proptest, because failures need to unwind cleanly for shrinking and diagnostics.

Comment thread tests/common/mod.rs
.into_iter()
.map(|node| {
let (stop_sender, stop_receiver) = tokio::sync::oneshot::channel();
std::thread::spawn(move || {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add comment why we need to do it concurrently? And how it would otherwise block the tokio worker?

Comment thread tests/common/mod.rs
loop {
if header.height >= min_height {
break;
if electrs.block_header(min_height).is_ok() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] tests/common/mod.rs:709-714 makes wait_for_block return on a stale reorged-out header. The two remaining callers use it after invalidate_blocks and manual generateblock calls, for example tests/integration_tests_rust.rs:746-756 and tests/integration_tests_rust.rs:1995-2005. At original_height + 1, electrs may already have the old block header from before the invalidation, so block_header(min_height).is_ok() can return immediately before electrs has switched to the replacement chain. Then the following wallet sync can still observe the pre-reorg chain, preserving the flake this helper is supposed to avoid. These callers need to wait for the replacement block hash, not just any header at that height.

Comment thread tests/common/mod.rs
assert!(tries < 120, "electrs did not serve block header {} within 60 seconds", min_height);
tries += 1;
tokio::time::sleep(delay).await;
if delay.as_millis() < 512 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not the exponential backoff anymore?

Comment thread tests/common/mod.rs
electrs.block_headers_subscribe().expect("failed to subscribe to block headers")
},
};
let mut delay = Duration::from_millis(64);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason this method is touched?

Comment thread tests/common/mod.rs

async fn wait_for_bitcoind_block_in_electrs<E: ElectrumApi>(
bitcoind: &BitcoindClient, electrs: &E, min_height: usize,
mut expected_block_hash: Option<BlockHash>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

min_height and expected_block_hash and their interaction might benefit from a comment explaining how to use it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants