Skip to content

Commit 3f46ae5

Browse files
committed
liquidity: add peer-level liquidity rules to allow aggregate management
We add 'peer-level' rules to allow assessment of liquidity on a per-peer level, rather than on an individual channel basis. No overlap is allowed with the existing set of channel rules because this could lead to contradictory rules.
1 parent d1f121c commit 3f46ae5

File tree

4 files changed

+506
-63
lines changed

4 files changed

+506
-63
lines changed

liquidity/autoloop_test.go

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
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.
271428
func existingSwapFromRequest(request *loop.OutRequest, initTime time.Time,

liquidity/balances.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ type balances struct {
1919
// outgoing is the local balance of the channel.
2020
outgoing btcutil.Amount
2121

22-
// channelID is the channel that has these balances.
23-
channelID lnwire.ShortChannelID
22+
// channels is the channel that has these balances represent. This may
23+
// be more than one channel in the case where we are examining a peer's
24+
// liquidity as a whole.
25+
channels []lnwire.ShortChannelID
2426

2527
// pubkey is the public key of the peer we have this balances set with.
2628
pubkey route.Vertex
@@ -29,10 +31,12 @@ type balances struct {
2931
// newBalances creates a balances struct from lndclient channel information.
3032
func newBalances(info lndclient.ChannelInfo) *balances {
3133
return &balances{
32-
capacity: info.Capacity,
33-
incoming: info.RemoteBalance,
34-
outgoing: info.LocalBalance,
35-
channelID: lnwire.NewShortChanIDFromInt(info.ChannelID),
36-
pubkey: info.PubKeyBytes,
34+
capacity: info.Capacity,
35+
incoming: info.RemoteBalance,
36+
outgoing: info.LocalBalance,
37+
channels: []lnwire.ShortChannelID{
38+
lnwire.NewShortChanIDFromInt(info.ChannelID),
39+
},
40+
pubkey: info.PubKeyBytes,
3741
}
3842
}

0 commit comments

Comments
 (0)