Skip to content

Commit cb8975d

Browse files
authored
Merge pull request #567 from GeorgeTsagk/autoloop-rework
Autoloop rework -- easy autoloop
2 parents 0a19485 + 5edc39d commit cb8975d

11 files changed

+846
-208
lines changed

cmd/loop/liquidity.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,17 @@ var setParamsCommand = cli.Command{
324324
Usage: "the confirmation target for loop in on-chain " +
325325
"htlcs",
326326
},
327+
cli.BoolFlag{
328+
Name: "easyautoloop",
329+
Usage: "set to true to enable easy autoloop, which " +
330+
"will automatically dispatch swaps in order " +
331+
"to meet the target local balance",
332+
},
333+
cli.Uint64Flag{
334+
Name: "localbalancesat",
335+
Usage: "the target size of total local balance in " +
336+
"satoshis, used by easy autoloop",
337+
},
327338
},
328339
Action: setParams,
329340
}
@@ -465,6 +476,17 @@ func setParams(ctx *cli.Context) error {
465476
flagSet = true
466477
}
467478

479+
if ctx.IsSet("easyautoloop") {
480+
params.EasyAutoloop = ctx.Bool("easyautoloop")
481+
flagSet = true
482+
}
483+
484+
if ctx.IsSet("localbalancesat") {
485+
params.EasyAutoloopLocalTargetSat =
486+
ctx.Uint64("localbalancesat")
487+
flagSet = true
488+
}
489+
468490
if !flagSet {
469491
return fmt.Errorf("at least one flag required to set params")
470492
}

docs/autoloop.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,24 @@ following command:
99
loop setparams --autoloop=true
1010
```
1111

12+
## Easy Autoloop
13+
14+
If you don't want to bother with setting up specific rules for each of your
15+
channels and peers you can use easy autoloop. This mode of autoloop requires
16+
for you to set only the overall channel balance that you don't wish to exceed
17+
on your lightning node. For example, if you want to keep your node's total
18+
channel balance below 1 million satoshis you can set the following
19+
```
20+
loop setparams --autoloop=true --easyautoloop=true --localbalancesat=1000000
21+
```
22+
23+
This will automatically start dispatching loop-outs whenever you exceed total
24+
channel balance of 1M sats. Keep in mind that on first time use this will use
25+
the default budget parameters. If you wish to configure a custom budget you can
26+
find more info in the [Budget](#budget) section.
27+
28+
## Liquidity Rules
29+
1230
At present, autoloop can be configured to either acquire incoming liquidity
1331
using loop out, or acquire outgoing liquidity using loop in. It cannot support
1432
automated swaps in both directions. To set the type of swaps you would like

liquidity/autoloop_test.go

Lines changed: 204 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ func TestAutoLoopEnabled(t *testing.T) {
207207
Events: []*loopdb.LoopEvent{
208208
{
209209
SwapStateData: loopdb.SwapStateData{
210-
State: loopdb.StateInitiated,
210+
State: loopdb.StateSuccess,
211211
},
212212
},
213213
},
@@ -472,7 +472,7 @@ func TestAutoloopAddress(t *testing.T) {
472472
Events: []*loopdb.LoopEvent{
473473
{
474474
SwapStateData: loopdb.SwapStateData{
475-
State: loopdb.StateHtlcPublished,
475+
State: loopdb.StateSuccess,
476476
},
477477
},
478478
},
@@ -647,7 +647,7 @@ func TestCompositeRules(t *testing.T) {
647647
Events: []*loopdb.LoopEvent{
648648
{
649649
SwapStateData: loopdb.SwapStateData{
650-
State: loopdb.StateHtlcPublished,
650+
State: loopdb.StateSuccess,
651651
},
652652
},
653653
},
@@ -984,7 +984,7 @@ func TestAutoloopBothTypes(t *testing.T) {
984984
Events: []*loopdb.LoopEvent{
985985
{
986986
SwapStateData: loopdb.SwapStateData{
987-
State: loopdb.StateHtlcPublished,
987+
State: loopdb.SwapState(loopdb.StateSuccess),
988988
},
989989
},
990990
},
@@ -1162,15 +1162,28 @@ func TestAutoLoopRecurringBudget(t *testing.T) {
11621162
},
11631163
},
11641164
}
1165+
1166+
singleLoopOut = &loopdb.LoopOut{
1167+
Loop: loopdb.Loop{
1168+
Events: []*loopdb.LoopEvent{
1169+
{
1170+
SwapStateData: loopdb.SwapStateData{
1171+
State: loopdb.SwapState(loopdb.StateSuccess),
1172+
},
1173+
},
1174+
},
1175+
},
1176+
}
11651177
)
11661178

11671179
// Tick our autolooper with no existing swaps, we expect a loop out
11681180
// swap to be dispatched on first channel.
11691181
step := &autoloopStep{
1170-
minAmt: 1,
1171-
maxAmt: amt + 1,
1172-
quotesOut: quotes1,
1173-
expectedOut: loopOuts1,
1182+
minAmt: 1,
1183+
maxAmt: amt + 1,
1184+
quotesOut: quotes1,
1185+
expectedOut: loopOuts1,
1186+
existingOutSingle: singleLoopOut,
11741187
}
11751188
c.autoloop(step)
11761189

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

11901203
step = &autoloopStep{
1191-
minAmt: 1,
1192-
maxAmt: amt + 1,
1193-
quotesOut: quotes2,
1194-
existingOut: existing,
1195-
expectedOut: nil,
1204+
minAmt: 1,
1205+
maxAmt: amt + 1,
1206+
quotesOut: quotes2,
1207+
existingOut: existing,
1208+
expectedOut: nil,
1209+
existingOutSingle: singleLoopOut,
11961210
}
11971211
// Tick again, we should expect no loop outs because our budget would be
11981212
// exceeded.
@@ -1222,11 +1236,12 @@ func TestAutoLoopRecurringBudget(t *testing.T) {
12221236
c.testClock.SetTime(testTime.Add(time.Hour * 25))
12231237

12241238
step = &autoloopStep{
1225-
minAmt: 1,
1226-
maxAmt: amt + 1,
1227-
quotesOut: quotes2,
1228-
existingOut: existing2,
1229-
expectedOut: loopOuts2,
1239+
minAmt: 1,
1240+
maxAmt: amt + 1,
1241+
quotesOut: quotes2,
1242+
existingOut: existing2,
1243+
expectedOut: loopOuts2,
1244+
existingOutSingle: singleLoopOut,
12301245
}
12311246

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

1253+
// TestEasyAutoloop tests that the easy autoloop logic works as expected. This
1254+
// involves testing that channels are correctly selected and that the balance
1255+
// target is successfully met.
1256+
func TestEasyAutoloop(t *testing.T) {
1257+
defer test.Guard(t)
1258+
1259+
// We need to change the default channels we use for tests so that they
1260+
// have different local balances in order to know which one is going to
1261+
// be selected by easy autoloop.
1262+
easyChannel1 := lndclient.ChannelInfo{
1263+
Active: true,
1264+
ChannelID: chanID1.ToUint64(),
1265+
PubKeyBytes: peer1,
1266+
LocalBalance: 95000,
1267+
RemoteBalance: 0,
1268+
Capacity: 100000,
1269+
}
1270+
1271+
easyChannel2 := lndclient.ChannelInfo{
1272+
Active: true,
1273+
ChannelID: chanID1.ToUint64(),
1274+
PubKeyBytes: peer1,
1275+
LocalBalance: 75000,
1276+
RemoteBalance: 0,
1277+
Capacity: 100000,
1278+
}
1279+
1280+
var (
1281+
channels = []lndclient.ChannelInfo{
1282+
easyChannel1, easyChannel2,
1283+
}
1284+
1285+
params = Parameters{
1286+
Autoloop: true,
1287+
AutoFeeBudget: 36000,
1288+
AutoFeeRefreshPeriod: time.Hour * 3,
1289+
AutoloopBudgetLastRefresh: testBudgetStart,
1290+
MaxAutoInFlight: 2,
1291+
FailureBackOff: time.Hour,
1292+
SweepConfTarget: 10,
1293+
HtlcConfTarget: defaultHtlcConfTarget,
1294+
EasyAutoloop: true,
1295+
EasyAutoloopTarget: 75000,
1296+
FeeLimit: defaultFeePortion(),
1297+
}
1298+
)
1299+
1300+
c := newAutoloopTestCtx(t, params, channels, testRestrictions)
1301+
c.start()
1302+
1303+
var (
1304+
maxAmt = 50000
1305+
1306+
chan1Swap = &loop.OutRequest{
1307+
Amount: btcutil.Amount(maxAmt),
1308+
OutgoingChanSet: loopdb.ChannelSet{easyChannel1.ChannelID},
1309+
Label: labels.AutoloopLabel(swap.TypeOut),
1310+
Initiator: autoloopSwapInitiator,
1311+
}
1312+
1313+
quotesOut1 = []quoteRequestResp{
1314+
{
1315+
request: &loop.LoopOutQuoteRequest{
1316+
Amount: btcutil.Amount(maxAmt),
1317+
},
1318+
quote: &loop.LoopOutQuote{
1319+
SwapFee: 1,
1320+
PrepayAmount: 1,
1321+
MinerFee: 1,
1322+
},
1323+
},
1324+
}
1325+
1326+
loopOut1 = []loopOutRequestResp{
1327+
{
1328+
request: chan1Swap,
1329+
response: &loop.LoopOutSwapInfo{
1330+
SwapHash: lntypes.Hash{1},
1331+
},
1332+
},
1333+
}
1334+
)
1335+
1336+
// We expected one max size swap to be dispatched on our channel with
1337+
// the biggest local balance.
1338+
step := &easyAutoloopStep{
1339+
minAmt: 1,
1340+
maxAmt: 50000,
1341+
quotesOut: quotesOut1,
1342+
expectedOut: loopOut1,
1343+
}
1344+
1345+
c.easyautoloop(step, false)
1346+
c.stop()
1347+
1348+
// In order to reflect the change on the channel balances we create a
1349+
// new context and restart the autolooper.
1350+
easyChannel1.LocalBalance -= chan1Swap.Amount
1351+
channels = []lndclient.ChannelInfo{
1352+
easyChannel1, easyChannel2,
1353+
}
1354+
1355+
c = newAutoloopTestCtx(t, params, channels, testRestrictions)
1356+
c.start()
1357+
1358+
var (
1359+
amt2 = 45_000
1360+
1361+
chan2Swap = &loop.OutRequest{
1362+
Amount: btcutil.Amount(amt2),
1363+
OutgoingChanSet: loopdb.ChannelSet{easyChannel2.ChannelID},
1364+
Label: labels.AutoloopLabel(swap.TypeOut),
1365+
Initiator: autoloopSwapInitiator,
1366+
}
1367+
1368+
quotesOut2 = []quoteRequestResp{
1369+
{
1370+
request: &loop.LoopOutQuoteRequest{
1371+
Amount: btcutil.Amount(amt2),
1372+
},
1373+
quote: &loop.LoopOutQuote{
1374+
SwapFee: 1,
1375+
PrepayAmount: 1,
1376+
MinerFee: 1,
1377+
},
1378+
},
1379+
}
1380+
1381+
loopOut2 = []loopOutRequestResp{
1382+
{
1383+
request: chan2Swap,
1384+
response: &loop.LoopOutSwapInfo{
1385+
SwapHash: lntypes.Hash{1},
1386+
},
1387+
},
1388+
}
1389+
)
1390+
1391+
// We expect a swap of size 45_000 to be dispatched in order to meet the
1392+
// defined target of 75_000.
1393+
step = &easyAutoloopStep{
1394+
minAmt: 1,
1395+
maxAmt: 50000,
1396+
quotesOut: quotesOut2,
1397+
expectedOut: loopOut2,
1398+
}
1399+
1400+
c.easyautoloop(step, false)
1401+
c.stop()
1402+
1403+
// In order to reflect the change on the channel balances we create a
1404+
// new context and restart the autolooper.
1405+
easyChannel2.LocalBalance -= btcutil.Amount(amt2)
1406+
channels = []lndclient.ChannelInfo{
1407+
easyChannel1, easyChannel2,
1408+
}
1409+
1410+
c = newAutoloopTestCtx(t, params, channels, testRestrictions)
1411+
c.start()
1412+
1413+
// We have met the target of 75_000 so we don't expect any action from
1414+
// easy autoloop. That's why we set noop to true in the call below.
1415+
step = &easyAutoloopStep{
1416+
minAmt: 1,
1417+
maxAmt: 50000,
1418+
}
1419+
1420+
c.easyautoloop(step, true)
1421+
c.stop()
1422+
}
1423+
12381424
// existingSwapFromRequest is a helper function which returns the db
12391425
// representation of a loop out request with the event set provided.
12401426
func existingSwapFromRequest(request *loop.OutRequest, initTime time.Time,

0 commit comments

Comments
 (0)