lnwallet: introduce GetDustSum method to calculate worst-case dust sum

It over-estimates the local or remote commitment's dust sum by
counting all updates in both updateLogs that are dust using the
trimmed-to-dust mechanism if applicable. The over-estimation is done
because ensuring an accurate counting is a trade-off between code
simplicity and accuracy.
This commit is contained in:
eugene committed 2021-09-30 13:44:26 -04:00
1 parent 34cb9357ee
commit 7d16e58b5c
4 files changed
+255 -18

No files matched your search

+175
View File
@@ -9816,3 +9816,178 @@ func assertCleanOrDirty(clean bool, alice, bob *LightningChannel,
require.False(t, alice.IsChannelClean())
require.False(t, bob.IsChannelClean())
}
// TestChannelGetDustSum tests that we correctly calculate the channel's dust
// sum for the local and remote commitments.
func TestChannelGetDustSum(t *testing.T) {
t.Run("dust sum tweakless", func(t *testing.T) {
testGetDustSum(t, channeldb.SingleFunderTweaklessBit)
})
t.Run("dust sum anchors zero htlc fee", func(t *testing.T) {
testGetDustSum(t, channeldb.SingleFunderTweaklessBit|
channeldb.AnchorOutputsBit|
channeldb.ZeroHtlcTxFeeBit,
)
})
}
func testGetDustSum(t *testing.T, chantype channeldb.ChannelType) {
t.Parallel()
// This makes a channel with Alice's dust limit set to 200sats and
// Bob's dust limit set to 1300sats.
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(chantype)
require.NoError(t, err)
defer cleanUp()
// Use a function closure to assert the dust sum for a passed channel's
// local and remote commitments match the expected values.
checkDust := func(c *LightningChannel, expLocal,
expRemote lnwire.MilliSatoshi) {
localDustSum := c.GetDustSum(false)
require.Equal(t, expLocal, localDustSum)
remoteDustSum := c.GetDustSum(true)
require.Equal(t, expRemote, remoteDustSum)
}
// We'll lower the fee from 6000sats/kWU to 253sats/kWU for our test.
fee := chainfee.SatPerKWeight(253)
err = aliceChannel.UpdateFee(fee)
require.NoError(t, err)
err = bobChannel.ReceiveUpdateFee(fee)
require.NoError(t, err)
err = ForceStateTransition(aliceChannel, bobChannel)
require.NoError(t, err)
// Create an HTLC that Bob will send to Alice which is above Alice's
// dust limit and below Bob's dust limit. This takes into account dust
// trimming for non-zero-fee channels.
htlc1Amt := lnwire.MilliSatoshi(700_000)
htlc1, preimage1 := createHTLC(0, htlc1Amt)
_, err = bobChannel.AddHTLC(htlc1, nil)
require.NoError(t, err)
_, err = aliceChannel.ReceiveHTLC(htlc1)
require.NoError(t, err)
// Assert that GetDustSum from Alice's perspective does not consider
// the HTLC dust on her commitment, but does on Bob's commitment.
checkDust(aliceChannel, lnwire.MilliSatoshi(0), htlc1Amt)
// Assert that GetDustSum from Bob's perspective results in the same
// conditions above holding.
checkDust(bobChannel, htlc1Amt, lnwire.MilliSatoshi(0))
// Forcing a state transition to occur should not change the dust sum.
err = ForceStateTransition(bobChannel, aliceChannel)
require.NoError(t, err)
checkDust(aliceChannel, lnwire.MilliSatoshi(0), htlc1Amt)
checkDust(bobChannel, htlc1Amt, lnwire.MilliSatoshi(0))
// Settling the HTLC back from Alice to Bob should not change the dust
// sum because the HTLC is counted until it's removed from the update
// logs via compactLogs.
err = aliceChannel.SettleHTLC(preimage1, uint64(0), nil, nil, nil)
require.NoError(t, err)
err = bobChannel.ReceiveHTLCSettle(preimage1, uint64(0))
require.NoError(t, err)
checkDust(aliceChannel, lnwire.MilliSatoshi(0), htlc1Amt)
checkDust(bobChannel, htlc1Amt, lnwire.MilliSatoshi(0))
// Forcing a state transition will remove the HTLC in-memory for Bob
// since ReceiveRevocation is called which calls compactLogs. Bob
// should have a zero dust sum at this point. Alice will see Bob as
// having the original dust sum since compactLogs hasn't been called.
err = ForceStateTransition(aliceChannel, bobChannel)
require.NoError(t, err)
checkDust(aliceChannel, lnwire.MilliSatoshi(0), htlc1Amt)
checkDust(bobChannel, lnwire.MilliSatoshi(0), lnwire.MilliSatoshi(0))
// Alice now sends an HTLC of 100sats, which is below both sides' dust
// limits.
htlc2Amt := lnwire.MilliSatoshi(100_000)
htlc2, _ := createHTLC(0, htlc2Amt)
_, err = aliceChannel.AddHTLC(htlc2, nil)
require.NoError(t, err)
_, err = bobChannel.ReceiveHTLC(htlc2)
require.NoError(t, err)
// Assert that GetDustSum from Alice's perspective includes the new
// HTLC as dust on both commitments.
checkDust(aliceChannel, htlc2Amt, htlc1Amt+htlc2Amt)
// Assert that GetDustSum from Bob's perspective also includes the HTLC
// on both commitments.
checkDust(bobChannel, htlc2Amt, htlc2Amt)
// Alice signs for this HTLC and neither perspective should change.
aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment()
require.NoError(t, err)
err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs)
require.NoError(t, err)
checkDust(aliceChannel, htlc2Amt, htlc1Amt+htlc2Amt)
checkDust(bobChannel, htlc2Amt, htlc2Amt)
// Bob now sends a revocation for his prior commitment, and this should
// change Alice's perspective to no longer include the first HTLC as
// dust.
bobRevocation, _, err := bobChannel.RevokeCurrentCommitment()
require.NoError(t, err)
_, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation)
require.NoError(t, err)
checkDust(aliceChannel, htlc2Amt, htlc2Amt)
checkDust(bobChannel, htlc2Amt, htlc2Amt)
// The rest of the dance is completed and neither perspective should
// change.
bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment()
require.NoError(t, err)
err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs)
require.NoError(t, err)
aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment()
require.NoError(t, err)
_, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation)
require.NoError(t, err)
checkDust(aliceChannel, htlc2Amt, htlc2Amt)
checkDust(bobChannel, htlc2Amt, htlc2Amt)
// We'll now assert that if Alice sends an HTLC above her dust limit
// and then updates the fee of the channel to trigger the trimmed to
// dust mechanism, Alice will count this HTLC in the dust sum for her
// commitment in the non-zero-fee case.
htlc3Amt := lnwire.MilliSatoshi(400_000)
htlc3, _ := createHTLC(1, htlc3Amt)
_, err = aliceChannel.AddHTLC(htlc3, nil)
require.NoError(t, err)
_, err = bobChannel.ReceiveHTLC(htlc3)
require.NoError(t, err)
// Assert that this new HTLC is not counted on Alice's local commitment
// in the dust sum. Bob's commitment should count it.
checkDust(aliceChannel, htlc2Amt, htlc2Amt+htlc3Amt)
checkDust(bobChannel, htlc2Amt+htlc3Amt, htlc2Amt)
// Alice will now send UpdateFee with a large feerate and neither
// perspective should change.
fee = chainfee.SatPerKWeight(50_000)
err = aliceChannel.UpdateFee(fee)
require.NoError(t, err)
err = bobChannel.ReceiveUpdateFee(fee)
require.NoError(t, err)
checkDust(aliceChannel, htlc2Amt, htlc2Amt+htlc3Amt)
checkDust(bobChannel, htlc2Amt+htlc3Amt, htlc2Amt)
// Forcing a state transition should change in the non-zero-fee case.
err = ForceStateTransition(aliceChannel, bobChannel)
require.NoError(t, err)
if chantype.ZeroHtlcTxFee() {
checkDust(aliceChannel, htlc2Amt, htlc2Amt+htlc3Amt)
checkDust(bobChannel, htlc2Amt+htlc3Amt, htlc2Amt)
} else {
checkDust(aliceChannel, htlc2Amt+htlc3Amt, htlc2Amt+htlc3Amt)
checkDust(bobChannel, htlc2Amt+htlc3Amt, htlc2Amt+htlc3Amt)
}
}