Open and accept zero reserve channels - #4428

Merged
TheBlueMatt merged 12 commits into
lightningdevkit:mainfrom
tankyleo:2026-02-zero-reserve
Mar 26, 2026
Merged

Open and accept zero reserve channels#4428
TheBlueMatt merged 12 commits into
lightningdevkit:mainfrom
tankyleo:2026-02-zero-reserve

Conversation

@tankyleo

@tankyleotankyleo commented Feb 19, 2026

Copy link
Copy Markdown
Contributor

Fixes#1801

@ldk-reviews-bot

ldk-reviews-bot commented Feb 19, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @carlaKC 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.

@tankyleotankyleo moved this to Goal: Merge in Weekly GoalsFeb 19, 2026
@tankyleotankyleo self-assigned this Feb 19, 2026
@tankyleo
tankyleo marked this pull request as ready for review February 26, 2026 10:45
@tankyleotankyleo added this to the 0.3 milestone Feb 26, 2026
@codecov

codecovBot commented Feb 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.11696% with 170 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.17%. Comparing base (d830f10) to head (32a67f8).
⚠️ Report is 22 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs71.12%148 Missing and 3 partials ⚠️
lightning/src/ln/channelmanager.rs84.78%14 Missing ⚠️
lightning/src/ln/msgs.rs94.87%4 Missing ⚠️
lightning/src/sign/tx_builder.rs99.37%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4428 +/- ##
==========================================
- Coverage 86.18% 86.17% -0.02% 
==========================================
Files 160 160 Lines 107536 108046 +510 Branches 107536 108046 +510 ==========================================
+ Hits 92680 93105 +425 - Misses 12231 12321 +90 + Partials 2625 2620 -5 
FlagCoverage Δ
tests86.17% <80.11%> (-0.02%)⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@TheBlueMattTheBlueMatt left a comment

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.

chanmon_consistency needs to be updated to have a 0-reserve channel or two (I believe we now have three channels between each pair of peers, so we can just do it on a subset of them, in fact we could have three separate channel types for better coverage).

/// Creates a new outbound channel to the given remote node and with the given value.
///
/// The only difference between this method and [`ChannelManager::create_channel`] is that this method sets
/// the reserve the counterparty must keep at all times in the channel to zero. This allows the counterparty to

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.

nit: If that's the only difference let's say create_channel_to_trusted_peer_0_reserve? Nice to be explicit, imo.

Comment threadlightning/src/ln/channel.rs Outdated

let channel_value_satoshis =
our_funding_contribution_sats.saturating_add(msg.common_fields.funding_satoshis);
// TODO(zero_reserve): support reading and writing the `disable_channel_reserve` field

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.

Two questions. Shouldn't we check that if a channel has the 0-reserve feature bit and if it is fail if the user isn't accepting 0-reserve? Also why shouldn't we just set it now? I'm not sure we need to bother with a staging bit, really, honestly...

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Needs rebase now :/

@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch from 471ba8f to 253db4dCompareMarch 5, 2026 10:55
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Needs rebase now :/

Let me know if you prefer I rebase first

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Feel free to go ahead and rebase and squash, yea.

@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch 2 times, most recently from 5fa3a7c to 43be438CompareMarch 5, 2026 17:21
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Squash diff (do not click compare just above, I pushed the wrong branch, and later corrected it):

https://github.com/lightningdevkit/rust-lightning/compare/253db4d9a05cda43f74c2835448126d3205b27b8..43be438f70ba28ad7918e407026a78763ac2b368

@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch from 43be438 to 7fde002CompareMarch 5, 2026 17:36
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

git range-diff main 43be438 7fde002

1: bfba6e993 ! 1: b916c3596 Add inbound and outbound checks for zero reserve channels
@@ lightning/src/ln/channel.rs: impl FundingScope {
// New reserve values are based on the new channel value and are v2-specific
- let counterparty_selected_channel_reserve_satoshis =
-- Some(get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS));
+- get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS);
- let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
- post_channel_value,
- context.counterparty_dust_limit_satoshis,
@@ lightning/src/ln/channel.rs: impl FundingScope {
+ == 0
+ {
+ // If we previously had a 0-value reserve, continue with the same reserve
-+ Some(0)
++ 0
+ } else {
-+ Some(get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS))
++ get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS)
+ };
+ let holder_selected_channel_reserve_satoshis =
+ if prev_funding.holder_selected_channel_reserve_satoshis == 0 {
@@ lightning/src/sign/tx_builder.rs: fn get_next_commitment_stats(
);
@@ lightning/src/sign/tx_builder.rs: fn get_available_balances(
- let fee_spike_buffer_htlc =
if channel_type.supports_anchor_zero_fee_commitments() { 0 } else { 1 };
+ // Note that the feerate is 0 in zero-fee commitment channels, so this statement is a noop
- let local_feerate = feerate_per_kw
+ let spiked_feerate = feerate_per_kw
* if is_outbound_from_holder && !channel_type.supports_anchors_zero_fee_htlc_tx() {
2: 1f8ca6c66 = 2: 53fc9a354 Add 0-reserve to `accept_inbound_channel_from_trusted_peer`
3: 1b94f7cc3 = 3: da0520818 Add `ChannelManager::create_channel_to_trusted_peer_0reserve`
4: 19b8d7943 = 4: 1905f94e7 Shakedown zero reserve channels
5: de2366da4 = 5: 4e6a7527a Format `ChannelManager::create_channel_internal` and...
6: 43be438f7 = 6: 7fde002e7 Update `chanmon_consistency` to include 0FC and 0-reserve channels

@ldk-reviews-bot

Copy link
Copy Markdown

✅ Added second reviewer: @joostjager

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@joostjager! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

1 similar comment
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@joostjager! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @TheBlueMatt@joostjager! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@tankyleo
tankyleo requested review from carlaKC and removed request for joostjagerMarch 10, 2026 14:33
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 3rd Reminder

Hey @TheBlueMatt@carlaKC! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@carlaKCcarlaKC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

First pass - haven't gone through the tests yet.

Comment threadlightning/src/sign/tx_builder.rs Outdated
Comment threadlightning/src/sign/tx_builder.rs
Comment threadlightning/src/sign/tx_builder.rs Outdated
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channelmanager.rs Outdated
/// If it does not confirm before we decide to close the channel, or if the funding transaction
/// does not pay to the correct script the correct amount, *you will lose funds*.
///
/// # Zero-reserve

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Shouldn't we be setting the option_zero_reserve feature in lightning/bolts#1140?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Discussed offline, I'll hold off on signaling for now pending further spec discussions

@tankyleo
tankyleo requested a review from carlaKCMarch 13, 2026 12:50
The goal is to prevent any commitments with no outputs, since these are
not broadcastable.
@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch 2 times, most recently from 91565f1 to 2967e6fCompareMarch 25, 2026 23:23
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

git diff bbe7a62 2967e6f, all significant changes in commit 5eb530d

diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs
index 8f8ab4a9b..fa394ee6e 100644
--- a/lightning/src/ln/channel.rs+++ b/lightning/src/ln/channel.rs@@ -15586,6 +15586,10 @@
}
let is_manual_broadcast = Some(self.context.is_manual_broadcast);
+ let has_0reserve =+ (self.funding.counterparty_selected_channel_reserve_satoshis.is_some_and(|r| r == 0)+ || self.funding.holder_selected_channel_reserve_satoshis == 0)+ .then_some(());
let holder_commitment_point_previous_revoked =
self.holder_commitment_point.previous_revoked_point();
let holder_commitment_point_last_revoked =
@@ -15655,6 +15659,7 @@
// 65 was previously used for quiescent_action
(67, pending_outbound_held_htlc_flags, optional_vec), // Added in 0.2
(69, holding_cell_held_htlc_flags, optional_vec), // Added in 0.2
+ (70, has_0reserve, option), // Added in 0.3 to prevent downgrades
(71, holder_commitment_point_previous_revoked, option), // Added in 0.3
(73, holder_commitment_point_last_revoked, option), // Added in 0.3
(75, inbound_committed_update_adds, optional_vec),
@@ -16028,6 +16033,7 @@
let mut malformed_htlcs: Option<Vec<(u64, u16, [u8; 32])>> = None;
let mut monitor_pending_update_adds: Option<Vec<msgs::UpdateAddHTLC>> = None;
+ let mut _has_0reserve: Option<()> = None;
let mut holder_commitment_point_previous_revoked_opt: Option<PublicKey> = None;
let mut holder_commitment_point_last_revoked_opt: Option<PublicKey> = None;
let mut holder_commitment_point_current_opt: Option<PublicKey> = None;
@@ -16098,6 +16104,7 @@
// 65 quiescent_action: Added in 0.2; removed in 0.3
(67, pending_outbound_held_htlc_flags_opt, optional_vec), // Added in 0.2
(69, holding_cell_held_htlc_flags_opt, optional_vec), // Added in 0.2
+ (70, _has_0reserve, option), // Added in 0.3 to prevent downgrades
(71, holder_commitment_point_previous_revoked_opt, option), // Added in 0.3
(73, holder_commitment_point_last_revoked_opt, option), // Added in 0.3
(75, inbound_committed_update_adds_opt, optional_vec),
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index 432070129..184bc4052 100644
--- a/lightning/src/ln/channelmanager.rs+++ b/lightning/src/ln/channelmanager.rs@@ -3555,7 +3555,7 @@
///
/// Note that there is no guarantee that the counterparty accepts such a channel themselves.
ZeroReserve,
-	/// Sets combination of [`TrustedChannelFeatures::ZeroConf`] and [`TrustedChannelFeatures::ZeroReserve`]+	/// Sets the combination of [`TrustedChannelFeatures::ZeroConf`] and [`TrustedChannelFeatures::ZeroReserve`]
ZeroConfZeroReserve,
}

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@carlaKC! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

TheBlueMatt
TheBlueMatt previously approved these changes Mar 26, 2026

@TheBlueMattTheBlueMatt left a comment

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.

Happy to land this to get the API changes in now, but a few small tweaks we'll want to change/address before release.

Comment threadlightning/src/sign/tx_builder.rs Outdated
value_to_remote_after_htlcs: u64, channel_type: &ChannelTypeFeatures,
fn saturating_sub_from_funder(
is_outbound_from_holder: bool, value_to_holder: u64, value_to_counterparty: u64,
subtrahend: u64,

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've never heard of a subtrahend 😂

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

haha yes I looked it up do you have a suggestion ? value_to_subtract comes to mind :)

Comment threadlightning/src/ln/msgs.rs Outdated
(0, self.common_fields.shutdown_scriptpubkey.as_ref().map(|s| WithoutLength(s)), option), // Don't encode length twice.
(1, self.common_fields.channel_type, option),
(2, self.require_confirmed_inputs, option),
(3, self.disable_channel_reserve, option),

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 assume this needs a bLIP update? Given this is for a bLIP and not a BOLT (IIRC?) should we not be using a +1000 or whatever range?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This is a BOLT proposal. Using the staging bit makes sense to me yes.

Comment threadlightning/src/ln/channel.rs Outdated
let is_manual_broadcast = Some(self.context.is_manual_broadcast);

let has_0reserve =
(self.funding.counterparty_selected_channel_reserve_satoshis.is_some_and(|r| r == 0)

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.

We've always supported counterparty-selected reserve of 0, so I don't think we want to break downgrade for that?

carlaKC
carlaKC previously approved these changes Mar 26, 2026

@carlaKCcarlaKC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lgtm!

@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Thank you for the review ! Happy to hold off on the merge and get this PR just right :)

tankyleoand others added 10 commits March 26, 2026 15:15
We prevent downgrades from 0.3 only in the case where the
holder-selected reserve is 0, as we've had support for counterparty
selected 0-reserves in prior releases.
There is no need for this sentinel in `FundingScope` serialization code
as this would only apply to pending `FundingScope`'s.
Also, if the current scope has some zero-reserve, that reserve is
carried over to all pending scopes automatically. Therefore it is not
possible for a pending scope to have some 0-reserve without the current
one also having it.
This new flag sets 0-reserve for the channel opener.
This new method sets 0-reserve for the channel accepter.
We do not care if our balance drops below the counterparty-selected
reserve upon an inbound `update_add_htlc`. This is the counterparty's
problem.
Hence, we drop the assumption that once our balance rises above the
counterparty-selected reserve, it will always remain above this reserve
for the lifetime of a funding scope.
In the following commit, we make the assumption that the counterparty
does not complain if we push them below our selected reserve when adding
a HTLC, so we accommodate this assumption here.
Note that this currently does not match the spec as we use an odd TLV
for the `disable_channel_reserve` field in `open_channel2` and
`accept_channel2` msgs.
If the counterparty does not understand this field, that's ok as it just
means that the counterparty will not send some HTLCs we would have
accepted.
We make the assumption that the counterparty will not complain if we
send a HTLC that pushes their balance below our selected reserve; this
could happen if the counterparty is the funder of the channel. They
should not complain because if we push them below our selected reserve,
this is our problem.
`ChannelContext::do_accept_channel_checks`,
`ChannelContext::new_for_outbound_channel`,
`ChannelContext::new_for_inbound_channel`,
`InboundV1Channel::new`,
`OutboundV1Channel::new`.
Reduce line count and indentation
Convert format string arguments to inline `{var}` captures where
the argument is a simple identifier (variable or constant). Field
accesses, method calls, and expressions remain as positional args.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@tankyleo
tankyleo dismissed stale reviews from carlaKC and TheBlueMatt via 32a67f8March 26, 2026 15:32
@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch from 2967e6f to 32a67f8CompareMarch 26, 2026 15:32

@carlaKCcarlaKC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

RIP subtrahend, word of the week ✊

@TheBlueMatt
TheBlueMatt merged commit 688544d into lightningdevkit:mainMar 26, 2026
24 of 44 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsMar 26, 2026
@tankyleo
tankyleo deleted the 2026-02-zero-reserve branch March 26, 2026 17:52
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Support 0 counterparty channel reserve 0-output commitment tx with 0-reserve

6 participants

@tankyleo@ldk-reviews-bot@TheBlueMatt@ldk-claude-review-bot@joostjager@carlaKC
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Open and accept zero reserve channels - #4428

Merged
TheBlueMatt merged 12 commits into
lightningdevkit:mainfrom
tankyleo:2026-02-zero-reserve
Mar 26, 2026
Merged

Open and accept zero reserve channels#4428
TheBlueMatt merged 12 commits into
lightningdevkit:mainfrom
tankyleo:2026-02-zero-reserve

Conversation

@tankyleo

@tankyleotankyleo commented Feb 19, 2026

Copy link
Copy Markdown
Contributor

Fixes#1801

@ldk-reviews-bot

ldk-reviews-bot commented Feb 19, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @carlaKC 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.

@tankyleotankyleo moved this to Goal: Merge in Weekly GoalsFeb 19, 2026
@tankyleotankyleo self-assigned this Feb 19, 2026
@tankyleo
tankyleo marked this pull request as ready for review February 26, 2026 10:45
@tankyleotankyleo added this to the 0.3 milestone Feb 26, 2026
@codecov

codecovBot commented Feb 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.11696% with 170 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.17%. Comparing base (d830f10) to head (32a67f8).
⚠️ Report is 22 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs71.12%148 Missing and 3 partials ⚠️
lightning/src/ln/channelmanager.rs84.78%14 Missing ⚠️
lightning/src/ln/msgs.rs94.87%4 Missing ⚠️
lightning/src/sign/tx_builder.rs99.37%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4428 +/- ##
==========================================
- Coverage 86.18% 86.17% -0.02% 
==========================================
Files 160 160 Lines 107536 108046 +510 Branches 107536 108046 +510 ==========================================
+ Hits 92680 93105 +425 - Misses 12231 12321 +90 + Partials 2625 2620 -5 
FlagCoverage Δ
tests86.17% <80.11%> (-0.02%)⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@TheBlueMattTheBlueMatt left a comment

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.

chanmon_consistency needs to be updated to have a 0-reserve channel or two (I believe we now have three channels between each pair of peers, so we can just do it on a subset of them, in fact we could have three separate channel types for better coverage).

/// Creates a new outbound channel to the given remote node and with the given value.
///
/// The only difference between this method and [`ChannelManager::create_channel`] is that this method sets
/// the reserve the counterparty must keep at all times in the channel to zero. This allows the counterparty to

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.

nit: If that's the only difference let's say create_channel_to_trusted_peer_0_reserve? Nice to be explicit, imo.

Comment threadlightning/src/ln/channel.rs Outdated

let channel_value_satoshis =
our_funding_contribution_sats.saturating_add(msg.common_fields.funding_satoshis);
// TODO(zero_reserve): support reading and writing the `disable_channel_reserve` field

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.

Two questions. Shouldn't we check that if a channel has the 0-reserve feature bit and if it is fail if the user isn't accepting 0-reserve? Also why shouldn't we just set it now? I'm not sure we need to bother with a staging bit, really, honestly...

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Needs rebase now :/

@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch from 471ba8f to 253db4dCompareMarch 5, 2026 10:55
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Needs rebase now :/

Let me know if you prefer I rebase first

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Feel free to go ahead and rebase and squash, yea.

@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch 2 times, most recently from 5fa3a7c to 43be438CompareMarch 5, 2026 17:21
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Squash diff (do not click compare just above, I pushed the wrong branch, and later corrected it):

https://github.com/lightningdevkit/rust-lightning/compare/253db4d9a05cda43f74c2835448126d3205b27b8..43be438f70ba28ad7918e407026a78763ac2b368

@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch from 43be438 to 7fde002CompareMarch 5, 2026 17:36
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

git range-diff main 43be438 7fde002

1: bfba6e993 ! 1: b916c3596 Add inbound and outbound checks for zero reserve channels
@@ lightning/src/ln/channel.rs: impl FundingScope {
// New reserve values are based on the new channel value and are v2-specific
- let counterparty_selected_channel_reserve_satoshis =
-- Some(get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS));
+- get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS);
- let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
- post_channel_value,
- context.counterparty_dust_limit_satoshis,
@@ lightning/src/ln/channel.rs: impl FundingScope {
+ == 0
+ {
+ // If we previously had a 0-value reserve, continue with the same reserve
-+ Some(0)
++ 0
+ } else {
-+ Some(get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS))
++ get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS)
+ };
+ let holder_selected_channel_reserve_satoshis =
+ if prev_funding.holder_selected_channel_reserve_satoshis == 0 {
@@ lightning/src/sign/tx_builder.rs: fn get_next_commitment_stats(
);
@@ lightning/src/sign/tx_builder.rs: fn get_available_balances(
- let fee_spike_buffer_htlc =
if channel_type.supports_anchor_zero_fee_commitments() { 0 } else { 1 };
+ // Note that the feerate is 0 in zero-fee commitment channels, so this statement is a noop
- let local_feerate = feerate_per_kw
+ let spiked_feerate = feerate_per_kw
* if is_outbound_from_holder && !channel_type.supports_anchors_zero_fee_htlc_tx() {
2: 1f8ca6c66 = 2: 53fc9a354 Add 0-reserve to `accept_inbound_channel_from_trusted_peer`
3: 1b94f7cc3 = 3: da0520818 Add `ChannelManager::create_channel_to_trusted_peer_0reserve`
4: 19b8d7943 = 4: 1905f94e7 Shakedown zero reserve channels
5: de2366da4 = 5: 4e6a7527a Format `ChannelManager::create_channel_internal` and...
6: 43be438f7 = 6: 7fde002e7 Update `chanmon_consistency` to include 0FC and 0-reserve channels

@ldk-reviews-bot

Copy link
Copy Markdown

✅ Added second reviewer: @joostjager

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@joostjager! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

1 similar comment
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@joostjager! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @TheBlueMatt@joostjager! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@tankyleo
tankyleo requested review from carlaKC and removed request for joostjagerMarch 10, 2026 14:33
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 3rd Reminder

Hey @TheBlueMatt@carlaKC! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@carlaKCcarlaKC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

First pass - haven't gone through the tests yet.

Comment threadlightning/src/sign/tx_builder.rs Outdated
Comment threadlightning/src/sign/tx_builder.rs
Comment threadlightning/src/sign/tx_builder.rs Outdated
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channelmanager.rs Outdated
/// If it does not confirm before we decide to close the channel, or if the funding transaction
/// does not pay to the correct script the correct amount, *you will lose funds*.
///
/// # Zero-reserve

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Shouldn't we be setting the option_zero_reserve feature in lightning/bolts#1140?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Discussed offline, I'll hold off on signaling for now pending further spec discussions

@tankyleo
tankyleo requested a review from carlaKCMarch 13, 2026 12:50
The goal is to prevent any commitments with no outputs, since these are
not broadcastable.
@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch 2 times, most recently from 91565f1 to 2967e6fCompareMarch 25, 2026 23:23
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

git diff bbe7a62 2967e6f, all significant changes in commit 5eb530d

diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs
index 8f8ab4a9b..fa394ee6e 100644
--- a/lightning/src/ln/channel.rs+++ b/lightning/src/ln/channel.rs@@ -15586,6 +15586,10 @@
}
let is_manual_broadcast = Some(self.context.is_manual_broadcast);
+ let has_0reserve =+ (self.funding.counterparty_selected_channel_reserve_satoshis.is_some_and(|r| r == 0)+ || self.funding.holder_selected_channel_reserve_satoshis == 0)+ .then_some(());
let holder_commitment_point_previous_revoked =
self.holder_commitment_point.previous_revoked_point();
let holder_commitment_point_last_revoked =
@@ -15655,6 +15659,7 @@
// 65 was previously used for quiescent_action
(67, pending_outbound_held_htlc_flags, optional_vec), // Added in 0.2
(69, holding_cell_held_htlc_flags, optional_vec), // Added in 0.2
+ (70, has_0reserve, option), // Added in 0.3 to prevent downgrades
(71, holder_commitment_point_previous_revoked, option), // Added in 0.3
(73, holder_commitment_point_last_revoked, option), // Added in 0.3
(75, inbound_committed_update_adds, optional_vec),
@@ -16028,6 +16033,7 @@
let mut malformed_htlcs: Option<Vec<(u64, u16, [u8; 32])>> = None;
let mut monitor_pending_update_adds: Option<Vec<msgs::UpdateAddHTLC>> = None;
+ let mut _has_0reserve: Option<()> = None;
let mut holder_commitment_point_previous_revoked_opt: Option<PublicKey> = None;
let mut holder_commitment_point_last_revoked_opt: Option<PublicKey> = None;
let mut holder_commitment_point_current_opt: Option<PublicKey> = None;
@@ -16098,6 +16104,7 @@
// 65 quiescent_action: Added in 0.2; removed in 0.3
(67, pending_outbound_held_htlc_flags_opt, optional_vec), // Added in 0.2
(69, holding_cell_held_htlc_flags_opt, optional_vec), // Added in 0.2
+ (70, _has_0reserve, option), // Added in 0.3 to prevent downgrades
(71, holder_commitment_point_previous_revoked_opt, option), // Added in 0.3
(73, holder_commitment_point_last_revoked_opt, option), // Added in 0.3
(75, inbound_committed_update_adds_opt, optional_vec),
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index 432070129..184bc4052 100644
--- a/lightning/src/ln/channelmanager.rs+++ b/lightning/src/ln/channelmanager.rs@@ -3555,7 +3555,7 @@
///
/// Note that there is no guarantee that the counterparty accepts such a channel themselves.
ZeroReserve,
-	/// Sets combination of [`TrustedChannelFeatures::ZeroConf`] and [`TrustedChannelFeatures::ZeroReserve`]+	/// Sets the combination of [`TrustedChannelFeatures::ZeroConf`] and [`TrustedChannelFeatures::ZeroReserve`]
ZeroConfZeroReserve,
}

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@carlaKC! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

TheBlueMatt
TheBlueMatt previously approved these changes Mar 26, 2026

@TheBlueMattTheBlueMatt left a comment

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.

Happy to land this to get the API changes in now, but a few small tweaks we'll want to change/address before release.

Comment threadlightning/src/sign/tx_builder.rs Outdated
value_to_remote_after_htlcs: u64, channel_type: &ChannelTypeFeatures,
fn saturating_sub_from_funder(
is_outbound_from_holder: bool, value_to_holder: u64, value_to_counterparty: u64,
subtrahend: u64,

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've never heard of a subtrahend 😂

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

haha yes I looked it up do you have a suggestion ? value_to_subtract comes to mind :)

Comment threadlightning/src/ln/msgs.rs Outdated
(0, self.common_fields.shutdown_scriptpubkey.as_ref().map(|s| WithoutLength(s)), option), // Don't encode length twice.
(1, self.common_fields.channel_type, option),
(2, self.require_confirmed_inputs, option),
(3, self.disable_channel_reserve, option),

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 assume this needs a bLIP update? Given this is for a bLIP and not a BOLT (IIRC?) should we not be using a +1000 or whatever range?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This is a BOLT proposal. Using the staging bit makes sense to me yes.

Comment threadlightning/src/ln/channel.rs Outdated
let is_manual_broadcast = Some(self.context.is_manual_broadcast);

let has_0reserve =
(self.funding.counterparty_selected_channel_reserve_satoshis.is_some_and(|r| r == 0)

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.

We've always supported counterparty-selected reserve of 0, so I don't think we want to break downgrade for that?

carlaKC
carlaKC previously approved these changes Mar 26, 2026

@carlaKCcarlaKC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lgtm!

@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Thank you for the review ! Happy to hold off on the merge and get this PR just right :)

tankyleoand others added 10 commits March 26, 2026 15:15
We prevent downgrades from 0.3 only in the case where the
holder-selected reserve is 0, as we've had support for counterparty
selected 0-reserves in prior releases.
There is no need for this sentinel in `FundingScope` serialization code
as this would only apply to pending `FundingScope`'s.
Also, if the current scope has some zero-reserve, that reserve is
carried over to all pending scopes automatically. Therefore it is not
possible for a pending scope to have some 0-reserve without the current
one also having it.
This new flag sets 0-reserve for the channel opener.
This new method sets 0-reserve for the channel accepter.
We do not care if our balance drops below the counterparty-selected
reserve upon an inbound `update_add_htlc`. This is the counterparty's
problem.
Hence, we drop the assumption that once our balance rises above the
counterparty-selected reserve, it will always remain above this reserve
for the lifetime of a funding scope.
In the following commit, we make the assumption that the counterparty
does not complain if we push them below our selected reserve when adding
a HTLC, so we accommodate this assumption here.
Note that this currently does not match the spec as we use an odd TLV
for the `disable_channel_reserve` field in `open_channel2` and
`accept_channel2` msgs.
If the counterparty does not understand this field, that's ok as it just
means that the counterparty will not send some HTLCs we would have
accepted.
We make the assumption that the counterparty will not complain if we
send a HTLC that pushes their balance below our selected reserve; this
could happen if the counterparty is the funder of the channel. They
should not complain because if we push them below our selected reserve,
this is our problem.
`ChannelContext::do_accept_channel_checks`,
`ChannelContext::new_for_outbound_channel`,
`ChannelContext::new_for_inbound_channel`,
`InboundV1Channel::new`,
`OutboundV1Channel::new`.
Reduce line count and indentation
Convert format string arguments to inline `{var}` captures where
the argument is a simple identifier (variable or constant). Field
accesses, method calls, and expressions remain as positional args.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@tankyleo
tankyleo dismissed stale reviews from carlaKC and TheBlueMatt via 32a67f8March 26, 2026 15:32
@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch from 2967e6f to 32a67f8CompareMarch 26, 2026 15:32

@carlaKCcarlaKC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

RIP subtrahend, word of the week ✊

@TheBlueMatt
TheBlueMatt merged commit 688544d into lightningdevkit:mainMar 26, 2026
24 of 44 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsMar 26, 2026
@tankyleo
tankyleo deleted the 2026-02-zero-reserve branch March 26, 2026 17:52
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Support 0 counterparty channel reserve 0-output commitment tx with 0-reserve

6 participants

@tankyleo@ldk-reviews-bot@TheBlueMatt@ldk-claude-review-bot@joostjager@carlaKC
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Open and accept zero reserve channels - #4428

Merged
TheBlueMatt merged 12 commits into
lightningdevkit:mainfrom
tankyleo:2026-02-zero-reserve
Mar 26, 2026
Merged

Open and accept zero reserve channels#4428
TheBlueMatt merged 12 commits into
lightningdevkit:mainfrom
tankyleo:2026-02-zero-reserve

Conversation

@tankyleo

@tankyleotankyleo commented Feb 19, 2026

Copy link
Copy Markdown
Contributor

Fixes#1801

@ldk-reviews-bot

ldk-reviews-bot commented Feb 19, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @carlaKC 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.

@tankyleotankyleo moved this to Goal: Merge in Weekly GoalsFeb 19, 2026
@tankyleotankyleo self-assigned this Feb 19, 2026
@tankyleo
tankyleo marked this pull request as ready for review February 26, 2026 10:45
@tankyleotankyleo added this to the 0.3 milestone Feb 26, 2026
@codecov

codecovBot commented Feb 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.11696% with 170 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.17%. Comparing base (d830f10) to head (32a67f8).
⚠️ Report is 22 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs71.12%148 Missing and 3 partials ⚠️
lightning/src/ln/channelmanager.rs84.78%14 Missing ⚠️
lightning/src/ln/msgs.rs94.87%4 Missing ⚠️
lightning/src/sign/tx_builder.rs99.37%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4428 +/- ##
==========================================
- Coverage 86.18% 86.17% -0.02% 
==========================================
Files 160 160 Lines 107536 108046 +510 Branches 107536 108046 +510 ==========================================
+ Hits 92680 93105 +425 - Misses 12231 12321 +90 + Partials 2625 2620 -5 
FlagCoverage Δ
tests86.17% <80.11%> (-0.02%)⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@TheBlueMattTheBlueMatt left a comment

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.

chanmon_consistency needs to be updated to have a 0-reserve channel or two (I believe we now have three channels between each pair of peers, so we can just do it on a subset of them, in fact we could have three separate channel types for better coverage).

/// Creates a new outbound channel to the given remote node and with the given value.
///
/// The only difference between this method and [`ChannelManager::create_channel`] is that this method sets
/// the reserve the counterparty must keep at all times in the channel to zero. This allows the counterparty to

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.

nit: If that's the only difference let's say create_channel_to_trusted_peer_0_reserve? Nice to be explicit, imo.

Comment threadlightning/src/ln/channel.rs Outdated

let channel_value_satoshis =
our_funding_contribution_sats.saturating_add(msg.common_fields.funding_satoshis);
// TODO(zero_reserve): support reading and writing the `disable_channel_reserve` field

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.

Two questions. Shouldn't we check that if a channel has the 0-reserve feature bit and if it is fail if the user isn't accepting 0-reserve? Also why shouldn't we just set it now? I'm not sure we need to bother with a staging bit, really, honestly...

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Needs rebase now :/

@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch from 471ba8f to 253db4dCompareMarch 5, 2026 10:55
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Needs rebase now :/

Let me know if you prefer I rebase first

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Feel free to go ahead and rebase and squash, yea.

@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch 2 times, most recently from 5fa3a7c to 43be438CompareMarch 5, 2026 17:21
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Squash diff (do not click compare just above, I pushed the wrong branch, and later corrected it):

https://github.com/lightningdevkit/rust-lightning/compare/253db4d9a05cda43f74c2835448126d3205b27b8..43be438f70ba28ad7918e407026a78763ac2b368

@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch from 43be438 to 7fde002CompareMarch 5, 2026 17:36
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

git range-diff main 43be438 7fde002

1: bfba6e993 ! 1: b916c3596 Add inbound and outbound checks for zero reserve channels
@@ lightning/src/ln/channel.rs: impl FundingScope {
// New reserve values are based on the new channel value and are v2-specific
- let counterparty_selected_channel_reserve_satoshis =
-- Some(get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS));
+- get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS);
- let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
- post_channel_value,
- context.counterparty_dust_limit_satoshis,
@@ lightning/src/ln/channel.rs: impl FundingScope {
+ == 0
+ {
+ // If we previously had a 0-value reserve, continue with the same reserve
-+ Some(0)
++ 0
+ } else {
-+ Some(get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS))
++ get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS)
+ };
+ let holder_selected_channel_reserve_satoshis =
+ if prev_funding.holder_selected_channel_reserve_satoshis == 0 {
@@ lightning/src/sign/tx_builder.rs: fn get_next_commitment_stats(
);
@@ lightning/src/sign/tx_builder.rs: fn get_available_balances(
- let fee_spike_buffer_htlc =
if channel_type.supports_anchor_zero_fee_commitments() { 0 } else { 1 };
+ // Note that the feerate is 0 in zero-fee commitment channels, so this statement is a noop
- let local_feerate = feerate_per_kw
+ let spiked_feerate = feerate_per_kw
* if is_outbound_from_holder && !channel_type.supports_anchors_zero_fee_htlc_tx() {
2: 1f8ca6c66 = 2: 53fc9a354 Add 0-reserve to `accept_inbound_channel_from_trusted_peer`
3: 1b94f7cc3 = 3: da0520818 Add `ChannelManager::create_channel_to_trusted_peer_0reserve`
4: 19b8d7943 = 4: 1905f94e7 Shakedown zero reserve channels
5: de2366da4 = 5: 4e6a7527a Format `ChannelManager::create_channel_internal` and...
6: 43be438f7 = 6: 7fde002e7 Update `chanmon_consistency` to include 0FC and 0-reserve channels

@ldk-reviews-bot

Copy link
Copy Markdown

✅ Added second reviewer: @joostjager

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@joostjager! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

1 similar comment
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@joostjager! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @TheBlueMatt@joostjager! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@tankyleo
tankyleo requested review from carlaKC and removed request for joostjagerMarch 10, 2026 14:33
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 3rd Reminder

Hey @TheBlueMatt@carlaKC! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@carlaKCcarlaKC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

First pass - haven't gone through the tests yet.

Comment threadlightning/src/sign/tx_builder.rs Outdated
Comment threadlightning/src/sign/tx_builder.rs
Comment threadlightning/src/sign/tx_builder.rs Outdated
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channelmanager.rs Outdated
/// If it does not confirm before we decide to close the channel, or if the funding transaction
/// does not pay to the correct script the correct amount, *you will lose funds*.
///
/// # Zero-reserve

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Shouldn't we be setting the option_zero_reserve feature in lightning/bolts#1140?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Discussed offline, I'll hold off on signaling for now pending further spec discussions

@tankyleo
tankyleo requested a review from carlaKCMarch 13, 2026 12:50
The goal is to prevent any commitments with no outputs, since these are
not broadcastable.
@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch 2 times, most recently from 91565f1 to 2967e6fCompareMarch 25, 2026 23:23
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

git diff bbe7a62 2967e6f, all significant changes in commit 5eb530d

diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs
index 8f8ab4a9b..fa394ee6e 100644
--- a/lightning/src/ln/channel.rs+++ b/lightning/src/ln/channel.rs@@ -15586,6 +15586,10 @@
}
let is_manual_broadcast = Some(self.context.is_manual_broadcast);
+ let has_0reserve =+ (self.funding.counterparty_selected_channel_reserve_satoshis.is_some_and(|r| r == 0)+ || self.funding.holder_selected_channel_reserve_satoshis == 0)+ .then_some(());
let holder_commitment_point_previous_revoked =
self.holder_commitment_point.previous_revoked_point();
let holder_commitment_point_last_revoked =
@@ -15655,6 +15659,7 @@
// 65 was previously used for quiescent_action
(67, pending_outbound_held_htlc_flags, optional_vec), // Added in 0.2
(69, holding_cell_held_htlc_flags, optional_vec), // Added in 0.2
+ (70, has_0reserve, option), // Added in 0.3 to prevent downgrades
(71, holder_commitment_point_previous_revoked, option), // Added in 0.3
(73, holder_commitment_point_last_revoked, option), // Added in 0.3
(75, inbound_committed_update_adds, optional_vec),
@@ -16028,6 +16033,7 @@
let mut malformed_htlcs: Option<Vec<(u64, u16, [u8; 32])>> = None;
let mut monitor_pending_update_adds: Option<Vec<msgs::UpdateAddHTLC>> = None;
+ let mut _has_0reserve: Option<()> = None;
let mut holder_commitment_point_previous_revoked_opt: Option<PublicKey> = None;
let mut holder_commitment_point_last_revoked_opt: Option<PublicKey> = None;
let mut holder_commitment_point_current_opt: Option<PublicKey> = None;
@@ -16098,6 +16104,7 @@
// 65 quiescent_action: Added in 0.2; removed in 0.3
(67, pending_outbound_held_htlc_flags_opt, optional_vec), // Added in 0.2
(69, holding_cell_held_htlc_flags_opt, optional_vec), // Added in 0.2
+ (70, _has_0reserve, option), // Added in 0.3 to prevent downgrades
(71, holder_commitment_point_previous_revoked_opt, option), // Added in 0.3
(73, holder_commitment_point_last_revoked_opt, option), // Added in 0.3
(75, inbound_committed_update_adds_opt, optional_vec),
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index 432070129..184bc4052 100644
--- a/lightning/src/ln/channelmanager.rs+++ b/lightning/src/ln/channelmanager.rs@@ -3555,7 +3555,7 @@
///
/// Note that there is no guarantee that the counterparty accepts such a channel themselves.
ZeroReserve,
-	/// Sets combination of [`TrustedChannelFeatures::ZeroConf`] and [`TrustedChannelFeatures::ZeroReserve`]+	/// Sets the combination of [`TrustedChannelFeatures::ZeroConf`] and [`TrustedChannelFeatures::ZeroReserve`]
ZeroConfZeroReserve,
}

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@carlaKC! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

TheBlueMatt
TheBlueMatt previously approved these changes Mar 26, 2026

@TheBlueMattTheBlueMatt left a comment

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.

Happy to land this to get the API changes in now, but a few small tweaks we'll want to change/address before release.

Comment threadlightning/src/sign/tx_builder.rs Outdated
value_to_remote_after_htlcs: u64, channel_type: &ChannelTypeFeatures,
fn saturating_sub_from_funder(
is_outbound_from_holder: bool, value_to_holder: u64, value_to_counterparty: u64,
subtrahend: u64,

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've never heard of a subtrahend 😂

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

haha yes I looked it up do you have a suggestion ? value_to_subtract comes to mind :)

Comment threadlightning/src/ln/msgs.rs Outdated
(0, self.common_fields.shutdown_scriptpubkey.as_ref().map(|s| WithoutLength(s)), option), // Don't encode length twice.
(1, self.common_fields.channel_type, option),
(2, self.require_confirmed_inputs, option),
(3, self.disable_channel_reserve, option),

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 assume this needs a bLIP update? Given this is for a bLIP and not a BOLT (IIRC?) should we not be using a +1000 or whatever range?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This is a BOLT proposal. Using the staging bit makes sense to me yes.

Comment threadlightning/src/ln/channel.rs Outdated
let is_manual_broadcast = Some(self.context.is_manual_broadcast);

let has_0reserve =
(self.funding.counterparty_selected_channel_reserve_satoshis.is_some_and(|r| r == 0)

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.

We've always supported counterparty-selected reserve of 0, so I don't think we want to break downgrade for that?

carlaKC
carlaKC previously approved these changes Mar 26, 2026

@carlaKCcarlaKC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lgtm!

@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Thank you for the review ! Happy to hold off on the merge and get this PR just right :)

tankyleoand others added 10 commits March 26, 2026 15:15
We prevent downgrades from 0.3 only in the case where the
holder-selected reserve is 0, as we've had support for counterparty
selected 0-reserves in prior releases.
There is no need for this sentinel in `FundingScope` serialization code
as this would only apply to pending `FundingScope`'s.
Also, if the current scope has some zero-reserve, that reserve is
carried over to all pending scopes automatically. Therefore it is not
possible for a pending scope to have some 0-reserve without the current
one also having it.
This new flag sets 0-reserve for the channel opener.
This new method sets 0-reserve for the channel accepter.
We do not care if our balance drops below the counterparty-selected
reserve upon an inbound `update_add_htlc`. This is the counterparty's
problem.
Hence, we drop the assumption that once our balance rises above the
counterparty-selected reserve, it will always remain above this reserve
for the lifetime of a funding scope.
In the following commit, we make the assumption that the counterparty
does not complain if we push them below our selected reserve when adding
a HTLC, so we accommodate this assumption here.
Note that this currently does not match the spec as we use an odd TLV
for the `disable_channel_reserve` field in `open_channel2` and
`accept_channel2` msgs.
If the counterparty does not understand this field, that's ok as it just
means that the counterparty will not send some HTLCs we would have
accepted.
We make the assumption that the counterparty will not complain if we
send a HTLC that pushes their balance below our selected reserve; this
could happen if the counterparty is the funder of the channel. They
should not complain because if we push them below our selected reserve,
this is our problem.
`ChannelContext::do_accept_channel_checks`,
`ChannelContext::new_for_outbound_channel`,
`ChannelContext::new_for_inbound_channel`,
`InboundV1Channel::new`,
`OutboundV1Channel::new`.
Reduce line count and indentation
Convert format string arguments to inline `{var}` captures where
the argument is a simple identifier (variable or constant). Field
accesses, method calls, and expressions remain as positional args.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@tankyleo
tankyleo dismissed stale reviews from carlaKC and TheBlueMatt via 32a67f8March 26, 2026 15:32
@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch from 2967e6f to 32a67f8CompareMarch 26, 2026 15:32

@carlaKCcarlaKC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

RIP subtrahend, word of the week ✊

@TheBlueMatt
TheBlueMatt merged commit 688544d into lightningdevkit:mainMar 26, 2026
24 of 44 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsMar 26, 2026
@tankyleo
tankyleo deleted the 2026-02-zero-reserve branch March 26, 2026 17:52
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Support 0 counterparty channel reserve 0-output commitment tx with 0-reserve

6 participants

@tankyleo@ldk-reviews-bot@TheBlueMatt@ldk-claude-review-bot@joostjager@carlaKC
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Open and accept zero reserve channels - #4428

Merged
TheBlueMatt merged 12 commits into
lightningdevkit:mainfrom
tankyleo:2026-02-zero-reserve
Mar 26, 2026
Merged

Open and accept zero reserve channels#4428
TheBlueMatt merged 12 commits into
lightningdevkit:mainfrom
tankyleo:2026-02-zero-reserve

Conversation

@tankyleo

@tankyleotankyleo commented Feb 19, 2026

Copy link
Copy Markdown
Contributor

Fixes#1801

@ldk-reviews-bot

ldk-reviews-bot commented Feb 19, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @carlaKC 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.

@tankyleotankyleo moved this to Goal: Merge in Weekly GoalsFeb 19, 2026
@tankyleotankyleo self-assigned this Feb 19, 2026
@tankyleo
tankyleo marked this pull request as ready for review February 26, 2026 10:45
@tankyleotankyleo added this to the 0.3 milestone Feb 26, 2026
@codecov

codecovBot commented Feb 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.11696% with 170 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.17%. Comparing base (d830f10) to head (32a67f8).
⚠️ Report is 22 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs71.12%148 Missing and 3 partials ⚠️
lightning/src/ln/channelmanager.rs84.78%14 Missing ⚠️
lightning/src/ln/msgs.rs94.87%4 Missing ⚠️
lightning/src/sign/tx_builder.rs99.37%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4428 +/- ##
==========================================
- Coverage 86.18% 86.17% -0.02% 
==========================================
Files 160 160 Lines 107536 108046 +510 Branches 107536 108046 +510 ==========================================
+ Hits 92680 93105 +425 - Misses 12231 12321 +90 + Partials 2625 2620 -5 
FlagCoverage Δ
tests86.17% <80.11%> (-0.02%)⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@TheBlueMattTheBlueMatt left a comment

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.

chanmon_consistency needs to be updated to have a 0-reserve channel or two (I believe we now have three channels between each pair of peers, so we can just do it on a subset of them, in fact we could have three separate channel types for better coverage).

/// Creates a new outbound channel to the given remote node and with the given value.
///
/// The only difference between this method and [`ChannelManager::create_channel`] is that this method sets
/// the reserve the counterparty must keep at all times in the channel to zero. This allows the counterparty to

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.

nit: If that's the only difference let's say create_channel_to_trusted_peer_0_reserve? Nice to be explicit, imo.

Comment threadlightning/src/ln/channel.rs Outdated

let channel_value_satoshis =
our_funding_contribution_sats.saturating_add(msg.common_fields.funding_satoshis);
// TODO(zero_reserve): support reading and writing the `disable_channel_reserve` field

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.

Two questions. Shouldn't we check that if a channel has the 0-reserve feature bit and if it is fail if the user isn't accepting 0-reserve? Also why shouldn't we just set it now? I'm not sure we need to bother with a staging bit, really, honestly...

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Needs rebase now :/

@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch from 471ba8f to 253db4dCompareMarch 5, 2026 10:55
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Needs rebase now :/

Let me know if you prefer I rebase first

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Feel free to go ahead and rebase and squash, yea.

@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch 2 times, most recently from 5fa3a7c to 43be438CompareMarch 5, 2026 17:21
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Squash diff (do not click compare just above, I pushed the wrong branch, and later corrected it):

https://github.com/lightningdevkit/rust-lightning/compare/253db4d9a05cda43f74c2835448126d3205b27b8..43be438f70ba28ad7918e407026a78763ac2b368

@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch from 43be438 to 7fde002CompareMarch 5, 2026 17:36
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

git range-diff main 43be438 7fde002

1: bfba6e993 ! 1: b916c3596 Add inbound and outbound checks for zero reserve channels
@@ lightning/src/ln/channel.rs: impl FundingScope {
// New reserve values are based on the new channel value and are v2-specific
- let counterparty_selected_channel_reserve_satoshis =
-- Some(get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS));
+- get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS);
- let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
- post_channel_value,
- context.counterparty_dust_limit_satoshis,
@@ lightning/src/ln/channel.rs: impl FundingScope {
+ == 0
+ {
+ // If we previously had a 0-value reserve, continue with the same reserve
-+ Some(0)
++ 0
+ } else {
-+ Some(get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS))
++ get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS)
+ };
+ let holder_selected_channel_reserve_satoshis =
+ if prev_funding.holder_selected_channel_reserve_satoshis == 0 {
@@ lightning/src/sign/tx_builder.rs: fn get_next_commitment_stats(
);
@@ lightning/src/sign/tx_builder.rs: fn get_available_balances(
- let fee_spike_buffer_htlc =
if channel_type.supports_anchor_zero_fee_commitments() { 0 } else { 1 };
+ // Note that the feerate is 0 in zero-fee commitment channels, so this statement is a noop
- let local_feerate = feerate_per_kw
+ let spiked_feerate = feerate_per_kw
* if is_outbound_from_holder && !channel_type.supports_anchors_zero_fee_htlc_tx() {
2: 1f8ca6c66 = 2: 53fc9a354 Add 0-reserve to `accept_inbound_channel_from_trusted_peer`
3: 1b94f7cc3 = 3: da0520818 Add `ChannelManager::create_channel_to_trusted_peer_0reserve`
4: 19b8d7943 = 4: 1905f94e7 Shakedown zero reserve channels
5: de2366da4 = 5: 4e6a7527a Format `ChannelManager::create_channel_internal` and...
6: 43be438f7 = 6: 7fde002e7 Update `chanmon_consistency` to include 0FC and 0-reserve channels

@ldk-reviews-bot

Copy link
Copy Markdown

✅ Added second reviewer: @joostjager

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@joostjager! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

1 similar comment
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@joostjager! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @TheBlueMatt@joostjager! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@tankyleo
tankyleo requested review from carlaKC and removed request for joostjagerMarch 10, 2026 14:33
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 3rd Reminder

Hey @TheBlueMatt@carlaKC! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@carlaKCcarlaKC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

First pass - haven't gone through the tests yet.

Comment threadlightning/src/sign/tx_builder.rs Outdated
Comment threadlightning/src/sign/tx_builder.rs
Comment threadlightning/src/sign/tx_builder.rs Outdated
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channelmanager.rs Outdated
/// If it does not confirm before we decide to close the channel, or if the funding transaction
/// does not pay to the correct script the correct amount, *you will lose funds*.
///
/// # Zero-reserve

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Shouldn't we be setting the option_zero_reserve feature in lightning/bolts#1140?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Discussed offline, I'll hold off on signaling for now pending further spec discussions

@tankyleo
tankyleo requested a review from carlaKCMarch 13, 2026 12:50
The goal is to prevent any commitments with no outputs, since these are
not broadcastable.
@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch 2 times, most recently from 91565f1 to 2967e6fCompareMarch 25, 2026 23:23
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

git diff bbe7a62 2967e6f, all significant changes in commit 5eb530d

diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs
index 8f8ab4a9b..fa394ee6e 100644
--- a/lightning/src/ln/channel.rs+++ b/lightning/src/ln/channel.rs@@ -15586,6 +15586,10 @@
}
let is_manual_broadcast = Some(self.context.is_manual_broadcast);
+ let has_0reserve =+ (self.funding.counterparty_selected_channel_reserve_satoshis.is_some_and(|r| r == 0)+ || self.funding.holder_selected_channel_reserve_satoshis == 0)+ .then_some(());
let holder_commitment_point_previous_revoked =
self.holder_commitment_point.previous_revoked_point();
let holder_commitment_point_last_revoked =
@@ -15655,6 +15659,7 @@
// 65 was previously used for quiescent_action
(67, pending_outbound_held_htlc_flags, optional_vec), // Added in 0.2
(69, holding_cell_held_htlc_flags, optional_vec), // Added in 0.2
+ (70, has_0reserve, option), // Added in 0.3 to prevent downgrades
(71, holder_commitment_point_previous_revoked, option), // Added in 0.3
(73, holder_commitment_point_last_revoked, option), // Added in 0.3
(75, inbound_committed_update_adds, optional_vec),
@@ -16028,6 +16033,7 @@
let mut malformed_htlcs: Option<Vec<(u64, u16, [u8; 32])>> = None;
let mut monitor_pending_update_adds: Option<Vec<msgs::UpdateAddHTLC>> = None;
+ let mut _has_0reserve: Option<()> = None;
let mut holder_commitment_point_previous_revoked_opt: Option<PublicKey> = None;
let mut holder_commitment_point_last_revoked_opt: Option<PublicKey> = None;
let mut holder_commitment_point_current_opt: Option<PublicKey> = None;
@@ -16098,6 +16104,7 @@
// 65 quiescent_action: Added in 0.2; removed in 0.3
(67, pending_outbound_held_htlc_flags_opt, optional_vec), // Added in 0.2
(69, holding_cell_held_htlc_flags_opt, optional_vec), // Added in 0.2
+ (70, _has_0reserve, option), // Added in 0.3 to prevent downgrades
(71, holder_commitment_point_previous_revoked_opt, option), // Added in 0.3
(73, holder_commitment_point_last_revoked_opt, option), // Added in 0.3
(75, inbound_committed_update_adds_opt, optional_vec),
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index 432070129..184bc4052 100644
--- a/lightning/src/ln/channelmanager.rs+++ b/lightning/src/ln/channelmanager.rs@@ -3555,7 +3555,7 @@
///
/// Note that there is no guarantee that the counterparty accepts such a channel themselves.
ZeroReserve,
-	/// Sets combination of [`TrustedChannelFeatures::ZeroConf`] and [`TrustedChannelFeatures::ZeroReserve`]+	/// Sets the combination of [`TrustedChannelFeatures::ZeroConf`] and [`TrustedChannelFeatures::ZeroReserve`]
ZeroConfZeroReserve,
}

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@carlaKC! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

TheBlueMatt
TheBlueMatt previously approved these changes Mar 26, 2026

@TheBlueMattTheBlueMatt left a comment

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.

Happy to land this to get the API changes in now, but a few small tweaks we'll want to change/address before release.

Comment threadlightning/src/sign/tx_builder.rs Outdated
value_to_remote_after_htlcs: u64, channel_type: &ChannelTypeFeatures,
fn saturating_sub_from_funder(
is_outbound_from_holder: bool, value_to_holder: u64, value_to_counterparty: u64,
subtrahend: u64,

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've never heard of a subtrahend 😂

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

haha yes I looked it up do you have a suggestion ? value_to_subtract comes to mind :)

Comment threadlightning/src/ln/msgs.rs Outdated
(0, self.common_fields.shutdown_scriptpubkey.as_ref().map(|s| WithoutLength(s)), option), // Don't encode length twice.
(1, self.common_fields.channel_type, option),
(2, self.require_confirmed_inputs, option),
(3, self.disable_channel_reserve, option),

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 assume this needs a bLIP update? Given this is for a bLIP and not a BOLT (IIRC?) should we not be using a +1000 or whatever range?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This is a BOLT proposal. Using the staging bit makes sense to me yes.

Comment threadlightning/src/ln/channel.rs Outdated
let is_manual_broadcast = Some(self.context.is_manual_broadcast);

let has_0reserve =
(self.funding.counterparty_selected_channel_reserve_satoshis.is_some_and(|r| r == 0)

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.

We've always supported counterparty-selected reserve of 0, so I don't think we want to break downgrade for that?

carlaKC
carlaKC previously approved these changes Mar 26, 2026

@carlaKCcarlaKC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lgtm!

@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Thank you for the review ! Happy to hold off on the merge and get this PR just right :)

tankyleoand others added 10 commits March 26, 2026 15:15
We prevent downgrades from 0.3 only in the case where the
holder-selected reserve is 0, as we've had support for counterparty
selected 0-reserves in prior releases.
There is no need for this sentinel in `FundingScope` serialization code
as this would only apply to pending `FundingScope`'s.
Also, if the current scope has some zero-reserve, that reserve is
carried over to all pending scopes automatically. Therefore it is not
possible for a pending scope to have some 0-reserve without the current
one also having it.
This new flag sets 0-reserve for the channel opener.
This new method sets 0-reserve for the channel accepter.
We do not care if our balance drops below the counterparty-selected
reserve upon an inbound `update_add_htlc`. This is the counterparty's
problem.
Hence, we drop the assumption that once our balance rises above the
counterparty-selected reserve, it will always remain above this reserve
for the lifetime of a funding scope.
In the following commit, we make the assumption that the counterparty
does not complain if we push them below our selected reserve when adding
a HTLC, so we accommodate this assumption here.
Note that this currently does not match the spec as we use an odd TLV
for the `disable_channel_reserve` field in `open_channel2` and
`accept_channel2` msgs.
If the counterparty does not understand this field, that's ok as it just
means that the counterparty will not send some HTLCs we would have
accepted.
We make the assumption that the counterparty will not complain if we
send a HTLC that pushes their balance below our selected reserve; this
could happen if the counterparty is the funder of the channel. They
should not complain because if we push them below our selected reserve,
this is our problem.
`ChannelContext::do_accept_channel_checks`,
`ChannelContext::new_for_outbound_channel`,
`ChannelContext::new_for_inbound_channel`,
`InboundV1Channel::new`,
`OutboundV1Channel::new`.
Reduce line count and indentation
Convert format string arguments to inline `{var}` captures where
the argument is a simple identifier (variable or constant). Field
accesses, method calls, and expressions remain as positional args.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@tankyleo
tankyleo dismissed stale reviews from carlaKC and TheBlueMatt via 32a67f8March 26, 2026 15:32
@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch from 2967e6f to 32a67f8CompareMarch 26, 2026 15:32

@carlaKCcarlaKC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

RIP subtrahend, word of the week ✊

@TheBlueMatt
TheBlueMatt merged commit 688544d into lightningdevkit:mainMar 26, 2026
24 of 44 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsMar 26, 2026
@tankyleo
tankyleo deleted the 2026-02-zero-reserve branch March 26, 2026 17:52
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Support 0 counterparty channel reserve 0-output commitment tx with 0-reserve

6 participants

@tankyleo@ldk-reviews-bot@TheBlueMatt@ldk-claude-review-bot@joostjager@carlaKC
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Open and accept zero reserve channels - #4428

Merged
TheBlueMatt merged 12 commits into
lightningdevkit:mainfrom
tankyleo:2026-02-zero-reserve
Mar 26, 2026
Merged

Open and accept zero reserve channels#4428
TheBlueMatt merged 12 commits into
lightningdevkit:mainfrom
tankyleo:2026-02-zero-reserve

Conversation

@tankyleo

@tankyleotankyleo commented Feb 19, 2026

Copy link
Copy Markdown
Contributor

Fixes#1801

@ldk-reviews-bot

ldk-reviews-bot commented Feb 19, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @carlaKC 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.

@tankyleotankyleo moved this to Goal: Merge in Weekly GoalsFeb 19, 2026
@tankyleotankyleo self-assigned this Feb 19, 2026
@tankyleo
tankyleo marked this pull request as ready for review February 26, 2026 10:45
@tankyleotankyleo added this to the 0.3 milestone Feb 26, 2026
@codecov

codecovBot commented Feb 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.11696% with 170 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.17%. Comparing base (d830f10) to head (32a67f8).
⚠️ Report is 22 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs71.12%148 Missing and 3 partials ⚠️
lightning/src/ln/channelmanager.rs84.78%14 Missing ⚠️
lightning/src/ln/msgs.rs94.87%4 Missing ⚠️
lightning/src/sign/tx_builder.rs99.37%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4428 +/- ##
==========================================
- Coverage 86.18% 86.17% -0.02% 
==========================================
Files 160 160 Lines 107536 108046 +510 Branches 107536 108046 +510 ==========================================
+ Hits 92680 93105 +425 - Misses 12231 12321 +90 + Partials 2625 2620 -5 
FlagCoverage Δ
tests86.17% <80.11%> (-0.02%)⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@TheBlueMattTheBlueMatt left a comment

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.

chanmon_consistency needs to be updated to have a 0-reserve channel or two (I believe we now have three channels between each pair of peers, so we can just do it on a subset of them, in fact we could have three separate channel types for better coverage).

/// Creates a new outbound channel to the given remote node and with the given value.
///
/// The only difference between this method and [`ChannelManager::create_channel`] is that this method sets
/// the reserve the counterparty must keep at all times in the channel to zero. This allows the counterparty to

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.

nit: If that's the only difference let's say create_channel_to_trusted_peer_0_reserve? Nice to be explicit, imo.

Comment threadlightning/src/ln/channel.rs Outdated

let channel_value_satoshis =
our_funding_contribution_sats.saturating_add(msg.common_fields.funding_satoshis);
// TODO(zero_reserve): support reading and writing the `disable_channel_reserve` field

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.

Two questions. Shouldn't we check that if a channel has the 0-reserve feature bit and if it is fail if the user isn't accepting 0-reserve? Also why shouldn't we just set it now? I'm not sure we need to bother with a staging bit, really, honestly...

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Needs rebase now :/

@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch from 471ba8f to 253db4dCompareMarch 5, 2026 10:55
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Needs rebase now :/

Let me know if you prefer I rebase first

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Feel free to go ahead and rebase and squash, yea.

@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch 2 times, most recently from 5fa3a7c to 43be438CompareMarch 5, 2026 17:21
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Squash diff (do not click compare just above, I pushed the wrong branch, and later corrected it):

https://github.com/lightningdevkit/rust-lightning/compare/253db4d9a05cda43f74c2835448126d3205b27b8..43be438f70ba28ad7918e407026a78763ac2b368

@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch from 43be438 to 7fde002CompareMarch 5, 2026 17:36
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

git range-diff main 43be438 7fde002

1: bfba6e993 ! 1: b916c3596 Add inbound and outbound checks for zero reserve channels
@@ lightning/src/ln/channel.rs: impl FundingScope {
// New reserve values are based on the new channel value and are v2-specific
- let counterparty_selected_channel_reserve_satoshis =
-- Some(get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS));
+- get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS);
- let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
- post_channel_value,
- context.counterparty_dust_limit_satoshis,
@@ lightning/src/ln/channel.rs: impl FundingScope {
+ == 0
+ {
+ // If we previously had a 0-value reserve, continue with the same reserve
-+ Some(0)
++ 0
+ } else {
-+ Some(get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS))
++ get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS)
+ };
+ let holder_selected_channel_reserve_satoshis =
+ if prev_funding.holder_selected_channel_reserve_satoshis == 0 {
@@ lightning/src/sign/tx_builder.rs: fn get_next_commitment_stats(
);
@@ lightning/src/sign/tx_builder.rs: fn get_available_balances(
- let fee_spike_buffer_htlc =
if channel_type.supports_anchor_zero_fee_commitments() { 0 } else { 1 };
+ // Note that the feerate is 0 in zero-fee commitment channels, so this statement is a noop
- let local_feerate = feerate_per_kw
+ let spiked_feerate = feerate_per_kw
* if is_outbound_from_holder && !channel_type.supports_anchors_zero_fee_htlc_tx() {
2: 1f8ca6c66 = 2: 53fc9a354 Add 0-reserve to `accept_inbound_channel_from_trusted_peer`
3: 1b94f7cc3 = 3: da0520818 Add `ChannelManager::create_channel_to_trusted_peer_0reserve`
4: 19b8d7943 = 4: 1905f94e7 Shakedown zero reserve channels
5: de2366da4 = 5: 4e6a7527a Format `ChannelManager::create_channel_internal` and...
6: 43be438f7 = 6: 7fde002e7 Update `chanmon_consistency` to include 0FC and 0-reserve channels

@ldk-reviews-bot

Copy link
Copy Markdown

✅ Added second reviewer: @joostjager

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@joostjager! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

1 similar comment
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@joostjager! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @TheBlueMatt@joostjager! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@tankyleo
tankyleo requested review from carlaKC and removed request for joostjagerMarch 10, 2026 14:33
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 3rd Reminder

Hey @TheBlueMatt@carlaKC! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@carlaKCcarlaKC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

First pass - haven't gone through the tests yet.

Comment threadlightning/src/sign/tx_builder.rs Outdated
Comment threadlightning/src/sign/tx_builder.rs
Comment threadlightning/src/sign/tx_builder.rs Outdated
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channelmanager.rs Outdated
/// If it does not confirm before we decide to close the channel, or if the funding transaction
/// does not pay to the correct script the correct amount, *you will lose funds*.
///
/// # Zero-reserve

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Shouldn't we be setting the option_zero_reserve feature in lightning/bolts#1140?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Discussed offline, I'll hold off on signaling for now pending further spec discussions

@tankyleo
tankyleo requested a review from carlaKCMarch 13, 2026 12:50
The goal is to prevent any commitments with no outputs, since these are
not broadcastable.
@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch 2 times, most recently from 91565f1 to 2967e6fCompareMarch 25, 2026 23:23
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

git diff bbe7a62 2967e6f, all significant changes in commit 5eb530d

diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs
index 8f8ab4a9b..fa394ee6e 100644
--- a/lightning/src/ln/channel.rs+++ b/lightning/src/ln/channel.rs@@ -15586,6 +15586,10 @@
}
let is_manual_broadcast = Some(self.context.is_manual_broadcast);
+ let has_0reserve =+ (self.funding.counterparty_selected_channel_reserve_satoshis.is_some_and(|r| r == 0)+ || self.funding.holder_selected_channel_reserve_satoshis == 0)+ .then_some(());
let holder_commitment_point_previous_revoked =
self.holder_commitment_point.previous_revoked_point();
let holder_commitment_point_last_revoked =
@@ -15655,6 +15659,7 @@
// 65 was previously used for quiescent_action
(67, pending_outbound_held_htlc_flags, optional_vec), // Added in 0.2
(69, holding_cell_held_htlc_flags, optional_vec), // Added in 0.2
+ (70, has_0reserve, option), // Added in 0.3 to prevent downgrades
(71, holder_commitment_point_previous_revoked, option), // Added in 0.3
(73, holder_commitment_point_last_revoked, option), // Added in 0.3
(75, inbound_committed_update_adds, optional_vec),
@@ -16028,6 +16033,7 @@
let mut malformed_htlcs: Option<Vec<(u64, u16, [u8; 32])>> = None;
let mut monitor_pending_update_adds: Option<Vec<msgs::UpdateAddHTLC>> = None;
+ let mut _has_0reserve: Option<()> = None;
let mut holder_commitment_point_previous_revoked_opt: Option<PublicKey> = None;
let mut holder_commitment_point_last_revoked_opt: Option<PublicKey> = None;
let mut holder_commitment_point_current_opt: Option<PublicKey> = None;
@@ -16098,6 +16104,7 @@
// 65 quiescent_action: Added in 0.2; removed in 0.3
(67, pending_outbound_held_htlc_flags_opt, optional_vec), // Added in 0.2
(69, holding_cell_held_htlc_flags_opt, optional_vec), // Added in 0.2
+ (70, _has_0reserve, option), // Added in 0.3 to prevent downgrades
(71, holder_commitment_point_previous_revoked_opt, option), // Added in 0.3
(73, holder_commitment_point_last_revoked_opt, option), // Added in 0.3
(75, inbound_committed_update_adds_opt, optional_vec),
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index 432070129..184bc4052 100644
--- a/lightning/src/ln/channelmanager.rs+++ b/lightning/src/ln/channelmanager.rs@@ -3555,7 +3555,7 @@
///
/// Note that there is no guarantee that the counterparty accepts such a channel themselves.
ZeroReserve,
-	/// Sets combination of [`TrustedChannelFeatures::ZeroConf`] and [`TrustedChannelFeatures::ZeroReserve`]+	/// Sets the combination of [`TrustedChannelFeatures::ZeroConf`] and [`TrustedChannelFeatures::ZeroReserve`]
ZeroConfZeroReserve,
}

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@carlaKC! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

TheBlueMatt
TheBlueMatt previously approved these changes Mar 26, 2026

@TheBlueMattTheBlueMatt left a comment

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.

Happy to land this to get the API changes in now, but a few small tweaks we'll want to change/address before release.

Comment threadlightning/src/sign/tx_builder.rs Outdated
value_to_remote_after_htlcs: u64, channel_type: &ChannelTypeFeatures,
fn saturating_sub_from_funder(
is_outbound_from_holder: bool, value_to_holder: u64, value_to_counterparty: u64,
subtrahend: u64,

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've never heard of a subtrahend 😂

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

haha yes I looked it up do you have a suggestion ? value_to_subtract comes to mind :)

Comment threadlightning/src/ln/msgs.rs Outdated
(0, self.common_fields.shutdown_scriptpubkey.as_ref().map(|s| WithoutLength(s)), option), // Don't encode length twice.
(1, self.common_fields.channel_type, option),
(2, self.require_confirmed_inputs, option),
(3, self.disable_channel_reserve, option),

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 assume this needs a bLIP update? Given this is for a bLIP and not a BOLT (IIRC?) should we not be using a +1000 or whatever range?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This is a BOLT proposal. Using the staging bit makes sense to me yes.

Comment threadlightning/src/ln/channel.rs Outdated
let is_manual_broadcast = Some(self.context.is_manual_broadcast);

let has_0reserve =
(self.funding.counterparty_selected_channel_reserve_satoshis.is_some_and(|r| r == 0)

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.

We've always supported counterparty-selected reserve of 0, so I don't think we want to break downgrade for that?

carlaKC
carlaKC previously approved these changes Mar 26, 2026

@carlaKCcarlaKC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lgtm!

@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Thank you for the review ! Happy to hold off on the merge and get this PR just right :)

tankyleoand others added 10 commits March 26, 2026 15:15
We prevent downgrades from 0.3 only in the case where the
holder-selected reserve is 0, as we've had support for counterparty
selected 0-reserves in prior releases.
There is no need for this sentinel in `FundingScope` serialization code
as this would only apply to pending `FundingScope`'s.
Also, if the current scope has some zero-reserve, that reserve is
carried over to all pending scopes automatically. Therefore it is not
possible for a pending scope to have some 0-reserve without the current
one also having it.
This new flag sets 0-reserve for the channel opener.
This new method sets 0-reserve for the channel accepter.
We do not care if our balance drops below the counterparty-selected
reserve upon an inbound `update_add_htlc`. This is the counterparty's
problem.
Hence, we drop the assumption that once our balance rises above the
counterparty-selected reserve, it will always remain above this reserve
for the lifetime of a funding scope.
In the following commit, we make the assumption that the counterparty
does not complain if we push them below our selected reserve when adding
a HTLC, so we accommodate this assumption here.
Note that this currently does not match the spec as we use an odd TLV
for the `disable_channel_reserve` field in `open_channel2` and
`accept_channel2` msgs.
If the counterparty does not understand this field, that's ok as it just
means that the counterparty will not send some HTLCs we would have
accepted.
We make the assumption that the counterparty will not complain if we
send a HTLC that pushes their balance below our selected reserve; this
could happen if the counterparty is the funder of the channel. They
should not complain because if we push them below our selected reserve,
this is our problem.
`ChannelContext::do_accept_channel_checks`,
`ChannelContext::new_for_outbound_channel`,
`ChannelContext::new_for_inbound_channel`,
`InboundV1Channel::new`,
`OutboundV1Channel::new`.
Reduce line count and indentation
Convert format string arguments to inline `{var}` captures where
the argument is a simple identifier (variable or constant). Field
accesses, method calls, and expressions remain as positional args.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@tankyleo
tankyleo dismissed stale reviews from carlaKC and TheBlueMatt via 32a67f8March 26, 2026 15:32
@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch from 2967e6f to 32a67f8CompareMarch 26, 2026 15:32

@carlaKCcarlaKC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

RIP subtrahend, word of the week ✊

@TheBlueMatt
TheBlueMatt merged commit 688544d into lightningdevkit:mainMar 26, 2026
24 of 44 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsMar 26, 2026
@tankyleo
tankyleo deleted the 2026-02-zero-reserve branch March 26, 2026 17:52
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Support 0 counterparty channel reserve 0-output commitment tx with 0-reserve

6 participants

@tankyleo@ldk-reviews-bot@TheBlueMatt@ldk-claude-review-bot@joostjager@carlaKC
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Open and accept zero reserve channels - #4428

Merged
TheBlueMatt merged 12 commits into
lightningdevkit:mainfrom
tankyleo:2026-02-zero-reserve
Mar 26, 2026
Merged

Open and accept zero reserve channels#4428
TheBlueMatt merged 12 commits into
lightningdevkit:mainfrom
tankyleo:2026-02-zero-reserve

Conversation

@tankyleo

@tankyleotankyleo commented Feb 19, 2026

Copy link
Copy Markdown
Contributor

Fixes#1801

@ldk-reviews-bot

ldk-reviews-bot commented Feb 19, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @carlaKC 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.

@tankyleotankyleo moved this to Goal: Merge in Weekly GoalsFeb 19, 2026
@tankyleotankyleo self-assigned this Feb 19, 2026
@tankyleo
tankyleo marked this pull request as ready for review February 26, 2026 10:45
@tankyleotankyleo added this to the 0.3 milestone Feb 26, 2026
@codecov

codecovBot commented Feb 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.11696% with 170 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.17%. Comparing base (d830f10) to head (32a67f8).
⚠️ Report is 22 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs71.12%148 Missing and 3 partials ⚠️
lightning/src/ln/channelmanager.rs84.78%14 Missing ⚠️
lightning/src/ln/msgs.rs94.87%4 Missing ⚠️
lightning/src/sign/tx_builder.rs99.37%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4428 +/- ##
==========================================
- Coverage 86.18% 86.17% -0.02% 
==========================================
Files 160 160 Lines 107536 108046 +510 Branches 107536 108046 +510 ==========================================
+ Hits 92680 93105 +425 - Misses 12231 12321 +90 + Partials 2625 2620 -5 
FlagCoverage Δ
tests86.17% <80.11%> (-0.02%)⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@TheBlueMattTheBlueMatt left a comment

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.

chanmon_consistency needs to be updated to have a 0-reserve channel or two (I believe we now have three channels between each pair of peers, so we can just do it on a subset of them, in fact we could have three separate channel types for better coverage).

/// Creates a new outbound channel to the given remote node and with the given value.
///
/// The only difference between this method and [`ChannelManager::create_channel`] is that this method sets
/// the reserve the counterparty must keep at all times in the channel to zero. This allows the counterparty to

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.

nit: If that's the only difference let's say create_channel_to_trusted_peer_0_reserve? Nice to be explicit, imo.

Comment threadlightning/src/ln/channel.rs Outdated

let channel_value_satoshis =
our_funding_contribution_sats.saturating_add(msg.common_fields.funding_satoshis);
// TODO(zero_reserve): support reading and writing the `disable_channel_reserve` field

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.

Two questions. Shouldn't we check that if a channel has the 0-reserve feature bit and if it is fail if the user isn't accepting 0-reserve? Also why shouldn't we just set it now? I'm not sure we need to bother with a staging bit, really, honestly...

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Needs rebase now :/

@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch from 471ba8f to 253db4dCompareMarch 5, 2026 10:55
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Needs rebase now :/

Let me know if you prefer I rebase first

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Feel free to go ahead and rebase and squash, yea.

@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch 2 times, most recently from 5fa3a7c to 43be438CompareMarch 5, 2026 17:21
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Squash diff (do not click compare just above, I pushed the wrong branch, and later corrected it):

https://github.com/lightningdevkit/rust-lightning/compare/253db4d9a05cda43f74c2835448126d3205b27b8..43be438f70ba28ad7918e407026a78763ac2b368

@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch from 43be438 to 7fde002CompareMarch 5, 2026 17:36
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

git range-diff main 43be438 7fde002

1: bfba6e993 ! 1: b916c3596 Add inbound and outbound checks for zero reserve channels
@@ lightning/src/ln/channel.rs: impl FundingScope {
// New reserve values are based on the new channel value and are v2-specific
- let counterparty_selected_channel_reserve_satoshis =
-- Some(get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS));
+- get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS);
- let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
- post_channel_value,
- context.counterparty_dust_limit_satoshis,
@@ lightning/src/ln/channel.rs: impl FundingScope {
+ == 0
+ {
+ // If we previously had a 0-value reserve, continue with the same reserve
-+ Some(0)
++ 0
+ } else {
-+ Some(get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS))
++ get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS)
+ };
+ let holder_selected_channel_reserve_satoshis =
+ if prev_funding.holder_selected_channel_reserve_satoshis == 0 {
@@ lightning/src/sign/tx_builder.rs: fn get_next_commitment_stats(
);
@@ lightning/src/sign/tx_builder.rs: fn get_available_balances(
- let fee_spike_buffer_htlc =
if channel_type.supports_anchor_zero_fee_commitments() { 0 } else { 1 };
+ // Note that the feerate is 0 in zero-fee commitment channels, so this statement is a noop
- let local_feerate = feerate_per_kw
+ let spiked_feerate = feerate_per_kw
* if is_outbound_from_holder && !channel_type.supports_anchors_zero_fee_htlc_tx() {
2: 1f8ca6c66 = 2: 53fc9a354 Add 0-reserve to `accept_inbound_channel_from_trusted_peer`
3: 1b94f7cc3 = 3: da0520818 Add `ChannelManager::create_channel_to_trusted_peer_0reserve`
4: 19b8d7943 = 4: 1905f94e7 Shakedown zero reserve channels
5: de2366da4 = 5: 4e6a7527a Format `ChannelManager::create_channel_internal` and...
6: 43be438f7 = 6: 7fde002e7 Update `chanmon_consistency` to include 0FC and 0-reserve channels

@ldk-reviews-bot

Copy link
Copy Markdown

✅ Added second reviewer: @joostjager

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@joostjager! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

1 similar comment
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@joostjager! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @TheBlueMatt@joostjager! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@tankyleo
tankyleo requested review from carlaKC and removed request for joostjagerMarch 10, 2026 14:33
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 3rd Reminder

Hey @TheBlueMatt@carlaKC! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@carlaKCcarlaKC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

First pass - haven't gone through the tests yet.

Comment threadlightning/src/sign/tx_builder.rs Outdated
Comment threadlightning/src/sign/tx_builder.rs
Comment threadlightning/src/sign/tx_builder.rs Outdated
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channelmanager.rs Outdated
/// If it does not confirm before we decide to close the channel, or if the funding transaction
/// does not pay to the correct script the correct amount, *you will lose funds*.
///
/// # Zero-reserve

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Shouldn't we be setting the option_zero_reserve feature in lightning/bolts#1140?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Discussed offline, I'll hold off on signaling for now pending further spec discussions

@tankyleo
tankyleo requested a review from carlaKCMarch 13, 2026 12:50
The goal is to prevent any commitments with no outputs, since these are
not broadcastable.
@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch 2 times, most recently from 91565f1 to 2967e6fCompareMarch 25, 2026 23:23
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

git diff bbe7a62 2967e6f, all significant changes in commit 5eb530d

diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs
index 8f8ab4a9b..fa394ee6e 100644
--- a/lightning/src/ln/channel.rs+++ b/lightning/src/ln/channel.rs@@ -15586,6 +15586,10 @@
}
let is_manual_broadcast = Some(self.context.is_manual_broadcast);
+ let has_0reserve =+ (self.funding.counterparty_selected_channel_reserve_satoshis.is_some_and(|r| r == 0)+ || self.funding.holder_selected_channel_reserve_satoshis == 0)+ .then_some(());
let holder_commitment_point_previous_revoked =
self.holder_commitment_point.previous_revoked_point();
let holder_commitment_point_last_revoked =
@@ -15655,6 +15659,7 @@
// 65 was previously used for quiescent_action
(67, pending_outbound_held_htlc_flags, optional_vec), // Added in 0.2
(69, holding_cell_held_htlc_flags, optional_vec), // Added in 0.2
+ (70, has_0reserve, option), // Added in 0.3 to prevent downgrades
(71, holder_commitment_point_previous_revoked, option), // Added in 0.3
(73, holder_commitment_point_last_revoked, option), // Added in 0.3
(75, inbound_committed_update_adds, optional_vec),
@@ -16028,6 +16033,7 @@
let mut malformed_htlcs: Option<Vec<(u64, u16, [u8; 32])>> = None;
let mut monitor_pending_update_adds: Option<Vec<msgs::UpdateAddHTLC>> = None;
+ let mut _has_0reserve: Option<()> = None;
let mut holder_commitment_point_previous_revoked_opt: Option<PublicKey> = None;
let mut holder_commitment_point_last_revoked_opt: Option<PublicKey> = None;
let mut holder_commitment_point_current_opt: Option<PublicKey> = None;
@@ -16098,6 +16104,7 @@
// 65 quiescent_action: Added in 0.2; removed in 0.3
(67, pending_outbound_held_htlc_flags_opt, optional_vec), // Added in 0.2
(69, holding_cell_held_htlc_flags_opt, optional_vec), // Added in 0.2
+ (70, _has_0reserve, option), // Added in 0.3 to prevent downgrades
(71, holder_commitment_point_previous_revoked_opt, option), // Added in 0.3
(73, holder_commitment_point_last_revoked_opt, option), // Added in 0.3
(75, inbound_committed_update_adds_opt, optional_vec),
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index 432070129..184bc4052 100644
--- a/lightning/src/ln/channelmanager.rs+++ b/lightning/src/ln/channelmanager.rs@@ -3555,7 +3555,7 @@
///
/// Note that there is no guarantee that the counterparty accepts such a channel themselves.
ZeroReserve,
-	/// Sets combination of [`TrustedChannelFeatures::ZeroConf`] and [`TrustedChannelFeatures::ZeroReserve`]+	/// Sets the combination of [`TrustedChannelFeatures::ZeroConf`] and [`TrustedChannelFeatures::ZeroReserve`]
ZeroConfZeroReserve,
}

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@carlaKC! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

TheBlueMatt
TheBlueMatt previously approved these changes Mar 26, 2026

@TheBlueMattTheBlueMatt left a comment

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.

Happy to land this to get the API changes in now, but a few small tweaks we'll want to change/address before release.

Comment threadlightning/src/sign/tx_builder.rs Outdated
value_to_remote_after_htlcs: u64, channel_type: &ChannelTypeFeatures,
fn saturating_sub_from_funder(
is_outbound_from_holder: bool, value_to_holder: u64, value_to_counterparty: u64,
subtrahend: u64,

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've never heard of a subtrahend 😂

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

haha yes I looked it up do you have a suggestion ? value_to_subtract comes to mind :)

Comment threadlightning/src/ln/msgs.rs Outdated
(0, self.common_fields.shutdown_scriptpubkey.as_ref().map(|s| WithoutLength(s)), option), // Don't encode length twice.
(1, self.common_fields.channel_type, option),
(2, self.require_confirmed_inputs, option),
(3, self.disable_channel_reserve, option),

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 assume this needs a bLIP update? Given this is for a bLIP and not a BOLT (IIRC?) should we not be using a +1000 or whatever range?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This is a BOLT proposal. Using the staging bit makes sense to me yes.

Comment threadlightning/src/ln/channel.rs Outdated
let is_manual_broadcast = Some(self.context.is_manual_broadcast);

let has_0reserve =
(self.funding.counterparty_selected_channel_reserve_satoshis.is_some_and(|r| r == 0)

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.

We've always supported counterparty-selected reserve of 0, so I don't think we want to break downgrade for that?

carlaKC
carlaKC previously approved these changes Mar 26, 2026

@carlaKCcarlaKC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lgtm!

@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Thank you for the review ! Happy to hold off on the merge and get this PR just right :)

tankyleoand others added 10 commits March 26, 2026 15:15
We prevent downgrades from 0.3 only in the case where the
holder-selected reserve is 0, as we've had support for counterparty
selected 0-reserves in prior releases.
There is no need for this sentinel in `FundingScope` serialization code
as this would only apply to pending `FundingScope`'s.
Also, if the current scope has some zero-reserve, that reserve is
carried over to all pending scopes automatically. Therefore it is not
possible for a pending scope to have some 0-reserve without the current
one also having it.
This new flag sets 0-reserve for the channel opener.
This new method sets 0-reserve for the channel accepter.
We do not care if our balance drops below the counterparty-selected
reserve upon an inbound `update_add_htlc`. This is the counterparty's
problem.
Hence, we drop the assumption that once our balance rises above the
counterparty-selected reserve, it will always remain above this reserve
for the lifetime of a funding scope.
In the following commit, we make the assumption that the counterparty
does not complain if we push them below our selected reserve when adding
a HTLC, so we accommodate this assumption here.
Note that this currently does not match the spec as we use an odd TLV
for the `disable_channel_reserve` field in `open_channel2` and
`accept_channel2` msgs.
If the counterparty does not understand this field, that's ok as it just
means that the counterparty will not send some HTLCs we would have
accepted.
We make the assumption that the counterparty will not complain if we
send a HTLC that pushes their balance below our selected reserve; this
could happen if the counterparty is the funder of the channel. They
should not complain because if we push them below our selected reserve,
this is our problem.
`ChannelContext::do_accept_channel_checks`,
`ChannelContext::new_for_outbound_channel`,
`ChannelContext::new_for_inbound_channel`,
`InboundV1Channel::new`,
`OutboundV1Channel::new`.
Reduce line count and indentation
Convert format string arguments to inline `{var}` captures where
the argument is a simple identifier (variable or constant). Field
accesses, method calls, and expressions remain as positional args.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@tankyleo
tankyleo dismissed stale reviews from carlaKC and TheBlueMatt via 32a67f8March 26, 2026 15:32
@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch from 2967e6f to 32a67f8CompareMarch 26, 2026 15:32

@carlaKCcarlaKC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

RIP subtrahend, word of the week ✊

@TheBlueMatt
TheBlueMatt merged commit 688544d into lightningdevkit:mainMar 26, 2026
24 of 44 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsMar 26, 2026
@tankyleo
tankyleo deleted the 2026-02-zero-reserve branch March 26, 2026 17:52
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Support 0 counterparty channel reserve 0-output commitment tx with 0-reserve

6 participants

@tankyleo@ldk-reviews-bot@TheBlueMatt@ldk-claude-review-bot@joostjager@carlaKC
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Open and accept zero reserve channels - #4428

Merged
TheBlueMatt merged 12 commits into
lightningdevkit:mainfrom
tankyleo:2026-02-zero-reserve
Mar 26, 2026
Merged

Open and accept zero reserve channels#4428
TheBlueMatt merged 12 commits into
lightningdevkit:mainfrom
tankyleo:2026-02-zero-reserve

Conversation

@tankyleo

@tankyleotankyleo commented Feb 19, 2026

Copy link
Copy Markdown
Contributor

Fixes#1801

@ldk-reviews-bot

ldk-reviews-bot commented Feb 19, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @carlaKC 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.

@tankyleotankyleo moved this to Goal: Merge in Weekly GoalsFeb 19, 2026
@tankyleotankyleo self-assigned this Feb 19, 2026
@tankyleo
tankyleo marked this pull request as ready for review February 26, 2026 10:45
@tankyleotankyleo added this to the 0.3 milestone Feb 26, 2026
@codecov

codecovBot commented Feb 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.11696% with 170 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.17%. Comparing base (d830f10) to head (32a67f8).
⚠️ Report is 22 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs71.12%148 Missing and 3 partials ⚠️
lightning/src/ln/channelmanager.rs84.78%14 Missing ⚠️
lightning/src/ln/msgs.rs94.87%4 Missing ⚠️
lightning/src/sign/tx_builder.rs99.37%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4428 +/- ##
==========================================
- Coverage 86.18% 86.17% -0.02% 
==========================================
Files 160 160 Lines 107536 108046 +510 Branches 107536 108046 +510 ==========================================
+ Hits 92680 93105 +425 - Misses 12231 12321 +90 + Partials 2625 2620 -5 
FlagCoverage Δ
tests86.17% <80.11%> (-0.02%)⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@TheBlueMattTheBlueMatt left a comment

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.

chanmon_consistency needs to be updated to have a 0-reserve channel or two (I believe we now have three channels between each pair of peers, so we can just do it on a subset of them, in fact we could have three separate channel types for better coverage).

/// Creates a new outbound channel to the given remote node and with the given value.
///
/// The only difference between this method and [`ChannelManager::create_channel`] is that this method sets
/// the reserve the counterparty must keep at all times in the channel to zero. This allows the counterparty to

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.

nit: If that's the only difference let's say create_channel_to_trusted_peer_0_reserve? Nice to be explicit, imo.

Comment threadlightning/src/ln/channel.rs Outdated

let channel_value_satoshis =
our_funding_contribution_sats.saturating_add(msg.common_fields.funding_satoshis);
// TODO(zero_reserve): support reading and writing the `disable_channel_reserve` field

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.

Two questions. Shouldn't we check that if a channel has the 0-reserve feature bit and if it is fail if the user isn't accepting 0-reserve? Also why shouldn't we just set it now? I'm not sure we need to bother with a staging bit, really, honestly...

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Needs rebase now :/

@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch from 471ba8f to 253db4dCompareMarch 5, 2026 10:55
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Needs rebase now :/

Let me know if you prefer I rebase first

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Feel free to go ahead and rebase and squash, yea.

@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch 2 times, most recently from 5fa3a7c to 43be438CompareMarch 5, 2026 17:21
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Squash diff (do not click compare just above, I pushed the wrong branch, and later corrected it):

https://github.com/lightningdevkit/rust-lightning/compare/253db4d9a05cda43f74c2835448126d3205b27b8..43be438f70ba28ad7918e407026a78763ac2b368

@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch from 43be438 to 7fde002CompareMarch 5, 2026 17:36
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

git range-diff main 43be438 7fde002

1: bfba6e993 ! 1: b916c3596 Add inbound and outbound checks for zero reserve channels
@@ lightning/src/ln/channel.rs: impl FundingScope {
// New reserve values are based on the new channel value and are v2-specific
- let counterparty_selected_channel_reserve_satoshis =
-- Some(get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS));
+- get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS);
- let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
- post_channel_value,
- context.counterparty_dust_limit_satoshis,
@@ lightning/src/ln/channel.rs: impl FundingScope {
+ == 0
+ {
+ // If we previously had a 0-value reserve, continue with the same reserve
-+ Some(0)
++ 0
+ } else {
-+ Some(get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS))
++ get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS)
+ };
+ let holder_selected_channel_reserve_satoshis =
+ if prev_funding.holder_selected_channel_reserve_satoshis == 0 {
@@ lightning/src/sign/tx_builder.rs: fn get_next_commitment_stats(
);
@@ lightning/src/sign/tx_builder.rs: fn get_available_balances(
- let fee_spike_buffer_htlc =
if channel_type.supports_anchor_zero_fee_commitments() { 0 } else { 1 };
+ // Note that the feerate is 0 in zero-fee commitment channels, so this statement is a noop
- let local_feerate = feerate_per_kw
+ let spiked_feerate = feerate_per_kw
* if is_outbound_from_holder && !channel_type.supports_anchors_zero_fee_htlc_tx() {
2: 1f8ca6c66 = 2: 53fc9a354 Add 0-reserve to `accept_inbound_channel_from_trusted_peer`
3: 1b94f7cc3 = 3: da0520818 Add `ChannelManager::create_channel_to_trusted_peer_0reserve`
4: 19b8d7943 = 4: 1905f94e7 Shakedown zero reserve channels
5: de2366da4 = 5: 4e6a7527a Format `ChannelManager::create_channel_internal` and...
6: 43be438f7 = 6: 7fde002e7 Update `chanmon_consistency` to include 0FC and 0-reserve channels

@ldk-reviews-bot

Copy link
Copy Markdown

✅ Added second reviewer: @joostjager

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@joostjager! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

1 similar comment
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@joostjager! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @TheBlueMatt@joostjager! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@tankyleo
tankyleo requested review from carlaKC and removed request for joostjagerMarch 10, 2026 14:33
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 3rd Reminder

Hey @TheBlueMatt@carlaKC! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@carlaKCcarlaKC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

First pass - haven't gone through the tests yet.

Comment threadlightning/src/sign/tx_builder.rs Outdated
Comment threadlightning/src/sign/tx_builder.rs
Comment threadlightning/src/sign/tx_builder.rs Outdated
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channelmanager.rs Outdated
/// If it does not confirm before we decide to close the channel, or if the funding transaction
/// does not pay to the correct script the correct amount, *you will lose funds*.
///
/// # Zero-reserve

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Shouldn't we be setting the option_zero_reserve feature in lightning/bolts#1140?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Discussed offline, I'll hold off on signaling for now pending further spec discussions

@tankyleo
tankyleo requested a review from carlaKCMarch 13, 2026 12:50
The goal is to prevent any commitments with no outputs, since these are
not broadcastable.
@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch 2 times, most recently from 91565f1 to 2967e6fCompareMarch 25, 2026 23:23
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

git diff bbe7a62 2967e6f, all significant changes in commit 5eb530d

diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs
index 8f8ab4a9b..fa394ee6e 100644
--- a/lightning/src/ln/channel.rs+++ b/lightning/src/ln/channel.rs@@ -15586,6 +15586,10 @@
}
let is_manual_broadcast = Some(self.context.is_manual_broadcast);
+ let has_0reserve =+ (self.funding.counterparty_selected_channel_reserve_satoshis.is_some_and(|r| r == 0)+ || self.funding.holder_selected_channel_reserve_satoshis == 0)+ .then_some(());
let holder_commitment_point_previous_revoked =
self.holder_commitment_point.previous_revoked_point();
let holder_commitment_point_last_revoked =
@@ -15655,6 +15659,7 @@
// 65 was previously used for quiescent_action
(67, pending_outbound_held_htlc_flags, optional_vec), // Added in 0.2
(69, holding_cell_held_htlc_flags, optional_vec), // Added in 0.2
+ (70, has_0reserve, option), // Added in 0.3 to prevent downgrades
(71, holder_commitment_point_previous_revoked, option), // Added in 0.3
(73, holder_commitment_point_last_revoked, option), // Added in 0.3
(75, inbound_committed_update_adds, optional_vec),
@@ -16028,6 +16033,7 @@
let mut malformed_htlcs: Option<Vec<(u64, u16, [u8; 32])>> = None;
let mut monitor_pending_update_adds: Option<Vec<msgs::UpdateAddHTLC>> = None;
+ let mut _has_0reserve: Option<()> = None;
let mut holder_commitment_point_previous_revoked_opt: Option<PublicKey> = None;
let mut holder_commitment_point_last_revoked_opt: Option<PublicKey> = None;
let mut holder_commitment_point_current_opt: Option<PublicKey> = None;
@@ -16098,6 +16104,7 @@
// 65 quiescent_action: Added in 0.2; removed in 0.3
(67, pending_outbound_held_htlc_flags_opt, optional_vec), // Added in 0.2
(69, holding_cell_held_htlc_flags_opt, optional_vec), // Added in 0.2
+ (70, _has_0reserve, option), // Added in 0.3 to prevent downgrades
(71, holder_commitment_point_previous_revoked_opt, option), // Added in 0.3
(73, holder_commitment_point_last_revoked_opt, option), // Added in 0.3
(75, inbound_committed_update_adds_opt, optional_vec),
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index 432070129..184bc4052 100644
--- a/lightning/src/ln/channelmanager.rs+++ b/lightning/src/ln/channelmanager.rs@@ -3555,7 +3555,7 @@
///
/// Note that there is no guarantee that the counterparty accepts such a channel themselves.
ZeroReserve,
-	/// Sets combination of [`TrustedChannelFeatures::ZeroConf`] and [`TrustedChannelFeatures::ZeroReserve`]+	/// Sets the combination of [`TrustedChannelFeatures::ZeroConf`] and [`TrustedChannelFeatures::ZeroReserve`]
ZeroConfZeroReserve,
}

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@carlaKC! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

TheBlueMatt
TheBlueMatt previously approved these changes Mar 26, 2026

@TheBlueMattTheBlueMatt left a comment

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.

Happy to land this to get the API changes in now, but a few small tweaks we'll want to change/address before release.

Comment threadlightning/src/sign/tx_builder.rs Outdated
value_to_remote_after_htlcs: u64, channel_type: &ChannelTypeFeatures,
fn saturating_sub_from_funder(
is_outbound_from_holder: bool, value_to_holder: u64, value_to_counterparty: u64,
subtrahend: u64,

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've never heard of a subtrahend 😂

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

haha yes I looked it up do you have a suggestion ? value_to_subtract comes to mind :)

Comment threadlightning/src/ln/msgs.rs Outdated
(0, self.common_fields.shutdown_scriptpubkey.as_ref().map(|s| WithoutLength(s)), option), // Don't encode length twice.
(1, self.common_fields.channel_type, option),
(2, self.require_confirmed_inputs, option),
(3, self.disable_channel_reserve, option),

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 assume this needs a bLIP update? Given this is for a bLIP and not a BOLT (IIRC?) should we not be using a +1000 or whatever range?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This is a BOLT proposal. Using the staging bit makes sense to me yes.

Comment threadlightning/src/ln/channel.rs Outdated
let is_manual_broadcast = Some(self.context.is_manual_broadcast);

let has_0reserve =
(self.funding.counterparty_selected_channel_reserve_satoshis.is_some_and(|r| r == 0)

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.

We've always supported counterparty-selected reserve of 0, so I don't think we want to break downgrade for that?

carlaKC
carlaKC previously approved these changes Mar 26, 2026

@carlaKCcarlaKC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lgtm!

@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Thank you for the review ! Happy to hold off on the merge and get this PR just right :)

tankyleoand others added 10 commits March 26, 2026 15:15
We prevent downgrades from 0.3 only in the case where the
holder-selected reserve is 0, as we've had support for counterparty
selected 0-reserves in prior releases.
There is no need for this sentinel in `FundingScope` serialization code
as this would only apply to pending `FundingScope`'s.
Also, if the current scope has some zero-reserve, that reserve is
carried over to all pending scopes automatically. Therefore it is not
possible for a pending scope to have some 0-reserve without the current
one also having it.
This new flag sets 0-reserve for the channel opener.
This new method sets 0-reserve for the channel accepter.
We do not care if our balance drops below the counterparty-selected
reserve upon an inbound `update_add_htlc`. This is the counterparty's
problem.
Hence, we drop the assumption that once our balance rises above the
counterparty-selected reserve, it will always remain above this reserve
for the lifetime of a funding scope.
In the following commit, we make the assumption that the counterparty
does not complain if we push them below our selected reserve when adding
a HTLC, so we accommodate this assumption here.
Note that this currently does not match the spec as we use an odd TLV
for the `disable_channel_reserve` field in `open_channel2` and
`accept_channel2` msgs.
If the counterparty does not understand this field, that's ok as it just
means that the counterparty will not send some HTLCs we would have
accepted.
We make the assumption that the counterparty will not complain if we
send a HTLC that pushes their balance below our selected reserve; this
could happen if the counterparty is the funder of the channel. They
should not complain because if we push them below our selected reserve,
this is our problem.
`ChannelContext::do_accept_channel_checks`,
`ChannelContext::new_for_outbound_channel`,
`ChannelContext::new_for_inbound_channel`,
`InboundV1Channel::new`,
`OutboundV1Channel::new`.
Reduce line count and indentation
Convert format string arguments to inline `{var}` captures where
the argument is a simple identifier (variable or constant). Field
accesses, method calls, and expressions remain as positional args.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@tankyleo
tankyleo dismissed stale reviews from carlaKC and TheBlueMatt via 32a67f8March 26, 2026 15:32
@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch from 2967e6f to 32a67f8CompareMarch 26, 2026 15:32

@carlaKCcarlaKC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

RIP subtrahend, word of the week ✊

@TheBlueMatt
TheBlueMatt merged commit 688544d into lightningdevkit:mainMar 26, 2026
24 of 44 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsMar 26, 2026
@tankyleo
tankyleo deleted the 2026-02-zero-reserve branch March 26, 2026 17:52
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Support 0 counterparty channel reserve 0-output commitment tx with 0-reserve

6 participants

@tankyleo@ldk-reviews-bot@TheBlueMatt@ldk-claude-review-bot@joostjager@carlaKC
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Open and accept zero reserve channels - #4428

Merged
TheBlueMatt merged 12 commits into
lightningdevkit:mainfrom
tankyleo:2026-02-zero-reserve
Mar 26, 2026
Merged

Open and accept zero reserve channels#4428
TheBlueMatt merged 12 commits into
lightningdevkit:mainfrom
tankyleo:2026-02-zero-reserve

Conversation

@tankyleo

@tankyleotankyleo commented Feb 19, 2026

Copy link
Copy Markdown
Contributor

Fixes#1801

@ldk-reviews-bot

ldk-reviews-bot commented Feb 19, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @carlaKC 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.

@tankyleotankyleo moved this to Goal: Merge in Weekly GoalsFeb 19, 2026
@tankyleotankyleo self-assigned this Feb 19, 2026
@tankyleo
tankyleo marked this pull request as ready for review February 26, 2026 10:45
@tankyleotankyleo added this to the 0.3 milestone Feb 26, 2026
@codecov

codecovBot commented Feb 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.11696% with 170 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.17%. Comparing base (d830f10) to head (32a67f8).
⚠️ Report is 22 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs71.12%148 Missing and 3 partials ⚠️
lightning/src/ln/channelmanager.rs84.78%14 Missing ⚠️
lightning/src/ln/msgs.rs94.87%4 Missing ⚠️
lightning/src/sign/tx_builder.rs99.37%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4428 +/- ##
==========================================
- Coverage 86.18% 86.17% -0.02% 
==========================================
Files 160 160 Lines 107536 108046 +510 Branches 107536 108046 +510 ==========================================
+ Hits 92680 93105 +425 - Misses 12231 12321 +90 + Partials 2625 2620 -5 
FlagCoverage Δ
tests86.17% <80.11%> (-0.02%)⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@TheBlueMattTheBlueMatt left a comment

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.

chanmon_consistency needs to be updated to have a 0-reserve channel or two (I believe we now have three channels between each pair of peers, so we can just do it on a subset of them, in fact we could have three separate channel types for better coverage).

/// Creates a new outbound channel to the given remote node and with the given value.
///
/// The only difference between this method and [`ChannelManager::create_channel`] is that this method sets
/// the reserve the counterparty must keep at all times in the channel to zero. This allows the counterparty to

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.

nit: If that's the only difference let's say create_channel_to_trusted_peer_0_reserve? Nice to be explicit, imo.

Comment threadlightning/src/ln/channel.rs Outdated

let channel_value_satoshis =
our_funding_contribution_sats.saturating_add(msg.common_fields.funding_satoshis);
// TODO(zero_reserve): support reading and writing the `disable_channel_reserve` field

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.

Two questions. Shouldn't we check that if a channel has the 0-reserve feature bit and if it is fail if the user isn't accepting 0-reserve? Also why shouldn't we just set it now? I'm not sure we need to bother with a staging bit, really, honestly...

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Needs rebase now :/

@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch from 471ba8f to 253db4dCompareMarch 5, 2026 10:55
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Needs rebase now :/

Let me know if you prefer I rebase first

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Feel free to go ahead and rebase and squash, yea.

@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch 2 times, most recently from 5fa3a7c to 43be438CompareMarch 5, 2026 17:21
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Squash diff (do not click compare just above, I pushed the wrong branch, and later corrected it):

https://github.com/lightningdevkit/rust-lightning/compare/253db4d9a05cda43f74c2835448126d3205b27b8..43be438f70ba28ad7918e407026a78763ac2b368

@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch from 43be438 to 7fde002CompareMarch 5, 2026 17:36
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

git range-diff main 43be438 7fde002

1: bfba6e993 ! 1: b916c3596 Add inbound and outbound checks for zero reserve channels
@@ lightning/src/ln/channel.rs: impl FundingScope {
// New reserve values are based on the new channel value and are v2-specific
- let counterparty_selected_channel_reserve_satoshis =
-- Some(get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS));
+- get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS);
- let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
- post_channel_value,
- context.counterparty_dust_limit_satoshis,
@@ lightning/src/ln/channel.rs: impl FundingScope {
+ == 0
+ {
+ // If we previously had a 0-value reserve, continue with the same reserve
-+ Some(0)
++ 0
+ } else {
-+ Some(get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS))
++ get_v2_channel_reserve_satoshis(post_channel_value, MIN_CHAN_DUST_LIMIT_SATOSHIS)
+ };
+ let holder_selected_channel_reserve_satoshis =
+ if prev_funding.holder_selected_channel_reserve_satoshis == 0 {
@@ lightning/src/sign/tx_builder.rs: fn get_next_commitment_stats(
);
@@ lightning/src/sign/tx_builder.rs: fn get_available_balances(
- let fee_spike_buffer_htlc =
if channel_type.supports_anchor_zero_fee_commitments() { 0 } else { 1 };
+ // Note that the feerate is 0 in zero-fee commitment channels, so this statement is a noop
- let local_feerate = feerate_per_kw
+ let spiked_feerate = feerate_per_kw
* if is_outbound_from_holder && !channel_type.supports_anchors_zero_fee_htlc_tx() {
2: 1f8ca6c66 = 2: 53fc9a354 Add 0-reserve to `accept_inbound_channel_from_trusted_peer`
3: 1b94f7cc3 = 3: da0520818 Add `ChannelManager::create_channel_to_trusted_peer_0reserve`
4: 19b8d7943 = 4: 1905f94e7 Shakedown zero reserve channels
5: de2366da4 = 5: 4e6a7527a Format `ChannelManager::create_channel_internal` and...
6: 43be438f7 = 6: 7fde002e7 Update `chanmon_consistency` to include 0FC and 0-reserve channels

@ldk-reviews-bot

Copy link
Copy Markdown

✅ Added second reviewer: @joostjager

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@joostjager! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

1 similar comment
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@joostjager! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @TheBlueMatt@joostjager! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@tankyleo
tankyleo requested review from carlaKC and removed request for joostjagerMarch 10, 2026 14:33
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 3rd Reminder

Hey @TheBlueMatt@carlaKC! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@carlaKCcarlaKC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

First pass - haven't gone through the tests yet.

Comment threadlightning/src/sign/tx_builder.rs Outdated
Comment threadlightning/src/sign/tx_builder.rs
Comment threadlightning/src/sign/tx_builder.rs Outdated
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channelmanager.rs Outdated
/// If it does not confirm before we decide to close the channel, or if the funding transaction
/// does not pay to the correct script the correct amount, *you will lose funds*.
///
/// # Zero-reserve

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Shouldn't we be setting the option_zero_reserve feature in lightning/bolts#1140?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Discussed offline, I'll hold off on signaling for now pending further spec discussions

@tankyleo
tankyleo requested a review from carlaKCMarch 13, 2026 12:50
The goal is to prevent any commitments with no outputs, since these are
not broadcastable.
@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch 2 times, most recently from 91565f1 to 2967e6fCompareMarch 25, 2026 23:23
@tankyleo

Copy link
Copy Markdown
ContributorAuthor

git diff bbe7a62 2967e6f, all significant changes in commit 5eb530d

diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs
index 8f8ab4a9b..fa394ee6e 100644
--- a/lightning/src/ln/channel.rs+++ b/lightning/src/ln/channel.rs@@ -15586,6 +15586,10 @@
}
let is_manual_broadcast = Some(self.context.is_manual_broadcast);
+ let has_0reserve =+ (self.funding.counterparty_selected_channel_reserve_satoshis.is_some_and(|r| r == 0)+ || self.funding.holder_selected_channel_reserve_satoshis == 0)+ .then_some(());
let holder_commitment_point_previous_revoked =
self.holder_commitment_point.previous_revoked_point();
let holder_commitment_point_last_revoked =
@@ -15655,6 +15659,7 @@
// 65 was previously used for quiescent_action
(67, pending_outbound_held_htlc_flags, optional_vec), // Added in 0.2
(69, holding_cell_held_htlc_flags, optional_vec), // Added in 0.2
+ (70, has_0reserve, option), // Added in 0.3 to prevent downgrades
(71, holder_commitment_point_previous_revoked, option), // Added in 0.3
(73, holder_commitment_point_last_revoked, option), // Added in 0.3
(75, inbound_committed_update_adds, optional_vec),
@@ -16028,6 +16033,7 @@
let mut malformed_htlcs: Option<Vec<(u64, u16, [u8; 32])>> = None;
let mut monitor_pending_update_adds: Option<Vec<msgs::UpdateAddHTLC>> = None;
+ let mut _has_0reserve: Option<()> = None;
let mut holder_commitment_point_previous_revoked_opt: Option<PublicKey> = None;
let mut holder_commitment_point_last_revoked_opt: Option<PublicKey> = None;
let mut holder_commitment_point_current_opt: Option<PublicKey> = None;
@@ -16098,6 +16104,7 @@
// 65 quiescent_action: Added in 0.2; removed in 0.3
(67, pending_outbound_held_htlc_flags_opt, optional_vec), // Added in 0.2
(69, holding_cell_held_htlc_flags_opt, optional_vec), // Added in 0.2
+ (70, _has_0reserve, option), // Added in 0.3 to prevent downgrades
(71, holder_commitment_point_previous_revoked_opt, option), // Added in 0.3
(73, holder_commitment_point_last_revoked_opt, option), // Added in 0.3
(75, inbound_committed_update_adds_opt, optional_vec),
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index 432070129..184bc4052 100644
--- a/lightning/src/ln/channelmanager.rs+++ b/lightning/src/ln/channelmanager.rs@@ -3555,7 +3555,7 @@
///
/// Note that there is no guarantee that the counterparty accepts such a channel themselves.
ZeroReserve,
-	/// Sets combination of [`TrustedChannelFeatures::ZeroConf`] and [`TrustedChannelFeatures::ZeroReserve`]+	/// Sets the combination of [`TrustedChannelFeatures::ZeroConf`] and [`TrustedChannelFeatures::ZeroReserve`]
ZeroConfZeroReserve,
}

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@carlaKC! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

TheBlueMatt
TheBlueMatt previously approved these changes Mar 26, 2026

@TheBlueMattTheBlueMatt left a comment

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.

Happy to land this to get the API changes in now, but a few small tweaks we'll want to change/address before release.

Comment threadlightning/src/sign/tx_builder.rs Outdated
value_to_remote_after_htlcs: u64, channel_type: &ChannelTypeFeatures,
fn saturating_sub_from_funder(
is_outbound_from_holder: bool, value_to_holder: u64, value_to_counterparty: u64,
subtrahend: u64,

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've never heard of a subtrahend 😂

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

haha yes I looked it up do you have a suggestion ? value_to_subtract comes to mind :)

Comment threadlightning/src/ln/msgs.rs Outdated
(0, self.common_fields.shutdown_scriptpubkey.as_ref().map(|s| WithoutLength(s)), option), // Don't encode length twice.
(1, self.common_fields.channel_type, option),
(2, self.require_confirmed_inputs, option),
(3, self.disable_channel_reserve, option),

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 assume this needs a bLIP update? Given this is for a bLIP and not a BOLT (IIRC?) should we not be using a +1000 or whatever range?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This is a BOLT proposal. Using the staging bit makes sense to me yes.

Comment threadlightning/src/ln/channel.rs Outdated
let is_manual_broadcast = Some(self.context.is_manual_broadcast);

let has_0reserve =
(self.funding.counterparty_selected_channel_reserve_satoshis.is_some_and(|r| r == 0)

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.

We've always supported counterparty-selected reserve of 0, so I don't think we want to break downgrade for that?

carlaKC
carlaKC previously approved these changes Mar 26, 2026

@carlaKCcarlaKC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lgtm!

@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Thank you for the review ! Happy to hold off on the merge and get this PR just right :)

tankyleoand others added 10 commits March 26, 2026 15:15
We prevent downgrades from 0.3 only in the case where the
holder-selected reserve is 0, as we've had support for counterparty
selected 0-reserves in prior releases.
There is no need for this sentinel in `FundingScope` serialization code
as this would only apply to pending `FundingScope`'s.
Also, if the current scope has some zero-reserve, that reserve is
carried over to all pending scopes automatically. Therefore it is not
possible for a pending scope to have some 0-reserve without the current
one also having it.
This new flag sets 0-reserve for the channel opener.
This new method sets 0-reserve for the channel accepter.
We do not care if our balance drops below the counterparty-selected
reserve upon an inbound `update_add_htlc`. This is the counterparty's
problem.
Hence, we drop the assumption that once our balance rises above the
counterparty-selected reserve, it will always remain above this reserve
for the lifetime of a funding scope.
In the following commit, we make the assumption that the counterparty
does not complain if we push them below our selected reserve when adding
a HTLC, so we accommodate this assumption here.
Note that this currently does not match the spec as we use an odd TLV
for the `disable_channel_reserve` field in `open_channel2` and
`accept_channel2` msgs.
If the counterparty does not understand this field, that's ok as it just
means that the counterparty will not send some HTLCs we would have
accepted.
We make the assumption that the counterparty will not complain if we
send a HTLC that pushes their balance below our selected reserve; this
could happen if the counterparty is the funder of the channel. They
should not complain because if we push them below our selected reserve,
this is our problem.
`ChannelContext::do_accept_channel_checks`,
`ChannelContext::new_for_outbound_channel`,
`ChannelContext::new_for_inbound_channel`,
`InboundV1Channel::new`,
`OutboundV1Channel::new`.
Reduce line count and indentation
Convert format string arguments to inline `{var}` captures where
the argument is a simple identifier (variable or constant). Field
accesses, method calls, and expressions remain as positional args.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@tankyleo
tankyleo dismissed stale reviews from carlaKC and TheBlueMatt via 32a67f8March 26, 2026 15:32
@tankyleo
tankyleoforce-pushed the 2026-02-zero-reserve branch from 2967e6f to 32a67f8CompareMarch 26, 2026 15:32

@carlaKCcarlaKC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

RIP subtrahend, word of the week ✊

@TheBlueMatt
TheBlueMatt merged commit 688544d into lightningdevkit:mainMar 26, 2026
24 of 44 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsMar 26, 2026
@tankyleo
tankyleo deleted the 2026-02-zero-reserve branch March 26, 2026 17:52
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Support 0 counterparty channel reserve 0-output commitment tx with 0-reserve

6 participants

@tankyleo@ldk-reviews-bot@TheBlueMatt@ldk-claude-review-bot@joostjager@carlaKC