71
71
// ErrLocalAddFailed signals that the ADD htlc for a local payment
72
72
// failed to be processed.
73
73
ErrLocalAddFailed = errors .New ("local add HTLC failed" )
74
+
75
+ // errDustThresholdExceeded is only surfaced to callers of SendHTLC and
76
+ // signals that sending the HTLC would exceed the outgoing link's dust
77
+ // threshold.
78
+ errDustThresholdExceeded = errors .New ("dust threshold exceeded" )
79
+
80
+ // defaultDustThreshold is the default threshold after which we'll fail
81
+ // payments if they are dust. This is currently set to 500k sats.
82
+ defaultDustThreshold = lnwire .MilliSatoshi (500_000_000 )
74
83
)
75
84
76
85
// plexPacket encapsulates switch packet and adds error channel to receive
@@ -469,6 +478,51 @@ func (s *Switch) SendHTLC(firstHop lnwire.ShortChannelID, attemptID uint64,
469
478
htlc : htlc ,
470
479
}
471
480
481
+ // Attempt to fetch the target link before creating a circuit so that
482
+ // we don't leave dangling circuits. The getLocalLink method does not
483
+ // require the circuit variable to be set on the *htlcPacket.
484
+ link , linkErr := s .getLocalLink (packet , htlc )
485
+ if linkErr != nil {
486
+ // Notify the htlc notifier of a link failure on our outgoing
487
+ // link. Incoming timelock/amount values are not set because
488
+ // they are not present for local sends.
489
+ s .cfg .HtlcNotifier .NotifyLinkFailEvent (
490
+ newHtlcKey (packet ),
491
+ HtlcInfo {
492
+ OutgoingTimeLock : htlc .Expiry ,
493
+ OutgoingAmt : htlc .Amount ,
494
+ },
495
+ HtlcEventTypeSend ,
496
+ linkErr ,
497
+ false ,
498
+ )
499
+
500
+ return linkErr
501
+ }
502
+
503
+ // Evaluate whether this HTLC would increase our exposure to dust. If
504
+ // it does, don't send it out and instead return an error.
505
+ if s .evaluateDustThreshold (link , htlc .Amount , false ) {
506
+ // Notify the htlc notifier of a link failure on our outgoing
507
+ // link. We use the FailTemporaryChannelFailure in place of a
508
+ // more descriptive error message.
509
+ linkErr := NewLinkError (
510
+ & lnwire.FailTemporaryChannelFailure {},
511
+ )
512
+ s .cfg .HtlcNotifier .NotifyLinkFailEvent (
513
+ newHtlcKey (packet ),
514
+ HtlcInfo {
515
+ OutgoingTimeLock : htlc .Expiry ,
516
+ OutgoingAmt : htlc .Amount ,
517
+ },
518
+ HtlcEventTypeSend ,
519
+ linkErr ,
520
+ false ,
521
+ )
522
+
523
+ return errDustThresholdExceeded
524
+ }
525
+
472
526
circuit := newPaymentCircuit (& htlc .PaymentHash , packet )
473
527
actions , err := s .circuits .CommitCircuits (circuit )
474
528
if err != nil {
@@ -488,27 +542,6 @@ func (s *Switch) SendHTLC(firstHop lnwire.ShortChannelID, attemptID uint64,
488
542
// Send packet to link.
489
543
packet .circuit = circuit
490
544
491
- // User has created the htlc update therefore we should find the
492
- // appropriate channel link and send the payment over this link.
493
- link , linkErr := s .getLocalLink (packet , htlc )
494
- if linkErr != nil {
495
- // Notify the htlc notifier of a link failure on our
496
- // outgoing link. Incoming timelock/amount values are
497
- // not set because they are not present for local sends.
498
- s .cfg .HtlcNotifier .NotifyLinkFailEvent (
499
- newHtlcKey (packet ),
500
- HtlcInfo {
501
- OutgoingTimeLock : htlc .Expiry ,
502
- OutgoingAmt : htlc .Amount ,
503
- },
504
- HtlcEventTypeSend ,
505
- linkErr ,
506
- false ,
507
- )
508
-
509
- return linkErr
510
- }
511
-
512
545
return link .handleLocalAddPacket (packet )
513
546
}
514
547
@@ -1098,6 +1131,52 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error {
1098
1131
// what the best channel is.
1099
1132
destination := destinations [rand .Intn (len (destinations ))]
1100
1133
1134
+ // Retrieve the incoming link by its ShortChannelID. Note that
1135
+ // the incomingChanID is never set to hop.Source here.
1136
+ s .indexMtx .RLock ()
1137
+ incomingLink , err := s .getLinkByShortID (packet .incomingChanID )
1138
+ if err != nil {
1139
+ s .indexMtx .RUnlock ()
1140
+
1141
+ // If we couldn't find the incoming link, we can't
1142
+ // evaluate the incoming's exposure to dust, so we just
1143
+ // fail the HTLC back.
1144
+ linkErr := NewLinkError (
1145
+ & lnwire.FailTemporaryChannelFailure {},
1146
+ )
1147
+
1148
+ return s .failAddPacket (packet , linkErr )
1149
+ }
1150
+ s .indexMtx .RUnlock ()
1151
+
1152
+ // Evaluate whether this HTLC would increase our exposure to
1153
+ // dust on the incoming link. If it does, fail it backwards.
1154
+ if s .evaluateDustThreshold (
1155
+ incomingLink , packet .incomingAmount , true ,
1156
+ ) {
1157
+ // The incoming dust exceeds the threshold, so we fail
1158
+ // the add back.
1159
+ linkErr := NewLinkError (
1160
+ & lnwire.FailTemporaryChannelFailure {},
1161
+ )
1162
+
1163
+ return s .failAddPacket (packet , linkErr )
1164
+ }
1165
+
1166
+ // Also evaluate whether this HTLC would increase our exposure
1167
+ // to dust on the destination link. If it does, fail it back.
1168
+ if s .evaluateDustThreshold (
1169
+ destination , packet .amount , false ,
1170
+ ) {
1171
+ // The outgoing dust exceeds the threshold, so we fail
1172
+ // the add back.
1173
+ linkErr := NewLinkError (
1174
+ & lnwire.FailTemporaryChannelFailure {},
1175
+ )
1176
+
1177
+ return s .failAddPacket (packet , linkErr )
1178
+ }
1179
+
1101
1180
// Send the packet to the destination channel link which
1102
1181
// manages the channel.
1103
1182
packet .outgoingChanID = destination .ShortChanID ()
@@ -2279,3 +2358,69 @@ func (s *Switch) FlushForwardingEvents() error {
2279
2358
func (s * Switch ) BestHeight () uint32 {
2280
2359
return atomic .LoadUint32 (& s .bestHeight )
2281
2360
}
2361
+
2362
+ // evaluateDustThreshold takes in a ChannelLink, HTLC amount, and a boolean to
2363
+ // determine whether the default dust threshold has been exceeded. This
2364
+ // heuristic is only used for HTLC's that are below either side's dust limits.
2365
+ // The sum of the commitment's dust with the mailbox's dust with the amount is
2366
+ // checked against the default threshold. If incoming is true, then the amount
2367
+ // is not included in the sum as it was already included in the commitment's
2368
+ // dust. A boolean is returned telling the caller whether the HTLC should be
2369
+ // failed back.
2370
+ func (s * Switch ) evaluateDustThreshold (link ChannelLink ,
2371
+ amount lnwire.MilliSatoshi , incoming bool ) bool {
2372
+
2373
+ // Retrieve the link's local and remote dust limits.
2374
+ localLimit , remoteLimit := link .getDustLimits ()
2375
+
2376
+ // Now check whether this HTLC amount is dust on either side's
2377
+ // commitment transaction.
2378
+ isLocalDust := amount < localLimit
2379
+ isRemoteDust := amount < remoteLimit
2380
+ if isLocalDust || isRemoteDust {
2381
+ // Fetch the dust sums currently in the mailbox for this link.
2382
+ cid := link .ChanID ()
2383
+ sid := link .ShortChanID ()
2384
+ mailbox := s .mailOrchestrator .GetOrCreateMailBox (cid , sid )
2385
+ localMailDust , remoteMailDust := mailbox .DustPackets ()
2386
+
2387
+ // If the htlc is dust on the local commitment, we'll obtain
2388
+ // the dust sum for it.
2389
+ if isLocalDust {
2390
+ localSum := link .getDustSum (false )
2391
+ localSum += localMailDust
2392
+
2393
+ // Optionally include the HTLC amount only for outgoing
2394
+ // channels.
2395
+ if ! incoming {
2396
+ localSum += amount
2397
+ }
2398
+
2399
+ // Finally check against the defined dust threshold.
2400
+ if localSum > defaultDustThreshold {
2401
+ return true
2402
+ }
2403
+ }
2404
+
2405
+ // Also check if the htlc is dust on the remote commitment, if
2406
+ // we've reached this point.
2407
+ if isRemoteDust {
2408
+ remoteSum := link .getDustSum (true )
2409
+ remoteSum += remoteMailDust
2410
+
2411
+ // Optionally include the HTLC amount only for outgoing
2412
+ // channels.
2413
+ if ! incoming {
2414
+ remoteSum += amount
2415
+ }
2416
+
2417
+ // Finally check against the defined dust threshold.
2418
+ if remoteSum > defaultDustThreshold {
2419
+ return true
2420
+ }
2421
+ }
2422
+ }
2423
+
2424
+ // If we reached this point, this HTLC is fine to forward.
2425
+ return false
2426
+ }
0 commit comments