liquidity: add reasons for autoloops not executing

pull/332/head
carla 3 years ago
parent b9b75c3c32
commit 7c4d71b175
No known key found for this signature in database
GPG Key ID: 4CA7FE54A6213C91

@ -516,12 +516,12 @@ func cloneParameters(params Parameters) Parameters {
// autoloop gets a set of suggested swaps and dispatches them automatically if
// we have automated looping enabled.
func (m *Manager) autoloop(ctx context.Context) error {
swaps, err := m.SuggestSwaps(ctx, true)
suggestion, err := m.SuggestSwaps(ctx, true)
if err != nil {
return err
}
for _, swap := range swaps {
for _, swap := range suggestion.OutSwaps {
// If we don't actually have dispatch of swaps enabled, log
// suggestions.
if !m.params.Autoloop {
@ -557,6 +557,36 @@ func (m *Manager) ForceAutoLoop(ctx context.Context) error {
}
}
// Suggestions provides a set of suggested swaps, and the set of channels that
// were excluded from consideration.
type Suggestions struct {
// OutSwaps is the set of loop out swaps that we suggest executing.
OutSwaps []loop.OutRequest
// DisqualifiedChans maps the set of channels that we do not recommend
// swaps on to the reason that we did not recommend a swap.
DisqualifiedChans map[lnwire.ShortChannelID]Reason
}
func newSuggestions() *Suggestions {
return &Suggestions{
DisqualifiedChans: make(map[lnwire.ShortChannelID]Reason),
}
}
// singleReasonSuggestion is a helper function which returns a set of
// suggestions where all of our rules are disqualified due to a reason that
// applies to all of them (such as being out of budget).
func (m *Manager) singleReasonSuggestion(reason Reason) *Suggestions {
resp := newSuggestions()
for id := range m.params.ChannelRules {
resp.DisqualifiedChans[id] = reason
}
return resp
}
// SuggestSwaps returns a set of swap suggestions based on our current liquidity
// balance for the set of rules configured for the manager, failing if there are
// no rules set. It takes an autoloop boolean that indicates whether the
@ -564,7 +594,7 @@ func (m *Manager) ForceAutoLoop(ctx context.Context) error {
// to determine the information we add to our swap suggestion and whether we
// return any suggestions.
func (m *Manager) SuggestSwaps(ctx context.Context, autoloop bool) (
[]loop.OutRequest, error) {
*Suggestions, error) {
m.paramsLock.Lock()
defer m.paramsLock.Unlock()
@ -582,7 +612,7 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoloop bool) (
log.Debugf("autoloop fee budget start time: %v is in "+
"the future", m.params.AutoFeeStartDate)
return nil, nil
return m.singleReasonSuggestion(ReasonBudgetNotStarted), nil
}
// 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) (
satPerKwToSatPerVByte(estimate),
satPerKwToSatPerVByte(m.params.SweepFeeRateLimit))
return nil, nil
return m.singleReasonSuggestion(ReasonSweepFees), nil
}
// Get the current server side restrictions, combined with the client
@ -640,7 +670,7 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoloop bool) (
m.params.AutoFeeBudget, summary.spentFees,
summary.pendingFees)
return nil, nil
return m.singleReasonSuggestion(ReasonBudgetElapsed), nil
}
// 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) (
if allowedSwaps <= 0 {
log.Debugf("%v autoloops allowed, %v in flight",
m.params.MaxAutoInFlight, summary.inFlightCount)
return nil, nil
return m.singleReasonSuggestion(ReasonInFlight), nil
}
channels, err := m.cfg.Lnd.Client.ListChannels(ctx)
@ -661,7 +692,10 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoloop bool) (
// to ongoing swaps.
traffic := m.currentSwapTraffic(loopOut, loopIn)
var suggestions []loop.OutRequest
var (
suggestions []loop.OutRequest
disqualified = make(map[lnwire.ShortChannelID]Reason)
)
for _, channel := range channels {
balance := newBalances(channel)
@ -671,7 +705,11 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoloop bool) (
continue
}
if !traffic.maySwap(channel.PubKeyBytes, balance.channelID) {
// 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
}
@ -679,6 +717,7 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoloop bool) (
// required, so we skip over them.
suggestion := rule.suggestSwap(balance, restrictions)
if suggestion == nil {
disqualified[balance.channelID] = ReasonLiquidityOk
continue
}
@ -700,11 +739,9 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoloop bool) (
// Check that the estimated fees for the suggested swap are
// below the fee limits configured by the manager.
err = m.checkFeeLimits(quote, suggestion.Amount)
if err != nil {
log.Infof("suggestion: %v expected fees too high: %v",
suggestion, err)
feeReason := m.checkFeeLimits(quote, suggestion.Amount)
if feeReason != ReasonNone {
disqualified[balance.channelID] = feeReason
continue
}
@ -717,10 +754,16 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoloop bool) (
suggestions = append(suggestions, outRequest)
}
// If we have no suggestions after we have applied all of our limits,
// just return.
// Finally, run through all possible swaps, excluding swaps that are
// not feasible due to fee or budget restrictions.
resp := &Suggestions{
DisqualifiedChans: disqualified,
}
// If we have no swaps to execute after we have applied all of our
// limits, just return our set of disqualified swaps.
if len(suggestions) == 0 {
return nil, nil
return resp, nil
}
// Sort suggestions by amount in descending order.
@ -730,12 +773,38 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoloop bool) (
// Run through our suggested swaps in descending order of amount and
// return all of the swaps which will fit within our remaining budget.
var (
available = m.params.AutoFeeBudget - summary.totalFees()
inBudget []loop.OutRequest
)
available := m.params.AutoFeeBudget - summary.totalFees()
// setReason is a helper that adds a swap's channels to our disqualified
// list with the reason provided.
setReason := func(reason Reason, swap loop.OutRequest) {
for _, id := range swap.OutgoingChanSet {
chanID := lnwire.NewShortChanIDFromInt(id)
resp.DisqualifiedChans[chanID] = reason
}
}
for _, swap := range suggestions {
swap := swap
// If we do not have enough funds available, or we hit our
// in flight limit, we record this value for the rest of the
// swaps.
var reason Reason
switch {
case available == 0:
reason = ReasonBudgetInsufficient
case len(resp.OutSwaps) == allowedSwaps:
reason = ReasonInFlight
}
if reason != ReasonNone {
setReason(reason, swap)
continue
}
fees := worstCaseOutFees(
swap.MaxPrepayRoutingFee, swap.MaxSwapRoutingFee,
swap.MaxSwapFee, swap.MaxMinerFee, swap.MaxPrepayAmount,
@ -746,17 +815,13 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoloop bool) (
// fall within the budget and decrement our available amount.
if fees <= available {
available -= fees
inBudget = append(inBudget, swap)
}
// If we're out of budget, or we have hit the max number of
// swaps that we want to dispatch at one time, exit early.
if available == 0 || allowedSwaps == len(inBudget) {
break
resp.OutSwaps = append(resp.OutSwaps, swap)
} else {
setReason(ReasonBudgetInsufficient, swap)
}
}
return inBudget, nil
return resp, nil
}
// getSwapRestrictions queries the server for its latest swap size restrictions,
@ -1030,56 +1095,62 @@ 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) bool {
chanID lnwire.ShortChannelID) Reason {
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 false
return ReasonFailureBackoff
}
if s.ongoingLoopOut[chanID] {
log.Debugf("Channel: %v not eligible for suggestions, "+
"ongoing loop out utilizing channel", chanID)
return false
return ReasonLoopOut
}
if s.ongoingLoopIn[peer] {
log.Debugf("Peer: %x not eligible for suggestions ongoing "+
"loop in utilizing peer", peer)
return false
return ReasonLoopIn
}
return true
return ReasonNone
}
// checkFeeLimits takes a set of fees for a swap and checks whether they exceed
// our swap limits.
func (m *Manager) checkFeeLimits(quote *loop.LoopOutQuote,
swapAmt btcutil.Amount) error {
swapAmt btcutil.Amount) Reason {
maxFee := ppmToSat(swapAmt, m.params.MaximumSwapFeePPM)
if quote.SwapFee > maxFee {
return fmt.Errorf("quoted swap fee: %v > maximum swap fee: %v",
log.Debugf("quoted swap fee: %v > maximum swap fee: %v",
quote.SwapFee, maxFee)
return ReasonSwapFee
}
if quote.MinerFee > m.params.MaximumMinerFee {
return fmt.Errorf("quoted miner fee: %v > maximum miner "+
log.Debugf("quoted miner fee: %v > maximum miner "+
"fee: %v", quote.MinerFee, m.params.MaximumMinerFee)
return ReasonMinerFee
}
if quote.PrepayAmount > m.params.MaximumPrepay {
return fmt.Errorf("quoted prepay: %v > maximum prepay: %v",
log.Debugf("quoted prepay: %v > maximum prepay: %v",
quote.PrepayAmount, m.params.MaximumPrepay)
return ReasonPrepay
}
return nil
return ReasonNone
}
// satPerKwToSatPerVByte converts sat per kWeight to sat per vByte.

@ -107,6 +107,10 @@ var (
}
testRestrictions = NewRestrictions(1, 10000)
// noneDisqualified can be used in tests where we don't have any
// disqualified channels so that we can use require.Equal.
noneDisqualified = make(map[lnwire.ShortChannelID]Reason)
)
// newTestConfig creates a default test config.
@ -283,7 +287,7 @@ func TestRestrictedSuggestions(t *testing.T) {
channels []lndclient.ChannelInfo
loopOut []*loopdb.LoopOut
loopIn []*loopdb.LoopIn
expected []loop.OutRequest
expected *Suggestions
}{
{
name: "no existing swaps",
@ -292,8 +296,11 @@ func TestRestrictedSuggestions(t *testing.T) {
},
loopOut: nil,
loopIn: nil,
expected: []loop.OutRequest{
chan1Rec,
expected: &Suggestions{
OutSwaps: []loop.OutRequest{
chan1Rec,
},
DisqualifiedChans: noneDisqualified,
},
},
{
@ -308,8 +315,11 @@ func TestRestrictedSuggestions(t *testing.T) {
},
},
},
expected: []loop.OutRequest{
chan1Rec,
expected: &Suggestions{
OutSwaps: []loop.OutRequest{
chan1Rec,
},
DisqualifiedChans: noneDisqualified,
},
},
{
@ -324,8 +334,11 @@ func TestRestrictedSuggestions(t *testing.T) {
},
},
},
expected: []loop.OutRequest{
chan1Rec,
expected: &Suggestions{
OutSwaps: []loop.OutRequest{
chan1Rec,
},
DisqualifiedChans: noneDisqualified,
},
},
{
@ -338,8 +351,13 @@ func TestRestrictedSuggestions(t *testing.T) {
Contract: chan1Out,
},
},
expected: []loop.OutRequest{
chan2Rec,
expected: &Suggestions{
OutSwaps: []loop.OutRequest{
chan2Rec,
},
DisqualifiedChans: map[lnwire.ShortChannelID]Reason{
chanID1: ReasonLoopOut,
},
},
},
{
@ -354,8 +372,13 @@ func TestRestrictedSuggestions(t *testing.T) {
},
},
},
expected: []loop.OutRequest{
chan1Rec,
expected: &Suggestions{
OutSwaps: []loop.OutRequest{
chan1Rec,
},
DisqualifiedChans: map[lnwire.ShortChannelID]Reason{
chanID2: ReasonLoopIn,
},
},
},
{
@ -373,7 +396,11 @@ func TestRestrictedSuggestions(t *testing.T) {
},
},
},
expected: nil,
expected: &Suggestions{
DisqualifiedChans: map[lnwire.ShortChannelID]Reason{
chanID1: ReasonFailureBackoff,
},
},
},
{
name: "swap failed before cutoff",
@ -390,8 +417,11 @@ func TestRestrictedSuggestions(t *testing.T) {
},
},
},
expected: []loop.OutRequest{
chan1Rec,
expected: &Suggestions{
OutSwaps: []loop.OutRequest{
chan1Rec,
},
DisqualifiedChans: noneDisqualified,
},
},
{
@ -409,7 +439,11 @@ func TestRestrictedSuggestions(t *testing.T) {
},
},
},
expected: nil,
expected: &Suggestions{
DisqualifiedChans: map[lnwire.ShortChannelID]Reason{
chanID1: ReasonLoopOut,
},
},
},
}
@ -447,21 +481,28 @@ func TestRestrictedSuggestions(t *testing.T) {
// fee is above and below the configured limit.
func TestSweepFeeLimit(t *testing.T) {
tests := []struct {
name string
feeRate chainfee.SatPerKWeight
swaps []loop.OutRequest
name string
feeRate chainfee.SatPerKWeight
suggestions *Suggestions
}{
{
name: "fee estimate ok",
feeRate: defaultSweepFeeRateLimit,
swaps: []loop.OutRequest{
chan1Rec,
suggestions: &Suggestions{
OutSwaps: []loop.OutRequest{
chan1Rec,
},
DisqualifiedChans: noneDisqualified,
},
},
{
name: "fee estimate above limit",
feeRate: defaultSweepFeeRateLimit + 1,
swaps: nil,
suggestions: &Suggestions{
DisqualifiedChans: map[lnwire.ShortChannelID]Reason{
chanID1: ReasonSweepFees,
},
},
},
}
@ -487,7 +528,7 @@ func TestSweepFeeLimit(t *testing.T) {
testSuggestSwaps(
t, newSuggestSwapsSetup(cfg, lnd, params),
testCase.swaps, nil,
testCase.suggestions, nil,
)
})
}
@ -497,10 +538,10 @@ func TestSweepFeeLimit(t *testing.T) {
// the liquidity manager and the current set of channel balances.
func TestSuggestSwaps(t *testing.T) {
tests := []struct {
name string
rules map[lnwire.ShortChannelID]*ThresholdRule
swaps []loop.OutRequest
err error
name string
rules map[lnwire.ShortChannelID]*ThresholdRule
suggestions *Suggestions
err error
}{
{
name: "no rules",
@ -512,8 +553,11 @@ func TestSuggestSwaps(t *testing.T) {
rules: map[lnwire.ShortChannelID]*ThresholdRule{
chanID1: chanRule,
},
swaps: []loop.OutRequest{
chan1Rec,
suggestions: &Suggestions{
OutSwaps: []loop.OutRequest{
chan1Rec,
},
DisqualifiedChans: noneDisqualified,
},
},
{
@ -521,7 +565,9 @@ func TestSuggestSwaps(t *testing.T) {
rules: map[lnwire.ShortChannelID]*ThresholdRule{
chanID2: NewThresholdRule(10, 10),
},
swaps: nil,
suggestions: &Suggestions{
DisqualifiedChans: noneDisqualified,
},
},
}
@ -540,7 +586,7 @@ func TestSuggestSwaps(t *testing.T) {
testSuggestSwaps(
t, newSuggestSwapsSetup(cfg, lnd, params),
testCase.swaps, testCase.err,
testCase.suggestions, testCase.err,
)
})
}
@ -549,15 +595,18 @@ func TestSuggestSwaps(t *testing.T) {
// TestFeeLimits tests limiting of swap suggestions by fees.
func TestFeeLimits(t *testing.T) {
tests := []struct {
name string
quote *loop.LoopOutQuote
expected []loop.OutRequest
name string
quote *loop.LoopOutQuote
suggestions *Suggestions
}{
{
name: "fees ok",
quote: testQuote,
expected: []loop.OutRequest{
chan1Rec,
suggestions: &Suggestions{
OutSwaps: []loop.OutRequest{
chan1Rec,
},
DisqualifiedChans: noneDisqualified,
},
},
{
@ -567,6 +616,11 @@ func TestFeeLimits(t *testing.T) {
PrepayAmount: defaultMaximumPrepay + 1,
MinerFee: 50,
},
suggestions: &Suggestions{
DisqualifiedChans: map[lnwire.ShortChannelID]Reason{
chanID1: ReasonPrepay,
},
},
},
{
name: "insufficient miner fee",
@ -575,6 +629,11 @@ func TestFeeLimits(t *testing.T) {
PrepayAmount: 100,
MinerFee: defaultMaximumMinerFee + 1,
},
suggestions: &Suggestions{
DisqualifiedChans: map[lnwire.ShortChannelID]Reason{
chanID1: ReasonMinerFee,
},
},
},
{
// Swap fee limited to 0.5% of 7500 = 37,5.
@ -584,6 +643,11 @@ func TestFeeLimits(t *testing.T) {
PrepayAmount: 100,
MinerFee: 500,
},
suggestions: &Suggestions{
DisqualifiedChans: map[lnwire.ShortChannelID]Reason{
chanID1: ReasonSwapFee,
},
},
},
}
@ -610,7 +674,7 @@ func TestFeeLimits(t *testing.T) {
testSuggestSwaps(
t, newSuggestSwapsSetup(cfg, lnd, params),
testCase.expected, nil,
testCase.suggestions, nil,
)
})
}
@ -641,8 +705,8 @@ func TestFeeBudget(t *testing.T) {
// last update time to their total cost.
existingSwaps map[time.Time]btcutil.Amount
// expectedSwaps is the set of swaps we expect to be suggested.
expectedSwaps []loop.OutRequest
// suggestions is the set of swaps we expect to be suggested.
suggestions *Suggestions
}{
{
// Two swaps will cost (78+5000)*2, set exactly 10156
@ -650,8 +714,11 @@ func TestFeeBudget(t *testing.T) {
name: "budget for 2 swaps, no existing",
budget: 10156,
maxMinerFee: 5000,
expectedSwaps: []loop.OutRequest{
chan1Rec, chan2Rec,
suggestions: &Suggestions{
OutSwaps: []loop.OutRequest{
chan1Rec, chan2Rec,
},
DisqualifiedChans: noneDisqualified,
},
},
{
@ -660,8 +727,13 @@ func TestFeeBudget(t *testing.T) {
name: "budget for 1 swaps, no existing",
budget: 10155,
maxMinerFee: 5000,
expectedSwaps: []loop.OutRequest{
chan1Rec,
suggestions: &Suggestions{
OutSwaps: []loop.OutRequest{
chan1Rec,
},
DisqualifiedChans: map[lnwire.ShortChannelID]Reason{
chanID2: ReasonBudgetInsufficient,
},
},
},
{
@ -673,8 +745,11 @@ func TestFeeBudget(t *testing.T) {
existingSwaps: map[time.Time]btcutil.Amount{
testBudgetStart.Add(time.Hour * -1): 200,
},
expectedSwaps: []loop.OutRequest{
chan1Rec, chan2Rec,
suggestions: &Suggestions{
OutSwaps: []loop.OutRequest{
chan1Rec, chan2Rec,
},
DisqualifiedChans: noneDisqualified,
},
},
{
@ -686,8 +761,13 @@ func TestFeeBudget(t *testing.T) {
existingSwaps: map[time.Time]btcutil.Amount{
testBudgetStart.Add(time.Hour): 500,
},
expectedSwaps: []loop.OutRequest{
chan1Rec,
suggestions: &Suggestions{
OutSwaps: []loop.OutRequest{
chan1Rec,
},
DisqualifiedChans: map[lnwire.ShortChannelID]Reason{
chanID2: ReasonBudgetInsufficient,
},
},
},
{
@ -697,7 +777,12 @@ func TestFeeBudget(t *testing.T) {
existingSwaps: map[time.Time]btcutil.Amount{
testBudgetStart.Add(time.Hour): 500,
},
expectedSwaps: nil,
suggestions: &Suggestions{
DisqualifiedChans: map[lnwire.ShortChannelID]Reason{
chanID1: ReasonBudgetElapsed,
chanID2: ReasonBudgetElapsed,
},
},
},
}
@ -760,14 +845,14 @@ func TestFeeBudget(t *testing.T) {
// Set our custom max miner fee on each expected swap,
// rather than having to create multiple vars for
// different rates.
for i := range testCase.expectedSwaps {
testCase.expectedSwaps[i].MaxMinerFee =
for i := range testCase.suggestions.OutSwaps {
testCase.suggestions.OutSwaps[i].MaxMinerFee =
testCase.maxMinerFee
}
testSuggestSwaps(
t, newSuggestSwapsSetup(cfg, lnd, params),
testCase.expectedSwaps, nil,
testCase.suggestions, nil,
)
})
}
@ -780,20 +865,26 @@ func TestInFlightLimit(t *testing.T) {
name string
maxInFlight int
existingSwaps []*loopdb.LoopOut
expectedSwaps []loop.OutRequest
suggestions *Suggestions
}{
{
name: "none in flight, extra space",
maxInFlight: 3,
expectedSwaps: []loop.OutRequest{
chan1Rec, chan2Rec,
suggestions: &Suggestions{
OutSwaps: []loop.OutRequest{
chan1Rec, chan2Rec,
},
DisqualifiedChans: noneDisqualified,
},
},
{
name: "none in flight, exact match",
maxInFlight: 2,
expectedSwaps: []loop.OutRequest{
chan1Rec, chan2Rec,
suggestions: &Suggestions{
OutSwaps: []loop.OutRequest{
chan1Rec, chan2Rec,
},
DisqualifiedChans: noneDisqualified,
},
},
{
@ -804,8 +895,13 @@ func TestInFlightLimit(t *testing.T) {
Contract: autoOutContract,
},
},
expectedSwaps: []loop.OutRequest{
chan1Rec,
suggestions: &Suggestions{
OutSwaps: []loop.OutRequest{
chan1Rec,
},
DisqualifiedChans: map[lnwire.ShortChannelID]Reason{
chanID2: ReasonInFlight,
},
},
},
{
@ -816,7 +912,12 @@ func TestInFlightLimit(t *testing.T) {
Contract: autoOutContract,
},
},
expectedSwaps: nil,
suggestions: &Suggestions{
DisqualifiedChans: map[lnwire.ShortChannelID]Reason{
chanID1: ReasonInFlight,
chanID2: ReasonInFlight,
},
},
},
{
name: "max swaps exceeded",
@ -829,7 +930,12 @@ func TestInFlightLimit(t *testing.T) {
Contract: autoOutContract,
},
},
expectedSwaps: nil,
suggestions: &Suggestions{
DisqualifiedChans: map[lnwire.ShortChannelID]Reason{
chanID1: ReasonInFlight,
chanID2: ReasonInFlight,
},
},
},
}
@ -860,7 +966,7 @@ func TestInFlightLimit(t *testing.T) {
testSuggestSwaps(
t, newSuggestSwapsSetup(cfg, lnd, params),
testCase.expectedSwaps, nil,
testCase.suggestions, nil,
)
})
}
@ -875,13 +981,18 @@ func TestSizeRestrictions(t *testing.T) {
}
outSwap = loop.OutRequest{
Amount: 7000,
OutgoingChanSet: loopdb.ChannelSet{chanID1.ToUint64()},
MaxPrepayRoutingFee: prepayFee,
MaxMinerFee: defaultMaximumMinerFee,
MaxSwapFee: testQuote.SwapFee,
MaxPrepayAmount: testQuote.PrepayAmount,
SweepConfTarget: loop.DefaultSweepConfTarget,
Initiator: autoloopSwapInitiator,
MaxSwapRoutingFee: ppmToSat(
7000,
defaultRoutingFeePPM,
),
MaxMinerFee: defaultMaximumMinerFee,
MaxSwapFee: testQuote.SwapFee,
MaxPrepayAmount: testQuote.PrepayAmount,
SweepConfTarget: loop.DefaultSweepConfTarget,
Initiator: autoloopSwapInitiator,
}
)
@ -896,8 +1007,8 @@ func TestSizeRestrictions(t *testing.T) {
// endpoint.
serverRestrictions []Restrictions
// expectedAmount is the amount that we expect for our swap.
expectedAmount btcutil.Amount
// suggestions is the set of suggestions we expect.
suggestions *Suggestions
// expectedError is the error we expect.
expectedError error
@ -910,7 +1021,12 @@ func TestSizeRestrictions(t *testing.T) {
serverRestrictions: []Restrictions{
serverRestrictions, serverRestrictions,
},
expectedAmount: 7500,
suggestions: &Suggestions{
OutSwaps: []loop.OutRequest{
chan1Rec,
},
DisqualifiedChans: noneDisqualified,
},
},
{
name: "minimum more than server, no swap",
@ -920,7 +1036,11 @@ func TestSizeRestrictions(t *testing.T) {
serverRestrictions: []Restrictions{
serverRestrictions, serverRestrictions,
},
expectedAmount: 0,
suggestions: &Suggestions{
DisqualifiedChans: map[lnwire.ShortChannelID]Reason{
chanID1: ReasonLiquidityOk,
},
},
},
{
name: "maximum less than server, swap happens",
@ -930,7 +1050,12 @@ func TestSizeRestrictions(t *testing.T) {
serverRestrictions: []Restrictions{
serverRestrictions, serverRestrictions,
},
expectedAmount: 7000,
suggestions: &Suggestions{
OutSwaps: []loop.OutRequest{
outSwap,
},
DisqualifiedChans: noneDisqualified,
},
},
{
// Originally, our client params are ok. But then the
@ -948,8 +1073,8 @@ func TestSizeRestrictions(t *testing.T) {
Maximum: 6000,
},
},
expectedAmount: 0,
expectedError: ErrMaxExceedsServer,
suggestions: nil,
expectedError: ErrMaxExceedsServer,
},
}
@ -981,25 +1106,9 @@ func TestSizeRestrictions(t *testing.T) {
return &restrictions, nil
}
// If we expect a swap (non-zero amount), we add a
// swap to our set of expected swaps, and update amount
// and fee accordingly.
var expectedSwaps []loop.OutRequest
if testCase.expectedAmount != 0 {
outSwap.Amount = testCase.expectedAmount
outSwap.MaxSwapRoutingFee = ppmToSat(
testCase.expectedAmount,
defaultRoutingFeePPM,
)
expectedSwaps = append(expectedSwaps, outSwap)
}
testSuggestSwaps(
t, newSuggestSwapsSetup(cfg, lnd, params),
expectedSwaps, testCase.expectedError,
testCase.suggestions, testCase.expectedError,
)
require.Equal(
@ -1034,7 +1143,7 @@ func newSuggestSwapsSetup(cfg *Config, lnd *test.LndMockServices,
// use the default parameters and setup two channels (channel1 + channel2) with
// chanRule set for each.
func testSuggestSwaps(t *testing.T, setup *testSuggestSwapsSetup,
expected []loop.OutRequest, expectedErr error) {
expected *Suggestions, expectedErr error) {
t.Parallel()

@ -0,0 +1,62 @@
package liquidity
// Reason is an enum which represents the various reasons we have for not
// executing a swap.
type Reason uint8
const (
// ReasonNone is the zero value reason, added so that this enum can
// align with the numeric values used in our protobufs and avoid
// ambiguity around default zero values.
ReasonNone Reason = iota
// ReasonBudgetNotStarted indicates that we do not recommend any swaps
// because the start time for our budget has not arrived yet.
ReasonBudgetNotStarted
// ReasonSweepFees indicates that the estimated fees to sweep swaps
// are too high right now.
ReasonSweepFees
// ReasonBudgetElapsed indicates that the autoloop budget for the
// period has been elapsed.
ReasonBudgetElapsed
// ReasonInFlight indicates that the limit on in-flight automatically
// dispatched swaps has already been reached.
ReasonInFlight
// ReasonSwapFee indicates that the server fee for a specific swap is
// too high.
ReasonSwapFee
// ReasonMinerFee indicates that the miner fee for a specific swap is
// to high.
ReasonMinerFee
// ReasonPrepay indicates that the prepay fee for a specific swap is
// too high.
ReasonPrepay
// ReasonFailureBackoff indicates that a swap has recently failed for
// this target, and the backoff period has not yet passed.
ReasonFailureBackoff
// ReasonLoopOut indicates that a loop out swap is currently utilizing
// the channel, so it is not eligible.
ReasonLoopOut
// ReasonLoopIn indicates that a loop in swap is currently in flight
// for the peer, so it is not eligible.
ReasonLoopIn
// ReasonLiquidityOk indicates that a target meets the liquidity
// balance expressed in its rule, so no swap is needed.
ReasonLiquidityOk
// ReasonBudgetInsufficient indicates that we cannot perform a swap
// because we do not have enough pending budget available. This differs
// from budget elapsed, because we still have some budget available,
// but we have allocated it to other swaps.
ReasonBudgetInsufficient
)

@ -704,7 +704,7 @@ func rpcToRule(rule *looprpc.LiquidityRule) (*liquidity.ThresholdRule, error) {
func (s *swapClientServer) SuggestSwaps(ctx context.Context,
_ *looprpc.SuggestSwapsRequest) (*looprpc.SuggestSwapsResponse, error) {
swaps, err := s.liquidityMgr.SuggestSwaps(ctx, false)
suggestions, err := s.liquidityMgr.SuggestSwaps(ctx, false)
switch err {
case liquidity.ErrNoRules:
return nil, status.Error(codes.FailedPrecondition, err.Error())
@ -717,7 +717,7 @@ func (s *swapClientServer) SuggestSwaps(ctx context.Context,
var loopOut []*looprpc.LoopOutRequest
for _, swap := range swaps {
for _, swap := range suggestions.OutSwaps {
loopOut = append(loopOut, &looprpc.LoopOutRequest{
Amt: int64(swap.Amount),
OutgoingChanSet: swap.OutgoingChanSet,

Loading…
Cancel
Save