Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions cmd/loop/liquidity.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"strconv"
"strings"

"github.com/lightninglabs/loop/liquidity"
"github.com/lightninglabs/loop/looprpc"
Expand Down Expand Up @@ -350,6 +351,18 @@ var setParamsCommand = &cli.Command{
Usage: "the target size of total local balance in " +
"satoshis, used by easy autoloop.",
},
&cli.StringSliceFlag{
Name: "excludeeasypeer",
Usage: "list of peer pubkeys (hex) to exclude from " +
"easy autoloop channel selection. Repeat " +
"--excludeeasypeer for multiple peers",
},
&cli.BoolFlag{
Name: "includealleasypeers",
Usage: "include all peers back into easy autoloop by " +
"clearing the exclusion list. It cannot be " +
"combined with --excludeeasypeer",
},
&cli.BoolFlag{
Name: "asset_easyautoloop",
Usage: "set to true to enable asset easy autoloop, which " +
Expand Down Expand Up @@ -567,6 +580,36 @@ func setParams(ctx context.Context, cmd *cli.Command) error {
flagSet = true
}

// If includealleasypeers is set, clear the entire exclusion list.
if cmd.IsSet("includealleasypeers") {
if cmd.IsSet("excludeeasypeer") {
return fmt.Errorf("includealleasypeers cannot be used with --excludeeasypeer")
}
params.EasyAutoloopExcludedPeers = nil
flagSet = true
}

if cmd.IsSet("excludeeasypeer") {
peers := cmd.StringSlice("excludeeasypeer")
// Reset and set according to a provided list.
params.EasyAutoloopExcludedPeers = make([][]byte, 0, len(peers))
for _, s := range peers {
s = strings.TrimSpace(s)
if s == "" {
continue
}
v, err := route.NewVertexFromStr(s)
if err != nil {
return fmt.Errorf("invalid peer pubkey "+
"%s: %v", s, err)
}
params.EasyAutoloopExcludedPeers = append(
params.EasyAutoloopExcludedPeers, v[:],
)
}
flagSet = true
}

if cmd.IsSet("asset_easyautoloop") {
if !cmd.IsSet("asset_id") {
return fmt.Errorf("asset_id must be set to use " +
Expand Down
6 changes: 6 additions & 0 deletions docs/loop.1
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,9 @@ update the parameters set for the liquidity manager
.PP
\fB--easyautoloop\fP: set to true to enable easy autoloop, which will automatically dispatch swaps in order to meet the target local balance.

.PP
\fB--excludeeasypeer\fP="": list of peer pubkeys (hex) to exclude from easy autoloop channel selection. Repeat --excludeeasypeer for multiple peers (default: [])

.PP
\fB--failurebackoff\fP="": the amount of time, in seconds, that should pass before a channel that previously had a failed swap will be included in suggestions. (default: 0)

Expand All @@ -350,6 +353,9 @@ update the parameters set for the liquidity manager
.PP
\fB--htlc_conf\fP="": the confirmation target for loop in on-chain htlcs. (default: 0)

.PP
\fB--includealleasypeers\fP: include all peers back into easy autoloop by clearing the exclusion list. It cannot be combined with --excludeeasypeer

.PP
\fB--localbalancesat\fP="": the target size of total local balance in satoshis, used by easy autoloop. (default: 0)

Expand Down
2 changes: 2 additions & 0 deletions docs/loop.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,8 @@ The following flags are supported:
| `--htlc_conf="…"` | the confirmation target for loop in on-chain htlcs | int | `0` |
| `--easyautoloop` | set to true to enable easy autoloop, which will automatically dispatch swaps in order to meet the target local balance | bool | `false` |
| `--localbalancesat="…"` | the target size of total local balance in satoshis, used by easy autoloop | uint | `0` |
| `--excludeeasypeer="…"` | list of peer pubkeys (hex) to exclude from easy autoloop channel selection. Repeat --excludeeasypeer for multiple peers | string | `[]` |
| `--includealleasypeers` | include all peers back into easy autoloop by clearing the exclusion list. It cannot be combined with --excludeeasypeer | bool | `false` |
| `--asset_easyautoloop` | set to true to enable asset easy autoloop, which will automatically dispatch asset swaps in order to meet the target local balance | bool | `false` |
| `--asset_id="…"` | If set to a valid asset ID, the easyautoloop and localbalancesat flags will be set for the specified asset | string |
| `--asset_localbalance="…"` | the target size of total local balance in asset units, used by asset easy autoloop | uint | `0` |
Expand Down
115 changes: 115 additions & 0 deletions liquidity/easy_autoloop_exclusions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package liquidity

import (
"testing"

"github.com/btcsuite/btcd/btcutil"
"github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/stretchr/testify/require"
)

// TestEasyAutoloopExcludedPeers ensures that peers listed in
// Parameters.EasyAutoloopExcludedPeers are not selected by
// pickEasyAutoloopChannel even if they would otherwise be preferred.
func TestEasyAutoloopExcludedPeers(t *testing.T) {
// Two channels, peer1 has the higher local balance and would be picked
// if not excluded.
ch1 := lndclient.ChannelInfo{
Active: true,
ChannelID: lnwire.NewShortChanIDFromInt(11).ToUint64(),
PubKeyBytes: peer1,
LocalBalance: 90000,
RemoteBalance: 0,
Capacity: 100000,
}
ch2 := lndclient.ChannelInfo{
Active: true,
ChannelID: lnwire.NewShortChanIDFromInt(22).ToUint64(),
PubKeyBytes: peer2,
LocalBalance: 80000,
RemoteBalance: 0,
Capacity: 100000,
}

params := defaultParameters
params.Autoloop = true
params.EasyAutoloop = true
params.EasyAutoloopTarget = 80000
params.ClientRestrictions.Minimum = btcutil.Amount(1)
params.ClientRestrictions.Maximum = btcutil.Amount(10000)
// Exclude peer1, even though its channel has more local balance.
params.EasyAutoloopExcludedPeers = []route.Vertex{peer1}

c := newAutoloopTestCtx(
t, params, []lndclient.ChannelInfo{ch1, ch2}, testRestrictions,
)

// Picking a channel should not pick the excluded peer's channel.
picked := c.manager.pickEasyAutoloopChannel(
[]lndclient.ChannelInfo{ch1, ch2}, &params.ClientRestrictions,
nil, nil, 1,
)
require.NotNil(t, picked)
require.Equal(
t, ch2.ChannelID, picked.ChannelID,
"should pick non-excluded peer's channel",
)
}

// TestEasyAutoloopIncludeAllPeers simulates the --includealleasypeers flag by
// clearing the exclusion list and ensuring a previously excluded peer can be
// selected again.
func TestEasyAutoloopIncludeAllPeers(t *testing.T) {
ch1 := lndclient.ChannelInfo{
Active: true,
ChannelID: lnwire.NewShortChanIDFromInt(33).ToUint64(),
PubKeyBytes: peer1,
LocalBalance: 90000,
RemoteBalance: 0,
Capacity: 100000,
}
ch2 := lndclient.ChannelInfo{
Active: true,
ChannelID: lnwire.NewShortChanIDFromInt(44).ToUint64(),
PubKeyBytes: peer2,
LocalBalance: 80000,
RemoteBalance: 0,
Capacity: 100000,
}

params := defaultParameters
params.Autoloop = true
params.EasyAutoloop = true
params.EasyAutoloopTarget = 80000
params.ClientRestrictions.Minimum = btcutil.Amount(1)
params.ClientRestrictions.Maximum = btcutil.Amount(10000)
params.EasyAutoloopExcludedPeers = []route.Vertex{peer1}

c := newAutoloopTestCtx(
t, params, []lndclient.ChannelInfo{ch1, ch2}, testRestrictions,
)

// With exclusion active, peer1 should not be picked.
picked := c.manager.pickEasyAutoloopChannel(
[]lndclient.ChannelInfo{ch1, ch2}, &params.ClientRestrictions,
nil, nil, 1,
)
require.NotNil(t, picked)
require.Equal(t, ch2.ChannelID, picked.ChannelID)

// Simulate --includealleasypeers by clearing the exclusion list as the
// CLI does before sending to the server.
c.manager.params.EasyAutoloopExcludedPeers = nil

picked = c.manager.pickEasyAutoloopChannel(
[]lndclient.ChannelInfo{ch1, ch2}, &params.ClientRestrictions,
nil, nil, 1,
)
require.NotNil(t, picked)
require.Equal(
t, ch1.ChannelID, picked.ChannelID,
"after include-all, highest local balance should win again",
)
}
20 changes: 19 additions & 1 deletion liquidity/liquidity.go
Original file line number Diff line number Diff line change
Expand Up @@ -1666,9 +1666,27 @@ func (m *Manager) pickEasyAutoloopChannel(channels []lndclient.ChannelInfo,
return channels[i].LocalBalance > channels[j].LocalBalance
})

// Check each channel, since channels are already sorted we return the
// Build a set of excluded peers for a quick lookup.
excluded := make(
map[route.Vertex]struct{},
len(m.params.EasyAutoloopExcludedPeers),
)
for _, v := range m.params.EasyAutoloopExcludedPeers {
excluded[v] = struct{}{}
}

// Check each channel, since channels are already sorted, we return the
// first channel that passes all checks.
for _, channel := range channels {
// Skip channels whose remote peer is excluded for easy autoloop.
if _, ok := excluded[channel.PubKeyBytes]; ok {
log.Debugf("Channel %v cannot be used for easy "+
"autoloop: peer %v manually excluded",
channel.ChannelID, channel.PubKeyBytes)

continue
}

shortChanID := lnwire.NewShortChanIDFromInt(channel.ChannelID)

if !channel.Active {
Expand Down
28 changes: 28 additions & 0 deletions liquidity/parameters.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ type Parameters struct {
// maintain in our channels.
EasyAutoloopTarget btcutil.Amount

// EasyAutoloopExcludedPeers is an optional list of peers that should be
// excluded from being selected for easy autoloop swaps.
EasyAutoloopExcludedPeers []route.Vertex

// AssetAutoloopParams maps an asset id hex encoded string to its
// easy autoloop parameters.
AssetAutoloopParams map[string]AssetParams
Expand Down Expand Up @@ -481,6 +485,21 @@ func RpcToParameters(req *clientrpc.LiquidityParameters) (*Parameters,
time.Second
}

// Map excluded peers for easy autoloop, if any.
excludedPeersRPC := req.GetEasyAutoloopExcludedPeers()
params.EasyAutoloopExcludedPeers = make(
[]route.Vertex, 0, len(excludedPeersRPC),
)
for _, p := range excludedPeersRPC {
v, err := route.NewVertexFromBytes(p)
if err != nil {
return nil, err
}
params.EasyAutoloopExcludedPeers = append(
params.EasyAutoloopExcludedPeers, v,
)
}

// If an old-style budget was written to storage then express it by
// using the new auto budget parameters. If the newly added parameters
// have the 0 default value, but a budget was defined that means the
Expand Down Expand Up @@ -603,6 +622,15 @@ func ParametersToRpc(cfg Parameters) (*clientrpc.LiquidityParameters,
EasyAssetParams: easyAssetMap,
FastSwapPublication: cfg.FastSwapPublication,
}
// Set excluded peers for easy autoloop.
rpcCfg.EasyAutoloopExcludedPeers = make(
[][]byte, 0, len(cfg.EasyAutoloopExcludedPeers),
)
for _, v := range cfg.EasyAutoloopExcludedPeers {
rpcCfg.EasyAutoloopExcludedPeers = append(
rpcCfg.EasyAutoloopExcludedPeers, v[:],
)
}

switch f := cfg.FeeLimit.(type) {
case *FeeCategoryLimit:
Expand Down
Loading