@@ -516,12 +516,12 @@ func cloneParameters(params Parameters) Parameters {
516516// autoloop gets a set of suggested swaps and dispatches them automatically if
517517// we have automated looping enabled.
518518func (m * Manager ) autoloop (ctx context.Context ) error {
519- swaps , err := m .SuggestSwaps (ctx , true )
519+ suggestion , err := m .SuggestSwaps (ctx , true )
520520 if err != nil {
521521 return err
522522 }
523523
524- for _ , swap := range swaps {
524+ for _ , swap := range suggestion . OutSwaps {
525525 // If we don't actually have dispatch of swaps enabled, log
526526 // suggestions.
527527 if ! m .params .Autoloop {
@@ -557,14 +557,44 @@ func (m *Manager) ForceAutoLoop(ctx context.Context) error {
557557 }
558558}
559559
560+ // Suggestions provides a set of suggested swaps, and the set of channels that
561+ // were excluded from consideration.
562+ type Suggestions struct {
563+ // OutSwaps is the set of loop out swaps that we suggest executing.
564+ OutSwaps []loop.OutRequest
565+
566+ // DisqualifiedChans maps the set of channels that we do not recommend
567+ // swaps on to the reason that we did not recommend a swap.
568+ DisqualifiedChans map [lnwire.ShortChannelID ]Reason
569+ }
570+
571+ func newSuggestions () * Suggestions {
572+ return & Suggestions {
573+ DisqualifiedChans : make (map [lnwire.ShortChannelID ]Reason ),
574+ }
575+ }
576+
577+ // singleReasonSuggestion is a helper function which returns a set of
578+ // suggestions where all of our rules are disqualified due to a reason that
579+ // applies to all of them (such as being out of budget).
580+ func (m * Manager ) singleReasonSuggestion (reason Reason ) * Suggestions {
581+ resp := newSuggestions ()
582+
583+ for id := range m .params .ChannelRules {
584+ resp .DisqualifiedChans [id ] = reason
585+ }
586+
587+ return resp
588+ }
589+
560590// SuggestSwaps returns a set of swap suggestions based on our current liquidity
561591// balance for the set of rules configured for the manager, failing if there are
562592// no rules set. It takes an autoloop boolean that indicates whether the
563593// suggestions are being used for our internal autolooper. This boolean is used
564594// to determine the information we add to our swap suggestion and whether we
565595// return any suggestions.
566596func (m * Manager ) SuggestSwaps (ctx context.Context , autoloop bool ) (
567- []loop. OutRequest , error ) {
597+ * Suggestions , error ) {
568598
569599 m .paramsLock .Lock ()
570600 defer m .paramsLock .Unlock ()
@@ -582,7 +612,7 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoloop bool) (
582612 log .Debugf ("autoloop fee budget start time: %v is in " +
583613 "the future" , m .params .AutoFeeStartDate )
584614
585- return nil , nil
615+ return m . singleReasonSuggestion ( ReasonBudgetNotStarted ) , nil
586616 }
587617
588618 // Before we get any swap suggestions, we check what the current fee
@@ -603,7 +633,7 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoloop bool) (
603633 satPerKwToSatPerVByte (estimate ),
604634 satPerKwToSatPerVByte (m .params .SweepFeeRateLimit ))
605635
606- return nil , nil
636+ return m . singleReasonSuggestion ( ReasonSweepFees ) , nil
607637 }
608638
609639 // Get the current server side restrictions, combined with the client
@@ -640,7 +670,7 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoloop bool) (
640670 m .params .AutoFeeBudget , summary .spentFees ,
641671 summary .pendingFees )
642672
643- return nil , nil
673+ return m . singleReasonSuggestion ( ReasonBudgetElapsed ) , nil
644674 }
645675
646676 // If we have already reached our total allowed number of in flight
@@ -649,7 +679,8 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoloop bool) (
649679 if allowedSwaps <= 0 {
650680 log .Debugf ("%v autoloops allowed, %v in flight" ,
651681 m .params .MaxAutoInFlight , summary .inFlightCount )
652- return nil , nil
682+
683+ return m .singleReasonSuggestion (ReasonInFlight ), nil
653684 }
654685
655686 channels , err := m .cfg .Lnd .Client .ListChannels (ctx )
@@ -661,7 +692,10 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoloop bool) (
661692 // to ongoing swaps.
662693 traffic := m .currentSwapTraffic (loopOut , loopIn )
663694
664- var suggestions []loop.OutRequest
695+ var (
696+ suggestions []loop.OutRequest
697+ disqualified = make (map [lnwire.ShortChannelID ]Reason )
698+ )
665699
666700 for _ , channel := range channels {
667701 balance := newBalances (channel )
@@ -671,14 +705,19 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoloop bool) (
671705 continue
672706 }
673707
674- if ! traffic .maySwap (channel .PubKeyBytes , balance .channelID ) {
708+ // Check whether we can perform a swap, adding the channel to
709+ // our set of disqualified swaps if it is not eligible.
710+ reason := traffic .maySwap (channel .PubKeyBytes , balance .channelID )
711+ if reason != ReasonNone {
712+ disqualified [balance .channelID ] = reason
675713 continue
676714 }
677715
678716 // We can have nil suggestions in the case where no action is
679717 // required, so we skip over them.
680718 suggestion := rule .suggestSwap (balance , restrictions )
681719 if suggestion == nil {
720+ disqualified [balance .channelID ] = ReasonLiquidityOk
682721 continue
683722 }
684723
@@ -700,11 +739,9 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoloop bool) (
700739
701740 // Check that the estimated fees for the suggested swap are
702741 // below the fee limits configured by the manager.
703- err = m .checkFeeLimits (quote , suggestion .Amount )
704- if err != nil {
705- log .Infof ("suggestion: %v expected fees too high: %v" ,
706- suggestion , err )
707-
742+ feeReason := m .checkFeeLimits (quote , suggestion .Amount )
743+ if feeReason != ReasonNone {
744+ disqualified [balance .channelID ] = feeReason
708745 continue
709746 }
710747
@@ -717,10 +754,16 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoloop bool) (
717754 suggestions = append (suggestions , outRequest )
718755 }
719756
720- // If we have no suggestions after we have applied all of our limits,
721- // just return.
757+ // Finally, run through all possible swaps, excluding swaps that are
758+ // not feasible due to fee or budget restrictions.
759+ resp := & Suggestions {
760+ DisqualifiedChans : disqualified ,
761+ }
762+
763+ // If we have no swaps to execute after we have applied all of our
764+ // limits, just return our set of disqualified swaps.
722765 if len (suggestions ) == 0 {
723- return nil , nil
766+ return resp , nil
724767 }
725768
726769 // Sort suggestions by amount in descending order.
@@ -730,12 +773,38 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoloop bool) (
730773
731774 // Run through our suggested swaps in descending order of amount and
732775 // return all of the swaps which will fit within our remaining budget.
733- var (
734- available = m .params .AutoFeeBudget - summary .totalFees ()
735- inBudget []loop.OutRequest
736- )
776+ available := m .params .AutoFeeBudget - summary .totalFees ()
777+
778+ // setReason is a helper that adds a swap's channels to our disqualified
779+ // list with the reason provided.
780+ setReason := func (reason Reason , swap loop.OutRequest ) {
781+ for _ , id := range swap .OutgoingChanSet {
782+ chanID := lnwire .NewShortChanIDFromInt (id )
783+
784+ resp .DisqualifiedChans [chanID ] = reason
785+ }
786+ }
737787
738788 for _ , swap := range suggestions {
789+ swap := swap
790+
791+ // If we do not have enough funds available, or we hit our
792+ // in flight limit, we record this value for the rest of the
793+ // swaps.
794+ var reason Reason
795+ switch {
796+ case available == 0 :
797+ reason = ReasonBudgetInsufficient
798+
799+ case len (resp .OutSwaps ) == allowedSwaps :
800+ reason = ReasonInFlight
801+ }
802+
803+ if reason != ReasonNone {
804+ setReason (reason , swap )
805+ continue
806+ }
807+
739808 fees := worstCaseOutFees (
740809 swap .MaxPrepayRoutingFee , swap .MaxSwapRoutingFee ,
741810 swap .MaxSwapFee , swap .MaxMinerFee , swap .MaxPrepayAmount ,
@@ -746,17 +815,13 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoloop bool) (
746815 // fall within the budget and decrement our available amount.
747816 if fees <= available {
748817 available -= fees
749- inBudget = append (inBudget , swap )
750- }
751-
752- // If we're out of budget, or we have hit the max number of
753- // swaps that we want to dispatch at one time, exit early.
754- if available == 0 || allowedSwaps == len (inBudget ) {
755- break
818+ resp .OutSwaps = append (resp .OutSwaps , swap )
819+ } else {
820+ setReason (ReasonBudgetInsufficient , swap )
756821 }
757822 }
758823
759- return inBudget , nil
824+ return resp , nil
760825}
761826
762827// getSwapRestrictions queries the server for its latest swap size restrictions,
@@ -1030,56 +1095,62 @@ func newSwapTraffic() *swapTraffic {
10301095// maySwap returns a boolean that indicates whether we may perform a swap for a
10311096// peer and its set of channels.
10321097func (s * swapTraffic ) maySwap (peer route.Vertex ,
1033- chanID lnwire.ShortChannelID ) bool {
1098+ chanID lnwire.ShortChannelID ) Reason {
10341099
10351100 lastFail , recentFail := s .failedLoopOut [chanID ]
10361101 if recentFail {
10371102 log .Debugf ("Channel: %v not eligible for suggestions, was " +
10381103 "part of a failed swap at: %v" , chanID , lastFail )
10391104
1040- return false
1105+ return ReasonFailureBackoff
10411106 }
10421107
10431108 if s .ongoingLoopOut [chanID ] {
10441109 log .Debugf ("Channel: %v not eligible for suggestions, " +
10451110 "ongoing loop out utilizing channel" , chanID )
10461111
1047- return false
1112+ return ReasonLoopOut
10481113 }
10491114
10501115 if s .ongoingLoopIn [peer ] {
10511116 log .Debugf ("Peer: %x not eligible for suggestions ongoing " +
10521117 "loop in utilizing peer" , peer )
10531118
1054- return false
1119+ return ReasonLoopIn
10551120 }
10561121
1057- return true
1122+ return ReasonNone
10581123}
10591124
10601125// checkFeeLimits takes a set of fees for a swap and checks whether they exceed
10611126// our swap limits.
10621127func (m * Manager ) checkFeeLimits (quote * loop.LoopOutQuote ,
1063- swapAmt btcutil.Amount ) error {
1128+ swapAmt btcutil.Amount ) Reason {
10641129
10651130 maxFee := ppmToSat (swapAmt , m .params .MaximumSwapFeePPM )
10661131
10671132 if quote .SwapFee > maxFee {
1068- return fmt . Errorf ("quoted swap fee: %v > maximum swap fee: %v" ,
1133+ log . Debugf ("quoted swap fee: %v > maximum swap fee: %v" ,
10691134 quote .SwapFee , maxFee )
1135+
1136+ return ReasonSwapFee
10701137 }
10711138
10721139 if quote .MinerFee > m .params .MaximumMinerFee {
1073- return fmt . Errorf ("quoted miner fee: %v > maximum miner " +
1140+ log . Debugf ("quoted miner fee: %v > maximum miner " +
10741141 "fee: %v" , quote .MinerFee , m .params .MaximumMinerFee )
1142+
1143+ return ReasonMinerFee
10751144 }
10761145
10771146 if quote .PrepayAmount > m .params .MaximumPrepay {
1078- return fmt . Errorf ("quoted prepay: %v > maximum prepay: %v" ,
1147+ log . Debugf ("quoted prepay: %v > maximum prepay: %v" ,
10791148 quote .PrepayAmount , m .params .MaximumPrepay )
1149+
1150+ return ReasonPrepay
10801151 }
10811152
1082- return nil
1153+ return ReasonNone
10831154}
10841155
10851156// satPerKwToSatPerVByte converts sat per kWeight to sat per vByte.
0 commit comments