Open and accept zero reserve channels#4428
Open and accept zero reserve channels#4428tankyleo wants to merge 9 commits intolightningdevkit:mainfrom
Conversation
|
👋 I see @TheBlueMatt was un-assigned. |
ffa1657 to
5fa3a7c
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #4428 +/- ##
==========================================
- Coverage 85.94% 85.91% -0.03%
==========================================
Files 159 159
Lines 104644 105103 +459
Branches 104644 105103 +459
==========================================
+ Hits 89934 90298 +364
- Misses 12204 12300 +96
+ Partials 2506 2505 -1
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.
|
|
||
| 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): |
The goal is to prevent any commitments with no outputs, since these are not broadcastable.
This new flag sets 0-reserve for the channel opener.
This new method sets 0-reserve for the channel accepter.
`ChannelContext::do_accept_channel_checks`, `ChannelContext::new_for_outbound_channel`, `ChannelContext::new_for_inbound_channel`, `InboundV1Channel::new`, `OutboundV1Channel::new`.
Co-Authored-By: HAL 9000
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/sign/tx_builder.rs
Outdated
| let dust_limit_msat = dust_limit_satoshis.saturating_mul(1000); | ||
| if holder_balance_msat.saturating_sub(max_dust_htlc_msat) < dust_limit_msat | ||
| && counterparty_balance_msat < dust_limit_msat | ||
| && nondust_htlc_count == 0 |
There was a problem hiding this comment.
Can we re-use check_no_outputs here? Could also return early if we don't need any action.
We'd need to change it to a saturating_sub, but I think that's okay because we have a checked_sub check of our balances on 321 after calling check_no_outputs anyway?
There was a problem hiding this comment.
done below thank you !
lightning/src/sign/tx_builder.rs
Outdated
| // 1) The dust_limit_satoshis plus the fee of the exisiting commitment at the spiked feerate. | ||
| // 2) The fee of the commitment with an additional non-dust HTLC, aka the fee spike buffer HTLC. | ||
| // In this case we don't mind the holder balance output dropping below the dust limit, as | ||
| // this additional non-dust HTLC will create the single remaining output on the commitment. | ||
| let min_balance_msat = | ||
| cmp::max(dust_limit_satoshis + tx_fee_sat, fee_spike_buffer_sat) * 1000; |
There was a problem hiding this comment.
Comment for (1) says "at the spiked feerate" but isn't tx_fee_sat is just our current feerate?
A more descriptive name would be helpful here (if you can thing of something less gross than max_output_preserving_fee?).
There was a problem hiding this comment.
Comment for (1) says "at the spiked feerate" but isn't
tx_fee_satis just our current feerate?
See the callsites of the function; we always use the spiked_feerate as the feerate_per_kw parameter. I admit this is confusing, should I rename the function parameter to spiked_feerate ?
A more descriptive name would be helpful here (if you can thing of something less gross than max_output_preserving_fee?).
How is current_spiked_tx_fee_sat ? It would be consistent with the spiked_feerate style. With max_output_preserving_fee you want to highlight that we want to maintain an output on the commitment transaction even with a 2x increase in the feerate ?
There was a problem hiding this comment.
went ahead with both variable renames below
| /// 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
Also take the opportunity to remove some test case runs that don't do anything novel compared to other runs.
|
🔔 4th Reminder Hey @TheBlueMatt @carlaKC! This PR has been waiting for your review. |
|
Needs rebase already :/ |
| .expect("counterparty reserve is set") | ||
| == 0 | ||
| { | ||
| // If we previously had a 0-value reserve, continue with the same reserve |
There was a problem hiding this comment.
The comment says "If we previously had a 0-value reserve" but counterparty_selected_channel_reserve_satoshis is the reserve selected by the counterparty for us (i.e., the amount we must keep). If this was 0, it means the counterparty previously didn't require us to hold any reserve — not that "we had a 0-value reserve" in general. The comment is technically correct but could be clearer: e.g., "If the counterparty previously set our reserve to 0, continue with the same reserve."
Similarly, line 2801's comment says "If the counterparty previously had a 0-value reserve" for holder_selected_channel_reserve_satoshis — this is the reserve we selected for the counterparty. The phrasing is correct but could match the field semantics more precisely.
Automated ReviewThe main concern is that One minor inline comment on a misleading comment in the splice reserve-preservation logic. 🤖 Generated with Claude Code |
|
🔔 1st Reminder Hey @TheBlueMatt @carlaKC! This PR has been waiting for your review. |
|
🔔 5th Reminder Hey @TheBlueMatt @carlaKC! This PR has been waiting for your review. |
carlaKC
left a comment
There was a problem hiding this comment.
Went through the tx_builder changes together IRL, those lgtm.
Just one q on v2 and a few comments on breaking up the tests for readability.
| } | ||
| } | ||
|
|
||
| pub fn handle_and_accept_open_zero_reserve_channel( |
There was a problem hiding this comment.
(first time I posted this I think it got lost in the depths of GH).
Can we live without some of these helpers, because this accept step is the only one where we need to take a custom action?
Will add a few lines in do_test_accept_inbound_channel_from_trusted_peer_0reserve but the others are unaffected?
| // We can't afford the fee for an additional non-dust HTLC + the fee spike HTLC, so we can only send | ||
| // dust HTLCs... | ||
| // We don't bother to add the second stage tx fees, these would only make this min bigger | ||
| let min_nondust_htlc_sat = dust_limit_satoshis; | ||
| assert!( | ||
| channel_value_sat | ||
| - commit_tx_fee_sat(spike_multiple * feerate_per_kw, 2, &channel_type) | ||
| < min_nondust_htlc_sat | ||
| ); | ||
| // But sending a big (not biggest) dust HTLC trims our balance output! | ||
| let max_dust_htlc = dust_limit_satoshis - 1; | ||
| assert!( | ||
| channel_value_sat - commit_tx_fee_sat(feerate_per_kw, 0, &channel_type) - max_dust_htlc | ||
| < dust_limit_satoshis | ||
| ); |
There was a problem hiding this comment.
Can we pull these asserts out of the channel type branching? If we use the correct fee rate / anchor values for each different type (eg, zero for 0FC) then they're the same?
| let sender_amount_msat = (channel_value_sat - min_value_sat) * 1000; | ||
| let details_0 = &nodes[0].node.list_channels()[0]; | ||
| assert_eq!(details_0.next_outbound_htlc_minimum_msat, 1000); | ||
| assert_eq!(details_0.next_outbound_htlc_limit_msat, sender_amount_msat); | ||
| assert!( | ||
| details_0.next_outbound_htlc_limit_msat > details_0.next_outbound_htlc_minimum_msat | ||
| ); |
There was a problem hiding this comment.
Same here I think? Asserts are common for sender_amount_msat, we may just sometimes have zero anchors/fees somtimes.
| // TODO(zero_reserve): support reading and writing the `disable_channel_reserve` field | ||
| let counterparty_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis( | ||
| channel_value_satoshis, msg.common_fields.dust_limit_satoshis); | ||
| let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis( | ||
| channel_value_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS); | ||
| let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis( | ||
| channel_value_satoshis, msg.common_fields.dust_limit_satoshis); |
There was a problem hiding this comment.
We've switched the dust limits used to floor channel reserve here - I think the counterparty's reserve should use the common_fields.dust_limit_satoshi and we should use MIN_CHAN_DUST_LIMIT_SATOSHIS for holder?
| { | ||
| // If this dust HTLC produces no outputs, then we have to say something! It is now possible to produce a | ||
| // commitment with no outputs. | ||
| if !has_output( |
| nodes[1].node.list_channels()[0].next_outbound_htlc_limit_msat, | ||
| sender_amount_msat | ||
| ); | ||
| send_payment(&nodes[1], &[&nodes[0]], sender_amount_msat); |
There was a problem hiding this comment.
PaymentSucceeds is the only test case that's common to all of our channel types, and FailsReceiverUpdateAddHTLC has its own branch within the failure case here.
Wondering if these can be separated into different handlers with common setup to improve readability? Would also save our needing to check the channel type / NoOutputs combination above - just assert that ReceiverCanAcceptA/B is only running on static_remote_key, for example.
Fixes #1801