From c6e816ad95e1bdef207a53cfaa59528b9a9fe86d Mon Sep 17 00:00:00 2001 From: carla Date: Tue, 16 Feb 2021 13:31:49 +0200 Subject: [PATCH] liquidity: move swap creation into separate function --- liquidity/balances.go | 5 ++ liquidity/liquidity.go | 117 +++++++++++++++++++++++++---------------- liquidity/reasons.go | 66 +++++++++++++++++++++++ 3 files changed, 144 insertions(+), 44 deletions(-) diff --git a/liquidity/balances.go b/liquidity/balances.go index 051cff5..d729e02 100644 --- a/liquidity/balances.go +++ b/liquidity/balances.go @@ -4,6 +4,7 @@ import ( "github.com/btcsuite/btcutil" "github.com/lightninglabs/lndclient" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" ) // balances summarizes the state of the balances of a channel. Channel reserve, @@ -20,6 +21,9 @@ type balances struct { // channelID is the channel that has these balances. channelID lnwire.ShortChannelID + + // pubkey is the public key of the peer we have this balances set with. + pubkey route.Vertex } // newBalances creates a balances struct from lndclient channel information. @@ -29,5 +33,6 @@ func newBalances(info lndclient.ChannelInfo) *balances { incoming: info.RemoteBalance, outgoing: info.LocalBalance, channelID: lnwire.NewShortChanIDFromInt(info.ChannelID), + pubkey: info.PubKeyBytes, } } diff --git a/liquidity/liquidity.go b/liquidity/liquidity.go index 30153a0..bf65014 100644 --- a/liquidity/liquidity.go +++ b/liquidity/liquidity.go @@ -705,53 +705,21 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoloop bool) ( continue } - // Check whether we can perform a swap, adding the channel to - // our set of disqualified swaps if it is not eligible. - reason := traffic.maySwap(channel.PubKeyBytes, balance.channelID) - if reason != ReasonNone { - disqualified[balance.channelID] = reason - continue - } - - // We can have zero amount in the case where no action is - // required, so we skip over them. - amount := rule.swapAmount(balance, restrictions) - if amount == 0 { - disqualified[balance.channelID] = ReasonLiquidityOk - continue - } - - // Get a quote for a swap of this amount. - quote, err := m.cfg.LoopOutQuote( - ctx, &loop.LoopOutQuoteRequest{ - Amount: amount, - SweepConfTarget: m.params.SweepConfTarget, - SwapPublicationDeadline: m.cfg.Clock.Now(), - }, + suggestion, err := m.suggestSwap( + ctx, traffic, balance, rule, restrictions, autoloop, ) - if err != nil { - return nil, err - } - log.Debugf("quote for suggestion: %v, swap fee: %v, "+ - "miner fee: %v, prepay: %v", amount, quote.SwapFee, - quote.MinerFee, quote.PrepayAmount) - - // Check that the estimated fees for the suggested swap are - // below the fee limits configured by the manager. - feeReason := m.checkFeeLimits(quote, amount) - if feeReason != ReasonNone { - disqualified[balance.channelID] = feeReason + var reasonErr *reasonError + if errors.As(err, &reasonErr) { + disqualified[balance.channelID] = reasonErr.reason continue } - outRequest, err := m.makeLoopOutRequest( - ctx, amount, balance, quote, autoloop, - ) if err != nil { return nil, err } - suggestions = append(suggestions, outRequest) + + suggestions = append(suggestions, *suggestion) } // Finally, run through all possible swaps, excluding swaps that are @@ -824,6 +792,67 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoloop bool) ( return resp, nil } +// suggestSwap checks whether we can currently perform a swap, and creates a +// swap request for the rule provided. +func (m *Manager) suggestSwap(ctx context.Context, traffic *swapTraffic, + balance *balances, rule *ThresholdRule, restrictions *Restrictions, + autoloop bool) (*loop.OutRequest, error) { + + // Check whether we can perform a swap. + err := traffic.maySwap(balance.pubkey, balance.channelID) + if err != nil { + return nil, err + } + + // We can have nil suggestions in the case where no action is + // required, so we skip over them. + amount := rule.swapAmount(balance, restrictions) + if amount == 0 { + return nil, newReasonError(ReasonLiquidityOk) + } + + return m.loopOutSwap(ctx, amount, balance, autoloop) +} + +// loopOutSwap creates a loop out swap with the amount provided for the balance +// described by the balance set provided. A reason that indicates whether we +// can swap is returned. If this value is not ReasonNone, there is no possible +// swap and the loop out request returned will be nil. +func (m *Manager) loopOutSwap(ctx context.Context, amount btcutil.Amount, + balance *balances, autoloop bool) (*loop.OutRequest, error) { + + quote, err := m.cfg.LoopOutQuote( + ctx, &loop.LoopOutQuoteRequest{ + Amount: amount, + SweepConfTarget: m.params.SweepConfTarget, + SwapPublicationDeadline: m.cfg.Clock.Now(), + }, + ) + if err != nil { + return nil, err + } + + log.Debugf("quote for suggestion: %v, swap fee: %v, "+ + "miner fee: %v, prepay: %v", amount, quote.SwapFee, + quote.MinerFee, quote.PrepayAmount) + + // Check that the estimated fees for the suggested swap are + // below the fee limits configured by the manager. + feeReason := m.checkFeeLimits(quote, amount) + if feeReason != ReasonNone { + return nil, newReasonError(feeReason) + } + + outRequest, err := m.makeLoopOutRequest( + ctx, amount, balance, quote, autoloop, + ) + if err != nil { + return nil, err + } + + return &outRequest, nil +} + // getSwapRestrictions queries the server for its latest swap size restrictions, // validates client restrictions (if present) against these values and merges // the client's custom requirements with the server's limits to produce a single @@ -1093,31 +1122,31 @@ func newSwapTraffic() *swapTraffic { // maySwap returns a boolean that indicates whether we may perform a swap for a // peer and its set of channels. func (s *swapTraffic) maySwap(peer route.Vertex, - chanID lnwire.ShortChannelID) Reason { + chanID lnwire.ShortChannelID) error { lastFail, recentFail := s.failedLoopOut[chanID] if recentFail { log.Debugf("Channel: %v not eligible for suggestions, was "+ "part of a failed swap at: %v", chanID, lastFail) - return ReasonFailureBackoff + return newReasonError(ReasonFailureBackoff) } if s.ongoingLoopOut[chanID] { log.Debugf("Channel: %v not eligible for suggestions, "+ "ongoing loop out utilizing channel", chanID) - return ReasonLoopOut + return newReasonError(ReasonLoopOut) } if s.ongoingLoopIn[peer] { log.Debugf("Peer: %x not eligible for suggestions ongoing "+ "loop in utilizing peer", peer) - return ReasonLoopIn + return newReasonError(ReasonLoopIn) } - return ReasonNone + return nil } // checkFeeLimits takes a set of fees for a swap and checks whether they exceed diff --git a/liquidity/reasons.go b/liquidity/reasons.go index d685f61..c97616f 100644 --- a/liquidity/reasons.go +++ b/liquidity/reasons.go @@ -1,5 +1,7 @@ package liquidity +import "fmt" + // Reason is an enum which represents the various reasons we have for not // executing a swap. type Reason uint8 @@ -60,3 +62,67 @@ const ( // but we have allocated it to other swaps. ReasonBudgetInsufficient ) + +// String returns a string representation of a reason. +func (r Reason) String() string { + switch r { + case ReasonNone: + return "none" + + case ReasonBudgetNotStarted: + return "budget not started" + + case ReasonSweepFees: + return "sweep fees to high" + + case ReasonBudgetElapsed: + return "budget elapsed" + + case ReasonInFlight: + return "autoloops already in flight" + + case ReasonSwapFee: + return "swap server fee to high" + + case ReasonMinerFee: + return "miner fee to high" + + case ReasonPrepay: + return "prepayment too high" + + case ReasonFailureBackoff: + return "backing off due to failure" + + case ReasonLoopOut: + return "loop out using channel" + + case ReasonLoopIn: + return "loop in using peer" + + case ReasonLiquidityOk: + return "liquidity balance ok" + + case ReasonBudgetInsufficient: + return "budget insufficient" + + default: + return "unknown" + } +} + +// reasonError is an error type which embeds our reasons for not performing +// swaps. +type reasonError struct { + reason Reason +} + +func newReasonError(r Reason) *reasonError { + return &reasonError{ + reason: r, + } +} + +// Error returns an error string for a reason error. +func (r *reasonError) Error() string { + return fmt.Sprintf("swap reason: %v", r.reason) +}