mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
liquidity: gate static autoloop
Static-address loop-ins in autoloop are still experimental. Reject loop_in_source=static-address at the RPC boundary unless loopd was started with --experimental, and pass the same opt-in into the liquidity manager so persisted params cannot bypass the gate after restart. The existing static swap accounting remains wired through the manager; the gate only controls accepting and planning new static-address autoloops.
This commit is contained in:
parent
8d6df2bf20
commit
7fcc508193
11 changed files with 88 additions and 6 deletions
|
|
@ -343,7 +343,8 @@ var setParamsCommand = &cli.Command{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "loopinsource",
|
Name: "loopinsource",
|
||||||
Usage: "the loop-in source to use for autoloop rules: " +
|
Usage: "the loop-in source to use for autoloop rules: " +
|
||||||
"wallet or static-address.",
|
"wallet or static-address. Static-address " +
|
||||||
|
"requires loopd --experimental.",
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "easyautoloop",
|
Name: "easyautoloop",
|
||||||
|
|
|
||||||
|
|
@ -360,7 +360,7 @@ update the parameters set for the liquidity manager
|
||||||
\fB--localbalancesat\fP="": the target size of total local balance in satoshis, used by easy autoloop. (default: 0)
|
\fB--localbalancesat\fP="": the target size of total local balance in satoshis, used by easy autoloop. (default: 0)
|
||||||
|
|
||||||
.PP
|
.PP
|
||||||
\fB--loopinsource\fP="": the loop-in source to use for autoloop rules: wallet or static-address.
|
\fB--loopinsource\fP="": the loop-in source to use for autoloop rules: wallet or static-address. Static-address requires loopd --experimental.
|
||||||
|
|
||||||
.PP
|
.PP
|
||||||
\fB--maxamt\fP="": the maximum amount in satoshis that the autoloop client will dispatch per-swap. (default: 0)
|
\fB--maxamt\fP="": the maximum amount in satoshis that the autoloop client will dispatch per-swap. (default: 0)
|
||||||
|
|
|
||||||
|
|
@ -371,7 +371,7 @@ The following flags are supported:
|
||||||
| `--minamt="…"` | the minimum amount in satoshis that the autoloop client will dispatch per-swap | 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` |
|
| `--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` |
|
| `--htlc_conf="…"` | the confirmation target for loop in on-chain htlcs | int | `0` |
|
||||||
| `--loopinsource="…"` | the loop-in source to use for autoloop rules: wallet or static-address | string |
|
| `--loopinsource="…"` | the loop-in source to use for autoloop rules: wallet or static-address. Static-address requires loopd --experimental | string |
|
||||||
| `--easyautoloop` | set to true to enable easy autoloop, which will automatically dispatch swaps in order to meet the target local balance | bool | `false` |
|
| `--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` |
|
| `--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 | `[]` |
|
| `--easyautoloop_excludepeer="…"` | list of peer pubkeys (hex) to exclude from easy autoloop channel selection; repeat --easyautoloop_excludepeer for multiple peers | string | `[]` |
|
||||||
|
|
|
||||||
|
|
@ -168,6 +168,11 @@ var (
|
||||||
// account address type is not or vice versa.
|
// account address type is not or vice versa.
|
||||||
ErrAccountAndAddrType = errors.New("account and address type have " +
|
ErrAccountAndAddrType = errors.New("account and address type have " +
|
||||||
"to be both either set or unset")
|
"to be both either set or unset")
|
||||||
|
|
||||||
|
// ErrStaticAddressAutoloopExperimental is returned when static-address
|
||||||
|
// autoloop is configured without the daemon experimental flag.
|
||||||
|
ErrStaticAddressAutoloopExperimental = errors.New("static address " +
|
||||||
|
"autoloop requires --experimental")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config contains the external functionality required to run the
|
// Config contains the external functionality required to run the
|
||||||
|
|
@ -226,6 +231,10 @@ type Config struct {
|
||||||
request *loop.StaticAddressLoopInRequest) (
|
request *loop.StaticAddressLoopInRequest) (
|
||||||
*StaticLoopInDispatchResult, error)
|
*StaticLoopInDispatchResult, error)
|
||||||
|
|
||||||
|
// EnableStaticAddressAutoloop allows the liquidity manager to accept
|
||||||
|
// static-address loop-in sources for new autoloop parameters.
|
||||||
|
EnableStaticAddressAutoloop bool
|
||||||
|
|
||||||
// LoopInTerms returns the terms for a loop in swap.
|
// LoopInTerms returns the terms for a loop in swap.
|
||||||
LoopInTerms func(ctx context.Context,
|
LoopInTerms func(ctx context.Context,
|
||||||
initiator string) (*loop.LoopInTerms, error)
|
initiator string) (*loop.LoopInTerms, error)
|
||||||
|
|
@ -392,6 +401,15 @@ func (m *Manager) SetParameters(ctx context.Context,
|
||||||
func (m *Manager) setParameters(ctx context.Context,
|
func (m *Manager) setParameters(ctx context.Context,
|
||||||
params Parameters) error {
|
params Parameters) error {
|
||||||
|
|
||||||
|
// Static-address autoloop is still experimental. Check this before
|
||||||
|
// any network-dependent validation so persisted params cannot start
|
||||||
|
// the feature after a restart without the daemon opt-in.
|
||||||
|
if params.LoopInSource == LoopInSourceStaticAddress &&
|
||||||
|
!m.cfg.EnableStaticAddressAutoloop {
|
||||||
|
|
||||||
|
return ErrStaticAddressAutoloopExperimental
|
||||||
|
}
|
||||||
|
|
||||||
restrictions, err := m.cfg.Restrictions(
|
restrictions, err := m.cfg.Restrictions(
|
||||||
ctx, swap.TypeOut, getInitiator(m.params),
|
ctx, swap.TypeOut, getInitiator(m.params),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -387,6 +387,7 @@ func TestPersistParams(t *testing.T) {
|
||||||
LoopInSource: clientrpc.LoopInSource_LOOP_IN_SOURCE_STATIC_ADDRESS,
|
LoopInSource: clientrpc.LoopInSource_LOOP_IN_SOURCE_STATIC_ADDRESS,
|
||||||
}
|
}
|
||||||
cfg, _ := newTestConfig()
|
cfg, _ := newTestConfig()
|
||||||
|
cfg.EnableStaticAddressAutoloop = true
|
||||||
manager := NewManager(cfg)
|
manager := NewManager(cfg)
|
||||||
|
|
||||||
ctx := t.Context()
|
ctx := t.Context()
|
||||||
|
|
@ -440,6 +441,26 @@ func TestPersistParams(t *testing.T) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestStaticAddressAutoloopRequiresExperimental verifies that static-address
|
||||||
|
// loop-in sources are rejected unless the daemon enabled the experimental
|
||||||
|
// static autoloop path.
|
||||||
|
func TestStaticAddressAutoloopRequiresExperimental(t *testing.T) {
|
||||||
|
ctx := t.Context()
|
||||||
|
|
||||||
|
cfg, _ := newTestConfig()
|
||||||
|
manager := NewManager(cfg)
|
||||||
|
|
||||||
|
params := manager.GetParameters()
|
||||||
|
params.LoopInSource = LoopInSourceStaticAddress
|
||||||
|
|
||||||
|
err := manager.setParameters(ctx, params)
|
||||||
|
require.ErrorIs(t, err, ErrStaticAddressAutoloopExperimental)
|
||||||
|
|
||||||
|
cfg.EnableStaticAddressAutoloop = true
|
||||||
|
err = manager.setParameters(ctx, params)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
// TestRestrictedSuggestions tests getting of swap suggestions when we have
|
// TestRestrictedSuggestions tests getting of swap suggestions when we have
|
||||||
// other in-flight swaps. We setup our manager with a set of channels and rules
|
// other in-flight swaps. We setup our manager with a set of channels and rules
|
||||||
// that require a loop out swap, focusing on the filtering our of channels that
|
// that require a loop out swap, focusing on the filtering our of channels that
|
||||||
|
|
|
||||||
|
|
@ -286,6 +286,7 @@ func TestSuggestSwapsStaticLoopInNoCandidate(t *testing.T) {
|
||||||
ctx := t.Context()
|
ctx := t.Context()
|
||||||
|
|
||||||
cfg, lnd := newTestConfig()
|
cfg, lnd := newTestConfig()
|
||||||
|
cfg.EnableStaticAddressAutoloop = true
|
||||||
cfg.PrepareStaticLoopIn = func(context.Context, route.Vertex,
|
cfg.PrepareStaticLoopIn = func(context.Context, route.Vertex,
|
||||||
btcutil.Amount, btcutil.Amount, string, string,
|
btcutil.Amount, btcutil.Amount, string, string,
|
||||||
[]string) (*PreparedStaticLoopIn, error) {
|
[]string) (*PreparedStaticLoopIn, error) {
|
||||||
|
|
@ -330,6 +331,7 @@ func TestSuggestSwapsMixedInFlightCount(t *testing.T) {
|
||||||
ctx := t.Context()
|
ctx := t.Context()
|
||||||
|
|
||||||
cfg, lnd := newTestConfig()
|
cfg, lnd := newTestConfig()
|
||||||
|
cfg.EnableStaticAddressAutoloop = true
|
||||||
cfg.PrepareStaticLoopIn = func(_ context.Context, peer route.Vertex,
|
cfg.PrepareStaticLoopIn = func(_ context.Context, peer route.Vertex,
|
||||||
_, _ btcutil.Amount, label, initiator string,
|
_, _ btcutil.Amount, label, initiator string,
|
||||||
_ []string) (*PreparedStaticLoopIn, error) {
|
_ []string) (*PreparedStaticLoopIn, error) {
|
||||||
|
|
@ -388,6 +390,7 @@ func TestAutoLoopDispatchesStaticLoopIn(t *testing.T) {
|
||||||
ctx := t.Context()
|
ctx := t.Context()
|
||||||
|
|
||||||
cfg, lnd := newTestConfig()
|
cfg, lnd := newTestConfig()
|
||||||
|
cfg.EnableStaticAddressAutoloop = true
|
||||||
|
|
||||||
var (
|
var (
|
||||||
prepareCalls int
|
prepareCalls int
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,7 @@ type Config struct {
|
||||||
MaxStaticAddrHtlcFeePercentage float64 `long:"maxstaticaddrhtlcfeepercentage" description:"The maximum fee percentage that the server can charge for the htlc tx."`
|
MaxStaticAddrHtlcFeePercentage float64 `long:"maxstaticaddrhtlcfeepercentage" description:"The maximum fee percentage that the server can charge for the htlc tx."`
|
||||||
MaxStaticAddrHtlcBackupFeePercentage float64 `long:"maxstaticaddrhtlcbackupfeepercentage" description:"The maximum fee percentage that the server can charge for the htlc backup tx. The backup transaction is only used in rare cases when the regular htlc tx is not confirmed on time. These backup transactions refer to high fee or extremely high fee transactions in the API."`
|
MaxStaticAddrHtlcBackupFeePercentage float64 `long:"maxstaticaddrhtlcbackupfeepercentage" description:"The maximum fee percentage that the server can charge for the htlc backup tx. The backup transaction is only used in rare cases when the regular htlc tx is not confirmed on time. These backup transactions refer to high fee or extremely high fee transactions in the API."`
|
||||||
|
|
||||||
EnableExperimental bool `long:"experimental" description:"Enable experimental features: reservations"`
|
EnableExperimental bool `long:"experimental" description:"Enable experimental features: reservations, static autoloop"`
|
||||||
|
|
||||||
MigrationRPCBatchSize int `long:"migrationrpcbatchsize" description:"The RPC batch size to use during migrations."`
|
MigrationRPCBatchSize int `long:"migrationrpcbatchsize" description:"The RPC batch size to use during migrations."`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -735,7 +735,7 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
liquidityMgr := getLiquidityManager(
|
liquidityMgr := getLiquidityManager(
|
||||||
swapClient, staticLoopInManager,
|
swapClient, staticLoopInManager, d.cfg.EnableExperimental,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Now finally fully initialize the swap client RPC server instance.
|
// Now finally fully initialize the swap client RPC server instance.
|
||||||
|
|
|
||||||
|
|
@ -1395,6 +1395,18 @@ func (s *swapClientServer) SetLiquidityParams(ctx context.Context,
|
||||||
in *looprpc.SetLiquidityParamsRequest) (*looprpc.SetLiquidityParamsResponse,
|
in *looprpc.SetLiquidityParamsRequest) (*looprpc.SetLiquidityParamsResponse,
|
||||||
error) {
|
error) {
|
||||||
|
|
||||||
|
enableExperimental := s.config != nil && s.config.EnableExperimental
|
||||||
|
params := in.GetParameters()
|
||||||
|
if params.GetLoopInSource() ==
|
||||||
|
looprpc.LoopInSource_LOOP_IN_SOURCE_STATIC_ADDRESS &&
|
||||||
|
!enableExperimental {
|
||||||
|
|
||||||
|
return nil, status.Error(
|
||||||
|
codes.FailedPrecondition,
|
||||||
|
liquidity.ErrStaticAddressAutoloopExperimental.Error(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
err := s.liquidityMgr.SetParameters(ctx, in.Parameters)
|
err := s.liquidityMgr.SetParameters(ctx, in.Parameters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,8 @@ import (
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/lightningnetwork/lnd/routing/route"
|
"github.com/lightningnetwork/lnd/routing/route"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -281,6 +283,29 @@ func TestStaticAddressLoopInRejectsReservedLabel(t *testing.T) {
|
||||||
require.ErrorContains(t, err, labels.ErrReservedPrefix.Error())
|
require.ErrorContains(t, err, labels.ErrReservedPrefix.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestSetLiquidityParamsRejectsStaticAutoloopWithoutExperimental verifies that
|
||||||
|
// users must restart loopd with --experimental before enabling static-address
|
||||||
|
// autoloop.
|
||||||
|
func TestSetLiquidityParamsRejectsStaticAutoloopWithoutExperimental(
|
||||||
|
t *testing.T) {
|
||||||
|
|
||||||
|
server := &swapClientServer{
|
||||||
|
config: &Config{},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := server.SetLiquidityParams(
|
||||||
|
t.Context(), &looprpc.SetLiquidityParamsRequest{
|
||||||
|
Parameters: &looprpc.LiquidityParameters{
|
||||||
|
LoopInSource: looprpc.
|
||||||
|
LoopInSource_LOOP_IN_SOURCE_STATIC_ADDRESS,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, codes.FailedPrecondition, status.Code(err))
|
||||||
|
require.ErrorContains(t, err, "--experimental")
|
||||||
|
}
|
||||||
|
|
||||||
// TestRPCAutoloopReasonStaticLoopInNoCandidate verifies that the new planner
|
// TestRPCAutoloopReasonStaticLoopInNoCandidate verifies that the new planner
|
||||||
// reason is exposed over rpc.
|
// reason is exposed over rpc.
|
||||||
func TestRPCAutoloopReasonStaticLoopInNoCandidate(t *testing.T) {
|
func TestRPCAutoloopReasonStaticLoopInNoCandidate(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,8 @@ func openDatabase(cfg *Config, chainParams *chaincfg.Params) (loopdb.SwapStore,
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLiquidityManager(client *loop.Client,
|
func getLiquidityManager(client *loop.Client,
|
||||||
staticLoopInManager *loopin.Manager) *liquidity.Manager {
|
staticLoopInManager *loopin.Manager,
|
||||||
|
enableExperimental bool) *liquidity.Manager {
|
||||||
|
|
||||||
listStaticLoopIn := func(
|
listStaticLoopIn := func(
|
||||||
ctx context.Context) ([]*liquidity.StaticLoopInInfo, error) {
|
ctx context.Context) ([]*liquidity.StaticLoopInInfo, error) {
|
||||||
|
|
@ -259,6 +260,7 @@ func getLiquidityManager(client *loop.Client,
|
||||||
PutLiquidityParams: client.Store.PutLiquidityParams,
|
PutLiquidityParams: client.Store.PutLiquidityParams,
|
||||||
FetchLiquidityParams: client.Store.FetchLiquidityParams,
|
FetchLiquidityParams: client.Store.FetchLiquidityParams,
|
||||||
}
|
}
|
||||||
|
mngrCfg.EnableStaticAddressAutoloop = enableExperimental
|
||||||
|
|
||||||
return liquidity.NewManager(mngrCfg)
|
return liquidity.NewManager(mngrCfg)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue