-
Notifications
You must be signed in to change notification settings - Fork 412
Tweak htlc dust exposure due to excess fees #3572
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
Tweak htlc dust exposure due to excess fees #3572
Conversation
See commit 51bf78d for further background. |
lightning/src/ln/channel.rs
Outdated
let htlc_dust_exposure_msat = | ||
per_outbound_htlc_counterparty_commit_tx_fee_msat(excess_feerate, &self.context.channel_type); | ||
let counterparty_tx_dust_exposure = | ||
htlc_stats.on_counterparty_tx_dust_exposure_msat.saturating_add(htlc_dust_exposure_msat); |
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.
IIUC, since can_accept_incoming_htlc
is now called once it's been irrevocably committed on both sides, I think the dust exposure for this HTLC is already being accounted for in get_pending_htlc_stats
. I think we just need to enforce the check below? Having a test would be helpful here to confirm the behavior.
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.
thanks for the review. I mistakenly assumed can_accept_incoming_htlc
== "can we add this htlc to the commit tx?"
But at this point in the flow, the htlc is already on the commit tx !
Now I struggle to understand the point of checking here whether that htlc pushes us over the dust limit if it has already been added to the commit tx.
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 i understand now, this is a limitation of the spec - you cannot really complain about dust limits in direct response to an update_add_htlc
msg.
133e661
to
188f299
Compare
Thanks! Some test coverage here would still be nice if you're keen and it's not too much trouble. |
907c100
to
188f299
Compare
eae77c0
to
b63b336
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.
At least some of the commits here should be backported to 0.1, I think. Maybe just "Correct the units of counterparty htlc tx excess fees calculations"?
lightning/src/ln/chan_utils.rs
Outdated
// Note that we need to divide before multiplying to round properly, | ||
// since the lowest denomination of bitcoin on-chain is the satoshi. | ||
let commitment_tx_fee_msat = COMMITMENT_TX_WEIGHT_PER_HTLC * feerate_per_kw as u64 / 1000 * 1000; | ||
// We avoid rounding here to make the returned value consistent with the slope (commit_tx_fee_sat/num_htlcs) |
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'm really not sure what this means, and the commit text doesn't explain it either. What is the issue which is being solved by this change?
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.
TLDR two reasons 1) we currently can get a 1 satoshi difference in the calculation of dust exposure between the inbound htlc case and the outbound htlc case for anchor channels when there should not be a difference; this commit tries to minimize that difference. 2) If someone later (currently not an issue) has the idea to multiply this value returned with num_htlcs
there would be a significant difference between commit_tx_fee_sat(num_htlcs)
and commit_tx_fee_sat(0) + num_htlcs * per_outbound_htlc_fee
.
The tests demo these two points.
We can a) drop this commit, b) leave this commit in c) drop this commit and find a better solution.
========
Explanation for 1):
-
In the case of anchor channels, the cost of the single additional non-dust htlc should be the same in both directions, as we do not include the second level htlc transactions in the total weight.
-
For the inbound htlc, we do
commit_tx_fee_sat(feerate, 1, features)
-
For the outbound htlc, we do
commit_tx_fee_sat(feerate, 0, features) + per_outbound_htlc_counterparty_commit_tx_fee_msat(feerate, features)
-
In the test in f6c14a8 we try to cover this calculation.
-
On the main branch it would be:
- anchors_zero_htlc_fee:
500_000 + 22 * (1_124 + 172) / 1000 * 1000 = 528_000
- vs
- anchors_zero_htlc_fee:
500_000 + 22 * 1124 / 1000 * 1000 + 22 * 172 / 1000 * 1000 = 527_000
- anchors_zero_htlc_fee:
-
With this commit, we now drop the last rounding on the calculation just above here, and we get
- anchors_zero_htlc_fee:
500_000 + 22 * 1124 / 1000 * 1000 + 22 * 172 = 527_784
- anchors_zero_htlc_fee:
-
This commit does not solve the root cause (the inbound htlc is already on the commit tx, the outbound htlc is not), but tries to minimize the difference in the inbound and outbound cases for anchor channels.
-
In the non-anchor case, there is already a difference between the inbound and outbound case due to the differences in weights of the htlc success and the htlc timeout transactions, so this is less a concern.
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 don't disagree that it makes sense to do this, just that we should motivate it in the commit description. I'm not entirely sure I buy the inbound-vs-outbound argument, but I do think it makes sense here because we're ultimately calculating the dust exposure in total and ultimately may calculate it wrong because we round on the original transaction fee calculation and round on the per-HTLC transaction fee, but ultimately our real dust limit will be summing the transaction weights first then converting to fees. With that said...I'm not sure just removing the round here fixes it? We really need to add first, then convert to fees in the outbound case, no?
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'm not sure just removing the round here fixes it?
Right, it does not remove it, just reduces the difference.
We really need to add first, then convert to fees in the outbound case, no?
Yes ! I'm working on this now thank you.
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 be fixed in 5177311
lightning/src/ln/channel.rs
Outdated
let counterparty_tx_dust_exposure = | ||
htlc_stats.on_counterparty_tx_dust_exposure_msat.saturating_add(htlc_dust_exposure_msat); | ||
if counterparty_tx_dust_exposure > max_dust_htlc_exposure_msat { | ||
let on_counterparty_tx_dust_htlc_exposure_msat = htlc_stats.on_counterparty_tx_dust_exposure_msat; |
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 is now equivalent to the amount < dust
branch above, so presumably they should be combined?
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! Should the log message and the error message to be different depending on the dust, non-dust case ? That part I wasn't sure about.
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.
Done in 988c33f
@@ -7333,13 +7333,10 @@ impl<SP: Deref> FundedChannel<SP> where | |||
return Err(("Exceeded our dust exposure limit on counterparty commitment tx", 0x1000|7)) | |||
} | |||
} else { | |||
let htlc_dust_exposure_msat = |
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.
It may be worth adding a comment at the top of the method here noting that, when we're called, the HTLC is actually already added on the channel, thus we're trying to decide whether to fail or forward/claim.
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.
Certainly will do
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.
Done in 9bea4ce
lightning/src/ln/chan_utils.rs
Outdated
// Note that we need to divide before multiplying to round properly, | ||
// since the lowest denomination of bitcoin on-chain is the satoshi. | ||
let commitment_tx_fee_msat = COMMITMENT_TX_WEIGHT_PER_HTLC * feerate_per_kw as u64 / 1000 * 1000; | ||
// We avoid rounding here to make the returned value consistent with the slope (commit_tx_fee_sat/num_htlcs) |
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 don't disagree that it makes sense to do this, just that we should motivate it in the commit description. I'm not entirely sure I buy the inbound-vs-outbound argument, but I do think it makes sense here because we're ultimately calculating the dust exposure in total and ultimately may calculate it wrong because we round on the original transaction fee calculation and round on the per-HTLC transaction fee, but ultimately our real dust limit will be summing the transaction weights first then converting to fees. With that said...I'm not sure just removing the round here fixes it? We really need to add first, then convert to fees in the outbound case, no?
607543c
to
988c33f
Compare
lightning/src/ln/functional_tests.rs
Outdated
@@ -10300,7 +10300,7 @@ fn do_test_max_dust_htlc_exposure(dust_outbound_balance: bool, exposure_breach_e | |||
} else { 0 }; | |||
let initial_feerate = if apply_excess_fee { 253 * 2 } else { 253 }; | |||
let expected_dust_buffer_feerate = initial_feerate + 2530; | |||
let mut commitment_tx_cost = commit_tx_fee_msat(initial_feerate - 253, nondust_htlc_count_in_limit, &ChannelTypeFeatures::empty()); | |||
let mut commitment_tx_cost = chan_utils::commit_tx_fee_msat(initial_feerate - 253, nondust_htlc_count_in_limit as usize, &ChannelTypeFeatures::empty()); |
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.
Further above in functional_tests
, we define a fn commit_tx_fee_msat
that rounds and returns msat precision. Here we want no rounding and msat precision. Let me know if this is ok.
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.
Sorry for the delay here. Feel free to squash, its a little hard to follow with fixup commits that are all at the end.
lightning/src/ln/channel.rs
Outdated
@@ -3665,14 +3665,14 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider { | |||
let on_counterparty_tx_nondust_htlcs = | |||
on_counterparty_tx_accepted_nondust_htlcs + on_counterparty_tx_offered_nondust_htlcs; | |||
on_counterparty_tx_dust_exposure_msat += | |||
commit_tx_fee_sat(excess_feerate, on_counterparty_tx_nondust_htlcs, &self.channel_type) * 1000; | |||
commit_tx_fee_msat(excess_feerate, on_counterparty_tx_nondust_htlcs, &self.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.
I'm not sure I agree that this is better. We can't have a dust exposure level that has msats in it, the whole point of dust exposure is that its the amount we lost when we went on chain. Now, maybe we need to always round up rather than rounding down, but not rounding doesn't seem any more "correct".
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 problem on the delay, thank you for reviewing.
- all the sums for
on_holder_tx_dust_exposure_msat
have msats precision, - all the sums besides this one (commit_tx_fee_sat) for
on_counterparty_tx_dust_exposure_msat
have msats precision. MaxDustHTLCExposure::FixedLimitMsat
has msat precision.
That's why I went this direction. Thank you let me know what you think.
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 other entries are using msats because the balance being lost is actually msats (its the full HTLC value). In this case we're not actually going to lose value in msats, we're going to lose value to "excess fees" which are only really amounts in sats (we expected to pay fee X sats, we end up paying fee Y sats). Similar for the change below in the tx weight calculations - we're never going to pay the msat amount, we're going to pay the sat-rounded amount in fees.
e64e7af
to
7c4f6cf
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! Sadly, I guess we've merged a few things and now it doesn't make sense to backport the first commit. Do you think it makes sense to backport some of the fixes (that apply pre-HTLC-check-after-accept) by splitting commits back up or should we just leave 0.1 as-is?
Oh, sadly this needs (trivial) rebase it seems. |
Previously, `get_pending_htlc_stats` did not account for the inbound htlc because `can_accept_incoming_htlc` was called before the htlc was irrevocably committed. But after commit d8d9dc7, `can_accept_incoming_htlc` is called only when the htlc is irrevocably committed, hence `get_pending_htlc_stats` does account for the inbound htlc. Nonetheless, in the case of a non-dust htlc, our calculation of the counterparty tx dust exposure still assumed that `get_pending_htlc_stats` did not account for the inbound htlc, causing us to add the dust exposure due to that inbound htlc twice. This commit removes this extra sum.
Previously, we calculated the fee of the commitment transaction with n htlcs, and the fee due to the candidate htlc, rounded the two fees to the lower satoshi, and then summed the fees. This is not equal to how fees of commitment transactions are calculated, which is to add up the total weight of the (n+1) htlc commitment transaction, convert to fee, then round to the lower satoshi. This commit corrects this delta by running the full fee calculation twice, once for the n htlc, and once for the (n+1) htlc counterparty commitment transactions.
The payments in this test previously failed for reasons other than exhausting the dust exposure limit with excess fees. Upon payment failures, we now check the logs to assert failures due to dust exposure exhaustion.
This test checks to a 1msat precision the accounting of dust exposure due to excess fees on counterparty commmitment and htlc transactions, for both inbound and outbound htlcs.
7c4f6cf
to
f931db5
Compare
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #3572 +/- ##
==========================================
- Coverage 88.61% 88.61% -0.01%
==========================================
Files 149 149
Lines 117091 117280 +189
Branches 117091 117280 +189
==========================================
+ Hits 103766 103927 +161
- Misses 10816 10841 +25
- Partials 2509 2512 +3 ☔ View full report in Codecov by Sentry. |
This rebase is just the trivial rebase of the four previous commits. No splitting done.
Some thoughts:
|
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.
Thanks for the test coverage! We probably don't need to backport since the 0.1 branch is currently enforcing a value below the expected max_dust_htlc_exposure
, not above.
This might mean that some payments fail when they should not right ? Though we've had no complaints so far. |
Yeah because it makes the limit a bit stricter, which is certainly not worse than making it looser given the type of attack it's preventing. |
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.
Thanks. I'm not sure "we haven't gotten any complaints" is quite the right benchmark given much of this logic shipped and has been in prod at a time when fees have slowly been coming down, where we expect it to not fire as often. That said, I'm also not too worried about getting this backported, really.
and
Only the first commit(?) should be backported