Skip to content
Merged
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
22 changes: 22 additions & 0 deletions cmd/loop/liquidity.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,17 @@ var setParamsCommand = cli.Command{
Usage: "the confirmation target for loop in on-chain " +
"htlcs",
},
cli.BoolFlag{
Name: "easyautoloop",
Usage: "set to true to enable easy autoloop, which " +
"will automatically dispatch swaps in order " +
"to meet the target local balance",
},
cli.Uint64Flag{
Name: "localbalancesat",
Usage: "the target size of total local balance in " +
"satoshis, used by easy autoloop",
},
},
Action: setParams,
}
Expand Down Expand Up @@ -465,6 +476,17 @@ func setParams(ctx *cli.Context) error {
flagSet = true
}

if ctx.IsSet("easyautoloop") {
params.EasyAutoloop = ctx.Bool("easyautoloop")
flagSet = true
}

if ctx.IsSet("localbalancesat") {
params.EasyAutoloopLocalTargetSat =
ctx.Uint64("localbalancesat")
flagSet = true
}

if !flagSet {
return fmt.Errorf("at least one flag required to set params")
}
Expand Down
18 changes: 18 additions & 0 deletions docs/autoloop.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,24 @@ following command:
loop setparams --autoloop=true
```

## Easy Autoloop

If you don't want to bother with setting up specific rules for each of your
channels and peers you can use easy autoloop. This mode of autoloop requires
for you to set only the overall channel balance that you don't wish to exceed
on your lightning node. For example, if you want to keep your node's total
channel balance below 1 million satoshis you can set the following
```
loop setparams --autoloop=true --easyautoloop=true --localbalancesat=1000000
```

This will automatically start dispatching loop-outs whenever you exceed total
channel balance of 1M sats. Keep in mind that on first time use this will use
the default budget parameters. If you wish to configure a custom budget you can
find more info in the [Budget](#budget) section.

## Liquidity Rules

At present, autoloop can be configured to either acquire incoming liquidity
using loop out, or acquire outgoing liquidity using loop in. It cannot support
automated swaps in both directions. To set the type of swaps you would like
Expand Down
222 changes: 204 additions & 18 deletions liquidity/autoloop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ func TestAutoLoopEnabled(t *testing.T) {
Events: []*loopdb.LoopEvent{
{
SwapStateData: loopdb.SwapStateData{
State: loopdb.StateInitiated,
State: loopdb.StateSuccess,
},
},
},
Expand Down Expand Up @@ -472,7 +472,7 @@ func TestAutoloopAddress(t *testing.T) {
Events: []*loopdb.LoopEvent{
{
SwapStateData: loopdb.SwapStateData{
State: loopdb.StateHtlcPublished,
State: loopdb.StateSuccess,
},
},
},
Expand Down Expand Up @@ -647,7 +647,7 @@ func TestCompositeRules(t *testing.T) {
Events: []*loopdb.LoopEvent{
{
SwapStateData: loopdb.SwapStateData{
State: loopdb.StateHtlcPublished,
State: loopdb.StateSuccess,
},
},
},
Expand Down Expand Up @@ -984,7 +984,7 @@ func TestAutoloopBothTypes(t *testing.T) {
Events: []*loopdb.LoopEvent{
{
SwapStateData: loopdb.SwapStateData{
State: loopdb.StateHtlcPublished,
State: loopdb.SwapState(loopdb.StateSuccess),
},
},
},
Expand Down Expand Up @@ -1162,15 +1162,28 @@ func TestAutoLoopRecurringBudget(t *testing.T) {
},
},
}

singleLoopOut = &loopdb.LoopOut{
Loop: loopdb.Loop{
Events: []*loopdb.LoopEvent{
{
SwapStateData: loopdb.SwapStateData{
State: loopdb.SwapState(loopdb.StateSuccess),
},
},
},
},
}
)

// Tick our autolooper with no existing swaps, we expect a loop out
// swap to be dispatched on first channel.
step := &autoloopStep{
minAmt: 1,
maxAmt: amt + 1,
quotesOut: quotes1,
expectedOut: loopOuts1,
minAmt: 1,
maxAmt: amt + 1,
quotesOut: quotes1,
expectedOut: loopOuts1,
existingOutSingle: singleLoopOut,
}
c.autoloop(step)

Expand All @@ -1188,11 +1201,12 @@ func TestAutoLoopRecurringBudget(t *testing.T) {
}

step = &autoloopStep{
minAmt: 1,
maxAmt: amt + 1,
quotesOut: quotes2,
existingOut: existing,
expectedOut: nil,
minAmt: 1,
maxAmt: amt + 1,
quotesOut: quotes2,
existingOut: existing,
expectedOut: nil,
existingOutSingle: singleLoopOut,
}
// Tick again, we should expect no loop outs because our budget would be
// exceeded.
Expand Down Expand Up @@ -1222,11 +1236,12 @@ func TestAutoLoopRecurringBudget(t *testing.T) {
c.testClock.SetTime(testTime.Add(time.Hour * 25))

step = &autoloopStep{
minAmt: 1,
maxAmt: amt + 1,
quotesOut: quotes2,
existingOut: existing2,
expectedOut: loopOuts2,
minAmt: 1,
maxAmt: amt + 1,
quotesOut: quotes2,
existingOut: existing2,
expectedOut: loopOuts2,
existingOutSingle: singleLoopOut,
}

// Tick again, we should expect a loop out to occur on the 2nd channel.
Expand All @@ -1235,6 +1250,177 @@ func TestAutoLoopRecurringBudget(t *testing.T) {
c.stop()
}

// TestEasyAutoloop tests that the easy autoloop logic works as expected. This
// involves testing that channels are correctly selected and that the balance
// target is successfully met.
func TestEasyAutoloop(t *testing.T) {
defer test.Guard(t)

// We need to change the default channels we use for tests so that they
// have different local balances in order to know which one is going to
// be selected by easy autoloop.
easyChannel1 := lndclient.ChannelInfo{
Active: true,
ChannelID: chanID1.ToUint64(),
PubKeyBytes: peer1,
LocalBalance: 95000,
RemoteBalance: 0,
Capacity: 100000,
}

easyChannel2 := lndclient.ChannelInfo{
Active: true,
ChannelID: chanID1.ToUint64(),
PubKeyBytes: peer1,
LocalBalance: 75000,
RemoteBalance: 0,
Capacity: 100000,
}

var (
channels = []lndclient.ChannelInfo{
easyChannel1, easyChannel2,
}

params = Parameters{
Autoloop: true,
AutoFeeBudget: 36000,
AutoFeeRefreshPeriod: time.Hour * 3,
AutoloopBudgetLastRefresh: testBudgetStart,
MaxAutoInFlight: 2,
FailureBackOff: time.Hour,
SweepConfTarget: 10,
HtlcConfTarget: defaultHtlcConfTarget,
EasyAutoloop: true,
EasyAutoloopTarget: 75000,
FeeLimit: defaultFeePortion(),
}
)

c := newAutoloopTestCtx(t, params, channels, testRestrictions)
c.start()

var (
maxAmt = 50000

chan1Swap = &loop.OutRequest{
Amount: btcutil.Amount(maxAmt),
OutgoingChanSet: loopdb.ChannelSet{easyChannel1.ChannelID},
Label: labels.AutoloopLabel(swap.TypeOut),
Initiator: autoloopSwapInitiator,
}

quotesOut1 = []quoteRequestResp{
{
request: &loop.LoopOutQuoteRequest{
Amount: btcutil.Amount(maxAmt),
},
quote: &loop.LoopOutQuote{
SwapFee: 1,
PrepayAmount: 1,
MinerFee: 1,
},
},
}

loopOut1 = []loopOutRequestResp{
{
request: chan1Swap,
response: &loop.LoopOutSwapInfo{
SwapHash: lntypes.Hash{1},
},
},
}
)

// We expected one max size swap to be dispatched on our channel with
// the biggest local balance.
step := &easyAutoloopStep{
minAmt: 1,
maxAmt: 50000,
quotesOut: quotesOut1,
expectedOut: loopOut1,
}

c.easyautoloop(step, false)
c.stop()

// In order to reflect the change on the channel balances we create a
// new context and restart the autolooper.
easyChannel1.LocalBalance -= chan1Swap.Amount
channels = []lndclient.ChannelInfo{
easyChannel1, easyChannel2,
}

c = newAutoloopTestCtx(t, params, channels, testRestrictions)
c.start()

var (
amt2 = 45_000

chan2Swap = &loop.OutRequest{
Amount: btcutil.Amount(amt2),
OutgoingChanSet: loopdb.ChannelSet{easyChannel2.ChannelID},
Label: labels.AutoloopLabel(swap.TypeOut),
Initiator: autoloopSwapInitiator,
}

quotesOut2 = []quoteRequestResp{
{
request: &loop.LoopOutQuoteRequest{
Amount: btcutil.Amount(amt2),
},
quote: &loop.LoopOutQuote{
SwapFee: 1,
PrepayAmount: 1,
MinerFee: 1,
},
},
}

loopOut2 = []loopOutRequestResp{
{
request: chan2Swap,
response: &loop.LoopOutSwapInfo{
SwapHash: lntypes.Hash{1},
},
},
}
)

// We expect a swap of size 45_000 to be dispatched in order to meet the
// defined target of 75_000.
step = &easyAutoloopStep{
minAmt: 1,
maxAmt: 50000,
quotesOut: quotesOut2,
expectedOut: loopOut2,
}

c.easyautoloop(step, false)
c.stop()

// In order to reflect the change on the channel balances we create a
// new context and restart the autolooper.
easyChannel2.LocalBalance -= btcutil.Amount(amt2)
channels = []lndclient.ChannelInfo{
easyChannel1, easyChannel2,
}

c = newAutoloopTestCtx(t, params, channels, testRestrictions)
c.start()

// We have met the target of 75_000 so we don't expect any action from
// easy autoloop. That's why we set noop to true in the call below.
step = &easyAutoloopStep{
minAmt: 1,
maxAmt: 50000,
}

c.easyautoloop(step, true)
c.stop()
}

// existingSwapFromRequest is a helper function which returns the db
// representation of a loop out request with the event set provided.
func existingSwapFromRequest(request *loop.OutRequest, initTime time.Time,
Expand Down
Loading