Merge pull request #4768 from halseth/htlcdust-calc

Correct HTLC dust calculation for remote commitment weight
This commit is contained in:
Olaoluwa Osuntokun 2020-11-19 19:55:02 -08:00 committed by GitHub
commit 0e14e0d904
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 136 additions and 2 deletions

@ -4021,7 +4021,7 @@ func (lc *LightningChannel) computeView(view *htlcView, remoteChain bool,
var totalHtlcWeight int64
for _, htlc := range filteredHTLCView.ourUpdates {
if htlcIsDust(
lc.channelState.ChanType, remoteChain, !remoteChain,
lc.channelState.ChanType, false, !remoteChain,
feePerKw, htlc.Amount.ToSatoshis(), dustLimit,
) {
continue
@ -4031,7 +4031,7 @@ func (lc *LightningChannel) computeView(view *htlcView, remoteChain bool,
}
for _, htlc := range filteredHTLCView.theirUpdates {
if htlcIsDust(
lc.channelState.ChanType, !remoteChain, !remoteChain,
lc.channelState.ChanType, true, !remoteChain,
feePerKw, htlc.Amount.ToSatoshis(), dustLimit,
) {
continue

@ -5331,6 +5331,140 @@ func TestChanAvailableBalanceNearHtlcFee(t *testing.T) {
checkBalance(t, expAliceBalance, expBobBalance)
}
// TestChanCommitWeightDustHtlcs checks that we correctly calculate the
// commitment weight when some HTLCs are dust.
func TestChanCommitWeightDustHtlcs(t *testing.T) {
t.Parallel()
// Create a test channel which will be used for the duration of this
// unittest. The channel will be funded evenly with Alice having 5 BTC,
// and Bob having 5 BTC.
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(
channeldb.SingleFunderTweaklessBit,
)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
aliceDustlimit := lnwire.NewMSatFromSatoshis(
aliceChannel.channelState.LocalChanCfg.DustLimit,
)
bobDustlimit := lnwire.NewMSatFromSatoshis(
bobChannel.channelState.LocalChanCfg.DustLimit,
)
feeRate := chainfee.SatPerKWeight(
aliceChannel.channelState.LocalCommitment.FeePerKw,
)
htlcTimeoutFee := lnwire.NewMSatFromSatoshis(
HtlcTimeoutFee(aliceChannel.channelState.ChanType, feeRate),
)
htlcSuccessFee := lnwire.NewMSatFromSatoshis(
HtlcSuccessFee(aliceChannel.channelState.ChanType, feeRate),
)
// Helper method to add an HTLC from Alice to Bob.
htlcIndex := uint64(0)
addHtlc := func(htlcAmt lnwire.MilliSatoshi) lntypes.Preimage {
t.Helper()
htlc, preImage := createHTLC(int(htlcIndex), htlcAmt)
if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil {
t.Fatalf("unable to add htlc: %v", err)
}
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
t.Fatalf("unable to recv htlc: %v", err)
}
if err := ForceStateTransition(aliceChannel, bobChannel); err != nil {
t.Fatalf("unable to complete alice's state "+
"transition: %v", err)
}
return preImage
}
settleHtlc := func(preImage lntypes.Preimage) {
t.Helper()
err = bobChannel.SettleHTLC(preImage, htlcIndex, nil, nil, nil)
if err != nil {
t.Fatalf("unable to settle htlc: %v", err)
}
err = aliceChannel.ReceiveHTLCSettle(preImage, htlcIndex)
if err != nil {
t.Fatalf("unable to settle htlc: %v", err)
}
if err := ForceStateTransition(aliceChannel, bobChannel); err != nil {
t.Fatalf("unable to complete alice's state "+
"transition: %v", err)
}
htlcIndex++
}
// Helper method that fetches the current remote commitment weight
// fromt the given channel's POV.
remoteCommitWeight := func(lc *LightningChannel) int64 {
remoteACKedIndex := lc.localCommitChain.tip().theirMessageIndex
htlcView := lc.fetchHTLCView(remoteACKedIndex,
lc.localUpdateLog.logIndex)
_, w := lc.availableCommitmentBalance(
htlcView, true,
)
return w
}
// Start by getting the initial remote commitment wight seen from
// Alice's perspective. At this point there are no HTLCs on the
// commitment.
weight1 := remoteCommitWeight(aliceChannel)
// Now add an HTLC that will be just below Bob's dustlimit.
// Since this is an HTLC added from Alice on Bob's commitment, we will
// use the HTLC success fee.
bobDustHtlc := bobDustlimit + htlcSuccessFee - 1
preimg := addHtlc(bobDustHtlc)
// Now get the current wight of the remote commitment. We expect it to
// not have changed, since the HTLC we added is considered dust.
weight2 := remoteCommitWeight(aliceChannel)
require.Equal(t, weight1, weight2)
// In addition, we expect this weight to result in the fee we currently
// see being paid on the remote commitent.
calcFee := feeRate.FeeForWeight(weight2)
remoteCommitFee := aliceChannel.channelState.RemoteCommitment.CommitFee
require.Equal(t, calcFee, remoteCommitFee)
// Settle the HTLC, bringing commitment weight back to base.
settleHtlc(preimg)
// Now we do a similar check from Bob's POV. Start with getting his
// current view of Alice's commitment weight.
weight1 = remoteCommitWeight(bobChannel)
// We'll add an HTLC from Alice to Bob, that is just above dust on
// Alice's commitment. Now we'll use the timeout fee.
aliceDustHtlc := aliceDustlimit + htlcTimeoutFee
preimg = addHtlc(aliceDustHtlc)
// Get the current remote commitment weight from Bob's POV, and ensure
// it is now heavier, since Alice added a non-dust HTLC.
weight2 = remoteCommitWeight(bobChannel)
require.Greater(t, weight2, weight1)
// Ensure the current remote commit has the expected commitfee.
calcFee = feeRate.FeeForWeight(weight2)
remoteCommitFee = bobChannel.channelState.RemoteCommitment.CommitFee
require.Equal(t, calcFee, remoteCommitFee)
settleHtlc(preimg)
}
// TestSignCommitmentFailNotLockedIn tests that a channel will not attempt to
// create a new state if it doesn't yet know of the next revocation point for
// the remote party.