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
46 changes: 46 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: "easyautoloop_excludepeer",
Usage: "list of peer pubkeys (hex) to exclude from " +
"easy autoloop channel selection; repeat " +
"--easyautoloop_excludepeer for multiple peers",
},
&cli.BoolFlag{
Name: "easyatutoloop_includeallpeers",
Usage: "include all peers back into easy autoloop by " +
"clearing the exclusion list. It cannot be " +
"combined with --easyautoloop_excludepeer",
},
&cli.BoolFlag{
Name: "asset_easyautoloop",
Usage: "set to true to enable asset easy autoloop, which " +
Expand Down Expand Up @@ -567,6 +580,39 @@ func setParams(ctx context.Context, cmd *cli.Command) error {
flagSet = true
}

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

if cmd.IsSet("easyautoloop_excludepeer") {
peers := cmd.StringSlice("easyautoloop_excludepeer")
// 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 @@ -332,9 +332,15 @@ update the parameters set for the liquidity manager
.PP
\fB--destaddr\fP="": custom address to be used as destination for autoloop loop out, set to "default" in order to revert to default behavior.

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

.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--easyautoloop_excludepeer\fP="": list of peer pubkeys (hex) to exclude from easy autoloop channel selection; repeat --easyautoloop_excludepeer 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 Down
58 changes: 30 additions & 28 deletions docs/loop.md

Large diffs are not rendered by default.

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