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 }