Account for missing balance in channel reserve assertions for splices
What changed, and why it matters
This commit fixes an internal debug-only accounting bug that occurs when a Lightning channel is spliced (its funding amount is changed) while payments are still in flight. The bug made a non-release debug assertion incorrectly fire, but it did not change normal runtime behavior or allow funds to be stolen.
Treat as a low-severity correctness fix. No urgent security patch is required; include in normal release cycle. If running debug builds with splicing and in-flight HTLCs, update to avoid assertion crashes during testing.
Security signals we found
Fixes a debug assertion failure in channel reserve accounting during splices
Only affects #[cfg(debug_assertions)] fields, not release builds
Relates to splice protocol correctness with in-flight HTLCs
No runtime funds-at-risk logic is changed
Evidence from the diff
When creating a post-splice FundingScope, the holder_max_commitment_tx_output and counterparty_max_commitment_tx_output monotonicity trackers were initialized to the post-splice balance without subtracting pending HTLCs or anchor fees. Because splices can carry in-flight HTLCs, the first post-splice commitment transaction’s actual balance was lower than the initialized maximum, triggering a debug_assert! in ChannelContext::build_commitment_transaction. The fix derives the new trackers from the pre-splice trackers, adding only each side’s splice funding contribution, so the pending HTLC/anchor deductions are preserved. The fields are guarded by #[cfg(debug_assertions)], so the change only affects debug/test builds.
Changed components
lightning/src/ln/channel.rsFundingScope::... (post-splice constructor)ChannelContext::build_commitment_transaction debug assertionInspect captured patch +14 / −8
diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs
index 27ccd1c..096a101 100644
--- a/lightning/src/ln/channel.rs
+++ b/lightning/src/ln/channel.rs
@@ -2847,15 +2847,21 @@ impl FundingScope {
counterparty_selected_channel_reserve_satoshis,
holder_selected_channel_reserve_satoshis,
#[cfg(debug_assertions)]
- holder_max_commitment_tx_output: Mutex::new((
- post_value_to_self_msat,
- (post_channel_value * 1000).saturating_sub(post_value_to_self_msat),
- )),
+ holder_max_commitment_tx_output: {
+ let prev = *prev_funding.holder_max_commitment_tx_output.lock().unwrap();
+ Mutex::new((
+ prev.0.saturating_add_signed(our_funding_contribution.to_sat() * 1000),
+ prev.1.saturating_add_signed(their_funding_contribution.to_sat() * 1000),
+ ))
+ },
#[cfg(debug_assertions)]
- counterparty_max_commitment_tx_output: Mutex::new((
- post_value_to_self_msat,
- (post_channel_value * 1000).saturating_sub(post_value_to_self_msat),
- )),
+ counterparty_max_commitment_tx_output: {
+ let prev = *prev_funding.counterparty_max_commitment_tx_output.lock().unwrap();
+ Mutex::new((
+ prev.0.saturating_add_signed(our_funding_contribution.to_sat() * 1000),
+ prev.1.saturating_add_signed(their_funding_contribution.to_sat() * 1000),
+ ))
+ },
#[cfg(any(test, fuzzing))]
next_local_fee: Mutex::new(PredictedNextFee::default()),
#[cfg(any(test, fuzzing))]
Why this scored 21/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.