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

lightningnetwork / lnd / 13157733617

05 Feb 2025 12:49PM UTC coverage: 57.712% (-1.1%) from 58.82%
13157733617

Pull #9447

github

yyforyongyu
sweep: rename methods for clarity

We now rename "third party" to "unknown" as the inputs can be spent via
an older sweeping tx, a third party (anchor), or a remote party (pin).
In fee bumper we don't have the info to distinguish the above cases, and
leave them to be further handled by the sweeper as it has more context.
Pull Request #9447: sweep: start tracking input spending status in the fee bumper

83 of 87 new or added lines in 2 files covered. (95.4%)

19472 existing lines in 252 files now uncovered.

103634 of 179570 relevant lines covered (57.71%)

24840.31 hits per line

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

89.59
/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) {
93✔
142

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

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

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

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

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

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

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

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

216✔
192
                // If this is an edge from a blinded path and the
216✔
193
                // blindedPayment variable has not been set yet, then set it now
216✔
194
                // by extracting the corresponding blinded payment from the
216✔
195
                // edge.
216✔
196
                isBlindedEdge := pathEdges[i].blindedPayment != nil
216✔
197
                if isBlindedEdge && blindedPayment == nil {
217✔
198
                        blindedPayment = pathEdges[i].blindedPayment
1✔
199
                }
1✔
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 (
216✔
207
                        amtToForward        lnwire.MilliSatoshi
216✔
208
                        fee                 int64
216✔
209
                        totalAmtMsatBlinded lnwire.MilliSatoshi
216✔
210
                        outgoingTimeLock    uint32
216✔
211
                        customRecords       record.CustomSet
216✔
212
                        mpp                 *record.MPP
216✔
213
                        metadata            []byte
216✔
214
                )
216✔
215

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

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

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

93✔
240
                        if blindedPathSet == nil {
185✔
241
                                totalTimeLock += uint32(finalHop.cltvDelta)
92✔
242
                        } else {
93✔
243
                                totalTimeLock += uint32(
1✔
244
                                        blindedPathSet.FinalCLTVDelta(),
1✔
245
                                )
1✔
246
                        }
1✔
247
                        outgoingTimeLock = totalTimeLock
93✔
248

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

93✔
252
                        // If we're attaching a payment addr but the receiver
93✔
253
                        // doesn't support both TLV and payment addrs, fail.
93✔
254
                        payAddr := supports(lnwire.PaymentAddrOptional)
93✔
255
                        if !payAddr && finalHop.paymentAddr.IsSome() {
93✔
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) {
135✔
264
                                mpp = record.NewMPP(finalHop.totalAmt, addr)
42✔
265
                        })
42✔
266

267
                        metadata = finalHop.metadata
93✔
268

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

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

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

123✔
290
                        fee = int64(outboundFee) + inboundFee
123✔
291
                        if fee < 0 {
125✔
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
123✔
299
                        totalTimeLock += uint32(
123✔
300
                                pathEdges[i+1].policy.TimeLockDelta,
123✔
301
                        )
123✔
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{
216✔
308
                        PubKeyBytes:      edge.ToNodePubKey(),
216✔
309
                        ChannelID:        edge.ChannelID,
216✔
310
                        AmtToForward:     amtToForward,
216✔
311
                        OutgoingTimeLock: outgoingTimeLock,
216✔
312
                        CustomRecords:    customRecords,
216✔
313
                        MPP:              mpp,
216✔
314
                        Metadata:         metadata,
216✔
315
                        TotalAmtMsat:     totalAmtMsatBlinded,
216✔
316
                }
216✔
317

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

216✔
320
                // Finally, we update the amount that needs to flow into the
216✔
321
                // *next* hop, which is the amount this hop needs to forward,
216✔
322
                // accounting for the fee that it takes.
216✔
323
                nextIncomingAmount = amtToForward + lnwire.MilliSatoshi(fee)
216✔
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 {
94✔
330
                // If the passed in BlindedPaymentPathSet is non-nil but no
1✔
331
                // edge had a BlindedPayment attached, it means that the path
1✔
332
                // chosen was an introduction-node-only path. So in this case,
1✔
333
                // we can assume the relevant payment is the only one in the
1✔
334
                // payment set.
1✔
335
                if blindedPayment == nil {
1✔
UNCOV
336
                        var err error
×
UNCOV
337
                        blindedPayment, err = blindedPathSet.IntroNodeOnlyPath()
×
UNCOV
338
                        if err != nil {
×
339
                                return nil, err
×
340
                        }
×
341
                }
342

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

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

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

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

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

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

377
                        dataIndex++
3✔
378
                }
379
        }
380

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

390
        return newRoute, nil
93✔
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,135✔
401
        // timeLockPenalty is the penalty for the time lock delta of this channel.
1,135✔
402
        // It is controlled by RiskFactorBillionths and scales proportional
1,135✔
403
        // to the amount that will pass through channel. Rationale is that it if
1,135✔
404
        // a twice as large amount gets locked up, it is twice as bad.
1,135✔
405
        timeLockPenalty := int64(lockedAmt) * int64(timeLockDelta) *
1,135✔
406
                RiskFactorBillionths / 1000000000
1,135✔
407

1,135✔
408
        return int64(fee) + timeLockPenalty
1,135✔
409
}
1,135✔
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) {
180✔
514

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

521
                chanID := channel.ChannelID
445✔
522

445✔
523
                // Enforce outgoing channel restriction.
445✔
524
                if outgoingChans != nil {
465✔
525
                        if _, ok := outgoingChans[chanID]; !ok {
32✔
526
                                return nil
12✔
527
                        }
12✔
528
                }
529

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

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

541
                if bandwidth > max {
656✔
542
                        max = bandwidth
223✔
543
                }
223✔
544

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

556
                return nil
433✔
557
        }
558

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

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

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

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

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

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

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

2✔
628
                return nil, 0, errNoPaymentAddr
2✔
629
        }
2✔
630

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1,252✔
758
                edgesExpanded++
1,252✔
759

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

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

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

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

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

796
                if totalFee > 0 && lnwire.MilliSatoshi(totalFee) > r.FeeLimit {
1,256✔
797
                        return
4✔
798
                }
4✔
799

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

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

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

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

1,248✔
833
                if fromVertex != source {
2,352✔
834
                        outboundFee = int64(
1,104✔
835
                                edge.policy.ComputeFee(amountToSend),
1,104✔
836
                        )
1,104✔
837
                        timeLockDelta = edge.policy.TimeLockDelta
1,104✔
838
                }
1,104✔
839

840
                incomingCltv := toNodeDist.incomingCltv + int32(timeLockDelta)
1,248✔
841

1,248✔
842
                // Check that we are within our CLTV limit.
1,248✔
843
                if uint64(incomingCltv) > absoluteCltvLimit {
1,260✔
844
                        return
12✔
845
                }
12✔
846

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

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

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

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

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

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

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

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

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

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

×
942
                                return
×
943
                        }
×
944

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

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

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

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

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

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

176✔
987
        // getGraphFeatures returns (cached) node features from the graph.
176✔
988
        getGraphFeatures := func(node route.Vertex) (*lnwire.FeatureVector,
176✔
989
                error) {
1,432✔
990

1,256✔
991
                // Check cache for features of the fromNode.
1,256✔
992
                fromFeatures, ok := featureCache[node]
1,256✔
993
                if ok {
1,719✔
994
                        return fromFeatures, nil
463✔
995
                }
463✔
996

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

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

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

1019
                // Update cache.
1020
                featureCache[node] = fromFeatures
789✔
1021

789✔
1022
                return fromFeatures, nil
789✔
1023
        }
1024

1025
        routeToSelf := source == target
176✔
1026
        for {
872✔
1027
                nodesVisited++
696✔
1028

696✔
1029
                pivot := partialPath.node
696✔
1030
                isExitHop := partialPath.nextHop == nil
696✔
1031

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

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

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

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

1068
                netAmountReceived := partialPath.netAmountReceived
696✔
1069

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

1082
                        // Apply last hop restriction if set.
1083
                        if r.LastHop != nil &&
1,290✔
1084
                                pivot == target && fromNode != *r.LastHop {
1,294✔
1085

4✔
1086
                                continue
4✔
1087
                        }
1088

1089
                        edge := edgeUnifier.getEdge(
1,286✔
1090
                                netAmountReceived, g.bandwidthHints,
1,286✔
1091
                                partialPath.outboundFee,
1,286✔
1092
                        )
1,286✔
1093

1,286✔
1094
                        if edge == nil {
1,316✔
1095
                                continue
30✔
1096
                        }
1097

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

1104
                        // If there are no valid features, skip this node.
1105
                        if fromFeatures == nil {
1,260✔
1106
                                continue
4✔
1107
                        }
1108

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

1114
                if nodeHeap.Len() == 0 {
736✔
1115
                        break
40✔
1116
                }
1117

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

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

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

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

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

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

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

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

136✔
1174
        return pathEdges, distance[source].probability, nil
136✔
1175
}
1176

1177
// blindedPathRestrictions are a set of constraints to adhere to when
1178
// choosing a set of blinded paths to this node.
1179
type blindedPathRestrictions struct {
1180
        // minNumHops is the minimum number of hops to include in a blinded
1181
        // path. This doesn't include our node, so if the minimum is 1, then
1182
        // the path will contain at minimum our node along with an introduction
1183
        // node hop. A minimum of 0 will include paths where this node is the
1184
        // introduction node and so should be used with caution.
1185
        minNumHops uint8
1186

1187
        // maxNumHops is the maximum number of hops to include in a blinded
1188
        // path. This doesn't include our node, so if the maximum is 1, then
1189
        // the path will contain our node along with an introduction node hop.
1190
        maxNumHops uint8
1191

1192
        // nodeOmissionSet holds a set of node IDs of nodes that we should
1193
        // ignore during blinded path selection.
1194
        nodeOmissionSet fn.Set[route.Vertex]
1195
}
1196

1197
// blindedHop holds the information about a hop we have selected for a blinded
1198
// path.
1199
type blindedHop struct {
1200
        vertex       route.Vertex
1201
        channelID    uint64
1202
        edgeCapacity btcutil.Amount
1203
}
1204

1205
// findBlindedPaths does a depth first search from the target node to find a set
1206
// of blinded paths to the target node given the set of restrictions. This
1207
// function will select and return any candidate path. A candidate path is a
1208
// path to the target node with a size determined by the given hop number
1209
// constraints where all the nodes on the path signal the route blinding feature
1210
// _and_ the introduction node for the path has more than one public channel.
1211
// Any filtering of paths based on payment value or success probabilities is
1212
// left to the caller.
1213
func findBlindedPaths(g Graph, target route.Vertex,
1214
        restrictions *blindedPathRestrictions) ([][]blindedHop, error) {
12✔
1215

12✔
1216
        // Sanity check the restrictions.
12✔
1217
        if restrictions.minNumHops > restrictions.maxNumHops {
12✔
1218
                return nil, fmt.Errorf("maximum number of blinded path hops "+
×
1219
                        "(%d) must be greater than or equal to the minimum "+
×
1220
                        "number of hops (%d)", restrictions.maxNumHops,
×
1221
                        restrictions.minNumHops)
×
1222
        }
×
1223

1224
        // If the node is not the destination node, then it is required that the
1225
        // node advertise the route blinding feature-bit in order for it to be
1226
        // chosen as a node on the blinded path.
1227
        supportsRouteBlinding := func(node route.Vertex) (bool, error) {
85✔
1228
                if node == target {
85✔
1229
                        return true, nil
12✔
1230
                }
12✔
1231

1232
                features, err := g.FetchNodeFeatures(node)
61✔
1233
                if err != nil {
61✔
1234
                        return false, err
×
1235
                }
×
1236

1237
                return features.HasFeature(lnwire.RouteBlindingOptional), nil
61✔
1238
        }
1239

1240
        // This function will have some recursion. We will spin out from the
1241
        // target node & append edges to the paths until we reach various exit
1242
        // conditions such as: The maxHops number being reached or reaching
1243
        // a node that doesn't have any other edges - in that final case, the
1244
        // whole path should be ignored.
1245
        paths, _, err := processNodeForBlindedPath(
12✔
1246
                g, target, supportsRouteBlinding, nil, restrictions,
12✔
1247
        )
12✔
1248
        if err != nil {
12✔
1249
                return nil, err
×
1250
        }
×
1251

1252
        // Reverse each path so that the order is correct (from introduction
1253
        // node to last hop node) and then append this node on as the
1254
        // destination of each path.
1255
        orderedPaths := make([][]blindedHop, len(paths))
12✔
1256
        for i, path := range paths {
43✔
1257
                sort.Slice(path, func(i, j int) bool {
62✔
1258
                        return j < i
31✔
1259
                })
31✔
1260

1261
                orderedPaths[i] = append(path, blindedHop{vertex: target})
31✔
1262
        }
1263

1264
        // Handle the special case that allows a blinded path with the
1265
        // introduction node as the destination node.
1266
        if restrictions.minNumHops == 0 {
14✔
1267
                singleHopPath := [][]blindedHop{{{vertex: target}}}
2✔
1268

2✔
1269
                //nolint:makezero
2✔
1270
                orderedPaths = append(
2✔
1271
                        orderedPaths, singleHopPath...,
2✔
1272
                )
2✔
1273
        }
2✔
1274

1275
        return orderedPaths, err
12✔
1276
}
1277

1278
// processNodeForBlindedPath is a recursive function that traverses the graph
1279
// in a depth first manner searching for a set of blinded paths to the given
1280
// node.
1281
func processNodeForBlindedPath(g Graph, node route.Vertex,
1282
        supportsRouteBlinding func(vertex route.Vertex) (bool, error),
1283
        alreadyVisited map[route.Vertex]bool,
1284
        restrictions *blindedPathRestrictions) ([][]blindedHop, bool, error) {
198✔
1285

198✔
1286
        // If we have already visited the maximum number of hops, then this path
198✔
1287
        // is complete and we can exit now.
198✔
1288
        if len(alreadyVisited) > int(restrictions.maxNumHops) {
290✔
1289
                return nil, false, nil
92✔
1290
        }
92✔
1291

1292
        // If we have already visited this peer on this path, then we skip
1293
        // processing it again.
1294
        if alreadyVisited[node] {
136✔
1295
                return nil, false, nil
30✔
1296
        }
30✔
1297

1298
        // If we have explicitly been told to ignore this node for blinded paths
1299
        // then we skip it too.
1300
        if restrictions.nodeOmissionSet.Contains(node) {
79✔
1301
                return nil, false, nil
3✔
1302
        }
3✔
1303

1304
        supports, err := supportsRouteBlinding(node)
73✔
1305
        if err != nil {
73✔
1306
                return nil, false, err
×
1307
        }
×
1308
        if !supports {
79✔
1309
                return nil, false, nil
6✔
1310
        }
6✔
1311

1312
        // At this point, copy the alreadyVisited map.
1313
        visited := make(map[route.Vertex]bool, len(alreadyVisited))
67✔
1314
        for r := range alreadyVisited {
151✔
1315
                visited[r] = true
84✔
1316
        }
84✔
1317

1318
        // Add this node the visited set.
1319
        visited[node] = true
67✔
1320

67✔
1321
        var (
67✔
1322
                hopSets   [][]blindedHop
67✔
1323
                chanCount int
67✔
1324
        )
67✔
1325

67✔
1326
        // Now, iterate over the node's channels in search for paths to this
67✔
1327
        // node that can be used for blinded paths
67✔
1328
        err = g.ForEachNodeChannel(node,
67✔
1329
                func(channel *graphdb.DirectedChannel) error {
253✔
1330
                        // Keep track of how many incoming channels this node
186✔
1331
                        // has. We only use a node as an introduction node if it
186✔
1332
                        // has channels other than the one that lead us to it.
186✔
1333
                        chanCount++
186✔
1334

186✔
1335
                        // Process each channel peer to gather any paths that
186✔
1336
                        // lead to the peer.
186✔
1337
                        nextPaths, hasMoreChans, err := processNodeForBlindedPath( //nolint:ll
186✔
1338
                                g, channel.OtherNode, supportsRouteBlinding,
186✔
1339
                                visited, restrictions,
186✔
1340
                        )
186✔
1341
                        if err != nil {
186✔
1342
                                return err
×
1343
                        }
×
1344

1345
                        hop := blindedHop{
186✔
1346
                                vertex:       channel.OtherNode,
186✔
1347
                                channelID:    channel.ChannelID,
186✔
1348
                                edgeCapacity: channel.Capacity,
186✔
1349
                        }
186✔
1350

186✔
1351
                        // For each of the paths returned, unwrap them and
186✔
1352
                        // append this hop to them.
186✔
1353
                        for _, path := range nextPaths {
215✔
1354
                                hopSets = append(
29✔
1355
                                        hopSets,
29✔
1356
                                        append([]blindedHop{hop}, path...),
29✔
1357
                                )
29✔
1358
                        }
29✔
1359

1360
                        // If this node does have channels other than the one
1361
                        // that lead to it, and if the hop count up to this node
1362
                        // meets the minHop requirement, then we also add a
1363
                        // path that starts at this node.
1364
                        if hasMoreChans &&
186✔
1365
                                len(visited) >= int(restrictions.minNumHops) {
217✔
1366

31✔
1367
                                hopSets = append(hopSets, []blindedHop{hop})
31✔
1368
                        }
31✔
1369

1370
                        return nil
186✔
1371
                },
1372
        )
1373
        if err != nil {
67✔
1374
                return nil, false, err
×
1375
        }
×
1376

1377
        return hopSets, chanCount > 1, nil
67✔
1378
}
1379

1380
// getProbabilityBasedDist converts a weight into a distance that takes into
1381
// account the success probability and the (virtual) cost of a failed payment
1382
// attempt.
1383
//
1384
// Derivation:
1385
//
1386
// Suppose there are two routes A and B with fees Fa and Fb and success
1387
// probabilities Pa and Pb.
1388
//
1389
// Is the expected cost of trying route A first and then B lower than trying the
1390
// other way around?
1391
//
1392
// The expected cost of A-then-B is: Pa*Fa + (1-Pa)*Pb*(c+Fb)
1393
//
1394
// The expected cost of B-then-A is: Pb*Fb + (1-Pb)*Pa*(c+Fa)
1395
//
1396
// In these equations, the term representing the case where both A and B fail is
1397
// left out because its value would be the same in both cases.
1398
//
1399
// Pa*Fa + (1-Pa)*Pb*(c+Fb) < Pb*Fb + (1-Pb)*Pa*(c+Fa)
1400
//
1401
// Pa*Fa + Pb*c + Pb*Fb - Pa*Pb*c - Pa*Pb*Fb < Pb*Fb + Pa*c + Pa*Fa - Pa*Pb*c - Pa*Pb*Fa
1402
//
1403
// Removing terms that cancel out:
1404
// Pb*c - Pa*Pb*Fb < Pa*c - Pa*Pb*Fa
1405
//
1406
// Divide by Pa*Pb:
1407
// c/Pa - Fb < c/Pb - Fa
1408
//
1409
// Move terms around:
1410
// Fa + c/Pa < Fb + c/Pb
1411
//
1412
// So the value of F + c/P can be used to compare routes.
1413
func getProbabilityBasedDist(weight int64, probability float64,
1414
        penalty float64) int64 {
1,135✔
1415

1,135✔
1416
        // Prevent divide by zero by returning early.
1,135✔
1417
        if probability == 0 {
1,135✔
1418
                return infinity
×
1419
        }
×
1420

1421
        // Calculate distance.
1422
        dist := float64(weight) + penalty/probability
1,135✔
1423

1,135✔
1424
        // Avoid cast if an overflow would occur. The maxFloat constant is
1,135✔
1425
        // chosen to stay well below the maximum float64 value that is still
1,135✔
1426
        // convertible to int64.
1,135✔
1427
        const maxFloat = 9000000000000000000
1,135✔
1428
        if dist > maxFloat {
1,135✔
1429
                return infinity
×
1430
        }
×
1431

1432
        return int64(dist)
1,135✔
1433
}
1434

1435
// lastHopPayloadSize calculates the payload size of the final hop in a route.
1436
// It depends on the tlv types which are present and also whether the hop is
1437
// part of a blinded route or not.
1438
func lastHopPayloadSize(r *RestrictParams, finalHtlcExpiry int32,
1439
        amount lnwire.MilliSatoshi) (uint64, error) {
179✔
1440

179✔
1441
        if r.BlindedPaymentPathSet != nil {
181✔
1442
                paymentPath, err := r.BlindedPaymentPathSet.
2✔
1443
                        LargestLastHopPayloadPath()
2✔
1444
                if err != nil {
2✔
1445
                        return 0, err
×
1446
                }
×
1447

1448
                blindedPath := paymentPath.BlindedPath.BlindedHops
2✔
1449
                blindedPoint := paymentPath.BlindedPath.BlindingPoint
2✔
1450

2✔
1451
                encryptedData := blindedPath[len(blindedPath)-1].CipherText
2✔
1452
                finalHop := route.Hop{
2✔
1453
                        AmtToForward:     amount,
2✔
1454
                        OutgoingTimeLock: uint32(finalHtlcExpiry),
2✔
1455
                        EncryptedData:    encryptedData,
2✔
1456
                }
2✔
1457
                if len(blindedPath) == 1 {
3✔
1458
                        finalHop.BlindingPoint = blindedPoint
1✔
1459
                }
1✔
1460

1461
                // The final hop does not have a short chanID set.
1462
                return finalHop.PayloadSize(0), nil
2✔
1463
        }
1464

1465
        var mpp *record.MPP
177✔
1466
        r.PaymentAddr.WhenSome(func(addr [32]byte) {
228✔
1467
                mpp = record.NewMPP(amount, addr)
51✔
1468
        })
51✔
1469

1470
        var amp *record.AMP
177✔
1471
        if r.Amp != nil {
178✔
1472
                // The AMP payload is not easy accessible at this point but we
1✔
1473
                // are only interested in the size of the payload so we just use
1✔
1474
                // the AMP record dummy.
1✔
1475
                amp = &record.MaxAmpPayLoadSize
1✔
1476
        }
1✔
1477

1478
        finalHop := route.Hop{
177✔
1479
                AmtToForward:     amount,
177✔
1480
                OutgoingTimeLock: uint32(finalHtlcExpiry),
177✔
1481
                CustomRecords:    r.DestCustomRecords,
177✔
1482
                MPP:              mpp,
177✔
1483
                AMP:              amp,
177✔
1484
                Metadata:         r.Metadata,
177✔
1485
        }
177✔
1486

177✔
1487
        // The final hop does not have a short chanID set.
177✔
1488
        return finalHop.PayloadSize(0), nil
177✔
1489
}
1490

1491
// overflowSafeAdd adds two MilliSatoshi values and returns the result. If an
1492
// overflow could occur, zero is returned instead and the boolean is set to
1493
// true.
1494
func overflowSafeAdd(x, y lnwire.MilliSatoshi) (lnwire.MilliSatoshi, bool) {
433✔
1495
        if y > math.MaxUint64-x {
433✔
1496
                // Overflow would occur, return 0 and set overflow flag.
×
1497
                return 0, true
×
1498
        }
×
1499

1500
        return x + y, false
433✔
1501
}
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