Skip to content

Conversation

@moisesPompilio
Copy link
Contributor

This PR introduces functional tests to validate node behavior under blockchain reorganization scenarios, specifically when opening and closing channels.

  • Channel open reorg: Ensures the node maintains channel consistency after a reorg occurring immediately after channel creation.
  • Channel close reorg: Validates both normal close and force close operations when a reorg happens during channel closure, confirming that state remains consistent.

Additionally, the premine_and_distribute_funds helper was optimized. Previously, when many addresses were funded, transactions were sent sequentially, waiting for each to appear in the mempool before sending the next. This was slow. The implementation now uses sendmany from Bitcoin Core to send all outputs in a single transaction, improving speed for all tests relying on this function.

Close #575

@ldk-reviews-bot
Copy link

ldk-reviews-bot commented Aug 8, 2025

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

@moisesPompilio moisesPompilio force-pushed the issue-575 branch 2 times, most recently from a9a00e4 to c28fa3c Compare August 9, 2025 16:13
@tnull tnull self-requested a review August 11, 2025 13:10
Copy link
Collaborator

@tnull tnull left a comment

Choose a reason for hiding this comment

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

Cool, thanks for looking into this! At a high-level this looks pretty much good to me, but I'll need to take a closer look at the test logic still.

I hope the base PR (#602) get's merged soon to fix builds on main. Mind adding a more descriptive commit message in the meantime?

@moisesPompilio
Copy link
Contributor Author

moisesPompilio commented Aug 11, 2025

Thanks! I was going to make the commit message more descriptive, but got caught up looking into the CI failure. Since PR (#602) fixed it, I forgot to update the message. I initially opened this as a draft to address the issue before marking it ready.

@moisesPompilio moisesPompilio marked this pull request as ready for review August 11, 2025 14:42
@tnull
Copy link
Collaborator

tnull commented Aug 11, 2025

This needs a rebase now.

@tnull tnull requested review from tnull and removed request for joostjager August 11, 2025 18:27
Copy link
Collaborator

@tnull tnull left a comment

Choose a reason for hiding this comment

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

Thanks! Basically LGTM, just some minor comments/nits.

macro_rules! reorg {
($reorg_depth: expr) => {{
invalidate_blocks(bitcoind, $reorg_depth);
generate_blocks_and_wait(bitcoind, electrs, $reorg_depth);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I want to note that operating on the same chain and generating/invalidating blocks is only safe/working because proptests are run sequentially as it seems. Fine for this case, but maybe we should inline init_setup_test_reorg to avoid confusion in the future if other tests would try to reuse the same helper?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You’re right. I initially created init_setup_test_reorg when building the test, thinking I’d need many proptest rounds. Since reorgs don’t require that many cases, the performance impact is negligible now, so I’ve removed the helper.

force_close in prop::bool::ANY,
) {

let (bitcoind, electrsd) = init_setup_test_reorg();
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: Seems all of this method needs one more indentation to the right. rustfmt didn't catch this as it doesn't operate on macro code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done!

assert!(node.list_balances().pending_balances_from_channel_closures.len() > 0);
match node.list_balances().pending_balances_from_channel_closures[0] {
PendingSweepBalance::BroadcastAwaitingConfirmation { .. } => {},
_ => println!("Unexpected balance state!"),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we panic here and below rather than just printing that we reached an unexpected state?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, had to remove it for tests, but added it back now.


// Check balance after close channel
nodes.iter().for_each(|node| {
assert!(node.list_balances().spendable_onchain_balance_sats > amount - 7000);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Where is this 7000 number coming from? Mind introducing an appropriately named variable for it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done! It’s the approximate tx fee to prevent amount check failures.

}

let mut node_channels_id = HashMap::new();
for index in 0..nodes.len() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: Rather than iterating over the index, can we use iterators? (here and elswhere)

Suggested change
for index in 0..nodes.len() {
for node in nodes.iter() {

They are usually a bit easier to read.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I replaced some for index loops with clearer iterator expressions.

Copy link
Collaborator

@tnull tnull left a comment

Choose a reason for hiding this comment

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

Thanks! One last comment, should otherwise be good to go.

}};
}

let amount = 2_100_000;
Copy link
Collaborator

Choose a reason for hiding this comment

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

For numerical amounts and fees please always append a _sat/_msat/_btc suffix to make it explicit which unit we're dealing in (here and everywhere else).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

- Introduced a property test to verify reorg handling during both channel opening and closing (normal and force close).
- Added `invalidate_block` helper to roll back the chain and regenerate blocks to the previous height.
Copy link
Collaborator

@tnull tnull left a comment

Choose a reason for hiding this comment

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

ACK, will merge if CI passes.

@tnull tnull merged commit 86cc3d2 into lightningdevkit:main Aug 15, 2025
14 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Property-based reorg tests

3 participants