diff --git a/liquidity/interface.go b/liquidity/interface.go index 4c695ad..c05091c 100644 --- a/liquidity/interface.go +++ b/liquidity/interface.go @@ -5,6 +5,7 @@ import ( "github.com/lightninglabs/loop" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" ) // FeeLimit is an interface implemented by different strategies for limiting @@ -43,8 +44,17 @@ type swapSuggestion interface { // channels returns the set of channels involved in the swap. channels() []lnwire.ShortChannelID + + // peers returns the set of peers involved in the swap, taking a map + // of known channel IDs to peers as an argument so that channel peers + // can be looked up. + peers(knownChans map[uint64]route.Vertex) []route.Vertex } +// Compile-time assertion that loopOutSwapSuggestion satisfies the +// swapSuggestion interface. +var _ swapSuggestion = (*loopOutSwapSuggestion)(nil) + type loopOutSwapSuggestion struct { loop.OutRequest } @@ -69,3 +79,26 @@ func (l *loopOutSwapSuggestion) channels() []lnwire.ShortChannelID { return channels } + +// peers returns the set of peers that the loop out swap is restricted to. +func (l *loopOutSwapSuggestion) peers( + knownChans map[uint64]route.Vertex) []route.Vertex { + + peers := make(map[route.Vertex]struct{}, len(knownChans)) + + for _, channel := range l.OutgoingChanSet { + peer, ok := knownChans[channel] + if !ok { + log.Warnf("peer for channel: %v unknown", channel) + } + + peers[peer] = struct{}{} + } + + peerList := make([]route.Vertex, 0, len(peers)) + for peer := range peers { + peerList = append(peerList, peer) + } + + return peerList +} diff --git a/liquidity/liquidity.go b/liquidity/liquidity.go index 91a6fb7..6189ed6 100644 --- a/liquidity/liquidity.go +++ b/liquidity/liquidity.go @@ -685,8 +685,13 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoloop bool) ( return nil, err } + // Collect a map of channel IDs to peer pubkeys, and a set of per-peer + // balances which we will use for peer-level liquidity rules. + channelPeers := make(map[uint64]route.Vertex) peerChannels := make(map[route.Vertex]*balances) for _, channel := range channels { + channelPeers[channel.ChannelID] = channel.PubKeyBytes + bal, ok := peerChannels[channel.PubKeyBytes] if !ok { bal = &balances{} @@ -777,6 +782,15 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoloop bool) ( // setReason is a helper that adds a swap's channels to our disqualified // list with the reason provided. setReason := func(reason Reason, swap swapSuggestion) { + for _, peer := range swap.peers(channelPeers) { + _, ok := m.params.PeerRules[peer] + if !ok { + continue + } + + resp.DisqualifiedPeers[peer] = reason + } + for _, channel := range swap.channels() { _, ok := m.params.ChannelRules[channel] if !ok { diff --git a/liquidity/liquidity_test.go b/liquidity/liquidity_test.go index c95e26a..ccded3a 100644 --- a/liquidity/liquidity_test.go +++ b/liquidity/liquidity_test.go @@ -1098,7 +1098,10 @@ func TestInFlightLimit(t *testing.T) { name string maxInFlight int existingSwaps []*loopdb.LoopOut - suggestions *Suggestions + // peerRules will only be set (instead of test default values) + // is it is non-nil. + peerRules map[route.Vertex]*ThresholdRule + suggestions *Suggestions }{ { name: "none in flight, extra space", @@ -1175,6 +1178,31 @@ func TestInFlightLimit(t *testing.T) { DisqualifiedPeers: noPeersDisqualified, }, }, + { + name: "peer rules max swaps exceeded", + maxInFlight: 2, + existingSwaps: []*loopdb.LoopOut{ + { + Contract: autoOutContract, + }, + }, + // Create two peer-level rules, both in need of a swap, + // but peer 1 needs a larger swap so will be + // prioritized. + peerRules: map[route.Vertex]*ThresholdRule{ + peer1: NewThresholdRule(50, 0), + peer2: NewThresholdRule(40, 0), + }, + suggestions: &Suggestions{ + OutSwaps: []loop.OutRequest{ + chan1Rec, + }, + DisqualifiedChans: noneDisqualified, + DisqualifiedPeers: map[route.Vertex]Reason{ + peer2: ReasonInFlight, + }, + }, + }, } for _, testCase := range tests { @@ -1191,10 +1219,17 @@ func TestInFlightLimit(t *testing.T) { } params := defaultParameters - params.ChannelRules = map[lnwire.ShortChannelID]*ThresholdRule{ - chanID1: chanRule, - chanID2: chanRule, + + if testCase.peerRules != nil { + params.PeerRules = testCase.peerRules + } else { + params.ChannelRules = + map[lnwire.ShortChannelID]*ThresholdRule{ + chanID1: chanRule, + chanID2: chanRule, + } } + params.MaxAutoInFlight = testCase.maxInFlight // By default we only have budget for one swap, increase diff --git a/release_notes.md b/release_notes.md index f557b3f..29b35f7 100644 --- a/release_notes.md +++ b/release_notes.md @@ -29,3 +29,7 @@ This file tracks release notes for the loop client. #### Breaking Changes #### Bug Fixes +* A bug that would not list autoloop rules set on a per-peer basis when they + were excluded due to insufficient budget, or the number of swaps in flight + has been corrected. These rules will now be included in the output of + `suggestswaps` with other autoloop peer rules.