Skip to content

Commit ca3e700

Browse files
authored
Merge pull request #321 from carlaKC/319-customautoloopsizelimits
liquidity: add custom swap amounts to autoloop
2 parents 6a44f9d + f8664c5 commit ca3e700

11 files changed

+606
-179
lines changed

cmd/loop/liquidity.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,16 @@ var setParamsCommand = cli.Command{
248248
"dispatched swaps that we allow to be in " +
249249
"flight",
250250
},
251+
cli.Uint64Flag{
252+
Name: "minamt",
253+
Usage: "the minimum amount in satoshis that the " +
254+
"autoloop client will dispatch per-swap",
255+
},
256+
cli.Uint64Flag{
257+
Name: "maxamt",
258+
Usage: "the maximum amount in satoshis that the " +
259+
"autoloop client will dispatch per-swap",
260+
},
251261
},
252262
Action: setParams,
253263
}
@@ -348,6 +358,16 @@ func setParams(ctx *cli.Context) error {
348358
flagSet = true
349359
}
350360

361+
if ctx.IsSet("minamt") {
362+
params.MinSwapAmount = ctx.Uint64("minamt")
363+
flagSet = true
364+
}
365+
366+
if ctx.IsSet("maxamt") {
367+
params.MaxSwapAmount = ctx.Uint64("maxamt")
368+
flagSet = true
369+
}
370+
351371
if !flagSet {
352372
return fmt.Errorf("at least one flag required to set params")
353373
}

docs/autoloop.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,33 @@ The default value for this parameter is 24hours, and it can be updated as follow
191191
loop setparams --failurebackoff={backoff in seconds}
192192
```
193193

194+
### Swap Size
195+
By default, the autolooper will execute a swap when the amount that needs to be
196+
rebalanced within a channel is equal to the swap server's minimum swap size.
197+
This means that it will dispatch swaps more regularly, and ensure that channels
198+
are not run down too far below their configured threshold. If you are willing
199+
to allow your liquidity to drop further than the minimum swap amount below your
200+
threshold, a custom minimum swap size can be set. If autolooper is configured
201+
with a larger minimum swap size, it will allow channels to drop further below
202+
their target threshold, but will perform fewer swaps, potentially saving on
203+
fees.
204+
205+
```
206+
loop setparams --minamt={amount in satoshis}
207+
```
208+
209+
Swaps are also limited to the maximum swap amount advertised by the server. If
210+
you would like to reduce the size of swap that autoloop created, this value can
211+
also be configured.
212+
213+
```
214+
loop setparams --maxamt={amount in satoshis}
215+
```
216+
217+
The server's current terms are provided by the `loop terms` cli command. The
218+
values set for minimum and maximum swap amount must be within the range that
219+
the server supports.
220+
194221
## Manual Swap Interaction
195222
The autolooper will not dispatch swaps over channels that are already included
196223
in manually dispatched swaps - for loop out, this would mean the channel is

liquidity/autoloop_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func TestAutoLoopDisabled(t *testing.T) {
2828
chanID1: chanRule,
2929
}
3030

31-
c := newAutoloopTestCtx(t, params, channels)
31+
c := newAutoloopTestCtx(t, params, channels, testRestrictions)
3232
c.start()
3333

3434
// We expect a single quote to be required for our swap on channel 1.
@@ -93,7 +93,7 @@ func TestAutoLoopEnabled(t *testing.T) {
9393
},
9494
}
9595

96-
c := newAutoloopTestCtx(t, params, channels)
96+
c := newAutoloopTestCtx(t, params, channels, testRestrictions)
9797
c.start()
9898

9999
// Calculate our maximum allowed fees and create quotes that fall within

liquidity/autoloop_testcontext_test.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ type autoloopTestCtx struct {
5858
// newAutoloopTestCtx creates a test context with custom liquidity manager
5959
// parameters and lnd channels.
6060
func newAutoloopTestCtx(t *testing.T, parameters Parameters,
61-
channels []lndclient.ChannelInfo) *autoloopTestCtx {
61+
channels []lndclient.ChannelInfo,
62+
server *Restrictions) *autoloopTestCtx {
6263

6364
// Create a mock lnd and set our expected fee rate for sweeps to our
6465
// sweep fee rate limit value.
@@ -121,11 +122,20 @@ func newAutoloopTestCtx(t *testing.T, parameters Parameters,
121122
Clock: testCtx.testClock,
122123
}
123124

125+
// SetParameters needs to make a call to our mocked restrictions call,
126+
// which will block, so we push our test values in a goroutine.
127+
done := make(chan struct{})
128+
go func() {
129+
testCtx.loopOutRestrictions <- server
130+
close(done)
131+
}()
132+
124133
// Create a manager with our test config and set our starting set of
125134
// parameters.
126135
testCtx.manager = NewManager(cfg)
127-
assert.NoError(t, testCtx.manager.SetParameters(parameters))
128-
136+
err := testCtx.manager.SetParameters(context.Background(), parameters)
137+
assert.NoError(t, err)
138+
<-done
129139
return testCtx
130140
}
131141

liquidity/liquidity.go

Lines changed: 111 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,21 @@ var (
162162

163163
// ErrZeroInFlight is returned is a zero in flight swaps value is set.
164164
ErrZeroInFlight = errors.New("max in flight swaps must be >=0")
165+
166+
// ErrMinimumExceedsMaximumAmt is returned when the minimum configured
167+
// swap amount is more than the maximum.
168+
ErrMinimumExceedsMaximumAmt = errors.New("minimum swap amount " +
169+
"exceeds maximum")
170+
171+
// ErrMaxExceedsServer is returned if the maximum swap amount set is
172+
// more than the server offers.
173+
ErrMaxExceedsServer = errors.New("maximum swap amount is more than " +
174+
"server maximum")
175+
176+
// ErrMinLessThanServer is returned if the minimum swap amount set is
177+
// less than the server minimum.
178+
ErrMinLessThanServer = errors.New("minimum swap amount is less than " +
179+
"server minimum")
165180
)
166181

167182
// Config contains the external functionality required to run the
@@ -264,6 +279,10 @@ type Parameters struct {
264279
// sweep during a fee spike.
265280
MaximumMinerFee btcutil.Amount
266281

282+
// ClientRestrictions are the restrictions placed on swap size by the
283+
// client.
284+
ClientRestrictions Restrictions
285+
267286
// ChannelRules maps a short channel ID to a rule that describes how we
268287
// would like liquidity to be managed.
269288
ChannelRules map[lnwire.ShortChannelID]*ThresholdRule
@@ -283,17 +302,19 @@ func (p Parameters) String() string {
283302
"fee rate limit: %v, sweep conf target: %v, maximum prepay: "+
284303
"%v, maximum miner fee: %v, maximum swap fee ppm: %v, maximum "+
285304
"routing fee ppm: %v, maximum prepay routing fee ppm: %v, "+
286-
"auto budget: %v, budget start: %v, max auto in flight: %v",
305+
"auto budget: %v, budget start: %v, max auto in flight: %v, "+
306+
"minimum swap size=%v, maximum swap size=%v",
287307
strings.Join(channelRules, ","), p.FailureBackOff,
288308
p.SweepFeeRateLimit, p.SweepConfTarget, p.MaximumPrepay,
289309
p.MaximumMinerFee, p.MaximumSwapFeePPM,
290310
p.MaximumRoutingFeePPM, p.MaximumPrepayRoutingFeePPM,
291-
p.AutoFeeBudget, p.AutoFeeStartDate, p.MaxAutoInFlight)
311+
p.AutoFeeBudget, p.AutoFeeStartDate, p.MaxAutoInFlight,
312+
p.ClientRestrictions.Minimum, p.ClientRestrictions.Maximum)
292313
}
293314

294315
// validate checks whether a set of parameters is valid. It takes the minimum
295316
// confirmations we allow for sweep confirmation target as a parameter.
296-
func (p Parameters) validate(minConfs int32) error {
317+
func (p Parameters) validate(minConfs int32, server *Restrictions) error {
297318
for channel, rule := range p.ChannelRules {
298319
if channel.ToUint64() == 0 {
299320
return ErrZeroChannelID
@@ -347,6 +368,47 @@ func (p Parameters) validate(minConfs int32) error {
347368
return ErrZeroInFlight
348369
}
349370

371+
err := validateRestrictions(server, &p.ClientRestrictions)
372+
if err != nil {
373+
return err
374+
}
375+
376+
return nil
377+
}
378+
379+
// validateRestrictions checks that client restrictions fall within the server's
380+
// restrictions.
381+
func validateRestrictions(server, client *Restrictions) error {
382+
zeroMin := client.Minimum == 0
383+
zeroMax := client.Maximum == 0
384+
385+
if zeroMin && zeroMax {
386+
return nil
387+
}
388+
389+
// If we have a non-zero maximum, we need to ensure it is greater than
390+
// our minimum (which is fine if min is zero), and does not exceed the
391+
// server's maximum.
392+
if !zeroMax {
393+
if client.Minimum > client.Maximum {
394+
return ErrMinimumExceedsMaximumAmt
395+
}
396+
397+
if client.Maximum > server.Maximum {
398+
return ErrMaxExceedsServer
399+
}
400+
}
401+
402+
if zeroMin {
403+
return nil
404+
}
405+
406+
// If the client set a minimum, ensure it is at least equal to the
407+
// server's limit.
408+
if client.Minimum < server.Minimum {
409+
return ErrMinLessThanServer
410+
}
411+
350412
return nil
351413
}
352414

@@ -403,8 +465,14 @@ func (m *Manager) GetParameters() Parameters {
403465

404466
// SetParameters updates our current set of parameters if the new parameters
405467
// provided are valid.
406-
func (m *Manager) SetParameters(params Parameters) error {
407-
if err := params.validate(m.cfg.MinimumConfirmations); err != nil {
468+
func (m *Manager) SetParameters(ctx context.Context, params Parameters) error {
469+
restrictions, err := m.cfg.LoopOutRestrictions(ctx)
470+
if err != nil {
471+
return err
472+
}
473+
474+
err = params.validate(m.cfg.MinimumConfirmations, restrictions)
475+
if err != nil {
408476
return err
409477
}
410478

@@ -517,8 +585,9 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoOut bool) (
517585
return nil, nil
518586
}
519587

520-
// Get the current server side restrictions.
521-
outRestrictions, err := m.cfg.LoopOutRestrictions(ctx)
588+
// Get the current server side restrictions, combined with the client
589+
// set restrictions, if any.
590+
outRestrictions, err := m.getLoopOutRestrictions(ctx)
522591
if err != nil {
523592
return nil, err
524593
}
@@ -674,6 +743,41 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoOut bool) (
674743
return inBudget, nil
675744
}
676745

746+
// getLoopOutRestrictions queries the server for its latest swap size
747+
// restrictions, validates client restrictions (if present) against these
748+
// values and merges the client's custom requirements with the server's limits
749+
// to produce a single set of limitations for our swap.
750+
func (m *Manager) getLoopOutRestrictions(ctx context.Context) (*Restrictions,
751+
error) {
752+
753+
restrictions, err := m.cfg.LoopOutRestrictions(ctx)
754+
if err != nil {
755+
return nil, err
756+
}
757+
758+
// It is possible that the server has updated its restrictions since
759+
// we validated our client restrictions, so we validate again to ensure
760+
// that our restrictions are within the server's bounds.
761+
err = validateRestrictions(restrictions, &m.params.ClientRestrictions)
762+
if err != nil {
763+
return nil, err
764+
}
765+
766+
// If our minimum is more than the server's minimum, we set it.
767+
if m.params.ClientRestrictions.Minimum > restrictions.Minimum {
768+
restrictions.Minimum = m.params.ClientRestrictions.Minimum
769+
}
770+
771+
// If our maximum set and is less than the server's maximum, we set it.
772+
if m.params.ClientRestrictions.Maximum != 0 &&
773+
m.params.ClientRestrictions.Maximum < restrictions.Maximum {
774+
775+
restrictions.Maximum = m.params.ClientRestrictions.Maximum
776+
}
777+
778+
return restrictions, nil
779+
}
780+
677781
// makeLoopOutRequest creates a loop out request from a suggestion. Since we
678782
// do not get any information about our off-chain routing fees when we request
679783
// a quote, we just set our prepay and route maximum fees directly from the

0 commit comments

Comments
 (0)