Skip to content

Randomize chain source selection in tests - #769

Merged
tnull merged 6 commits into
lightningdevkit:mainfrom
tnull:2026-01-test-setup
Feb 16, 2026
Merged

Randomize chain source selection in tests#769
tnull merged 6 commits into
lightningdevkit:mainfrom
tnull:2026-01-test-setup

Conversation

@tnull

Copy link
Copy Markdown
Collaborator

.. all of our tests should be robust against switching chain sources. We
here opt to pick a random one each time to considerably extend our test
coverage, instead of just running some cases against non-Esplora chain
sources.

@ldk-reviews-bot

ldk-reviews-bot commented Jan 23, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt 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.

@tnull

Copy link
Copy Markdown
CollaboratorAuthor

This will be flaky, at the very least until lightningdevkit/rust-lightning#4341 gets release, which would have been caught if we'd ever had run the 0conf test case with an Electrum chain source.

TheBlueMatt
TheBlueMatt previously approved these changes Jan 23, 2026

@TheBlueMattTheBlueMatt 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.

makes sense to me. Hopefully CI fails until lightningdevkit/rust-lightning#4341

@tnull

Copy link
Copy Markdown
CollaboratorAuthor

makes sense to me. Hopefully CI fails until lightningdevkit/rust-lightning#4341

I think I'll whack-a-mole a few more flakes/bugs before landing this.

@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Previously

 simple_bolt12_send_receive (bitcoind RPC)
test_node_announcement_propagation (bitcoind RPC)

had failed, I want to double-check them once more before moving forward here.

@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Pushed an update, but currently onchain_send_receive is still (or newly?) broken when syncing with bitcoind. Will look into that.

@tnull
tnull marked this pull request as draft January 29, 2026 19:12
@tnull
tnullforce-pushed the 2026-01-test-setup branch from b8179ca to 282605bCompareJanuary 29, 2026 19:12
It's weird to have a special intermediary `setup_node` method if we have
`TestConfig` for exactly that reason by now. So we move
`async_payment_role` over.
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Pushed an update, but currently onchain_send_receive is still (or newly?) broken when syncing with bitcoind. Will look into that.

Alright, fixed that one, too and rebased. Should be ready for review now.

@tnull
tnull marked this pull request as ready for review February 12, 2026 10:10
.. all of our tests should be robust against switching chain sources. We
here opt to pick a random one each time to considerably extend our test
coverage, instead of just running some cases against non-Esplora chain
sources.
Signed-off-by: Elias Rohrer <dev@tnull.de>

@TheBlueMattTheBlueMatt 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.

I don't really know BDK well enough to feel like I can meaningfully review this, but a few questions.

Comment threadsrc/wallet/mod.rs

// FIXME/TODO: This is copied-over from bdk_wallet and only used to generate `WalletEvent`s after
// applying mempool transactions. We should drop this when BDK offers to generate events for
// mempool transactions natively.

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.

Hmm, if BDK doesn't support this natively, do they have logic to correctly handle conflicts and build balance knowledge appropriately? ie if there's an RBF that conflicts do we remove the 0conf tx from our balance?

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.

Yes, they support all of the canonical transaction handling, it's just when they recently introduced the WalletEvent, they didn't update all APIs we require to return them. Arguably, they should just expose the helper we here copied over which would avoid them having to provide _event variants for all API endpoints individually. See also the discussion on bitcoindevkit/bdk_wallet#374

Comment threadsrc/builder.rs
///
/// This should only be set on first startup when importing an older wallet from a previously
/// used [`NodeEntropy`].
pub fn set_wallet_recovery_mode(&mut self) -> &mut Self {

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.

Rather than a coarse "recovery mode" can we just enable setting the "wallet birthday"? In some setups that might be available and would avoid a lot of effort.

@tnulltnullFeb 12, 2026

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.

Hmm, wallet birthday support is still inflight on the BDK side (see bitcoindevkit/bdk_wallet#368) and I'd like to punt on it until we can actually make use of that and remove our hacky birthday logic.

That said, if you prefer we could of course rename the current "recovery_mode" API to "disable_wallet_birthday" for now which ~does the same thing?

@tnulltnull moved this to Goal: Merge in Weekly GoalsFeb 12, 2026
@tnulltnull self-assigned this Feb 12, 2026
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Amended the last commit to include a comment explaining what we do/why we do it:

diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs
index 835e1b31..2decf981 100644
--- a/src/wallet/mod.rs+++ b/src/wallet/mod.rs@@ -1031,4 +1031,12 @@ impl Listen for Wallet {
}
+ // In order to be able to reliably calculate fees the `Wallet` needs access to the previous+ // ouput data. To this end, we here insert any ouputs of transactions that LDK is intersted+ // in (e.g., funding transaction ouputs) into the wallet's transaction graph when we see+ // them, so it is reliably able to calculate fees for subsequent spends.+ //+ // FIXME: technically, we should also do this for mempool transactions. However, at the+ // current time fixing the edge case doesn't seem worth the additional conplexity /+ // additional overhead..
let registered_txids = self.chain_source.registered_txids();
for tx in &block.txdata {

Comment threadsrc/wallet/mod.rs
Error::PersistenceFailed
})?;

self.update_payment_store(&mut *locked_wallet, events).map_err(|e| {

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.

Should this happen before locked_wallet.persist()? What happens if we crash between the wallet persist and the payment store update (presumably the second is idempotent so we should do it first to reply on restart if needed?)?

@tnulltnullFeb 16, 2026

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.

Also a good point. I think the current approach worked fine when we still iterated on the wallet txs, but now with the events we should consistently update first to ensure events are replayed. Now added a fixup and another commit fixing it for apply_update.

Comment threadsrc/wallet/mod.rs
&self, unconfirmed_txs: Vec<(Transaction, u64)>, evicted_txids: Vec<(Txid, u64)>,
) -> Result<(), Error> {
if unconfirmed_txs.is_empty() {
return 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.

This doesn't appear to be tested. Also not clear to me why we'd want to skip this if we don't have any unconfirmed txs in the mempool but do have some txs that got evicted (iiuc).

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.

Hmm, good point, we should only skip if there's nothing to update. Added a fixup

@TheBlueMattTheBlueMatt 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.

Really don't fully grok all the BDK parts, but from what I can tell this looks reasonable. Feel free to squash + land.

When we intially implemented `bitcoind` syncing polling the mempool was
very frequent and rather inefficient so we made a choice not to
unnecessarily update the payment store for mempool changes, especially
since we only consider transactions `Succeeded` after
`ANTI_REORG_DELAY` anyways.
However, since then we made quite a few peformance improvements to the
mempool syncing, and by now we should just update they payment store as
not doing so will lead to rather unexpected behavior, making some tests
fail for `TestChainSource::Bitcoind`, e.g., `channel_full_cycle_0conf`,
which we fix here.
As we recently switched to updating the payment store based on BDK's
`WalletEvent`, but they currently don't offer an API returning such
events when applying mempool transactions, we copy over the respective
method for generating events from `bdk_wallet`, with the intention of
dropping it again once they do.
Signed-off-by: Elias Rohrer <dev@tnull.de>
Previously, we fixed than a fresh node syncing via `bitcoind` RPC would
resync all chain data back to genesis. However, while introducing a
wallet birthday is great, it disallowed discovery of historical funds if
a wallet would be imported from seed. Here, we add a recovery mode flag
to the builder that explictly allows to re-enable resyncing from genesis
in such a scenario. Going forward, we intend to reuse that API for an
upcoming Lightning recoery flow, too.
Previously, we'd selectively insert the funding outputs into the onchain
wallet to later allow calculating `fees_paid` when creating payment
store entries (only for splicing mostly). However, this didn't always work, and we might for
example end up with a missing funding output (and hence would fall back
to `fees_paid: Some(0)`) if it was a counterparty-initiated channel and
we synced via `bitcoind` RPC.
Here, we fix this by tracking all LDK-registered `txids` in
`ChainSource` and then in the `Wallet`'s `Listen` implementation insert
all outputs of all registered transactions into the `Wallet`, ensuring
we'd always have sufficient data for `calculate_fee` available.
Thereby we also fix the `onchain_send_receive` test which previously
failed when using `TestChainSource::Bitcoind`.
Signed-off-by: Elias Rohrer <dev@tnull.de>
Previously, we'd update the payment store after persisting the wallet in
some cases. This was fine as long as we iterated all wallet transactions
anyways (hence idempotent). However, now that we use the event-based
flow we should persist the payment store(s) first, so that wallet events
get replayed if there was a crash in-between some of the persistence
operations.
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Really don't fully grok all the BDK parts, but from what I can tell this looks reasonable. Feel free to squash + land.

Squashed fixups without further changes.

@tnull
tnull merged commit b55de44 into lightningdevkit:mainFeb 16, 2026
23 of 28 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsFeb 16, 2026
@joostjager

Copy link
Copy Markdown
Contributor

For the record, as I've also noted in other PRs that randomize tests: I don't think this is a good approach. A more extensive test suite that runs on main or as a nightly job, iterating through all backends, would be a far superior solution in my opinion.

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

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@tnull@ldk-reviews-bot@joostjager@TheBlueMatt
, '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" + '
Randomize chain source selection in tests by tnull · Pull Request #769 · lightningdevkit/ldk-node · GitHub
Skip to content

Randomize chain source selection in tests - #769

Merged
tnull merged 6 commits into
lightningdevkit:mainfrom
tnull:2026-01-test-setup
Feb 16, 2026
Merged

Randomize chain source selection in tests#769
tnull merged 6 commits into
lightningdevkit:mainfrom
tnull:2026-01-test-setup

Conversation

@tnull

Copy link
Copy Markdown
Collaborator

.. all of our tests should be robust against switching chain sources. We
here opt to pick a random one each time to considerably extend our test
coverage, instead of just running some cases against non-Esplora chain
sources.

@ldk-reviews-bot

ldk-reviews-bot commented Jan 23, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt 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.

@tnull

Copy link
Copy Markdown
CollaboratorAuthor

This will be flaky, at the very least until lightningdevkit/rust-lightning#4341 gets release, which would have been caught if we'd ever had run the 0conf test case with an Electrum chain source.

TheBlueMatt
TheBlueMatt previously approved these changes Jan 23, 2026

@TheBlueMattTheBlueMatt 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.

makes sense to me. Hopefully CI fails until lightningdevkit/rust-lightning#4341

@tnull

Copy link
Copy Markdown
CollaboratorAuthor

makes sense to me. Hopefully CI fails until lightningdevkit/rust-lightning#4341

I think I'll whack-a-mole a few more flakes/bugs before landing this.

@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Previously

 simple_bolt12_send_receive (bitcoind RPC)
test_node_announcement_propagation (bitcoind RPC)

had failed, I want to double-check them once more before moving forward here.

@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Pushed an update, but currently onchain_send_receive is still (or newly?) broken when syncing with bitcoind. Will look into that.

@tnull
tnull marked this pull request as draft January 29, 2026 19:12
@tnull
tnullforce-pushed the 2026-01-test-setup branch from b8179ca to 282605bCompareJanuary 29, 2026 19:12
It's weird to have a special intermediary `setup_node` method if we have
`TestConfig` for exactly that reason by now. So we move
`async_payment_role` over.
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Pushed an update, but currently onchain_send_receive is still (or newly?) broken when syncing with bitcoind. Will look into that.

Alright, fixed that one, too and rebased. Should be ready for review now.

@tnull
tnull marked this pull request as ready for review February 12, 2026 10:10
.. all of our tests should be robust against switching chain sources. We
here opt to pick a random one each time to considerably extend our test
coverage, instead of just running some cases against non-Esplora chain
sources.
Signed-off-by: Elias Rohrer <dev@tnull.de>

@TheBlueMattTheBlueMatt 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.

I don't really know BDK well enough to feel like I can meaningfully review this, but a few questions.

Comment threadsrc/wallet/mod.rs

// FIXME/TODO: This is copied-over from bdk_wallet and only used to generate `WalletEvent`s after
// applying mempool transactions. We should drop this when BDK offers to generate events for
// mempool transactions natively.

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.

Hmm, if BDK doesn't support this natively, do they have logic to correctly handle conflicts and build balance knowledge appropriately? ie if there's an RBF that conflicts do we remove the 0conf tx from our balance?

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.

Yes, they support all of the canonical transaction handling, it's just when they recently introduced the WalletEvent, they didn't update all APIs we require to return them. Arguably, they should just expose the helper we here copied over which would avoid them having to provide _event variants for all API endpoints individually. See also the discussion on bitcoindevkit/bdk_wallet#374

Comment threadsrc/builder.rs
///
/// This should only be set on first startup when importing an older wallet from a previously
/// used [`NodeEntropy`].
pub fn set_wallet_recovery_mode(&mut self) -> &mut Self {

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.

Rather than a coarse "recovery mode" can we just enable setting the "wallet birthday"? In some setups that might be available and would avoid a lot of effort.

@tnulltnullFeb 12, 2026

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.

Hmm, wallet birthday support is still inflight on the BDK side (see bitcoindevkit/bdk_wallet#368) and I'd like to punt on it until we can actually make use of that and remove our hacky birthday logic.

That said, if you prefer we could of course rename the current "recovery_mode" API to "disable_wallet_birthday" for now which ~does the same thing?

@tnulltnull moved this to Goal: Merge in Weekly GoalsFeb 12, 2026
@tnulltnull self-assigned this Feb 12, 2026
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Amended the last commit to include a comment explaining what we do/why we do it:

diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs
index 835e1b31..2decf981 100644
--- a/src/wallet/mod.rs+++ b/src/wallet/mod.rs@@ -1031,4 +1031,12 @@ impl Listen for Wallet {
}
+ // In order to be able to reliably calculate fees the `Wallet` needs access to the previous+ // ouput data. To this end, we here insert any ouputs of transactions that LDK is intersted+ // in (e.g., funding transaction ouputs) into the wallet's transaction graph when we see+ // them, so it is reliably able to calculate fees for subsequent spends.+ //+ // FIXME: technically, we should also do this for mempool transactions. However, at the+ // current time fixing the edge case doesn't seem worth the additional conplexity /+ // additional overhead..
let registered_txids = self.chain_source.registered_txids();
for tx in &block.txdata {

Comment threadsrc/wallet/mod.rs
Error::PersistenceFailed
})?;

self.update_payment_store(&mut *locked_wallet, events).map_err(|e| {

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.

Should this happen before locked_wallet.persist()? What happens if we crash between the wallet persist and the payment store update (presumably the second is idempotent so we should do it first to reply on restart if needed?)?

@tnulltnullFeb 16, 2026

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.

Also a good point. I think the current approach worked fine when we still iterated on the wallet txs, but now with the events we should consistently update first to ensure events are replayed. Now added a fixup and another commit fixing it for apply_update.

Comment threadsrc/wallet/mod.rs
&self, unconfirmed_txs: Vec<(Transaction, u64)>, evicted_txids: Vec<(Txid, u64)>,
) -> Result<(), Error> {
if unconfirmed_txs.is_empty() {
return 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.

This doesn't appear to be tested. Also not clear to me why we'd want to skip this if we don't have any unconfirmed txs in the mempool but do have some txs that got evicted (iiuc).

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.

Hmm, good point, we should only skip if there's nothing to update. Added a fixup

@TheBlueMattTheBlueMatt 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.

Really don't fully grok all the BDK parts, but from what I can tell this looks reasonable. Feel free to squash + land.

When we intially implemented `bitcoind` syncing polling the mempool was
very frequent and rather inefficient so we made a choice not to
unnecessarily update the payment store for mempool changes, especially
since we only consider transactions `Succeeded` after
`ANTI_REORG_DELAY` anyways.
However, since then we made quite a few peformance improvements to the
mempool syncing, and by now we should just update they payment store as
not doing so will lead to rather unexpected behavior, making some tests
fail for `TestChainSource::Bitcoind`, e.g., `channel_full_cycle_0conf`,
which we fix here.
As we recently switched to updating the payment store based on BDK's
`WalletEvent`, but they currently don't offer an API returning such
events when applying mempool transactions, we copy over the respective
method for generating events from `bdk_wallet`, with the intention of
dropping it again once they do.
Signed-off-by: Elias Rohrer <dev@tnull.de>
Previously, we fixed than a fresh node syncing via `bitcoind` RPC would
resync all chain data back to genesis. However, while introducing a
wallet birthday is great, it disallowed discovery of historical funds if
a wallet would be imported from seed. Here, we add a recovery mode flag
to the builder that explictly allows to re-enable resyncing from genesis
in such a scenario. Going forward, we intend to reuse that API for an
upcoming Lightning recoery flow, too.
Previously, we'd selectively insert the funding outputs into the onchain
wallet to later allow calculating `fees_paid` when creating payment
store entries (only for splicing mostly). However, this didn't always work, and we might for
example end up with a missing funding output (and hence would fall back
to `fees_paid: Some(0)`) if it was a counterparty-initiated channel and
we synced via `bitcoind` RPC.
Here, we fix this by tracking all LDK-registered `txids` in
`ChainSource` and then in the `Wallet`'s `Listen` implementation insert
all outputs of all registered transactions into the `Wallet`, ensuring
we'd always have sufficient data for `calculate_fee` available.
Thereby we also fix the `onchain_send_receive` test which previously
failed when using `TestChainSource::Bitcoind`.
Signed-off-by: Elias Rohrer <dev@tnull.de>
Previously, we'd update the payment store after persisting the wallet in
some cases. This was fine as long as we iterated all wallet transactions
anyways (hence idempotent). However, now that we use the event-based
flow we should persist the payment store(s) first, so that wallet events
get replayed if there was a crash in-between some of the persistence
operations.
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Really don't fully grok all the BDK parts, but from what I can tell this looks reasonable. Feel free to squash + land.

Squashed fixups without further changes.

@tnull
tnull merged commit b55de44 into lightningdevkit:mainFeb 16, 2026
23 of 28 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsFeb 16, 2026
@joostjager

Copy link
Copy Markdown
Contributor

For the record, as I've also noted in other PRs that randomize tests: I don't think this is a good approach. A more extensive test suite that runs on main or as a nightly job, iterating through all backends, would be a far superior solution in my opinion.

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

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@tnull@ldk-reviews-bot@joostjager@TheBlueMatt
, '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('^' + ".*" + ' Randomize chain source selection in tests by tnull · Pull Request #769 · lightningdevkit/ldk-node · GitHub
Skip to content

Randomize chain source selection in tests - #769

Merged
tnull merged 6 commits into
lightningdevkit:mainfrom
tnull:2026-01-test-setup
Feb 16, 2026
Merged

Randomize chain source selection in tests#769
tnull merged 6 commits into
lightningdevkit:mainfrom
tnull:2026-01-test-setup

Conversation

@tnull

Copy link
Copy Markdown
Collaborator

.. all of our tests should be robust against switching chain sources. We
here opt to pick a random one each time to considerably extend our test
coverage, instead of just running some cases against non-Esplora chain
sources.

@ldk-reviews-bot

ldk-reviews-bot commented Jan 23, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt 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.

@tnull

Copy link
Copy Markdown
CollaboratorAuthor

This will be flaky, at the very least until lightningdevkit/rust-lightning#4341 gets release, which would have been caught if we'd ever had run the 0conf test case with an Electrum chain source.

TheBlueMatt
TheBlueMatt previously approved these changes Jan 23, 2026

@TheBlueMattTheBlueMatt 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.

makes sense to me. Hopefully CI fails until lightningdevkit/rust-lightning#4341

@tnull

Copy link
Copy Markdown
CollaboratorAuthor

makes sense to me. Hopefully CI fails until lightningdevkit/rust-lightning#4341

I think I'll whack-a-mole a few more flakes/bugs before landing this.

@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Previously

 simple_bolt12_send_receive (bitcoind RPC)
test_node_announcement_propagation (bitcoind RPC)

had failed, I want to double-check them once more before moving forward here.

@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Pushed an update, but currently onchain_send_receive is still (or newly?) broken when syncing with bitcoind. Will look into that.

@tnull
tnull marked this pull request as draft January 29, 2026 19:12
@tnull
tnullforce-pushed the 2026-01-test-setup branch from b8179ca to 282605bCompareJanuary 29, 2026 19:12
It's weird to have a special intermediary `setup_node` method if we have
`TestConfig` for exactly that reason by now. So we move
`async_payment_role` over.
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Pushed an update, but currently onchain_send_receive is still (or newly?) broken when syncing with bitcoind. Will look into that.

Alright, fixed that one, too and rebased. Should be ready for review now.

@tnull
tnull marked this pull request as ready for review February 12, 2026 10:10
.. all of our tests should be robust against switching chain sources. We
here opt to pick a random one each time to considerably extend our test
coverage, instead of just running some cases against non-Esplora chain
sources.
Signed-off-by: Elias Rohrer <dev@tnull.de>

@TheBlueMattTheBlueMatt 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.

I don't really know BDK well enough to feel like I can meaningfully review this, but a few questions.

Comment threadsrc/wallet/mod.rs

// FIXME/TODO: This is copied-over from bdk_wallet and only used to generate `WalletEvent`s after
// applying mempool transactions. We should drop this when BDK offers to generate events for
// mempool transactions natively.

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.

Hmm, if BDK doesn't support this natively, do they have logic to correctly handle conflicts and build balance knowledge appropriately? ie if there's an RBF that conflicts do we remove the 0conf tx from our balance?

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.

Yes, they support all of the canonical transaction handling, it's just when they recently introduced the WalletEvent, they didn't update all APIs we require to return them. Arguably, they should just expose the helper we here copied over which would avoid them having to provide _event variants for all API endpoints individually. See also the discussion on bitcoindevkit/bdk_wallet#374

Comment threadsrc/builder.rs
///
/// This should only be set on first startup when importing an older wallet from a previously
/// used [`NodeEntropy`].
pub fn set_wallet_recovery_mode(&mut self) -> &mut Self {

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.

Rather than a coarse "recovery mode" can we just enable setting the "wallet birthday"? In some setups that might be available and would avoid a lot of effort.

@tnulltnullFeb 12, 2026

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.

Hmm, wallet birthday support is still inflight on the BDK side (see bitcoindevkit/bdk_wallet#368) and I'd like to punt on it until we can actually make use of that and remove our hacky birthday logic.

That said, if you prefer we could of course rename the current "recovery_mode" API to "disable_wallet_birthday" for now which ~does the same thing?

@tnulltnull moved this to Goal: Merge in Weekly GoalsFeb 12, 2026
@tnulltnull self-assigned this Feb 12, 2026
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Amended the last commit to include a comment explaining what we do/why we do it:

diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs
index 835e1b31..2decf981 100644
--- a/src/wallet/mod.rs+++ b/src/wallet/mod.rs@@ -1031,4 +1031,12 @@ impl Listen for Wallet {
}
+ // In order to be able to reliably calculate fees the `Wallet` needs access to the previous+ // ouput data. To this end, we here insert any ouputs of transactions that LDK is intersted+ // in (e.g., funding transaction ouputs) into the wallet's transaction graph when we see+ // them, so it is reliably able to calculate fees for subsequent spends.+ //+ // FIXME: technically, we should also do this for mempool transactions. However, at the+ // current time fixing the edge case doesn't seem worth the additional conplexity /+ // additional overhead..
let registered_txids = self.chain_source.registered_txids();
for tx in &block.txdata {

Comment threadsrc/wallet/mod.rs
Error::PersistenceFailed
})?;

self.update_payment_store(&mut *locked_wallet, events).map_err(|e| {

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.

Should this happen before locked_wallet.persist()? What happens if we crash between the wallet persist and the payment store update (presumably the second is idempotent so we should do it first to reply on restart if needed?)?

@tnulltnullFeb 16, 2026

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.

Also a good point. I think the current approach worked fine when we still iterated on the wallet txs, but now with the events we should consistently update first to ensure events are replayed. Now added a fixup and another commit fixing it for apply_update.

Comment threadsrc/wallet/mod.rs
&self, unconfirmed_txs: Vec<(Transaction, u64)>, evicted_txids: Vec<(Txid, u64)>,
) -> Result<(), Error> {
if unconfirmed_txs.is_empty() {
return 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.

This doesn't appear to be tested. Also not clear to me why we'd want to skip this if we don't have any unconfirmed txs in the mempool but do have some txs that got evicted (iiuc).

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.

Hmm, good point, we should only skip if there's nothing to update. Added a fixup

@TheBlueMattTheBlueMatt 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.

Really don't fully grok all the BDK parts, but from what I can tell this looks reasonable. Feel free to squash + land.

When we intially implemented `bitcoind` syncing polling the mempool was
very frequent and rather inefficient so we made a choice not to
unnecessarily update the payment store for mempool changes, especially
since we only consider transactions `Succeeded` after
`ANTI_REORG_DELAY` anyways.
However, since then we made quite a few peformance improvements to the
mempool syncing, and by now we should just update they payment store as
not doing so will lead to rather unexpected behavior, making some tests
fail for `TestChainSource::Bitcoind`, e.g., `channel_full_cycle_0conf`,
which we fix here.
As we recently switched to updating the payment store based on BDK's
`WalletEvent`, but they currently don't offer an API returning such
events when applying mempool transactions, we copy over the respective
method for generating events from `bdk_wallet`, with the intention of
dropping it again once they do.
Signed-off-by: Elias Rohrer <dev@tnull.de>
Previously, we fixed than a fresh node syncing via `bitcoind` RPC would
resync all chain data back to genesis. However, while introducing a
wallet birthday is great, it disallowed discovery of historical funds if
a wallet would be imported from seed. Here, we add a recovery mode flag
to the builder that explictly allows to re-enable resyncing from genesis
in such a scenario. Going forward, we intend to reuse that API for an
upcoming Lightning recoery flow, too.
Previously, we'd selectively insert the funding outputs into the onchain
wallet to later allow calculating `fees_paid` when creating payment
store entries (only for splicing mostly). However, this didn't always work, and we might for
example end up with a missing funding output (and hence would fall back
to `fees_paid: Some(0)`) if it was a counterparty-initiated channel and
we synced via `bitcoind` RPC.
Here, we fix this by tracking all LDK-registered `txids` in
`ChainSource` and then in the `Wallet`'s `Listen` implementation insert
all outputs of all registered transactions into the `Wallet`, ensuring
we'd always have sufficient data for `calculate_fee` available.
Thereby we also fix the `onchain_send_receive` test which previously
failed when using `TestChainSource::Bitcoind`.
Signed-off-by: Elias Rohrer <dev@tnull.de>
Previously, we'd update the payment store after persisting the wallet in
some cases. This was fine as long as we iterated all wallet transactions
anyways (hence idempotent). However, now that we use the event-based
flow we should persist the payment store(s) first, so that wallet events
get replayed if there was a crash in-between some of the persistence
operations.
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Really don't fully grok all the BDK parts, but from what I can tell this looks reasonable. Feel free to squash + land.

Squashed fixups without further changes.

@tnull
tnull merged commit b55de44 into lightningdevkit:mainFeb 16, 2026
23 of 28 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsFeb 16, 2026
@joostjager

Copy link
Copy Markdown
Contributor

For the record, as I've also noted in other PRs that randomize tests: I don't think this is a good approach. A more extensive test suite that runs on main or as a nightly job, iterating through all backends, would be a far superior solution in my opinion.

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

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@tnull@ldk-reviews-bot@joostjager@TheBlueMatt
, '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('^' + ".*" + ' Randomize chain source selection in tests by tnull · Pull Request #769 · lightningdevkit/ldk-node · GitHub
Skip to content

Randomize chain source selection in tests - #769

Merged
tnull merged 6 commits into
lightningdevkit:mainfrom
tnull:2026-01-test-setup
Feb 16, 2026
Merged

Randomize chain source selection in tests#769
tnull merged 6 commits into
lightningdevkit:mainfrom
tnull:2026-01-test-setup

Conversation

@tnull

Copy link
Copy Markdown
Collaborator

.. all of our tests should be robust against switching chain sources. We
here opt to pick a random one each time to considerably extend our test
coverage, instead of just running some cases against non-Esplora chain
sources.

@ldk-reviews-bot

ldk-reviews-bot commented Jan 23, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt 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.

@tnull

Copy link
Copy Markdown
CollaboratorAuthor

This will be flaky, at the very least until lightningdevkit/rust-lightning#4341 gets release, which would have been caught if we'd ever had run the 0conf test case with an Electrum chain source.

TheBlueMatt
TheBlueMatt previously approved these changes Jan 23, 2026

@TheBlueMattTheBlueMatt 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.

makes sense to me. Hopefully CI fails until lightningdevkit/rust-lightning#4341

@tnull

Copy link
Copy Markdown
CollaboratorAuthor

makes sense to me. Hopefully CI fails until lightningdevkit/rust-lightning#4341

I think I'll whack-a-mole a few more flakes/bugs before landing this.

@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Previously

 simple_bolt12_send_receive (bitcoind RPC)
test_node_announcement_propagation (bitcoind RPC)

had failed, I want to double-check them once more before moving forward here.

@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Pushed an update, but currently onchain_send_receive is still (or newly?) broken when syncing with bitcoind. Will look into that.

@tnull
tnull marked this pull request as draft January 29, 2026 19:12
@tnull
tnullforce-pushed the 2026-01-test-setup branch from b8179ca to 282605bCompareJanuary 29, 2026 19:12
It's weird to have a special intermediary `setup_node` method if we have
`TestConfig` for exactly that reason by now. So we move
`async_payment_role` over.
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Pushed an update, but currently onchain_send_receive is still (or newly?) broken when syncing with bitcoind. Will look into that.

Alright, fixed that one, too and rebased. Should be ready for review now.

@tnull
tnull marked this pull request as ready for review February 12, 2026 10:10
.. all of our tests should be robust against switching chain sources. We
here opt to pick a random one each time to considerably extend our test
coverage, instead of just running some cases against non-Esplora chain
sources.
Signed-off-by: Elias Rohrer <dev@tnull.de>

@TheBlueMattTheBlueMatt 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.

I don't really know BDK well enough to feel like I can meaningfully review this, but a few questions.

Comment threadsrc/wallet/mod.rs

// FIXME/TODO: This is copied-over from bdk_wallet and only used to generate `WalletEvent`s after
// applying mempool transactions. We should drop this when BDK offers to generate events for
// mempool transactions natively.

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.

Hmm, if BDK doesn't support this natively, do they have logic to correctly handle conflicts and build balance knowledge appropriately? ie if there's an RBF that conflicts do we remove the 0conf tx from our balance?

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.

Yes, they support all of the canonical transaction handling, it's just when they recently introduced the WalletEvent, they didn't update all APIs we require to return them. Arguably, they should just expose the helper we here copied over which would avoid them having to provide _event variants for all API endpoints individually. See also the discussion on bitcoindevkit/bdk_wallet#374

Comment threadsrc/builder.rs
///
/// This should only be set on first startup when importing an older wallet from a previously
/// used [`NodeEntropy`].
pub fn set_wallet_recovery_mode(&mut self) -> &mut Self {

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.

Rather than a coarse "recovery mode" can we just enable setting the "wallet birthday"? In some setups that might be available and would avoid a lot of effort.

@tnulltnullFeb 12, 2026

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.

Hmm, wallet birthday support is still inflight on the BDK side (see bitcoindevkit/bdk_wallet#368) and I'd like to punt on it until we can actually make use of that and remove our hacky birthday logic.

That said, if you prefer we could of course rename the current "recovery_mode" API to "disable_wallet_birthday" for now which ~does the same thing?

@tnulltnull moved this to Goal: Merge in Weekly GoalsFeb 12, 2026
@tnulltnull self-assigned this Feb 12, 2026
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Amended the last commit to include a comment explaining what we do/why we do it:

diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs
index 835e1b31..2decf981 100644
--- a/src/wallet/mod.rs+++ b/src/wallet/mod.rs@@ -1031,4 +1031,12 @@ impl Listen for Wallet {
}
+ // In order to be able to reliably calculate fees the `Wallet` needs access to the previous+ // ouput data. To this end, we here insert any ouputs of transactions that LDK is intersted+ // in (e.g., funding transaction ouputs) into the wallet's transaction graph when we see+ // them, so it is reliably able to calculate fees for subsequent spends.+ //+ // FIXME: technically, we should also do this for mempool transactions. However, at the+ // current time fixing the edge case doesn't seem worth the additional conplexity /+ // additional overhead..
let registered_txids = self.chain_source.registered_txids();
for tx in &block.txdata {

Comment threadsrc/wallet/mod.rs
Error::PersistenceFailed
})?;

self.update_payment_store(&mut *locked_wallet, events).map_err(|e| {

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.

Should this happen before locked_wallet.persist()? What happens if we crash between the wallet persist and the payment store update (presumably the second is idempotent so we should do it first to reply on restart if needed?)?

@tnulltnullFeb 16, 2026

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.

Also a good point. I think the current approach worked fine when we still iterated on the wallet txs, but now with the events we should consistently update first to ensure events are replayed. Now added a fixup and another commit fixing it for apply_update.

Comment threadsrc/wallet/mod.rs
&self, unconfirmed_txs: Vec<(Transaction, u64)>, evicted_txids: Vec<(Txid, u64)>,
) -> Result<(), Error> {
if unconfirmed_txs.is_empty() {
return 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.

This doesn't appear to be tested. Also not clear to me why we'd want to skip this if we don't have any unconfirmed txs in the mempool but do have some txs that got evicted (iiuc).

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.

Hmm, good point, we should only skip if there's nothing to update. Added a fixup

@TheBlueMattTheBlueMatt 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.

Really don't fully grok all the BDK parts, but from what I can tell this looks reasonable. Feel free to squash + land.

When we intially implemented `bitcoind` syncing polling the mempool was
very frequent and rather inefficient so we made a choice not to
unnecessarily update the payment store for mempool changes, especially
since we only consider transactions `Succeeded` after
`ANTI_REORG_DELAY` anyways.
However, since then we made quite a few peformance improvements to the
mempool syncing, and by now we should just update they payment store as
not doing so will lead to rather unexpected behavior, making some tests
fail for `TestChainSource::Bitcoind`, e.g., `channel_full_cycle_0conf`,
which we fix here.
As we recently switched to updating the payment store based on BDK's
`WalletEvent`, but they currently don't offer an API returning such
events when applying mempool transactions, we copy over the respective
method for generating events from `bdk_wallet`, with the intention of
dropping it again once they do.
Signed-off-by: Elias Rohrer <dev@tnull.de>
Previously, we fixed than a fresh node syncing via `bitcoind` RPC would
resync all chain data back to genesis. However, while introducing a
wallet birthday is great, it disallowed discovery of historical funds if
a wallet would be imported from seed. Here, we add a recovery mode flag
to the builder that explictly allows to re-enable resyncing from genesis
in such a scenario. Going forward, we intend to reuse that API for an
upcoming Lightning recoery flow, too.
Previously, we'd selectively insert the funding outputs into the onchain
wallet to later allow calculating `fees_paid` when creating payment
store entries (only for splicing mostly). However, this didn't always work, and we might for
example end up with a missing funding output (and hence would fall back
to `fees_paid: Some(0)`) if it was a counterparty-initiated channel and
we synced via `bitcoind` RPC.
Here, we fix this by tracking all LDK-registered `txids` in
`ChainSource` and then in the `Wallet`'s `Listen` implementation insert
all outputs of all registered transactions into the `Wallet`, ensuring
we'd always have sufficient data for `calculate_fee` available.
Thereby we also fix the `onchain_send_receive` test which previously
failed when using `TestChainSource::Bitcoind`.
Signed-off-by: Elias Rohrer <dev@tnull.de>
Previously, we'd update the payment store after persisting the wallet in
some cases. This was fine as long as we iterated all wallet transactions
anyways (hence idempotent). However, now that we use the event-based
flow we should persist the payment store(s) first, so that wallet events
get replayed if there was a crash in-between some of the persistence
operations.
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Really don't fully grok all the BDK parts, but from what I can tell this looks reasonable. Feel free to squash + land.

Squashed fixups without further changes.

@tnull
tnull merged commit b55de44 into lightningdevkit:mainFeb 16, 2026
23 of 28 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsFeb 16, 2026
@joostjager

Copy link
Copy Markdown
Contributor

For the record, as I've also noted in other PRs that randomize tests: I don't think this is a good approach. A more extensive test suite that runs on main or as a nightly job, iterating through all backends, would be a far superior solution in my opinion.

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

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@tnull@ldk-reviews-bot@joostjager@TheBlueMatt
, '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" + ' Randomize chain source selection in tests by tnull · Pull Request #769 · lightningdevkit/ldk-node · GitHub
Skip to content

Randomize chain source selection in tests - #769

Merged
tnull merged 6 commits into
lightningdevkit:mainfrom
tnull:2026-01-test-setup
Feb 16, 2026
Merged

Randomize chain source selection in tests#769
tnull merged 6 commits into
lightningdevkit:mainfrom
tnull:2026-01-test-setup

Conversation

@tnull

Copy link
Copy Markdown
Collaborator

.. all of our tests should be robust against switching chain sources. We
here opt to pick a random one each time to considerably extend our test
coverage, instead of just running some cases against non-Esplora chain
sources.

@ldk-reviews-bot

ldk-reviews-bot commented Jan 23, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt 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.

@tnull

Copy link
Copy Markdown
CollaboratorAuthor

This will be flaky, at the very least until lightningdevkit/rust-lightning#4341 gets release, which would have been caught if we'd ever had run the 0conf test case with an Electrum chain source.

TheBlueMatt
TheBlueMatt previously approved these changes Jan 23, 2026

@TheBlueMattTheBlueMatt 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.

makes sense to me. Hopefully CI fails until lightningdevkit/rust-lightning#4341

@tnull

Copy link
Copy Markdown
CollaboratorAuthor

makes sense to me. Hopefully CI fails until lightningdevkit/rust-lightning#4341

I think I'll whack-a-mole a few more flakes/bugs before landing this.

@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Previously

 simple_bolt12_send_receive (bitcoind RPC)
test_node_announcement_propagation (bitcoind RPC)

had failed, I want to double-check them once more before moving forward here.

@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Pushed an update, but currently onchain_send_receive is still (or newly?) broken when syncing with bitcoind. Will look into that.

@tnull
tnull marked this pull request as draft January 29, 2026 19:12
@tnull
tnullforce-pushed the 2026-01-test-setup branch from b8179ca to 282605bCompareJanuary 29, 2026 19:12
It's weird to have a special intermediary `setup_node` method if we have
`TestConfig` for exactly that reason by now. So we move
`async_payment_role` over.
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Pushed an update, but currently onchain_send_receive is still (or newly?) broken when syncing with bitcoind. Will look into that.

Alright, fixed that one, too and rebased. Should be ready for review now.

@tnull
tnull marked this pull request as ready for review February 12, 2026 10:10
.. all of our tests should be robust against switching chain sources. We
here opt to pick a random one each time to considerably extend our test
coverage, instead of just running some cases against non-Esplora chain
sources.
Signed-off-by: Elias Rohrer <dev@tnull.de>

@TheBlueMattTheBlueMatt 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.

I don't really know BDK well enough to feel like I can meaningfully review this, but a few questions.

Comment threadsrc/wallet/mod.rs

// FIXME/TODO: This is copied-over from bdk_wallet and only used to generate `WalletEvent`s after
// applying mempool transactions. We should drop this when BDK offers to generate events for
// mempool transactions natively.

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.

Hmm, if BDK doesn't support this natively, do they have logic to correctly handle conflicts and build balance knowledge appropriately? ie if there's an RBF that conflicts do we remove the 0conf tx from our balance?

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.

Yes, they support all of the canonical transaction handling, it's just when they recently introduced the WalletEvent, they didn't update all APIs we require to return them. Arguably, they should just expose the helper we here copied over which would avoid them having to provide _event variants for all API endpoints individually. See also the discussion on bitcoindevkit/bdk_wallet#374

Comment threadsrc/builder.rs
///
/// This should only be set on first startup when importing an older wallet from a previously
/// used [`NodeEntropy`].
pub fn set_wallet_recovery_mode(&mut self) -> &mut Self {

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.

Rather than a coarse "recovery mode" can we just enable setting the "wallet birthday"? In some setups that might be available and would avoid a lot of effort.

@tnulltnullFeb 12, 2026

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.

Hmm, wallet birthday support is still inflight on the BDK side (see bitcoindevkit/bdk_wallet#368) and I'd like to punt on it until we can actually make use of that and remove our hacky birthday logic.

That said, if you prefer we could of course rename the current "recovery_mode" API to "disable_wallet_birthday" for now which ~does the same thing?

@tnulltnull moved this to Goal: Merge in Weekly GoalsFeb 12, 2026
@tnulltnull self-assigned this Feb 12, 2026
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Amended the last commit to include a comment explaining what we do/why we do it:

diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs
index 835e1b31..2decf981 100644
--- a/src/wallet/mod.rs+++ b/src/wallet/mod.rs@@ -1031,4 +1031,12 @@ impl Listen for Wallet {
}
+ // In order to be able to reliably calculate fees the `Wallet` needs access to the previous+ // ouput data. To this end, we here insert any ouputs of transactions that LDK is intersted+ // in (e.g., funding transaction ouputs) into the wallet's transaction graph when we see+ // them, so it is reliably able to calculate fees for subsequent spends.+ //+ // FIXME: technically, we should also do this for mempool transactions. However, at the+ // current time fixing the edge case doesn't seem worth the additional conplexity /+ // additional overhead..
let registered_txids = self.chain_source.registered_txids();
for tx in &block.txdata {

Comment threadsrc/wallet/mod.rs
Error::PersistenceFailed
})?;

self.update_payment_store(&mut *locked_wallet, events).map_err(|e| {

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.

Should this happen before locked_wallet.persist()? What happens if we crash between the wallet persist and the payment store update (presumably the second is idempotent so we should do it first to reply on restart if needed?)?

@tnulltnullFeb 16, 2026

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.

Also a good point. I think the current approach worked fine when we still iterated on the wallet txs, but now with the events we should consistently update first to ensure events are replayed. Now added a fixup and another commit fixing it for apply_update.

Comment threadsrc/wallet/mod.rs
&self, unconfirmed_txs: Vec<(Transaction, u64)>, evicted_txids: Vec<(Txid, u64)>,
) -> Result<(), Error> {
if unconfirmed_txs.is_empty() {
return 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.

This doesn't appear to be tested. Also not clear to me why we'd want to skip this if we don't have any unconfirmed txs in the mempool but do have some txs that got evicted (iiuc).

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.

Hmm, good point, we should only skip if there's nothing to update. Added a fixup

@TheBlueMattTheBlueMatt 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.

Really don't fully grok all the BDK parts, but from what I can tell this looks reasonable. Feel free to squash + land.

When we intially implemented `bitcoind` syncing polling the mempool was
very frequent and rather inefficient so we made a choice not to
unnecessarily update the payment store for mempool changes, especially
since we only consider transactions `Succeeded` after
`ANTI_REORG_DELAY` anyways.
However, since then we made quite a few peformance improvements to the
mempool syncing, and by now we should just update they payment store as
not doing so will lead to rather unexpected behavior, making some tests
fail for `TestChainSource::Bitcoind`, e.g., `channel_full_cycle_0conf`,
which we fix here.
As we recently switched to updating the payment store based on BDK's
`WalletEvent`, but they currently don't offer an API returning such
events when applying mempool transactions, we copy over the respective
method for generating events from `bdk_wallet`, with the intention of
dropping it again once they do.
Signed-off-by: Elias Rohrer <dev@tnull.de>
Previously, we fixed than a fresh node syncing via `bitcoind` RPC would
resync all chain data back to genesis. However, while introducing a
wallet birthday is great, it disallowed discovery of historical funds if
a wallet would be imported from seed. Here, we add a recovery mode flag
to the builder that explictly allows to re-enable resyncing from genesis
in such a scenario. Going forward, we intend to reuse that API for an
upcoming Lightning recoery flow, too.
Previously, we'd selectively insert the funding outputs into the onchain
wallet to later allow calculating `fees_paid` when creating payment
store entries (only for splicing mostly). However, this didn't always work, and we might for
example end up with a missing funding output (and hence would fall back
to `fees_paid: Some(0)`) if it was a counterparty-initiated channel and
we synced via `bitcoind` RPC.
Here, we fix this by tracking all LDK-registered `txids` in
`ChainSource` and then in the `Wallet`'s `Listen` implementation insert
all outputs of all registered transactions into the `Wallet`, ensuring
we'd always have sufficient data for `calculate_fee` available.
Thereby we also fix the `onchain_send_receive` test which previously
failed when using `TestChainSource::Bitcoind`.
Signed-off-by: Elias Rohrer <dev@tnull.de>
Previously, we'd update the payment store after persisting the wallet in
some cases. This was fine as long as we iterated all wallet transactions
anyways (hence idempotent). However, now that we use the event-based
flow we should persist the payment store(s) first, so that wallet events
get replayed if there was a crash in-between some of the persistence
operations.
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Really don't fully grok all the BDK parts, but from what I can tell this looks reasonable. Feel free to squash + land.

Squashed fixups without further changes.

@tnull
tnull merged commit b55de44 into lightningdevkit:mainFeb 16, 2026
23 of 28 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsFeb 16, 2026
@joostjager

Copy link
Copy Markdown
Contributor

For the record, as I've also noted in other PRs that randomize tests: I don't think this is a good approach. A more extensive test suite that runs on main or as a nightly job, iterating through all backends, would be a far superior solution in my opinion.

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

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@tnull@ldk-reviews-bot@joostjager@TheBlueMatt
, '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('^' + ".*" + ' Randomize chain source selection in tests by tnull · Pull Request #769 · lightningdevkit/ldk-node · GitHub
Skip to content

Randomize chain source selection in tests - #769

Merged
tnull merged 6 commits into
lightningdevkit:mainfrom
tnull:2026-01-test-setup
Feb 16, 2026
Merged

Randomize chain source selection in tests#769
tnull merged 6 commits into
lightningdevkit:mainfrom
tnull:2026-01-test-setup

Conversation

@tnull

Copy link
Copy Markdown
Collaborator

.. all of our tests should be robust against switching chain sources. We
here opt to pick a random one each time to considerably extend our test
coverage, instead of just running some cases against non-Esplora chain
sources.

@ldk-reviews-bot

ldk-reviews-bot commented Jan 23, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt 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.

@tnull

Copy link
Copy Markdown
CollaboratorAuthor

This will be flaky, at the very least until lightningdevkit/rust-lightning#4341 gets release, which would have been caught if we'd ever had run the 0conf test case with an Electrum chain source.

TheBlueMatt
TheBlueMatt previously approved these changes Jan 23, 2026

@TheBlueMattTheBlueMatt 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.

makes sense to me. Hopefully CI fails until lightningdevkit/rust-lightning#4341

@tnull

Copy link
Copy Markdown
CollaboratorAuthor

makes sense to me. Hopefully CI fails until lightningdevkit/rust-lightning#4341

I think I'll whack-a-mole a few more flakes/bugs before landing this.

@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Previously

 simple_bolt12_send_receive (bitcoind RPC)
test_node_announcement_propagation (bitcoind RPC)

had failed, I want to double-check them once more before moving forward here.

@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Pushed an update, but currently onchain_send_receive is still (or newly?) broken when syncing with bitcoind. Will look into that.

@tnull
tnull marked this pull request as draft January 29, 2026 19:12
@tnull
tnullforce-pushed the 2026-01-test-setup branch from b8179ca to 282605bCompareJanuary 29, 2026 19:12
It's weird to have a special intermediary `setup_node` method if we have
`TestConfig` for exactly that reason by now. So we move
`async_payment_role` over.
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Pushed an update, but currently onchain_send_receive is still (or newly?) broken when syncing with bitcoind. Will look into that.

Alright, fixed that one, too and rebased. Should be ready for review now.

@tnull
tnull marked this pull request as ready for review February 12, 2026 10:10
.. all of our tests should be robust against switching chain sources. We
here opt to pick a random one each time to considerably extend our test
coverage, instead of just running some cases against non-Esplora chain
sources.
Signed-off-by: Elias Rohrer <dev@tnull.de>

@TheBlueMattTheBlueMatt 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.

I don't really know BDK well enough to feel like I can meaningfully review this, but a few questions.

Comment threadsrc/wallet/mod.rs

// FIXME/TODO: This is copied-over from bdk_wallet and only used to generate `WalletEvent`s after
// applying mempool transactions. We should drop this when BDK offers to generate events for
// mempool transactions natively.

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.

Hmm, if BDK doesn't support this natively, do they have logic to correctly handle conflicts and build balance knowledge appropriately? ie if there's an RBF that conflicts do we remove the 0conf tx from our balance?

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.

Yes, they support all of the canonical transaction handling, it's just when they recently introduced the WalletEvent, they didn't update all APIs we require to return them. Arguably, they should just expose the helper we here copied over which would avoid them having to provide _event variants for all API endpoints individually. See also the discussion on bitcoindevkit/bdk_wallet#374

Comment threadsrc/builder.rs
///
/// This should only be set on first startup when importing an older wallet from a previously
/// used [`NodeEntropy`].
pub fn set_wallet_recovery_mode(&mut self) -> &mut Self {

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.

Rather than a coarse "recovery mode" can we just enable setting the "wallet birthday"? In some setups that might be available and would avoid a lot of effort.

@tnulltnullFeb 12, 2026

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.

Hmm, wallet birthday support is still inflight on the BDK side (see bitcoindevkit/bdk_wallet#368) and I'd like to punt on it until we can actually make use of that and remove our hacky birthday logic.

That said, if you prefer we could of course rename the current "recovery_mode" API to "disable_wallet_birthday" for now which ~does the same thing?

@tnulltnull moved this to Goal: Merge in Weekly GoalsFeb 12, 2026
@tnulltnull self-assigned this Feb 12, 2026
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Amended the last commit to include a comment explaining what we do/why we do it:

diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs
index 835e1b31..2decf981 100644
--- a/src/wallet/mod.rs+++ b/src/wallet/mod.rs@@ -1031,4 +1031,12 @@ impl Listen for Wallet {
}
+ // In order to be able to reliably calculate fees the `Wallet` needs access to the previous+ // ouput data. To this end, we here insert any ouputs of transactions that LDK is intersted+ // in (e.g., funding transaction ouputs) into the wallet's transaction graph when we see+ // them, so it is reliably able to calculate fees for subsequent spends.+ //+ // FIXME: technically, we should also do this for mempool transactions. However, at the+ // current time fixing the edge case doesn't seem worth the additional conplexity /+ // additional overhead..
let registered_txids = self.chain_source.registered_txids();
for tx in &block.txdata {

Comment threadsrc/wallet/mod.rs
Error::PersistenceFailed
})?;

self.update_payment_store(&mut *locked_wallet, events).map_err(|e| {

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.

Should this happen before locked_wallet.persist()? What happens if we crash between the wallet persist and the payment store update (presumably the second is idempotent so we should do it first to reply on restart if needed?)?

@tnulltnullFeb 16, 2026

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.

Also a good point. I think the current approach worked fine when we still iterated on the wallet txs, but now with the events we should consistently update first to ensure events are replayed. Now added a fixup and another commit fixing it for apply_update.

Comment threadsrc/wallet/mod.rs
&self, unconfirmed_txs: Vec<(Transaction, u64)>, evicted_txids: Vec<(Txid, u64)>,
) -> Result<(), Error> {
if unconfirmed_txs.is_empty() {
return 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.

This doesn't appear to be tested. Also not clear to me why we'd want to skip this if we don't have any unconfirmed txs in the mempool but do have some txs that got evicted (iiuc).

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.

Hmm, good point, we should only skip if there's nothing to update. Added a fixup

@TheBlueMattTheBlueMatt 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.

Really don't fully grok all the BDK parts, but from what I can tell this looks reasonable. Feel free to squash + land.

When we intially implemented `bitcoind` syncing polling the mempool was
very frequent and rather inefficient so we made a choice not to
unnecessarily update the payment store for mempool changes, especially
since we only consider transactions `Succeeded` after
`ANTI_REORG_DELAY` anyways.
However, since then we made quite a few peformance improvements to the
mempool syncing, and by now we should just update they payment store as
not doing so will lead to rather unexpected behavior, making some tests
fail for `TestChainSource::Bitcoind`, e.g., `channel_full_cycle_0conf`,
which we fix here.
As we recently switched to updating the payment store based on BDK's
`WalletEvent`, but they currently don't offer an API returning such
events when applying mempool transactions, we copy over the respective
method for generating events from `bdk_wallet`, with the intention of
dropping it again once they do.
Signed-off-by: Elias Rohrer <dev@tnull.de>
Previously, we fixed than a fresh node syncing via `bitcoind` RPC would
resync all chain data back to genesis. However, while introducing a
wallet birthday is great, it disallowed discovery of historical funds if
a wallet would be imported from seed. Here, we add a recovery mode flag
to the builder that explictly allows to re-enable resyncing from genesis
in such a scenario. Going forward, we intend to reuse that API for an
upcoming Lightning recoery flow, too.
Previously, we'd selectively insert the funding outputs into the onchain
wallet to later allow calculating `fees_paid` when creating payment
store entries (only for splicing mostly). However, this didn't always work, and we might for
example end up with a missing funding output (and hence would fall back
to `fees_paid: Some(0)`) if it was a counterparty-initiated channel and
we synced via `bitcoind` RPC.
Here, we fix this by tracking all LDK-registered `txids` in
`ChainSource` and then in the `Wallet`'s `Listen` implementation insert
all outputs of all registered transactions into the `Wallet`, ensuring
we'd always have sufficient data for `calculate_fee` available.
Thereby we also fix the `onchain_send_receive` test which previously
failed when using `TestChainSource::Bitcoind`.
Signed-off-by: Elias Rohrer <dev@tnull.de>
Previously, we'd update the payment store after persisting the wallet in
some cases. This was fine as long as we iterated all wallet transactions
anyways (hence idempotent). However, now that we use the event-based
flow we should persist the payment store(s) first, so that wallet events
get replayed if there was a crash in-between some of the persistence
operations.
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Really don't fully grok all the BDK parts, but from what I can tell this looks reasonable. Feel free to squash + land.

Squashed fixups without further changes.

@tnull
tnull merged commit b55de44 into lightningdevkit:mainFeb 16, 2026
23 of 28 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsFeb 16, 2026
@joostjager

Copy link
Copy Markdown
Contributor

For the record, as I've also noted in other PRs that randomize tests: I don't think this is a good approach. A more extensive test suite that runs on main or as a nightly job, iterating through all backends, would be a far superior solution in my opinion.

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

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@tnull@ldk-reviews-bot@joostjager@TheBlueMatt
, '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); } })(); })(); Randomize chain source selection in tests by tnull · Pull Request #769 · lightningdevkit/ldk-node · GitHub
Skip to content

Randomize chain source selection in tests - #769

Merged
tnull merged 6 commits into
lightningdevkit:mainfrom
tnull:2026-01-test-setup
Feb 16, 2026
Merged

Randomize chain source selection in tests#769
tnull merged 6 commits into
lightningdevkit:mainfrom
tnull:2026-01-test-setup

Conversation

@tnull

Copy link
Copy Markdown
Collaborator

.. all of our tests should be robust against switching chain sources. We
here opt to pick a random one each time to considerably extend our test
coverage, instead of just running some cases against non-Esplora chain
sources.

@ldk-reviews-bot

ldk-reviews-bot commented Jan 23, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt 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.

@tnull

Copy link
Copy Markdown
CollaboratorAuthor

This will be flaky, at the very least until lightningdevkit/rust-lightning#4341 gets release, which would have been caught if we'd ever had run the 0conf test case with an Electrum chain source.

TheBlueMatt
TheBlueMatt previously approved these changes Jan 23, 2026

@TheBlueMattTheBlueMatt 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.

makes sense to me. Hopefully CI fails until lightningdevkit/rust-lightning#4341

@tnull

Copy link
Copy Markdown
CollaboratorAuthor

makes sense to me. Hopefully CI fails until lightningdevkit/rust-lightning#4341

I think I'll whack-a-mole a few more flakes/bugs before landing this.

@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Previously

 simple_bolt12_send_receive (bitcoind RPC)
test_node_announcement_propagation (bitcoind RPC)

had failed, I want to double-check them once more before moving forward here.

@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Pushed an update, but currently onchain_send_receive is still (or newly?) broken when syncing with bitcoind. Will look into that.

@tnull
tnull marked this pull request as draft January 29, 2026 19:12
@tnull
tnullforce-pushed the 2026-01-test-setup branch from b8179ca to 282605bCompareJanuary 29, 2026 19:12
It's weird to have a special intermediary `setup_node` method if we have
`TestConfig` for exactly that reason by now. So we move
`async_payment_role` over.
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Pushed an update, but currently onchain_send_receive is still (or newly?) broken when syncing with bitcoind. Will look into that.

Alright, fixed that one, too and rebased. Should be ready for review now.

@tnull
tnull marked this pull request as ready for review February 12, 2026 10:10
.. all of our tests should be robust against switching chain sources. We
here opt to pick a random one each time to considerably extend our test
coverage, instead of just running some cases against non-Esplora chain
sources.
Signed-off-by: Elias Rohrer <dev@tnull.de>

@TheBlueMattTheBlueMatt 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.

I don't really know BDK well enough to feel like I can meaningfully review this, but a few questions.

Comment threadsrc/wallet/mod.rs

// FIXME/TODO: This is copied-over from bdk_wallet and only used to generate `WalletEvent`s after
// applying mempool transactions. We should drop this when BDK offers to generate events for
// mempool transactions natively.

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.

Hmm, if BDK doesn't support this natively, do they have logic to correctly handle conflicts and build balance knowledge appropriately? ie if there's an RBF that conflicts do we remove the 0conf tx from our balance?

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.

Yes, they support all of the canonical transaction handling, it's just when they recently introduced the WalletEvent, they didn't update all APIs we require to return them. Arguably, they should just expose the helper we here copied over which would avoid them having to provide _event variants for all API endpoints individually. See also the discussion on bitcoindevkit/bdk_wallet#374

Comment threadsrc/builder.rs
///
/// This should only be set on first startup when importing an older wallet from a previously
/// used [`NodeEntropy`].
pub fn set_wallet_recovery_mode(&mut self) -> &mut Self {

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.

Rather than a coarse "recovery mode" can we just enable setting the "wallet birthday"? In some setups that might be available and would avoid a lot of effort.

@tnulltnullFeb 12, 2026

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.

Hmm, wallet birthday support is still inflight on the BDK side (see bitcoindevkit/bdk_wallet#368) and I'd like to punt on it until we can actually make use of that and remove our hacky birthday logic.

That said, if you prefer we could of course rename the current "recovery_mode" API to "disable_wallet_birthday" for now which ~does the same thing?

@tnulltnull moved this to Goal: Merge in Weekly GoalsFeb 12, 2026
@tnulltnull self-assigned this Feb 12, 2026
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Amended the last commit to include a comment explaining what we do/why we do it:

diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs
index 835e1b31..2decf981 100644
--- a/src/wallet/mod.rs+++ b/src/wallet/mod.rs@@ -1031,4 +1031,12 @@ impl Listen for Wallet {
}
+ // In order to be able to reliably calculate fees the `Wallet` needs access to the previous+ // ouput data. To this end, we here insert any ouputs of transactions that LDK is intersted+ // in (e.g., funding transaction ouputs) into the wallet's transaction graph when we see+ // them, so it is reliably able to calculate fees for subsequent spends.+ //+ // FIXME: technically, we should also do this for mempool transactions. However, at the+ // current time fixing the edge case doesn't seem worth the additional conplexity /+ // additional overhead..
let registered_txids = self.chain_source.registered_txids();
for tx in &block.txdata {

Comment threadsrc/wallet/mod.rs
Error::PersistenceFailed
})?;

self.update_payment_store(&mut *locked_wallet, events).map_err(|e| {

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.

Should this happen before locked_wallet.persist()? What happens if we crash between the wallet persist and the payment store update (presumably the second is idempotent so we should do it first to reply on restart if needed?)?

@tnulltnullFeb 16, 2026

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.

Also a good point. I think the current approach worked fine when we still iterated on the wallet txs, but now with the events we should consistently update first to ensure events are replayed. Now added a fixup and another commit fixing it for apply_update.

Comment threadsrc/wallet/mod.rs
&self, unconfirmed_txs: Vec<(Transaction, u64)>, evicted_txids: Vec<(Txid, u64)>,
) -> Result<(), Error> {
if unconfirmed_txs.is_empty() {
return 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.

This doesn't appear to be tested. Also not clear to me why we'd want to skip this if we don't have any unconfirmed txs in the mempool but do have some txs that got evicted (iiuc).

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.

Hmm, good point, we should only skip if there's nothing to update. Added a fixup

@TheBlueMattTheBlueMatt 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.

Really don't fully grok all the BDK parts, but from what I can tell this looks reasonable. Feel free to squash + land.

When we intially implemented `bitcoind` syncing polling the mempool was
very frequent and rather inefficient so we made a choice not to
unnecessarily update the payment store for mempool changes, especially
since we only consider transactions `Succeeded` after
`ANTI_REORG_DELAY` anyways.
However, since then we made quite a few peformance improvements to the
mempool syncing, and by now we should just update they payment store as
not doing so will lead to rather unexpected behavior, making some tests
fail for `TestChainSource::Bitcoind`, e.g., `channel_full_cycle_0conf`,
which we fix here.
As we recently switched to updating the payment store based on BDK's
`WalletEvent`, but they currently don't offer an API returning such
events when applying mempool transactions, we copy over the respective
method for generating events from `bdk_wallet`, with the intention of
dropping it again once they do.
Signed-off-by: Elias Rohrer <dev@tnull.de>
Previously, we fixed than a fresh node syncing via `bitcoind` RPC would
resync all chain data back to genesis. However, while introducing a
wallet birthday is great, it disallowed discovery of historical funds if
a wallet would be imported from seed. Here, we add a recovery mode flag
to the builder that explictly allows to re-enable resyncing from genesis
in such a scenario. Going forward, we intend to reuse that API for an
upcoming Lightning recoery flow, too.
Previously, we'd selectively insert the funding outputs into the onchain
wallet to later allow calculating `fees_paid` when creating payment
store entries (only for splicing mostly). However, this didn't always work, and we might for
example end up with a missing funding output (and hence would fall back
to `fees_paid: Some(0)`) if it was a counterparty-initiated channel and
we synced via `bitcoind` RPC.
Here, we fix this by tracking all LDK-registered `txids` in
`ChainSource` and then in the `Wallet`'s `Listen` implementation insert
all outputs of all registered transactions into the `Wallet`, ensuring
we'd always have sufficient data for `calculate_fee` available.
Thereby we also fix the `onchain_send_receive` test which previously
failed when using `TestChainSource::Bitcoind`.
Signed-off-by: Elias Rohrer <dev@tnull.de>
Previously, we'd update the payment store after persisting the wallet in
some cases. This was fine as long as we iterated all wallet transactions
anyways (hence idempotent). However, now that we use the event-based
flow we should persist the payment store(s) first, so that wallet events
get replayed if there was a crash in-between some of the persistence
operations.
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Really don't fully grok all the BDK parts, but from what I can tell this looks reasonable. Feel free to squash + land.

Squashed fixups without further changes.

@tnull
tnull merged commit b55de44 into lightningdevkit:mainFeb 16, 2026
23 of 28 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsFeb 16, 2026
@joostjager

Copy link
Copy Markdown
Contributor

For the record, as I've also noted in other PRs that randomize tests: I don't think this is a good approach. A more extensive test suite that runs on main or as a nightly job, iterating through all backends, would be a far superior solution in my opinion.

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

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@tnull@ldk-reviews-bot@joostjager@TheBlueMatt