Skip to content

Splice fixes for chanmon_consistency fuzz target#4655

Open
wpaulino wants to merge 4 commits into
lightningdevkit:mainfrom
wpaulino:splice-chanmon-consistency-fixes
Open

Splice fixes for chanmon_consistency fuzz target#4655
wpaulino wants to merge 4 commits into
lightningdevkit:mainfrom
wpaulino:splice-chanmon-consistency-fixes

Conversation

@wpaulino
Copy link
Copy Markdown
Contributor

@wpaulino wpaulino commented Jun 1, 2026

While this target found several issues in our production code, there were also issues with the fuzz target itself, which this PR addresses. It fixes the following payloads from #4363:

00a6a61b1b1b211b211e1b1b1b1b211b211b211e1b1b261b1b211b1ba1
e8a3a3201ba31b201ba3a3201ba3a3201b201ba3a3201b040d80c113ff
a3cd00a2a1181021181118101810211811181000bb10181021ffff38
ffa1ffffa0414da373ff
a2cd00a2a118102118602700cda2a000a100733a3a6b73a3ffb4ffb7a1
ffa1ffacab211b11baff
88a4a6ffacadab21bcff

wpaulino added 4 commits June 1, 2026 15:08
If the input ends immediately after `tx_signatures`, the corresponding
`SpliceNegotiated` event may still be pending, leaving the valid
interactive funding transaction in the test broadcaster.
Now that the fuzz target supports canceling splice funding attempts, we
may see failed signing attempts due to the cancellation.
LDK and the chanmon_consistency fuzz target have grown in complexity
recently and thus require more iterations than previously assumed to
fully settle the state of all active channels.
This replicates what a node in production would see, and is necessary to
replay certain actions within LDK that happened prior to the reload.
@ldk-reviews-bot
Copy link
Copy Markdown

ldk-reviews-bot commented Jun 1, 2026

👋 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.

@wpaulino wpaulino requested a review from TheBlueMatt June 1, 2026 22:17
@wpaulino wpaulino self-assigned this Jun 1, 2026
@wpaulino wpaulino added this to the 0.3 milestone Jun 1, 2026
Comment on lines +2016 to +2021
for (idx, node) in nodes.iter().enumerate() {
if node.broadcaster.txn_broadcasted.borrow().is_empty() {
continue;
}

let pending_events = node.get_and_clear_pending_events();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The verification is one-directional: for each broadcast tx, you check a matching SpliceNegotiated event exists, but you don't verify the reverse — that every pending SpliceNegotiated event has a corresponding broadcast tx. A node with no broadcasts (skipped at line 2017) but with dangling SpliceNegotiated events passes silently.

Consider also checking nodes that have no broadcasts but do have pending SpliceNegotiated events, as that would indicate the splice tx was not broadcast despite an event being generated.

Suggested change
for (idx, node) in nodes.iter().enumerate() {
if node.broadcaster.txn_broadcasted.borrow().is_empty() {
continue;
}
let pending_events = node.get_and_clear_pending_events();
for (idx, node) in nodes.iter().enumerate() {
let pending_events = node.get_and_clear_pending_events();
if node.broadcaster.txn_broadcasted.borrow().is_empty() {
assert!(
!pending_events.iter().any(|e| matches!(e, events::Event::SpliceNegotiated { .. })),
"node {} has pending SpliceNegotiated event(s) but no broadcast tx",
idx,
);
continue;
}

@ldk-claude-review-bot
Copy link
Copy Markdown
Collaborator

Review Summary

1 inline comment posted:

  • fuzz/src/chanmon_consistency.rs:2016-2021 — The broadcast/event verification in assert_test_invariants is one-directional: it checks every broadcast tx has a matching SpliceNegotiated event, but nodes with no broadcasts are skipped entirely even if they have dangling SpliceNegotiated events. This could mask a bug where an event is generated without its corresponding tx being broadcast.

No other issues found. The remaining changes are correct:

  • Height tracking on reload (self.height = manager.0.height) and block replay in restart_node properly handle the case where the deserialized manager is behind the pre-reload height.
  • The FundingTransactionReadyForSigning error handling for stale signing events invalidated by tx_abort is appropriate.
  • The MAX_SETTLE_ITERATIONS increase from 100 to 256 and the typo fix are straightforward.
  • Storing (Transaction, TransactionType) in the broadcaster and asserting InteractiveFunding in the SpliceNegotiated handler adds useful validation.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants