Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 44 additions & 13 deletions 02-peer-protocol.md
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I also have this diff against the tx_complete message, since it is possible to reach no-outputs in a 0-reserve channel with a "splice out".

diff --git a/02-peer-protocol.md b/02-peer-protocol.md
index 5957012..498f4dc 100644
--- a/02-peer-protocol.md
+++ b/02-peer-protocol.md
@@ -1796,6 +1796,8 @@ The receiving node:
     - Either side has added an output other than the channel funding output
       and the balance for that side is less than the channel reserve that
       matches the new channel capacity.
+    - Either side has added an output other than the channel funding output
+      and any resulting commitment transactions would have no outputs.

 ##### Rationale

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I'll rebase to integrate that on top of the splicing changes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Added in 668c0f8

Copy link
Copy Markdown

@tankyleo tankyleo Apr 6, 2026

Choose a reason for hiding this comment

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

Also should we clarify that after a channel has been spliced, the reserve remains 0 for zero-reserve channels ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I think it's obvious, since there's no mechanism to re-negotiate a reserve during a splice? Do you really think it's useful?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

To me it's quite obvious yes. Feel free to resolve this thank you.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I made this review comment because @TheBlueMatt had this question when reviewing the 0-reserve PR in LDK IIRC.

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.

I dunno feels like it could be specified? Splicing mandates that the reserve changes to 10%, no?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Splicing mandates that the reserve changes to 10%, no?

What's that? What makes you say that?

Original file line number Diff line number Diff line change
Expand Up @@ -797,7 +797,10 @@ The sending node:
- MUST set `push_msat` to equal or less than 1000 * `funding_satoshis`.
- MUST set `funding_pubkey`, `revocation_basepoint`, `htlc_basepoint`, `payment_basepoint`, and `delayed_payment_basepoint` to valid secp256k1 pubkeys in compressed format.
- MUST set `first_per_commitment_point` to the per-commitment point to be used for the initial commitment transaction, derived as specified in [BOLT #3](03-transactions.md#per-commitment-secret-requirements).
- MUST set `channel_reserve_satoshis` greater than or equal to `dust_limit_satoshis`.
- If the receiver supports `option_zero_reserve`:
- MAY set `channel_reserve_satoshis` to `0`.
- Otherwise (`option_zero_reserve` cannot be used):
- MUST set `channel_reserve_satoshis` greater than or equal to `dust_limit_satoshis`.
- MUST set undefined bits in `channel_flags` to 0.
- if both nodes advertised the `option_upfront_shutdown_script` feature:
- MUST include `upfront_shutdown_script` with either a valid `shutdown_scriptpubkey` as required by `shutdown` `scriptpubkey`, or a zero-length `shutdown_scriptpubkey` (ie. `0x0000`).
Expand Down Expand Up @@ -847,10 +850,10 @@ The receiving node MUST fail the channel if:
- it considers `feerate_per_kw` too small for timely processing or unreasonably large.
- `funding_pubkey`, `revocation_basepoint`, `htlc_basepoint`, `payment_basepoint`, or `delayed_payment_basepoint`
are not valid secp256k1 pubkeys in compressed format.
- `dust_limit_satoshis` is greater than `channel_reserve_satoshis`.
- it doesn't support `option_zero_reserve` and `dust_limit_satoshis` is greater than `channel_reserve_satoshis`.
- `dust_limit_satoshis` is smaller than `354 satoshis` (see [BOLT 3](03-transactions.md#dust-limits)).
- the funder's amount for the initial commitment transaction is not sufficient for full [fee payment](03-transactions.md#fee-payment).
- both `to_local` and `to_remote` amounts for the initial commitment transaction are less than or equal to `channel_reserve_satoshis` (see [BOLT 3](03-transactions.md#commitment-transaction-outputs)).
- it doesn't support `option_zero_reserve` and both `to_local` and `to_remote` amounts for the initial commitment transaction are less than or equal to `channel_reserve_satoshis` (see [BOLT 3](03-transactions.md#commitment-transaction-outputs)).
- `funding_satoshis` is greater than or equal to 2^24 and the receiver does not support `option_support_large_channel`.
- the `channel_type` is not suitable.
- the `channel_type` includes `option_zeroconf` and it does not trust the sender to open an unconfirmed channel.
Expand All @@ -874,7 +877,7 @@ The requirement that `channel_reserve_satoshis` is not considered dust
according to `dust_limit_satoshis` eliminates cases where all outputs
would be eliminated as dust. The similar requirements in
`accept_channel` ensure that both sides' `channel_reserve_satoshis`
are above both `dust_limit_satoshis`.
are above both `dust_limit_satoshis`, unless `option_zero_reserve` is used.

The receiver should not accept large `dust_limit_satoshis`, as this could be
used in griefing attacks, where the peer publishes its commitment with a lot
Expand Down Expand Up @@ -928,16 +931,21 @@ The sender:
- MUST set `minimum_depth` to zero.
- otherwise:
- SHOULD set `minimum_depth` to a number of blocks it considers reasonable to avoid double-spending of the funding transaction.
- MUST set `channel_reserve_satoshis` greater than or equal to `dust_limit_satoshis` from the `open_channel` message.
- MUST set `dust_limit_satoshis` less than or equal to `channel_reserve_satoshis` from the `open_channel` message.
- MUST set `channel_type` to the `channel_type` from `open_channel`.
- If the receiver supports `option_zero_reserve` and `channel_reserve_satoshis` from the `open_channel` message is `0`:
- MAY set `channel_reserve_satoshis` to `0`.
- If the receiver supports `option_zero_reserve`:
- MAY set `channel_reserve_satoshis` to `0`.
Comment on lines +934 to +937
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hmm I'm not sure that the opener of the channel setting 0-reserve for the accepter should have any bearing on whether the accepter sets 0-reserve in return (in current LDK it does not); I would have used MAY in both cases here.

Copy link
Copy Markdown
Collaborator Author

@t-bast t-bast Apr 9, 2026

Choose a reason for hiding this comment

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

I'd like to keep it like that, because we should incentivize a tit-for-tat usage of 0-reserve. If you only set it on one side (for example on the mobile wallet only), then you don't get the full UX benefits: you're able to send all your balance, but you're not able to receive all the remaining channel amount.

There's no way to express: "I'm offering you 0-reserve but only if you do the same". Some nodes may choose to cancel the channel open if they offer 0-reserve but the remote peer doesn't offer it in response. So it's IMO a better idea to do tit-for-tat and use the SHOULD here in most cases.

Note that I'm biased towards the case where the opener is for example purchasing liquidity (mobile wallet), where as an acceptor you're being paid, so it's fine even if the other side ends up "cheating" and broadcasting a revoked commitment, because what you've been paid upfront will be more interesting than the force-close cost). But you're right that in the case where some random node opens a channel to you and lets you use zero-reserve, there's no reason to echo it back, so both options make sense (which is why this is not a MUST).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I've changed this in 668c0f8 so that:

  • single-funding uses a MAY, since in that case there's no way to purchase a liquidity ads and the more general case would be to not use tit-for-tat with random nodes
  • dual-funding uses a SHOULD when there is a liquidity purchase, and a MAY otherwise, to reflect the case we're using for Phoenix (and what other mobile wallets should do with their LSP)

- Otherwise (`option_zero_reserve` cannot be used):
- MUST set `channel_reserve_satoshis` greater than or equal to `dust_limit_satoshis` from the `open_channel` message.
- MUST set `dust_limit_satoshis` less than or equal to `channel_reserve_satoshis` from the `open_channel` message.
- MUST set `channel_type` to the `channel_type` from `open_channel`.

The receiver:
- if `minimum_depth` is unreasonably large:
- MAY fail the channel.
- if `channel_reserve_satoshis` is less than `dust_limit_satoshis` within the `open_channel` message:
- if `channel_reserve_satoshis` is less than `dust_limit_satoshis` within the `open_channel` message and `option_zero_reserve` is not set:
- MUST fail the channel.
- if `channel_reserve_satoshis` from the `open_channel` message is less than `dust_limit_satoshis`:
- if `channel_reserve_satoshis` from the `open_channel` message is less than `dust_limit_satoshis` and `option_zero_reserve` is not set:
- MUST fail the channel.
- if the message doesn't include a `channel_type`:
- MUST fail the channel.
Expand Down Expand Up @@ -1187,6 +1195,7 @@ This message initiates the v2 channel establishment workflow.
2. data:
* [`...*byte`:`type`]
1. type: 2 (`require_confirmed_inputs`)
1. type: 4 (`disable_channel_reserve`)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

While we have not finished dual-funded channels, LDK currently use an odd TLV here:

  • If the counterparty does not send an update_fee / update_add_htlc / splice_out we would have accepted, this is ok.
  • In case the funder of the channel does not understand this field, we do not expect the funder of the channel to complain if the fundee adds an HTLC that bumps the funder's balance below its reserve; this is the fundee's problem, not the funder's.

Copy link
Copy Markdown
Collaborator Author

@t-bast t-bast Apr 9, 2026

Choose a reason for hiding this comment

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

I don't understand, do you mean that you've introduced a similar TLV to the open_channel and accept_channel messages (used for single-funding)? That is completely unnecessary, you can rely on the existing channel_reserve_satoshis field as specified in this PR?

This TLV is necessary for the dual-funding case because the channel_reserve_satoshis field was removed (in favor of a default 1% reserve).

Copy link
Copy Markdown

@tankyleo tankyleo Apr 14, 2026

Choose a reason for hiding this comment

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

I mean that for the disable_channel_reserve field (used in the dual-funding case), we use an odd TLV, type: 3 instead of type: 4, as we don't mind if the receiver of the message does not understand that field for the two reasons I listed above.


Rationale and Requirements are the same as for [`open_channel`](#the-open_channel-message),
with the following additions.
Expand All @@ -1202,6 +1211,8 @@ The sending node:
- MUST set `funding_feerate_perkw` to the feerate for this transaction
- If it requires the receiving node to only use confirmed inputs:
- MUST set `require_confirmed_inputs`
- If the receiving node supports `option_zero_reserve`:
- MAY set `disable_channel_reserve`.

The receiving node:
- MAY fail the negotiation if:
Expand Down Expand Up @@ -1232,7 +1243,8 @@ Note that `open_channel`'s `channel_reserve_satoshi` has been omitted.
Instead, the channel reserve is fixed at 1% of the total channel balance
(`open_channel2`.`funding_satoshis` + `accept_channel2`.`funding_satoshis`)
rounded down to the nearest whole satoshi or the `dust_limit_satoshis`,
whichever is greater.
whichever is greater, unless `disable_channel_reserve` is set, in which
case there will be no channel reserve on the receiver side.

Note that `push_msat` has been omitted.

Expand Down Expand Up @@ -1276,6 +1288,7 @@ acceptance of the new channel.
2. data:
* [`...*byte`:`type`]
1. type: 2 (`require_confirmed_inputs`)
1. type: 4 (`disable_channel_reserve`)

Rationale and Requirements are the same as listed above,
for [`accept_channel`](#the-accept_channel-message) with the following
Expand All @@ -1288,7 +1301,15 @@ The accepting node:
- MUST set `channel_type` to the `channel_type` from `open_channel2`.
- MAY respond with a `funding_satoshis` value of zero.
- If it requires the opening node to only use confirmed inputs:
- MUST set `require_confirmed_inputs`.
- MUST set `require_confirmed_inputs`
- If the receiving node supports `option_zero_reserve` and the `open_channel2`
message contains `disable_channel_reserve`:
- If the opening node is purchasing liquidity:
- SHOULD set `disable_channel_reserve`.
- Otherwise:
- MAY set `disable_channel_reserve`.
- If the receiving node supports `option_zero_reserve`:
- MAY set `disable_channel_reserve`.

The receiving node:
- MUST fail the negotiation if:
Expand All @@ -1304,7 +1325,8 @@ Note that `accept_channel`'s `channel_reserve_satoshi` has been omitted.
Instead, the channel reserve is fixed at 1% of the total channel balance
(`open_channel2`.`funding_satoshis` + `accept_channel2`.`funding_satoshis`)
rounded down to the nearest whole satoshi or the `dust_limit_satoshis`,
whichever is greater.
whichever is greater, unless `disable_channel_reserve` is set, in which
case there will be no channel reserve on the receiver side.

### Funding Composition

Expand Down Expand Up @@ -1796,6 +1818,8 @@ The receiving node:
- Either side has added an output other than the channel funding output
and the balance for that side is less than the channel reserve that
matches the new channel capacity.
- Either side has added an output other than the channel funding output
and any resulting commitment transactions would have no outputs.

##### Rationale

Expand Down Expand Up @@ -2787,14 +2811,18 @@ A sending node:
- SHOULD NOT offer `amount_msat` if, after adding that HTLC to its commitment
transaction, its remaining balance doesn't allow it to pay the commitment
transaction fee when receiving or sending a future additional non-dust HTLC
while maintaining its channel reserve. It is recommended that this "fee spike
while maintaining its channel reserve, or if the resulting commitment
transaction would have no outputs. It is recommended that this "fee spike
buffer" can handle twice the current `feerate_per_kw` to ensure predictability
between implementations.
- if it is _not responsible_ for paying the Bitcoin fee:
- SHOULD NOT offer `amount_msat` if, once the remote node adds that HTLC to
its commitment transaction, it cannot pay the fee for the updated local or
remote transaction at the current `feerate_per_kw` while maintaining its
channel reserve.
- if, after adding that HTLC to its commitment transaction, that transaction
doesn't have any output:
- MUST NOT offer `amount_msat`.
Comment on lines +2823 to +2825
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I want to confirm here that in 0FC channels, the "has at least one output" check goes away because of the permanent presence of the P2A output.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes, we should do that indeed! Note that it would only happen if all of the channel's balance goes to HTLCs that are below the dust_limit. Since nodes shouldn't allow channels to become ridiculously small, in practice this will never happen, but it's good to have the theoretical case covered.

- MUST offer `amount_msat` greater than 0.
- MUST NOT offer `amount_msat` below the receiving node's `htlc_minimum_msat`
- MUST set `cltv_expiry` less than 500000000.
Expand Down Expand Up @@ -2822,6 +2850,9 @@ A receiving node:
- receiving an `amount_msat` that the sending node cannot afford at the current `feerate_per_kw` (while maintaining its channel reserve and any `to_local_anchor` and `to_remote_anchor` costs):
- SHOULD send a `warning` and close the connection, or send an
`error` and fail the channel.
- receiving an `amount_msat` that results in a commitment transaction without any output:
- SHOULD send a `warning` and close the connection, or send an
`error` and fail the channel.
- if a sending node adds more than receiver `max_accepted_htlcs` HTLCs to
its local commitment transaction, OR adds more than receiver `max_htlc_value_in_flight_msat` worth of offered HTLCs to its local commitment transaction:
- SHOULD send a `warning` and close the connection, or send an
Expand Down
1 change: 1 addition & 0 deletions 09-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ The Context column decodes as follows:
| 50/51 | `option_zeroconf` | Understands zeroconf channel types | INT | `option_scid_alias` | [BOLT #2][bolt02-channel-ready] |
| 60/61 | `option_simple_close` | Simplified closing negotiation | IN | `option_shutdown_anysegwit` | [BOLT #2][bolt02-simple-close] |
| 62/63 | `option_splice` | Allows replacing the funding transaction with a new one | IN | | [BOLT #2](02-peer-protocol.md#channel-splicing) |
| 64/65 | `option_zero_reserve` | This node may accept zero-reserve channels | IN | | [BOLT #2][bolt02-open] |
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

  • LDK does not signal this feature at the moment, since we never expect channel accepters to grant 0-reserve to channel openers that they don't have some pre-existing, out-of-band relationship with.
  • Since December 2021, LDK has not complained if some party opens a channel to LDK, and sets 0-reserve on LDK's side of the channel, see Explicitly support counterparty setting 0 channel reserve lightningdevkit/rust-lightning#1163. If the channel opener sets 0-reserve for the channel accepter, the drop in security is entirely on the channel opener.


## Requirements

Expand Down