-
Notifications
You must be signed in to change notification settings - Fork 376
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Clean up existing and add range-based closing_signed negotiation #1011
Clean up existing and add range-based closing_signed negotiation #1011
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made it through most commits but need to review the second half of the PR still.
lightning/src/ln/channel.rs
Outdated
/// 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this the same constant 2
used in ChannelManager::update_channel_fee
or related in any way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
@@ -733,7 +733,7 @@ impl<Signer: Sign> Channel<Signer> { | |||
if feerate_per_kw < lower_limit { | |||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps a documented constant is in order if this needs a lengthy explanation in the commit message?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was rewritten in #985.
Is this also based on #985? I just realized my comments overlap with that PR 😕 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
@@ -26,7 +26,7 @@ macro_rules! encode_tlv { | |||
} | |||
|
|||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be ?
for zero or one instead of *
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe we support versions of rust prior to the introduction of ?
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
lightning/src/util/ser_macros.rs
Outdated
@@ -8,6 +8,9 @@ | |||
// licenses. | |||
|
|||
macro_rules! encode_tlv { | |||
($stream: expr, $type: expr, $field: expr, (default_value, $default: expr)) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about (default: $default: expr)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hitting a bit of a wall tonight, so will take a closer look at the test and coverage in the morning.
@@ -2943,6 +3007,8 @@ impl<Signer: Sign> Channel<Signer> { | |||
// Upon reconnect we have to start the closing_signed dance over, but shutdown messages | |||
// will be retransmitted. | |||
self.last_sent_closing_fee = None; | |||
self.pending_counterparty_closing_signed = None; | |||
self.closing_fee_limits = None; | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we also set closing_signed_in_flight
to false here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why 500?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.