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

lightningnetwork / lnd / 13907742068

17 Mar 2025 07:03PM UTC coverage: 58.307% (-0.008%) from 58.315%
13907742068

Pull #9334

github

web-flow
Merge 1b389e91e into 053d63e11
Pull Request #9334: Use all valid routes during blinded path construction

103 of 117 new or added lines in 4 files covered. (88.03%)

49 existing lines in 12 files now uncovered.

94796 of 162581 relevant lines covered (58.31%)

1.81 hits per line

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

86.03
/routing/pathfind.go
1
package routing
2

3
import (
4
        "bytes"
5
        "container/heap"
6
        "errors"
7
        "fmt"
8
        "math"
9
        "sort"
10
        "time"
11

12
        "github.com/btcsuite/btcd/btcutil"
13
        sphinx "github.com/lightningnetwork/lightning-onion"
14
        "github.com/lightningnetwork/lnd/feature"
15
        "github.com/lightningnetwork/lnd/fn/v2"
16
        graphdb "github.com/lightningnetwork/lnd/graph/db"
17
        "github.com/lightningnetwork/lnd/graph/db/models"
18
        "github.com/lightningnetwork/lnd/lnutils"
19
        "github.com/lightningnetwork/lnd/lnwire"
20
        "github.com/lightningnetwork/lnd/record"
21
        "github.com/lightningnetwork/lnd/routing/blindedpath"
22
        "github.com/lightningnetwork/lnd/routing/route"
23
)
24

25
const (
26
        // infinity is used as a starting distance in our shortest path search.
27
        infinity = math.MaxInt64
28

29
        // RiskFactorBillionths controls the influence of time lock delta
30
        // of a channel on route selection. It is expressed as billionths
31
        // of msat per msat sent through the channel per time lock delta
32
        // block. See edgeWeight function below for more details.
33
        // The chosen value is based on the previous incorrect weight function
34
        // 1 + timelock + fee * fee. In this function, the fee penalty
35
        // diminishes the time lock penalty for all but the smallest amounts.
36
        // To not change the behaviour of path finding too drastically, a
37
        // relatively small value is chosen which is still big enough to give
38
        // some effect with smaller time lock values. The value may need
39
        // tweaking and/or be made configurable in the future.
40
        RiskFactorBillionths = 15
41

42
        // estimatedNodeCount is used to preallocate the path finding structures
43
        // to avoid resizing and copies. It should be number on the same order as
44
        // the number of active nodes in the network.
45
        estimatedNodeCount = 10000
46

47
        // fakeHopHintCapacity is the capacity we assume for hop hint channels.
48
        // This is a high number, which expresses that a hop hint channel should
49
        // be able to route payments.
50
        fakeHopHintCapacity = btcutil.Amount(10 * btcutil.SatoshiPerBitcoin)
51
)
52

53
// pathFinder defines the interface of a path finding algorithm.
54
type pathFinder = func(g *graphParams, r *RestrictParams,
55
        cfg *PathFindingConfig, self, source, target route.Vertex,
56
        amt lnwire.MilliSatoshi, timePref float64, finalHtlcExpiry int32) (
57
        []*unifiedEdge, float64, error)
58

59
var (
60
        // DefaultEstimator is the default estimator used for computing
61
        // probabilities in pathfinding.
62
        DefaultEstimator = AprioriEstimatorName
63

64
        // DefaultAttemptCost is the default fixed virtual cost in path finding
65
        // of a failed payment attempt. It is used to trade off potentially
66
        // better routes against their probability of succeeding.
67
        DefaultAttemptCost = lnwire.NewMSatFromSatoshis(100)
68

69
        // DefaultAttemptCostPPM is the default proportional virtual cost in
70
        // path finding weight units of executing a payment attempt that fails.
71
        // It is used to trade off potentially better routes against their
72
        // probability of succeeding. This parameter is expressed in parts per
73
        // million of the payment amount.
74
        //
75
        // It is impossible to pick a perfect default value. The current value
76
        // of 0.1% is based on the idea that a transaction fee of 1% is within
77
        // reasonable territory and that a payment shouldn't need more than 10
78
        // attempts.
79
        DefaultAttemptCostPPM = int64(1000)
80

81
        // DefaultMinRouteProbability is the default minimum probability for routes
82
        // returned from findPath.
83
        DefaultMinRouteProbability = float64(0.01)
84

85
        // DefaultAprioriHopProbability is the default a priori probability for
86
        // a hop.
87
        DefaultAprioriHopProbability = float64(0.6)
88
)
89

90
// edgePolicyWithSource is a helper struct to keep track of the source node
91
// of a channel edge. ChannelEdgePolicy only contains to destination node
92
// of the edge.
93
type edgePolicyWithSource struct {
94
        sourceNode route.Vertex
95
        edge       AdditionalEdge
96
}
97

98
// finalHopParams encapsulates various parameters for route construction that
99
// apply to the final hop in a route. These features include basic payment data
100
// such as amounts and cltvs, as well as more complex features like destination
101
// custom records and payment address.
102
type finalHopParams struct {
103
        amt      lnwire.MilliSatoshi
104
        totalAmt lnwire.MilliSatoshi
105

106
        // cltvDelta is the final hop's minimum CLTV expiry delta.
107
        //
108
        // NOTE that in the case of paying to a blinded path, this value will
109
        // be set to a duplicate of the blinded path's accumulated CLTV value.
110
        // We would then only need to use this value in the case where the
111
        // introduction node of the path is also the destination node.
112
        cltvDelta uint16
113

114
        records     record.CustomSet
115
        paymentAddr fn.Option[[32]byte]
116

117
        // metadata is additional data that is sent along with the payment to
118
        // the payee.
119
        metadata []byte
120
}
121

122
// newRoute constructs a route using the provided path and final hop constraints.
123
// Any destination specific fields from the final hop params  will be attached
124
// assuming the destination's feature vector signals support, otherwise this
125
// method will fail.  If the route is too long, or the selected path cannot
126
// support the fully payment including fees, then a non-nil error is returned.
127
// If the route is to a blinded path, the blindedPath parameter is used to
128
// back fill additional fields that are required for a blinded payment. This is
129
// done in a separate pass to keep our route construction simple, as blinded
130
// paths require zero expiry and amount values for intermediate hops (which
131
// makes calculating the totals during route construction difficult if we
132
// include blinded paths on the first pass).
133
//
134
// NOTE: The passed slice of unified edges MUST be sorted in forward order: from
135
// the source to the target node of the path finding attempt. It is assumed that
136
// any feature vectors on all hops have been validated for transitive
137
// dependencies.
138
// NOTE: If a non-nil blinded path is provided it is assumed to have been
139
// validated by the caller.
140
func newRoute(sourceVertex route.Vertex,
141
        pathEdges []*unifiedEdge, currentHeight uint32, finalHop finalHopParams,
142
        blindedPathSet *BlindedPaymentPathSet) (*route.Route, error) {
3✔
143

3✔
144
        var (
3✔
145
                hops []*route.Hop
3✔
146

3✔
147
                // totalTimeLock will accumulate the cumulative time lock
3✔
148
                // across the entire route. This value represents how long the
3✔
149
                // sender will need to wait in the *worst* case.
3✔
150
                totalTimeLock = currentHeight
3✔
151

3✔
152
                // nextIncomingAmount is the amount that will need to flow into
3✔
153
                // the *next* hop. Since we're going to be walking the route
3✔
154
                // backwards below, this next hop gets closer and closer to the
3✔
155
                // sender of the payment.
3✔
156
                nextIncomingAmount lnwire.MilliSatoshi
3✔
157

3✔
158
                blindedPayment *BlindedPayment
3✔
159
        )
3✔
160

3✔
161
        pathLength := len(pathEdges)
3✔
162

3✔
163
        // When paying to a blinded route we might have appended a dummy hop at
3✔
164
        // the end to make MPP payments possible via all paths of the blinded
3✔
165
        // route set. We always append a dummy hop when the internal pathfiner
3✔
166
        // looks for a route to a blinded path which is at least one hop long
3✔
167
        // (excluding the introduction point). We add this dummy hop so that
3✔
168
        // we search for a universal target but also respect potential mc
3✔
169
        // entries which might already be present for a particular blinded path.
3✔
170
        // However when constructing the Sphinx packet we need to remove this
3✔
171
        // dummy hop again which we do here.
3✔
172
        //
3✔
173
        // NOTE: The path length is always at least 1 because there must be one
3✔
174
        // edge from the source to the destination. However we check for > 0
3✔
175
        // just for robustness here.
3✔
176
        if blindedPathSet != nil && pathLength > 0 {
6✔
177
                finalBlindedPubKey := pathEdges[pathLength-1].policy.
3✔
178
                        ToNodePubKey()
3✔
179

3✔
180
                if IsBlindedRouteNUMSTargetKey(finalBlindedPubKey[:]) {
6✔
181
                        // If the last hop is the NUMS key for blinded paths, we
3✔
182
                        // remove the dummy hop from the route.
3✔
183
                        pathEdges = pathEdges[:pathLength-1]
3✔
184
                        pathLength--
3✔
185
                }
3✔
186
        }
187

188
        for i := pathLength - 1; i >= 0; i-- {
6✔
189
                // Now we'll start to calculate the items within the per-hop
3✔
190
                // payload for the hop this edge is leading to.
3✔
191
                edge := pathEdges[i].policy
3✔
192

3✔
193
                // If this is an edge from a blinded path and the
3✔
194
                // blindedPayment variable has not been set yet, then set it now
3✔
195
                // by extracting the corresponding blinded payment from the
3✔
196
                // edge.
3✔
197
                isBlindedEdge := pathEdges[i].blindedPayment != nil
3✔
198
                if isBlindedEdge && blindedPayment == nil {
6✔
199
                        blindedPayment = pathEdges[i].blindedPayment
3✔
200
                }
3✔
201

202
                // We'll calculate the amounts, timelocks, and fees for each hop
203
                // in the route. The base case is the final hop which includes
204
                // their amount and timelocks. These values will accumulate
205
                // contributions from the preceding hops back to the sender as
206
                // we compute the route in reverse.
207
                var (
3✔
208
                        amtToForward        lnwire.MilliSatoshi
3✔
209
                        fee                 int64
3✔
210
                        totalAmtMsatBlinded lnwire.MilliSatoshi
3✔
211
                        outgoingTimeLock    uint32
3✔
212
                        customRecords       record.CustomSet
3✔
213
                        mpp                 *record.MPP
3✔
214
                        metadata            []byte
3✔
215
                )
3✔
216

3✔
217
                // Define a helper function that checks this edge's feature
3✔
218
                // vector for support for a given feature. We assume at this
3✔
219
                // point that the feature vectors transitive dependencies have
3✔
220
                // been validated.
3✔
221
                supports := func(feature lnwire.FeatureBit) bool {
6✔
222
                        // If this edge comes from router hints, the features
3✔
223
                        // could be nil.
3✔
224
                        if edge.ToNodeFeatures == nil {
3✔
225
                                return false
×
226
                        }
×
227
                        return edge.ToNodeFeatures.HasFeature(feature)
3✔
228
                }
229

230
                if i == len(pathEdges)-1 {
6✔
231
                        // If this is the last hop, then the hop payload will
3✔
232
                        // contain the exact amount. In BOLT #4: Onion Routing
3✔
233
                        // Protocol / "Payload for the Last Node", this is
3✔
234
                        // detailed.
3✔
235
                        amtToForward = finalHop.amt
3✔
236

3✔
237
                        // Fee is not part of the hop payload, but only used for
3✔
238
                        // reporting through RPC. Set to zero for the final hop.
3✔
239
                        fee = 0
3✔
240

3✔
241
                        if blindedPathSet == nil {
6✔
242
                                totalTimeLock += uint32(finalHop.cltvDelta)
3✔
243
                        } else {
6✔
244
                                totalTimeLock += uint32(
3✔
245
                                        blindedPathSet.FinalCLTVDelta(),
3✔
246
                                )
3✔
247
                        }
3✔
248
                        outgoingTimeLock = totalTimeLock
3✔
249

3✔
250
                        // Attach any custom records to the final hop.
3✔
251
                        customRecords = finalHop.records
3✔
252

3✔
253
                        // If we're attaching a payment addr but the receiver
3✔
254
                        // doesn't support both TLV and payment addrs, fail.
3✔
255
                        payAddr := supports(lnwire.PaymentAddrOptional)
3✔
256
                        if !payAddr && finalHop.paymentAddr.IsSome() {
3✔
257
                                return nil, errors.New("cannot attach " +
×
258
                                        "payment addr")
×
259
                        }
×
260

261
                        // Otherwise attach the mpp record if it exists.
262
                        // TODO(halseth): move this to payment life cycle,
263
                        // where AMP options are set.
264
                        finalHop.paymentAddr.WhenSome(func(addr [32]byte) {
6✔
265
                                mpp = record.NewMPP(finalHop.totalAmt, addr)
3✔
266
                        })
3✔
267

268
                        metadata = finalHop.metadata
3✔
269

3✔
270
                        if blindedPathSet != nil {
6✔
271
                                totalAmtMsatBlinded = finalHop.totalAmt
3✔
272
                        }
3✔
273
                } else {
3✔
274
                        // The amount that the current hop needs to forward is
3✔
275
                        // equal to the incoming amount of the next hop.
3✔
276
                        amtToForward = nextIncomingAmount
3✔
277

3✔
278
                        // The fee that needs to be paid to the current hop is
3✔
279
                        // based on the amount that this hop needs to forward
3✔
280
                        // and its policy for the outgoing channel. This policy
3✔
281
                        // is stored as part of the incoming channel of
3✔
282
                        // the next hop.
3✔
283
                        outboundFee := pathEdges[i+1].policy.ComputeFee(
3✔
284
                                amtToForward,
3✔
285
                        )
3✔
286

3✔
287
                        inboundFee := pathEdges[i].inboundFees.CalcFee(
3✔
288
                                amtToForward + outboundFee,
3✔
289
                        )
3✔
290

3✔
291
                        fee = int64(outboundFee) + inboundFee
3✔
292
                        if fee < 0 {
3✔
293
                                fee = 0
×
294
                        }
×
295

296
                        // We'll take the total timelock of the preceding hop as
297
                        // the outgoing timelock or this hop. Then we'll
298
                        // increment the total timelock incurred by this hop.
299
                        outgoingTimeLock = totalTimeLock
3✔
300
                        totalTimeLock += uint32(
3✔
301
                                pathEdges[i+1].policy.TimeLockDelta,
3✔
302
                        )
3✔
303
                }
304

305
                // Since we're traversing the path backwards atm, we prepend
306
                // each new hop such that, the final slice of hops will be in
307
                // the forwards order.
308
                currentHop := &route.Hop{
3✔
309
                        PubKeyBytes:      edge.ToNodePubKey(),
3✔
310
                        ChannelID:        edge.ChannelID,
3✔
311
                        AmtToForward:     amtToForward,
3✔
312
                        OutgoingTimeLock: outgoingTimeLock,
3✔
313
                        CustomRecords:    customRecords,
3✔
314
                        MPP:              mpp,
3✔
315
                        Metadata:         metadata,
3✔
316
                        TotalAmtMsat:     totalAmtMsatBlinded,
3✔
317
                }
3✔
318

3✔
319
                hops = append([]*route.Hop{currentHop}, hops...)
3✔
320

3✔
321
                // Finally, we update the amount that needs to flow into the
3✔
322
                // *next* hop, which is the amount this hop needs to forward,
3✔
323
                // accounting for the fee that it takes.
3✔
324
                nextIncomingAmount = amtToForward + lnwire.MilliSatoshi(fee)
3✔
325
        }
326

327
        // If we are creating a route to a blinded path, we need to add some
328
        // additional data to the route that is required for blinded forwarding.
329
        // We do another pass on our edges to append this data.
330
        if blindedPathSet != nil {
6✔
331
                // If the passed in BlindedPaymentPathSet is non-nil but no
3✔
332
                // edge had a BlindedPayment attached, it means that the path
3✔
333
                // chosen was an introduction-node-only path. So in this case,
3✔
334
                // we can assume the relevant payment is the only one in the
3✔
335
                // payment set.
3✔
336
                if blindedPayment == nil {
6✔
337
                        var err error
3✔
338
                        blindedPayment, err = blindedPathSet.IntroNodeOnlyPath()
3✔
339
                        if err != nil {
3✔
340
                                return nil, err
×
341
                        }
×
342
                }
343

344
                var (
3✔
345
                        inBlindedRoute bool
3✔
346
                        dataIndex      = 0
3✔
347

3✔
348
                        blindedPath = blindedPayment.BlindedPath
3✔
349
                        introVertex = route.NewVertex(
3✔
350
                                blindedPath.IntroductionPoint,
3✔
351
                        )
3✔
352
                )
3✔
353

3✔
354
                for i, hop := range hops {
6✔
355
                        // Once we locate our introduction node, we know that
3✔
356
                        // every hop after this is part of the blinded route.
3✔
357
                        if bytes.Equal(hop.PubKeyBytes[:], introVertex[:]) {
6✔
358
                                inBlindedRoute = true
3✔
359
                                hop.BlindingPoint = blindedPath.BlindingPoint
3✔
360
                        }
3✔
361

362
                        // We don't need to modify edges outside of our blinded
363
                        // route.
364
                        if !inBlindedRoute {
6✔
365
                                continue
3✔
366
                        }
367

368
                        payload := blindedPath.BlindedHops[dataIndex].CipherText
3✔
369
                        hop.EncryptedData = payload
3✔
370

3✔
371
                        // All of the hops in a blinded route *except* the
3✔
372
                        // final hop should have zero amounts / time locks.
3✔
373
                        if i != len(hops)-1 {
6✔
374
                                hop.AmtToForward = 0
3✔
375
                                hop.OutgoingTimeLock = 0
3✔
376
                        }
3✔
377

378
                        dataIndex++
3✔
379
                }
380
        }
381

382
        // With the base routing data expressed as hops, build the full route
383
        newRoute, err := route.NewRouteFromHops(
3✔
384
                nextIncomingAmount, totalTimeLock, route.Vertex(sourceVertex),
3✔
385
                hops,
3✔
386
        )
3✔
387
        if err != nil {
3✔
388
                return nil, err
×
389
        }
×
390

391
        return newRoute, nil
3✔
392
}
393

394
// edgeWeight computes the weight of an edge. This value is used when searching
395
// for the shortest path within the channel graph between two nodes. Weight is
396
// is the fee itself plus a time lock penalty added to it. This benefits
397
// channels with shorter time lock deltas and shorter (hops) routes in general.
398
// RiskFactor controls the influence of time lock on route selection. This is
399
// currently a fixed value, but might be configurable in the future.
400
func edgeWeight(lockedAmt lnwire.MilliSatoshi, fee lnwire.MilliSatoshi,
401
        timeLockDelta uint16) int64 {
3✔
402
        // timeLockPenalty is the penalty for the time lock delta of this channel.
3✔
403
        // It is controlled by RiskFactorBillionths and scales proportional
3✔
404
        // to the amount that will pass through channel. Rationale is that it if
3✔
405
        // a twice as large amount gets locked up, it is twice as bad.
3✔
406
        timeLockPenalty := int64(lockedAmt) * int64(timeLockDelta) *
3✔
407
                RiskFactorBillionths / 1000000000
3✔
408

3✔
409
        return int64(fee) + timeLockPenalty
3✔
410
}
3✔
411

412
// graphParams wraps the set of graph parameters passed to findPath.
413
type graphParams struct {
414
        // graph is the ChannelGraph to be used during path finding.
415
        graph Graph
416

417
        // additionalEdges is an optional set of edges that should be
418
        // considered during path finding, that is not already found in the
419
        // channel graph. These can either be private edges for bolt 11 invoices
420
        // or blinded edges when a payment to a blinded path is made.
421
        additionalEdges map[route.Vertex][]AdditionalEdge
422

423
        // bandwidthHints is an interface that provides bandwidth hints that
424
        // can provide a better estimate of the current channel bandwidth than
425
        // what is found in the graph. It will override the capacities and
426
        // disabled flags found in the graph for local channels when doing
427
        // path finding if it has updated values for that channel. In
428
        // particular, it should be set to the current available sending
429
        // bandwidth for active local channels, and 0 for inactive channels.
430
        bandwidthHints bandwidthHints
431
}
432

433
// RestrictParams wraps the set of restrictions passed to findPath that the
434
// found path must adhere to.
435
type RestrictParams struct {
436
        // ProbabilitySource is a callback that is expected to return the
437
        // success probability of traversing the channel from the node.
438
        ProbabilitySource func(route.Vertex, route.Vertex,
439
                lnwire.MilliSatoshi, btcutil.Amount) float64
440

441
        // FeeLimit is a maximum fee amount allowed to be used on the path from
442
        // the source to the target.
443
        FeeLimit lnwire.MilliSatoshi
444

445
        // OutgoingChannelIDs is the list of channels that are allowed for the
446
        // first hop. If nil, any channel may be used.
447
        OutgoingChannelIDs []uint64
448

449
        // LastHop is the pubkey of the last node before the final destination
450
        // is reached. If nil, any node may be used.
451
        LastHop *route.Vertex
452

453
        // CltvLimit is the maximum time lock of the route excluding the final
454
        // ctlv. After path finding is complete, the caller needs to increase
455
        // all cltv expiry heights with the required final cltv delta.
456
        CltvLimit uint32
457

458
        // DestCustomRecords contains the custom records to drop off at the
459
        // final hop, if any.
460
        DestCustomRecords record.CustomSet
461

462
        // DestFeatures is a feature vector describing what the final hop
463
        // supports. If none are provided, pathfinding will try to inspect any
464
        // features on the node announcement instead.
465
        DestFeatures *lnwire.FeatureVector
466

467
        // PaymentAddr is a random 32-byte value generated by the receiver to
468
        // mitigate probing vectors and payment sniping attacks on overpaid
469
        // invoices.
470
        PaymentAddr fn.Option[[32]byte]
471

472
        // Amp signals to the pathfinder that this payment is an AMP payment
473
        // and therefore it needs to account for additional AMP data in the
474
        // final hop payload size calculation.
475
        Amp *AMPOptions
476

477
        // Metadata is additional data that is sent along with the payment to
478
        // the payee.
479
        Metadata []byte
480

481
        // BlindedPaymentPathSet is necessary to determine the hop size of the
482
        // last/exit hop.
483
        BlindedPaymentPathSet *BlindedPaymentPathSet
484

485
        // FirstHopCustomRecords includes any records that should be included in
486
        // the update_add_htlc message towards our peer.
487
        FirstHopCustomRecords lnwire.CustomRecords
488
}
489

490
// PathFindingConfig defines global parameters that control the trade-off in
491
// path finding between fees and probability.
492
type PathFindingConfig struct {
493
        // AttemptCost is the fixed virtual cost in path finding of a failed
494
        // payment attempt. It is used to trade off potentially better routes
495
        // against their probability of succeeding.
496
        AttemptCost lnwire.MilliSatoshi
497

498
        // AttemptCostPPM is the proportional virtual cost in path finding of a
499
        // failed payment attempt. It is used to trade off potentially better
500
        // routes against their probability of succeeding. This parameter is
501
        // expressed in parts per million of the total payment amount.
502
        AttemptCostPPM int64
503

504
        // MinProbability defines the minimum success probability of the
505
        // returned route.
506
        MinProbability float64
507
}
508

509
// getOutgoingBalance returns the maximum available balance in any of the
510
// channels of the given node. The second return parameters is the total
511
// available balance.
512
func getOutgoingBalance(node route.Vertex, outgoingChans map[uint64]struct{},
513
        bandwidthHints bandwidthHints,
514
        g Graph) (lnwire.MilliSatoshi, lnwire.MilliSatoshi, error) {
3✔
515

3✔
516
        var max, total lnwire.MilliSatoshi
3✔
517
        cb := func(channel *graphdb.DirectedChannel) error {
6✔
518
                if !channel.OutPolicySet {
3✔
519
                        return nil
×
520
                }
×
521

522
                chanID := channel.ChannelID
3✔
523

3✔
524
                // Enforce outgoing channel restriction.
3✔
525
                if outgoingChans != nil {
3✔
526
                        if _, ok := outgoingChans[chanID]; !ok {
×
527
                                return nil
×
528
                        }
×
529
                }
530

531
                bandwidth, ok := bandwidthHints.availableChanBandwidth(
3✔
532
                        chanID, 0,
3✔
533
                )
3✔
534

3✔
535
                // If the bandwidth is not available, use the channel capacity.
3✔
536
                // This can happen when a channel is added to the graph after
3✔
537
                // we've already queried the bandwidth hints.
3✔
538
                if !ok {
3✔
539
                        bandwidth = lnwire.NewMSatFromSatoshis(channel.Capacity)
×
540
                }
×
541

542
                if bandwidth > max {
6✔
543
                        max = bandwidth
3✔
544
                }
3✔
545

546
                var overflow bool
3✔
547
                total, overflow = overflowSafeAdd(total, bandwidth)
3✔
548
                if overflow {
3✔
549
                        // If the current total and the bandwidth would
×
550
                        // overflow the maximum value, we set the total to the
×
551
                        // maximum value. Which is more milli-satoshis than are
×
552
                        // in existence anyway, so the actual value is
×
553
                        // irrelevant.
×
554
                        total = lnwire.MilliSatoshi(math.MaxUint64)
×
555
                }
×
556

557
                return nil
3✔
558
        }
559

560
        // Iterate over all channels of the to node.
561
        err := g.ForEachNodeDirectedChannel(node, cb)
3✔
562
        if err != nil {
3✔
563
                return 0, 0, err
×
564
        }
×
565
        return max, total, err
3✔
566
}
567

568
// findPath attempts to find a path from the source node within the ChannelGraph
569
// to the target node that's capable of supporting a payment of `amt` value. The
570
// current approach implemented is modified version of Dijkstra's algorithm to
571
// find a single shortest path between the source node and the destination. The
572
// distance metric used for edges is related to the time-lock+fee costs along a
573
// particular edge. If a path is found, this function returns a slice of
574
// ChannelHop structs which encoded the chosen path from the target to the
575
// source. The search is performed backwards from destination node back to
576
// source. This is to properly accumulate fees that need to be paid along the
577
// path and accurately check the amount to forward at every node against the
578
// available bandwidth.
579
func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
580
        self, source, target route.Vertex, amt lnwire.MilliSatoshi,
581
        timePref float64, finalHtlcExpiry int32) ([]*unifiedEdge, float64,
582
        error) {
3✔
583

3✔
584
        // Pathfinding can be a significant portion of the total payment
3✔
585
        // latency, especially on low-powered devices. Log several metrics to
3✔
586
        // aid in the analysis performance problems in this area.
3✔
587
        start := time.Now()
3✔
588
        nodesVisited := 0
3✔
589
        edgesExpanded := 0
3✔
590
        defer func() {
6✔
591
                timeElapsed := time.Since(start)
3✔
592
                log.Debugf("Pathfinding perf metrics: nodes=%v, edges=%v, "+
3✔
593
                        "time=%v", nodesVisited, edgesExpanded, timeElapsed)
3✔
594
        }()
3✔
595

596
        // If no destination features are provided, we will load what features
597
        // we have for the target node from our graph.
598
        features := r.DestFeatures
3✔
599
        if features == nil {
6✔
600
                var err error
3✔
601
                features, err = g.graph.FetchNodeFeatures(target)
3✔
602
                if err != nil {
3✔
603
                        return nil, 0, err
×
604
                }
×
605
        }
606

607
        // Ensure that the destination's features don't include unknown
608
        // required features.
609
        err := feature.ValidateRequired(features)
3✔
610
        if err != nil {
3✔
611
                log.Warnf("Pathfinding destination node features: %v", err)
×
612
                return nil, 0, errUnknownRequiredFeature
×
613
        }
×
614

615
        // Ensure that all transitive dependencies are set.
616
        err = feature.ValidateDeps(features)
3✔
617
        if err != nil {
3✔
618
                log.Warnf("Pathfinding destination node features: %v", err)
×
619
                return nil, 0, errMissingDependentFeature
×
620
        }
×
621

622
        // Now that we know the feature vector is well-formed, we'll proceed in
623
        // checking that it supports the features we need. If the caller has a
624
        // payment address to attach, check that our destination feature vector
625
        // supports them.
626
        if r.PaymentAddr.IsSome() &&
3✔
627
                !features.HasFeature(lnwire.PaymentAddrOptional) {
3✔
628

×
629
                return nil, 0, errNoPaymentAddr
×
630
        }
×
631

632
        // Set up outgoing channel map for quicker access.
633
        var outgoingChanMap map[uint64]struct{}
3✔
634
        if len(r.OutgoingChannelIDs) > 0 {
3✔
635
                outgoingChanMap = make(map[uint64]struct{})
×
636
                for _, outChan := range r.OutgoingChannelIDs {
×
637
                        outgoingChanMap[outChan] = struct{}{}
×
638
                }
×
639
        }
640

641
        // If we are routing from ourselves, check that we have enough local
642
        // balance available.
643
        if source == self {
6✔
644
                max, total, err := getOutgoingBalance(
3✔
645
                        self, outgoingChanMap, g.bandwidthHints, g.graph,
3✔
646
                )
3✔
647
                if err != nil {
3✔
648
                        return nil, 0, err
×
649
                }
×
650

651
                // If the total outgoing balance isn't sufficient, it will be
652
                // impossible to complete the payment.
653
                if total < amt {
6✔
654
                        log.Warnf("Not enough outbound balance to send "+
3✔
655
                                "htlc of amount: %v, only have local "+
3✔
656
                                "balance: %v", amt, total)
3✔
657

3✔
658
                        return nil, 0, errInsufficientBalance
3✔
659
                }
3✔
660

661
                // If there is only not enough capacity on a single route, it
662
                // may still be possible to complete the payment by splitting.
663
                if max < amt {
6✔
664
                        return nil, 0, errNoPathFound
3✔
665
                }
3✔
666
        }
667

668
        // First we'll initialize an empty heap which'll help us to quickly
669
        // locate the next edge we should visit next during our graph
670
        // traversal.
671
        nodeHeap := newDistanceHeap(estimatedNodeCount)
3✔
672

3✔
673
        // Holds the current best distance for a given node.
3✔
674
        distance := make(map[route.Vertex]*nodeWithDist, estimatedNodeCount)
3✔
675

3✔
676
        additionalEdgesWithSrc := make(map[route.Vertex][]*edgePolicyWithSource)
3✔
677
        for vertex, additionalEdges := range g.additionalEdges {
6✔
678
                // Edges connected to self are always included in the graph,
3✔
679
                // therefore can be skipped. This prevents us from trying
3✔
680
                // routes to malformed hop hints.
3✔
681
                if vertex == self {
6✔
682
                        continue
3✔
683
                }
684

685
                // Build reverse lookup to find incoming edges. Needed because
686
                // search is taken place from target to source.
687
                for _, additionalEdge := range additionalEdges {
6✔
688
                        outgoingEdgePolicy := additionalEdge.EdgePolicy()
3✔
689
                        toVertex := outgoingEdgePolicy.ToNodePubKey()
3✔
690

3✔
691
                        incomingEdgePolicy := &edgePolicyWithSource{
3✔
692
                                sourceNode: vertex,
3✔
693
                                edge:       additionalEdge,
3✔
694
                        }
3✔
695

3✔
696
                        additionalEdgesWithSrc[toVertex] =
3✔
697
                                append(additionalEdgesWithSrc[toVertex],
3✔
698
                                        incomingEdgePolicy)
3✔
699
                }
3✔
700
        }
701

702
        // The payload size of the final hop differ from intermediate hops
703
        // and depends on whether the destination is blinded or not.
704
        lastHopPayloadSize, err := lastHopPayloadSize(r, finalHtlcExpiry, amt)
3✔
705
        if err != nil {
3✔
706
                return nil, 0, err
×
707
        }
×
708

709
        // We can't always assume that the end destination is publicly
710
        // advertised to the network so we'll manually include the target node.
711
        // The target node charges no fee. Distance is set to 0, because this is
712
        // the starting point of the graph traversal. We are searching backwards
713
        // to get the fees first time right and correctly match channel
714
        // bandwidth.
715
        //
716
        // Don't record the initial partial path in the distance map and reserve
717
        // that key for the source key in the case we route to ourselves.
718
        partialPath := &nodeWithDist{
3✔
719
                dist:              0,
3✔
720
                weight:            0,
3✔
721
                node:              target,
3✔
722
                netAmountReceived: amt,
3✔
723
                incomingCltv:      finalHtlcExpiry,
3✔
724
                probability:       1,
3✔
725
                routingInfoSize:   lastHopPayloadSize,
3✔
726
        }
3✔
727

3✔
728
        // Calculate the absolute cltv limit. Use uint64 to prevent an overflow
3✔
729
        // if the cltv limit is MaxUint32.
3✔
730
        absoluteCltvLimit := uint64(r.CltvLimit) + uint64(finalHtlcExpiry)
3✔
731

3✔
732
        // Calculate the default attempt cost as configured globally.
3✔
733
        defaultAttemptCost := float64(
3✔
734
                cfg.AttemptCost +
3✔
735
                        amt*lnwire.MilliSatoshi(cfg.AttemptCostPPM)/1000000,
3✔
736
        )
3✔
737

3✔
738
        // Validate time preference value.
3✔
739
        if math.Abs(timePref) > 1 {
3✔
740
                return nil, 0, fmt.Errorf("time preference %v out of range "+
×
741
                        "[-1, 1]", timePref)
×
742
        }
×
743

744
        // Scale to avoid the extremes -1 and 1 which run into infinity issues.
745
        timePref *= 0.9
3✔
746

3✔
747
        // Apply time preference. At 0, the default attempt cost will
3✔
748
        // be used.
3✔
749
        absoluteAttemptCost := defaultAttemptCost * (1/(0.5-timePref/2) - 1)
3✔
750

3✔
751
        log.Debugf("Pathfinding absolute attempt cost: %v sats",
3✔
752
                absoluteAttemptCost/1000)
3✔
753

3✔
754
        // processEdge is a helper closure that will be used to make sure edges
3✔
755
        // satisfy our specific requirements.
3✔
756
        processEdge := func(fromVertex route.Vertex,
3✔
757
                edge *unifiedEdge, toNodeDist *nodeWithDist) {
6✔
758

3✔
759
                edgesExpanded++
3✔
760

3✔
761
                // Calculate inbound fee charged by "to" node. The exit hop
3✔
762
                // doesn't charge inbound fees. If the "to" node is the exit
3✔
763
                // hop, its inbound fees have already been set to zero by
3✔
764
                // nodeEdgeUnifier.
3✔
765
                inboundFee := edge.inboundFees.CalcFee(
3✔
766
                        toNodeDist.netAmountReceived,
3✔
767
                )
3✔
768

3✔
769
                // Make sure that the node total fee is never negative.
3✔
770
                // Routing nodes treat a total fee that turns out
3✔
771
                // negative as a zero fee and pathfinding should do the
3✔
772
                // same.
3✔
773
                minInboundFee := -int64(toNodeDist.outboundFee)
3✔
774
                if inboundFee < minInboundFee {
3✔
775
                        inboundFee = minInboundFee
×
776
                }
×
777

778
                // Calculate amount that the candidate node would have to send
779
                // out.
780
                amountToSend := toNodeDist.netAmountReceived +
3✔
781
                        lnwire.MilliSatoshi(inboundFee)
3✔
782

3✔
783
                // Check if accumulated fees would exceed fee limit when this
3✔
784
                // node would be added to the path.
3✔
785
                totalFee := int64(amountToSend) - int64(amt)
3✔
786

3✔
787
                log.Trace(lnutils.NewLogClosure(func() string {
3✔
788
                        return fmt.Sprintf(
×
789
                                "Checking fromVertex (%v) with "+
×
790
                                        "minInboundFee=%v, inboundFee=%v, "+
×
791
                                        "amountToSend=%v, amt=%v, totalFee=%v",
×
792
                                fromVertex, minInboundFee, inboundFee,
×
793
                                amountToSend, amt, totalFee,
×
794
                        )
×
795
                }))
×
796

797
                if totalFee > 0 && lnwire.MilliSatoshi(totalFee) > r.FeeLimit {
3✔
798
                        return
×
799
                }
×
800

801
                // Request the success probability for this edge.
802
                edgeProbability := r.ProbabilitySource(
3✔
803
                        fromVertex, toNodeDist.node, amountToSend,
3✔
804
                        edge.capacity,
3✔
805
                )
3✔
806

3✔
807
                log.Trace(lnutils.NewLogClosure(func() string {
3✔
808
                        return fmt.Sprintf("path finding probability: fromnode=%v,"+
×
809
                                " tonode=%v, amt=%v, cap=%v, probability=%v",
×
810
                                fromVertex, toNodeDist.node, amountToSend,
×
811
                                edge.capacity, edgeProbability)
×
812
                }))
×
813

814
                // If the probability is zero, there is no point in trying.
815
                if edgeProbability == 0 {
3✔
816
                        return
×
817
                }
×
818

819
                // Compute fee that fromVertex is charging. It is based on the
820
                // amount that needs to be sent to the next node in the route.
821
                //
822
                // Source node has no predecessor to pay a fee. Therefore set
823
                // fee to zero, because it should not be included in the fee
824
                // limit check and edge weight.
825
                //
826
                // Also determine the time lock delta that will be added to the
827
                // route if fromVertex is selected. If fromVertex is the source
828
                // node, no additional timelock is required.
829
                var (
3✔
830
                        timeLockDelta uint16
3✔
831
                        outboundFee   int64
3✔
832
                )
3✔
833

3✔
834
                if fromVertex != source {
6✔
835
                        outboundFee = int64(
3✔
836
                                edge.policy.ComputeFee(amountToSend),
3✔
837
                        )
3✔
838
                        timeLockDelta = edge.policy.TimeLockDelta
3✔
839
                }
3✔
840

841
                incomingCltv := toNodeDist.incomingCltv + int32(timeLockDelta)
3✔
842

3✔
843
                // Check that we are within our CLTV limit.
3✔
844
                if uint64(incomingCltv) > absoluteCltvLimit {
3✔
845
                        return
×
846
                }
×
847

848
                // netAmountToReceive is the amount that the node that is added
849
                // to the distance map needs to receive from a (to be found)
850
                // previous node in the route. The inbound fee of the receiving
851
                // node is already subtracted from this value. The previous node
852
                // will need to pay the amount that this node forwards plus the
853
                // fee it charges plus this node's inbound fee.
854
                netAmountToReceive := amountToSend +
3✔
855
                        lnwire.MilliSatoshi(outboundFee)
3✔
856

3✔
857
                // Calculate total probability of successfully reaching target
3✔
858
                // by multiplying the probabilities. Both this edge and the rest
3✔
859
                // of the route must succeed.
3✔
860
                probability := toNodeDist.probability * edgeProbability
3✔
861

3✔
862
                // If the probability is below the specified lower bound, we can
3✔
863
                // abandon this direction. Adding further nodes can only lower
3✔
864
                // the probability more.
3✔
865
                if probability < cfg.MinProbability {
6✔
866
                        return
3✔
867
                }
3✔
868

869
                // Calculate the combined fee for this edge. Dijkstra does not
870
                // support negative edge weights. Because this fee feeds into
871
                // the edge weight calculation, we don't allow it to be
872
                // negative.
873
                signedFee := inboundFee + outboundFee
3✔
874
                fee := lnwire.MilliSatoshi(0)
3✔
875
                if signedFee > 0 {
6✔
876
                        fee = lnwire.MilliSatoshi(signedFee)
3✔
877
                }
3✔
878

879
                // By adding fromVertex in the route, there will be an extra
880
                // weight composed of the fee that this node will charge and
881
                // the amount that will be locked for timeLockDelta blocks in
882
                // the HTLC that is handed out to fromVertex.
883
                weight := edgeWeight(amountToSend, fee, timeLockDelta)
3✔
884

3✔
885
                // Compute the tentative weight to this new channel/edge
3✔
886
                // which is the weight from our toNode to the target node
3✔
887
                // plus the weight of this edge.
3✔
888
                tempWeight := toNodeDist.weight + weight
3✔
889

3✔
890
                // Add an extra factor to the weight to take into account the
3✔
891
                // probability. Another reason why we rounded the fee up to zero
3✔
892
                // is to prevent a highly negative fee from cancelling out the
3✔
893
                // extra factor. We don't want an always-failing node to attract
3✔
894
                // traffic using a highly negative fee and escape penalization.
3✔
895
                tempDist := getProbabilityBasedDist(
3✔
896
                        tempWeight, probability,
3✔
897
                        absoluteAttemptCost,
3✔
898
                )
3✔
899

3✔
900
                // If there is already a best route stored, compare this
3✔
901
                // candidate route with the best route so far.
3✔
902
                current, ok := distance[fromVertex]
3✔
903
                if ok {
6✔
904
                        // If this route is worse than what we already found,
3✔
905
                        // skip this route.
3✔
906
                        if tempDist > current.dist {
6✔
907
                                return
3✔
908
                        }
3✔
909

910
                        // If the route is equally good and the probability
911
                        // isn't better, skip this route. It is important to
912
                        // also return if both cost and probability are equal,
913
                        // because otherwise the algorithm could run into an
914
                        // endless loop.
915
                        probNotBetter := probability <= current.probability
3✔
916
                        if tempDist == current.dist && probNotBetter {
6✔
917
                                return
3✔
918
                        }
3✔
919
                }
920

921
                // Calculate the total routing info size if this hop were to be
922
                // included. If we are coming from the source hop, the payload
923
                // size is zero, because the original htlc isn't in the onion
924
                // blob.
925
                //
926
                // NOTE: For blinded paths with the NUMS key as the last hop,
927
                // the payload size accounts for this dummy hop which is of
928
                // the same size as the real last hop. So we account for a
929
                // bigger size than the route is however we accept this
930
                // little inaccuracy here because we are over estimating by
931
                // 1 hop.
932
                var payloadSize uint64
3✔
933
                if fromVertex != source {
6✔
934
                        // In case the unifiedEdge does not have a payload size
3✔
935
                        // function supplied we request a graceful shutdown
3✔
936
                        // because this should never happen.
3✔
937
                        if edge.hopPayloadSizeFn == nil {
3✔
938
                                log.Criticalf("No payload size function "+
×
939
                                        "available for edge=%v unable to "+
×
940
                                        "determine payload size: %v", edge,
×
941
                                        ErrNoPayLoadSizeFunc)
×
942

×
943
                                return
×
944
                        }
×
945

946
                        payloadSize = edge.hopPayloadSizeFn(
3✔
947
                                amountToSend,
3✔
948
                                uint32(toNodeDist.incomingCltv),
3✔
949
                                edge.policy.ChannelID,
3✔
950
                        )
3✔
951
                }
952

953
                routingInfoSize := toNodeDist.routingInfoSize + payloadSize
3✔
954
                // Skip paths that would exceed the maximum routing info size.
3✔
955
                if routingInfoSize > sphinx.MaxPayloadSize {
3✔
956
                        return
×
957
                }
×
958

959
                // All conditions are met and this new tentative distance is
960
                // better than the current best known distance to this node.
961
                // The new better distance is recorded, and also our "next hop"
962
                // map is populated with this edge.
963
                withDist := &nodeWithDist{
3✔
964
                        dist:              tempDist,
3✔
965
                        weight:            tempWeight,
3✔
966
                        node:              fromVertex,
3✔
967
                        netAmountReceived: netAmountToReceive,
3✔
968
                        outboundFee:       lnwire.MilliSatoshi(outboundFee),
3✔
969
                        incomingCltv:      incomingCltv,
3✔
970
                        probability:       probability,
3✔
971
                        nextHop:           edge,
3✔
972
                        routingInfoSize:   routingInfoSize,
3✔
973
                }
3✔
974
                distance[fromVertex] = withDist
3✔
975

3✔
976
                // Either push withDist onto the heap if the node
3✔
977
                // represented by fromVertex is not already on the heap OR adjust
3✔
978
                // its position within the heap via heap.Fix.
3✔
979
                nodeHeap.PushOrFix(withDist)
3✔
980
        }
981

982
        // TODO(roasbeef): also add path caching
983
        //  * similar to route caching, but doesn't factor in the amount
984

985
        // Cache features because we visit nodes multiple times.
986
        featureCache := make(map[route.Vertex]*lnwire.FeatureVector)
3✔
987

3✔
988
        // getGraphFeatures returns (cached) node features from the graph.
3✔
989
        getGraphFeatures := func(node route.Vertex) (*lnwire.FeatureVector,
3✔
990
                error) {
6✔
991

3✔
992
                // Check cache for features of the fromNode.
3✔
993
                fromFeatures, ok := featureCache[node]
3✔
994
                if ok {
6✔
995
                        return fromFeatures, nil
3✔
996
                }
3✔
997

998
                // Fetch node features fresh from the graph.
999
                fromFeatures, err := g.graph.FetchNodeFeatures(node)
3✔
1000
                if err != nil {
3✔
1001
                        return nil, err
×
1002
                }
×
1003

1004
                // Don't route through nodes that contain unknown required
1005
                // features and mark as nil in the cache.
1006
                err = feature.ValidateRequired(fromFeatures)
3✔
1007
                if err != nil {
3✔
1008
                        featureCache[node] = nil
×
1009
                        return nil, nil
×
1010
                }
×
1011

1012
                // Don't route through nodes that don't properly set all
1013
                // transitive feature dependencies and mark as nil in the cache.
1014
                err = feature.ValidateDeps(fromFeatures)
3✔
1015
                if err != nil {
3✔
1016
                        featureCache[node] = nil
×
1017
                        return nil, nil
×
1018
                }
×
1019

1020
                // Update cache.
1021
                featureCache[node] = fromFeatures
3✔
1022

3✔
1023
                return fromFeatures, nil
3✔
1024
        }
1025

1026
        routeToSelf := source == target
3✔
1027
        for {
6✔
1028
                nodesVisited++
3✔
1029

3✔
1030
                pivot := partialPath.node
3✔
1031
                isExitHop := partialPath.nextHop == nil
3✔
1032

3✔
1033
                // Create unified policies for all incoming connections. Don't
3✔
1034
                // use inbound fees for the exit hop.
3✔
1035
                u := newNodeEdgeUnifier(
3✔
1036
                        self, pivot, !isExitHop, outgoingChanMap,
3✔
1037
                )
3✔
1038

3✔
1039
                err := u.addGraphPolicies(g.graph)
3✔
1040
                if err != nil {
3✔
1041
                        return nil, 0, err
×
1042
                }
×
1043

1044
                // We add hop hints that were supplied externally.
1045
                for _, reverseEdge := range additionalEdgesWithSrc[pivot] {
6✔
1046
                        // Assume zero inbound fees for route hints. If inbound
3✔
1047
                        // fees would apply, they couldn't be communicated in
3✔
1048
                        // bolt11 invoices currently.
3✔
1049
                        inboundFee := models.InboundFee{}
3✔
1050

3✔
1051
                        // Hop hints don't contain a capacity. We set one here,
3✔
1052
                        // since a capacity is needed for probability
3✔
1053
                        // calculations. We set a high capacity to act as if
3✔
1054
                        // there is enough liquidity, otherwise the hint would
3✔
1055
                        // not have been added by a wallet.
3✔
1056
                        // We also pass the payload size function to the
3✔
1057
                        // graph data so that we calculate the exact payload
3✔
1058
                        // size when evaluating this hop for a route.
3✔
1059
                        u.addPolicy(
3✔
1060
                                reverseEdge.sourceNode,
3✔
1061
                                reverseEdge.edge.EdgePolicy(),
3✔
1062
                                inboundFee,
3✔
1063
                                fakeHopHintCapacity,
3✔
1064
                                reverseEdge.edge.IntermediatePayloadSize,
3✔
1065
                                reverseEdge.edge.BlindedPayment(),
3✔
1066
                        )
3✔
1067
                }
3✔
1068

1069
                netAmountReceived := partialPath.netAmountReceived
3✔
1070

3✔
1071
                // Expand all connections using the optimal policy for each
3✔
1072
                // connection.
3✔
1073
                for fromNode, edgeUnifier := range u.edgeUnifiers {
6✔
1074
                        // The target node is not recorded in the distance map.
3✔
1075
                        // Therefore we need to have this check to prevent
3✔
1076
                        // creating a cycle. Only when we intend to route to
3✔
1077
                        // self, we allow this cycle to form. In that case we'll
3✔
1078
                        // also break out of the search loop below.
3✔
1079
                        if !routeToSelf && fromNode == target {
6✔
1080
                                continue
3✔
1081
                        }
1082

1083
                        // Apply last hop restriction if set.
1084
                        if r.LastHop != nil &&
3✔
1085
                                pivot == target && fromNode != *r.LastHop {
3✔
1086

×
1087
                                continue
×
1088
                        }
1089

1090
                        edge := edgeUnifier.getEdge(
3✔
1091
                                netAmountReceived, g.bandwidthHints,
3✔
1092
                                partialPath.outboundFee,
3✔
1093
                        )
3✔
1094

3✔
1095
                        if edge == nil {
6✔
1096
                                continue
3✔
1097
                        }
1098

1099
                        // Get feature vector for fromNode.
1100
                        fromFeatures, err := getGraphFeatures(fromNode)
3✔
1101
                        if err != nil {
3✔
1102
                                return nil, 0, err
×
1103
                        }
×
1104

1105
                        // If there are no valid features, skip this node.
1106
                        if fromFeatures == nil {
3✔
1107
                                continue
×
1108
                        }
1109

1110
                        // Check if this candidate node is better than what we
1111
                        // already have.
1112
                        processEdge(fromNode, edge, partialPath)
3✔
1113
                }
1114

1115
                if nodeHeap.Len() == 0 {
6✔
1116
                        break
3✔
1117
                }
1118

1119
                // Fetch the node within the smallest distance from our source
1120
                // from the heap.
1121
                partialPath = heap.Pop(&nodeHeap).(*nodeWithDist)
3✔
1122

3✔
1123
                // If we've reached our source (or we don't have any incoming
3✔
1124
                // edges), then we're done here and can exit the graph
3✔
1125
                // traversal early.
3✔
1126
                if partialPath.node == source {
6✔
1127
                        break
3✔
1128
                }
1129
        }
1130

1131
        // Use the distance map to unravel the forward path from source to
1132
        // target.
1133
        var pathEdges []*unifiedEdge
3✔
1134
        currentNode := source
3✔
1135
        for {
6✔
1136
                // Determine the next hop forward using the next map.
3✔
1137
                currentNodeWithDist, ok := distance[currentNode]
3✔
1138
                if !ok {
6✔
1139
                        // If the node doesn't have a next hop it means we
3✔
1140
                        // didn't find a path.
3✔
1141
                        return nil, 0, errNoPathFound
3✔
1142
                }
3✔
1143

1144
                // Add the next hop to the list of path edges.
1145
                pathEdges = append(pathEdges, currentNodeWithDist.nextHop)
3✔
1146

3✔
1147
                // Advance current node.
3✔
1148
                currentNode = currentNodeWithDist.nextHop.policy.ToNodePubKey()
3✔
1149

3✔
1150
                // Check stop condition at the end of this loop. This prevents
3✔
1151
                // breaking out too soon for self-payments that have target set
3✔
1152
                // to source.
3✔
1153
                if currentNode == target {
6✔
1154
                        break
3✔
1155
                }
1156
        }
1157

1158
        // For the final hop, we'll set the node features to those determined
1159
        // above. These are either taken from the destination features, e.g.
1160
        // virtual or invoice features, or loaded as a fallback from the graph.
1161
        // The transitive dependencies were already validated above, so no need
1162
        // to do so now.
1163
        //
1164
        // NOTE: This may overwrite features loaded from the graph if
1165
        // destination features were provided. This is fine though, since our
1166
        // route construction does not care where the features are actually
1167
        // taken from. In the future we may wish to do route construction within
1168
        // findPath, and avoid using ChannelEdgePolicy altogether.
1169
        pathEdges[len(pathEdges)-1].policy.ToNodeFeatures = features
3✔
1170

3✔
1171
        log.Debugf("Found route: probability=%v, hops=%v, fee=%v",
3✔
1172
                distance[source].probability, len(pathEdges),
3✔
1173
                distance[source].netAmountReceived-amt)
3✔
1174

3✔
1175
        return pathEdges, distance[source].probability, nil
3✔
1176
}
1177

1178
// FindBlindedPaths does a depth first search from the target node to find a set
1179
// of blinded paths to the target node given the set of restrictions. This
1180
// function will select and return any candidate path. A candidate path is a
1181
// path to the target node with a size determined by the given hop number
1182
// constraints where all the nodes on the path signal the route blinding feature
1183
// _and_ the introduction node for the path has more than one public channel.
1184
// Any filtering of paths based on payment value or success probabilities is
1185
// left to the caller.
1186
func FindBlindedPaths(g Graph, target route.Vertex,
1187
        restrictions *BlindedPathRestrictions) ([][]blindedpath.BlindedHop,
1188
        error) {
3✔
1189

3✔
1190
        // Sanity check the restrictions.
3✔
1191
        if restrictions.MinDistanceFromIntroNode > restrictions.NumHops {
3✔
1192
                return nil, fmt.Errorf("maximum number of blinded path hops "+
×
1193
                        "(%d) must be greater than or equal to the minimum "+
×
NEW
1194
                        "number of hops (%d)", restrictions.NumHops,
×
NEW
1195
                        restrictions.MinDistanceFromIntroNode)
×
UNCOV
1196
        }
×
1197

1198
        // If the node is not the destination node, then it is required that the
1199
        // node advertise the route blinding feature-bit in order for it to be
1200
        // chosen as a node on the blinded path.
1201
        supportsRouteBlinding := func(node route.Vertex) (bool, error) {
6✔
1202
                if node == target {
6✔
1203
                        return true, nil
3✔
1204
                }
3✔
1205

1206
                features, err := g.FetchNodeFeatures(node)
3✔
1207
                if err != nil {
3✔
1208
                        return false, err
×
1209
                }
×
1210

1211
                return features.HasFeature(lnwire.RouteBlindingOptional), nil
3✔
1212
        }
1213

1214
        // This function will have some recursion. We will spin out from the
1215
        // target node & append edges to the paths until we reach various exit
1216
        // conditions such as: The maxHops number being reached or reaching
1217
        // a node that doesn't have any other edges - in that final case, the
1218
        // whole path should be ignored.
1219
        paths, _, err := processNodeForBlindedPath(
3✔
1220
                g, target, supportsRouteBlinding, nil, restrictions,
3✔
1221
        )
3✔
1222
        if err != nil {
3✔
1223
                return nil, err
×
1224
        }
×
1225

1226
        // Reverse each path so that the order is correct (from introduction
1227
        // node to last hop node) and then append this node on as the
1228
        // destination of each path.
1229
        orderedPaths := make([][]blindedpath.BlindedHop, len(paths))
3✔
1230
        for i, path := range paths {
6✔
1231
                sort.Slice(path, func(i, j int) bool {
6✔
1232
                        return j < i
3✔
1233
                })
3✔
1234

1235
                orderedPaths[i] = append(path,
3✔
1236
                        blindedpath.BlindedHop{Vertex: target})
3✔
1237
        }
1238

1239
        // Handle the special case that allows a blinded path with the
1240
        // introduction node as the destination node.
1241
        if restrictions.MinDistanceFromIntroNode == 0 {
6✔
1242
                singleHopPath := [][]blindedpath.BlindedHop{{{Vertex: target}}}
3✔
1243

3✔
1244
                //nolint:makezero
3✔
1245
                orderedPaths = append(
3✔
1246
                        orderedPaths, singleHopPath...,
3✔
1247
                )
3✔
1248
        }
3✔
1249

1250
        return orderedPaths, err
3✔
1251
}
1252

1253
// processNodeForBlindedPath is a recursive function that traverses the graph
1254
// in a depth first manner searching for a set of blinded paths to the given
1255
// node.
1256
func processNodeForBlindedPath(g Graph, node route.Vertex,
1257
        supportsRouteBlinding func(vertex route.Vertex) (bool, error),
1258
        alreadyVisited map[route.Vertex]bool,
1259
        restrictions *BlindedPathRestrictions) ([][]blindedpath.BlindedHop,
1260
        bool, error) {
3✔
1261

3✔
1262
        // If we have already visited the maximum number of hops, then this path
3✔
1263
        // is complete and we can exit now.
3✔
1264
        if len(alreadyVisited) > int(restrictions.NumHops) {
6✔
1265
                return nil, false, nil
3✔
1266
        }
3✔
1267

1268
        // If we have already visited this peer on this path, then we skip
1269
        // processing it again.
1270
        if alreadyVisited[node] {
6✔
1271
                return nil, false, nil
3✔
1272
        }
3✔
1273

1274
        // If we have explicitly been told to ignore this node for blinded paths
1275
        // then we skip it too.
1276
        if restrictions.NodeOmissionSet.Contains(node) {
3✔
1277
                return nil, false, nil
×
1278
        }
×
1279

1280
        supports, err := supportsRouteBlinding(node)
3✔
1281
        if err != nil {
3✔
1282
                return nil, false, err
×
1283
        }
×
1284
        if !supports {
6✔
1285
                return nil, false, nil
3✔
1286
        }
3✔
1287

1288
        // At this point, copy the alreadyVisited map.
1289
        visited := make(map[route.Vertex]bool, len(alreadyVisited))
3✔
1290
        for r := range alreadyVisited {
6✔
1291
                visited[r] = true
3✔
1292
        }
3✔
1293

1294
        // Add this node the visited set.
1295
        visited[node] = true
3✔
1296

3✔
1297
        var (
3✔
1298
                hopSets   [][]blindedpath.BlindedHop
3✔
1299
                chanCount int
3✔
1300
        )
3✔
1301

3✔
1302
        // Now, iterate over the node's channels in search for paths to this
3✔
1303
        // node that can be used for blinded paths
3✔
1304
        err = g.ForEachNodeDirectedChannel(node,
3✔
1305
                func(channel *graphdb.DirectedChannel) error {
6✔
1306
                        // Keep track of how many incoming channels this node
3✔
1307
                        // has. We only use a node as an introduction node if it
3✔
1308
                        // has channels other than the one that lead us to it.
3✔
1309
                        chanCount++
3✔
1310

3✔
1311
                        // Process each channel peer to gather any paths that
3✔
1312
                        // lead to the peer.
3✔
1313
                        nextPaths, hasMoreChans, err := processNodeForBlindedPath( //nolint:ll
3✔
1314
                                g, channel.OtherNode, supportsRouteBlinding,
3✔
1315
                                visited, restrictions,
3✔
1316
                        )
3✔
1317
                        if err != nil {
3✔
1318
                                return err
×
1319
                        }
×
1320

1321
                        hop := blindedpath.BlindedHop{
3✔
1322
                                Vertex:       channel.OtherNode,
3✔
1323
                                ChannelID:    channel.ChannelID,
3✔
1324
                                EdgeCapacity: channel.Capacity,
3✔
1325
                        }
3✔
1326

3✔
1327
                        // For each of the paths returned, unwrap them and
3✔
1328
                        // append this hop to them.
3✔
1329
                        for _, path := range nextPaths {
6✔
1330
                                hopSets = append(
3✔
1331
                                        hopSets,
3✔
1332
                                        append([]blindedpath.BlindedHop{hop},
3✔
1333
                                                path...),
3✔
1334
                                )
3✔
1335
                        }
3✔
1336

1337
                        // If this node does have channels other than the one
1338
                        // that lead to it, and if the hop count up to this node
1339
                        // meets the minHop requirement, then we also add a
1340
                        // path that starts at this node.
1341
                        minNumHops := restrictions.MinDistanceFromIntroNode
3✔
1342
                        if hasMoreChans &&
3✔
1343
                                len(visited) >= int(minNumHops) {
6✔
1344

3✔
1345
                                hopSets = append(hopSets,
3✔
1346
                                        []blindedpath.BlindedHop{hop})
3✔
1347
                        }
3✔
1348

1349
                        return nil
3✔
1350
                },
1351
        )
1352
        if err != nil {
3✔
1353
                return nil, false, err
×
1354
        }
×
1355

1356
        return hopSets, chanCount > 1, nil
3✔
1357
}
1358

1359
// getProbabilityBasedDist converts a weight into a distance that takes into
1360
// account the success probability and the (virtual) cost of a failed payment
1361
// attempt.
1362
//
1363
// Derivation:
1364
//
1365
// Suppose there are two routes A and B with fees Fa and Fb and success
1366
// probabilities Pa and Pb.
1367
//
1368
// Is the expected cost of trying route A first and then B lower than trying the
1369
// other way around?
1370
//
1371
// The expected cost of A-then-B is: Pa*Fa + (1-Pa)*Pb*(c+Fb)
1372
//
1373
// The expected cost of B-then-A is: Pb*Fb + (1-Pb)*Pa*(c+Fa)
1374
//
1375
// In these equations, the term representing the case where both A and B fail is
1376
// left out because its value would be the same in both cases.
1377
//
1378
// Pa*Fa + (1-Pa)*Pb*(c+Fb) < Pb*Fb + (1-Pb)*Pa*(c+Fa)
1379
//
1380
// Pa*Fa + Pb*c + Pb*Fb - Pa*Pb*c - Pa*Pb*Fb < Pb*Fb + Pa*c + Pa*Fa - Pa*Pb*c - Pa*Pb*Fa
1381
//
1382
// Removing terms that cancel out:
1383
// Pb*c - Pa*Pb*Fb < Pa*c - Pa*Pb*Fa
1384
//
1385
// Divide by Pa*Pb:
1386
// c/Pa - Fb < c/Pb - Fa
1387
//
1388
// Move terms around:
1389
// Fa + c/Pa < Fb + c/Pb
1390
//
1391
// So the value of F + c/P can be used to compare routes.
1392
func getProbabilityBasedDist(weight int64, probability float64,
1393
        penalty float64) int64 {
3✔
1394

3✔
1395
        // Prevent divide by zero by returning early.
3✔
1396
        if probability == 0 {
3✔
1397
                return infinity
×
1398
        }
×
1399

1400
        // Calculate distance.
1401
        dist := float64(weight) + penalty/probability
3✔
1402

3✔
1403
        // Avoid cast if an overflow would occur. The maxFloat constant is
3✔
1404
        // chosen to stay well below the maximum float64 value that is still
3✔
1405
        // convertible to int64.
3✔
1406
        const maxFloat = 9000000000000000000
3✔
1407
        if dist > maxFloat {
3✔
1408
                return infinity
×
1409
        }
×
1410

1411
        return int64(dist)
3✔
1412
}
1413

1414
// lastHopPayloadSize calculates the payload size of the final hop in a route.
1415
// It depends on the tlv types which are present and also whether the hop is
1416
// part of a blinded route or not.
1417
func lastHopPayloadSize(r *RestrictParams, finalHtlcExpiry int32,
1418
        amount lnwire.MilliSatoshi) (uint64, error) {
3✔
1419

3✔
1420
        if r.BlindedPaymentPathSet != nil {
6✔
1421
                paymentPath, err := r.BlindedPaymentPathSet.
3✔
1422
                        LargestLastHopPayloadPath()
3✔
1423
                if err != nil {
3✔
1424
                        return 0, err
×
1425
                }
×
1426

1427
                blindedPath := paymentPath.BlindedPath.BlindedHops
3✔
1428
                blindedPoint := paymentPath.BlindedPath.BlindingPoint
3✔
1429

3✔
1430
                encryptedData := blindedPath[len(blindedPath)-1].CipherText
3✔
1431
                finalHop := route.Hop{
3✔
1432
                        AmtToForward:     amount,
3✔
1433
                        OutgoingTimeLock: uint32(finalHtlcExpiry),
3✔
1434
                        EncryptedData:    encryptedData,
3✔
1435
                }
3✔
1436
                if len(blindedPath) == 1 {
6✔
1437
                        finalHop.BlindingPoint = blindedPoint
3✔
1438
                }
3✔
1439

1440
                // The final hop does not have a short chanID set.
1441
                return finalHop.PayloadSize(0), nil
3✔
1442
        }
1443

1444
        var mpp *record.MPP
3✔
1445
        r.PaymentAddr.WhenSome(func(addr [32]byte) {
6✔
1446
                mpp = record.NewMPP(amount, addr)
3✔
1447
        })
3✔
1448

1449
        var amp *record.AMP
3✔
1450
        if r.Amp != nil {
6✔
1451
                // The AMP payload is not easy accessible at this point but we
3✔
1452
                // are only interested in the size of the payload so we just use
3✔
1453
                // the AMP record dummy.
3✔
1454
                amp = &record.MaxAmpPayLoadSize
3✔
1455
        }
3✔
1456

1457
        finalHop := route.Hop{
3✔
1458
                AmtToForward:     amount,
3✔
1459
                OutgoingTimeLock: uint32(finalHtlcExpiry),
3✔
1460
                CustomRecords:    r.DestCustomRecords,
3✔
1461
                MPP:              mpp,
3✔
1462
                AMP:              amp,
3✔
1463
                Metadata:         r.Metadata,
3✔
1464
        }
3✔
1465

3✔
1466
        // The final hop does not have a short chanID set.
3✔
1467
        return finalHop.PayloadSize(0), nil
3✔
1468
}
1469

1470
// overflowSafeAdd adds two MilliSatoshi values and returns the result. If an
1471
// overflow could occur, zero is returned instead and the boolean is set to
1472
// true.
1473
func overflowSafeAdd(x, y lnwire.MilliSatoshi) (lnwire.MilliSatoshi, bool) {
3✔
1474
        if y > math.MaxUint64-x {
3✔
1475
                // Overflow would occur, return 0 and set overflow flag.
×
1476
                return 0, true
×
1477
        }
×
1478

1479
        return x + y, false
3✔
1480
}
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