Skip to content

Fix flaky reorg tests - #972

Merged
tnull merged 4 commits into
lightningdevkit:mainfrom
tnull:2026-07-fix-flaky-reorg-tests
Jul 15, 2026
Merged

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

Conversation

@tnull

@tnulltnull commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

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

@tnull
tnull requested a review from joostjagerJuly 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 threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/reorg_test.rs Outdated
Comment threadtests/reorg_test.rs Outdated
Comment threadtests/reorg_test.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
The test gave funding six confirmations while allowing six-block
reorgs. That drops regular channel funding to zero confirmations and
changes the scenario from a close reorg into a funding force-close.
rust-lightning PR #4231 keeps trusted zero-conf channels open after
funding is reorged out, but regular channels still force-close at zero
confirmations. Mine one extra block so the deepest reorg leaves one
confirmation.
Preserve the CI counterexample that exposed this boundary.
Co-Authored-By: HAL 9000
@tnull
tnullforce-pushed the 2026-07-fix-flaky-reorg-tests branch from f34c3ea to 5174199CompareJuly 15, 2026 11:09
@tnull

tnull commented Jul 15, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Alright, excuse the delay here. I now revisited the failure cases and narrowed the changes down to what's absolutely necessary, while improving the commit messages and comments. Also found another root cause for a flake, as we'd force-close if we entirely unconfirm a funding for a non-0conf channel.

@tnull
tnullforce-pushed the 2026-07-fix-flaky-reorg-tests branch from 5174199 to f79d235CompareJuly 15, 2026 11:26
@tnull
tnull requested a review from joostjagerJuly 15, 2026 11:27

@joostjagerjoostjager left a comment

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.

Looks good, clear improvements. Only minor questions.

Comment threadtests/reorg_test.rs
generate_blocks_and_wait(bitcoind, electrs, 6).await;
// Keep funding confirmed across the deepest reorg. rust-lightning PR #4231 exempts
// only trusted zero-conf channels; regular channels still force-close at zero confirmations.
generate_blocks_and_wait(bitcoind, electrs, 7).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.

Curious how this could have caused a flake?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

LDK currently force-closes a non-0conf channel if the funding is entirely unconfirmed due to a reorg. In the reorg test we randomize reorg height to be 1..=6, and if it happen to roll 6 in this particular instance the channel would force-close and we'd get the flake. The fix here is to just generate one more block to assert that we never completely reorg out the funding transaction here.

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.

I understand the fix, but in my opinion non-deterministic tests are something to avoid. Obviously it's a cheap way to expand coverage, but the cost of that is felt down the line.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Oh, I disagree on that sentiment. Exactly for things like reorgs you want to introduce some cases that make use of randomization (essentially chaos testing) to test cases that you'd otherwise only ever hit in production.

but the cost of that is felt down the line

I have no idea what 'the cost of that' is meant to be in this case.

Also, didn't you just expand/improve reorg and mempool coverage of LDK's fuzzer? How's that materially different from the proptests here?

@joostjagerjoostjagerJul 15, 2026

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.

Fair, I should have been clearer. I’m not against randomized/chaos testing, quite the opposite, that’s also why I expanded the reorg/mempool fuzzer coverage.

The cost I meant is mostly CI signal: before a counterexample is persisted, a deterministic boundary case can look intermittent because it only fails when proptest generates that input. The repro output helps, but there is still triage overhead.

My unease is mostly about the overlap between fuzzing and proptests, since both serve a discovery role. I’m trying to get clearer on which randomized coverage we want in regular CI versus in fuzz testing.

Comment threadtests/common/mod.rs Outdated
let _block_hashes_res = bitcoind.generate_to_address(num, &address);
wait_for_block(electrs, cur_height as usize + num).await;
let min_height = cur_height as usize + num;
wait_for_block(bitcoind, electrs, min_height).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.

Unnecessary var extraction, not consistent with calls below in the integration test?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Will revert.

Comment threadtests/common/mod.rs Outdated
let is_spent =
!electrs.script_list_unspent(&txout_script).unwrap().iter().any(|output| {
output.tx_hash == outpoint.txid && output.tx_pos == outpoint.vout as usize
});

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.

Initial poll looks like an optimization, but is it needed?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

It was an optimization that is now gone as you seem to prefer it.

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.

If we need the optimization, it doesn't need to be removed. But if it doesn't add anything meaningful, it's just duplicated code that can get out of sync.

Comment threadtests/reorg_test.rs Outdated
tnull added 3 commits July 15, 2026 16:05
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
The spend helper treated any script history as proof of a spend. The
funding transaction itself already creates such a history entry, so the
helper normally returned before Electrum indexed the closing
transaction. A following reorg could therefore begin while the funding
outpoint was still unspent.
Wait until the exact transaction output leaves Electrum's unspent set.
Co-Authored-By: HAL 9000
The force-close loop mined separately for each node even though every
node shares one chain. Mining for the first node advanced later nodes
past the exact intermediate state the test required. Sweep publication
and Electrum indexing are also asynchronous, so immediate assertions
could observe either valid state.
Advance the shared chain once and poll every claimable sweep through
broadcast and confirmation. Preserve the force-close counterexample
that fails when only the funding-depth fix is applied.
Co-Authored-By: HAL 9000
@tnull
tnullforce-pushed the 2026-07-fix-flaky-reorg-tests branch from f79d235 to 87b780eCompareJuly 15, 2026 14:35
@tnull
tnull requested a review from joostjagerJuly 15, 2026 14:36
@tnull
tnull merged commit 03812a0 into lightningdevkit:mainJul 15, 2026
16 of 23 checks passed
Sign up for freeto 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

@tnull@ldk-reviews-bot@joostjager
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Fix flaky reorg tests by tnull · Pull Request #972 · lightningdevkit/ldk-node · GitHub
Skip to content

Fix flaky reorg tests - #972

Merged
tnull merged 4 commits into
lightningdevkit:mainfrom
tnull:2026-07-fix-flaky-reorg-tests
Jul 15, 2026
Merged

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

Conversation

@tnull

@tnulltnull commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

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

@tnull
tnull requested a review from joostjagerJuly 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 threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/reorg_test.rs Outdated
Comment threadtests/reorg_test.rs Outdated
Comment threadtests/reorg_test.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
The test gave funding six confirmations while allowing six-block
reorgs. That drops regular channel funding to zero confirmations and
changes the scenario from a close reorg into a funding force-close.
rust-lightning PR #4231 keeps trusted zero-conf channels open after
funding is reorged out, but regular channels still force-close at zero
confirmations. Mine one extra block so the deepest reorg leaves one
confirmation.
Preserve the CI counterexample that exposed this boundary.
Co-Authored-By: HAL 9000
@tnull
tnullforce-pushed the 2026-07-fix-flaky-reorg-tests branch from f34c3ea to 5174199CompareJuly 15, 2026 11:09
@tnull

tnull commented Jul 15, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Alright, excuse the delay here. I now revisited the failure cases and narrowed the changes down to what's absolutely necessary, while improving the commit messages and comments. Also found another root cause for a flake, as we'd force-close if we entirely unconfirm a funding for a non-0conf channel.

@tnull
tnullforce-pushed the 2026-07-fix-flaky-reorg-tests branch from 5174199 to f79d235CompareJuly 15, 2026 11:26
@tnull
tnull requested a review from joostjagerJuly 15, 2026 11:27

@joostjagerjoostjager left a comment

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.

Looks good, clear improvements. Only minor questions.

Comment threadtests/reorg_test.rs
generate_blocks_and_wait(bitcoind, electrs, 6).await;
// Keep funding confirmed across the deepest reorg. rust-lightning PR #4231 exempts
// only trusted zero-conf channels; regular channels still force-close at zero confirmations.
generate_blocks_and_wait(bitcoind, electrs, 7).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.

Curious how this could have caused a flake?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

LDK currently force-closes a non-0conf channel if the funding is entirely unconfirmed due to a reorg. In the reorg test we randomize reorg height to be 1..=6, and if it happen to roll 6 in this particular instance the channel would force-close and we'd get the flake. The fix here is to just generate one more block to assert that we never completely reorg out the funding transaction here.

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.

I understand the fix, but in my opinion non-deterministic tests are something to avoid. Obviously it's a cheap way to expand coverage, but the cost of that is felt down the line.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Oh, I disagree on that sentiment. Exactly for things like reorgs you want to introduce some cases that make use of randomization (essentially chaos testing) to test cases that you'd otherwise only ever hit in production.

but the cost of that is felt down the line

I have no idea what 'the cost of that' is meant to be in this case.

Also, didn't you just expand/improve reorg and mempool coverage of LDK's fuzzer? How's that materially different from the proptests here?

@joostjagerjoostjagerJul 15, 2026

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.

Fair, I should have been clearer. I’m not against randomized/chaos testing, quite the opposite, that’s also why I expanded the reorg/mempool fuzzer coverage.

The cost I meant is mostly CI signal: before a counterexample is persisted, a deterministic boundary case can look intermittent because it only fails when proptest generates that input. The repro output helps, but there is still triage overhead.

My unease is mostly about the overlap between fuzzing and proptests, since both serve a discovery role. I’m trying to get clearer on which randomized coverage we want in regular CI versus in fuzz testing.

Comment threadtests/common/mod.rs Outdated
let _block_hashes_res = bitcoind.generate_to_address(num, &address);
wait_for_block(electrs, cur_height as usize + num).await;
let min_height = cur_height as usize + num;
wait_for_block(bitcoind, electrs, min_height).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.

Unnecessary var extraction, not consistent with calls below in the integration test?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Will revert.

Comment threadtests/common/mod.rs Outdated
let is_spent =
!electrs.script_list_unspent(&txout_script).unwrap().iter().any(|output| {
output.tx_hash == outpoint.txid && output.tx_pos == outpoint.vout as usize
});

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.

Initial poll looks like an optimization, but is it needed?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

It was an optimization that is now gone as you seem to prefer it.

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.

If we need the optimization, it doesn't need to be removed. But if it doesn't add anything meaningful, it's just duplicated code that can get out of sync.

Comment threadtests/reorg_test.rs Outdated
tnull added 3 commits July 15, 2026 16:05
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
The spend helper treated any script history as proof of a spend. The
funding transaction itself already creates such a history entry, so the
helper normally returned before Electrum indexed the closing
transaction. A following reorg could therefore begin while the funding
outpoint was still unspent.
Wait until the exact transaction output leaves Electrum's unspent set.
Co-Authored-By: HAL 9000
The force-close loop mined separately for each node even though every
node shares one chain. Mining for the first node advanced later nodes
past the exact intermediate state the test required. Sweep publication
and Electrum indexing are also asynchronous, so immediate assertions
could observe either valid state.
Advance the shared chain once and poll every claimable sweep through
broadcast and confirmation. Preserve the force-close counterexample
that fails when only the funding-depth fix is applied.
Co-Authored-By: HAL 9000
@tnull
tnullforce-pushed the 2026-07-fix-flaky-reorg-tests branch from f79d235 to 87b780eCompareJuly 15, 2026 14:35
@tnull
tnull requested a review from joostjagerJuly 15, 2026 14:36
@tnull
tnull merged commit 03812a0 into lightningdevkit:mainJul 15, 2026
16 of 23 checks passed
Sign up for freeto 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

@tnull@ldk-reviews-bot@joostjager
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Fix flaky reorg tests by tnull · Pull Request #972 · lightningdevkit/ldk-node · GitHub
Skip to content

Fix flaky reorg tests - #972

Merged
tnull merged 4 commits into
lightningdevkit:mainfrom
tnull:2026-07-fix-flaky-reorg-tests
Jul 15, 2026
Merged

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

Conversation

@tnull

@tnulltnull commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

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

@tnull
tnull requested a review from joostjagerJuly 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 threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/reorg_test.rs Outdated
Comment threadtests/reorg_test.rs Outdated
Comment threadtests/reorg_test.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
The test gave funding six confirmations while allowing six-block
reorgs. That drops regular channel funding to zero confirmations and
changes the scenario from a close reorg into a funding force-close.
rust-lightning PR #4231 keeps trusted zero-conf channels open after
funding is reorged out, but regular channels still force-close at zero
confirmations. Mine one extra block so the deepest reorg leaves one
confirmation.
Preserve the CI counterexample that exposed this boundary.
Co-Authored-By: HAL 9000
@tnull
tnullforce-pushed the 2026-07-fix-flaky-reorg-tests branch from f34c3ea to 5174199CompareJuly 15, 2026 11:09
@tnull

tnull commented Jul 15, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Alright, excuse the delay here. I now revisited the failure cases and narrowed the changes down to what's absolutely necessary, while improving the commit messages and comments. Also found another root cause for a flake, as we'd force-close if we entirely unconfirm a funding for a non-0conf channel.

@tnull
tnullforce-pushed the 2026-07-fix-flaky-reorg-tests branch from 5174199 to f79d235CompareJuly 15, 2026 11:26
@tnull
tnull requested a review from joostjagerJuly 15, 2026 11:27

@joostjagerjoostjager left a comment

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.

Looks good, clear improvements. Only minor questions.

Comment threadtests/reorg_test.rs
generate_blocks_and_wait(bitcoind, electrs, 6).await;
// Keep funding confirmed across the deepest reorg. rust-lightning PR #4231 exempts
// only trusted zero-conf channels; regular channels still force-close at zero confirmations.
generate_blocks_and_wait(bitcoind, electrs, 7).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.

Curious how this could have caused a flake?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

LDK currently force-closes a non-0conf channel if the funding is entirely unconfirmed due to a reorg. In the reorg test we randomize reorg height to be 1..=6, and if it happen to roll 6 in this particular instance the channel would force-close and we'd get the flake. The fix here is to just generate one more block to assert that we never completely reorg out the funding transaction here.

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.

I understand the fix, but in my opinion non-deterministic tests are something to avoid. Obviously it's a cheap way to expand coverage, but the cost of that is felt down the line.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Oh, I disagree on that sentiment. Exactly for things like reorgs you want to introduce some cases that make use of randomization (essentially chaos testing) to test cases that you'd otherwise only ever hit in production.

but the cost of that is felt down the line

I have no idea what 'the cost of that' is meant to be in this case.

Also, didn't you just expand/improve reorg and mempool coverage of LDK's fuzzer? How's that materially different from the proptests here?

@joostjagerjoostjagerJul 15, 2026

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.

Fair, I should have been clearer. I’m not against randomized/chaos testing, quite the opposite, that’s also why I expanded the reorg/mempool fuzzer coverage.

The cost I meant is mostly CI signal: before a counterexample is persisted, a deterministic boundary case can look intermittent because it only fails when proptest generates that input. The repro output helps, but there is still triage overhead.

My unease is mostly about the overlap between fuzzing and proptests, since both serve a discovery role. I’m trying to get clearer on which randomized coverage we want in regular CI versus in fuzz testing.

Comment threadtests/common/mod.rs Outdated
let _block_hashes_res = bitcoind.generate_to_address(num, &address);
wait_for_block(electrs, cur_height as usize + num).await;
let min_height = cur_height as usize + num;
wait_for_block(bitcoind, electrs, min_height).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.

Unnecessary var extraction, not consistent with calls below in the integration test?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Will revert.

Comment threadtests/common/mod.rs Outdated
let is_spent =
!electrs.script_list_unspent(&txout_script).unwrap().iter().any(|output| {
output.tx_hash == outpoint.txid && output.tx_pos == outpoint.vout as usize
});

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.

Initial poll looks like an optimization, but is it needed?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

It was an optimization that is now gone as you seem to prefer it.

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.

If we need the optimization, it doesn't need to be removed. But if it doesn't add anything meaningful, it's just duplicated code that can get out of sync.

Comment threadtests/reorg_test.rs Outdated
tnull added 3 commits July 15, 2026 16:05
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
The spend helper treated any script history as proof of a spend. The
funding transaction itself already creates such a history entry, so the
helper normally returned before Electrum indexed the closing
transaction. A following reorg could therefore begin while the funding
outpoint was still unspent.
Wait until the exact transaction output leaves Electrum's unspent set.
Co-Authored-By: HAL 9000
The force-close loop mined separately for each node even though every
node shares one chain. Mining for the first node advanced later nodes
past the exact intermediate state the test required. Sweep publication
and Electrum indexing are also asynchronous, so immediate assertions
could observe either valid state.
Advance the shared chain once and poll every claimable sweep through
broadcast and confirmation. Preserve the force-close counterexample
that fails when only the funding-depth fix is applied.
Co-Authored-By: HAL 9000
@tnull
tnullforce-pushed the 2026-07-fix-flaky-reorg-tests branch from f79d235 to 87b780eCompareJuly 15, 2026 14:35
@tnull
tnull requested a review from joostjagerJuly 15, 2026 14:36
@tnull
tnull merged commit 03812a0 into lightningdevkit:mainJul 15, 2026
16 of 23 checks passed
Sign up for freeto 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

@tnull@ldk-reviews-bot@joostjager
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Fix flaky reorg tests by tnull · Pull Request #972 · lightningdevkit/ldk-node · GitHub
Skip to content

Fix flaky reorg tests - #972

Merged
tnull merged 4 commits into
lightningdevkit:mainfrom
tnull:2026-07-fix-flaky-reorg-tests
Jul 15, 2026
Merged

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

Conversation

@tnull

@tnulltnull commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

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

@tnull
tnull requested a review from joostjagerJuly 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 threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/reorg_test.rs Outdated
Comment threadtests/reorg_test.rs Outdated
Comment threadtests/reorg_test.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
The test gave funding six confirmations while allowing six-block
reorgs. That drops regular channel funding to zero confirmations and
changes the scenario from a close reorg into a funding force-close.
rust-lightning PR #4231 keeps trusted zero-conf channels open after
funding is reorged out, but regular channels still force-close at zero
confirmations. Mine one extra block so the deepest reorg leaves one
confirmation.
Preserve the CI counterexample that exposed this boundary.
Co-Authored-By: HAL 9000
@tnull
tnullforce-pushed the 2026-07-fix-flaky-reorg-tests branch from f34c3ea to 5174199CompareJuly 15, 2026 11:09
@tnull

tnull commented Jul 15, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Alright, excuse the delay here. I now revisited the failure cases and narrowed the changes down to what's absolutely necessary, while improving the commit messages and comments. Also found another root cause for a flake, as we'd force-close if we entirely unconfirm a funding for a non-0conf channel.

@tnull
tnullforce-pushed the 2026-07-fix-flaky-reorg-tests branch from 5174199 to f79d235CompareJuly 15, 2026 11:26
@tnull
tnull requested a review from joostjagerJuly 15, 2026 11:27

@joostjagerjoostjager left a comment

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.

Looks good, clear improvements. Only minor questions.

Comment threadtests/reorg_test.rs
generate_blocks_and_wait(bitcoind, electrs, 6).await;
// Keep funding confirmed across the deepest reorg. rust-lightning PR #4231 exempts
// only trusted zero-conf channels; regular channels still force-close at zero confirmations.
generate_blocks_and_wait(bitcoind, electrs, 7).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.

Curious how this could have caused a flake?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

LDK currently force-closes a non-0conf channel if the funding is entirely unconfirmed due to a reorg. In the reorg test we randomize reorg height to be 1..=6, and if it happen to roll 6 in this particular instance the channel would force-close and we'd get the flake. The fix here is to just generate one more block to assert that we never completely reorg out the funding transaction here.

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.

I understand the fix, but in my opinion non-deterministic tests are something to avoid. Obviously it's a cheap way to expand coverage, but the cost of that is felt down the line.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Oh, I disagree on that sentiment. Exactly for things like reorgs you want to introduce some cases that make use of randomization (essentially chaos testing) to test cases that you'd otherwise only ever hit in production.

but the cost of that is felt down the line

I have no idea what 'the cost of that' is meant to be in this case.

Also, didn't you just expand/improve reorg and mempool coverage of LDK's fuzzer? How's that materially different from the proptests here?

@joostjagerjoostjagerJul 15, 2026

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.

Fair, I should have been clearer. I’m not against randomized/chaos testing, quite the opposite, that’s also why I expanded the reorg/mempool fuzzer coverage.

The cost I meant is mostly CI signal: before a counterexample is persisted, a deterministic boundary case can look intermittent because it only fails when proptest generates that input. The repro output helps, but there is still triage overhead.

My unease is mostly about the overlap between fuzzing and proptests, since both serve a discovery role. I’m trying to get clearer on which randomized coverage we want in regular CI versus in fuzz testing.

Comment threadtests/common/mod.rs Outdated
let _block_hashes_res = bitcoind.generate_to_address(num, &address);
wait_for_block(electrs, cur_height as usize + num).await;
let min_height = cur_height as usize + num;
wait_for_block(bitcoind, electrs, min_height).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.

Unnecessary var extraction, not consistent with calls below in the integration test?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Will revert.

Comment threadtests/common/mod.rs Outdated
let is_spent =
!electrs.script_list_unspent(&txout_script).unwrap().iter().any(|output| {
output.tx_hash == outpoint.txid && output.tx_pos == outpoint.vout as usize
});

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.

Initial poll looks like an optimization, but is it needed?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

It was an optimization that is now gone as you seem to prefer it.

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.

If we need the optimization, it doesn't need to be removed. But if it doesn't add anything meaningful, it's just duplicated code that can get out of sync.

Comment threadtests/reorg_test.rs Outdated
tnull added 3 commits July 15, 2026 16:05
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
The spend helper treated any script history as proof of a spend. The
funding transaction itself already creates such a history entry, so the
helper normally returned before Electrum indexed the closing
transaction. A following reorg could therefore begin while the funding
outpoint was still unspent.
Wait until the exact transaction output leaves Electrum's unspent set.
Co-Authored-By: HAL 9000
The force-close loop mined separately for each node even though every
node shares one chain. Mining for the first node advanced later nodes
past the exact intermediate state the test required. Sweep publication
and Electrum indexing are also asynchronous, so immediate assertions
could observe either valid state.
Advance the shared chain once and poll every claimable sweep through
broadcast and confirmation. Preserve the force-close counterexample
that fails when only the funding-depth fix is applied.
Co-Authored-By: HAL 9000
@tnull
tnullforce-pushed the 2026-07-fix-flaky-reorg-tests branch from f79d235 to 87b780eCompareJuly 15, 2026 14:35
@tnull
tnull requested a review from joostjagerJuly 15, 2026 14:36
@tnull
tnull merged commit 03812a0 into lightningdevkit:mainJul 15, 2026
16 of 23 checks passed
Sign up for freeto 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

@tnull@ldk-reviews-bot@joostjager
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Fix flaky reorg tests by tnull · Pull Request #972 · lightningdevkit/ldk-node · GitHub
Skip to content

Fix flaky reorg tests - #972

Merged
tnull merged 4 commits into
lightningdevkit:mainfrom
tnull:2026-07-fix-flaky-reorg-tests
Jul 15, 2026
Merged

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

Conversation

@tnull

@tnulltnull commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

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

@tnull
tnull requested a review from joostjagerJuly 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 threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/reorg_test.rs Outdated
Comment threadtests/reorg_test.rs Outdated
Comment threadtests/reorg_test.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
The test gave funding six confirmations while allowing six-block
reorgs. That drops regular channel funding to zero confirmations and
changes the scenario from a close reorg into a funding force-close.
rust-lightning PR #4231 keeps trusted zero-conf channels open after
funding is reorged out, but regular channels still force-close at zero
confirmations. Mine one extra block so the deepest reorg leaves one
confirmation.
Preserve the CI counterexample that exposed this boundary.
Co-Authored-By: HAL 9000
@tnull
tnullforce-pushed the 2026-07-fix-flaky-reorg-tests branch from f34c3ea to 5174199CompareJuly 15, 2026 11:09
@tnull

tnull commented Jul 15, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Alright, excuse the delay here. I now revisited the failure cases and narrowed the changes down to what's absolutely necessary, while improving the commit messages and comments. Also found another root cause for a flake, as we'd force-close if we entirely unconfirm a funding for a non-0conf channel.

@tnull
tnullforce-pushed the 2026-07-fix-flaky-reorg-tests branch from 5174199 to f79d235CompareJuly 15, 2026 11:26
@tnull
tnull requested a review from joostjagerJuly 15, 2026 11:27

@joostjagerjoostjager left a comment

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.

Looks good, clear improvements. Only minor questions.

Comment threadtests/reorg_test.rs
generate_blocks_and_wait(bitcoind, electrs, 6).await;
// Keep funding confirmed across the deepest reorg. rust-lightning PR #4231 exempts
// only trusted zero-conf channels; regular channels still force-close at zero confirmations.
generate_blocks_and_wait(bitcoind, electrs, 7).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.

Curious how this could have caused a flake?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

LDK currently force-closes a non-0conf channel if the funding is entirely unconfirmed due to a reorg. In the reorg test we randomize reorg height to be 1..=6, and if it happen to roll 6 in this particular instance the channel would force-close and we'd get the flake. The fix here is to just generate one more block to assert that we never completely reorg out the funding transaction here.

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.

I understand the fix, but in my opinion non-deterministic tests are something to avoid. Obviously it's a cheap way to expand coverage, but the cost of that is felt down the line.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Oh, I disagree on that sentiment. Exactly for things like reorgs you want to introduce some cases that make use of randomization (essentially chaos testing) to test cases that you'd otherwise only ever hit in production.

but the cost of that is felt down the line

I have no idea what 'the cost of that' is meant to be in this case.

Also, didn't you just expand/improve reorg and mempool coverage of LDK's fuzzer? How's that materially different from the proptests here?

@joostjagerjoostjagerJul 15, 2026

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.

Fair, I should have been clearer. I’m not against randomized/chaos testing, quite the opposite, that’s also why I expanded the reorg/mempool fuzzer coverage.

The cost I meant is mostly CI signal: before a counterexample is persisted, a deterministic boundary case can look intermittent because it only fails when proptest generates that input. The repro output helps, but there is still triage overhead.

My unease is mostly about the overlap between fuzzing and proptests, since both serve a discovery role. I’m trying to get clearer on which randomized coverage we want in regular CI versus in fuzz testing.

Comment threadtests/common/mod.rs Outdated
let _block_hashes_res = bitcoind.generate_to_address(num, &address);
wait_for_block(electrs, cur_height as usize + num).await;
let min_height = cur_height as usize + num;
wait_for_block(bitcoind, electrs, min_height).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.

Unnecessary var extraction, not consistent with calls below in the integration test?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Will revert.

Comment threadtests/common/mod.rs Outdated
let is_spent =
!electrs.script_list_unspent(&txout_script).unwrap().iter().any(|output| {
output.tx_hash == outpoint.txid && output.tx_pos == outpoint.vout as usize
});

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.

Initial poll looks like an optimization, but is it needed?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

It was an optimization that is now gone as you seem to prefer it.

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.

If we need the optimization, it doesn't need to be removed. But if it doesn't add anything meaningful, it's just duplicated code that can get out of sync.

Comment threadtests/reorg_test.rs Outdated
tnull added 3 commits July 15, 2026 16:05
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
The spend helper treated any script history as proof of a spend. The
funding transaction itself already creates such a history entry, so the
helper normally returned before Electrum indexed the closing
transaction. A following reorg could therefore begin while the funding
outpoint was still unspent.
Wait until the exact transaction output leaves Electrum's unspent set.
Co-Authored-By: HAL 9000
The force-close loop mined separately for each node even though every
node shares one chain. Mining for the first node advanced later nodes
past the exact intermediate state the test required. Sweep publication
and Electrum indexing are also asynchronous, so immediate assertions
could observe either valid state.
Advance the shared chain once and poll every claimable sweep through
broadcast and confirmation. Preserve the force-close counterexample
that fails when only the funding-depth fix is applied.
Co-Authored-By: HAL 9000
@tnull
tnullforce-pushed the 2026-07-fix-flaky-reorg-tests branch from f79d235 to 87b780eCompareJuly 15, 2026 14:35
@tnull
tnull requested a review from joostjagerJuly 15, 2026 14:36
@tnull
tnull merged commit 03812a0 into lightningdevkit:mainJul 15, 2026
16 of 23 checks passed
Sign up for freeto 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

@tnull@ldk-reviews-bot@joostjager
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Fix flaky reorg tests by tnull · Pull Request #972 · lightningdevkit/ldk-node · GitHub
Skip to content

Fix flaky reorg tests - #972

Merged
tnull merged 4 commits into
lightningdevkit:mainfrom
tnull:2026-07-fix-flaky-reorg-tests
Jul 15, 2026
Merged

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

Conversation

@tnull

@tnulltnull commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

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

@tnull
tnull requested a review from joostjagerJuly 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 threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/reorg_test.rs Outdated
Comment threadtests/reorg_test.rs Outdated
Comment threadtests/reorg_test.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
The test gave funding six confirmations while allowing six-block
reorgs. That drops regular channel funding to zero confirmations and
changes the scenario from a close reorg into a funding force-close.
rust-lightning PR #4231 keeps trusted zero-conf channels open after
funding is reorged out, but regular channels still force-close at zero
confirmations. Mine one extra block so the deepest reorg leaves one
confirmation.
Preserve the CI counterexample that exposed this boundary.
Co-Authored-By: HAL 9000
@tnull
tnullforce-pushed the 2026-07-fix-flaky-reorg-tests branch from f34c3ea to 5174199CompareJuly 15, 2026 11:09
@tnull

tnull commented Jul 15, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Alright, excuse the delay here. I now revisited the failure cases and narrowed the changes down to what's absolutely necessary, while improving the commit messages and comments. Also found another root cause for a flake, as we'd force-close if we entirely unconfirm a funding for a non-0conf channel.

@tnull
tnullforce-pushed the 2026-07-fix-flaky-reorg-tests branch from 5174199 to f79d235CompareJuly 15, 2026 11:26
@tnull
tnull requested a review from joostjagerJuly 15, 2026 11:27

@joostjagerjoostjager left a comment

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.

Looks good, clear improvements. Only minor questions.

Comment threadtests/reorg_test.rs
generate_blocks_and_wait(bitcoind, electrs, 6).await;
// Keep funding confirmed across the deepest reorg. rust-lightning PR #4231 exempts
// only trusted zero-conf channels; regular channels still force-close at zero confirmations.
generate_blocks_and_wait(bitcoind, electrs, 7).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.

Curious how this could have caused a flake?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

LDK currently force-closes a non-0conf channel if the funding is entirely unconfirmed due to a reorg. In the reorg test we randomize reorg height to be 1..=6, and if it happen to roll 6 in this particular instance the channel would force-close and we'd get the flake. The fix here is to just generate one more block to assert that we never completely reorg out the funding transaction here.

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.

I understand the fix, but in my opinion non-deterministic tests are something to avoid. Obviously it's a cheap way to expand coverage, but the cost of that is felt down the line.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Oh, I disagree on that sentiment. Exactly for things like reorgs you want to introduce some cases that make use of randomization (essentially chaos testing) to test cases that you'd otherwise only ever hit in production.

but the cost of that is felt down the line

I have no idea what 'the cost of that' is meant to be in this case.

Also, didn't you just expand/improve reorg and mempool coverage of LDK's fuzzer? How's that materially different from the proptests here?

@joostjagerjoostjagerJul 15, 2026

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.

Fair, I should have been clearer. I’m not against randomized/chaos testing, quite the opposite, that’s also why I expanded the reorg/mempool fuzzer coverage.

The cost I meant is mostly CI signal: before a counterexample is persisted, a deterministic boundary case can look intermittent because it only fails when proptest generates that input. The repro output helps, but there is still triage overhead.

My unease is mostly about the overlap between fuzzing and proptests, since both serve a discovery role. I’m trying to get clearer on which randomized coverage we want in regular CI versus in fuzz testing.

Comment threadtests/common/mod.rs Outdated
let _block_hashes_res = bitcoind.generate_to_address(num, &address);
wait_for_block(electrs, cur_height as usize + num).await;
let min_height = cur_height as usize + num;
wait_for_block(bitcoind, electrs, min_height).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.

Unnecessary var extraction, not consistent with calls below in the integration test?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Will revert.

Comment threadtests/common/mod.rs Outdated
let is_spent =
!electrs.script_list_unspent(&txout_script).unwrap().iter().any(|output| {
output.tx_hash == outpoint.txid && output.tx_pos == outpoint.vout as usize
});

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.

Initial poll looks like an optimization, but is it needed?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

It was an optimization that is now gone as you seem to prefer it.

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.

If we need the optimization, it doesn't need to be removed. But if it doesn't add anything meaningful, it's just duplicated code that can get out of sync.

Comment threadtests/reorg_test.rs Outdated
tnull added 3 commits July 15, 2026 16:05
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
The spend helper treated any script history as proof of a spend. The
funding transaction itself already creates such a history entry, so the
helper normally returned before Electrum indexed the closing
transaction. A following reorg could therefore begin while the funding
outpoint was still unspent.
Wait until the exact transaction output leaves Electrum's unspent set.
Co-Authored-By: HAL 9000
The force-close loop mined separately for each node even though every
node shares one chain. Mining for the first node advanced later nodes
past the exact intermediate state the test required. Sweep publication
and Electrum indexing are also asynchronous, so immediate assertions
could observe either valid state.
Advance the shared chain once and poll every claimable sweep through
broadcast and confirmation. Preserve the force-close counterexample
that fails when only the funding-depth fix is applied.
Co-Authored-By: HAL 9000
@tnull
tnullforce-pushed the 2026-07-fix-flaky-reorg-tests branch from f79d235 to 87b780eCompareJuly 15, 2026 14:35
@tnull
tnull requested a review from joostjagerJuly 15, 2026 14:36
@tnull
tnull merged commit 03812a0 into lightningdevkit:mainJul 15, 2026
16 of 23 checks passed
Sign up for freeto 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

@tnull@ldk-reviews-bot@joostjager
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Fix flaky reorg tests by tnull · Pull Request #972 · lightningdevkit/ldk-node · GitHub
Skip to content

Fix flaky reorg tests - #972

Merged
tnull merged 4 commits into
lightningdevkit:mainfrom
tnull:2026-07-fix-flaky-reorg-tests
Jul 15, 2026
Merged

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

Conversation

@tnull

@tnulltnull commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

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

@tnull
tnull requested a review from joostjagerJuly 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 threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/reorg_test.rs Outdated
Comment threadtests/reorg_test.rs Outdated
Comment threadtests/reorg_test.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
The test gave funding six confirmations while allowing six-block
reorgs. That drops regular channel funding to zero confirmations and
changes the scenario from a close reorg into a funding force-close.
rust-lightning PR #4231 keeps trusted zero-conf channels open after
funding is reorged out, but regular channels still force-close at zero
confirmations. Mine one extra block so the deepest reorg leaves one
confirmation.
Preserve the CI counterexample that exposed this boundary.
Co-Authored-By: HAL 9000
@tnull
tnullforce-pushed the 2026-07-fix-flaky-reorg-tests branch from f34c3ea to 5174199CompareJuly 15, 2026 11:09
@tnull

tnull commented Jul 15, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Alright, excuse the delay here. I now revisited the failure cases and narrowed the changes down to what's absolutely necessary, while improving the commit messages and comments. Also found another root cause for a flake, as we'd force-close if we entirely unconfirm a funding for a non-0conf channel.

@tnull
tnullforce-pushed the 2026-07-fix-flaky-reorg-tests branch from 5174199 to f79d235CompareJuly 15, 2026 11:26
@tnull
tnull requested a review from joostjagerJuly 15, 2026 11:27

@joostjagerjoostjager left a comment

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.

Looks good, clear improvements. Only minor questions.

Comment threadtests/reorg_test.rs
generate_blocks_and_wait(bitcoind, electrs, 6).await;
// Keep funding confirmed across the deepest reorg. rust-lightning PR #4231 exempts
// only trusted zero-conf channels; regular channels still force-close at zero confirmations.
generate_blocks_and_wait(bitcoind, electrs, 7).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.

Curious how this could have caused a flake?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

LDK currently force-closes a non-0conf channel if the funding is entirely unconfirmed due to a reorg. In the reorg test we randomize reorg height to be 1..=6, and if it happen to roll 6 in this particular instance the channel would force-close and we'd get the flake. The fix here is to just generate one more block to assert that we never completely reorg out the funding transaction here.

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.

I understand the fix, but in my opinion non-deterministic tests are something to avoid. Obviously it's a cheap way to expand coverage, but the cost of that is felt down the line.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Oh, I disagree on that sentiment. Exactly for things like reorgs you want to introduce some cases that make use of randomization (essentially chaos testing) to test cases that you'd otherwise only ever hit in production.

but the cost of that is felt down the line

I have no idea what 'the cost of that' is meant to be in this case.

Also, didn't you just expand/improve reorg and mempool coverage of LDK's fuzzer? How's that materially different from the proptests here?

@joostjagerjoostjagerJul 15, 2026

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.

Fair, I should have been clearer. I’m not against randomized/chaos testing, quite the opposite, that’s also why I expanded the reorg/mempool fuzzer coverage.

The cost I meant is mostly CI signal: before a counterexample is persisted, a deterministic boundary case can look intermittent because it only fails when proptest generates that input. The repro output helps, but there is still triage overhead.

My unease is mostly about the overlap between fuzzing and proptests, since both serve a discovery role. I’m trying to get clearer on which randomized coverage we want in regular CI versus in fuzz testing.

Comment threadtests/common/mod.rs Outdated
let _block_hashes_res = bitcoind.generate_to_address(num, &address);
wait_for_block(electrs, cur_height as usize + num).await;
let min_height = cur_height as usize + num;
wait_for_block(bitcoind, electrs, min_height).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.

Unnecessary var extraction, not consistent with calls below in the integration test?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Will revert.

Comment threadtests/common/mod.rs Outdated
let is_spent =
!electrs.script_list_unspent(&txout_script).unwrap().iter().any(|output| {
output.tx_hash == outpoint.txid && output.tx_pos == outpoint.vout as usize
});

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.

Initial poll looks like an optimization, but is it needed?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

It was an optimization that is now gone as you seem to prefer it.

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.

If we need the optimization, it doesn't need to be removed. But if it doesn't add anything meaningful, it's just duplicated code that can get out of sync.

Comment threadtests/reorg_test.rs Outdated
tnull added 3 commits July 15, 2026 16:05
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
The spend helper treated any script history as proof of a spend. The
funding transaction itself already creates such a history entry, so the
helper normally returned before Electrum indexed the closing
transaction. A following reorg could therefore begin while the funding
outpoint was still unspent.
Wait until the exact transaction output leaves Electrum's unspent set.
Co-Authored-By: HAL 9000
The force-close loop mined separately for each node even though every
node shares one chain. Mining for the first node advanced later nodes
past the exact intermediate state the test required. Sweep publication
and Electrum indexing are also asynchronous, so immediate assertions
could observe either valid state.
Advance the shared chain once and poll every claimable sweep through
broadcast and confirmation. Preserve the force-close counterexample
that fails when only the funding-depth fix is applied.
Co-Authored-By: HAL 9000
@tnull
tnullforce-pushed the 2026-07-fix-flaky-reorg-tests branch from f79d235 to 87b780eCompareJuly 15, 2026 14:35
@tnull
tnull requested a review from joostjagerJuly 15, 2026 14:36
@tnull
tnull merged commit 03812a0 into lightningdevkit:mainJul 15, 2026
16 of 23 checks passed
Sign up for freeto 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

@tnull@ldk-reviews-bot@joostjager
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Fix flaky reorg tests by tnull · Pull Request #972 · lightningdevkit/ldk-node · GitHub
Skip to content

Fix flaky reorg tests - #972

Merged
tnull merged 4 commits into
lightningdevkit:mainfrom
tnull:2026-07-fix-flaky-reorg-tests
Jul 15, 2026
Merged

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

Conversation

@tnull

@tnulltnull commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

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

@tnull
tnull requested a review from joostjagerJuly 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 threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/reorg_test.rs Outdated
Comment threadtests/reorg_test.rs Outdated
Comment threadtests/reorg_test.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
Comment threadtests/common/mod.rs Outdated
The test gave funding six confirmations while allowing six-block
reorgs. That drops regular channel funding to zero confirmations and
changes the scenario from a close reorg into a funding force-close.
rust-lightning PR #4231 keeps trusted zero-conf channels open after
funding is reorged out, but regular channels still force-close at zero
confirmations. Mine one extra block so the deepest reorg leaves one
confirmation.
Preserve the CI counterexample that exposed this boundary.
Co-Authored-By: HAL 9000
@tnull
tnullforce-pushed the 2026-07-fix-flaky-reorg-tests branch from f34c3ea to 5174199CompareJuly 15, 2026 11:09
@tnull

tnull commented Jul 15, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Alright, excuse the delay here. I now revisited the failure cases and narrowed the changes down to what's absolutely necessary, while improving the commit messages and comments. Also found another root cause for a flake, as we'd force-close if we entirely unconfirm a funding for a non-0conf channel.

@tnull
tnullforce-pushed the 2026-07-fix-flaky-reorg-tests branch from 5174199 to f79d235CompareJuly 15, 2026 11:26
@tnull
tnull requested a review from joostjagerJuly 15, 2026 11:27

@joostjagerjoostjager left a comment

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.

Looks good, clear improvements. Only minor questions.

Comment threadtests/reorg_test.rs
generate_blocks_and_wait(bitcoind, electrs, 6).await;
// Keep funding confirmed across the deepest reorg. rust-lightning PR #4231 exempts
// only trusted zero-conf channels; regular channels still force-close at zero confirmations.
generate_blocks_and_wait(bitcoind, electrs, 7).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.

Curious how this could have caused a flake?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

LDK currently force-closes a non-0conf channel if the funding is entirely unconfirmed due to a reorg. In the reorg test we randomize reorg height to be 1..=6, and if it happen to roll 6 in this particular instance the channel would force-close and we'd get the flake. The fix here is to just generate one more block to assert that we never completely reorg out the funding transaction here.

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.

I understand the fix, but in my opinion non-deterministic tests are something to avoid. Obviously it's a cheap way to expand coverage, but the cost of that is felt down the line.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Oh, I disagree on that sentiment. Exactly for things like reorgs you want to introduce some cases that make use of randomization (essentially chaos testing) to test cases that you'd otherwise only ever hit in production.

but the cost of that is felt down the line

I have no idea what 'the cost of that' is meant to be in this case.

Also, didn't you just expand/improve reorg and mempool coverage of LDK's fuzzer? How's that materially different from the proptests here?

@joostjagerjoostjagerJul 15, 2026

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.

Fair, I should have been clearer. I’m not against randomized/chaos testing, quite the opposite, that’s also why I expanded the reorg/mempool fuzzer coverage.

The cost I meant is mostly CI signal: before a counterexample is persisted, a deterministic boundary case can look intermittent because it only fails when proptest generates that input. The repro output helps, but there is still triage overhead.

My unease is mostly about the overlap between fuzzing and proptests, since both serve a discovery role. I’m trying to get clearer on which randomized coverage we want in regular CI versus in fuzz testing.

Comment threadtests/common/mod.rs Outdated
let _block_hashes_res = bitcoind.generate_to_address(num, &address);
wait_for_block(electrs, cur_height as usize + num).await;
let min_height = cur_height as usize + num;
wait_for_block(bitcoind, electrs, min_height).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.

Unnecessary var extraction, not consistent with calls below in the integration test?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Will revert.

Comment threadtests/common/mod.rs Outdated
let is_spent =
!electrs.script_list_unspent(&txout_script).unwrap().iter().any(|output| {
output.tx_hash == outpoint.txid && output.tx_pos == outpoint.vout as usize
});

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.

Initial poll looks like an optimization, but is it needed?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

It was an optimization that is now gone as you seem to prefer it.

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.

If we need the optimization, it doesn't need to be removed. But if it doesn't add anything meaningful, it's just duplicated code that can get out of sync.

Comment threadtests/reorg_test.rs Outdated
tnull added 3 commits July 15, 2026 16:05
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
The spend helper treated any script history as proof of a spend. The
funding transaction itself already creates such a history entry, so the
helper normally returned before Electrum indexed the closing
transaction. A following reorg could therefore begin while the funding
outpoint was still unspent.
Wait until the exact transaction output leaves Electrum's unspent set.
Co-Authored-By: HAL 9000
The force-close loop mined separately for each node even though every
node shares one chain. Mining for the first node advanced later nodes
past the exact intermediate state the test required. Sweep publication
and Electrum indexing are also asynchronous, so immediate assertions
could observe either valid state.
Advance the shared chain once and poll every claimable sweep through
broadcast and confirmation. Preserve the force-close counterexample
that fails when only the funding-depth fix is applied.
Co-Authored-By: HAL 9000
@tnull
tnullforce-pushed the 2026-07-fix-flaky-reorg-tests branch from f79d235 to 87b780eCompareJuly 15, 2026 14:35
@tnull
tnull requested a review from joostjagerJuly 15, 2026 14:36
@tnull
tnull merged commit 03812a0 into lightningdevkit:mainJul 15, 2026
16 of 23 checks passed
Sign up for freeto 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

@tnull@ldk-reviews-bot@joostjager