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

lightningnetwork / lnd / 16096652134

06 Jul 2025 07:30AM UTC coverage: 67.451% (+9.6%) from 57.803%
16096652134

Pull #10042

github

web-flow
Merge 149a502d7 into ff32e90d1
Pull Request #10042: routing: add logs to debug potential payment sending issue

17 of 23 new or added lines in 2 files covered. (73.91%)

288 existing lines in 17 files now uncovered.

134916 of 200021 relevant lines covered (67.45%)

21849.77 hits per line

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

90.65
/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/route"
22
)
23

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

95✔
143
        var (
95✔
144
                hops []*route.Hop
95✔
145

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

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

95✔
157
                blindedPayment *BlindedPayment
95✔
158
        )
95✔
159

95✔
160
        pathLength := len(pathEdges)
95✔
161

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

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

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

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

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

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

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

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

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

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

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

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

267
                        metadata = finalHop.metadata
95✔
268

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

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

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

125✔
290
                        fee = int64(outboundFee) + inboundFee
125✔
291
                        if fee < 0 {
127✔
292
                                fee = 0
2✔
293
                        }
2✔
294

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

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

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

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

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

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

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

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

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

367
                        payload := blindedPath.BlindedHops[dataIndex].CipherText
5✔
368
                        hop.EncryptedData = payload
5✔
369

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

377
                        dataIndex++
5✔
378
                }
379
        }
380

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

390
        return newRoute, nil
95✔
391
}
392

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

1,137✔
408
        return int64(fee) + timeLockPenalty
1,137✔
409
}
1,137✔
410

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

182✔
515
        var max, total lnwire.MilliSatoshi
182✔
516
        cb := func(channel *graphdb.DirectedChannel) error {
629✔
517
                shortID := lnwire.NewShortChanIDFromInt(channel.ChannelID)
447✔
518

447✔
519
                // This log line is needed to debug issues in case we do not
447✔
520
                // have a channel in our graph for some reason when evaluating
447✔
521
                // the local balance. Otherwise we could not tell whether all
447✔
522
                // channels are being evaluated.
447✔
523
                log.Tracef("Evaluating channel %v for local balance", shortID)
447✔
524

447✔
525
                if !channel.OutPolicySet {
447✔
NEW
526
                        log.Debugf("ShortChannelID=%v: has no out policy set, "+
×
NEW
527
                                "skipping", shortID)
×
NEW
528

×
529
                        return nil
×
530
                }
×
531

532
                chanID := channel.ChannelID
447✔
533

447✔
534
                // Enforce outgoing channel restriction.
447✔
535
                if outgoingChans != nil {
467✔
536
                        if _, ok := outgoingChans[chanID]; !ok {
32✔
537
                                return nil
12✔
538
                        }
12✔
539
                }
540

541
                bandwidth, ok := bandwidthHints.availableChanBandwidth(
435✔
542
                        chanID, 0,
435✔
543
                )
435✔
544

435✔
545
                // If the bandwidth is not available, use the channel capacity.
435✔
546
                // This can happen when a channel is added to the graph after
435✔
547
                // we've already queried the bandwidth hints.
435✔
548
                if !ok {
677✔
549
                        bandwidth = lnwire.NewMSatFromSatoshis(channel.Capacity)
242✔
550
                }
242✔
551

552
                if bandwidth > max {
661✔
553
                        max = bandwidth
226✔
554
                }
226✔
555

556
                var overflow bool
435✔
557
                total, overflow = overflowSafeAdd(total, bandwidth)
435✔
558
                if overflow {
435✔
NEW
559
                        log.Warnf("ShortChannelID=%v: overflow detected, "+
×
NEW
560
                                "setting total to max value", shortID)
×
NEW
561

×
562
                        // If the current total and the bandwidth would
×
563
                        // overflow the maximum value, we set the total to the
×
564
                        // maximum value. Which is more milli-satoshis than are
×
565
                        // in existence anyway, so the actual value is
×
566
                        // irrelevant.
×
567
                        total = lnwire.MilliSatoshi(math.MaxUint64)
×
568
                }
×
569

570
                return nil
435✔
571
        }
572

573
        // Iterate over all channels of the to node.
574
        err := g.ForEachNodeDirectedChannel(node, cb)
182✔
575
        if err != nil {
182✔
576
                return 0, 0, err
×
577
        }
×
578
        return max, total, err
182✔
579
}
580

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

188✔
597
        // Pathfinding can be a significant portion of the total payment
188✔
598
        // latency, especially on low-powered devices. Log several metrics to
188✔
599
        // aid in the analysis performance problems in this area.
188✔
600
        start := time.Now()
188✔
601
        nodesVisited := 0
188✔
602
        edgesExpanded := 0
188✔
603
        defer func() {
376✔
604
                timeElapsed := time.Since(start)
188✔
605
                log.Debugf("Pathfinding perf metrics: nodes=%v, edges=%v, "+
188✔
606
                        "time=%v", nodesVisited, edgesExpanded, timeElapsed)
188✔
607
        }()
188✔
608

609
        // If no destination features are provided, we will load what features
610
        // we have for the target node from our graph.
611
        features := r.DestFeatures
188✔
612
        if features == nil {
308✔
613
                var err error
120✔
614
                features, err = g.graph.FetchNodeFeatures(target)
120✔
615
                if err != nil {
120✔
616
                        return nil, 0, err
×
617
                }
×
618
        }
619

620
        // Ensure that the destination's features don't include unknown
621
        // required features.
622
        err := feature.ValidateRequired(features)
188✔
623
        if err != nil {
190✔
624
                log.Warnf("Pathfinding destination node features: %v", err)
2✔
625
                return nil, 0, errUnknownRequiredFeature
2✔
626
        }
2✔
627

628
        // Ensure that all transitive dependencies are set.
629
        err = feature.ValidateDeps(features)
186✔
630
        if err != nil {
188✔
631
                log.Warnf("Pathfinding destination node features: %v", err)
2✔
632
                return nil, 0, errMissingDependentFeature
2✔
633
        }
2✔
634

635
        // Now that we know the feature vector is well-formed, we'll proceed in
636
        // checking that it supports the features we need. If the caller has a
637
        // payment address to attach, check that our destination feature vector
638
        // supports them.
639
        if r.PaymentAddr.IsSome() &&
184✔
640
                !features.HasFeature(lnwire.PaymentAddrOptional) {
186✔
641

2✔
642
                return nil, 0, errNoPaymentAddr
2✔
643
        }
2✔
644

645
        // Set up outgoing channel map for quicker access.
646
        var outgoingChanMap map[uint64]struct{}
182✔
647
        if len(r.OutgoingChannelIDs) > 0 {
188✔
648
                outgoingChanMap = make(map[uint64]struct{})
6✔
649
                for _, outChan := range r.OutgoingChannelIDs {
14✔
650
                        outgoingChanMap[outChan] = struct{}{}
8✔
651
                }
8✔
652
        }
653

654
        // If we are routing from ourselves, check that we have enough local
655
        // balance available.
656
        if source == self {
364✔
657
                max, total, err := getOutgoingBalance(
182✔
658
                        self, outgoingChanMap, g.bandwidthHints, g.graph,
182✔
659
                )
182✔
660
                if err != nil {
182✔
661
                        return nil, 0, err
×
662
                }
×
663

664
                // If the total outgoing balance isn't sufficient, it will be
665
                // impossible to complete the payment.
666
                if total < amt {
187✔
667
                        log.Warnf("Not enough outbound balance to send "+
5✔
668
                                "htlc of amount: %v, only have local "+
5✔
669
                                "balance: %v", amt, total)
5✔
670

5✔
671
                        return nil, 0, errInsufficientBalance
5✔
672
                }
5✔
673

674
                // If there is only not enough capacity on a single route, it
675
                // may still be possible to complete the payment by splitting.
676
                if max < amt {
182✔
677
                        return nil, 0, errNoPathFound
3✔
678
                }
3✔
679
        }
680

681
        // First we'll initialize an empty heap which'll help us to quickly
682
        // locate the next edge we should visit next during our graph
683
        // traversal.
684
        nodeHeap := newDistanceHeap(estimatedNodeCount)
178✔
685

178✔
686
        // Holds the current best distance for a given node.
178✔
687
        distance := make(map[route.Vertex]*nodeWithDist, estimatedNodeCount)
178✔
688

178✔
689
        additionalEdgesWithSrc := make(map[route.Vertex][]*edgePolicyWithSource)
178✔
690
        for vertex, additionalEdges := range g.additionalEdges {
200✔
691
                // Edges connected to self are always included in the graph,
22✔
692
                // therefore can be skipped. This prevents us from trying
22✔
693
                // routes to malformed hop hints.
22✔
694
                if vertex == self {
28✔
695
                        continue
6✔
696
                }
697

698
                // Build reverse lookup to find incoming edges. Needed because
699
                // search is taken place from target to source.
700
                for _, additionalEdge := range additionalEdges {
36✔
701
                        outgoingEdgePolicy := additionalEdge.EdgePolicy()
18✔
702
                        toVertex := outgoingEdgePolicy.ToNodePubKey()
18✔
703

18✔
704
                        incomingEdgePolicy := &edgePolicyWithSource{
18✔
705
                                sourceNode: vertex,
18✔
706
                                edge:       additionalEdge,
18✔
707
                        }
18✔
708

18✔
709
                        additionalEdgesWithSrc[toVertex] =
18✔
710
                                append(additionalEdgesWithSrc[toVertex],
18✔
711
                                        incomingEdgePolicy)
18✔
712
                }
18✔
713
        }
714

715
        // The payload size of the final hop differ from intermediate hops
716
        // and depends on whether the destination is blinded or not.
717
        lastHopPayloadSize, err := lastHopPayloadSize(r, finalHtlcExpiry, amt)
178✔
718
        if err != nil {
178✔
719
                return nil, 0, err
×
720
        }
×
721

722
        // We can't always assume that the end destination is publicly
723
        // advertised to the network so we'll manually include the target node.
724
        // The target node charges no fee. Distance is set to 0, because this is
725
        // the starting point of the graph traversal. We are searching backwards
726
        // to get the fees first time right and correctly match channel
727
        // bandwidth.
728
        //
729
        // Don't record the initial partial path in the distance map and reserve
730
        // that key for the source key in the case we route to ourselves.
731
        partialPath := &nodeWithDist{
178✔
732
                dist:              0,
178✔
733
                weight:            0,
178✔
734
                node:              target,
178✔
735
                netAmountReceived: amt,
178✔
736
                incomingCltv:      finalHtlcExpiry,
178✔
737
                probability:       1,
178✔
738
                routingInfoSize:   lastHopPayloadSize,
178✔
739
        }
178✔
740

178✔
741
        // Calculate the absolute cltv limit. Use uint64 to prevent an overflow
178✔
742
        // if the cltv limit is MaxUint32.
178✔
743
        absoluteCltvLimit := uint64(r.CltvLimit) + uint64(finalHtlcExpiry)
178✔
744

178✔
745
        // Calculate the default attempt cost as configured globally.
178✔
746
        defaultAttemptCost := float64(
178✔
747
                cfg.AttemptCost +
178✔
748
                        amt*lnwire.MilliSatoshi(cfg.AttemptCostPPM)/1000000,
178✔
749
        )
178✔
750

178✔
751
        // Validate time preference value.
178✔
752
        if math.Abs(timePref) > 1 {
178✔
753
                return nil, 0, fmt.Errorf("time preference %v out of range "+
×
754
                        "[-1, 1]", timePref)
×
755
        }
×
756

757
        // Scale to avoid the extremes -1 and 1 which run into infinity issues.
758
        timePref *= 0.9
178✔
759

178✔
760
        // Apply time preference. At 0, the default attempt cost will
178✔
761
        // be used.
178✔
762
        absoluteAttemptCost := defaultAttemptCost * (1/(0.5-timePref/2) - 1)
178✔
763

178✔
764
        log.Debugf("Pathfinding absolute attempt cost: %v sats",
178✔
765
                absoluteAttemptCost/1000)
178✔
766

178✔
767
        // processEdge is a helper closure that will be used to make sure edges
178✔
768
        // satisfy our specific requirements.
178✔
769
        processEdge := func(fromVertex route.Vertex,
178✔
770
                edge *unifiedEdge, toNodeDist *nodeWithDist) {
1,432✔
771

1,254✔
772
                edgesExpanded++
1,254✔
773

1,254✔
774
                // Calculate inbound fee charged by "to" node. The exit hop
1,254✔
775
                // doesn't charge inbound fees. If the "to" node is the exit
1,254✔
776
                // hop, its inbound fees have already been set to zero by
1,254✔
777
                // nodeEdgeUnifier.
1,254✔
778
                inboundFee := edge.inboundFees.CalcFee(
1,254✔
779
                        toNodeDist.netAmountReceived,
1,254✔
780
                )
1,254✔
781

1,254✔
782
                // Make sure that the node total fee is never negative.
1,254✔
783
                // Routing nodes treat a total fee that turns out
1,254✔
784
                // negative as a zero fee and pathfinding should do the
1,254✔
785
                // same.
1,254✔
786
                minInboundFee := -int64(toNodeDist.outboundFee)
1,254✔
787
                if inboundFee < minInboundFee {
1,256✔
788
                        inboundFee = minInboundFee
2✔
789
                }
2✔
790

791
                // Calculate amount that the candidate node would have to send
792
                // out.
793
                amountToSend := toNodeDist.netAmountReceived +
1,254✔
794
                        lnwire.MilliSatoshi(inboundFee)
1,254✔
795

1,254✔
796
                // Check if accumulated fees would exceed fee limit when this
1,254✔
797
                // node would be added to the path.
1,254✔
798
                totalFee := int64(amountToSend) - int64(amt)
1,254✔
799

1,254✔
800
                log.Trace(lnutils.NewLogClosure(func() string {
1,254✔
801
                        return fmt.Sprintf(
×
802
                                "Checking fromVertex (%v) with "+
×
803
                                        "minInboundFee=%v, inboundFee=%v, "+
×
804
                                        "amountToSend=%v, amt=%v, totalFee=%v",
×
805
                                fromVertex, minInboundFee, inboundFee,
×
806
                                amountToSend, amt, totalFee,
×
807
                        )
×
808
                }))
×
809

810
                if totalFee > 0 && lnwire.MilliSatoshi(totalFee) > r.FeeLimit {
1,258✔
811
                        return
4✔
812
                }
4✔
813

814
                // Request the success probability for this edge.
815
                edgeProbability := r.ProbabilitySource(
1,250✔
816
                        fromVertex, toNodeDist.node, amountToSend,
1,250✔
817
                        edge.capacity,
1,250✔
818
                )
1,250✔
819

1,250✔
820
                log.Trace(lnutils.NewLogClosure(func() string {
1,250✔
821
                        return fmt.Sprintf("path finding probability: fromnode=%v,"+
×
822
                                " tonode=%v, amt=%v, cap=%v, probability=%v",
×
823
                                fromVertex, toNodeDist.node, amountToSend,
×
824
                                edge.capacity, edgeProbability)
×
825
                }))
×
826

827
                // If the probability is zero, there is no point in trying.
828
                if edgeProbability == 0 {
1,250✔
829
                        return
×
830
                }
×
831

832
                // Compute fee that fromVertex is charging. It is based on the
833
                // amount that needs to be sent to the next node in the route.
834
                //
835
                // Source node has no predecessor to pay a fee. Therefore set
836
                // fee to zero, because it should not be included in the fee
837
                // limit check and edge weight.
838
                //
839
                // Also determine the time lock delta that will be added to the
840
                // route if fromVertex is selected. If fromVertex is the source
841
                // node, no additional timelock is required.
842
                var (
1,250✔
843
                        timeLockDelta uint16
1,250✔
844
                        outboundFee   int64
1,250✔
845
                )
1,250✔
846

1,250✔
847
                if fromVertex != source {
2,356✔
848
                        outboundFee = int64(
1,106✔
849
                                edge.policy.ComputeFee(amountToSend),
1,106✔
850
                        )
1,106✔
851
                        timeLockDelta = edge.policy.TimeLockDelta
1,106✔
852
                }
1,106✔
853

854
                incomingCltv := toNodeDist.incomingCltv + int32(timeLockDelta)
1,250✔
855

1,250✔
856
                // Check that we are within our CLTV limit.
1,250✔
857
                if uint64(incomingCltv) > absoluteCltvLimit {
1,262✔
858
                        return
12✔
859
                }
12✔
860

861
                // netAmountToReceive is the amount that the node that is added
862
                // to the distance map needs to receive from a (to be found)
863
                // previous node in the route. The inbound fee of the receiving
864
                // node is already subtracted from this value. The previous node
865
                // will need to pay the amount that this node forwards plus the
866
                // fee it charges plus this node's inbound fee.
867
                netAmountToReceive := amountToSend +
1,238✔
868
                        lnwire.MilliSatoshi(outboundFee)
1,238✔
869

1,238✔
870
                // Calculate total probability of successfully reaching target
1,238✔
871
                // by multiplying the probabilities. Both this edge and the rest
1,238✔
872
                // of the route must succeed.
1,238✔
873
                probability := toNodeDist.probability * edgeProbability
1,238✔
874

1,238✔
875
                // If the probability is below the specified lower bound, we can
1,238✔
876
                // abandon this direction. Adding further nodes can only lower
1,238✔
877
                // the probability more.
1,238✔
878
                if probability < cfg.MinProbability {
1,341✔
879
                        return
103✔
880
                }
103✔
881

882
                // Calculate the combined fee for this edge. Dijkstra does not
883
                // support negative edge weights. Because this fee feeds into
884
                // the edge weight calculation, we don't allow it to be
885
                // negative.
886
                signedFee := inboundFee + outboundFee
1,137✔
887
                fee := lnwire.MilliSatoshi(0)
1,137✔
888
                if signedFee > 0 {
1,697✔
889
                        fee = lnwire.MilliSatoshi(signedFee)
560✔
890
                }
560✔
891

892
                // By adding fromVertex in the route, there will be an extra
893
                // weight composed of the fee that this node will charge and
894
                // the amount that will be locked for timeLockDelta blocks in
895
                // the HTLC that is handed out to fromVertex.
896
                weight := edgeWeight(amountToSend, fee, timeLockDelta)
1,137✔
897

1,137✔
898
                // Compute the tentative weight to this new channel/edge
1,137✔
899
                // which is the weight from our toNode to the target node
1,137✔
900
                // plus the weight of this edge.
1,137✔
901
                tempWeight := toNodeDist.weight + weight
1,137✔
902

1,137✔
903
                // Add an extra factor to the weight to take into account the
1,137✔
904
                // probability. Another reason why we rounded the fee up to zero
1,137✔
905
                // is to prevent a highly negative fee from cancelling out the
1,137✔
906
                // extra factor. We don't want an always-failing node to attract
1,137✔
907
                // traffic using a highly negative fee and escape penalization.
1,137✔
908
                tempDist := getProbabilityBasedDist(
1,137✔
909
                        tempWeight, probability,
1,137✔
910
                        absoluteAttemptCost,
1,137✔
911
                )
1,137✔
912

1,137✔
913
                // If there is already a best route stored, compare this
1,137✔
914
                // candidate route with the best route so far.
1,137✔
915
                current, ok := distance[fromVertex]
1,137✔
916
                if ok {
1,534✔
917
                        // If this route is worse than what we already found,
397✔
918
                        // skip this route.
397✔
919
                        if tempDist > current.dist {
704✔
920
                                return
307✔
921
                        }
307✔
922

923
                        // If the route is equally good and the probability
924
                        // isn't better, skip this route. It is important to
925
                        // also return if both cost and probability are equal,
926
                        // because otherwise the algorithm could run into an
927
                        // endless loop.
928
                        probNotBetter := probability <= current.probability
92✔
929
                        if tempDist == current.dist && probNotBetter {
153✔
930
                                return
61✔
931
                        }
61✔
932
                }
933

934
                // Calculate the total routing info size if this hop were to be
935
                // included. If we are coming from the source hop, the payload
936
                // size is zero, because the original htlc isn't in the onion
937
                // blob.
938
                //
939
                // NOTE: For blinded paths with the NUMS key as the last hop,
940
                // the payload size accounts for this dummy hop which is of
941
                // the same size as the real last hop. So we account for a
942
                // bigger size than the route is however we accept this
943
                // little inaccuracy here because we are over estimating by
944
                // 1 hop.
945
                var payloadSize uint64
773✔
946
                if fromVertex != source {
1,406✔
947
                        // In case the unifiedEdge does not have a payload size
633✔
948
                        // function supplied we request a graceful shutdown
633✔
949
                        // because this should never happen.
633✔
950
                        if edge.hopPayloadSizeFn == nil {
633✔
951
                                log.Criticalf("No payload size function "+
×
952
                                        "available for edge=%v unable to "+
×
953
                                        "determine payload size: %v", edge,
×
954
                                        ErrNoPayLoadSizeFunc)
×
955

×
956
                                return
×
957
                        }
×
958

959
                        payloadSize = edge.hopPayloadSizeFn(
633✔
960
                                amountToSend,
633✔
961
                                uint32(toNodeDist.incomingCltv),
633✔
962
                                edge.policy.ChannelID,
633✔
963
                        )
633✔
964
                }
965

966
                routingInfoSize := toNodeDist.routingInfoSize + payloadSize
773✔
967
                // Skip paths that would exceed the maximum routing info size.
773✔
968
                if routingInfoSize > sphinx.MaxPayloadSize {
779✔
969
                        return
6✔
970
                }
6✔
971

972
                // All conditions are met and this new tentative distance is
973
                // better than the current best known distance to this node.
974
                // The new better distance is recorded, and also our "next hop"
975
                // map is populated with this edge.
976
                withDist := &nodeWithDist{
767✔
977
                        dist:              tempDist,
767✔
978
                        weight:            tempWeight,
767✔
979
                        node:              fromVertex,
767✔
980
                        netAmountReceived: netAmountToReceive,
767✔
981
                        outboundFee:       lnwire.MilliSatoshi(outboundFee),
767✔
982
                        incomingCltv:      incomingCltv,
767✔
983
                        probability:       probability,
767✔
984
                        nextHop:           edge,
767✔
985
                        routingInfoSize:   routingInfoSize,
767✔
986
                }
767✔
987
                distance[fromVertex] = withDist
767✔
988

767✔
989
                // Either push withDist onto the heap if the node
767✔
990
                // represented by fromVertex is not already on the heap OR adjust
767✔
991
                // its position within the heap via heap.Fix.
767✔
992
                nodeHeap.PushOrFix(withDist)
767✔
993
        }
994

995
        // TODO(roasbeef): also add path caching
996
        //  * similar to route caching, but doesn't factor in the amount
997

998
        // Cache features because we visit nodes multiple times.
999
        featureCache := make(map[route.Vertex]*lnwire.FeatureVector)
178✔
1000

178✔
1001
        // getGraphFeatures returns (cached) node features from the graph.
178✔
1002
        getGraphFeatures := func(node route.Vertex) (*lnwire.FeatureVector,
178✔
1003
                error) {
1,436✔
1004

1,258✔
1005
                // Check cache for features of the fromNode.
1,258✔
1006
                fromFeatures, ok := featureCache[node]
1,258✔
1007
                if ok {
1,723✔
1008
                        return fromFeatures, nil
465✔
1009
                }
465✔
1010

1011
                // Fetch node features fresh from the graph.
1012
                fromFeatures, err := g.graph.FetchNodeFeatures(node)
795✔
1013
                if err != nil {
795✔
1014
                        return nil, err
×
1015
                }
×
1016

1017
                // Don't route through nodes that contain unknown required
1018
                // features and mark as nil in the cache.
1019
                err = feature.ValidateRequired(fromFeatures)
795✔
1020
                if err != nil {
797✔
1021
                        featureCache[node] = nil
2✔
1022
                        return nil, nil
2✔
1023
                }
2✔
1024

1025
                // Don't route through nodes that don't properly set all
1026
                // transitive feature dependencies and mark as nil in the cache.
1027
                err = feature.ValidateDeps(fromFeatures)
793✔
1028
                if err != nil {
795✔
1029
                        featureCache[node] = nil
2✔
1030
                        return nil, nil
2✔
1031
                }
2✔
1032

1033
                // Update cache.
1034
                featureCache[node] = fromFeatures
791✔
1035

791✔
1036
                return fromFeatures, nil
791✔
1037
        }
1038

1039
        routeToSelf := source == target
178✔
1040
        for {
875✔
1041
                nodesVisited++
697✔
1042

697✔
1043
                pivot := partialPath.node
697✔
1044
                isExitHop := partialPath.nextHop == nil
697✔
1045

697✔
1046
                // Create unified policies for all incoming connections. Don't
697✔
1047
                // use inbound fees for the exit hop.
697✔
1048
                u := newNodeEdgeUnifier(
697✔
1049
                        self, pivot, !isExitHop, outgoingChanMap,
697✔
1050
                )
697✔
1051

697✔
1052
                err := u.addGraphPolicies(g.graph)
697✔
1053
                if err != nil {
697✔
1054
                        return nil, 0, err
×
1055
                }
×
1056

1057
                // We add hop hints that were supplied externally.
1058
                for _, reverseEdge := range additionalEdgesWithSrc[pivot] {
715✔
1059
                        // Assume zero inbound fees for route hints. If inbound
18✔
1060
                        // fees would apply, they couldn't be communicated in
18✔
1061
                        // bolt11 invoices currently.
18✔
1062
                        inboundFee := models.InboundFee{}
18✔
1063

18✔
1064
                        // Hop hints don't contain a capacity. We set one here,
18✔
1065
                        // since a capacity is needed for probability
18✔
1066
                        // calculations. We set a high capacity to act as if
18✔
1067
                        // there is enough liquidity, otherwise the hint would
18✔
1068
                        // not have been added by a wallet.
18✔
1069
                        // We also pass the payload size function to the
18✔
1070
                        // graph data so that we calculate the exact payload
18✔
1071
                        // size when evaluating this hop for a route.
18✔
1072
                        u.addPolicy(
18✔
1073
                                reverseEdge.sourceNode,
18✔
1074
                                reverseEdge.edge.EdgePolicy(),
18✔
1075
                                inboundFee,
18✔
1076
                                fakeHopHintCapacity,
18✔
1077
                                reverseEdge.edge.IntermediatePayloadSize,
18✔
1078
                                reverseEdge.edge.BlindedPayment(),
18✔
1079
                        )
18✔
1080
                }
18✔
1081

1082
                netAmountReceived := partialPath.netAmountReceived
697✔
1083

697✔
1084
                // Expand all connections using the optimal policy for each
697✔
1085
                // connection.
697✔
1086
                for fromNode, edgeUnifier := range u.edgeUnifiers {
2,318✔
1087
                        // The target node is not recorded in the distance map.
1,621✔
1088
                        // Therefore we need to have this check to prevent
1,621✔
1089
                        // creating a cycle. Only when we intend to route to
1,621✔
1090
                        // self, we allow this cycle to form. In that case we'll
1,621✔
1091
                        // also break out of the search loop below.
1,621✔
1092
                        if !routeToSelf && fromNode == target {
1,952✔
1093
                                continue
331✔
1094
                        }
1095

1096
                        // Apply last hop restriction if set.
1097
                        if r.LastHop != nil &&
1,292✔
1098
                                pivot == target && fromNode != *r.LastHop {
1,296✔
1099

4✔
1100
                                continue
4✔
1101
                        }
1102

1103
                        edge := edgeUnifier.getEdge(
1,288✔
1104
                                netAmountReceived, g.bandwidthHints,
1,288✔
1105
                                partialPath.outboundFee,
1,288✔
1106
                        )
1,288✔
1107

1,288✔
1108
                        if edge == nil {
1,320✔
1109
                                continue
32✔
1110
                        }
1111

1112
                        // Get feature vector for fromNode.
1113
                        fromFeatures, err := getGraphFeatures(fromNode)
1,258✔
1114
                        if err != nil {
1,258✔
1115
                                return nil, 0, err
×
1116
                        }
×
1117

1118
                        // If there are no valid features, skip this node.
1119
                        if fromFeatures == nil {
1,262✔
1120
                                continue
4✔
1121
                        }
1122

1123
                        // Check if this candidate node is better than what we
1124
                        // already have.
1125
                        processEdge(fromNode, edge, partialPath)
1,254✔
1126
                }
1127

1128
                if nodeHeap.Len() == 0 {
739✔
1129
                        break
42✔
1130
                }
1131

1132
                // Fetch the node within the smallest distance from our source
1133
                // from the heap.
1134
                partialPath = heap.Pop(&nodeHeap).(*nodeWithDist)
657✔
1135

657✔
1136
                // If we've reached our source (or we don't have any incoming
657✔
1137
                // edges), then we're done here and can exit the graph
657✔
1138
                // traversal early.
657✔
1139
                if partialPath.node == source {
795✔
1140
                        break
138✔
1141
                }
1142
        }
1143

1144
        // Use the distance map to unravel the forward path from source to
1145
        // target.
1146
        var pathEdges []*unifiedEdge
178✔
1147
        currentNode := source
178✔
1148
        for {
560✔
1149
                // Determine the next hop forward using the next map.
382✔
1150
                currentNodeWithDist, ok := distance[currentNode]
382✔
1151
                if !ok {
424✔
1152
                        // If the node doesn't have a next hop it means we
42✔
1153
                        // didn't find a path.
42✔
1154
                        return nil, 0, errNoPathFound
42✔
1155
                }
42✔
1156

1157
                // Add the next hop to the list of path edges.
1158
                pathEdges = append(pathEdges, currentNodeWithDist.nextHop)
342✔
1159

342✔
1160
                // Advance current node.
342✔
1161
                currentNode = currentNodeWithDist.nextHop.policy.ToNodePubKey()
342✔
1162

342✔
1163
                // Check stop condition at the end of this loop. This prevents
342✔
1164
                // breaking out too soon for self-payments that have target set
342✔
1165
                // to source.
342✔
1166
                if currentNode == target {
480✔
1167
                        break
138✔
1168
                }
1169
        }
1170

1171
        // For the final hop, we'll set the node features to those determined
1172
        // above. These are either taken from the destination features, e.g.
1173
        // virtual or invoice features, or loaded as a fallback from the graph.
1174
        // The transitive dependencies were already validated above, so no need
1175
        // to do so now.
1176
        //
1177
        // NOTE: This may overwrite features loaded from the graph if
1178
        // destination features were provided. This is fine though, since our
1179
        // route construction does not care where the features are actually
1180
        // taken from. In the future we may wish to do route construction within
1181
        // findPath, and avoid using ChannelEdgePolicy altogether.
1182
        pathEdges[len(pathEdges)-1].policy.ToNodeFeatures = features
138✔
1183

138✔
1184
        log.Debugf("Found route: probability=%v, hops=%v, fee=%v",
138✔
1185
                distance[source].probability, len(pathEdges),
138✔
1186
                distance[source].netAmountReceived-amt)
138✔
1187

138✔
1188
        return pathEdges, distance[source].probability, nil
138✔
1189
}
1190

1191
// blindedPathRestrictions are a set of constraints to adhere to when
1192
// choosing a set of blinded paths to this node.
1193
type blindedPathRestrictions struct {
1194
        // minNumHops is the minimum number of hops to include in a blinded
1195
        // path. This doesn't include our node, so if the minimum is 1, then
1196
        // the path will contain at minimum our node along with an introduction
1197
        // node hop. A minimum of 0 will include paths where this node is the
1198
        // introduction node and so should be used with caution.
1199
        minNumHops uint8
1200

1201
        // maxNumHops is the maximum number of hops to include in a blinded
1202
        // path. This doesn't include our node, so if the maximum is 1, then
1203
        // the path will contain our node along with an introduction node hop.
1204
        maxNumHops uint8
1205

1206
        // nodeOmissionSet holds a set of node IDs of nodes that we should
1207
        // ignore during blinded path selection.
1208
        nodeOmissionSet fn.Set[route.Vertex]
1209

1210
        // incomingChainedChannels holds the chained channels list specified
1211
        // via scid (short channel id) starting from a channel which points to
1212
        // the receiver node.
1213
        incomingChainedChannels []uint64
1214
}
1215

1216
// blindedHop holds the information about a hop we have selected for a blinded
1217
// path.
1218
type blindedHop struct {
1219
        vertex       route.Vertex
1220
        channelID    uint64
1221
        edgeCapacity btcutil.Amount
1222
}
1223

1224
// findBlindedPaths does a depth first search from the target node to find a set
1225
// of blinded paths to the target node given the set of restrictions. This
1226
// function will select and return any candidate path. A candidate path is a
1227
// path to the target node with a size determined by the given hop number
1228
// constraints where all the nodes on the path signal the route blinding feature
1229
// _and_ the introduction node for the path has more than one public channel.
1230
// Any filtering of paths based on payment value or success probabilities is
1231
// left to the caller.
1232
func findBlindedPaths(g Graph, target route.Vertex,
1233
        restrictions *blindedPathRestrictions) ([][]blindedHop, error) {
21✔
1234

21✔
1235
        // Sanity check the restrictions.
21✔
1236
        if restrictions.minNumHops > restrictions.maxNumHops {
21✔
1237
                return nil, fmt.Errorf("maximum number of blinded path hops "+
×
1238
                        "(%d) must be greater than or equal to the minimum "+
×
1239
                        "number of hops (%d)", restrictions.maxNumHops,
×
1240
                        restrictions.minNumHops)
×
1241
        }
×
1242

1243
        var (
21✔
1244
                // The target node is always the last hop in the path, and so
21✔
1245
                // we add it to the incoming path from the get-go. Any additions
21✔
1246
                // to the slice should be prepended.
21✔
1247
                incomingPath = []blindedHop{{
21✔
1248
                        vertex: target,
21✔
1249
                }}
21✔
1250

21✔
1251
                // supportsRouteBlinding is a list of nodes that we can assume
21✔
1252
                // support route blinding without needing to rely on the feature
21✔
1253
                // bits announced in their node announcement. Since we are
21✔
1254
                // finding a path to the target node, we can assume it supports
21✔
1255
                // route blinding.
21✔
1256
                supportsRouteBlinding = map[route.Vertex]bool{
21✔
1257
                        target: true,
21✔
1258
                }
21✔
1259

21✔
1260
                visited          = make(map[route.Vertex]bool)
21✔
1261
                nextTarget       = target
21✔
1262
                haveIncomingPath = len(restrictions.incomingChainedChannels) > 0
21✔
1263

21✔
1264
                // errChanFound is an error variable we return from the DB
21✔
1265
                // iteration call below when we have found the channel we are
21✔
1266
                // looking for. This lets us exit the iteration early.
21✔
1267
                errChanFound = errors.New("found incoming channel")
21✔
1268
        )
21✔
1269
        for _, chanID := range restrictions.incomingChainedChannels {
34✔
1270
                // Mark that we have visited this node so that we don't revisit
13✔
1271
                // it later on when we call "processNodeForBlindedPath".
13✔
1272
                visited[nextTarget] = true
13✔
1273

13✔
1274
                err := g.ForEachNodeDirectedChannel(nextTarget,
13✔
1275
                        func(channel *graphdb.DirectedChannel) error {
36✔
1276
                                // This is not the right channel, continue to
23✔
1277
                                // the node's other channels.
23✔
1278
                                if channel.ChannelID != chanID {
35✔
1279
                                        return nil
12✔
1280
                                }
12✔
1281

1282
                                // We found the channel in question. Prepend it
1283
                                // to the incoming path.
1284
                                incomingPath = append([]blindedHop{
13✔
1285
                                        {
13✔
1286
                                                vertex:       channel.OtherNode,
13✔
1287
                                                channelID:    channel.ChannelID,
13✔
1288
                                                edgeCapacity: channel.Capacity,
13✔
1289
                                        },
13✔
1290
                                }, incomingPath...)
13✔
1291

13✔
1292
                                // Update the target node.
13✔
1293
                                nextTarget = channel.OtherNode
13✔
1294

13✔
1295
                                return errChanFound
13✔
1296
                        },
1297
                )
1298
                // We expect errChanFound to be returned if the channel in
1299
                // question was found.
1300
                if !errors.Is(err, errChanFound) && err != nil {
13✔
1301
                        return nil, err
×
1302
                } else if err == nil {
15✔
1303
                        return nil, fmt.Errorf("incoming channel %d is not "+
2✔
1304
                                "seen as owned by node %v", chanID, nextTarget)
2✔
1305
                }
2✔
1306

1307
                // Check that the user didn't accidentally add a channel that
1308
                // is owned by a node in the node omission set.
1309
                if restrictions.nodeOmissionSet.Contains(nextTarget) {
16✔
1310
                        return nil, fmt.Errorf("node %v cannot simultaneously "+
3✔
1311
                                "be included in the omission set and in the "+
3✔
1312
                                "partially specified path", nextTarget)
3✔
1313
                }
3✔
1314

1315
                // Check that we have not already visited the next target node
1316
                // since this would mean a circular incoming path.
1317
                if visited[nextTarget] {
13✔
1318
                        return nil, fmt.Errorf("a circular route cannot be " +
1✔
1319
                                "specified for the incoming blinded path")
1✔
1320
                }
1✔
1321

1322
                supportsRouteBlinding[nextTarget] = true
11✔
1323
        }
1324

1325
        // A helper closure which checks if the node in question has advertised
1326
        // that it supports route blinding.
1327
        nodeSupportsRouteBlinding := func(node route.Vertex) (bool, error) {
107✔
1328
                if supportsRouteBlinding[node] {
107✔
1329
                        return true, nil
19✔
1330
                }
19✔
1331

1332
                features, err := g.FetchNodeFeatures(node)
71✔
1333
                if err != nil {
71✔
1334
                        return false, err
×
1335
                }
×
1336

1337
                return features.HasFeature(lnwire.RouteBlindingOptional), nil
71✔
1338
        }
1339

1340
        // This function will have some recursion. We will spin out from the
1341
        // target node & append edges to the paths until we reach various exit
1342
        // conditions such as: The maxHops number being reached or reaching
1343
        // a node that doesn't have any other edges - in that final case, the
1344
        // whole path should be ignored.
1345
        //
1346
        // NOTE: any paths returned will end at the "nextTarget" node meaning
1347
        // that if we have a fixed list of incoming chained channels, then this
1348
        // fixed list must be appended to any of the returned paths.
1349
        paths, _, err := processNodeForBlindedPath(
19✔
1350
                g, nextTarget, nodeSupportsRouteBlinding, visited, restrictions,
19✔
1351
        )
19✔
1352
        if err != nil {
19✔
1353
                return nil, err
×
1354
        }
×
1355

1356
        // Reverse each path so that the order is correct (from introduction
1357
        // node to last hop node) and then append the incoming path (if any was
1358
        // specified) to the end of the path.
1359
        orderedPaths := make([][]blindedHop, 0, len(paths))
19✔
1360
        for _, path := range paths {
60✔
1361
                sort.Slice(path, func(i, j int) bool {
79✔
1362
                        return j < i
38✔
1363
                })
38✔
1364

1365
                orderedPaths = append(
41✔
1366
                        orderedPaths, append(path, incomingPath...),
41✔
1367
                )
41✔
1368
        }
1369

1370
        // There is a chance we have an incoming path that by itself satisfies
1371
        // the minimum hop restriction. In that case, we add it as its own path.
1372
        if haveIncomingPath &&
19✔
1373
                len(incomingPath) > int(restrictions.minNumHops) {
26✔
1374

7✔
1375
                orderedPaths = append(orderedPaths, incomingPath)
7✔
1376
        }
7✔
1377

1378
        // Handle the special case that allows a blinded path with the
1379
        // introduction node as the destination node. This only applies if no
1380
        // incoming path was specified since in that case, by definition, the
1381
        // caller wants a non-zero length blinded path.
1382
        if restrictions.minNumHops == 0 && !haveIncomingPath {
23✔
1383
                singleHopPath := [][]blindedHop{{{vertex: target}}}
4✔
1384

4✔
1385
                orderedPaths = append(
4✔
1386
                        orderedPaths, singleHopPath...,
4✔
1387
                )
4✔
1388
        }
4✔
1389

1390
        return orderedPaths, err
19✔
1391
}
1392

1393
// processNodeForBlindedPath is a recursive function that traverses the graph
1394
// in a depth first manner searching for a set of blinded paths to the given
1395
// node.
1396
func processNodeForBlindedPath(g Graph, node route.Vertex,
1397
        supportsRouteBlinding func(vertex route.Vertex) (bool, error),
1398
        alreadyVisited map[route.Vertex]bool,
1399
        restrictions *blindedPathRestrictions) ([][]blindedHop, bool, error) {
236✔
1400

236✔
1401
        // If we have already visited the maximum number of hops, then this path
236✔
1402
        // is complete and we can exit now.
236✔
1403
        if len(alreadyVisited) > int(restrictions.maxNumHops) {
343✔
1404
                return nil, false, nil
107✔
1405
        }
107✔
1406

1407
        // If we have already visited this peer on this path, then we skip
1408
        // processing it again.
1409
        if alreadyVisited[node] {
172✔
1410
                return nil, false, nil
41✔
1411
        }
41✔
1412

1413
        // If we have explicitly been told to ignore this node for blinded paths
1414
        // then we skip it too.
1415
        if restrictions.nodeOmissionSet.Contains(node) {
96✔
1416
                return nil, false, nil
4✔
1417
        }
4✔
1418

1419
        supports, err := supportsRouteBlinding(node)
88✔
1420
        if err != nil {
88✔
1421
                return nil, false, err
×
1422
        }
×
1423
        if !supports {
96✔
1424
                return nil, false, nil
8✔
1425
        }
8✔
1426

1427
        // At this point, copy the alreadyVisited map.
1428
        visited := make(map[route.Vertex]bool, len(alreadyVisited))
82✔
1429
        for r := range alreadyVisited {
196✔
1430
                visited[r] = true
114✔
1431
        }
114✔
1432

1433
        // Add this node the visited set.
1434
        visited[node] = true
82✔
1435

82✔
1436
        var (
82✔
1437
                hopSets   [][]blindedHop
82✔
1438
                chanCount int
82✔
1439
        )
82✔
1440

82✔
1441
        // Now, iterate over the node's channels in search for paths to this
82✔
1442
        // node that can be used for blinded paths
82✔
1443
        err = g.ForEachNodeDirectedChannel(node,
82✔
1444
                func(channel *graphdb.DirectedChannel) error {
301✔
1445
                        // Keep track of how many incoming channels this node
219✔
1446
                        // has. We only use a node as an introduction node if it
219✔
1447
                        // has channels other than the one that lead us to it.
219✔
1448
                        chanCount++
219✔
1449

219✔
1450
                        // Process each channel peer to gather any paths that
219✔
1451
                        // lead to the peer.
219✔
1452
                        nextPaths, hasMoreChans, err := processNodeForBlindedPath( //nolint:ll
219✔
1453
                                g, channel.OtherNode, supportsRouteBlinding,
219✔
1454
                                visited, restrictions,
219✔
1455
                        )
219✔
1456
                        if err != nil {
219✔
1457
                                return err
×
1458
                        }
×
1459

1460
                        hop := blindedHop{
219✔
1461
                                vertex:       channel.OtherNode,
219✔
1462
                                channelID:    channel.ChannelID,
219✔
1463
                                edgeCapacity: channel.Capacity,
219✔
1464
                        }
219✔
1465

219✔
1466
                        // For each of the paths returned, unwrap them and
219✔
1467
                        // append this hop to them.
219✔
1468
                        for _, path := range nextPaths {
254✔
1469
                                hopSets = append(
35✔
1470
                                        hopSets,
35✔
1471
                                        append([]blindedHop{hop}, path...),
35✔
1472
                                )
35✔
1473
                        }
35✔
1474

1475
                        // If this node does have channels other than the one
1476
                        // that lead to it, and if the hop count up to this node
1477
                        // meets the minHop requirement, then we also add a
1478
                        // path that starts at this node.
1479
                        if hasMoreChans &&
219✔
1480
                                len(visited) >= int(restrictions.minNumHops) {
260✔
1481

41✔
1482
                                hopSets = append(hopSets, []blindedHop{hop})
41✔
1483
                        }
41✔
1484

1485
                        return nil
219✔
1486
                },
1487
        )
1488
        if err != nil {
82✔
1489
                return nil, false, err
×
1490
        }
×
1491

1492
        return hopSets, chanCount > 1, nil
82✔
1493
}
1494

1495
// getProbabilityBasedDist converts a weight into a distance that takes into
1496
// account the success probability and the (virtual) cost of a failed payment
1497
// attempt.
1498
//
1499
// Derivation:
1500
//
1501
// Suppose there are two routes A and B with fees Fa and Fb and success
1502
// probabilities Pa and Pb.
1503
//
1504
// Is the expected cost of trying route A first and then B lower than trying the
1505
// other way around?
1506
//
1507
// The expected cost of A-then-B is: Pa*Fa + (1-Pa)*Pb*(c+Fb)
1508
//
1509
// The expected cost of B-then-A is: Pb*Fb + (1-Pb)*Pa*(c+Fa)
1510
//
1511
// In these equations, the term representing the case where both A and B fail is
1512
// left out because its value would be the same in both cases.
1513
//
1514
// Pa*Fa + (1-Pa)*Pb*(c+Fb) < Pb*Fb + (1-Pb)*Pa*(c+Fa)
1515
//
1516
// Pa*Fa + Pb*c + Pb*Fb - Pa*Pb*c - Pa*Pb*Fb < Pb*Fb + Pa*c + Pa*Fa - Pa*Pb*c - Pa*Pb*Fa
1517
//
1518
// Removing terms that cancel out:
1519
// Pb*c - Pa*Pb*Fb < Pa*c - Pa*Pb*Fa
1520
//
1521
// Divide by Pa*Pb:
1522
// c/Pa - Fb < c/Pb - Fa
1523
//
1524
// Move terms around:
1525
// Fa + c/Pa < Fb + c/Pb
1526
//
1527
// So the value of F + c/P can be used to compare routes.
1528
func getProbabilityBasedDist(weight int64, probability float64,
1529
        penalty float64) int64 {
1,137✔
1530

1,137✔
1531
        // Prevent divide by zero by returning early.
1,137✔
1532
        if probability == 0 {
1,137✔
1533
                return infinity
×
1534
        }
×
1535

1536
        // Calculate distance.
1537
        dist := float64(weight) + penalty/probability
1,137✔
1538

1,137✔
1539
        // Avoid cast if an overflow would occur. The maxFloat constant is
1,137✔
1540
        // chosen to stay well below the maximum float64 value that is still
1,137✔
1541
        // convertible to int64.
1,137✔
1542
        const maxFloat = 9000000000000000000
1,137✔
1543
        if dist > maxFloat {
1,137✔
1544
                return infinity
×
1545
        }
×
1546

1547
        return int64(dist)
1,137✔
1548
}
1549

1550
// lastHopPayloadSize calculates the payload size of the final hop in a route.
1551
// It depends on the tlv types which are present and also whether the hop is
1552
// part of a blinded route or not.
1553
func lastHopPayloadSize(r *RestrictParams, finalHtlcExpiry int32,
1554
        amount lnwire.MilliSatoshi) (uint64, error) {
181✔
1555

181✔
1556
        if r.BlindedPaymentPathSet != nil {
185✔
1557
                paymentPath, err := r.BlindedPaymentPathSet.
4✔
1558
                        LargestLastHopPayloadPath()
4✔
1559
                if err != nil {
4✔
1560
                        return 0, err
×
1561
                }
×
1562

1563
                blindedPath := paymentPath.BlindedPath.BlindedHops
4✔
1564
                blindedPoint := paymentPath.BlindedPath.BlindingPoint
4✔
1565

4✔
1566
                encryptedData := blindedPath[len(blindedPath)-1].CipherText
4✔
1567
                finalHop := route.Hop{
4✔
1568
                        AmtToForward:     amount,
4✔
1569
                        OutgoingTimeLock: uint32(finalHtlcExpiry),
4✔
1570
                        EncryptedData:    encryptedData,
4✔
1571
                }
4✔
1572
                if len(blindedPath) == 1 {
7✔
1573
                        finalHop.BlindingPoint = blindedPoint
3✔
1574
                }
3✔
1575

1576
                // The final hop does not have a short chanID set.
1577
                return finalHop.PayloadSize(0), nil
4✔
1578
        }
1579

1580
        var mpp *record.MPP
179✔
1581
        r.PaymentAddr.WhenSome(func(addr [32]byte) {
232✔
1582
                mpp = record.NewMPP(amount, addr)
53✔
1583
        })
53✔
1584

1585
        var amp *record.AMP
179✔
1586
        if r.Amp != nil {
182✔
1587
                // The AMP payload is not easy accessible at this point but we
3✔
1588
                // are only interested in the size of the payload so we just use
3✔
1589
                // the AMP record dummy.
3✔
1590
                amp = &record.MaxAmpPayLoadSize
3✔
1591
        }
3✔
1592

1593
        finalHop := route.Hop{
179✔
1594
                AmtToForward:     amount,
179✔
1595
                OutgoingTimeLock: uint32(finalHtlcExpiry),
179✔
1596
                CustomRecords:    r.DestCustomRecords,
179✔
1597
                MPP:              mpp,
179✔
1598
                AMP:              amp,
179✔
1599
                Metadata:         r.Metadata,
179✔
1600
        }
179✔
1601

179✔
1602
        // The final hop does not have a short chanID set.
179✔
1603
        return finalHop.PayloadSize(0), nil
179✔
1604
}
1605

1606
// overflowSafeAdd adds two MilliSatoshi values and returns the result. If an
1607
// overflow could occur, zero is returned instead and the boolean is set to
1608
// true.
1609
func overflowSafeAdd(x, y lnwire.MilliSatoshi) (lnwire.MilliSatoshi, bool) {
435✔
1610
        if y > math.MaxUint64-x {
435✔
1611
                // Overflow would occur, return 0 and set overflow flag.
×
1612
                return 0, true
×
1613
        }
×
1614

1615
        return x + y, false
435✔
1616
}
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