Skip to content
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

Merged
merged 13 commits into from
Aug 17, 2021

Conversation

TheBlueMatt
Copy link
Collaborator

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.

@TheBlueMatt TheBlueMatt force-pushed the 2021-07-new-closing-fee branch from 58a5baa to 02af55e Compare July 20, 2021 14:35
@codecov
Copy link

codecov bot commented Jul 20, 2021

Codecov Report

Merging #1011 (db6c69d) into main (a369f9e) will increase coverage by 0.05%.
The diff coverage is 89.87%.

❗ Current head db6c69d differs from pull request most recent head 82e7df1. Consider uploading reports for the commit 82e7df1 to get more accurate results
Impacted file tree graph

@@            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     
Impacted Files Coverage Δ
lightning/src/ln/mod.rs 90.00% <ø> (ø)
lightning/src/util/ser_macros.rs 87.71% <0.00%> (-8.61%) ⬇️
lightning/src/util/config.rs 46.66% <50.00%> (+0.15%) ⬆️
lightning/src/ln/channelmanager.rs 85.84% <70.88%> (-0.27%) ⬇️
lightning/src/ln/channel.rs 88.70% <72.78%> (-0.67%) ⬇️
lightning/src/ln/shutdown_tests.rs 95.58% <95.58%> (ø)
lightning/src/ln/chanmon_update_fail_tests.rs 97.89% <100.00%> (+0.02%) ⬆️
lightning/src/ln/functional_test_utils.rs 95.13% <100.00%> (+0.01%) ⬆️
lightning/src/ln/functional_tests.rs 97.50% <100.00%> (+0.17%) ⬆️
lightning/src/ln/msgs.rs 89.10% <100.00%> (+0.24%) ⬆️
... and 6 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update a369f9e...82e7df1. Read the comment docs.

@TheBlueMatt TheBlueMatt force-pushed the 2021-07-new-closing-fee branch 3 times, most recently from ac65c47 to d986933 Compare July 21, 2021 15:33
@TheBlueMatt TheBlueMatt added this to the 0.0.100 milestone Jul 22, 2021
@TheBlueMatt
Copy link
Collaborator Author

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.

@jkczyz jkczyz self-requested a review July 26, 2021 19:15
@TheBlueMatt TheBlueMatt force-pushed the 2021-07-new-closing-fee branch from cafce2b to 9e1028c Compare July 26, 2021 19:43
@TheBlueMatt
Copy link
Collaborator Author

Updated to latest BOLT PR version, now based on #985.

@TheBlueMatt TheBlueMatt force-pushed the 2021-07-new-closing-fee branch 2 times, most recently from 498c66c to 11be074 Compare July 29, 2021 14:14
Copy link

@ariard ariard left a 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.

lightning/src/ln/channel.rs Outdated Show resolved Hide resolved
lightning/src/ln/channel.rs Show resolved Hide resolved
};

// 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
Copy link

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.

Copy link
Collaborator Author

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.

lightning/src/ln/channel.rs Show resolved Hide resolved
lightning/src/ln/channelmanager.rs Outdated Show resolved Hide resolved
lightning/src/ln/msgs.rs Outdated Show resolved Hide resolved
/// 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.
Copy link

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...

Copy link
Collaborator Author

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).

@TheBlueMatt
Copy link
Collaborator Author

Rebased on #1019.

@jkczyz jkczyz requested a review from valentinewallace August 9, 2021 19:12
@TheBlueMatt TheBlueMatt force-pushed the 2021-07-new-closing-fee branch from 9e0b281 to aab9709 Compare August 10, 2021 00:00
@TheBlueMatt
Copy link
Collaborator Author

Rebased after #1019 was merged and fixed the outstanding issues (I believe).

@TheBlueMatt TheBlueMatt force-pushed the 2021-07-new-closing-fee branch 2 times, most recently from 2ef0139 to 46d1f6e Compare August 10, 2021 22:25
Copy link
Contributor

@jkczyz jkczyz left a 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/channelmanager.rs Outdated Show resolved Hide resolved
lightning/src/ln/channelmanager.rs Outdated Show resolved Hide resolved
lightning/src/ln/channelmanager.rs Outdated Show resolved Hide resolved
lightning/src/ln/channelmanager.rs Outdated Show resolved Hide resolved
/// 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;
Copy link
Contributor

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?

Copy link
Collaborator Author

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 Show resolved Hide resolved
lightning/src/ln/channel.rs Outdated Show resolved Hide resolved
lightning/src/ln/channel.rs Outdated Show resolved Hide resolved
lightning/src/ln/channel.rs Outdated Show resolved Hide resolved
@@ -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;
Copy link
Contributor

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?

Copy link
Collaborator Author

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.

@jkczyz
Copy link
Contributor

jkczyz commented Aug 11, 2021

Is this also based on #985? I just realized my comments overlap with that PR 😕

Copy link
Contributor

@jkczyz jkczyz left a 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.

@@ -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)),* $(,)*}) => { {
Copy link
Contributor

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 *?

Copy link
Collaborator Author

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 ?.

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Collaborator Author

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/ln/channel.rs Show resolved Hide resolved
lightning/src/ln/channelmanager.rs Outdated Show resolved Hide resolved
lightning/src/ln/channel.rs Outdated Show resolved Hide resolved
@@ -8,6 +8,9 @@
// licenses.

macro_rules! encode_tlv {
($stream: expr, $type: expr, $field: expr, (default_value, $default: expr)) => {
Copy link
Contributor

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)?

Copy link
Collaborator Author

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 Show resolved Hide resolved
lightning/src/ln/functional_tests.rs Show resolved Hide resolved
lightning/src/ln/channelmanager.rs Outdated Show resolved Hide resolved
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()));
Copy link
Contributor

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?

Copy link
Collaborator Author

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.

@TheBlueMatt
Copy link
Collaborator Author

Is this also based on #985? I just realized my comments overlap with that PR confused

Oops, yes, sorry, it still is. I addressed a number of your comments here on that PR.

@TheBlueMatt TheBlueMatt force-pushed the 2021-07-new-closing-fee branch from 46d1f6e to d18d119 Compare August 12, 2021 18:41
@TheBlueMatt
Copy link
Collaborator Author

Rebased on latest #985 (and upstream).

Copy link
Contributor

@jkczyz jkczyz left a 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.

lightning/src/util/config.rs Show resolved Hide resolved
lightning/src/util/config.rs Show resolved Hide resolved
lightning/src/ln/channelmanager.rs Outdated Show resolved Hide resolved
lightning/src/ln/channelmanager.rs Outdated Show resolved Hide resolved
lightning/src/ln/channel.rs Outdated Show resolved Hide resolved
lightning/src/ln/channel.rs Outdated Show resolved Hide resolved
lightning/src/ln/channel.rs Outdated Show resolved Hide resolved
lightning/src/ln/channelmanager.rs Outdated Show resolved Hide resolved
lightning/src/ln/channel.rs Outdated Show resolved Hide resolved
lightning/src/ln/channel.rs Outdated Show resolved Hide resolved
@@ -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;

Copy link
Contributor

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?

Copy link
Collaborator Author

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.

lightning/src/ln/channel.rs Outdated Show resolved Hide resolved
lightning/src/ln/channel.rs Show resolved Hide resolved
@TheBlueMatt TheBlueMatt force-pushed the 2021-07-new-closing-fee branch from 92feba8 to 4b31519 Compare August 13, 2021 22:11
Copy link
Contributor

@jkczyz jkczyz left a 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.

lightning/src/ln/channelmanager.rs Outdated Show resolved Hide resolved
}

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);
Copy link
Contributor

Choose a reason for hiding this comment

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

why 500?

Copy link
Collaborator Author

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.

lightning/src/ln/shutdown_tests.rs Outdated Show resolved Hide resolved
lightning/src/ln/shutdown_tests.rs Outdated Show resolved Hide resolved
Comment on lines +3719 to 3749
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);
}
}
Copy link
Contributor

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?

Copy link
Collaborator Author

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.

@TheBlueMatt TheBlueMatt force-pushed the 2021-07-new-closing-fee branch 2 times, most recently from 313b773 to 54b70fa Compare August 13, 2021 22:50
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
@TheBlueMatt TheBlueMatt force-pushed the 2021-07-new-closing-fee branch from 54b70fa to 3b706a1 Compare August 13, 2021 23:02
@TheBlueMatt
Copy link
Collaborator Author

TheBlueMatt commented Aug 13, 2021

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."
@TheBlueMatt TheBlueMatt force-pushed the 2021-07-new-closing-fee branch from 3b706a1 to 2141a3a Compare August 13, 2021 23:08
@TheBlueMatt TheBlueMatt mentioned this pull request Aug 14, 2021
Copy link
Contributor

@valentinewallace valentinewallace left a 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

lightning/src/ln/channel.rs Outdated Show resolved Hide resolved
lightning/src/ln/channel.rs Show resolved Hide resolved
lightning/src/ln/channelmanager.rs Outdated Show resolved Hide resolved
@TheBlueMatt TheBlueMatt force-pushed the 2021-07-new-closing-fee branch from 2141a3a to db6c69d Compare August 16, 2021 22:50
@TheBlueMatt
Copy link
Collaborator Author

Do we wanna add the test coverage or punt it? I'm ACK

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.

@jkczyz
Copy link
Contributor

jkczyz commented Aug 17, 2021

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.
@TheBlueMatt TheBlueMatt force-pushed the 2021-07-new-closing-fee branch from db6c69d to 82e7df1 Compare August 17, 2021 02:16
@TheBlueMatt
Copy link
Collaborator Author

Squashed without diff (db6c69d8...82e7df1c), diff from Val's ACK-with-comments above is:

$ git diff-tree -U1  2141a3a 82e7df1c
diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs
index ca25cb19..03968118 100644
--- a/lightning/src/ln/channel.rs
+++ b/lightning/src/ln/channel.rs
@@ -456,3 +456,3 @@ pub(super) struct Channel<Signer: Sign> {
 
-	/// If our counterparty sent us a closing_signed while we were waiting for a channel monitor
+	/// If our counterparty sent us a closing_signed while we were waiting for a `ChannelMonitor`
 	/// update, we need to delay processing it until later. We do that here by simply storing the
@@ -3378,2 +3378,5 @@ impl<Signer: Sign> Channel<Signer> {
 
+	/// Calculates and returns our minimum and maximum closing transaction fee amounts, in whole
+	/// satoshis. The amounts remain consistent unless a peer disconnects/reconnects or we restart,
+	/// at which point they will be recalculated.
 	fn calculate_closing_fee_limits<F: Deref>(&mut self, fee_estimator: &F) -> (u64, u64)
@@ -3387,7 +3390,4 @@ impl<Signer: Sign> Channel<Signer> {
 		let mut proposed_feerate = fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::Background);
-		let mut proposed_max_feerate = if self.is_outbound() {
-				fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::Normal)
-			} else {
-				u32::max_value()
-			};
+		let normal_feerate = fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::Normal);
+		let mut proposed_max_feerate = if self.is_outbound() { normal_feerate } else { u32::max_value() };
 
@@ -3415,3 +3415,7 @@ impl<Signer: Sign> Channel<Signer> {
 		let proposed_max_total_fee_satoshis = if self.is_outbound() {
-				proposed_max_feerate as u64 * tx_weight / 1000 + self.config.force_close_avoidance_max_fee_satoshis
+				// We always add force_close_avoidance_max_fee_satoshis to our normal
+				// feerate-calculated fee, but allow the max to be overridden if we're using a
+				// target feerate-calculated fee.
+				cmp::max(normal_feerate as u64 * tx_weight / 1000 + self.config.force_close_avoidance_max_fee_satoshis,
+					proposed_max_feerate as u64 * tx_weight / 1000)
 			} else {
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index 67c316d6..cff31467 100644
--- a/lightning/src/ln/channelmanager.rs
+++ b/lightning/src/ln/channelmanager.rs
@@ -1355,3 +1355,3 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
 	///  * If our counterparty is the channel initiator, we will require a channel closing
-	///    transaction feerate of our [`Background`] feerate or the feerate which
+	///    transaction feerate of at least our [`Background`] feerate or the feerate which
 	///    would appear on a force-closure transaction, whichever is lower. We will allow our
diff --git a/lightning/src/ln/shutdown_tests.rs b/lightning/src/ln/shutdown_tests.rs
index f4cc3d40..a40ad237 100644
--- a/lightning/src/ln/shutdown_tests.rs
+++ b/lightning/src/ln/shutdown_tests.rs
@@ -881 +881,48 @@ fn simple_legacy_shutdown_test() {
 }
+
+#[test]
+fn simple_target_feerate_shutdown() {
+	// Simple test of target in `close_channel_with_target_feerate`.
+	let chanmon_cfgs = create_chanmon_cfgs(2);
+	let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+	let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+	let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+	let chan = create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known());
+	let chan_id = OutPoint { txid: chan.3.txid(), index: 0 }.to_channel_id();
+
+	nodes[0].node.close_channel_with_target_feerate(&chan_id, 253 * 10).unwrap();
+	let node_0_shutdown = get_event_msg!(nodes[0], MessageSendEvent::SendShutdown, nodes[1].node.get_our_node_id());
+	nodes[1].node.close_channel_with_target_feerate(&chan_id, 253 * 5).unwrap();
+	let node_1_shutdown = get_event_msg!(nodes[1], MessageSendEvent::SendShutdown, nodes[0].node.get_our_node_id());
+
+	nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &InitFeatures::known(), &node_0_shutdown);
+	nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &InitFeatures::known(), &node_1_shutdown);
+
+	let node_0_closing_signed = get_event_msg!(nodes[0], MessageSendEvent::SendClosingSigned, nodes[1].node.get_our_node_id());
+	nodes[1].node.handle_closing_signed(&nodes[0].node.get_our_node_id(), &node_0_closing_signed);
+	let (_, node_1_closing_signed_opt) = get_closing_signed_broadcast!(nodes[1].node, nodes[0].node.get_our_node_id());
+	let node_1_closing_signed = node_1_closing_signed_opt.unwrap();
+
+	// nodes[1] was passed a target which was larger than the current channel feerate, which it
+	// should ignore in favor of the channel fee, as there is no use demanding a minimum higher
+	// than what will be paid on a force-close transaction. Note that we have to consider rounding,
+	// so only check that we're within 10 sats.
+	assert!(node_0_closing_signed.fee_range.as_ref().unwrap().min_fee_satoshis >=
+	        node_1_closing_signed.fee_range.as_ref().unwrap().min_fee_satoshis * 10 - 5);
+	assert!(node_0_closing_signed.fee_range.as_ref().unwrap().min_fee_satoshis <=
+	        node_1_closing_signed.fee_range.as_ref().unwrap().min_fee_satoshis * 10 + 5);
+
+	// Further, because nodes[0]'s target fee is larger than the `Normal` fee estimation plus our
+	// force-closure-avoidance buffer, min should equal max, and the nodes[1]-selected fee should
+	// be the nodes[0] only available fee.
+	assert_eq!(node_0_closing_signed.fee_range.as_ref().unwrap().min_fee_satoshis,
+	           node_0_closing_signed.fee_range.as_ref().unwrap().max_fee_satoshis);
+	assert_eq!(node_0_closing_signed.fee_range.as_ref().unwrap().min_fee_satoshis,
+	           node_0_closing_signed.fee_satoshis);
+	assert_eq!(node_0_closing_signed.fee_satoshis, node_1_closing_signed.fee_satoshis);
+
+	nodes[0].node.handle_closing_signed(&nodes[1].node.get_our_node_id(), &node_1_closing_signed);
+	let (_, node_0_none) = get_closing_signed_broadcast!(nodes[0].node, nodes[1].node.get_our_node_id());
+	assert!(node_0_none.is_none());
+}
$

Copy link

@ariard ariard left a 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
Copy link

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.

Copy link
Collaborator Author

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);
Copy link

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.

Copy link
Collaborator Author

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.

@TheBlueMatt TheBlueMatt merged commit 6f16453 into lightningdevkit:main Aug 17, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants