• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 14388908780

10 Apr 2025 07:39PM UTC coverage: 56.811% (-12.3%) from 69.08%
14388908780

Pull #9702

github

web-flow
Merge f006bbf4d into b732525a9
Pull Request #9702: multi: make payment address mandatory

28 of 42 new or added lines in 11 files covered. (66.67%)

23231 existing lines in 283 files now uncovered.

107286 of 188846 relevant lines covered (56.81%)

22749.28 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

34.52
/lnrpc/invoicesrpc/addinvoice.go
1
package invoicesrpc
2

3
import (
4
        "bytes"
5
        "context"
6
        "crypto/rand"
7
        "errors"
8
        "fmt"
9
        "math"
10
        mathRand "math/rand"
11
        "sort"
12
        "time"
13

14
        "github.com/btcsuite/btcd/btcec/v2"
15
        "github.com/btcsuite/btcd/btcec/v2/ecdsa"
16
        "github.com/btcsuite/btcd/btcutil"
17
        "github.com/btcsuite/btcd/chaincfg"
18
        "github.com/btcsuite/btcd/chaincfg/chainhash"
19
        "github.com/btcsuite/btcd/wire"
20
        "github.com/lightningnetwork/lnd/channeldb"
21
        "github.com/lightningnetwork/lnd/graph/db/models"
22
        "github.com/lightningnetwork/lnd/invoices"
23
        "github.com/lightningnetwork/lnd/lntypes"
24
        "github.com/lightningnetwork/lnd/lnutils"
25
        "github.com/lightningnetwork/lnd/lnwire"
26
        "github.com/lightningnetwork/lnd/netann"
27
        "github.com/lightningnetwork/lnd/routing"
28
        "github.com/lightningnetwork/lnd/routing/blindedpath"
29
        "github.com/lightningnetwork/lnd/routing/route"
30
        "github.com/lightningnetwork/lnd/zpay32"
31
)
32

33
const (
34
        // DefaultInvoiceExpiry is the default invoice expiry for new MPP
35
        // invoices.
36
        DefaultInvoiceExpiry = 24 * time.Hour
37

38
        // DefaultAMPInvoiceExpiry is the default invoice expiry for new AMP
39
        // invoices.
40
        DefaultAMPInvoiceExpiry = 30 * 24 * time.Hour
41

42
        // hopHintFactor is factor by which we scale the total amount of
43
        // inbound capacity we want our hop hints to represent, allowing us to
44
        // have some leeway if peers go offline.
45
        hopHintFactor = 2
46

47
        // maxHopHints is the maximum number of hint paths that will be included
48
        // in an invoice.
49
        maxHopHints = 20
50
)
51

52
// AddInvoiceConfig contains dependencies for invoice creation.
53
type AddInvoiceConfig struct {
54
        // AddInvoice is called to add the invoice to the registry.
55
        AddInvoice func(ctx context.Context, invoice *invoices.Invoice,
56
                paymentHash lntypes.Hash) (uint64, error)
57

58
        // IsChannelActive is used to generate valid hop hints.
59
        IsChannelActive func(chanID lnwire.ChannelID) bool
60

61
        // ChainParams are required to properly decode invoice payment requests
62
        // that are marshalled over rpc.
63
        ChainParams *chaincfg.Params
64

65
        // NodeSigner is an implementation of the MessageSigner implementation
66
        // that's backed by the identity private key of the running lnd node.
67
        NodeSigner *netann.NodeSigner
68

69
        // DefaultCLTVExpiry is the default invoice expiry if no values is
70
        // specified.
71
        DefaultCLTVExpiry uint32
72

73
        // ChanDB is a global boltdb instance which is needed to access the
74
        // channel graph.
75
        ChanDB *channeldb.ChannelStateDB
76

77
        // Graph gives the invoice server access to various graph related
78
        // queries.
79
        Graph GraphSource
80

81
        // GenInvoiceFeatures returns a feature containing feature bits that
82
        // should be advertised on freshly generated invoices.
83
        GenInvoiceFeatures func() *lnwire.FeatureVector
84

85
        // GenAmpInvoiceFeatures returns a feature containing feature bits that
86
        // should be advertised on freshly generated AMP invoices.
87
        GenAmpInvoiceFeatures func() *lnwire.FeatureVector
88

89
        // GetAlias allows the peer's alias SCID to be retrieved for private
90
        // option_scid_alias channels.
91
        GetAlias func(lnwire.ChannelID) (lnwire.ShortChannelID, error)
92

93
        // BestHeight returns the current best block height that this node is
94
        // aware of.
95
        BestHeight func() (uint32, error)
96

97
        // QueryBlindedRoutes can be used to generate a few routes to this node
98
        // that can then be used in the construction of a blinded payment path.
99
        QueryBlindedRoutes func(lnwire.MilliSatoshi) ([]*route.Route, error)
100
}
101

102
// AddInvoiceData contains the required data to create a new invoice.
103
type AddInvoiceData struct {
104
        // An optional memo to attach along with the invoice. Used for record
105
        // keeping purposes for the invoice's creator, and will also be set in
106
        // the description field of the encoded payment request if the
107
        // description_hash field is not being used.
108
        Memo string
109

110
        // The preimage which will allow settling an incoming HTLC payable to
111
        // this preimage. If Preimage is set, Hash should be nil. If both
112
        // Preimage and Hash are nil, a random preimage is generated.
113
        Preimage *lntypes.Preimage
114

115
        // The hash of the preimage. If Hash is set, Preimage should be nil.
116
        // This condition indicates that we have a 'hold invoice' for which the
117
        // htlc will be accepted and held until the preimage becomes known.
118
        Hash *lntypes.Hash
119

120
        // The value of this invoice in millisatoshis.
121
        Value lnwire.MilliSatoshi
122

123
        // Hash (SHA-256) of a description of the payment. Used if the
124
        // description of payment (memo) is too long to naturally fit within the
125
        // description field of an encoded payment request.
126
        DescriptionHash []byte
127

128
        // Payment request expiry time in seconds. Default is 3600 (1 hour).
129
        Expiry int64
130

131
        // Fallback on-chain address.
132
        FallbackAddr string
133

134
        // Delta to use for the time-lock of the CLTV extended to the final hop.
135
        CltvExpiry uint64
136

137
        // Whether this invoice should include routing hints for private
138
        // channels.
139
        Private bool
140

141
        // HodlInvoice signals that this invoice shouldn't be settled
142
        // immediately upon receiving the payment.
143
        HodlInvoice bool
144

145
        // Amp signals whether or not to create an AMP invoice.
146
        //
147
        // NOTE: Preimage should always be set to nil when this value is true.
148
        Amp bool
149

150
        // BlindedPathCfg holds the config values to use when constructing
151
        // blinded paths to add to the invoice. A non-nil BlindedPathCfg signals
152
        // that this invoice should disguise the location of the recipient by
153
        // adding blinded payment paths to the invoice instead of revealing the
154
        // destination node's real pub key.
155
        BlindedPathCfg *BlindedPathConfig
156

157
        // RouteHints are optional route hints that can each be individually
158
        // used to assist in reaching the invoice's destination.
159
        RouteHints [][]zpay32.HopHint
160
}
161

162
// BlindedPathConfig holds the configuration values required for blinded path
163
// generation for invoices.
164
type BlindedPathConfig struct {
165
        // RoutePolicyIncrMultiplier is the amount by which policy values for
166
        // hops in a blinded route will be bumped to avoid easy probing. For
167
        // example, a multiplier of 1.1 will bump all appropriate the values
168
        // (base fee, fee rate, CLTV delta and min HLTC) by 10%.
169
        RoutePolicyIncrMultiplier float64
170

171
        // RoutePolicyDecrMultiplier is the amount by which appropriate policy
172
        // values for hops in a blinded route will be decreased to avoid easy
173
        // probing. For example, a multiplier of 0.9 will reduce appropriate
174
        // values (like maximum HTLC) by 10%.
175
        RoutePolicyDecrMultiplier float64
176

177
        // MinNumPathHops is the minimum number of hops that a blinded path
178
        // should be. Dummy hops will be used to pad any route with a length
179
        // less than this.
180
        MinNumPathHops uint8
181

182
        // DefaultDummyHopPolicy holds the default policy values to use for
183
        // dummy hops in a blinded path in the case where they cant be derived
184
        // through other means.
185
        DefaultDummyHopPolicy *blindedpath.BlindedHopPolicy
186
}
187

188
// paymentHashAndPreimage returns the payment hash and preimage for this invoice
189
// depending on the configuration.
190
//
191
// For AMP invoices (when Amp flag is true), this method always returns a nil
192
// preimage. The hash value can be set externally by the user using the Hash
193
// field, or one will be generated randomly. The payment hash here only serves
194
// as a unique identifier for insertion into the invoice index, as there is
195
// no universal preimage for an AMP payment.
196
//
197
// For MPP invoices (when Amp flag is false), this method may return nil
198
// preimage when create a hodl invoice, but otherwise will always return a
199
// non-nil preimage and the corresponding payment hash. The valid combinations
200
// are parsed as follows:
201
//   - Preimage == nil && Hash == nil -> (random preimage, H(random preimage))
202
//   - Preimage != nil && Hash == nil -> (Preimage, H(Preimage))
203
//   - Preimage == nil && Hash != nil -> (nil, Hash)
204
func (d *AddInvoiceData) paymentHashAndPreimage() (
UNCOV
205
        *lntypes.Preimage, lntypes.Hash, error) {
×
UNCOV
206

×
UNCOV
207
        if d.Amp {
×
UNCOV
208
                return d.ampPaymentHashAndPreimage()
×
UNCOV
209
        }
×
210

UNCOV
211
        return d.mppPaymentHashAndPreimage()
×
212
}
213

214
// ampPaymentHashAndPreimage returns the payment hash to use for an AMP invoice.
215
// The preimage will always be nil.
216
func (d *AddInvoiceData) ampPaymentHashAndPreimage() (*lntypes.Preimage,
UNCOV
217
        lntypes.Hash, error) {
×
UNCOV
218

×
UNCOV
219
        switch {
×
220
        // Preimages cannot be set on AMP invoice.
221
        case d.Preimage != nil:
×
222
                return nil, lntypes.Hash{},
×
223
                        errors.New("preimage set on AMP invoice")
×
224

225
        // If a specific hash was requested, use that.
226
        case d.Hash != nil:
×
227
                return nil, *d.Hash, nil
×
228

229
        // Otherwise generate a random hash value, just needs to be unique to be
230
        // added to the invoice index.
UNCOV
231
        default:
×
UNCOV
232
                var paymentHash lntypes.Hash
×
UNCOV
233
                if _, err := rand.Read(paymentHash[:]); err != nil {
×
234
                        return nil, lntypes.Hash{}, err
×
235
                }
×
236

UNCOV
237
                return nil, paymentHash, nil
×
238
        }
239
}
240

241
// mppPaymentHashAndPreimage returns the payment hash and preimage to use for an
242
// MPP invoice.
243
func (d *AddInvoiceData) mppPaymentHashAndPreimage() (*lntypes.Preimage,
UNCOV
244
        lntypes.Hash, error) {
×
UNCOV
245

×
UNCOV
246
        var (
×
UNCOV
247
                paymentPreimage *lntypes.Preimage
×
UNCOV
248
                paymentHash     lntypes.Hash
×
UNCOV
249
        )
×
UNCOV
250

×
UNCOV
251
        switch {
×
252

253
        // Only either preimage or hash can be set.
254
        case d.Preimage != nil && d.Hash != nil:
×
255
                return nil, lntypes.Hash{},
×
256
                        errors.New("preimage and hash both set")
×
257

258
        // If no hash or preimage is given, generate a random preimage.
UNCOV
259
        case d.Preimage == nil && d.Hash == nil:
×
UNCOV
260
                paymentPreimage = &lntypes.Preimage{}
×
UNCOV
261
                if _, err := rand.Read(paymentPreimage[:]); err != nil {
×
262
                        return nil, lntypes.Hash{}, err
×
263
                }
×
UNCOV
264
                paymentHash = paymentPreimage.Hash()
×
265

266
        // If just a hash is given, we create a hold invoice by setting the
267
        // preimage to unknown.
UNCOV
268
        case d.Preimage == nil && d.Hash != nil:
×
UNCOV
269
                paymentHash = *d.Hash
×
270

271
        // A specific preimage was supplied. Use that for the invoice.
UNCOV
272
        case d.Preimage != nil && d.Hash == nil:
×
UNCOV
273
                preimage := *d.Preimage
×
UNCOV
274
                paymentPreimage = &preimage
×
UNCOV
275
                paymentHash = d.Preimage.Hash()
×
276
        }
277

UNCOV
278
        return paymentPreimage, paymentHash, nil
×
279
}
280

281
// AddInvoice attempts to add a new invoice to the invoice database. Any
282
// duplicated invoices are rejected, therefore all invoices *must* have a
283
// unique payment preimage.
284
func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig,
UNCOV
285
        invoice *AddInvoiceData) (*lntypes.Hash, *invoices.Invoice, error) {
×
UNCOV
286

×
UNCOV
287
        blind := invoice.BlindedPathCfg != nil
×
UNCOV
288

×
UNCOV
289
        if invoice.Amp && blind {
×
290
                return nil, nil, fmt.Errorf("AMP invoices with blinded paths " +
×
291
                        "are not yet supported")
×
292
        }
×
293

UNCOV
294
        paymentPreimage, paymentHash, err := invoice.paymentHashAndPreimage()
×
UNCOV
295
        if err != nil {
×
296
                return nil, nil, err
×
297
        }
×
298

299
        // The size of the memo, receipt and description hash attached must not
300
        // exceed the maximum values for either of the fields.
UNCOV
301
        if len(invoice.Memo) > invoices.MaxMemoSize {
×
302
                return nil, nil, fmt.Errorf("memo too large: %v bytes "+
×
303
                        "(maxsize=%v)", len(invoice.Memo),
×
304
                        invoices.MaxMemoSize)
×
305
        }
×
UNCOV
306
        if len(invoice.DescriptionHash) > 0 &&
×
UNCOV
307
                len(invoice.DescriptionHash) != 32 {
×
308

×
309
                return nil, nil, fmt.Errorf("description hash is %v bytes, "+
×
310
                        "must be 32", len(invoice.DescriptionHash))
×
311
        }
×
312

313
        // We set the max invoice amount to 100k BTC, which itself is several
314
        // multiples off the current block reward.
UNCOV
315
        maxInvoiceAmt := btcutil.Amount(btcutil.SatoshiPerBitcoin * 100000)
×
UNCOV
316

×
UNCOV
317
        switch {
×
318
        // The value of the invoice must not be negative.
319
        case int64(invoice.Value) < 0:
×
320
                return nil, nil, fmt.Errorf("payments of negative value "+
×
321
                        "are not allowed, value is %v", int64(invoice.Value))
×
322

323
        // Also ensure that the invoice is actually realistic, while preventing
324
        // any issues due to underflow.
325
        case invoice.Value.ToSatoshis() > maxInvoiceAmt:
×
326
                return nil, nil, fmt.Errorf("invoice amount %v is "+
×
327
                        "too large, max is %v", invoice.Value.ToSatoshis(),
×
328
                        maxInvoiceAmt)
×
329
        }
330

UNCOV
331
        amtMSat := invoice.Value
×
UNCOV
332

×
UNCOV
333
        // We also create an encoded payment request which allows the
×
UNCOV
334
        // caller to compactly send the invoice to the payer. We'll create a
×
UNCOV
335
        // list of options to be added to the encoded payment request. For now
×
UNCOV
336
        // we only support the required fields description/description_hash,
×
UNCOV
337
        // expiry, fallback address, and the amount field.
×
UNCOV
338
        var options []func(*zpay32.Invoice)
×
UNCOV
339

×
UNCOV
340
        // We only include the amount in the invoice if it is greater than 0.
×
UNCOV
341
        // By not including the amount, we enable the creation of invoices that
×
UNCOV
342
        // allow the payer to specify the amount of satoshis they wish to send.
×
UNCOV
343
        if amtMSat > 0 {
×
UNCOV
344
                options = append(options, zpay32.Amount(amtMSat))
×
UNCOV
345
        }
×
346

347
        // If specified, add a fallback address to the payment request.
UNCOV
348
        if len(invoice.FallbackAddr) > 0 {
×
349
                addr, err := btcutil.DecodeAddress(
×
350
                        invoice.FallbackAddr, cfg.ChainParams,
×
351
                )
×
352
                if err != nil {
×
353
                        return nil, nil, fmt.Errorf("invalid fallback "+
×
354
                                "address: %v", err)
×
355
                }
×
356

357
                if !addr.IsForNet(cfg.ChainParams) {
×
358
                        return nil, nil, fmt.Errorf("fallback address is not "+
×
359
                                "for %s", cfg.ChainParams.Name)
×
360
                }
×
361

362
                options = append(options, zpay32.FallbackAddr(addr))
×
363
        }
364

UNCOV
365
        var expiry time.Duration
×
UNCOV
366
        switch {
×
367
        // An invoice expiry has been provided by the caller.
368
        case invoice.Expiry > 0:
×
369

×
370
                // We'll ensure that the specified expiry is restricted to sane
×
371
                // number of seconds. As a result, we'll reject an invoice with
×
372
                // an expiry greater than 1 year.
×
373
                maxExpiry := time.Hour * 24 * 365
×
374
                expSeconds := invoice.Expiry
×
375

×
376
                if float64(expSeconds) > maxExpiry.Seconds() {
×
377
                        return nil, nil, fmt.Errorf("expiry of %v seconds "+
×
378
                                "greater than max expiry of %v seconds",
×
379
                                float64(expSeconds), maxExpiry.Seconds())
×
380
                }
×
381

382
                expiry = time.Duration(invoice.Expiry) * time.Second
×
383

384
        // If no custom expiry is provided, use the default MPP expiry.
UNCOV
385
        case !invoice.Amp:
×
UNCOV
386
                expiry = DefaultInvoiceExpiry
×
387

388
        // Otherwise, use the default AMP expiry.
UNCOV
389
        default:
×
UNCOV
390
                expiry = DefaultAMPInvoiceExpiry
×
391
        }
392

UNCOV
393
        options = append(options, zpay32.Expiry(expiry))
×
UNCOV
394

×
UNCOV
395
        // If the description hash is set, then we add it do the list of
×
UNCOV
396
        // options. If not, use the memo field as the payment request
×
UNCOV
397
        // description.
×
UNCOV
398
        if len(invoice.DescriptionHash) > 0 {
×
399
                var descHash [32]byte
×
400
                copy(descHash[:], invoice.DescriptionHash[:])
×
401
                options = append(options, zpay32.DescriptionHash(descHash))
×
UNCOV
402
        } else {
×
UNCOV
403
                // Use the memo field as the description. If this is not set
×
UNCOV
404
                // this will just be an empty string.
×
UNCOV
405
                options = append(options, zpay32.Description(invoice.Memo))
×
UNCOV
406
        }
×
407

UNCOV
408
        if invoice.CltvExpiry > routing.MaxCLTVDelta {
×
409
                return nil, nil, fmt.Errorf("CLTV delta of %v is too large, "+
×
410
                        "max accepted is: %v", invoice.CltvExpiry,
×
411
                        math.MaxUint16)
×
412
        }
×
413

414
        // We'll use our current default CLTV value unless one was specified as
415
        // an option on the command line when creating an invoice.
UNCOV
416
        cltvExpiryDelta := uint64(cfg.DefaultCLTVExpiry)
×
UNCOV
417
        if invoice.CltvExpiry != 0 {
×
UNCOV
418
                // Disallow user-chosen final CLTV deltas below the required
×
UNCOV
419
                // minimum.
×
UNCOV
420
                if invoice.CltvExpiry < routing.MinCLTVDelta {
×
421
                        return nil, nil, fmt.Errorf("CLTV delta of %v must be "+
×
422
                                "greater than minimum of %v",
×
423
                                invoice.CltvExpiry, routing.MinCLTVDelta)
×
424
                }
×
425

UNCOV
426
                cltvExpiryDelta = invoice.CltvExpiry
×
427
        }
428

429
        // Only include a final CLTV expiry delta if this is not a blinded
430
        // invoice. In a blinded invoice, this value will be added to the total
431
        // blinded route CLTV delta value
UNCOV
432
        if !blind {
×
UNCOV
433
                options = append(options, zpay32.CLTVExpiry(cltvExpiryDelta))
×
UNCOV
434
        }
×
435

436
        // We make sure that the given invoice routing hints number is within
437
        // the valid range
UNCOV
438
        if len(invoice.RouteHints) > maxHopHints {
×
439
                return nil, nil, fmt.Errorf("number of routing hints must "+
×
440
                        "not exceed maximum of %v", maxHopHints)
×
441
        }
×
442

443
        // Include route hints if needed.
UNCOV
444
        if len(invoice.RouteHints) > 0 || invoice.Private {
×
UNCOV
445
                if blind {
×
446
                        return nil, nil, fmt.Errorf("can't set both hop " +
×
447
                                "hints and add blinded payment paths")
×
448
                }
×
449

450
                // Validate provided hop hints.
UNCOV
451
                for _, hint := range invoice.RouteHints {
×
UNCOV
452
                        if len(hint) == 0 {
×
453
                                return nil, nil, fmt.Errorf("number of hop " +
×
454
                                        "hint within a route must be positive")
×
455
                        }
×
456
                }
457

UNCOV
458
                totalHopHints := len(invoice.RouteHints)
×
UNCOV
459
                if invoice.Private {
×
UNCOV
460
                        totalHopHints = maxHopHints
×
UNCOV
461
                }
×
462

UNCOV
463
                hopHintsCfg := newSelectHopHintsCfg(cfg, totalHopHints)
×
UNCOV
464
                hopHints, err := PopulateHopHints(
×
UNCOV
465
                        hopHintsCfg, amtMSat, invoice.RouteHints,
×
UNCOV
466
                )
×
UNCOV
467
                if err != nil {
×
468
                        return nil, nil, fmt.Errorf("unable to populate hop "+
×
469
                                "hints: %v", err)
×
470
                }
×
471

472
                // Convert our set of selected hop hints into route
473
                // hints and add to our invoice options.
UNCOV
474
                for _, hopHint := range hopHints {
×
UNCOV
475
                        routeHint := zpay32.RouteHint(hopHint)
×
UNCOV
476

×
UNCOV
477
                        options = append(
×
UNCOV
478
                                options, routeHint,
×
UNCOV
479
                        )
×
UNCOV
480
                }
×
481
        }
482

483
        // Set our desired invoice features and add them to our list of options.
UNCOV
484
        var invoiceFeatures *lnwire.FeatureVector
×
UNCOV
485
        if invoice.Amp {
×
UNCOV
486
                invoiceFeatures = cfg.GenAmpInvoiceFeatures()
×
UNCOV
487
        } else {
×
UNCOV
488
                invoiceFeatures = cfg.GenInvoiceFeatures()
×
UNCOV
489
        }
×
UNCOV
490
        options = append(options, zpay32.Features(invoiceFeatures))
×
UNCOV
491

×
UNCOV
492
        // Generate and set a random payment address for this payment. If the
×
UNCOV
493
        // sender understands payment addresses, this can be used to avoid
×
UNCOV
494
        // intermediaries probing the receiver. If the invoice does not have
×
UNCOV
495
        // blinded paths, then this will be encoded in the invoice itself.
×
UNCOV
496
        // Otherwise, it will instead be embedded in the encrypted recipient
×
UNCOV
497
        // data of blinded paths. In the blinded path case, this will be used
×
UNCOV
498
        // for the PathID.
×
UNCOV
499
        var paymentAddr [32]byte
×
UNCOV
500
        if _, err := rand.Read(paymentAddr[:]); err != nil {
×
501
                return nil, nil, err
×
502
        }
×
503

UNCOV
504
        if blind {
×
UNCOV
505
                blindCfg := invoice.BlindedPathCfg
×
UNCOV
506

×
UNCOV
507
                // Use the 10-min-per-block assumption to get a rough estimate
×
UNCOV
508
                // of the number of blocks until the invoice expires. We want
×
UNCOV
509
                // to make sure that the blinded path definitely does not expire
×
UNCOV
510
                // before the invoice does, and so we add a healthy buffer.
×
UNCOV
511
                invoiceExpiry := uint32(expiry.Minutes() / 10)
×
UNCOV
512
                blindedPathExpiry := invoiceExpiry * 2
×
UNCOV
513

×
UNCOV
514
                // Add BlockPadding to the finalCltvDelta so that the receiving
×
UNCOV
515
                // node does not reject the HTLC if some blocks are mined while
×
UNCOV
516
                // the payment is in-flight. Note that unlike vanilla invoices,
×
UNCOV
517
                // with blinded paths, the recipient is responsible for adding
×
UNCOV
518
                // this block padding instead of the sender.
×
UNCOV
519
                finalCLTVDelta := uint32(cltvExpiryDelta)
×
UNCOV
520
                finalCLTVDelta += uint32(routing.BlockPadding)
×
UNCOV
521

×
UNCOV
522
                //nolint:ll
×
UNCOV
523
                paths, err := blindedpath.BuildBlindedPaymentPaths(
×
UNCOV
524
                        &blindedpath.BuildBlindedPathCfg{
×
UNCOV
525
                                FindRoutes:              cfg.QueryBlindedRoutes,
×
UNCOV
526
                                FetchChannelEdgesByID:   cfg.Graph.FetchChannelEdgesByID,
×
UNCOV
527
                                FetchOurOpenChannels:    cfg.ChanDB.FetchAllOpenChannels,
×
UNCOV
528
                                PathID:                  paymentAddr[:],
×
UNCOV
529
                                ValueMsat:               invoice.Value,
×
UNCOV
530
                                BestHeight:              cfg.BestHeight,
×
UNCOV
531
                                MinFinalCLTVExpiryDelta: finalCLTVDelta,
×
UNCOV
532
                                BlocksUntilExpiry:       blindedPathExpiry,
×
UNCOV
533
                                AddPolicyBuffer: func(
×
UNCOV
534
                                        p *blindedpath.BlindedHopPolicy) (
×
UNCOV
535
                                        *blindedpath.BlindedHopPolicy, error) {
×
UNCOV
536

×
UNCOV
537
                                        //nolint:ll
×
UNCOV
538
                                        return blindedpath.AddPolicyBuffer(
×
UNCOV
539
                                                p, blindCfg.RoutePolicyIncrMultiplier,
×
UNCOV
540
                                                blindCfg.RoutePolicyDecrMultiplier,
×
UNCOV
541
                                        )
×
UNCOV
542
                                },
×
543
                                MinNumHops:            blindCfg.MinNumPathHops,
544
                                DefaultDummyHopPolicy: blindCfg.DefaultDummyHopPolicy,
545
                        },
546
                )
UNCOV
547
                if err != nil {
×
548
                        return nil, nil, err
×
549
                }
×
550

UNCOV
551
                for _, path := range paths {
×
UNCOV
552
                        options = append(options, zpay32.WithBlindedPaymentPath(
×
UNCOV
553
                                path,
×
UNCOV
554
                        ))
×
UNCOV
555
                }
×
556
        }
557

558
        // Create and encode the payment request as a bech32 (zpay32) string.
UNCOV
559
        creationDate := time.Now()
×
UNCOV
560
        payReq, err := zpay32.NewInvoice(
×
NEW
561
                cfg.ChainParams, paymentHash, creationDate, paymentAddr, options...,
×
UNCOV
562
        )
×
UNCOV
563
        if err != nil {
×
564
                return nil, nil, err
×
565
        }
×
566

UNCOV
567
        payReqString, err := payReq.Encode(zpay32.MessageSigner{
×
UNCOV
568
                SignCompact: func(msg []byte) ([]byte, error) {
×
UNCOV
569
                        // For an invoice without a blinded path, the main node
×
UNCOV
570
                        // key is used to sign the invoice so that the sender
×
UNCOV
571
                        // can derive the true pub key of the recipient.
×
UNCOV
572
                        if !blind {
×
UNCOV
573
                                return cfg.NodeSigner.SignMessageCompact(
×
UNCOV
574
                                        msg, false,
×
UNCOV
575
                                )
×
UNCOV
576
                        }
×
577

578
                        // For an invoice with a blinded path, we use an
579
                        // ephemeral key to sign the invoice since we don't want
580
                        // the sender to be able to know the real pub key of
581
                        // the recipient.
UNCOV
582
                        ephemKey, err := btcec.NewPrivateKey()
×
UNCOV
583
                        if err != nil {
×
584
                                return nil, err
×
585
                        }
×
586

UNCOV
587
                        return ecdsa.SignCompact(
×
UNCOV
588
                                ephemKey, chainhash.HashB(msg), true,
×
UNCOV
589
                        ), nil
×
590
                },
591
        })
UNCOV
592
        if err != nil {
×
593
                return nil, nil, err
×
594
        }
×
595

UNCOV
596
        newInvoice := &invoices.Invoice{
×
UNCOV
597
                CreationDate:   creationDate,
×
UNCOV
598
                Memo:           []byte(invoice.Memo),
×
UNCOV
599
                PaymentRequest: []byte(payReqString),
×
UNCOV
600
                Terms: invoices.ContractTerm{
×
UNCOV
601
                        FinalCltvDelta:  int32(payReq.MinFinalCLTVExpiry()),
×
UNCOV
602
                        Expiry:          payReq.Expiry(),
×
UNCOV
603
                        Value:           amtMSat,
×
UNCOV
604
                        PaymentPreimage: paymentPreimage,
×
UNCOV
605
                        PaymentAddr:     paymentAddr,
×
UNCOV
606
                        Features:        invoiceFeatures,
×
UNCOV
607
                },
×
UNCOV
608
                HodlInvoice: invoice.HodlInvoice,
×
UNCOV
609
        }
×
UNCOV
610

×
UNCOV
611
        log.Tracef("[addinvoice] adding new invoice %v",
×
UNCOV
612
                lnutils.SpewLogClosure(newInvoice))
×
UNCOV
613

×
UNCOV
614
        // With all sanity checks passed, write the invoice to the database.
×
UNCOV
615
        _, err = cfg.AddInvoice(ctx, newInvoice, paymentHash)
×
UNCOV
616
        if err != nil {
×
617
                return nil, nil, err
×
618
        }
×
619

UNCOV
620
        return &paymentHash, newInvoice, nil
×
621
}
622

623
// chanCanBeHopHint returns true if the target channel is eligible to be a hop
624
// hint.
625
func chanCanBeHopHint(channel *HopHintInfo, cfg *SelectHopHintsCfg) (
626
        *models.ChannelEdgePolicy, bool) {
17✔
627

17✔
628
        // Since we're only interested in our private channels, we'll skip
17✔
629
        // public ones.
17✔
630
        if channel.IsPublic {
18✔
631
                return nil, false
1✔
632
        }
1✔
633

634
        // Make sure the channel is active.
635
        if !channel.IsActive {
17✔
636
                log.Debugf("Skipping channel %v due to not "+
1✔
637
                        "being eligible to forward payments",
1✔
638
                        channel.ShortChannelID)
1✔
639
                return nil, false
1✔
640
        }
1✔
641

642
        // To ensure we don't leak unadvertised nodes, we'll make sure our
643
        // counterparty is publicly advertised within the network.  Otherwise,
644
        // we'll end up leaking information about nodes that intend to stay
645
        // unadvertised, like in the case of a node only having private
646
        // channels.
647
        var remotePub [33]byte
15✔
648
        copy(remotePub[:], channel.RemotePubkey.SerializeCompressed())
15✔
649
        isRemoteNodePublic, err := cfg.IsPublicNode(remotePub)
15✔
650
        if err != nil {
15✔
651
                log.Errorf("Unable to determine if node %x "+
×
652
                        "is advertised: %v", remotePub, err)
×
653
                return nil, false
×
654
        }
×
655

656
        if !isRemoteNodePublic {
16✔
657
                log.Debugf("Skipping channel %v due to "+
1✔
658
                        "counterparty %x being unadvertised",
1✔
659
                        channel.ShortChannelID, remotePub)
1✔
660
                return nil, false
1✔
661
        }
1✔
662

663
        // Fetch the policies for each end of the channel.
664
        info, p1, p2, err := cfg.FetchChannelEdgesByID(channel.ShortChannelID)
14✔
665
        if err != nil {
15✔
666
                // In the case of zero-conf channels, it may be the case that
1✔
667
                // the alias SCID was deleted from the graph, and replaced by
1✔
668
                // the confirmed SCID. Check the Graph for the confirmed SCID.
1✔
669
                confirmedScid := channel.ConfirmedScidZC
1✔
670
                info, p1, p2, err = cfg.FetchChannelEdgesByID(confirmedScid)
1✔
671
                if err != nil {
2✔
672
                        log.Errorf("Unable to fetch the routing policies for "+
1✔
673
                                "the edges of the channel %v: %v",
1✔
674
                                channel.ShortChannelID, err)
1✔
675
                        return nil, false
1✔
676
                }
1✔
677
        }
678

679
        // Now, we'll need to determine which is the correct policy for HTLCs
680
        // being sent from the remote node.
681
        var remotePolicy *models.ChannelEdgePolicy
13✔
682
        if bytes.Equal(remotePub[:], info.NodeKey1Bytes[:]) {
14✔
683
                remotePolicy = p1
1✔
684
        } else {
13✔
685
                remotePolicy = p2
12✔
686
        }
12✔
687

688
        return remotePolicy, true
13✔
689
}
690

691
// HopHintInfo contains the channel information required to create a hop hint.
692
type HopHintInfo struct {
693
        // IsPublic indicates whether a channel is advertised to the network.
694
        IsPublic bool
695

696
        // IsActive indicates whether the channel is online and available for
697
        // use.
698
        IsActive bool
699

700
        // FundingOutpoint is the funding txid:index for the channel.
701
        FundingOutpoint wire.OutPoint
702

703
        // RemotePubkey is the public key of the remote party that this channel
704
        // is in.
705
        RemotePubkey *btcec.PublicKey
706

707
        // RemoteBalance is the remote party's balance (our current incoming
708
        // capacity).
709
        RemoteBalance lnwire.MilliSatoshi
710

711
        // ShortChannelID is the short channel ID of the channel.
712
        ShortChannelID uint64
713

714
        // ConfirmedScidZC is the confirmed SCID of a zero-conf channel. This
715
        // may be used for looking up a channel in the graph.
716
        ConfirmedScidZC uint64
717

718
        // ScidAliasFeature denotes whether the channel has negotiated the
719
        // option-scid-alias feature bit.
720
        ScidAliasFeature bool
721
}
722

723
func newHopHintInfo(c *channeldb.OpenChannel, isActive bool) *HopHintInfo {
17✔
724
        isPublic := c.ChannelFlags&lnwire.FFAnnounceChannel != 0
17✔
725

17✔
726
        return &HopHintInfo{
17✔
727
                IsPublic:         isPublic,
17✔
728
                IsActive:         isActive,
17✔
729
                FundingOutpoint:  c.FundingOutpoint,
17✔
730
                RemotePubkey:     c.IdentityPub,
17✔
731
                RemoteBalance:    c.LocalCommitment.RemoteBalance,
17✔
732
                ShortChannelID:   c.ShortChannelID.ToUint64(),
17✔
733
                ConfirmedScidZC:  c.ZeroConfRealScid().ToUint64(),
17✔
734
                ScidAliasFeature: c.ChanType.HasScidAliasFeature(),
17✔
735
        }
17✔
736
}
17✔
737

738
// newHopHint returns a new hop hint using the relevant data from a hopHintInfo
739
// and a ChannelEdgePolicy.
740
func newHopHint(hopHintInfo *HopHintInfo,
741
        chanPolicy *models.ChannelEdgePolicy) zpay32.HopHint {
11✔
742

11✔
743
        return zpay32.HopHint{
11✔
744
                NodeID:      hopHintInfo.RemotePubkey,
11✔
745
                ChannelID:   hopHintInfo.ShortChannelID,
11✔
746
                FeeBaseMSat: uint32(chanPolicy.FeeBaseMSat),
11✔
747
                FeeProportionalMillionths: uint32(
11✔
748
                        chanPolicy.FeeProportionalMillionths,
11✔
749
                ),
11✔
750
                CLTVExpiryDelta: chanPolicy.TimeLockDelta,
11✔
751
        }
11✔
752
}
11✔
753

754
// SelectHopHintsCfg contains the dependencies required to obtain hop hints
755
// for an invoice.
756
type SelectHopHintsCfg struct {
757
        // IsPublicNode is returns a bool indicating whether the node with the
758
        // given public key is seen as a public node in the graph from the
759
        // graph's source node's point of view.
760
        IsPublicNode func(pubKey [33]byte) (bool, error)
761

762
        // FetchChannelEdgesByID attempts to lookup the two directed edges for
763
        // the channel identified by the channel ID.
764
        FetchChannelEdgesByID func(chanID uint64) (*models.ChannelEdgeInfo,
765
                *models.ChannelEdgePolicy, *models.ChannelEdgePolicy,
766
                error)
767

768
        // GetAlias allows the peer's alias SCID to be retrieved for private
769
        // option_scid_alias channels.
770
        GetAlias func(lnwire.ChannelID) (lnwire.ShortChannelID, error)
771

772
        // FetchAllChannels retrieves all open channels currently stored
773
        // within the database.
774
        FetchAllChannels func() ([]*channeldb.OpenChannel, error)
775

776
        // IsChannelActive checks whether the channel identified by the provided
777
        // ChannelID is considered active.
778
        IsChannelActive func(chanID lnwire.ChannelID) bool
779

780
        // MaxHopHints is the maximum number of hop hints we are interested in.
781
        MaxHopHints int
782
}
783

784
func newSelectHopHintsCfg(invoicesCfg *AddInvoiceConfig,
UNCOV
785
        maxHopHints int) *SelectHopHintsCfg {
×
UNCOV
786

×
UNCOV
787
        return &SelectHopHintsCfg{
×
UNCOV
788
                FetchAllChannels:      invoicesCfg.ChanDB.FetchAllChannels,
×
UNCOV
789
                IsChannelActive:       invoicesCfg.IsChannelActive,
×
UNCOV
790
                IsPublicNode:          invoicesCfg.Graph.IsPublicNode,
×
UNCOV
791
                FetchChannelEdgesByID: invoicesCfg.Graph.FetchChannelEdgesByID,
×
UNCOV
792
                GetAlias:              invoicesCfg.GetAlias,
×
UNCOV
793
                MaxHopHints:           maxHopHints,
×
UNCOV
794
        }
×
UNCOV
795
}
×
796

797
// sufficientHints checks whether we have sufficient hop hints, based on the
798
// any of the following criteria:
799
//   - Hop hint count: the number of hints have reach our max target.
800
//   - Total incoming capacity (for non-zero invoice amounts): the sum of the
801
//     remote balance amount in the hints is bigger of equal than our target
802
//     (currently twice the invoice amount)
803
//
804
// We limit our number of hop hints like this to keep our invoice size down,
805
// and to avoid leaking all our private channels when we don't need to.
806
func sufficientHints(nHintsLeft int, currentAmount,
807
        targetAmount lnwire.MilliSatoshi) bool {
16✔
808

16✔
809
        if nHintsLeft <= 0 {
20✔
810
                log.Debugf("Reached targeted number of hop hints")
4✔
811
                return true
4✔
812
        }
4✔
813

814
        if targetAmount != 0 && currentAmount >= targetAmount {
14✔
815
                log.Debugf("Total hint amount: %v has reached target hint "+
2✔
816
                        "bandwidth: %v", currentAmount, targetAmount)
2✔
817
                return true
2✔
818
        }
2✔
819

820
        return false
10✔
821
}
822

823
// getPotentialHints returns a slice of open channels that should be considered
824
// for the hopHint list in an invoice. The slice is sorted in descending order
825
// based on the remote balance.
826
func getPotentialHints(cfg *SelectHopHintsCfg) ([]*channeldb.OpenChannel,
827
        error) {
6✔
828

6✔
829
        // TODO(positiveblue): get the channels slice already filtered by
6✔
830
        // private == true and sorted by RemoteBalance?
6✔
831
        openChannels, err := cfg.FetchAllChannels()
6✔
832
        if err != nil {
6✔
833
                return nil, err
×
834
        }
×
835

836
        privateChannels := make([]*channeldb.OpenChannel, 0, len(openChannels))
6✔
837
        for _, oc := range openChannels {
18✔
838
                isPublic := oc.ChannelFlags&lnwire.FFAnnounceChannel != 0
12✔
839
                if !isPublic {
24✔
840
                        privateChannels = append(privateChannels, oc)
12✔
841
                }
12✔
842
        }
843

844
        // Sort the channels in descending remote balance.
845
        compareRemoteBalance := func(i, j int) bool {
12✔
846
                iBalance := privateChannels[i].LocalCommitment.RemoteBalance
6✔
847
                jBalance := privateChannels[j].LocalCommitment.RemoteBalance
6✔
848
                return iBalance > jBalance
6✔
849
        }
6✔
850
        sort.Slice(privateChannels, compareRemoteBalance)
6✔
851

6✔
852
        return privateChannels, nil
6✔
853
}
854

855
// shouldIncludeChannel returns true if the channel passes all the checks to
856
// be a hopHint in a given invoice.
857
func shouldIncludeChannel(cfg *SelectHopHintsCfg,
858
        channel *channeldb.OpenChannel,
859
        alreadyIncluded map[uint64]bool) (zpay32.HopHint, lnwire.MilliSatoshi,
860
        bool) {
18✔
861

18✔
862
        if _, ok := alreadyIncluded[channel.ShortChannelID.ToUint64()]; ok {
19✔
863
                return zpay32.HopHint{}, 0, false
1✔
864
        }
1✔
865

866
        chanID := lnwire.NewChanIDFromOutPoint(
17✔
867
                channel.FundingOutpoint,
17✔
868
        )
17✔
869

17✔
870
        hopHintInfo := newHopHintInfo(channel, cfg.IsChannelActive(chanID))
17✔
871

17✔
872
        // If this channel can't be a hop hint, then skip it.
17✔
873
        edgePolicy, canBeHopHint := chanCanBeHopHint(hopHintInfo, cfg)
17✔
874
        if edgePolicy == nil || !canBeHopHint {
21✔
875
                return zpay32.HopHint{}, 0, false
4✔
876
        }
4✔
877

878
        if hopHintInfo.ScidAliasFeature {
16✔
879
                alias, err := cfg.GetAlias(chanID)
3✔
880
                if err != nil {
3✔
881
                        return zpay32.HopHint{}, 0, false
×
882
                }
×
883

884
                if alias.IsDefault() || alreadyIncluded[alias.ToUint64()] {
5✔
885
                        return zpay32.HopHint{}, 0, false
2✔
886
                }
2✔
887

888
                hopHintInfo.ShortChannelID = alias.ToUint64()
1✔
889
        }
890

891
        // Now that we know this channel use usable, add it as a hop hint and
892
        // the indexes we'll use later.
893
        hopHint := newHopHint(hopHintInfo, edgePolicy)
11✔
894
        return hopHint, hopHintInfo.RemoteBalance, true
11✔
895
}
896

897
// selectHopHints iterates a list of potential hints selecting the valid hop
898
// hints until we have enough hints or run out of channels.
899
//
900
// NOTE: selectHopHints expects potentialHints to be already sorted in
901
// descending priority.
902
func selectHopHints(cfg *SelectHopHintsCfg, nHintsLeft int,
903
        targetBandwidth lnwire.MilliSatoshi,
904
        potentialHints []*channeldb.OpenChannel,
905
        alreadyIncluded map[uint64]bool) [][]zpay32.HopHint {
6✔
906

6✔
907
        currentBandwidth := lnwire.MilliSatoshi(0)
6✔
908
        hopHints := make([][]zpay32.HopHint, 0, nHintsLeft)
6✔
909
        for _, channel := range potentialHints {
18✔
910
                enoughHopHints := sufficientHints(
12✔
911
                        nHintsLeft, currentBandwidth, targetBandwidth,
12✔
912
                )
12✔
913
                if enoughHopHints {
16✔
914
                        return hopHints
4✔
915
                }
4✔
916

917
                hopHint, remoteBalance, include := shouldIncludeChannel(
8✔
918
                        cfg, channel, alreadyIncluded,
8✔
919
                )
8✔
920

8✔
921
                if include {
16✔
922
                        // Now that we now this channel use usable, add it as a hop
8✔
923
                        // hint and the indexes we'll use later.
8✔
924
                        hopHints = append(hopHints, []zpay32.HopHint{hopHint})
8✔
925
                        currentBandwidth += remoteBalance
8✔
926
                        nHintsLeft--
8✔
927
                }
8✔
928
        }
929

930
        // We do not want to leak information about how our remote balance is
931
        // distributed in our private channels. We shuffle the selected ones
932
        // here so they do not appear in order in the invoice.
933
        mathRand.Shuffle(
2✔
934
                len(hopHints), func(i, j int) {
4✔
935
                        hopHints[i], hopHints[j] = hopHints[j], hopHints[i]
2✔
936
                },
2✔
937
        )
938
        return hopHints
2✔
939
}
940

941
// PopulateHopHints will select up to cfg.MaxHophints from the current open
942
// channels. The set of hop hints will be returned as a slice of functional
943
// options that'll append the route hint to the set of all route hints.
944
//
945
// TODO(roasbeef): do proper sub-set sum max hints usually << numChans.
946
func PopulateHopHints(cfg *SelectHopHintsCfg, amtMSat lnwire.MilliSatoshi,
947
        forcedHints [][]zpay32.HopHint) ([][]zpay32.HopHint, error) {
7✔
948

7✔
949
        hopHints := forcedHints
7✔
950

7✔
951
        // If we already have enough hints we don't need to add any more.
7✔
952
        nHintsLeft := cfg.MaxHopHints - len(hopHints)
7✔
953
        if nHintsLeft <= 0 {
8✔
954
                return hopHints, nil
1✔
955
        }
1✔
956

957
        alreadyIncluded := make(map[uint64]bool)
6✔
958
        for _, hopHint := range hopHints {
6✔
959
                alreadyIncluded[hopHint[0].ChannelID] = true
×
960
        }
×
961

962
        potentialHints, err := getPotentialHints(cfg)
6✔
963
        if err != nil {
6✔
964
                return nil, err
×
965
        }
×
966

967
        targetBandwidth := amtMSat * hopHintFactor
6✔
968
        selectedHints := selectHopHints(
6✔
969
                cfg, nHintsLeft, targetBandwidth, potentialHints,
6✔
970
                alreadyIncluded,
6✔
971
        )
6✔
972

6✔
973
        hopHints = append(hopHints, selectedHints...)
6✔
974
        return hopHints, nil
6✔
975
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc