Clean up existing and add range-based closing_signed negotiation#1011
Clean up existing and add range-based closing_signed negotiation#1011TheBlueMatt merged 13 commits intolightningdevkit:mainfrom
Conversation
58a5baa to
02af55e
Compare
Codecov Report
@@ Coverage Diff @@
## main #1011 +/- ##
==========================================
+ Coverage 90.96% 91.02% +0.05%
==========================================
Files 64 65 +1
Lines 32393 33856 +1463
==========================================
+ Hits 29467 30818 +1351
- Misses 2926 3038 +112
Continue to review full report at Codecov.
|
ac65c47 to
d986933
Compare
|
Tagged 0.0.100 given this fixes some current issues users have commented on where we're too restrictive in the existing negotiation. Also may depend on #1013 depending on how the BOLT changes go. |
cafce2b to
9e1028c
Compare
|
Updated to latest BOLT PR version, now based on #985. |
498c66c to
11be074
Compare
ariard
left a comment
There was a problem hiding this comment.
Mostly nits so far, thinking if there are more economically optimal strategies for funder or flexibility desirered (e,g if you're a LSP and you want to cache pre-signed cooperative close, attaching a CPFP at latter broadcast). Greedy fundee sounds a good one.
| }; | ||
|
|
||
| // The spec requires that (when the channel does not have anchors) we only send absolute | ||
| // channel fees no greater than the absolute channel fee on the current commitment |
There was a problem hiding this comment.
I think the spec could be change here and prepare to splice-out support, where the closing transaction size might be bigger than a commitment one ? Let's avoid to multiply closing pipeline in function of channel type.
There was a problem hiding this comment.
All of the channel close stuff is based on fees, not feerates. The old code based on feerates being so complicated and brittle I think is good enough reason to move to something fee-based with calculation at start. We'll probably want separate storage for splice negotiation anyway.
| /// When we are not the funder, we require the closing transaction fee pay at least our | ||
| /// [`Background`] fee estimate, but allow our counterparty to pay as much fee as they like. | ||
| /// | ||
| /// Default value: 1000 satoshis. |
There was a problem hiding this comment.
Well a smarter value setting could be based on to_self_delay length and channel value size? Hmmm...
There was a problem hiding this comment.
Sure, there's lots of things we could do, but adding more knobs does not better enable users to control things, usually the opposite, knobs are just confusing. If users want to do really fine-grained control based on the channel details they can set the target when they call close (or update this value, when we implement that).
11be074 to
81e98c0
Compare
81e98c0 to
9e0b281
Compare
|
Rebased on #1019. |
9e0b281 to
aab9709
Compare
|
Rebased after #1019 was merged and fixed the outstanding issues (I believe). |
2ef0139 to
46d1f6e
Compare
jkczyz
left a comment
There was a problem hiding this comment.
Made it through most commits but need to review the second half of the PR still.
| /// HTLCs for days we may need this to suffice for feerate increases across days, but that may | ||
| /// leave the channel less usable as we hold a bigger reserve. | ||
| #[cfg(fuzzing)] | ||
| pub const FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE: u64 = 2; |
There was a problem hiding this comment.
Is this the same constant 2 used in ChannelManager::update_channel_fee or related in any way?
There was a problem hiding this comment.
No, this is only at issue if the fee is increasing, the 2 constant in update_channel_fee is "minimum the feerate needs to decrease before we bother updating the channel feerate".
lightning/src/ln/channel.rs
Outdated
| return Err(ChannelError::Close(format!("Peer's feerate much too low. Actual: {}. Our expected lower limit: {}", feerate_per_kw, lower_limit))); | ||
| } | ||
| let upper_limit = fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::HighPriority) as u64 * 2; | ||
| let upper_limit = fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::HighPriority) as u64 * 4; |
There was a problem hiding this comment.
Perhaps a documented constant is in order if this needs a lengthy explanation in the commit message?
|
Is this also based on #985? I just realized my comments overlap with that PR 😕 |
jkczyz
left a comment
There was a problem hiding this comment.
Nice clean-up! I have a couple more commits to dig into a bit later, but wanted to send out comments in the meanwhile.
lightning/src/util/ser_macros.rs
Outdated
|
|
||
| macro_rules! encode_tlv_stream { | ||
| ($stream: expr, {$(($type: expr, $field: expr, $fieldty: ident)),*}) => { { | ||
| ($stream: expr, {$(($type: expr, $field: expr, $fieldty: ident)),* $(,)*}) => { { |
There was a problem hiding this comment.
Should this be ? for zero or one instead of *?
There was a problem hiding this comment.
I believe we support versions of rust prior to the introduction of ?.
There was a problem hiding this comment.
Looks like it made it into 1.32.
https://github.com/rust-lang/rust/blob/master/RELEASES.md#version-1320-2019-01-17
There was a problem hiding this comment.
Ah, maybe it would work now and my information is out of date? In any case, we use * in a number of places and this particular commit has already landed upstream on another PR.
| // licenses. | ||
|
|
||
| macro_rules! encode_tlv { | ||
| ($stream: expr, $type: expr, $field: expr, (default_value, $default: expr)) => { |
There was a problem hiding this comment.
How about (default: $default: expr)?
There was a problem hiding this comment.
Sadly this already landed upstream via a different PR, but I agree this would be a nice cleanup.
lightning/src/ln/channel.rs
Outdated
| pub fn timer_check_closing_negotiation_progress(&mut self) -> Result<(), ChannelError> { | ||
| if self.closing_negotiation_ready() { | ||
| if self.closing_signed_in_flight { | ||
| return Err(ChannelError::Close("closing_signed negotiation failed to finish within one minute".to_owned())); |
There was a problem hiding this comment.
The "within one minute" wording here assumes the user is using BackgroundProcessor and that negotiation began at the last tick. Maybe something less precise?
There was a problem hiding this comment.
I made it "two timer ticks", though note that the ChannelManager docs strongly suggest minutely calls.
Oops, yes, sorry, it still is. I addressed a number of your comments here on that PR. |
46d1f6e to
d18d119
Compare
|
Rebased on latest #985 (and upstream). |
jkczyz
left a comment
There was a problem hiding this comment.
Hitting a bit of a wall tonight, so will take a closer look at the test and coverage in the morning.
| self.last_sent_closing_fee = None; | ||
| self.pending_counterparty_closing_signed = None; | ||
| self.closing_fee_limits = None; | ||
|
|
There was a problem hiding this comment.
Should we also set closing_signed_in_flight to false here?
There was a problem hiding this comment.
I don't think so - if we started the close negotiation and our peer disconnects, we should still force close after a little bit.
92feba8 to
4b31519
Compare
jkczyz
left a comment
There was a problem hiding this comment.
Yes, looking good implementation-wise, coverage concerns aside.
| } | ||
|
|
||
| let mut node_0_closing_signed = get_event_msg!(nodes[0], MessageSendEvent::SendClosingSigned, nodes[1].node.get_our_node_id()); | ||
| assert!(node_0_closing_signed.fee_satoshis <= 500); |
There was a problem hiding this comment.
Decent middle ground to indicate "less than the 10 sat/vbyte nodes[1] wants". added a comment.
| if let Some((last_fee, _)) = self.last_sent_closing_fee { | ||
| if msg.fee_satoshis > last_fee { | ||
| if msg.fee_satoshis < our_max_fee { | ||
| propose_fee!(msg.fee_satoshis); | ||
| } else if last_fee < our_max_fee { | ||
| propose_fee!(our_max_fee); | ||
| } else { | ||
| return Err(ChannelError::Close(format!("Unable to come to consensus about closing feerate, remote wants something ({} sat) higher than our max fee ({} sat)", msg.fee_satoshis, our_max_fee))); | ||
| } | ||
| } else { | ||
| if msg.fee_satoshis > our_min_fee { | ||
| propose_fee!(msg.fee_satoshis); | ||
| } else if last_fee > our_min_fee { | ||
| propose_fee!(our_min_fee); | ||
| } else { | ||
| return Err(ChannelError::Close(format!("Unable to come to consensus about closing feerate, remote wants something ({} sat) lower than our min fee ({} sat)", msg.fee_satoshis, our_min_fee))); | ||
| } | ||
| } | ||
| } else { | ||
| if msg.fee_satoshis < our_min_fee { | ||
| propose_fee!(our_min_fee); | ||
| } else if msg.fee_satoshis > our_max_fee { | ||
| propose_fee!(our_max_fee); | ||
| } else { | ||
| propose_fee!(msg.fee_satoshis); | ||
| } | ||
| } |
There was a problem hiding this comment.
Some rate limiting is preventing me from viewing the code coverage report. Are these cases largely covered by test_closing_signed_reinit_timeout?
There was a problem hiding this comment.
The legacy feerate negotiation stuff is probably pretty under-tested after this PR (not that it was super well-tested before this PR, given it had a handful of bugs), I added a pretty simple tests of it in a new commit. At least the logic that is legacy-specific is pretty trivial.
313b773 to
54b70fa
Compare
We don't actually yet support `warning` messages as there are issues left to resolve in the spec PR, but there's nothing to stop us adding an internal enum variant for sending a warning message before we actually support doing so.
This allows decode_tlv_stream!() to be called with either a mutable reference to a stream or a stream itself and allows encode_tlv_stream!() to be called with an excess , at the end of the parameter list.
This adds the serialization and structures for the new fee range specifiers in closing_signed as added upstream at lightning/bolts#847
54b70fa to
3b706a1
Compare
|
Rebased on latest git after merge of #985 and squashed fixup commits down. |
We're supposed to write `Channel` to disk as if `remove_uncommitted_htlcs_and_mark_paused` had just run, however we were writing `last_sent_closing_fee` to disk (if it is not-None), whereas `remove_uncommitted_htlcs_and_mark_paused` clears it. Indeed, the BOLTs say fee "... negotiation restarts on reconnection."
3b706a1 to
2141a3a
Compare
valentinewallace
left a comment
There was a problem hiding this comment.
Do we wanna add the test coverage or punt it? I'm ACK
2141a3a to
db6c69d
Compare
Ooops! Yes, I definitely want to add test coverage for the new target-based feerate stuff. I added a simple test of it, at least, in a new commit. |
|
ACK db6c69d Good to go after squash. |
When we added the support for external signing, many of the signing functions were allowed to return an error, closing the channel in such a case. `sign_closing_transaction` is one such function which can now return an error, except instead of handling it properly we'd simply never send a `closing_signed` message, hanging the channel until users intervene and force-close it. Piping the channel-closing error back through the various callsites (several of which already have pending results by the time they call `maybe_propose_first_closing_signed`) may be rather complicated, so instead we simply attempt to propose the initial `closing_signed` in `get_and_clear_pending_msg_events` like we do for holding-cell freeing. Further, since we now (possibly) generate a `ChannelMonitorUpdate` on `shutdown`, we may need to wait for monitor updating to complete before we can send a `closing_signed`, meaning we need to handle the send asynchronously anyway. This simplifies a few function interfaces and has no impact on behavior, aside from a few message-ordering edge-cases, as seen in the two small test changes required.
This adds the new range-based closing_signed negotiation specified in lightning/bolts#847 as well as cleans up the existing closing_signed negotiation to unify the new codepaths and the old ones. Note that because the new range-based closing_signed negotiation allows the channel fundee to ultimately select the fee out of a range specified by the funder, which we, of course, always select the highest allowed amount from. Thus, we've added an extra round of closing_signed in the common case as we will not simply accept the first fee we see, always preferring to make the funder pay as much as they're willing to.
Because ln::functional_tests if over 9000 LoC long, its useful to move tests into new modules as we can. Here we move all cooperative shutdown related tests into a new module entitled `shutdown_tests`
This doesn't exhaustively test closing fee negotiation at all, but ensures that it is at least basically able to come to consensus and sign cooperative closing transactions.
db6c69d to
82e7df1
Compare
|
Squashed without diff ( |
ariard
left a comment
There was a problem hiding this comment.
Code Review ACK 82e7df1.
I had a minimal last look, I don't think it's breaking more a part of the codebase which was already buggy and non-compliant. We can improve further once the spec has made progress on solving the dust_limit_satoshis issue at closing and we have CPFP support post-anchor output.
| } | ||
|
|
||
| // Note that technically we could end up with a lower minimum fee if one sides' balance is | ||
| // below our dust limit, causing the output to disappear. We don't bother handling this |
There was a problem hiding this comment.
As observed yesterday during the meeting, I think enforcing the dust_limit_satoshis on closing transactions is a bit dubious as it would re-introduce a negotiation among counterparties on the "honest" dust_limit_satoshis at ongoing feerate.
If the output type is known, let's just enforced compared to known Core values and otherwise take the non-safety risk of non-propagating transactions. We can always add checks when new outputs types will be deployed with a corresponding policy. Though spec discussion.
There was a problem hiding this comment.
Right, I think we'll want to use the "each side can unilaterally remove its output" thing, but its somewhat broken in the current spec. Will work with t-bast on it.
| // Propose a range from our current Background feerate to our Normal feerate plus our | ||
| // force_close_avoidance_max_fee_satoshis. | ||
| // If we fail to come to consensus, we'll have to force-close. | ||
| let mut proposed_feerate = fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::Background); |
There was a problem hiding this comment.
One hardening we could introduce in the future would be to hardcode an upper bound in case of compromise of our fee-estimator. The closing transaction max size is known and in the worst-case scenario the confirmation of a closing transaction is not a matter of safety.
There was a problem hiding this comment.
Hmm, yea, we've talked a few times about bounding the fee estimator results to "reasonable" values. We should probably do it sometime, but for now I'd say its fine.
This adds the new range-based closing_signed negotiation specified
in lightning/bolts#847 as
well as cleans up the existing closing_signed negotiation to unify
the new codepaths and the old ones.
Note that because the new range-based closing_signed negotiation
allows the channel fundee to ultimately select the fee out of a
range specified by the funder, which we, of course, always select
the highest allowed amount from. Thus, we've added an extra round
of closing_signed in the common case as we will not simply accept
the first fee we see, always preferring to make the funder pay as
much as they're willing to.
Probably needs a few additional tests, but the existing shutdown coverage isn't bad either. May need to wait on resolution of a few issues in the spec PR.