htlcswitch+lnwallet: calculate fee exposure as commit fees + dust

This commit expands the definition of the dust limit to take into
account commitment fees as well as dust HTLCs. The dust limit is now
known as a fee exposure threshold. Dust HTLCs are fees anyways so it
makes sense to account for commitment fees as well. The link has
been modified slightly to calculate dust. In the future, the switch
dust calculations can be removed.
This commit is contained in:
Eugene Siegel committed 2024-07-29 14:13:21 -04:00
1 parent b7c59b36a7
commit d6001d033b
11 files changed
+456 -88

No files matched your search

+79 -35
View File
@@ -18,10 +18,12 @@ import (
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channeldb/models"
"github.com/lightningnetwork/lnd/contractcourt"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/htlcswitch/hodl"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/lntest/mock"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/ticker"
"github.com/stretchr/testify/require"
@@ -4259,7 +4261,7 @@ func TestInterceptableSwitchWatchDog(t *testing.T) {
}
// TestSwitchDustForwarding tests that the switch properly fails HTLC's which
// have incoming or outgoing links that breach their dust thresholds.
// have incoming or outgoing links that breach their fee thresholds.
func TestSwitchDustForwarding(t *testing.T) {
t.Parallel()
@@ -4288,14 +4290,15 @@ func TestSwitchDustForwarding(t *testing.T) {
// We'll test that once the default threshold is exceeded on the
// Alice -> Bob channel, either side's calls to SendHTLC will fail.
//
// Alice will send 357 HTLC's of 700sats. Bob will also send 357 HTLC's
// of 700sats. If either side attempts to send a dust HTLC, it will
// fail so amounts below 800sats will breach the dust threshold.
// Alice will send 354 HTLC's of 700sats. Bob will also send 354 HTLC's
// of 700sats.
numHTLCs := 354
aliceAttemptID, bobAttemptID := numHTLCs, numHTLCs
amt := lnwire.NewMSatFromSatoshis(700)
aliceBobFirstHop := n.aliceChannelLink.ShortChanID()
sendDustHtlcs(t, n, true, amt, aliceBobFirstHop)
sendDustHtlcs(t, n, false, amt, aliceBobFirstHop)
sendDustHtlcs(t, n, true, amt, aliceBobFirstHop, numHTLCs)
sendDustHtlcs(t, n, false, amt, aliceBobFirstHop, numHTLCs)
// Generate the parameters needed for Bob to send another dust HTLC.
_, timelock, hops := generateHops(
@@ -4320,7 +4323,7 @@ func TestSwitchDustForwarding(t *testing.T) {
timeout := time.After(15 * time.Second)
pollInterval := 300 * time.Millisecond
expectedDust := 357 * 2 * amt
expectedDust := 354 * 2 * amt
for {
<-time.After(pollInterval)
@@ -4331,7 +4334,9 @@ func TestSwitchDustForwarding(t *testing.T) {
default:
}
linkDust := link.getDustSum(remote)
linkDust := link.getDustSum(
remote, fn.None[chainfee.SatPerKWeight](),
)
localMailDust, remoteMailDust := mbox.DustPackets()
totalDust := linkDust
@@ -4349,18 +4354,34 @@ func TestSwitchDustForwarding(t *testing.T) {
return true
}
// Wait until Bob is almost at the dust threshold.
// Wait until Bob is almost at the fee threshold.
bobMbox := n.bobServer.htlcSwitch.mailOrchestrator.GetOrCreateMailBox(
n.firstBobChannelLink.ChanID(),
n.firstBobChannelLink.ShortChanID(),
)
require.True(t, checkAlmostDust(n.firstBobChannelLink, bobMbox, false))
// Assert that the HTLC is failed due to the dust threshold.
// Sending one more HTLC should fail. SendHTLC won't error, but the
// HTLC should be failed backwards.
err = n.bobServer.htlcSwitch.SendHTLC(
aliceBobFirstHop, uint64(357), failingHtlc,
aliceBobFirstHop, uint64(bobAttemptID), failingHtlc,
)
require.ErrorIs(t, err, errDustThresholdExceeded)
require.Nil(t, err)
// Use the network result store to ensure the HTLC was failed
// backwards.
bobResultChan, err := n.bobServer.htlcSwitch.GetAttemptResult(
uint64(bobAttemptID), failingHash, newMockDeobfuscator(),
)
require.NoError(t, err)
result, ok := <-bobResultChan
require.True(t, ok)
assertFailureCode(
t, result.Error, lnwire.CodeTemporaryChannelFailure,
)
bobAttemptID++
// Generate the parameters needed for bob to send a non-dust HTLC.
nondustAmt := lnwire.NewMSatFromSatoshis(10_000)
@@ -4371,8 +4392,9 @@ func TestSwitchDustForwarding(t *testing.T) {
blob, err = generateRoute(hops...)
require.NoError(t, err)
// Now attempt to send an HTLC above Bob's dust limit. It should
// succeed.
// Now attempt to send an HTLC above Bob's dust limit. Even though this
// is not a dust HTLC, it should fail because the increase in weight
// pushes us over the threshold.
nondustPreimage := lntypes.Preimage{0, 0, 4}
nondustHash := nondustPreimage.Hash()
nondustHtlc := &lnwire.UpdateAddHTLC{
@@ -4382,12 +4404,23 @@ func TestSwitchDustForwarding(t *testing.T) {
OnionBlob: blob,
}
// Assert that SendHTLC succeeds and evaluateDustThreshold returns
// false.
err = n.bobServer.htlcSwitch.SendHTLC(
aliceBobFirstHop, uint64(358), nondustHtlc,
aliceBobFirstHop, uint64(bobAttemptID), nondustHtlc,
)
require.NoError(t, err)
require.True(t, checkAlmostDust(n.firstBobChannelLink, bobMbox, false))
// Check that the HTLC failed.
bobResultChan, err = n.bobServer.htlcSwitch.GetAttemptResult(
uint64(bobAttemptID), nondustHash, newMockDeobfuscator(),
)
require.NoError(t, err)
result, ok = <-bobResultChan
require.True(t, ok)
assertFailureCode(
t, result.Error, lnwire.CodeTemporaryChannelFailure,
)
// Introduce Carol into the mix and assert that sending a multi-hop
// dust HTLC to Alice will fail. Bob should fail back the HTLC with a
@@ -4417,21 +4450,19 @@ func TestSwitchDustForwarding(t *testing.T) {
carolHtlc,
)
require.NoError(t, err)
carolAttemptID++
carolResultChan, err := n.carolServer.htlcSwitch.GetAttemptResult(
uint64(carolAttemptID-1), carolHash, newMockDeobfuscator(),
uint64(carolAttemptID), carolHash, newMockDeobfuscator(),
)
require.NoError(t, err)
result, ok := <-carolResultChan
result, ok = <-carolResultChan
require.True(t, ok)
assertFailureCode(
t, result.Error, lnwire.CodeTemporaryChannelFailure,
)
// Send an HTLC from Alice to Carol and assert that it is failed at the
// call to SendHTLC.
// Send an HTLC from Alice to Carol and assert that it gets failed.
htlcAmt, totalTimelock, aliceHops := generateHops(
amt, testStartingHeight, n.firstBobChannelLink,
n.carolChannelLink,
@@ -4450,7 +4481,7 @@ func TestSwitchDustForwarding(t *testing.T) {
}
// Wait until Alice's expected dust for the remote commitment is just
// under the dust threshold.
// under the fee threshold.
aliceOrch := n.aliceServer.htlcSwitch.mailOrchestrator
aliceMbox := aliceOrch.GetOrCreateMailBox(
n.aliceChannelLink.ChanID(), n.aliceChannelLink.ShortChanID(),
@@ -4458,23 +4489,36 @@ func TestSwitchDustForwarding(t *testing.T) {
require.True(t, checkAlmostDust(n.aliceChannelLink, aliceMbox, true))
err = n.aliceServer.htlcSwitch.SendHTLC(
n.aliceChannelLink.ShortChanID(), uint64(357),
n.aliceChannelLink.ShortChanID(), uint64(aliceAttemptID),
aliceMultihopHtlc,
)
require.ErrorIs(t, err, errDustThresholdExceeded)
require.Nil(t, err)
aliceResultChan, err := n.aliceServer.htlcSwitch.GetAttemptResult(
uint64(aliceAttemptID), aliceMultihopHash,
newMockDeobfuscator(),
)
require.NoError(t, err)
result, ok = <-aliceResultChan
require.True(t, ok)
assertFailureCode(
t, result.Error, lnwire.CodeTemporaryChannelFailure,
)
// Check that there are numHTLCs circuits open for both Alice and Bob.
require.Equal(t, numHTLCs, n.aliceServer.htlcSwitch.circuits.NumOpen())
require.Equal(t, numHTLCs, n.bobServer.htlcSwitch.circuits.NumOpen())
}
// sendDustHtlcs is a helper function used to send many dust HTLC's to test the
// Switch's dust-threshold logic. It takes a boolean denoting whether or not
// Alice is the sender.
func sendDustHtlcs(t *testing.T, n *threeHopNetwork, alice bool,
amt lnwire.MilliSatoshi, sid lnwire.ShortChannelID) {
amt lnwire.MilliSatoshi, sid lnwire.ShortChannelID, numHTLCs int) {
t.Helper()
// The number of dust HTLC's we'll send for both Alice and Bob.
numHTLCs := 357
// Extract the destination into a variable. If alice is the sender, the
// destination is Bob.
destLink := n.aliceChannelLink
@@ -4527,8 +4571,8 @@ func sendDustHtlcs(t *testing.T, n *threeHopNetwork, alice bool,
}
for {
// It may be the case that the dust threshold is hit
// before all 357*2 HTLC's are sent due to double
// It may be the case that the fee threshold is hit
// before all numHTLCs*2 HTLC's are sent due to double
// counting. Get around this by continuing to send
// until successful.
err = sendingSwitch.SendHTLC(sid, attemptID, htlc)
@@ -4542,7 +4586,7 @@ func sendDustHtlcs(t *testing.T, n *threeHopNetwork, alice bool,
}
// TestSwitchMailboxDust tests that the switch takes into account the mailbox
// dust when evaluating the dust threshold. The mockChannelLink does not have
// dust when evaluating the fee threshold. The mockChannelLink does not have
// channel state, so this only tests the switch-mailbox interaction.
func TestSwitchMailboxDust(t *testing.T) {
t.Parallel()
@@ -4612,7 +4656,7 @@ func TestSwitchMailboxDust(t *testing.T) {
var carolHTLCID uint64
// It will take aliceCount HTLC's of 350sats to fill up Alice's mailbox
// to the point where another would put Alice over the dust threshold.
// to the point where another would put Alice over the fee threshold.
aliceCount := 1428
mailbox := s.mailOrchestrator.GetOrCreateMailBox(chanID1, aliceChanID)
@@ -4634,10 +4678,10 @@ func TestSwitchMailboxDust(t *testing.T) {
carolHTLCID++
}
// Sending one more HTLC to Alice should result in the dust threshold
// Sending one more HTLC to Alice should result in the fee threshold
// being breached.
err = s.SendHTLC(aliceChanID, 0, addMsg)
require.ErrorIs(t, err, errDustThresholdExceeded)
require.ErrorIs(t, err, errFeeExposureExceeded)
// We'll now call ForwardPackets from Bob to ensure that the mailbox
// sum is also accounted for in the forwarding case.