bolt2: fee_range clarification
I am a little confused about the fee_range requirements for coop close.
I asked some clarifying questions of @t-bast in https://github.com/lightning/bolts/pull/847#discussion_r893605295:
"Yes, fee_range allows the funder to update its fee_range if the fundee doesn't respond or sent a warning."
I didn't realize until now that the funder may update its fee_range even if the fundee did not send a warning. I think this could lead to the following issue:
A's PoV
A B
fr=[100, 200], fs=150
----closing_signed---->
fr=[200, 300], fs=275
----closing_signed----> (A doesn't hear back soon enough and updates the range)
fr=[150, 250], fs=175
<---closing_signed----- (spec says A should fail here since 175 is not in the overlap)
B's PoV
A B
fr=[100, 200], fs=150
----closing_signed---->
fr=[150, 250], fs=175
<---closing_signed----- (B sends acceptable message back)
fr=[200, 300], fs=275
----closing_signed----> (spec says B should fail since 175 wasn't received)
-
A fails since there is an overlap between A's range [200, 300] and B's range [150, 250] of [200, 250], but 175 isn't in the overlap:
- if the message contains a fee_range:
- if there is no overlap between that and its own fee_range:
- SHOULD send a warning
- MUST fail the channel if it doesn't receive a satisfying fee_range after a reasonable amount of time
- otherwise:
-
if it is the funder:
-
if fee_satoshis is not in the overlap between the sent and received fee_range:
- MUST fail the channel
-
if fee_satoshis is not in the overlap between the sent and received fee_range:
-
if it is the funder:
- if there is no overlap between that and its own fee_range:
- if the message contains a fee_range:
-
B fails since there is an overlap between A's range [200, 300] and B's range [150, 250] of [200, 250], but 175 should have been received from A since B has already sent closing_signed.
- if the message contains a fee_range:
- if there is no overlap between that and its own fee_range:
- SHOULD send a warning
- MUST fail the channel if it doesn't receive a satisfying fee_range after a reasonable amount of time
- otherwise:
-
otherwise (it is not the funder):
-
if it has already sent a closing_signed:
-
if fee_satoshis is not the same as the value it sent:
- MUST fail the channel
-
if fee_satoshis is not the same as the value it sent:
-
if it has already sent a closing_signed:
-
otherwise (it is not the funder):
- if there is no overlap between that and its own fee_range:
- if the message contains a fee_range:
You're right, that case would lead to a force-close. It can happen in theory, but I'd argue it likely won't happen in practice: A will send its second range after a while if it believes that B is unresponsive. It's very unlikely that B will decide to respond to an old messages exactly at the same time. So there is indeed a potential race condition, but we're very unlikely to hit it.
Nevertheless, as discussed on the PR, we can change this to be a turn-based protocol by adding a new message to explicitly reject a fee range (e.g. reject_closing_fee_range) and require B to always respond to closing_signed with either its own closing_signed or reject_closing_fee_range. Having a turn-based protocol is probably a good idea, at the time I didn't want to introduce a new message but it is probably required if we want musig2 to work.
I also don't think it's likely and depends on implementations "unresponsive timers" and maybe connection issues. I think making it turn-based by using a special message instead of a warning would fix this but I'd have to think a bit more about it.
Here is a high-level proposal for an updated turn-based closing flow:
- initiator A starts with
closing_signedand provides afee_range - non-initiator B responds with either:
-
closing_signedwith a fee contained in A'sfee_range: happy path, we can now broadcast the cooperative close tx - a new
closing_fee_range_mismatchodd message if A'sfee_rangeis unacceptable: this new message contains B's acceptablefee_range
-
Then A's behavior should be left open to implementations. A can now either force-close or sending another closing_signed that matches B's proposed fee_range, but we want to make sure we don't accidentally re-implement the fee negotiation loop we wanted to get rid of!
On the eclair side, here is what I plan to offer to node operators. When they initiate the mutual close and provide a fee range, they can also provide a boolean flag force_close_on_fee_range_mismatch. If true, we will automatically force-close if we receive closing_fee_range_mismatch. If false, when we receive closing_fee_range_mismatch, we will log it to a special file containing important node operator notifications (which is usually forwarded to telegram bots or something similar). The node operator can then manually decide to either accept the proposed fee_range, do nothing or force-close.
When the mutual close is initiated by our peer (ie we're not funder and won't pay the on-chain fee), we will accept whatever fee_range they provide unless it's below the current dynamic minimum mempool fee. If it is below that value, we will send closing_fee_range_mismatch and wait for another closing_signed with an updated fee_range.
Would that work for you? We can decide later (when actually implementing it) whether it needs a feature bit or not. It technically doesn't need one, but I don't mind adding one if others feel it's useful or better to make explicit.
I like the proposal.
- I think your users could also try again later if
force_close_on_fee_range_mismatchis true instead of force closing immediately? Just a suggestion, don't know all the details. - It works with the taproot-fee_range change here https://github.com/Roasbeef/lightning-rfc/pull/2 and I think it is useful independent of taproot-related changes.
I think your users could also try again later if force_close_on_fee_range_mismatch is true instead of force closing immediately? Just a suggestion, don't know all the details.
True, maybe we can instead have a field force_close_on_fee_range_mismatch_after = N hours and if the node operator retries before the timeout is reached, that lets them propose a new fee_range. I'll discuss this with node operators to see what they'd like.
I don't really understand why this is a (material) concern. If A is gonna send a new closing_signed, A should handle this. If you don't want to handle it, disconnect and reconnect :). More realistically, basically every node should send a warning now, so you can just treat the no-warning-received case as "peer has gone away".
after what time can you determine that your peer hasn't sent the warning? the wording of the spec says that the warning is optional (SHOULD). the spec says to fail the channel here. also having a turn-based fee_range is needed for simple-taproot-channels unless an overhaul of coop closing is done where only sigs are sent at the end
Its optional primarily for backwards compatibility reasons - warnings were not merged when the new closing stuff was merged. I think we could just switch it to MUST (which, actually, isn't that much of a backwards compatibility concern - if a peer "takes forever" to respond you have to handle that another way anyway).
Ultimately the intent here is that retrying with a new fee range will not be something that's even remotely quick - it either requires a user manually responding to a warning message (that includes human-readable text explaining that the peer doesn't like our fees) or the node deciding "enough" time has passed (probably on the order of many blocks where its feerate estimates have changed).
Yes it can be changed to a MUST. I know that it's not going to happen, but the way the spec is written doesn't really convey the intent behind the change (waiting a while and whatnot)
Should the sender of warning give a hint about the range like too high or too low in TLV? Otherwise, the funder will be guessing.
Also is it possible that a warning is sent from B -> A regarding the same channel but unrelated to coop close? If so, it could confuse A.