|
|
|
@ -1,6 +1,12 @@
|
|
|
|
|
// Package liquidity is responsible for monitoring our node's liquidity. It
|
|
|
|
|
// allows setting of a liquidity rule which describes the desired liquidity
|
|
|
|
|
// balance on a per-channel basis.
|
|
|
|
|
//
|
|
|
|
|
// Swap suggestions are limited to channels that are not currently being used
|
|
|
|
|
// for a pending swap. If we are currently processing an unrestricted swap (ie,
|
|
|
|
|
// a loop out with no outgoing channel targets set or a loop in with no last
|
|
|
|
|
// hop set), we will not suggest any swaps because these swaps will shift the
|
|
|
|
|
// balances of our channels in ways we can't predict.
|
|
|
|
|
package liquidity
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
@ -15,6 +21,7 @@ import (
|
|
|
|
|
"github.com/lightninglabs/loop/loopdb"
|
|
|
|
|
"github.com/lightningnetwork/lnd/clock"
|
|
|
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
|
|
|
"github.com/lightningnetwork/lnd/routing/route"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
@ -65,6 +72,12 @@ type Config struct {
|
|
|
|
|
// Lnd provides us with access to lnd's rpc servers.
|
|
|
|
|
Lnd *lndclient.LndServices
|
|
|
|
|
|
|
|
|
|
// ListLoopOut returns all of the loop our swaps stored on disk.
|
|
|
|
|
ListLoopOut func() ([]*loopdb.LoopOut, error)
|
|
|
|
|
|
|
|
|
|
// ListLoopIn returns all of the loop in swaps stored on disk.
|
|
|
|
|
ListLoopIn func() ([]*loopdb.LoopIn, error)
|
|
|
|
|
|
|
|
|
|
// Clock allows easy mocking of time in unit tests.
|
|
|
|
|
Clock clock.Clock
|
|
|
|
|
}
|
|
|
|
@ -184,19 +197,32 @@ func (m *Manager) SuggestSwaps(ctx context.Context) (
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
channels, err := m.cfg.Lnd.Client.ListChannels(ctx)
|
|
|
|
|
// Get the current server side restrictions.
|
|
|
|
|
outRestrictions, err := m.cfg.LoopOutRestrictions(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get the current server side restrictions.
|
|
|
|
|
outRestrictions, err := m.cfg.LoopOutRestrictions(ctx)
|
|
|
|
|
// List our current set of swaps so that we can determine which channels
|
|
|
|
|
// are already being utilized by swaps. Note that these calls may race
|
|
|
|
|
// with manual initiation of swaps.
|
|
|
|
|
loopOut, err := m.cfg.ListLoopOut()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loopIn, err := m.cfg.ListLoopIn()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
eligible, err := m.getEligibleChannels(ctx, loopOut, loopIn)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var suggestions []loop.OutRequest
|
|
|
|
|
for _, channel := range channels {
|
|
|
|
|
for _, channel := range eligible {
|
|
|
|
|
channelID := lnwire.NewShortChanIDFromInt(channel.ChannelID)
|
|
|
|
|
rule, ok := m.params.ChannelRules[channelID]
|
|
|
|
|
if !ok {
|
|
|
|
@ -242,6 +268,94 @@ func makeLoopOutRequest(suggestion *LoopOutRecommendation) loop.OutRequest {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// getEligibleChannels takes lists of our existing loop out and in swaps, and
|
|
|
|
|
// gets a list of channels that are not currently being utilized for a swap.
|
|
|
|
|
// If an unrestricted swap is ongoing, we return an empty set of channels
|
|
|
|
|
// because we don't know which channels balances it will affect.
|
|
|
|
|
func (m *Manager) getEligibleChannels(ctx context.Context,
|
|
|
|
|
loopOut []*loopdb.LoopOut, loopIn []*loopdb.LoopIn) (
|
|
|
|
|
[]lndclient.ChannelInfo, error) {
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
existingOut = make(map[lnwire.ShortChannelID]bool)
|
|
|
|
|
existingIn = make(map[route.Vertex]bool)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
for _, out := range loopOut {
|
|
|
|
|
var (
|
|
|
|
|
state = out.State().State
|
|
|
|
|
chanSet = out.Contract.OutgoingChanSet
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Skip completed swaps, they can't affect our channel balances.
|
|
|
|
|
if state.Type() != loopdb.StateTypePending {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(chanSet) == 0 {
|
|
|
|
|
log.Debugf("Ongoing unrestricted loop out: "+
|
|
|
|
|
"%v, no suggestions at present", out.Hash)
|
|
|
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, id := range chanSet {
|
|
|
|
|
chanID := lnwire.NewShortChanIDFromInt(id)
|
|
|
|
|
existingOut[chanID] = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, in := range loopIn {
|
|
|
|
|
// Skip completed swaps, they can't affect our channel balances.
|
|
|
|
|
if in.State().State.Type() != loopdb.StateTypePending {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if in.Contract.LastHop == nil {
|
|
|
|
|
log.Debugf("Ongoing unrestricted loop in: "+
|
|
|
|
|
"%v, no suggestions at present", in.Hash)
|
|
|
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
existingIn[*in.Contract.LastHop] = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
channels, err := m.cfg.Lnd.Client.ListChannels(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Run through our set of channels and skip over any channels that
|
|
|
|
|
// are currently being utilized by a restricted swap (where restricted
|
|
|
|
|
// means that a loop out limited channels, or a loop in limited last
|
|
|
|
|
// hop).
|
|
|
|
|
var eligible []lndclient.ChannelInfo
|
|
|
|
|
for _, channel := range channels {
|
|
|
|
|
shortID := lnwire.NewShortChanIDFromInt(channel.ChannelID)
|
|
|
|
|
|
|
|
|
|
if existingOut[shortID] {
|
|
|
|
|
log.Debugf("Channel: %v not eligible for "+
|
|
|
|
|
"suggestions, ongoing loop out utilizing "+
|
|
|
|
|
"channel", channel.ChannelID)
|
|
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if existingIn[channel.PubKeyBytes] {
|
|
|
|
|
log.Debugf("Channel: %v not eligible for "+
|
|
|
|
|
"suggestions, ongoing loop in utilizing "+
|
|
|
|
|
"peer", channel.ChannelID)
|
|
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
eligible = append(eligible, channel)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return eligible, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ppmToSat takes an amount and a measure of parts per million for the amount
|
|
|
|
|
// and returns the amount that the ppm represents.
|
|
|
|
|
func ppmToSat(amount btcutil.Amount, ppm int) btcutil.Amount {
|
|
|
|
|