lnrpc/invoicesrpc: extend hop hint selection to account for MPP

In this commit, we update the hop hint selection to account for the fact
that with MPP, a single payment may consume multiple channels. As is, if
a user only has two 0.5 BTC channels, and tries to make a 1 BTC channel,
then the current logic won't include any hop hints.

To solve this, we first add all the channels which in isolation can
carry the payment in question. We then do another pass that accumulates
channels until either we reach our hop-hint limit, or the total
bandwidth that we've accumulate is greater than 2x the payment amount.
This commit is contained in:
Olaoluwa Osuntokun 2020-08-10 16:18:48 -07:00
parent f42d7780f1
commit f6d6d6609f
No known key found for this signature in database
GPG Key ID: BC13F65E2DC84465

@ -10,6 +10,7 @@ import (
"time"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/davecgh/go-spew/spew"
@ -394,7 +395,7 @@ func chanCanBeHopHint(channel *channeldb.OpenChannel,
// addHopHint creates a hop hint out of the passed channel and channel policy.
// The new hop hint is appended to the passed slice.
func addHopHint(hopHints []func(*zpay32.Invoice),
func addHopHint(hopHints *[]func(*zpay32.Invoice),
channel *channeldb.OpenChannel, chanPolicy *channeldb.ChannelEdgePolicy) {
hopHint := zpay32.HopHint{
@ -406,28 +407,80 @@ func addHopHint(hopHints []func(*zpay32.Invoice),
),
CLTVExpiryDelta: chanPolicy.TimeLockDelta,
}
hopHints = append(
hopHints, zpay32.RouteHint([]zpay32.HopHint{hopHint}),
*hopHints = append(
*hopHints, zpay32.RouteHint([]zpay32.HopHint{hopHint}),
)
}
// selectHopHints will selects up to numMaxHophints from the set of passed open
// selectHopHints will select up to numMaxHophints from the set of passed open
// channels. The set of hop hints will be returned as a slice of functional
// options that'll append the route hint to the set of all route hints.
//
// TODO(roasbeef): do proper sub-set sum max hints usually << numChans
func selectHopHints(amtMSat lnwire.MilliSatoshi, cfg *AddInvoiceConfig,
openChannels []*channeldb.OpenChannel,
numMaxHophints int) []func(*zpay32.Invoice) {
graph := cfg.ChanDB.ChannelGraph()
numHints := 0
// We'll add our hop hints in two passes, first we'll add all channels
// that are eligible to be hop hints, and also have a local balance
// above the payment amount.
var totalHintBandwidth lnwire.MilliSatoshi
hopHintChans := make(map[wire.OutPoint]struct{})
hopHints := make([]func(*zpay32.Invoice), 0, numMaxHophints)
for _, channel := range openChannels {
if numHints >= numMaxHophints {
// If this channel can't be a hop hint, then skip it.
edgePolicy, canBeHopHint := chanCanBeHopHint(
channel, graph, cfg,
)
if edgePolicy == nil || !canBeHopHint {
continue
}
// Similarly, in this first pass, we'll ignore all channels in
// isolation can't satisfy this payment.
if channel.LocalCommitment.RemoteBalance < amtMSat {
continue
}
// Now that we now this channel use usable, add it as a hop
// hint and the indexes we'll use later.
addHopHint(&hopHints, channel, edgePolicy)
hopHintChans[channel.FundingOutpoint] = struct{}{}
totalHintBandwidth += channel.LocalCommitment.RemoteBalance
}
// If we have enough hop hints at this point, then we'll exit early.
// Otherwise, we'll continue to add more that may help out mpp users.
if len(hopHints) >= numMaxHophints {
return hopHints
}
// In this second pass we'll add channels, and we'll either stop when
// we have 20 hop hints, we've run through all the available channels,
// or if the sum of available bandwidth in the routing hints exceeds 2x
// the payment amount. We do 2x here to account for a margin of error
// if some of the selected channels no longer become operable.
hopHintFactor := lnwire.MilliSatoshi(2)
for i := 0; i < len(openChannels); i++ {
// If we hit either of our early termination conditions, then
// we'll break the loop here.
if totalHintBandwidth > amtMSat*hopHintFactor ||
len(hopHints) >= numMaxHophints {
break
}
// If the channel can't a hop hint, then we'll skip it.
channel := openChannels[i]
// Skip the channel if we already selected it.
if _, ok := hopHintChans[channel.FundingOutpoint]; ok {
continue
}
// If the channel can't be a hop hint, then we'll skip it.
// Otherwise, we'll use the policy information to populate the
// hop hint.
remotePolicy, canBeHopHint := chanCanBeHopHint(
@ -439,9 +492,13 @@ func selectHopHints(amtMSat lnwire.MilliSatoshi, cfg *AddInvoiceConfig,
// Include the route hint in our set of options that will be
// used when creating the invoice.
addHopHint(hopHints, channel, remotePolicy)
addHopHint(&hopHints, channel, remotePolicy)
numHints++
// As we've just added a new hop hint, we'll accumulate it's
// available balance now to update our tally.
//
// TODO(roasbeef): have a cut off based on min bandwidth?
totalHintBandwidth += channel.LocalCommitment.RemoteBalance
}
return hopHints