liquidity: move swap creation into separate function

pull/333/head
carla 3 years ago
parent 9c35946cab
commit c6e816ad95
No known key found for this signature in database
GPG Key ID: 4CA7FE54A6213C91

@ -4,6 +4,7 @@ import (
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/lightninglabs/lndclient" "github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
) )
// balances summarizes the state of the balances of a channel. Channel reserve, // 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 is the channel that has these balances.
channelID lnwire.ShortChannelID 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. // newBalances creates a balances struct from lndclient channel information.
@ -29,5 +33,6 @@ func newBalances(info lndclient.ChannelInfo) *balances {
incoming: info.RemoteBalance, incoming: info.RemoteBalance,
outgoing: info.LocalBalance, outgoing: info.LocalBalance,
channelID: lnwire.NewShortChanIDFromInt(info.ChannelID), channelID: lnwire.NewShortChanIDFromInt(info.ChannelID),
pubkey: info.PubKeyBytes,
} }
} }

@ -705,53 +705,21 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoloop bool) (
continue continue
} }
// Check whether we can perform a swap, adding the channel to suggestion, err := m.suggestSwap(
// our set of disqualified swaps if it is not eligible. ctx, traffic, balance, rule, restrictions, autoloop,
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(),
},
) )
if err != nil {
return nil, err
}
log.Debugf("quote for suggestion: %v, swap fee: %v, "+ var reasonErr *reasonError
"miner fee: %v, prepay: %v", amount, quote.SwapFee, if errors.As(err, &reasonErr) {
quote.MinerFee, quote.PrepayAmount) disqualified[balance.channelID] = reasonErr.reason
// 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
continue continue
} }
outRequest, err := m.makeLoopOutRequest(
ctx, amount, balance, quote, autoloop,
)
if err != nil { if err != nil {
return nil, err return nil, err
} }
suggestions = append(suggestions, outRequest)
suggestions = append(suggestions, *suggestion)
} }
// Finally, run through all possible swaps, excluding swaps that are // 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 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, // getSwapRestrictions queries the server for its latest swap size restrictions,
// validates client restrictions (if present) against these values and merges // validates client restrictions (if present) against these values and merges
// the client's custom requirements with the server's limits to produce a single // 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 // maySwap returns a boolean that indicates whether we may perform a swap for a
// peer and its set of channels. // peer and its set of channels.
func (s *swapTraffic) maySwap(peer route.Vertex, func (s *swapTraffic) maySwap(peer route.Vertex,
chanID lnwire.ShortChannelID) Reason { chanID lnwire.ShortChannelID) error {
lastFail, recentFail := s.failedLoopOut[chanID] lastFail, recentFail := s.failedLoopOut[chanID]
if recentFail { if recentFail {
log.Debugf("Channel: %v not eligible for suggestions, was "+ log.Debugf("Channel: %v not eligible for suggestions, was "+
"part of a failed swap at: %v", chanID, lastFail) "part of a failed swap at: %v", chanID, lastFail)
return ReasonFailureBackoff return newReasonError(ReasonFailureBackoff)
} }
if s.ongoingLoopOut[chanID] { if s.ongoingLoopOut[chanID] {
log.Debugf("Channel: %v not eligible for suggestions, "+ log.Debugf("Channel: %v not eligible for suggestions, "+
"ongoing loop out utilizing channel", chanID) "ongoing loop out utilizing channel", chanID)
return ReasonLoopOut return newReasonError(ReasonLoopOut)
} }
if s.ongoingLoopIn[peer] { if s.ongoingLoopIn[peer] {
log.Debugf("Peer: %x not eligible for suggestions ongoing "+ log.Debugf("Peer: %x not eligible for suggestions ongoing "+
"loop in utilizing peer", peer) "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 // checkFeeLimits takes a set of fees for a swap and checks whether they exceed

@ -1,5 +1,7 @@
package liquidity package liquidity
import "fmt"
// Reason is an enum which represents the various reasons we have for not // Reason is an enum which represents the various reasons we have for not
// executing a swap. // executing a swap.
type Reason uint8 type Reason uint8
@ -60,3 +62,67 @@ const (
// but we have allocated it to other swaps. // but we have allocated it to other swaps.
ReasonBudgetInsufficient 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)
}

Loading…
Cancel
Save