Open and accept zero reserve channels#4428
Open and accept zero reserve channels#4428TheBlueMatt merged 12 commits intolightningdevkit:mainfrom
Conversation
|
👋 Thanks for assigning @carlaKC as a reviewer! |
ffa1657 to
5fa3a7c
Compare
Codecov Report❌ Patch coverage is 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
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
TheBlueMatt
left a comment
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
nit: If that's the only difference let's say create_channel_to_trusted_peer_0_reserve? Nice to be explicit, imo.
lightning/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 |
There was a problem hiding this comment.
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...
|
Needs rebase now :/ |
471ba8f to
253db4d
Compare
Let me know if you prefer I rebase first |
|
Feel free to go ahead and rebase and squash, yea. |
5fa3a7c to
43be438
Compare
|
Squash diff (do not click compare just above, I pushed the wrong branch, and later corrected it): |
43be438 to
7fde002
Compare
|
|
|
✅ Added second reviewer: @joostjager |
|
🔔 1st Reminder Hey @TheBlueMatt @joostjager! This PR has been waiting for your review. |
1 similar comment
|
🔔 1st Reminder Hey @TheBlueMatt @joostjager! This PR has been waiting for your review. |
|
🔔 2nd Reminder Hey @TheBlueMatt @joostjager! This PR has been waiting for your review. |
|
🔔 3rd Reminder Hey @TheBlueMatt @carlaKC! This PR has been waiting for your review. |
carlaKC
left a comment
There was a problem hiding this comment.
First pass - haven't gone through the tests yet.
lightning/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 |
There was a problem hiding this comment.
Shouldn't we be setting the option_zero_reserve feature in lightning/bolts#1140?
There was a problem hiding this comment.
Discussed offline, I'll hold off on signaling for now pending further spec discussions
|
I think the changes here LGTM. Feel free to squash from my side, once @carlaKC says good I think we're good. |
The floor for *our* selected reserve is *their* dust limit.
The goal is to prevent any commitments with no outputs, since these are not broadcastable.
91565f1 to
2967e6f
Compare
|
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,
} |
|
🔔 1st Reminder Hey @TheBlueMatt @carlaKC! This PR has been waiting for your review. |
TheBlueMatt
left a comment
There was a problem hiding this comment.
Happy to land this to get the API changes in now, but a few small tweaks we'll want to change/address before release.
lightning/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, |
There was a problem hiding this comment.
I've never heard of a subtrahend 😂
There was a problem hiding this comment.
haha yes I looked it up do you have a suggestion ? value_to_subtract comes to mind :)
lightning/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), |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
This is a BOLT proposal. Using the staging bit makes sense to me yes.
lightning/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) |
There was a problem hiding this comment.
We've always supported counterparty-selected reserve of 0, so I don't think we want to break downgrade for that?
|
Thank you for the review ! Happy to hold off on the merge and get this PR just right :) |
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.
Co-Authored-By: HAL 9000
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>
2967e6f to
32a67f8
Compare
carlaKC
left a comment
There was a problem hiding this comment.
RIP subtrahend, word of the week ✊
Fixes #1801