github.com/decred/dcrlnd@v0.7.6/lnrpc/invoicesrpc/addinvoice.go (about)

     1  package invoicesrpc
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"crypto/rand"
     7  	"errors"
     8  	"fmt"
     9  	"math"
    10  	"time"
    11  
    12  	"github.com/davecgh/go-spew/spew"
    13  	"github.com/decred/dcrd/chaincfg/v3"
    14  	"github.com/decred/dcrd/dcrec/secp256k1/v4"
    15  	"github.com/decred/dcrd/dcrutil/v4"
    16  	"github.com/decred/dcrd/txscript/v4/stdaddr"
    17  	"github.com/decred/dcrd/wire"
    18  
    19  	"github.com/decred/dcrlnd/channeldb"
    20  	"github.com/decred/dcrlnd/lntypes"
    21  	"github.com/decred/dcrlnd/lnwire"
    22  	"github.com/decred/dcrlnd/netann"
    23  	"github.com/decred/dcrlnd/routing"
    24  	"github.com/decred/dcrlnd/zpay32"
    25  )
    26  
    27  const (
    28  	// DefaultInvoiceExpiry is the default invoice expiry for new MPP
    29  	// invoices.
    30  	DefaultInvoiceExpiry = 24 * time.Hour
    31  
    32  	// DefaultAMPInvoiceExpiry is the default invoice expiry for new AMP
    33  	// invoices.
    34  	DefaultAMPInvoiceExpiry = 30 * 24 * time.Hour
    35  
    36  	// hopHintFactor is factor by which we scale the total amount of
    37  	// inbound capacity we want our hop hints to represent, allowing us to
    38  	// have some leeway if peers go offline.
    39  	hopHintFactor = 2
    40  )
    41  
    42  // AddInvoiceConfig contains dependencies for invoice creation.
    43  type AddInvoiceConfig struct {
    44  	// AddInvoice is called to add the invoice to the registry.
    45  	AddInvoice func(invoice *channeldb.Invoice, paymentHash lntypes.Hash) (
    46  		uint64, error)
    47  
    48  	// IsChannelActive is used to generate valid hop hints.
    49  	IsChannelActive func(chanID lnwire.ChannelID) bool
    50  
    51  	// ChainParams are required to properly decode invoice payment requests
    52  	// that are marshalled over rpc.
    53  	ChainParams *chaincfg.Params
    54  
    55  	// NodeSigner is an implementation of the MessageSigner implementation
    56  	// that's backed by the identity private key of the running lnd node.
    57  	NodeSigner *netann.NodeSigner
    58  
    59  	// DefaultCLTVExpiry is the default invoice expiry if no values is
    60  	// specified.
    61  	DefaultCLTVExpiry uint32
    62  
    63  	// ChanDB is a global bboltdb instance which is needed to access the
    64  	// channel graph.
    65  	ChanDB *channeldb.ChannelStateDB
    66  
    67  	// Graph holds a reference to the ChannelGraph database.
    68  	Graph *channeldb.ChannelGraph
    69  
    70  	// GenInvoiceFeatures returns a feature containing feature bits that
    71  	// should be advertised on freshly generated invoices.
    72  	GenInvoiceFeatures func() *lnwire.FeatureVector
    73  
    74  	// GenAmpInvoiceFeatures returns a feature containing feature bits that
    75  	// should be advertised on freshly generated AMP invoices.
    76  	GenAmpInvoiceFeatures func() *lnwire.FeatureVector
    77  }
    78  
    79  // AddInvoiceData contains the required data to create a new invoice.
    80  type AddInvoiceData struct {
    81  	// An optional memo to attach along with the invoice. Used for record
    82  	// keeping purposes for the invoice's creator, and will also be set in
    83  	// the description field of the encoded payment request if the
    84  	// description_hash field is not being used.
    85  	Memo string
    86  
    87  	// The preimage which will allow settling an incoming HTLC payable to
    88  	// this preimage. If Preimage is set, Hash should be nil. If both
    89  	// Preimage and Hash are nil, a random preimage is generated.
    90  	Preimage *lntypes.Preimage
    91  
    92  	// The hash of the preimage. If Hash is set, Preimage should be nil.
    93  	// This condition indicates that we have a 'hold invoice' for which the
    94  	// htlc will be accepted and held until the preimage becomes known.
    95  	Hash *lntypes.Hash
    96  
    97  	// The value of this invoice in milliatoms.
    98  	Value lnwire.MilliAtom
    99  
   100  	// Hash (SHA-256) of a description of the payment. Used if the
   101  	// description of payment (memo) is too long to naturally fit within the
   102  	// description field of an encoded payment request.
   103  	DescriptionHash []byte
   104  
   105  	// Payment request expiry time in seconds. Default is 3600 (1 hour).
   106  	Expiry int64
   107  
   108  	// Fallback on-chain address.
   109  	FallbackAddr string
   110  
   111  	// Delta to use for the time-lock of the CLTV extended to the final hop.
   112  	CltvExpiry uint64
   113  
   114  	// Whether this invoice should include routing hints for private
   115  	// channels.
   116  	Private bool
   117  
   118  	// HodlInvoice signals that this invoice shouldn't be settled
   119  	// immediately upon receiving the payment.
   120  	HodlInvoice bool
   121  
   122  	// Amp signals whether or not to create an AMP invoice.
   123  	//
   124  	// NOTE: Preimage should always be set to nil when this value is true.
   125  	Amp bool
   126  
   127  	// RouteHints are optional route hints that can each be individually used
   128  	// to assist in reaching the invoice's destination.
   129  	RouteHints [][]zpay32.HopHint
   130  }
   131  
   132  // paymentHashAndPreimage returns the payment hash and preimage for this invoice
   133  // depending on the configuration.
   134  //
   135  // For AMP invoices (when Amp flag is true), this method always returns a nil
   136  // preimage. The hash value can be set externally by the user using the Hash
   137  // field, or one will be generated randomly. The payment hash here only serves
   138  // as a unique identifier for insertion into the invoice index, as there is
   139  // no universal preimage for an AMP payment.
   140  //
   141  // For MPP invoices (when Amp flag is false), this method may return nil
   142  // preimage when create a hodl invoice, but otherwise will always return a
   143  // non-nil preimage and the corresponding payment hash. The valid combinations
   144  // are parsed as follows:
   145  //   - Preimage == nil && Hash == nil -> (random preimage, H(random preimage))
   146  //   - Preimage != nil && Hash == nil -> (Preimage, H(Preimage))
   147  //   - Preimage == nil && Hash != nil -> (nil, Hash)
   148  func (d *AddInvoiceData) paymentHashAndPreimage() (
   149  	*lntypes.Preimage, lntypes.Hash, error) {
   150  
   151  	if d.Amp {
   152  		return d.ampPaymentHashAndPreimage()
   153  	}
   154  
   155  	return d.mppPaymentHashAndPreimage()
   156  }
   157  
   158  // ampPaymentHashAndPreimage returns the payment hash to use for an AMP invoice.
   159  // The preimage will always be nil.
   160  func (d *AddInvoiceData) ampPaymentHashAndPreimage() (*lntypes.Preimage, lntypes.Hash, error) {
   161  	switch {
   162  
   163  	// Preimages cannot be set on AMP invoice.
   164  	case d.Preimage != nil:
   165  		return nil, lntypes.Hash{},
   166  			errors.New("preimage set on AMP invoice")
   167  
   168  	// If a specific hash was requested, use that.
   169  	case d.Hash != nil:
   170  		return nil, *d.Hash, nil
   171  
   172  	// Otherwise generate a random hash value, just needs to be unique to be
   173  	// added to the invoice index.
   174  	default:
   175  		var paymentHash lntypes.Hash
   176  		if _, err := rand.Read(paymentHash[:]); err != nil {
   177  			return nil, lntypes.Hash{}, err
   178  		}
   179  
   180  		return nil, paymentHash, nil
   181  	}
   182  }
   183  
   184  // mppPaymentHashAndPreimage returns the payment hash and preimage to use for an
   185  // MPP invoice.
   186  func (d *AddInvoiceData) mppPaymentHashAndPreimage() (*lntypes.Preimage, lntypes.Hash, error) {
   187  	var (
   188  		paymentPreimage *lntypes.Preimage
   189  		paymentHash     lntypes.Hash
   190  	)
   191  
   192  	switch {
   193  
   194  	// Only either preimage or hash can be set.
   195  	case d.Preimage != nil && d.Hash != nil:
   196  		return nil, lntypes.Hash{},
   197  			errors.New("preimage and hash both set")
   198  
   199  	// If no hash or preimage is given, generate a random preimage.
   200  	case d.Preimage == nil && d.Hash == nil:
   201  		paymentPreimage = &lntypes.Preimage{}
   202  		if _, err := rand.Read(paymentPreimage[:]); err != nil {
   203  			return nil, lntypes.Hash{}, err
   204  		}
   205  		paymentHash = paymentPreimage.Hash()
   206  
   207  	// If just a hash is given, we create a hold invoice by setting the
   208  	// preimage to unknown.
   209  	case d.Preimage == nil && d.Hash != nil:
   210  		paymentHash = *d.Hash
   211  
   212  	// A specific preimage was supplied. Use that for the invoice.
   213  	case d.Preimage != nil && d.Hash == nil:
   214  		preimage := *d.Preimage
   215  		paymentPreimage = &preimage
   216  		paymentHash = d.Preimage.Hash()
   217  	}
   218  
   219  	return paymentPreimage, paymentHash, nil
   220  }
   221  
   222  // AddInvoice attempts to add a new invoice to the invoice database. Any
   223  // duplicated invoices are rejected, therefore all invoices *must* have a
   224  // unique payment preimage.
   225  func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig,
   226  	invoice *AddInvoiceData) (*lntypes.Hash, *channeldb.Invoice, error) {
   227  
   228  	paymentPreimage, paymentHash, err := invoice.paymentHashAndPreimage()
   229  	if err != nil {
   230  		return nil, nil, err
   231  	}
   232  
   233  	// The size of the memo, receipt and description hash attached must not
   234  	// exceed the maximum values for either of the fields.
   235  	if len(invoice.Memo) > channeldb.MaxMemoSize {
   236  		return nil, nil, fmt.Errorf("memo too large: %v bytes "+
   237  			"(maxsize=%v)", len(invoice.Memo), channeldb.MaxMemoSize)
   238  	}
   239  	if len(invoice.DescriptionHash) > 0 && len(invoice.DescriptionHash) != 32 {
   240  		return nil, nil, fmt.Errorf("description hash is %v bytes, must be 32",
   241  			len(invoice.DescriptionHash))
   242  	}
   243  
   244  	// We set the max invoice amount to 100k DCR, which itself is several
   245  	// multiples off the current block reward.
   246  	maxInvoiceAmt := dcrutil.Amount(dcrutil.AtomsPerCoin * 100000)
   247  
   248  	switch {
   249  	// The value of the invoice must not be negative.
   250  	case invoice.Value < 0:
   251  		return nil, nil, fmt.Errorf("payments of negative value "+
   252  			"are not allowed, value is %v", invoice.Value)
   253  
   254  	// Also ensure that the invoice is actually realistic, while preventing
   255  	// any issues due to underflow.
   256  	case invoice.Value.ToAtoms() > maxInvoiceAmt:
   257  		return nil, nil, fmt.Errorf("invoice amount %v is "+
   258  			"too large, max is %v", invoice.Value.ToAtoms(),
   259  			maxInvoiceAmt)
   260  	}
   261  
   262  	amtMAtoms := invoice.Value
   263  
   264  	// We also create an encoded payment request which allows the caller to
   265  	// compactly send the invoice to the payer. We'll create a list of
   266  	// options to be added to the encoded payment request. For now we only
   267  	// support the required fields description/description_hash, expiry,
   268  	// fallback address, and the amount field.
   269  	var options []func(*zpay32.Invoice)
   270  
   271  	// We only include the amount in the invoice if it is greater than 0.
   272  	// By not including the amount, we enable the creation of invoices that
   273  	// allow the payee to specify the amount of satoshis they wish to send.
   274  	if amtMAtoms > 0 {
   275  		options = append(options, zpay32.Amount(amtMAtoms))
   276  	}
   277  
   278  	// If specified, add a fallback address to the payment request.
   279  	if len(invoice.FallbackAddr) > 0 {
   280  		addr, err := stdaddr.DecodeAddress(
   281  			invoice.FallbackAddr, cfg.ChainParams,
   282  		)
   283  		if err != nil {
   284  			return nil, nil, fmt.Errorf("invalid fallback address: %v",
   285  				err)
   286  		}
   287  		options = append(options, zpay32.FallbackAddr(addr))
   288  	}
   289  
   290  	switch {
   291  
   292  	// If expiry is set, specify it. If it is not provided, no expiry time
   293  	// will be explicitly added to this payment request, which will imply
   294  	// the default 3600 seconds.
   295  	case invoice.Expiry > 0:
   296  
   297  		// We'll ensure that the specified expiry is restricted to sane
   298  		// number of seconds. As a result, we'll reject an invoice with
   299  		// an expiry greater than 1 year.
   300  		maxExpiry := time.Hour * 24 * 365
   301  		expSeconds := invoice.Expiry
   302  
   303  		if float64(expSeconds) > maxExpiry.Seconds() {
   304  			return nil, nil, fmt.Errorf("expiry of %v seconds "+
   305  				"greater than max expiry of %v seconds",
   306  				float64(expSeconds), maxExpiry.Seconds())
   307  		}
   308  
   309  		expiry := time.Duration(invoice.Expiry) * time.Second
   310  		options = append(options, zpay32.Expiry(expiry))
   311  
   312  	// If no custom expiry is provided, use the default MPP expiry.
   313  	case !invoice.Amp:
   314  		options = append(options, zpay32.Expiry(DefaultInvoiceExpiry))
   315  
   316  	// Otherwise, use the default AMP expiry.
   317  	default:
   318  		options = append(options, zpay32.Expiry(DefaultAMPInvoiceExpiry))
   319  
   320  	}
   321  
   322  	// If the description hash is set, then we add it do the list of options.
   323  	// If not, use the memo field as the payment request description.
   324  	if len(invoice.DescriptionHash) > 0 {
   325  		var descHash [32]byte
   326  		copy(descHash[:], invoice.DescriptionHash[:])
   327  		options = append(options, zpay32.DescriptionHash(descHash))
   328  	} else {
   329  		// Use the memo field as the description. If this is not set
   330  		// this will just be an empty string.
   331  		options = append(options, zpay32.Description(invoice.Memo))
   332  	}
   333  
   334  	// We'll use our current default CLTV value unless one was specified as
   335  	// an option on the command line when creating an invoice.
   336  	switch {
   337  	case invoice.CltvExpiry > math.MaxUint16:
   338  		return nil, nil, fmt.Errorf("CLTV delta of %v is too large, max "+
   339  			"accepted is: %v", invoice.CltvExpiry, math.MaxUint16)
   340  	case invoice.CltvExpiry != 0:
   341  		// Disallow user-chosen final CLTV deltas below the required
   342  		// minimum.
   343  		if invoice.CltvExpiry < routing.MinCLTVDelta {
   344  			return nil, nil, fmt.Errorf("CLTV delta of %v must be "+
   345  				"greater than minimum of %v",
   346  				routing.MinCLTVDelta, invoice.CltvExpiry)
   347  		}
   348  
   349  		options = append(options,
   350  			zpay32.CLTVExpiry(invoice.CltvExpiry))
   351  	default:
   352  		// TODO(roasbeef): assumes set delta between versions
   353  		defaultDelta := cfg.DefaultCLTVExpiry
   354  		options = append(options, zpay32.CLTVExpiry(uint64(defaultDelta)))
   355  	}
   356  
   357  	// We make sure that the given invoice routing hints number is within the
   358  	// valid range
   359  	if len(invoice.RouteHints) > 20 {
   360  		return nil, nil, fmt.Errorf("number of routing hints must not exceed " +
   361  			"maximum of 20")
   362  	}
   363  
   364  	// We continue by populating the requested routing hints indexing their
   365  	// corresponding channels so we won't duplicate them.
   366  	forcedHints := make(map[uint64]struct{})
   367  	for _, h := range invoice.RouteHints {
   368  		if len(h) == 0 {
   369  			return nil, nil, fmt.Errorf("number of hop hint within a route must " +
   370  				"be positive")
   371  		}
   372  		options = append(options, zpay32.RouteHint(h))
   373  
   374  		// Only this first hop is our direct channel.
   375  		forcedHints[h[0].ChannelID] = struct{}{}
   376  	}
   377  
   378  	// If we were requested to include routing hints in the invoice, then
   379  	// we'll fetch all of our available private channels and create routing
   380  	// hints for them.
   381  	if invoice.Private {
   382  		openChannels, err := cfg.ChanDB.FetchAllChannels()
   383  		if err != nil {
   384  			return nil, nil, fmt.Errorf("could not fetch all channels")
   385  		}
   386  
   387  		if len(openChannels) > 0 {
   388  			// We filter the channels by excluding the ones that were specified by
   389  			// the caller and were already added.
   390  			var filteredChannels []*HopHintInfo
   391  			for _, c := range openChannels {
   392  				if _, ok := forcedHints[c.ShortChanID().ToUint64()]; ok {
   393  					continue
   394  				}
   395  
   396  				chanID := lnwire.NewChanIDFromOutPoint(
   397  					&c.FundingOutpoint,
   398  				)
   399  				isActive := cfg.IsChannelActive(chanID)
   400  
   401  				hopHintInfo := newHopHintInfo(c, isActive)
   402  				filteredChannels = append(
   403  					filteredChannels, hopHintInfo,
   404  				)
   405  			}
   406  
   407  			// We'll restrict the number of individual route hints
   408  			// to 20 to avoid creating overly large invoices.
   409  			numMaxHophints := 20 - len(forcedHints)
   410  
   411  			hopHintsCfg := newSelectHopHintsCfg(cfg)
   412  			hopHints := SelectHopHints(
   413  				amtMAtoms, hopHintsCfg, filteredChannels,
   414  				numMaxHophints,
   415  			)
   416  
   417  			// Convert our set of selected hop hints into route
   418  			// hints and add to our invoice options.
   419  			for _, hopHint := range hopHints {
   420  				routeHint := zpay32.RouteHint(hopHint)
   421  
   422  				options = append(
   423  					options, routeHint,
   424  				)
   425  			}
   426  		}
   427  	}
   428  
   429  	// Set our desired invoice features and add them to our list of options.
   430  	var invoiceFeatures *lnwire.FeatureVector
   431  	if invoice.Amp {
   432  		invoiceFeatures = cfg.GenAmpInvoiceFeatures()
   433  	} else {
   434  		invoiceFeatures = cfg.GenInvoiceFeatures()
   435  	}
   436  	options = append(options, zpay32.Features(invoiceFeatures))
   437  
   438  	// Generate and set a random payment address for this invoice. If the
   439  	// sender understands payment addresses, this can be used to avoid
   440  	// intermediaries probing the receiver.
   441  	var paymentAddr [32]byte
   442  	if _, err := rand.Read(paymentAddr[:]); err != nil {
   443  		return nil, nil, err
   444  	}
   445  	options = append(options, zpay32.PaymentAddr(paymentAddr))
   446  
   447  	// Create and encode the payment request as a bech32 (zpay32) string.
   448  	creationDate := time.Now()
   449  	payReq, err := zpay32.NewInvoice(
   450  		cfg.ChainParams, paymentHash, creationDate, options...,
   451  	)
   452  	if err != nil {
   453  		return nil, nil, err
   454  	}
   455  
   456  	payReqString, err := payReq.Encode(zpay32.MessageSigner{
   457  		SignCompact: func(msg []byte) ([]byte, error) {
   458  			return cfg.NodeSigner.SignMessageCompact(msg, false)
   459  		},
   460  	})
   461  	if err != nil {
   462  		return nil, nil, err
   463  	}
   464  
   465  	newInvoice := &channeldb.Invoice{
   466  		CreationDate:   creationDate,
   467  		Memo:           []byte(invoice.Memo),
   468  		PaymentRequest: []byte(payReqString),
   469  		Terms: channeldb.ContractTerm{
   470  			FinalCltvDelta:  int32(payReq.MinFinalCLTVExpiry()),
   471  			Expiry:          payReq.Expiry(),
   472  			Value:           amtMAtoms,
   473  			PaymentPreimage: paymentPreimage,
   474  			PaymentAddr:     paymentAddr,
   475  			Features:        invoiceFeatures,
   476  		},
   477  		HodlInvoice: invoice.HodlInvoice,
   478  	}
   479  
   480  	log.Tracef("[addinvoice] adding new invoice %v",
   481  		newLogClosure(func() string {
   482  			return spew.Sdump(newInvoice)
   483  		}),
   484  	)
   485  
   486  	// With all sanity checks passed, write the invoice to the database.
   487  	_, err = cfg.AddInvoice(newInvoice, paymentHash)
   488  	if err != nil {
   489  		return nil, nil, err
   490  	}
   491  
   492  	return &paymentHash, newInvoice, nil
   493  }
   494  
   495  // chanCanBeHopHint returns true if the target channel is eligible to be a hop
   496  // hint.
   497  func chanCanBeHopHint(channel *HopHintInfo, cfg *SelectHopHintsCfg) (
   498  	*channeldb.ChannelEdgePolicy, bool) {
   499  
   500  	// Since we're only interested in our private channels, we'll skip
   501  	// public ones.
   502  	if channel.IsPublic {
   503  		return nil, false
   504  	}
   505  
   506  	// Make sure the channel is active.
   507  	if !channel.IsActive {
   508  		log.Debugf("Skipping channel %v due to not "+
   509  			"being eligible to forward payments",
   510  			channel.ShortChannelID)
   511  		return nil, false
   512  	}
   513  
   514  	// To ensure we don't leak unadvertised nodes, we'll make sure our
   515  	// counterparty is publicly advertised within the network.  Otherwise,
   516  	// we'll end up leaking information about nodes that intend to stay
   517  	// unadvertised, like in the case of a node only having private
   518  	// channels.
   519  	var remotePub [33]byte
   520  	copy(remotePub[:], channel.RemotePubkey.SerializeCompressed())
   521  	isRemoteNodePublic, err := cfg.IsPublicNode(remotePub)
   522  	if err != nil {
   523  		log.Errorf("Unable to determine if node %x "+
   524  			"is advertised: %v", remotePub, err)
   525  		return nil, false
   526  	}
   527  
   528  	if !isRemoteNodePublic {
   529  		log.Debugf("Skipping channel %v due to "+
   530  			"counterparty %x being unadvertised",
   531  			channel.ShortChannelID, remotePub)
   532  		return nil, false
   533  	}
   534  
   535  	// Fetch the policies for each end of the channel.
   536  	info, p1, p2, err := cfg.FetchChannelEdgesByID(channel.ShortChannelID)
   537  	if err != nil {
   538  		log.Errorf("Unable to fetch the routing "+
   539  			"policies for the edges of the channel "+
   540  			"%v: %v", channel.ShortChannelID, err)
   541  		return nil, false
   542  	}
   543  
   544  	// Now, we'll need to determine which is the correct policy for HTLCs
   545  	// being sent from the remote node.
   546  	var remotePolicy *channeldb.ChannelEdgePolicy
   547  	if bytes.Equal(remotePub[:], info.NodeKey1Bytes[:]) {
   548  		remotePolicy = p1
   549  	} else {
   550  		remotePolicy = p2
   551  	}
   552  
   553  	return remotePolicy, true
   554  }
   555  
   556  // addHopHint creates a hop hint out of the passed channel and channel policy.
   557  // The new hop hint is appended to the passed slice.
   558  func addHopHint(hopHints *[][]zpay32.HopHint,
   559  	channel *HopHintInfo, chanPolicy *channeldb.ChannelEdgePolicy) {
   560  
   561  	hopHint := zpay32.HopHint{
   562  		NodeID:        channel.RemotePubkey,
   563  		ChannelID:     channel.ShortChannelID,
   564  		FeeBaseMAtoms: uint32(chanPolicy.FeeBaseMAtoms),
   565  		FeeProportionalMillionths: uint32(
   566  			chanPolicy.FeeProportionalMillionths,
   567  		),
   568  		CLTVExpiryDelta: chanPolicy.TimeLockDelta,
   569  	}
   570  
   571  	*hopHints = append(*hopHints, []zpay32.HopHint{hopHint})
   572  }
   573  
   574  // HopHintInfo contains the channel information required to create a hop hint.
   575  type HopHintInfo struct {
   576  	// IsPublic indicates whether a channel is advertised to the network.
   577  	IsPublic bool
   578  
   579  	// IsActive indicates whether the channel is online and available for
   580  	// use.
   581  	IsActive bool
   582  
   583  	// FundingOutpoint is the funding txid:index for the channel.
   584  	FundingOutpoint wire.OutPoint
   585  
   586  	// RemotePubkey is the public key of the remote party that this channel
   587  	// is in.
   588  	RemotePubkey *secp256k1.PublicKey
   589  
   590  	// RemoteBalance is the remote party's balance (our current incoming
   591  	// capacity).
   592  	RemoteBalance lnwire.MilliAtom
   593  
   594  	// ShortChannelID is the short channel ID of the channel.
   595  	ShortChannelID uint64
   596  }
   597  
   598  func newHopHintInfo(c *channeldb.OpenChannel, isActive bool) *HopHintInfo {
   599  	isPublic := c.ChannelFlags&lnwire.FFAnnounceChannel != 0
   600  
   601  	return &HopHintInfo{
   602  		IsPublic:        isPublic,
   603  		IsActive:        isActive,
   604  		FundingOutpoint: c.FundingOutpoint,
   605  		RemotePubkey:    c.IdentityPub,
   606  		RemoteBalance:   c.LocalCommitment.RemoteBalance,
   607  		ShortChannelID:  c.ShortChannelID.ToUint64(),
   608  	}
   609  }
   610  
   611  // SelectHopHintsCfg contains the dependencies required to obtain hop hints
   612  // for an invoice.
   613  type SelectHopHintsCfg struct {
   614  	// IsPublicNode is returns a bool indicating whether the node with the
   615  	// given public key is seen as a public node in the graph from the
   616  	// graph's source node's point of view.
   617  	IsPublicNode func(pubKey [33]byte) (bool, error)
   618  
   619  	// FetchChannelEdgesByID attempts to lookup the two directed edges for
   620  	// the channel identified by the channel ID.
   621  	FetchChannelEdgesByID func(chanID uint64) (*channeldb.ChannelEdgeInfo,
   622  		*channeldb.ChannelEdgePolicy, *channeldb.ChannelEdgePolicy,
   623  		error)
   624  }
   625  
   626  func newSelectHopHintsCfg(invoicesCfg *AddInvoiceConfig) *SelectHopHintsCfg {
   627  	return &SelectHopHintsCfg{
   628  		IsPublicNode:          invoicesCfg.Graph.IsPublicNode,
   629  		FetchChannelEdgesByID: invoicesCfg.Graph.FetchChannelEdgesByID,
   630  	}
   631  }
   632  
   633  // SelectHopHints will select up to numMaxHophints from the set of passed open
   634  // channels. The set of hop hints will be returned as a slice of functional
   635  // options that'll append the route hint to the set of all route hints.
   636  //
   637  // TODO(roasbeef): do proper sub-set sum max hints usually << numChans
   638  func SelectHopHints(amtMAtoms lnwire.MilliAtom, cfg *SelectHopHintsCfg,
   639  	openChannels []*HopHintInfo,
   640  	numMaxHophints int) [][]zpay32.HopHint {
   641  
   642  	// We'll add our hop hints in two passes, first we'll add all channels
   643  	// that are eligible to be hop hints, and also have a local balance
   644  	// above the payment amount.
   645  	var totalHintBandwidth lnwire.MilliAtom
   646  	hopHintChans := make(map[wire.OutPoint]struct{})
   647  	hopHints := make([][]zpay32.HopHint, 0, numMaxHophints)
   648  	for _, channel := range openChannels {
   649  		// If this channel can't be a hop hint, then skip it.
   650  		edgePolicy, canBeHopHint := chanCanBeHopHint(channel, cfg)
   651  		if edgePolicy == nil || !canBeHopHint {
   652  			continue
   653  		}
   654  
   655  		// Similarly, in this first pass, we'll ignore all channels in
   656  		// isolation can't satisfy this payment.
   657  		if channel.RemoteBalance < amtMAtoms {
   658  			continue
   659  		}
   660  
   661  		// Now that we now this channel use usable, add it as a hop
   662  		// hint and the indexes we'll use later.
   663  		addHopHint(&hopHints, channel, edgePolicy)
   664  
   665  		hopHintChans[channel.FundingOutpoint] = struct{}{}
   666  		totalHintBandwidth += channel.RemoteBalance
   667  	}
   668  
   669  	// If we have enough hop hints at this point, then we'll exit early.
   670  	// Otherwise, we'll continue to add more that may help out mpp users.
   671  	if len(hopHints) >= numMaxHophints {
   672  		return hopHints
   673  	}
   674  
   675  	// In this second pass we'll add channels, and we'll either stop when
   676  	// we have 20 hop hints, we've run through all the available channels,
   677  	// or if the sum of available bandwidth in the routing hints exceeds 2x
   678  	// the payment amount. We do 2x here to account for a margin of error
   679  	// if some of the selected channels no longer become operable.
   680  	for i := 0; i < len(openChannels); i++ {
   681  		// If we hit either of our early termination conditions, then
   682  		// we'll break the loop here.
   683  		if totalHintBandwidth > amtMAtoms*hopHintFactor ||
   684  			len(hopHints) >= numMaxHophints {
   685  
   686  			break
   687  		}
   688  
   689  		channel := openChannels[i]
   690  
   691  		// Skip the channel if we already selected it.
   692  		if _, ok := hopHintChans[channel.FundingOutpoint]; ok {
   693  			continue
   694  		}
   695  
   696  		// If the channel can't be a hop hint, then we'll skip it.
   697  		// Otherwise, we'll use the policy information to populate the
   698  		// hop hint.
   699  		remotePolicy, canBeHopHint := chanCanBeHopHint(channel, cfg)
   700  		if !canBeHopHint || remotePolicy == nil {
   701  			continue
   702  		}
   703  
   704  		// Include the route hint in our set of options that will be
   705  		// used when creating the invoice.
   706  		addHopHint(&hopHints, channel, remotePolicy)
   707  
   708  		// As we've just added a new hop hint, we'll accumulate it's
   709  		// available balance now to update our tally.
   710  		//
   711  		// TODO(roasbeef): have a cut off based on min bandwidth?
   712  		totalHintBandwidth += channel.RemoteBalance
   713  	}
   714  
   715  	return hopHints
   716  }