44 "testing"
55 "time"
66
7+ "github.com/btcsuite/btcutil"
78 "github.com/lightninglabs/lndclient"
89 "github.com/lightninglabs/loop"
910 "github.com/lightninglabs/loop/labels"
@@ -12,6 +13,7 @@ import (
1213 "github.com/lightninglabs/loop/test"
1314 "github.com/lightningnetwork/lnd/lntypes"
1415 "github.com/lightningnetwork/lnd/lnwire"
16+ "github.com/lightningnetwork/lnd/routing/route"
1517)
1618
1719// TestAutoLoopDisabled tests the case where we need to perform a swap, but
@@ -266,6 +268,161 @@ func TestAutoLoopEnabled(t *testing.T) {
266268 c .stop ()
267269}
268270
271+ // TestCompositeRules tests the case where we have rules set on a per peer
272+ // and per channel basis, and perform swaps for both targets.
273+ func TestCompositeRules (t * testing.T ) {
274+ defer test .Guard (t )()
275+
276+ // Setup our channels so that we have two channels with peer 2, and
277+ // a single channel with peer 1.
278+ channel3 := lndclient.ChannelInfo {
279+ ChannelID : chanID3 .ToUint64 (),
280+ PubKeyBytes : peer2 ,
281+ LocalBalance : 10000 ,
282+ RemoteBalance : 0 ,
283+ Capacity : 10000 ,
284+ }
285+
286+ channels := []lndclient.ChannelInfo {
287+ channel1 , channel2 , channel3 ,
288+ }
289+
290+ // Create a set of parameters with autoloop enabled, set our budget to
291+ // a value that will easily accommodate our two swaps.
292+ params := Parameters {
293+ Autoloop : true ,
294+ AutoFeeBudget : 100000 ,
295+ AutoFeeStartDate : testTime ,
296+ MaxAutoInFlight : 2 ,
297+ FailureBackOff : time .Hour ,
298+ SweepFeeRateLimit : 20000 ,
299+ SweepConfTarget : 10 ,
300+ MaximumPrepay : 20000 ,
301+ MaximumSwapFeePPM : 1000 ,
302+ MaximumRoutingFeePPM : 1000 ,
303+ MaximumPrepayRoutingFeePPM : 1000 ,
304+ MaximumMinerFee : 20000 ,
305+ ChannelRules : map [lnwire.ShortChannelID ]* ThresholdRule {
306+ chanID1 : chanRule ,
307+ },
308+ PeerRules : map [route.Vertex ]* ThresholdRule {
309+ peer2 : chanRule ,
310+ },
311+ }
312+
313+ c := newAutoloopTestCtx (t , params , channels , testRestrictions )
314+ c .start ()
315+
316+ // Calculate our maximum allowed fees and create quotes that fall within
317+ // our budget.
318+ var (
319+ // Create a quote for our peer level swap that is within
320+ // our budget, with an amount which would balance the peer
321+ /// across all of its channels.
322+ peerAmount = btcutil .Amount (15000 )
323+ maxPeerSwapFee = ppmToSat (peerAmount , params .MaximumSwapFeePPM )
324+
325+ peerSwapQuote = & loop.LoopOutQuote {
326+ SwapFee : maxPeerSwapFee ,
327+ PrepayAmount : params .MaximumPrepay - 20 ,
328+ }
329+
330+ peerSwapQuoteRequest = & loop.LoopOutQuoteRequest {
331+ Amount : peerAmount ,
332+ SweepConfTarget : params .SweepConfTarget ,
333+ }
334+
335+ maxPeerRouteFee = ppmToSat (
336+ peerAmount , params .MaximumRoutingFeePPM ,
337+ )
338+
339+ peerSwap = & loop.OutRequest {
340+ Amount : peerAmount ,
341+ MaxSwapRoutingFee : maxPeerRouteFee ,
342+ MaxPrepayRoutingFee : ppmToSat (
343+ peerSwapQuote .PrepayAmount ,
344+ params .MaximumPrepayRoutingFeePPM ,
345+ ),
346+ MaxSwapFee : peerSwapQuote .SwapFee ,
347+ MaxPrepayAmount : peerSwapQuote .PrepayAmount ,
348+ MaxMinerFee : params .MaximumMinerFee ,
349+ SweepConfTarget : params .SweepConfTarget ,
350+ OutgoingChanSet : loopdb.ChannelSet {
351+ chanID2 .ToUint64 (), chanID3 .ToUint64 (),
352+ },
353+ Label : labels .AutoloopLabel (swap .TypeOut ),
354+ Initiator : autoloopSwapInitiator ,
355+ }
356+ // Create a quote for our single channel swap that is within
357+ // our budget.
358+ chanAmount = chan1Rec .Amount
359+ maxChanSwapFee = ppmToSat (chanAmount , params .MaximumSwapFeePPM )
360+
361+ channelSwapQuote = & loop.LoopOutQuote {
362+ SwapFee : maxChanSwapFee ,
363+ PrepayAmount : params .MaximumPrepay - 10 ,
364+ }
365+
366+ chanSwapQuoteRequest = & loop.LoopOutQuoteRequest {
367+ Amount : chanAmount ,
368+ SweepConfTarget : params .SweepConfTarget ,
369+ }
370+
371+ maxChanRouteFee = ppmToSat (
372+ chanAmount , params .MaximumRoutingFeePPM ,
373+ )
374+
375+ chanSwap = & loop.OutRequest {
376+ Amount : chanAmount ,
377+ MaxSwapRoutingFee : maxChanRouteFee ,
378+ MaxPrepayRoutingFee : ppmToSat (
379+ channelSwapQuote .PrepayAmount ,
380+ params .MaximumPrepayRoutingFeePPM ,
381+ ),
382+ MaxSwapFee : channelSwapQuote .SwapFee ,
383+ MaxPrepayAmount : channelSwapQuote .PrepayAmount ,
384+ MaxMinerFee : params .MaximumMinerFee ,
385+ SweepConfTarget : params .SweepConfTarget ,
386+ OutgoingChanSet : loopdb.ChannelSet {chanID1 .ToUint64 ()},
387+ Label : labels .AutoloopLabel (swap .TypeOut ),
388+ Initiator : autoloopSwapInitiator ,
389+ }
390+ quotes = []quoteRequestResp {
391+ {
392+ request : peerSwapQuoteRequest ,
393+ quote : peerSwapQuote ,
394+ },
395+ {
396+ request : chanSwapQuoteRequest ,
397+ quote : channelSwapQuote ,
398+ },
399+ }
400+
401+ loopOuts = []loopOutRequestResp {
402+ {
403+ request : peerSwap ,
404+ response : & loop.LoopOutSwapInfo {
405+ SwapHash : lntypes.Hash {2 },
406+ },
407+ },
408+ {
409+ request : chanSwap ,
410+ response : & loop.LoopOutSwapInfo {
411+ SwapHash : lntypes.Hash {1 },
412+ },
413+ },
414+ }
415+ )
416+
417+ // Tick our autolooper with no existing swaps, we expect a loop out
418+ // swap to be dispatched for each of our rules. We set our server side
419+ // maximum to be greater than the swap amount for our peer swap (which
420+ // is the larger of the two swaps).
421+ c .autoloop (1 , peerAmount + 1 , nil , quotes , loopOuts )
422+
423+ c .stop ()
424+ }
425+
269426// existingSwapFromRequest is a helper function which returns the db
270427// representation of a loop out request with the event set provided.
271428func existingSwapFromRequest (request * loop.OutRequest , initTime time.Time ,
0 commit comments