Skip to content

Commit 9a97667

Browse files
committed
liquidity: add easy autoloop test
1 parent fad9f40 commit 9a97667

File tree

2 files changed

+262
-18
lines changed

2 files changed

+262
-18
lines changed

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,

liquidity/autoloop_testcontext_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ import (
1919
"github.com/stretchr/testify/require"
2020
)
2121

22+
const (
23+
defaultEventuallyTimeout = time.Second * 45
24+
defaultEventuallyInterval = time.Millisecond * 100
25+
)
26+
2227
type autoloopTestCtx struct {
2328
t *testing.T
2429
manager *Manager
@@ -272,6 +277,15 @@ type autoloopStep struct {
272277
keepDestAddr bool
273278
}
274279

280+
type easyAutoloopStep struct {
281+
minAmt btcutil.Amount
282+
maxAmt btcutil.Amount
283+
existingOut []*loopdb.LoopOut
284+
existingIn []*loopdb.LoopIn
285+
quotesOut []quoteRequestResp
286+
expectedOut []loopOutRequestResp
287+
}
288+
275289
// autoloop walks our test context through the process of triggering our
276290
// autoloop functionality, providing mocked values as required. The set of
277291
// quotes provided indicates that we expect swap suggestions to be made (since
@@ -325,6 +339,50 @@ func (c *autoloopTestCtx) autoloop(step *autoloopStep) {
325339

326340
require.True(c.t, c.matchLoopOuts(step.expectedOut, step.keepDestAddr))
327341
require.True(c.t, c.matchLoopIns(step.expectedIn))
342+
343+
require.Eventuallyf(c.t, func() bool {
344+
return c.manager.numActiveStickyLoops() == 0
345+
}, defaultEventuallyTimeout, defaultEventuallyInterval, "failed to"+
346+
" wait for sticky loop counter")
347+
}
348+
349+
// easyautoloop walks our test context through the process of triggering our
350+
// easy autoloop functionality, providing mocked values as required. The number
351+
// of values needed to mock easy autoloop are less than standard autoloop as the
352+
// goal of easy autoloop is to simplify its usage.
353+
func (c *autoloopTestCtx) easyautoloop(step *easyAutoloopStep, noop bool) {
354+
// Tick our autoloop ticker to force assessing whether we want to loop.
355+
c.manager.cfg.AutoloopTicker.Force <- testTime
356+
357+
// Provide the liquidity manager with our desired existing set of swaps.
358+
c.loopOuts <- step.existingOut
359+
c.loopIns <- step.existingIn
360+
361+
// If easy autoloop is not meant to be triggered we skip sending the
362+
// mock response for restrictions, as this is never called.
363+
if !noop {
364+
// Send a mocked response from the server with the swap size limits.
365+
c.loopOutRestrictions <- NewRestrictions(step.minAmt, step.maxAmt)
366+
}
367+
368+
for _, expected := range step.quotesOut {
369+
request := <-c.quoteRequest
370+
require.Equal(
371+
c.t, expected.request.Amount, request.Amount,
372+
)
373+
374+
c.quotes <- expected.quote
375+
}
376+
377+
for _, expected := range step.expectedOut {
378+
actual := <-c.outRequest
379+
380+
require.Equal(c.t, expected.request.Amount, actual.Amount)
381+
require.Equal(
382+
c.t, expected.request.OutgoingChanSet,
383+
actual.OutgoingChanSet,
384+
)
385+
}
328386
}
329387

330388
// matchLoopOuts checks that the actual loop out requests we got match the

0 commit comments

Comments
 (0)