lnd/lnrpc/invoicesrpc/addinvoice.go
2022-08-23 22:10:24 +08:00

833 lines
27 KiB
Go

package invoicesrpc
import (
"bytes"
"context"
"crypto/rand"
"errors"
"fmt"
"math"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/wire"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/netann"
"github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/zpay32"
)
const (
// DefaultInvoiceExpiry is the default invoice expiry for new MPP
// invoices.
DefaultInvoiceExpiry = 24 * time.Hour
// DefaultAMPInvoiceExpiry is the default invoice expiry for new AMP
// invoices.
DefaultAMPInvoiceExpiry = 30 * 24 * time.Hour
// hopHintFactor is factor by which we scale the total amount of
// inbound capacity we want our hop hints to represent, allowing us to
// have some leeway if peers go offline.
hopHintFactor = 2
)
// AddInvoiceConfig contains dependencies for invoice creation.
type AddInvoiceConfig struct {
// AddInvoice is called to add the invoice to the registry.
AddInvoice func(invoice *channeldb.Invoice, paymentHash lntypes.Hash) (
uint64, error)
// IsChannelActive is used to generate valid hop hints.
IsChannelActive func(chanID lnwire.ChannelID) bool
// ChainParams are required to properly decode invoice payment requests
// that are marshalled over rpc.
ChainParams *chaincfg.Params
// NodeSigner is an implementation of the MessageSigner implementation
// that's backed by the identity private key of the running lnd node.
NodeSigner *netann.NodeSigner
// DefaultCLTVExpiry is the default invoice expiry if no values is
// specified.
DefaultCLTVExpiry uint32
// ChanDB is a global boltdb instance which is needed to access the
// channel graph.
ChanDB *channeldb.ChannelStateDB
// Graph holds a reference to the ChannelGraph database.
Graph *channeldb.ChannelGraph
// GenInvoiceFeatures returns a feature containing feature bits that
// should be advertised on freshly generated invoices.
GenInvoiceFeatures func() *lnwire.FeatureVector
// GenAmpInvoiceFeatures returns a feature containing feature bits that
// should be advertised on freshly generated AMP invoices.
GenAmpInvoiceFeatures func() *lnwire.FeatureVector
// GetAlias allows the peer's alias SCID to be retrieved for private
// option_scid_alias channels.
GetAlias func(lnwire.ChannelID) (lnwire.ShortChannelID, error)
}
// AddInvoiceData contains the required data to create a new invoice.
type AddInvoiceData struct {
// An optional memo to attach along with the invoice. Used for record
// keeping purposes for the invoice's creator, and will also be set in
// the description field of the encoded payment request if the
// description_hash field is not being used.
Memo string
// The preimage which will allow settling an incoming HTLC payable to
// this preimage. If Preimage is set, Hash should be nil. If both
// Preimage and Hash are nil, a random preimage is generated.
Preimage *lntypes.Preimage
// The hash of the preimage. If Hash is set, Preimage should be nil.
// This condition indicates that we have a 'hold invoice' for which the
// htlc will be accepted and held until the preimage becomes known.
Hash *lntypes.Hash
// The value of this invoice in millisatoshis.
Value lnwire.MilliSatoshi
// Hash (SHA-256) of a description of the payment. Used if the
// description of payment (memo) is too long to naturally fit within the
// description field of an encoded payment request.
DescriptionHash []byte
// Payment request expiry time in seconds. Default is 3600 (1 hour).
Expiry int64
// Fallback on-chain address.
FallbackAddr string
// Delta to use for the time-lock of the CLTV extended to the final hop.
CltvExpiry uint64
// Whether this invoice should include routing hints for private
// channels.
Private bool
// HodlInvoice signals that this invoice shouldn't be settled
// immediately upon receiving the payment.
HodlInvoice bool
// Amp signals whether or not to create an AMP invoice.
//
// NOTE: Preimage should always be set to nil when this value is true.
Amp bool
// RouteHints are optional route hints that can each be individually used
// to assist in reaching the invoice's destination.
RouteHints [][]zpay32.HopHint
}
// paymentHashAndPreimage returns the payment hash and preimage for this invoice
// depending on the configuration.
//
// For AMP invoices (when Amp flag is true), this method always returns a nil
// preimage. The hash value can be set externally by the user using the Hash
// field, or one will be generated randomly. The payment hash here only serves
// as a unique identifier for insertion into the invoice index, as there is
// no universal preimage for an AMP payment.
//
// For MPP invoices (when Amp flag is false), this method may return nil
// preimage when create a hodl invoice, but otherwise will always return a
// non-nil preimage and the corresponding payment hash. The valid combinations
// are parsed as follows:
// - Preimage == nil && Hash == nil -> (random preimage, H(random preimage))
// - Preimage != nil && Hash == nil -> (Preimage, H(Preimage))
// - Preimage == nil && Hash != nil -> (nil, Hash)
func (d *AddInvoiceData) paymentHashAndPreimage() (
*lntypes.Preimage, lntypes.Hash, error) {
if d.Amp {
return d.ampPaymentHashAndPreimage()
}
return d.mppPaymentHashAndPreimage()
}
// ampPaymentHashAndPreimage returns the payment hash to use for an AMP invoice.
// The preimage will always be nil.
func (d *AddInvoiceData) ampPaymentHashAndPreimage() (*lntypes.Preimage, lntypes.Hash, error) {
switch {
// Preimages cannot be set on AMP invoice.
case d.Preimage != nil:
return nil, lntypes.Hash{},
errors.New("preimage set on AMP invoice")
// If a specific hash was requested, use that.
case d.Hash != nil:
return nil, *d.Hash, nil
// Otherwise generate a random hash value, just needs to be unique to be
// added to the invoice index.
default:
var paymentHash lntypes.Hash
if _, err := rand.Read(paymentHash[:]); err != nil {
return nil, lntypes.Hash{}, err
}
return nil, paymentHash, nil
}
}
// mppPaymentHashAndPreimage returns the payment hash and preimage to use for an
// MPP invoice.
func (d *AddInvoiceData) mppPaymentHashAndPreimage() (*lntypes.Preimage, lntypes.Hash, error) {
var (
paymentPreimage *lntypes.Preimage
paymentHash lntypes.Hash
)
switch {
// Only either preimage or hash can be set.
case d.Preimage != nil && d.Hash != nil:
return nil, lntypes.Hash{},
errors.New("preimage and hash both set")
// If no hash or preimage is given, generate a random preimage.
case d.Preimage == nil && d.Hash == nil:
paymentPreimage = &lntypes.Preimage{}
if _, err := rand.Read(paymentPreimage[:]); err != nil {
return nil, lntypes.Hash{}, err
}
paymentHash = paymentPreimage.Hash()
// If just a hash is given, we create a hold invoice by setting the
// preimage to unknown.
case d.Preimage == nil && d.Hash != nil:
paymentHash = *d.Hash
// A specific preimage was supplied. Use that for the invoice.
case d.Preimage != nil && d.Hash == nil:
preimage := *d.Preimage
paymentPreimage = &preimage
paymentHash = d.Preimage.Hash()
}
return paymentPreimage, paymentHash, nil
}
// AddInvoice attempts to add a new invoice to the invoice database. Any
// duplicated invoices are rejected, therefore all invoices *must* have a
// unique payment preimage.
func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig,
invoice *AddInvoiceData) (*lntypes.Hash, *channeldb.Invoice, error) {
paymentPreimage, paymentHash, err := invoice.paymentHashAndPreimage()
if err != nil {
return nil, nil, err
}
// The size of the memo, receipt and description hash attached must not
// exceed the maximum values for either of the fields.
if len(invoice.Memo) > channeldb.MaxMemoSize {
return nil, nil, fmt.Errorf("memo too large: %v bytes "+
"(maxsize=%v)", len(invoice.Memo), channeldb.MaxMemoSize)
}
if len(invoice.DescriptionHash) > 0 && len(invoice.DescriptionHash) != 32 {
return nil, nil, fmt.Errorf("description hash is %v bytes, must be 32",
len(invoice.DescriptionHash))
}
// We set the max invoice amount to 100k BTC, which itself is several
// multiples off the current block reward.
maxInvoiceAmt := btcutil.Amount(btcutil.SatoshiPerBitcoin * 100000)
switch {
// The value of the invoice must not be negative.
case int64(invoice.Value) < 0:
return nil, nil, fmt.Errorf("payments of negative value "+
"are not allowed, value is %v", int64(invoice.Value))
// Also ensure that the invoice is actually realistic, while preventing
// any issues due to underflow.
case invoice.Value.ToSatoshis() > maxInvoiceAmt:
return nil, nil, fmt.Errorf("invoice amount %v is "+
"too large, max is %v", invoice.Value.ToSatoshis(),
maxInvoiceAmt)
}
amtMSat := invoice.Value
// We also create an encoded payment request which allows the
// caller to compactly send the invoice to the payer. We'll create a
// list of options to be added to the encoded payment request. For now
// we only support the required fields description/description_hash,
// expiry, fallback address, and the amount field.
var options []func(*zpay32.Invoice)
// We only include the amount in the invoice if it is greater than 0.
// By not including the amount, we enable the creation of invoices that
// allow the payee to specify the amount of satoshis they wish to send.
if amtMSat > 0 {
options = append(options, zpay32.Amount(amtMSat))
}
// If specified, add a fallback address to the payment request.
if len(invoice.FallbackAddr) > 0 {
addr, err := btcutil.DecodeAddress(invoice.FallbackAddr,
cfg.ChainParams)
if err != nil {
return nil, nil, fmt.Errorf("invalid fallback address: %v",
err)
}
options = append(options, zpay32.FallbackAddr(addr))
}
switch {
// If expiry is set, specify it. If it is not provided, no expiry time
// will be explicitly added to this payment request, which will imply
// the default 3600 seconds.
case invoice.Expiry > 0:
// We'll ensure that the specified expiry is restricted to sane
// number of seconds. As a result, we'll reject an invoice with
// an expiry greater than 1 year.
maxExpiry := time.Hour * 24 * 365
expSeconds := invoice.Expiry
if float64(expSeconds) > maxExpiry.Seconds() {
return nil, nil, fmt.Errorf("expiry of %v seconds "+
"greater than max expiry of %v seconds",
float64(expSeconds), maxExpiry.Seconds())
}
expiry := time.Duration(invoice.Expiry) * time.Second
options = append(options, zpay32.Expiry(expiry))
// If no custom expiry is provided, use the default MPP expiry.
case !invoice.Amp:
options = append(options, zpay32.Expiry(DefaultInvoiceExpiry))
// Otherwise, use the default AMP expiry.
default:
options = append(options, zpay32.Expiry(DefaultAMPInvoiceExpiry))
}
// If the description hash is set, then we add it do the list of options.
// If not, use the memo field as the payment request description.
if len(invoice.DescriptionHash) > 0 {
var descHash [32]byte
copy(descHash[:], invoice.DescriptionHash[:])
options = append(options, zpay32.DescriptionHash(descHash))
} else {
// Use the memo field as the description. If this is not set
// this will just be an empty string.
options = append(options, zpay32.Description(invoice.Memo))
}
// We'll use our current default CLTV value unless one was specified as
// an option on the command line when creating an invoice.
switch {
case invoice.CltvExpiry > math.MaxUint16:
return nil, nil, fmt.Errorf("CLTV delta of %v is too large, max "+
"accepted is: %v", invoice.CltvExpiry, math.MaxUint16)
case invoice.CltvExpiry != 0:
// Disallow user-chosen final CLTV deltas below the required
// minimum.
if invoice.CltvExpiry < routing.MinCLTVDelta {
return nil, nil, fmt.Errorf("CLTV delta of %v must be "+
"greater than minimum of %v",
routing.MinCLTVDelta, invoice.CltvExpiry)
}
options = append(options,
zpay32.CLTVExpiry(invoice.CltvExpiry))
default:
// TODO(roasbeef): assumes set delta between versions
defaultDelta := cfg.DefaultCLTVExpiry
options = append(options, zpay32.CLTVExpiry(uint64(defaultDelta)))
}
// We make sure that the given invoice routing hints number is within the
// valid range
if len(invoice.RouteHints) > 20 {
return nil, nil, fmt.Errorf("number of routing hints must not exceed " +
"maximum of 20")
}
// We continue by populating the requested routing hints indexing their
// corresponding channels so we won't duplicate them.
forcedHints := make(map[uint64]struct{})
for _, h := range invoice.RouteHints {
if len(h) == 0 {
return nil, nil, fmt.Errorf("number of hop hint within a route must " +
"be positive")
}
options = append(options, zpay32.RouteHint(h))
// Only this first hop is our direct channel.
forcedHints[h[0].ChannelID] = struct{}{}
}
// If we were requested to include routing hints in the invoice, then
// we'll fetch all of our available private channels and create routing
// hints for them.
if invoice.Private {
openChannels, err := cfg.ChanDB.FetchAllChannels()
if err != nil {
return nil, nil, fmt.Errorf("could not fetch all channels")
}
if len(openChannels) > 0 {
// We filter the channels by excluding the ones that were specified by
// the caller and were already added.
var filteredChannels []*HopHintInfo
for _, c := range openChannels {
if _, ok := forcedHints[c.ShortChanID().ToUint64()]; ok {
continue
}
// If this is a zero-conf channel, check if the
// confirmed SCID was used in forcedHints.
realScid := c.ZeroConfRealScid().ToUint64()
if c.IsZeroConf() {
if _, ok := forcedHints[realScid]; ok {
continue
}
}
chanID := lnwire.NewChanIDFromOutPoint(
&c.FundingOutpoint,
)
// Check whether the the peer's alias was
// provided in forcedHints.
peerAlias, _ := cfg.GetAlias(chanID)
peerScid := peerAlias.ToUint64()
if _, ok := forcedHints[peerScid]; ok {
continue
}
isActive := cfg.IsChannelActive(chanID)
hopHintInfo := newHopHintInfo(c, isActive)
filteredChannels = append(
filteredChannels, hopHintInfo,
)
}
// We'll restrict the number of individual route hints
// to 20 to avoid creating overly large invoices.
numMaxHophints := 20 - len(forcedHints)
hopHintsCfg := newSelectHopHintsCfg(cfg)
hopHints := SelectHopHints(
amtMSat, hopHintsCfg, filteredChannels,
numMaxHophints,
)
// Convert our set of selected hop hints into route
// hints and add to our invoice options.
for _, hopHint := range hopHints {
routeHint := zpay32.RouteHint(hopHint)
options = append(
options, routeHint,
)
}
}
}
// Set our desired invoice features and add them to our list of options.
var invoiceFeatures *lnwire.FeatureVector
if invoice.Amp {
invoiceFeatures = cfg.GenAmpInvoiceFeatures()
} else {
invoiceFeatures = cfg.GenInvoiceFeatures()
}
options = append(options, zpay32.Features(invoiceFeatures))
// Generate and set a random payment address for this invoice. If the
// sender understands payment addresses, this can be used to avoid
// intermediaries probing the receiver.
var paymentAddr [32]byte
if _, err := rand.Read(paymentAddr[:]); err != nil {
return nil, nil, err
}
options = append(options, zpay32.PaymentAddr(paymentAddr))
// Create and encode the payment request as a bech32 (zpay32) string.
creationDate := time.Now()
payReq, err := zpay32.NewInvoice(
cfg.ChainParams, paymentHash, creationDate, options...,
)
if err != nil {
return nil, nil, err
}
payReqString, err := payReq.Encode(zpay32.MessageSigner{
SignCompact: func(msg []byte) ([]byte, error) {
return cfg.NodeSigner.SignMessageCompact(msg, false)
},
})
if err != nil {
return nil, nil, err
}
newInvoice := &channeldb.Invoice{
CreationDate: creationDate,
Memo: []byte(invoice.Memo),
PaymentRequest: []byte(payReqString),
Terms: channeldb.ContractTerm{
FinalCltvDelta: int32(payReq.MinFinalCLTVExpiry()),
Expiry: payReq.Expiry(),
Value: amtMSat,
PaymentPreimage: paymentPreimage,
PaymentAddr: paymentAddr,
Features: invoiceFeatures,
},
HodlInvoice: invoice.HodlInvoice,
}
log.Tracef("[addinvoice] adding new invoice %v",
newLogClosure(func() string {
return spew.Sdump(newInvoice)
}),
)
// With all sanity checks passed, write the invoice to the database.
_, err = cfg.AddInvoice(newInvoice, paymentHash)
if err != nil {
return nil, nil, err
}
return &paymentHash, newInvoice, nil
}
// chanCanBeHopHint returns true if the target channel is eligible to be a hop
// hint.
func chanCanBeHopHint(channel *HopHintInfo, cfg *SelectHopHintsCfg) (
*channeldb.ChannelEdgePolicy, bool) {
// Since we're only interested in our private channels, we'll skip
// public ones.
if channel.IsPublic {
return nil, false
}
// Make sure the channel is active.
if !channel.IsActive {
log.Debugf("Skipping channel %v due to not "+
"being eligible to forward payments",
channel.ShortChannelID)
return nil, false
}
// To ensure we don't leak unadvertised nodes, we'll make sure our
// counterparty is publicly advertised within the network. Otherwise,
// we'll end up leaking information about nodes that intend to stay
// unadvertised, like in the case of a node only having private
// channels.
var remotePub [33]byte
copy(remotePub[:], channel.RemotePubkey.SerializeCompressed())
isRemoteNodePublic, err := cfg.IsPublicNode(remotePub)
if err != nil {
log.Errorf("Unable to determine if node %x "+
"is advertised: %v", remotePub, err)
return nil, false
}
if !isRemoteNodePublic {
log.Debugf("Skipping channel %v due to "+
"counterparty %x being unadvertised",
channel.ShortChannelID, remotePub)
return nil, false
}
// Fetch the policies for each end of the channel.
info, p1, p2, err := cfg.FetchChannelEdgesByID(channel.ShortChannelID)
if err != nil {
// In the case of zero-conf channels, it may be the case that
// the alias SCID was deleted from the graph, and replaced by
// the confirmed SCID. Check the Graph for the confirmed SCID.
confirmedScid := channel.ConfirmedScidZC
info, p1, p2, err = cfg.FetchChannelEdgesByID(confirmedScid)
if err != nil {
log.Errorf("Unable to fetch the routing policies for "+
"the edges of the channel %v: %v",
channel.ShortChannelID, err)
return nil, false
}
}
// Now, we'll need to determine which is the correct policy for HTLCs
// being sent from the remote node.
var remotePolicy *channeldb.ChannelEdgePolicy
if bytes.Equal(remotePub[:], info.NodeKey1Bytes[:]) {
remotePolicy = p1
} else {
remotePolicy = p2
}
return remotePolicy, true
}
// 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 *[][]zpay32.HopHint,
channel *HopHintInfo, chanPolicy *channeldb.ChannelEdgePolicy,
aliasScid lnwire.ShortChannelID) {
hopHint := zpay32.HopHint{
NodeID: channel.RemotePubkey,
ChannelID: channel.ShortChannelID,
FeeBaseMSat: uint32(chanPolicy.FeeBaseMSat),
FeeProportionalMillionths: uint32(
chanPolicy.FeeProportionalMillionths,
),
CLTVExpiryDelta: chanPolicy.TimeLockDelta,
}
var defaultScid lnwire.ShortChannelID
if aliasScid != defaultScid {
hopHint.ChannelID = aliasScid.ToUint64()
}
*hopHints = append(*hopHints, []zpay32.HopHint{hopHint})
}
// HopHintInfo contains the channel information required to create a hop hint.
type HopHintInfo struct {
// IsPublic indicates whether a channel is advertised to the network.
IsPublic bool
// IsActive indicates whether the channel is online and available for
// use.
IsActive bool
// FundingOutpoint is the funding txid:index for the channel.
FundingOutpoint wire.OutPoint
// RemotePubkey is the public key of the remote party that this channel
// is in.
RemotePubkey *btcec.PublicKey
// RemoteBalance is the remote party's balance (our current incoming
// capacity).
RemoteBalance lnwire.MilliSatoshi
// ShortChannelID is the short channel ID of the channel.
ShortChannelID uint64
// ConfirmedScidZC is the confirmed SCID of a zero-conf channel. This
// may be used for looking up a channel in the graph.
ConfirmedScidZC uint64
// ScidAliasFeature denotes whether the channel has negotiated the
// option-scid-alias feature bit.
ScidAliasFeature bool
}
func newHopHintInfo(c *channeldb.OpenChannel, isActive bool) *HopHintInfo {
isPublic := c.ChannelFlags&lnwire.FFAnnounceChannel != 0
return &HopHintInfo{
IsPublic: isPublic,
IsActive: isActive,
FundingOutpoint: c.FundingOutpoint,
RemotePubkey: c.IdentityPub,
RemoteBalance: c.LocalCommitment.RemoteBalance,
ShortChannelID: c.ShortChannelID.ToUint64(),
ConfirmedScidZC: c.ZeroConfRealScid().ToUint64(),
ScidAliasFeature: c.ChanType.HasScidAliasFeature(),
}
}
// SelectHopHintsCfg contains the dependencies required to obtain hop hints
// for an invoice.
type SelectHopHintsCfg struct {
// IsPublicNode is returns a bool indicating whether the node with the
// given public key is seen as a public node in the graph from the
// graph's source node's point of view.
IsPublicNode func(pubKey [33]byte) (bool, error)
// FetchChannelEdgesByID attempts to lookup the two directed edges for
// the channel identified by the channel ID.
FetchChannelEdgesByID func(chanID uint64) (*channeldb.ChannelEdgeInfo,
*channeldb.ChannelEdgePolicy, *channeldb.ChannelEdgePolicy,
error)
// GetAlias allows the peer's alias SCID to be retrieved for private
// option_scid_alias channels.
GetAlias func(lnwire.ChannelID) (lnwire.ShortChannelID, error)
}
func newSelectHopHintsCfg(invoicesCfg *AddInvoiceConfig) *SelectHopHintsCfg {
return &SelectHopHintsCfg{
IsPublicNode: invoicesCfg.Graph.IsPublicNode,
FetchChannelEdgesByID: invoicesCfg.Graph.FetchChannelEdgesByID,
GetAlias: invoicesCfg.GetAlias,
}
}
// sufficientHints checks whether we have sufficient hop hints, based on the
// following criteria:
// - Hop hint count: limit to a set number of hop hints, regardless of whether
// we've reached our invoice amount or not.
// - Total incoming capacity: limit to our invoice amount * scaling factor to
// allow for some of our links going offline.
//
// We limit our number of hop hints like this to keep our invoice size down,
// and to avoid leaking all our private channels when we don't need to.
func sufficientHints(numHints, maxHints, scalingFactor int, amount,
totalHintAmount lnwire.MilliSatoshi) bool {
if numHints >= maxHints {
log.Debug("Reached maximum number of hop hints")
return true
}
requiredAmount := amount * lnwire.MilliSatoshi(scalingFactor)
if totalHintAmount >= requiredAmount {
log.Debugf("Total hint amount: %v has reached target hint "+
"bandwidth: %v (invoice amount: %v * factor: %v)",
totalHintAmount, requiredAmount, amount,
scalingFactor)
return true
}
return false
}
// 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 *SelectHopHintsCfg,
openChannels []*HopHintInfo,
numMaxHophints int) [][]zpay32.HopHint {
// 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([][]zpay32.HopHint, 0, numMaxHophints)
for _, channel := range openChannels {
enoughHopHints := sufficientHints(
len(hopHints), numMaxHophints, hopHintFactor, amtMSat,
totalHintBandwidth,
)
if enoughHopHints {
log.Debugf("First pass of hop selection has " +
"sufficient hints")
return hopHints
}
// If this channel can't be a hop hint, then skip it.
edgePolicy, canBeHopHint := chanCanBeHopHint(channel, 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.RemoteBalance < amtMSat {
continue
}
// Lookup and see if there is an alias SCID that exists.
chanID := lnwire.NewChanIDFromOutPoint(
&channel.FundingOutpoint,
)
alias, _ := cfg.GetAlias(chanID)
// If this is a channel where the option-scid-alias feature bit
// was negotiated and the alias is not yet assigned, we cannot
// issue an invoice. Doing so might expose the confirmed SCID
// of a private channel.
if channel.ScidAliasFeature {
var defaultScid lnwire.ShortChannelID
if alias == defaultScid {
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, alias)
hopHintChans[channel.FundingOutpoint] = struct{}{}
totalHintBandwidth += channel.RemoteBalance
}
// 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.
for i := 0; i < len(openChannels); i++ {
enoughHopHints := sufficientHints(
len(hopHints), numMaxHophints, hopHintFactor, amtMSat,
totalHintBandwidth,
)
if enoughHopHints {
log.Debugf("Second pass of hop selection has " +
"sufficient hints")
return hopHints
}
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(channel, cfg)
if !canBeHopHint || remotePolicy == nil {
continue
}
// Lookup and see if there's an alias SCID that exists.
chanID := lnwire.NewChanIDFromOutPoint(
&channel.FundingOutpoint,
)
alias, _ := cfg.GetAlias(chanID)
// If this is a channel where the option-scid-alias feature bit
// was negotiated and the alias is not yet assigned, we cannot
// issue an invoice. Doing so might expose the confirmed SCID
// of a private channel.
if channel.ScidAliasFeature {
var defaultScid lnwire.ShortChannelID
if alias == defaultScid {
continue
}
}
// Include the route hint in our set of options that will be
// used when creating the invoice.
addHopHint(&hopHints, channel, remotePolicy, alias)
// 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.RemoteBalance
}
return hopHints
}