-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
lnwallet: Introduce a fee buffer. #8096
lnwallet: Introduce a fee buffer. #8096
Conversation
8d40c55
to
acd61b6
Compare
5824d55
to
4390b6c
Compare
44bc9e7
to
bac7015
Compare
a6d398a
to
2fcfb82
Compare
I wonder if we can write a fuzzer that it able to trigger the original edge case, which would then add confidence that this properly resolves the issue. |
I encounter this edge case about once per week on my live node. I stopped accepting channels from LND nodes that are sources to me, becaue if I accept those channels they end up in a FC usually in less than a week. So if you wanna trigger this case. |
I will definitely look into ways reproducing this error (fuzzing,itesting) and see whether this PR fixes the issues, good point. |
It would probably be easiest to write a unit test in |
4fef44e
to
5452f83
Compare
So I reworked this PR a bit from the initial version, so I generalized the part where we account for the I am actually not sure if we really need to FuzzTest this scenario, if so I would love to get some ideas from the pros here 😀. I left some TODO's in this PR which I want you to focus on while reviewing, especially the proposal in #8249 where we would allow the peer to put us below our reserve if we are the opener of the channel. (only relevant if the channel is already stuck when running this new code, so I guess we need to think about it?) So this PR is not final yet but ready to receive a Design Review. |
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.
Very important work! Concept ACK. Some notes may be helpful for other reviewers, the core change happens in applyCommitFee
, which is called by,
`validateCommitmentSanity`, and used in various methods:
- `SignNextCommitment` - `NoBuffer`
- `ReceiveNewCommitment` - `NoBuffer`
- `ReceiveHTLC` - `NoBuffer`
- `validateAddHtlc`
- `AddHTLC` - options - `FeeBuffer`
- `MayAddOutgoingHtlc` - `FeeBuffer`
`availableCommitmentBalance`, and used in various methods:
- `availableBalance`
- `validateFeeRate` - `AdditionalHtlc`
- `UpdateFee` which sends `update_fee` msg
- `AvailableBalance` - `FeeBuffer` - used in link to decide channel bandwith
- `MaxFeeRate` - `AdditionalHtlc` TODO(yy): downgrade it to private method
- `IdealCommitFeeRate` - used in link to decide new commit fee rate
To summarize, when we add a htlc, we enforce a fee buffer. When we receive an HTLC or commit_sig
or sign a new commit_sig
, we don't check for fee buffer. When we send a update_fee
message, we only account one more htlc as our fee buffer.
My questions and suggestions,
-
not sure about
AdditionalHtlc
's use case. I think it makes sense to only account for one extra HTLC when we know we will do a coop close, to make sure there's enough budget to pay the final tx fees. Not sure tho, if there another round of commitment dance, then this budget may not be enough? I guess in that case we can always fail it if it cannot meet the fee buffer? -
maybe we don't change
validateCommitmentSanity
, useFeeBuffer
and add code/method to validate fee buffer invalidateAddHtlc
directly, seems to give us less code change.
lnwallet/channel.go
Outdated
switch { | ||
// TODO(ziggie): Remove the InitialBalance check? |
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.
Let's focus on adding the fee buffer in this PR and add a dedicated PR to fix the chan reserve thing?
I think this might be a good idea, will try to code this variant and see if I can make it more clean 👍 EDIT: So I tried this solution as well but I think this makes the code change more narrow (we don't have to add the
hmm I think the |
b88a80d
to
a7a114a
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.
Looking good. Still need to do another round to review the tests. Meanwhile sharing some notes, I think it's helpful to unify the concepts that are used here re the value stored in a channel,
┌─────────────────────┐
│ ┌─────────────────┐ │
│ │ │ │
│ │ routing │ │
│ │ │ │
│ │ bandwidth │ │
│ │ │ │
│ ├─────────────────┤ │
│ │ fee buffer │ │
│ ├─────────────────┤ │
│ │ channel reserve │ │
│ └─────────────────┘ │
│ │
│ channel balance │
│ │
├─────────────────────┤
│ commit fees │
└─────────────────────┘
commit fees
is the onchain fee, and channel balance
is what the user will receive once the channel is closed, which is made of three parts,
channel reserve
, the security deposit.fee buffer
, a dedicated amount that can be used to deal with fee spikes, and we expectcommit fees <= fee buffer
.routing bandwidth
, the value that can be used for routing, ie, add htlcs.
If we can agree on these concepts we could make future PRs to apply them in our code, which can make things easier.
In a following PR I think we should make the fee buffer configurable, something like currentFeeRate * BufferFactor * (commitweight + additionalHTLCWeigth * NumBufferedHTLCs)
1cc3da1
to
3f2fa00
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.
Very nice👍 Think we can finally fix this issue, also great unit tests! No major comments, only questions and nits. Haven't finished link_test.go
and itest yet, but I think this is veeeerry close!
commitWeight int64) lnwire.MilliSatoshi { | ||
|
||
// Account for a 100% in fee rate increase. | ||
bufferFeePerKw := 2 * feePerKw |
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.
Q: for anchor channels do we still need this buffer? My understanding for non-anchor channel is we build a fee buffer based on the commit fee, so I guess for anchor channels we'd build it based on relaying fee?
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, so I think we should always look at the current fee rate of the commitment transaction and go from there? As you correctly pointed out, these should be different for anchor and non-anchor channels (which currently they are but not in a smart way - for anchors its just hardcapped but should rather be at least floored at the min_relay fee of the avarage peers.) I agree that we should add a floor for the buffer in case we have very low feerates but I would do this in a future PR.
} else { | ||
// No FeeBuffer is enforced when we are not the initiator of | ||
// the channel. We cannot do this, because if our peer does not | ||
// enforce the FeeBuffer (older LND software) the peer might |
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 guess this doesn't fix #8315 since if our peer is using an older LND, we'd still have this issue. If we could define a different behavior based on our channel balance, such that,
- when it dips below channel reserve, fail the link and force close
- when it dips below fee buffer, fail the ADD but keep the link
Then this could be resolved. However, this would require a larger change(and specs change?) so I think we should do it in a future 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.
Yeah it will not fix 8315 until we carve-out the cases where we acutally allow the peer dip us below our reserve (outlined in the TODO).
when it dips below fee buffer, fail the ADD but keep the link
I think this is already in place when this PR is merged, we do not account for the fee buffer when receiving, and when sending we can easily roll back the ADD
.
lnwallet/channel.go
Outdated
// We are not enforcing the FeeBuffer here because our peer is not | ||
// aware of it and neither should they be. This is one of the reasons | ||
// the FeeBuffer was introduced so we use it and therefore do not | ||
// enforce the additional buffer. |
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 guess the reason we are not using FeeBuffer
here is because for our balance this HTLC will increase it and for their balance we don't ever check it in validateCommitmentSanity
.
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.
Rephrased the comment, so the main reason we do not use it here is for the concurrency problem we try to solve:
// We do not enforce the FeeBuffer here because one of the reasons it
// was introduced is to protect against asynchronous sending of htlcs so
// we use it here. The current lightning protocol does not allow to
// reject ADDs already sent by the peer.
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 point you are making, that we should look at whether the htlc ADDs balance to our local will be implemented in a future PR, this relates to the "allow dipping the peer below the reserve" question.
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.
LGTM besides nits 🌵 great work on this
ccd9bba
to
24d6d12
Compare
We take into account a fee buffer of twice the current fee rate of the commitment transaction plus an additional htlc output when we are the opener of the channel hence pay when publishing the commitment transaction. This buffer is not consensus critical because we only consider it when we are in control of adding a new htlc to the state. The goal is to prevent situations where we push our local balance below our channel reserve due to parallel adding of htlcs to the state. Its not a panacea for these situations but until we have __option_simplified_update__ deployed widely on the network its a good precaution to protect against fee spikes and parallel adding of htlcs to the update log. Moreover the way the available balance for a channel changed. We now need to account for a fee buffer when we are the channel opener. Therefore all the tests had to be adopted.
24d6d12
to
921ffb7
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.
LGTM⛵️ Thank you for all the effort, and, looking forward to the followup PR on fixing the dip-below-reserve-error on the remote side!
Because we need to account for an addtional fee buffer we need to increase channel capacities for the multi payment tests.
921ffb7
to
320108a
Compare
* [Fixed](https://github.com/lightningnetwork/lnd/pull/8096) a case where `lnd` | ||
might dip below its channel reserve when htlcs are added concurrently. A | ||
fee buffer (additional balance) is now always kept on the local side ONLY | ||
if the channel was opened locally. This is in accordance with the BOTL 02 |
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.
nit: typo
Fixes #7657/#7721
This change introduces a fee buffer which accounts for a 100% fee rate increase plus an additional htlc output on the commitment transaction
at the current fee rate. This equals the behaviour what CLN does currently and is spec compliant IMO. It's a conservative approach because we are not taking any trimmed Htlcs at higher fee rates into account, but this has a positive side effect when fee rates decrease and dust htlcs become normal outputs (only relevant for non-anchor channels because they have to pay for the second-level covenant transaction).Still need to write tests in the coming days. Change seems small at first glance but lets wait until tests are ready.EDIT: Before updating all the unit tests and thinking of itests, I would be happy to receive first feedback on this approach (especially the additonal argumentenforceFeeBuffer
extra argument, maybe there is a cleaner approach?).It is important to underline that the
fee buffer
is only a BOLT02 requirement, all the requirements regarding BOLT03 where the testcase go below the imaginable fee buffer still need hold.This PR takes the recommendation from BOTL02 and calculates the fee buffer like the following:
feeBuffer = currentFeeRate *2 * commitweight + additionalHTLCWeigth * currentFeeRate *2
This is the same behaviour as eclair and cln follow. Subtle difference to eclair is that eclair accounts for the potential trimmed htlcs at higher fee rates whereas we do not account for this trimming (CLN does not as well). This is only relevant for non-anchor channels because anchor channels have zerofee htlc second-level covenant transactions
eclair: https://github.com/ACINQ/eclair/blob/97c9b579f319952c215f2a1930357de25a0640ab/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala#L135-L151
cln: https://github.com/ElementsProject/lightning/blob/62ddf84b4f15a460ed5a8f72b313998a83eefe19/channeld/full_channel.c#L565-L569
So there are two occasions we have to differentiate when we want to enforce the fee buffer and when not.
In addition what do you think of a new field when showing the channel:
CanSend
, which show the exact max amount one can send currently ?