Skip to content

Commit 32fa48d

Browse files
authored
Merge pull request #5770 from Crypt-iQ/dust_threshold_0619
lnwallet+htlcswitch: make Switch dust-aware
2 parents 8ba68ca + 25a0fe2 commit 32fa48d

File tree

16 files changed

+1120
-39
lines changed

16 files changed

+1120
-39
lines changed

config.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,8 @@ type Config struct {
355355

356356
GcCanceledInvoicesOnTheFly bool `long:"gc-canceled-invoices-on-the-fly" description:"If true, we'll delete newly canceled invoices on the fly."`
357357

358+
DustThreshold uint64 `long:"dust-threshold" description:"Sets the dust sum threshold in satoshis for a channel after which dust HTLC's will be failed."`
359+
358360
Invoices *lncfg.Invoices `group:"invoices" namespace:"invoices"`
359361

360362
Routing *lncfg.Routing `group:"routing" namespace:"routing"`
@@ -550,6 +552,7 @@ func DefaultConfig() Config {
550552
MaxOutgoingCltvExpiry: htlcswitch.DefaultMaxOutgoingCltvExpiry,
551553
MaxChannelFeeAllocation: htlcswitch.DefaultMaxLinkFeeAllocation,
552554
MaxCommitFeeRateAnchors: lnwallet.DefaultAnchorsCommitMaxFeeRateSatPerVByte,
555+
DustThreshold: uint64(htlcswitch.DefaultDustThreshold.ToSatoshis()),
553556
LogWriter: build.NewRotatingLogWriter(),
554557
DB: lncfg.DefaultDB(),
555558
Cluster: lncfg.DefaultCluster(),

docs/release-notes/release-notes-0.13.3.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
* [The `DefaultDustLimit` method has been removed in favor of `DustLimitForSize` which calculates the proper network dust limit for a given output size. This also fixes certain APIs like SendCoins to be able to send 294 sats to a P2WPKH script.](https://github.com/lightningnetwork/lnd/pull/5781)
66

7+
## Safety
8+
9+
* [The `htlcswitch.Switch` has been modified to take into account the total dust sum on the incoming and outgoing channels before forwarding. After the defined threshold is reached, dust HTLC's will start to be failed. The default threshold is 500K satoshis and can be modified by setting `--dust-threshold=` when running `lnd`.](https://github.com/lightningnetwork/lnd/pull/5770)
10+
711
# Contributors (Alphabetical Order)
812

913
* Eugene Siegel

htlcswitch/interfaces.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/lightningnetwork/lnd/lnpeer"
88
"github.com/lightningnetwork/lnd/lntypes"
99
"github.com/lightningnetwork/lnd/lnwallet"
10+
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
1011
"github.com/lightningnetwork/lnd/lnwire"
1112
"github.com/lightningnetwork/lnd/record"
1213
)
@@ -57,6 +58,21 @@ type packetHandler interface {
5758
handleLocalAddPacket(*htlcPacket) error
5859
}
5960

61+
// dustHandler is an interface used exclusively by the Switch to evaluate
62+
// whether a link has too much dust exposure.
63+
type dustHandler interface {
64+
// getDustSum returns the dust sum on either the local or remote
65+
// commitment.
66+
getDustSum(remote bool) lnwire.MilliSatoshi
67+
68+
// getFeeRate returns the current channel feerate.
69+
getFeeRate() chainfee.SatPerKWeight
70+
71+
// getDustClosure returns a closure that can evaluate whether a passed
72+
// HTLC is dust.
73+
getDustClosure() dustClosure
74+
}
75+
6076
// ChannelUpdateHandler is an interface that provides methods that allow
6177
// sending lnwire.Message to the underlying link as well as querying state.
6278
type ChannelUpdateHandler interface {
@@ -122,6 +138,9 @@ type ChannelLink interface {
122138
// Embed the ChannelUpdateHandler interface.
123139
ChannelUpdateHandler
124140

141+
// Embed the dustHandler interface.
142+
dustHandler
143+
125144
// ChannelPoint returns the channel outpoint for the channel link.
126145
ChannelPoint() *wire.OutPoint
127146

htlcswitch/link.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"github.com/btcsuite/btcd/wire"
1414
"github.com/btcsuite/btclog"
15+
"github.com/btcsuite/btcutil"
1516
"github.com/davecgh/go-spew/spew"
1617
"github.com/go-errors/errors"
1718
"github.com/lightningnetwork/lnd/build"
@@ -1929,6 +1930,10 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) {
19291930
"error receiving fee update: %v", err)
19301931
return
19311932
}
1933+
1934+
// Update the mailbox's feerate as well.
1935+
l.mailBox.SetFeeRate(fee)
1936+
19321937
case *lnwire.Error:
19331938
// Error received from remote, MUST fail channel, but should
19341939
// only print the contents of the error message if all
@@ -2192,6 +2197,64 @@ func (l *channelLink) MayAddOutgoingHtlc() error {
21922197
return l.channel.MayAddOutgoingHtlc()
21932198
}
21942199

2200+
// getDustSum is a wrapper method that calls the underlying channel's dust sum
2201+
// method.
2202+
//
2203+
// NOTE: Part of the dustHandler interface.
2204+
func (l *channelLink) getDustSum(remote bool) lnwire.MilliSatoshi {
2205+
return l.channel.GetDustSum(remote)
2206+
}
2207+
2208+
// getFeeRate is a wrapper method that retrieves the underlying channel's
2209+
// feerate.
2210+
//
2211+
// NOTE: Part of the dustHandler interface.
2212+
func (l *channelLink) getFeeRate() chainfee.SatPerKWeight {
2213+
return l.channel.CommitFeeRate()
2214+
}
2215+
2216+
// getDustClosure returns a closure that can be used by the switch or mailbox
2217+
// to evaluate whether a given HTLC is dust.
2218+
//
2219+
// NOTE: Part of the dustHandler interface.
2220+
func (l *channelLink) getDustClosure() dustClosure {
2221+
localDustLimit := l.channel.State().LocalChanCfg.DustLimit
2222+
remoteDustLimit := l.channel.State().RemoteChanCfg.DustLimit
2223+
chanType := l.channel.State().ChanType
2224+
2225+
return dustHelper(chanType, localDustLimit, remoteDustLimit)
2226+
}
2227+
2228+
// dustClosure is a function that evaluates whether an HTLC is dust. It returns
2229+
// true if the HTLC is dust. It takes in a feerate, a boolean denoting whether
2230+
// the HTLC is incoming (i.e. one that the remote sent), a boolean denoting
2231+
// whether to evaluate on the local or remote commit, and finally an HTLC
2232+
// amount to test.
2233+
type dustClosure func(chainfee.SatPerKWeight, bool, bool, btcutil.Amount) bool
2234+
2235+
// dustHelper is used to construct the dustClosure.
2236+
func dustHelper(chantype channeldb.ChannelType, localDustLimit,
2237+
remoteDustLimit btcutil.Amount) dustClosure {
2238+
2239+
isDust := func(feerate chainfee.SatPerKWeight, incoming,
2240+
localCommit bool, amt btcutil.Amount) bool {
2241+
2242+
if localCommit {
2243+
return lnwallet.HtlcIsDust(
2244+
chantype, incoming, true, feerate, amt,
2245+
localDustLimit,
2246+
)
2247+
}
2248+
2249+
return lnwallet.HtlcIsDust(
2250+
chantype, incoming, false, feerate, amt,
2251+
remoteDustLimit,
2252+
)
2253+
}
2254+
2255+
return isDust
2256+
}
2257+
21952258
// AttachMailBox updates the current mailbox used by this link, and hooks up
21962259
// the mailbox's message and packet outboxes to the link's upstream and
21972260
// downstream chans, respectively.
@@ -2201,6 +2264,14 @@ func (l *channelLink) AttachMailBox(mailbox MailBox) {
22012264
l.upstream = mailbox.MessageOutBox()
22022265
l.downstream = mailbox.PacketOutBox()
22032266
l.Unlock()
2267+
2268+
// Set the mailbox's fee rate. This may be refreshing a feerate that was
2269+
// never committed.
2270+
l.mailBox.SetFeeRate(l.getFeeRate())
2271+
2272+
// Also set the mailbox's dust closure so that it can query whether HTLC's
2273+
// are dust given the current feerate.
2274+
l.mailBox.SetDustClosure(l.getDustClosure())
22042275
}
22052276

22062277
// UpdateForwardingPolicy updates the forwarding policy for the target
@@ -2501,6 +2572,10 @@ func (l *channelLink) updateChannelFee(feePerKw chainfee.SatPerKWeight) error {
25012572
return err
25022573
}
25032574

2575+
// The fee passed the channel's validation checks, so we update the
2576+
// mailbox feerate.
2577+
l.mailBox.SetFeeRate(feePerKw)
2578+
25042579
// We'll then attempt to send a new UpdateFee message, and also lock it
25052580
// in immediately by triggering a commitment update.
25062581
msg := lnwire.NewUpdateFee(l.ChanID(), uint32(feePerKw))

htlcswitch/mailbox.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"time"
1010

1111
"github.com/lightningnetwork/lnd/clock"
12+
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
1213
"github.com/lightningnetwork/lnd/lnwire"
1314
)
1415

@@ -66,6 +67,17 @@ type MailBox interface {
6667
// Reset the packet head to point at the first element in the list.
6768
ResetPackets() error
6869

70+
// SetDustClosure takes in a closure that is used to evaluate whether
71+
// mailbox HTLC's are dust.
72+
SetDustClosure(isDust dustClosure)
73+
74+
// SetFeeRate sets the feerate to be used when evaluating dust.
75+
SetFeeRate(feerate chainfee.SatPerKWeight)
76+
77+
// DustPackets returns the dust sum for Adds in the mailbox for the
78+
// local and remote commitments.
79+
DustPackets() (lnwire.MilliSatoshi, lnwire.MilliSatoshi)
80+
6981
// Start starts the mailbox and any goroutines it needs to operate
7082
// properly.
7183
Start()
@@ -131,6 +143,17 @@ type memoryMailBox struct {
131143
wireShutdown chan struct{}
132144
pktShutdown chan struct{}
133145
quit chan struct{}
146+
147+
// feeRate is set when the link receives or sends out fee updates. It
148+
// is refreshed when AttachMailBox is called in case a fee update did
149+
// not get committed. In some cases it may be out of sync with the
150+
// channel's feerate, but it should eventually get back in sync.
151+
feeRate chainfee.SatPerKWeight
152+
153+
// isDust is set when AttachMailBox is called and serves to evaluate
154+
// the outstanding dust in the memoryMailBox given the current set
155+
// feeRate.
156+
isDust dustClosure
134157
}
135158

136159
// newMemoryMailBox creates a new instance of the memoryMailBox.
@@ -610,6 +633,61 @@ func (m *memoryMailBox) AddPacket(pkt *htlcPacket) error {
610633
return nil
611634
}
612635

636+
// SetFeeRate sets the memoryMailBox's feerate for use in DustPackets.
637+
func (m *memoryMailBox) SetFeeRate(feeRate chainfee.SatPerKWeight) {
638+
m.pktCond.L.Lock()
639+
defer m.pktCond.L.Unlock()
640+
641+
m.feeRate = feeRate
642+
}
643+
644+
// SetDustClosure sets the memoryMailBox's dustClosure for use in DustPackets.
645+
func (m *memoryMailBox) SetDustClosure(isDust dustClosure) {
646+
m.pktCond.L.Lock()
647+
defer m.pktCond.L.Unlock()
648+
649+
m.isDust = isDust
650+
}
651+
652+
// DustPackets returns the dust sum for add packets in the mailbox. The first
653+
// return value is the local dust sum and the second is the remote dust sum.
654+
// This will keep track of a given dust HTLC from the time it is added via
655+
// AddPacket until it is removed via AckPacket.
656+
func (m *memoryMailBox) DustPackets() (lnwire.MilliSatoshi,
657+
lnwire.MilliSatoshi) {
658+
659+
m.pktCond.L.Lock()
660+
defer m.pktCond.L.Unlock()
661+
662+
var (
663+
localDustSum lnwire.MilliSatoshi
664+
remoteDustSum lnwire.MilliSatoshi
665+
)
666+
667+
// Run through the map of HTLC's and determine the dust sum with calls
668+
// to the memoryMailBox's isDust closure. Note that all mailbox packets
669+
// are outgoing so the second argument to isDust will be false.
670+
for _, e := range m.addIndex {
671+
addPkt := e.Value.(*pktWithExpiry).pkt
672+
673+
// Evaluate whether this HTLC is dust on the local commitment.
674+
if m.isDust(
675+
m.feeRate, false, true, addPkt.amount.ToSatoshis(),
676+
) {
677+
localDustSum += addPkt.amount
678+
}
679+
680+
// Evaluate whether this HTLC is dust on the remote commitment.
681+
if m.isDust(
682+
m.feeRate, false, false, addPkt.amount.ToSatoshis(),
683+
) {
684+
remoteDustSum += addPkt.amount
685+
}
686+
}
687+
688+
return localDustSum, remoteDustSum
689+
}
690+
613691
// FailAdd fails an UpdateAddHTLC that exists within the mailbox, removing it
614692
// from the in-memory replay buffer. This will prevent the packet from being
615693
// delivered after the link restarts if the switch has remained online. The

0 commit comments

Comments
 (0)