Skip to content

chanmon_consistency regression after 369a2cf9c, over-advertised outbound HTLC capacity trips reserve invariant #4563

@joostjager

Description

@joostjager

Tags: @TheBlueMatt @leonash

The chanmon_consistency fuzz target started failing after 369a2cf9c8ef810deea0cd2b4cf6ed0691b78144 on April 2, 2026, which merged PR #4529, "Add unannounced_channel_max_inbound_htlc_value_in_flight_percentage".

Repro

This input reproduces the failure on 369a2cf9c8ef810deea0cd2b4cf6ed0691b78144:

8584328489ffffff3a3a3a3a3a3a3affffffff3a3a3a3a3a3a3a3a3a3a

On 369a2cf9c8ef810deea0cd2b4cf6ed0691b78144, this panics at:

lightning/src/ln/channel.rs:6236
assertion failed: remote_stats.commitment_stats.holder_balance_msat >=
    funding.counterparty_selected_channel_reserve_satoshis.unwrap_or(0) * 1000

The backtrace goes through:

  • ChannelContext::get_available_balances_for_scope
  • FundedChannel::get_available_balances
  • ChannelDetails::from_channel
  • ChannelManager::list_funded_channels_with_filter
  • chanmon_consistency::send_payment

Regression window

I compared:

  • 369a2cf9c8ef810deea0cd2b4cf6ed0691b78144
  • first parent, 77a0e4545

The same input:

  • panics on 369a2cf9c8ef810deea0cd2b4cf6ed0691b78144
  • exits cleanly on 77a0e4545

So this is a real regression introduced by the merge.

What changed

The failing assertion itself predates the merge. What changed is the state space that can now reach it.

PR #4529 split the inbound in-flight percentage setting into announced and unannounced variants, and also changed defaults to:

  • announced channels: 25%
  • unannounced channels: 100%

Before this change, the effective default was 10%.

In the repro logs, the affected announced channels are opened with:

  • pre-merge: max_htlc_value_in_flight_msat: 10000000
  • post-merge: max_htlc_value_in_flight_msat: 25000000

That higher limit appears to let get_available_balances advertise more outbound capacity than can actually be added while preserving the reserve invariant on the next remote commitment.

Observed bad path

The problematic channel in the repro is ch:8c0000.

On 369a2cf9c8ef810deea0cd2b4cf6ed0691b78144:

  • a 10_000_000 msat outbound HTLC is sent on the channel
  • repeated 1_000_000 msat sends are then accepted into the holding cell while awaiting RAA / monitor completion
  • later, list_funded_channels_with_filter asks for channel details
  • get_available_balances_for_scope simulates one more outbound HTLC using balances.next_outbound_htlc_limit_msat
  • that simulation drops remote_stats.commitment_stats.holder_balance_msat below counterparty_selected_channel_reserve_satoshis, tripping the debug assertion

On the pre-merge parent, the same later sends are rejected with:

Cannot send more than our next-HTLC maximum - 0 msat

That strongly suggests we are now overestimating next_outbound_htlc_limit_msat for this state.

Likely root cause

This looks like an available-balance computation bug, not a fuzz harness issue.

Relevant code paths:

  • lightning/src/sign/tx_builder.rs, get_available_balances
  • lightning/src/ln/channel.rs, get_available_balances_for_scope

The debug check in get_available_balances_for_scope is effectively saying that the advertised "max next outbound HTLC" is not actually safe, because adding an HTLC of exactly that size violates the reserve requirement on the next remote commitment.

Security status

Current assessment:

  • This is a confirmed correctness regression.
  • This is a reachable panic in fuzzing / debug-assertion builds.
  • This is not currently a confirmed fund-loss bug.
  • This is not currently a confirmed release-build panic, because the failing assertion is debug-only.
  • This is security-adjacent, because it means channel liquidity can be overstated and HTLC admission can be inconsistent with reserve constraints.

What I have not yet proven is whether release builds can drive this same state into:

  • a clean payment failure only
  • a channel error / forced close
  • a broader remote-triggerable DoS condition

So I would treat it as:

  • not a confirmed security vulnerability at this point
  • worth fixing promptly
  • worth checking in release mode to determine whether the impact is limited to misreported liquidity and payment failure, or can escalate into channel disruption

Suggested follow-up

get_available_balances should likely be tightened so that next_outbound_htlc_limit_msat never advertises a value that would cause the next remote commitment to violate:

holder_balance_msat >= counterparty_selected_channel_reserve_satoshis * 1000

In other words, the debug assertion looks valid, but the computed outbound limit is too permissive in this state after the PR changed the HTLC in-flight defaults.

[AI]

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions