Merge pull request #1016 from hieblmi/easy-exclude-peers

easyautoloop: exclude peers
This commit is contained in:
Slyghtning 2025-11-24 13:12:55 +01:00 committed by GitHub
commit 83628d775d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 938 additions and 693 deletions

View file

@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"strconv"
"strings"
"github.com/lightninglabs/loop/liquidity"
"github.com/lightninglabs/loop/looprpc"
@ -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 " +
@ -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 " +

View file

@ -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)

View file

@ -328,34 +328,36 @@ $ loop [GLOBAL FLAGS] setparams [COMMAND FLAGS] [ARGUMENTS...]
The following flags are supported:
| Name | Description | Type | Default value |
|---------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|:-------------:|
| `--sweeplimit="…"` | the limit placed on our estimated sweep fee in sat/vByte | int | `0` |
| `--feepercent="…"` | the maximum percentage of swap amount to be used across all fee categories | float | `0` |
| `--maxswapfee="…"` | the maximum percentage of swap volume we are willing to pay in server fees | float | `0` |
| `--maxroutingfee="…"` | the maximum percentage of off-chain payment volume that we are willing to pay in routingfees | float | `0` |
| `--maxprepayfee="…"` | the maximum percentage of off-chain prepay volume that we are willing to pay in routing fees | float | `0` |
| `--maxprepay="…"` | the maximum no-show (prepay) in satoshis that swap suggestions should be limited to | uint | `0` |
| `--maxminer="…"` | the maximum miner fee in satoshis that swap suggestions should be limited to | uint | `0` |
| `--sweepconf="…"` | the number of blocks from htlc height that swap suggestion sweeps should target, used to estimate max miner fee | int | `0` |
| `--failurebackoff="…"` | the amount of time, in seconds, that should pass before a channel that previously had a failed swap will be included in suggestions | uint | `0` |
| `--autoloop` | set to true to enable automated dispatch of swaps, limited to the budget set by autobudget | bool | `false` |
| `--destaddr="…"` | custom address to be used as destination for autoloop loop out, set to "default" in order to revert to default behavior | string |
| `--account="…"` | the name of the account to generate a new address from. You can list the names of valid accounts in your backing lnd instance with "lncli wallet accounts list" | string |
| `--account_addr_type="…"` | the address type of the extended public key specified in account. Currently only pay-to-taproot-pubkey(p2tr) is supported | string | `p2tr` |
| `--autobudget="…"` | the maximum amount of fees in satoshis that automatically dispatched loop out swaps may spend | uint | `0` |
| `--autobudgetrefreshperiod="…"` | the time period over which the automated loop budget is refreshed | duration | `0s` |
| `--autoinflight="…"` | the maximum number of automatically dispatched swaps that we allow to be in flight | uint | `0` |
| `--minamt="…"` | the minimum amount in satoshis that the autoloop client will dispatch per-swap | uint | `0` |
| `--maxamt="…"` | the maximum amount in satoshis that the autoloop client will dispatch per-swap | uint | `0` |
| `--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` |
| `--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` |
| `--fast` | if set new swaps are expected to be published immediately, paying a potentially higher fee. If not set the swap server might choose to wait up to 30 minutes before publishing swap HTLCs on-chain, to save on chain fees. Not setting this flag therefore might result in a lower swap fees | bool | `false` |
| `--help` (`-h`) | show help | bool | `false` |
| Name | Description | Type | Default value |
|-----------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|:-------------:|
| `--sweeplimit="…"` | the limit placed on our estimated sweep fee in sat/vByte | int | `0` |
| `--feepercent="…"` | the maximum percentage of swap amount to be used across all fee categories | float | `0` |
| `--maxswapfee="…"` | the maximum percentage of swap volume we are willing to pay in server fees | float | `0` |
| `--maxroutingfee="…"` | the maximum percentage of off-chain payment volume that we are willing to pay in routingfees | float | `0` |
| `--maxprepayfee="…"` | the maximum percentage of off-chain prepay volume that we are willing to pay in routing fees | float | `0` |
| `--maxprepay="…"` | the maximum no-show (prepay) in satoshis that swap suggestions should be limited to | uint | `0` |
| `--maxminer="…"` | the maximum miner fee in satoshis that swap suggestions should be limited to | uint | `0` |
| `--sweepconf="…"` | the number of blocks from htlc height that swap suggestion sweeps should target, used to estimate max miner fee | int | `0` |
| `--failurebackoff="…"` | the amount of time, in seconds, that should pass before a channel that previously had a failed swap will be included in suggestions | uint | `0` |
| `--autoloop` | set to true to enable automated dispatch of swaps, limited to the budget set by autobudget | bool | `false` |
| `--destaddr="…"` | custom address to be used as destination for autoloop loop out, set to "default" in order to revert to default behavior | string |
| `--account="…"` | the name of the account to generate a new address from. You can list the names of valid accounts in your backing lnd instance with "lncli wallet accounts list" | string |
| `--account_addr_type="…"` | the address type of the extended public key specified in account. Currently only pay-to-taproot-pubkey(p2tr) is supported | string | `p2tr` |
| `--autobudget="…"` | the maximum amount of fees in satoshis that automatically dispatched loop out swaps may spend | uint | `0` |
| `--autobudgetrefreshperiod="…"` | the time period over which the automated loop budget is refreshed | duration | `0s` |
| `--autoinflight="…"` | the maximum number of automatically dispatched swaps that we allow to be in flight | uint | `0` |
| `--minamt="…"` | the minimum amount in satoshis that the autoloop client will dispatch per-swap | uint | `0` |
| `--maxamt="…"` | the maximum amount in satoshis that the autoloop client will dispatch per-swap | uint | `0` |
| `--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` |
| `--easyautoloop_excludepeer="…"` | list of peer pubkeys (hex) to exclude from easy autoloop channel selection; repeat --easyautoloop_excludepeer for multiple peers | string | `[]` |
| `--easyatutoloop_includeallpeers` | include all peers back into easy autoloop by clearing the exclusion list. It cannot be combined with --easyautoloop_excludepeer | 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` |
| `--fast` | if set new swaps are expected to be published immediately, paying a potentially higher fee. If not set the swap server might choose to wait up to 30 minutes before publishing swap HTLCs on-chain, to save on chain fees. Not setting this flag therefore might result in a lower swap fees | bool | `false` |
| `--help` (`-h`) | show help | bool | `false` |
### `getinfo` command

View file

@ -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",
)
}

View file

@ -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 {

View file

@ -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
@ -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
@ -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:

File diff suppressed because it is too large Load diff

View file

@ -1280,6 +1280,13 @@ message LiquidityParameters {
* not be able to be batched with other swaps.
*/
bool fast_swap_publication = 26;
/*
A list of peers (their public keys) that should be excluded from the easy
autoloop run. If set, channels connected to these peers won't be
considered for easy autoloop swaps.
*/
repeated bytes easy_autoloop_excluded_peers = 27;
}
message EasyAssetAutoloopParams {

View file

@ -1787,6 +1787,14 @@
"fast_swap_publication": {
"type": "boolean",
"description": "Set to true to enable fast swap publication. If set, the server will\npublish the HTLC immediately after receiving the swap request. This\nsetting has direct implications on the swap fees, as fast swaps may\nnot be able to be batched with other swaps."
},
"easy_autoloop_excluded_peers": {
"type": "array",
"items": {
"type": "string",
"format": "byte"
},
"description": "A list of peers (their public keys) that should be excluded from the easy\nautoloop run. If set, channels connected to these peers won't be\nconsidered for easy autoloop swaps."
}
}
},