Summary
When a peer opens a channel to us and sets channel_reserve_satoshis below our own dust limit (546 sat), openingd does not fail the negotiation. It replies with an accept_channel carrying dust_limit_satoshis = 546, which violates a BOLT 2 MUST for the sender of accept_channel. A spec-compliant peer then has to fail the channel — as it MUST — and the operator sees no reason for it anywhere in our logs.
This is both a conformance bug and, in practice, an interop wall with LDK-based LSPs, whose default dust limit is 354 sat.
Reproduction
Real capture, log-level=io, CLN v25.02, signet (Mutinynet), counterparty is an LSPS1 LSP with a 354 sat dust limit. The node runs a remote signer, which plays no part here — the negotiation ends before anything beyond basepoints is requested.
Their open_channel (0x0020):
funding_satoshis 150000
push_msat 0
dust_limit_satoshis 354
channel_reserve_satoshis 354 <-- reserve required from us
htlc_minimum_msat 1
feerate_per_kw 750
to_self_delay 144
max_accepted_htlcs 10
Our accept_channel (0x0021):
dust_limit_satoshis 546 <-- 546 > 354
channel_reserve_satoshis 1500
htlc_minimum_msat 0
minimum_depth 1
to_self_delay 6
max_accepted_htlcs 483
170 ms later they sent WIRE_ERROR and the negotiation was aborted:
openingd-chan#2: peer_in WIRE_OPEN_CHANNEL
openingd-chan#2: Setting their reserve to 1500sat
openingd-chan#2: peer_out WIRE_ACCEPT_CHANNEL
openingd-chan#2: billboard: Incoming channel: accepted, now waiting for them to create funding tx
openingd-chan#2: peer_in WIRE_ERROR
openingd-chan#2: aborted opening negotiation: They sent ERROR channel <id>: funding failed due to internal error
openingd-chan#2: Status closed, but not exited. Killing
At log-level=info nothing at all is logged about the cause. From the operator's point of view the channel open simply never happens, repeatedly, with no diagnosable reason — that is what makes this worth reporting beyond the conformance point.
The requirements
BOLT 2, sender of accept_channel:
MUST set dust_limit_satoshis less than or equal to channel_reserve_satoshis from the open_channel message.
BOLT 2, receiver of accept_channel:
if channel_reserve_satoshis from the open_channel message is less than dust_limit_satoshis: MUST fail the channel.
So the peer is not merely entitled to reject us, it is required to.
Where it comes from
openingd/openingd.c has the symmetric check, gated by allowdustreserve:
if (!state->allowdustreserve
&& amount_sat_greater(state->remoteconf.dust_limit,
state->localconf.channel_reserve))
negotiation_failed(state, "Our channel reserve %s"
" would be below their dust %s", ...);
but there appears to be no check in the other direction — our dust_limit against their channel_reserve — which is precisely the one BOLT 2 imposes on the message we are about to send. Still the case in v26.06.6, where dust_limit is also unchanged at 546 for every network in bitcoin/chainparams.c.
Suggested minimal fix
Mirror the existing check in the fundee path, under the same allowdustreserve gate so opt-in setups keep their behaviour:
if (!state->allowdustreserve
&& amount_sat_greater(state->localconf.dust_limit,
state->remoteconf.channel_reserve))
negotiation_failed(state, "Their channel reserve %s"
" would be below our dust %s", ...);
This does not make the channel open succeed — it cannot, see below — but it turns a silently invalid message into an explicit, diagnosable refusal.
The broader question
Even with that check, no CLN node can complete an opening where the peer sets the client reserve to 354 sat, since 546 is a constant with no configuration knob. LDK's default dust limit is 354, and at least one LSPS1 provider sets the client reserve to exactly its own dust limit — which works for LDK clients and can never work for a Core Lightning one, at any channel size, since the comparison does not involve capacity.
Is keeping dust_limit fixed at 546 the intended long-term position? Would lowering it, or making it configurable within safe bounds, be considered for interop with LDK-based LSPs — or is the expectation that such providers raise the client reserve instead? Happy to take that reading back to the provider if the latter is the answer.
Possibly related
openingd/dualopend.c has no reserve/dust cross-check and no allowdustreserve. In v2 the reserve is derived (1% of the total) rather than negotiated, so the accept_channel requirement does not map directly — but for a dual-funded channel under ~54 600 sat the derived reserve is itself below 546. I have not tested that path; flagging it in case it deserves a look.
Versions
- Observed on v25.02 (signet), wire capture above.
- Code inspected on v26.06.6: same constant, same single-direction check.
Summary
When a peer opens a channel to us and sets
channel_reserve_satoshisbelow our own dust limit (546 sat),openingddoes not fail the negotiation. It replies with anaccept_channelcarryingdust_limit_satoshis = 546, which violates a BOLT 2 MUST for the sender ofaccept_channel. A spec-compliant peer then has to fail the channel — as it MUST — and the operator sees no reason for it anywhere in our logs.This is both a conformance bug and, in practice, an interop wall with LDK-based LSPs, whose default dust limit is 354 sat.
Reproduction
Real capture,
log-level=io, CLN v25.02, signet (Mutinynet), counterparty is an LSPS1 LSP with a 354 sat dust limit. The node runs a remote signer, which plays no part here — the negotiation ends before anything beyond basepoints is requested.Their
open_channel(0x0020):Our
accept_channel(0x0021):170 ms later they sent
WIRE_ERRORand the negotiation was aborted:At
log-level=infonothing at all is logged about the cause. From the operator's point of view the channel open simply never happens, repeatedly, with no diagnosable reason — that is what makes this worth reporting beyond the conformance point.The requirements
BOLT 2, sender of
accept_channel:BOLT 2, receiver of
accept_channel:So the peer is not merely entitled to reject us, it is required to.
Where it comes from
openingd/openingd.chas the symmetric check, gated byallowdustreserve:but there appears to be no check in the other direction — our
dust_limitagainst theirchannel_reserve— which is precisely the one BOLT 2 imposes on the message we are about to send. Still the case in v26.06.6, wheredust_limitis also unchanged at 546 for every network inbitcoin/chainparams.c.Suggested minimal fix
Mirror the existing check in the fundee path, under the same
allowdustreservegate so opt-in setups keep their behaviour:This does not make the channel open succeed — it cannot, see below — but it turns a silently invalid message into an explicit, diagnosable refusal.
The broader question
Even with that check, no CLN node can complete an opening where the peer sets the client reserve to 354 sat, since 546 is a constant with no configuration knob. LDK's default dust limit is 354, and at least one LSPS1 provider sets the client reserve to exactly its own dust limit — which works for LDK clients and can never work for a Core Lightning one, at any channel size, since the comparison does not involve capacity.
Is keeping
dust_limitfixed at 546 the intended long-term position? Would lowering it, or making it configurable within safe bounds, be considered for interop with LDK-based LSPs — or is the expectation that such providers raise the client reserve instead? Happy to take that reading back to the provider if the latter is the answer.Possibly related
openingd/dualopend.chas no reserve/dust cross-check and noallowdustreserve. In v2 the reserve is derived (1% of the total) rather than negotiated, so theaccept_channelrequirement does not map directly — but for a dual-funded channel under ~54 600 sat the derived reserve is itself below 546. I have not tested that path; flagging it in case it deserves a look.Versions