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

lightningnetwork / lnd / 12165272839

04 Dec 2024 05:35PM UTC coverage: 58.978% (+0.2%) from 58.789%
12165272839

Pull #9316

github

ziggie1984
docs: add release-notes.
Pull Request #9316: routing: fix mc blinded path behaviour.

125 of 130 new or added lines in 4 files covered. (96.15%)

55 existing lines in 9 files now uncovered.

133554 of 226448 relevant lines covered (58.98%)

19570.84 hits per line

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

91.06
/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"
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) {
97✔
142

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

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

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

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

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

97✔
162
        // When paying to a blinded route we might have appened a dummy hop at
97✔
163
        // the end to make MPP payment possible via the whole path. Before
97✔
164
        // sending out the onion payload we make sure we remove this hop again.
97✔
165
        //
97✔
166
        // NOTE: The path length is always at least 1 because there must be one
97✔
167
        // edge from the source to the destination. However we check for > 0
97✔
168
        // just for robustness here.
97✔
169
        if blindedPathSet != nil && pathLength > 0 {
102✔
170
                finalBlindedPubKey := pathEdges[pathLength-1].policy.
5✔
171
                        ToNodePubKey()
5✔
172

5✔
173
                if isBlindedRouteNUMSTargetKey(finalBlindedPubKey[:]) {
9✔
174
                        // If the last hop is the NUMS key for blinded paths, we
4✔
175
                        // remove the dummy hop from the route.
4✔
176
                        pathEdges = pathEdges[:pathLength-1]
4✔
177
                        pathLength--
4✔
178
                }
4✔
179
        }
180

181
        for i := pathLength - 1; i >= 0; i-- {
317✔
182
                // Now we'll start to calculate the items within the per-hop
220✔
183
                // payload for the hop this edge is leading to.
220✔
184
                edge := pathEdges[i].policy
220✔
185

220✔
186
                // If this is an edge from a blinded path and the
220✔
187
                // blindedPayment variable has not been set yet, then set it now
220✔
188
                // by extracting the corresponding blinded payment from the
220✔
189
                // edge.
220✔
190
                isBlindedEdge := pathEdges[i].blindedPayment != nil
220✔
191
                if isBlindedEdge && blindedPayment == nil {
225✔
192
                        blindedPayment = pathEdges[i].blindedPayment
5✔
193
                }
5✔
194

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

220✔
210
                // Define a helper function that checks this edge's feature
220✔
211
                // vector for support for a given feature. We assume at this
220✔
212
                // point that the feature vectors transitive dependencies have
220✔
213
                // been validated.
220✔
214
                supports := func(feature lnwire.FeatureBit) bool {
317✔
215
                        // If this edge comes from router hints, the features
97✔
216
                        // could be nil.
97✔
217
                        if edge.ToNodeFeatures == nil {
97✔
218
                                return false
×
219
                        }
×
220
                        return edge.ToNodeFeatures.HasFeature(feature)
97✔
221
                }
222

223
                if i == len(pathEdges)-1 {
317✔
224
                        // If this is the last hop, then the hop payload will
97✔
225
                        // contain the exact amount. In BOLT #4: Onion Routing
97✔
226
                        // Protocol / "Payload for the Last Node", this is
97✔
227
                        // detailed.
97✔
228
                        amtToForward = finalHop.amt
97✔
229

97✔
230
                        // Fee is not part of the hop payload, but only used for
97✔
231
                        // reporting through RPC. Set to zero for the final hop.
97✔
232
                        fee = 0
97✔
233

97✔
234
                        if blindedPathSet == nil {
193✔
235
                                totalTimeLock += uint32(finalHop.cltvDelta)
96✔
236
                        } else {
101✔
237
                                totalTimeLock += uint32(
5✔
238
                                        blindedPathSet.FinalCLTVDelta(),
5✔
239
                                )
5✔
240
                        }
5✔
241
                        outgoingTimeLock = totalTimeLock
97✔
242

97✔
243
                        // Attach any custom records to the final hop.
97✔
244
                        customRecords = finalHop.records
97✔
245

97✔
246
                        // If we're attaching a payment addr but the receiver
97✔
247
                        // doesn't support both TLV and payment addrs, fail.
97✔
248
                        payAddr := supports(lnwire.PaymentAddrOptional)
97✔
249
                        if !payAddr && finalHop.paymentAddr.IsSome() {
97✔
250
                                return nil, errors.New("cannot attach " +
×
251
                                        "payment addr")
×
252
                        }
×
253

254
                        // Otherwise attach the mpp record if it exists.
255
                        // TODO(halseth): move this to payment life cycle,
256
                        // where AMP options are set.
257
                        finalHop.paymentAddr.WhenSome(func(addr [32]byte) {
143✔
258
                                mpp = record.NewMPP(finalHop.totalAmt, addr)
46✔
259
                        })
46✔
260

261
                        metadata = finalHop.metadata
97✔
262

97✔
263
                        if blindedPathSet != nil {
102✔
264
                                totalAmtMsatBlinded = finalHop.totalAmt
5✔
265
                        }
5✔
266
                } else {
127✔
267
                        // The amount that the current hop needs to forward is
127✔
268
                        // equal to the incoming amount of the next hop.
127✔
269
                        amtToForward = nextIncomingAmount
127✔
270

127✔
271
                        // The fee that needs to be paid to the current hop is
127✔
272
                        // based on the amount that this hop needs to forward
127✔
273
                        // and its policy for the outgoing channel. This policy
127✔
274
                        // is stored as part of the incoming channel of
127✔
275
                        // the next hop.
127✔
276
                        outboundFee := pathEdges[i+1].policy.ComputeFee(
127✔
277
                                amtToForward,
127✔
278
                        )
127✔
279

127✔
280
                        inboundFee := pathEdges[i].inboundFees.CalcFee(
127✔
281
                                amtToForward + outboundFee,
127✔
282
                        )
127✔
283

127✔
284
                        fee = int64(outboundFee) + inboundFee
127✔
285
                        if fee < 0 {
129✔
286
                                fee = 0
2✔
287
                        }
2✔
288

289
                        // We'll take the total timelock of the preceding hop as
290
                        // the outgoing timelock or this hop. Then we'll
291
                        // increment the total timelock incurred by this hop.
292
                        outgoingTimeLock = totalTimeLock
127✔
293
                        totalTimeLock += uint32(
127✔
294
                                pathEdges[i+1].policy.TimeLockDelta,
127✔
295
                        )
127✔
296
                }
297

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

220✔
312
                hops = append([]*route.Hop{currentHop}, hops...)
220✔
313

220✔
314
                // Finally, we update the amount that needs to flow into the
220✔
315
                // *next* hop, which is the amount this hop needs to forward,
220✔
316
                // accounting for the fee that it takes.
220✔
317
                nextIncomingAmount = amtToForward + lnwire.MilliSatoshi(fee)
220✔
318
        }
319

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

337
                var (
5✔
338
                        inBlindedRoute bool
5✔
339
                        dataIndex      = 0
5✔
340

5✔
341
                        blindedPath = blindedPayment.BlindedPath
5✔
342
                        introVertex = route.NewVertex(
5✔
343
                                blindedPath.IntroductionPoint,
5✔
344
                        )
5✔
345
                )
5✔
346

5✔
347
                for i, hop := range hops {
13✔
348
                        // Once we locate our introduction node, we know that
8✔
349
                        // every hop after this is part of the blinded route.
8✔
350
                        if bytes.Equal(hop.PubKeyBytes[:], introVertex[:]) {
13✔
351
                                inBlindedRoute = true
5✔
352
                                hop.BlindingPoint = blindedPath.BlindingPoint
5✔
353
                        }
5✔
354

355
                        // We don't need to modify edges outside of our blinded
356
                        // route.
357
                        if !inBlindedRoute {
13✔
358
                                continue
5✔
359
                        }
360

361
                        payload := blindedPath.BlindedHops[dataIndex].CipherText
7✔
362
                        hop.EncryptedData = payload
7✔
363

7✔
364
                        // All of the hops in a blinded route *except* the
7✔
365
                        // final hop should have zero amounts / time locks.
7✔
366
                        if i != len(hops)-1 {
13✔
367
                                hop.AmtToForward = 0
6✔
368
                                hop.OutgoingTimeLock = 0
6✔
369
                        }
6✔
370

371
                        dataIndex++
7✔
372
                }
373
        }
374

375
        // With the base routing data expressed as hops, build the full route
376
        newRoute, err := route.NewRouteFromHops(
97✔
377
                nextIncomingAmount, totalTimeLock, route.Vertex(sourceVertex),
97✔
378
                hops,
97✔
379
        )
97✔
380
        if err != nil {
97✔
381
                return nil, err
×
382
        }
×
383

384
        return newRoute, nil
97✔
385
}
386

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

1,139✔
402
        return int64(fee) + timeLockPenalty
1,139✔
403
}
1,139✔
404

405
// graphParams wraps the set of graph parameters passed to findPath.
406
type graphParams struct {
407
        // graph is the ChannelGraph to be used during path finding.
408
        graph Graph
409

410
        // additionalEdges is an optional set of edges that should be
411
        // considered during path finding, that is not already found in the
412
        // channel graph. These can either be private edges for bolt 11 invoices
413
        // or blinded edges when a payment to a blinded path is made.
414
        additionalEdges map[route.Vertex][]AdditionalEdge
415

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

426
// RestrictParams wraps the set of restrictions passed to findPath that the
427
// found path must adhere to.
428
type RestrictParams struct {
429
        // ProbabilitySource is a callback that is expected to return the
430
        // success probability of traversing the channel from the node.
431
        ProbabilitySource func(route.Vertex, route.Vertex,
432
                lnwire.MilliSatoshi, btcutil.Amount) float64
433

434
        // FeeLimit is a maximum fee amount allowed to be used on the path from
435
        // the source to the target.
436
        FeeLimit lnwire.MilliSatoshi
437

438
        // OutgoingChannelIDs is the list of channels that are allowed for the
439
        // first hop. If nil, any channel may be used.
440
        OutgoingChannelIDs []uint64
441

442
        // LastHop is the pubkey of the last node before the final destination
443
        // is reached. If nil, any node may be used.
444
        LastHop *route.Vertex
445

446
        // CltvLimit is the maximum time lock of the route excluding the final
447
        // ctlv. After path finding is complete, the caller needs to increase
448
        // all cltv expiry heights with the required final cltv delta.
449
        CltvLimit uint32
450

451
        // DestCustomRecords contains the custom records to drop off at the
452
        // final hop, if any.
453
        DestCustomRecords record.CustomSet
454

455
        // DestFeatures is a feature vector describing what the final hop
456
        // supports. If none are provided, pathfinding will try to inspect any
457
        // features on the node announcement instead.
458
        DestFeatures *lnwire.FeatureVector
459

460
        // PaymentAddr is a random 32-byte value generated by the receiver to
461
        // mitigate probing vectors and payment sniping attacks on overpaid
462
        // invoices.
463
        PaymentAddr fn.Option[[32]byte]
464

465
        // Amp signals to the pathfinder that this payment is an AMP payment
466
        // and therefore it needs to account for additional AMP data in the
467
        // final hop payload size calculation.
468
        Amp *AMPOptions
469

470
        // Metadata is additional data that is sent along with the payment to
471
        // the payee.
472
        Metadata []byte
473

474
        // BlindedPaymentPathSet is necessary to determine the hop size of the
475
        // last/exit hop.
476
        BlindedPaymentPathSet *BlindedPaymentPathSet
477

478
        // FirstHopCustomRecords includes any records that should be included in
479
        // the update_add_htlc message towards our peer.
480
        FirstHopCustomRecords lnwire.CustomRecords
481
}
482

483
// PathFindingConfig defines global parameters that control the trade-off in
484
// path finding between fees and probability.
485
type PathFindingConfig struct {
486
        // AttemptCost is the fixed virtual cost in path finding of a failed
487
        // payment attempt. It is used to trade off potentially better routes
488
        // against their probability of succeeding.
489
        AttemptCost lnwire.MilliSatoshi
490

491
        // AttemptCostPPM is the proportional virtual cost in path finding of a
492
        // failed payment attempt. It is used to trade off potentially better
493
        // routes against their probability of succeeding. This parameter is
494
        // expressed in parts per million of the total payment amount.
495
        AttemptCostPPM int64
496

497
        // MinProbability defines the minimum success probability of the
498
        // returned route.
499
        MinProbability float64
500
}
501

502
// getOutgoingBalance returns the maximum available balance in any of the
503
// channels of the given node. The second return parameters is the total
504
// available balance.
505
func getOutgoingBalance(node route.Vertex, outgoingChans map[uint64]struct{},
506
        bandwidthHints bandwidthHints,
507
        g Graph) (lnwire.MilliSatoshi, lnwire.MilliSatoshi, error) {
184✔
508

184✔
509
        var max, total lnwire.MilliSatoshi
184✔
510
        cb := func(channel *graphdb.DirectedChannel) error {
633✔
511
                if !channel.OutPolicySet {
449✔
512
                        return nil
×
513
                }
×
514

515
                chanID := channel.ChannelID
449✔
516

449✔
517
                // Enforce outgoing channel restriction.
449✔
518
                if outgoingChans != nil {
469✔
519
                        if _, ok := outgoingChans[chanID]; !ok {
32✔
520
                                return nil
12✔
521
                        }
12✔
522
                }
523

524
                bandwidth, ok := bandwidthHints.availableChanBandwidth(
437✔
525
                        chanID, 0,
437✔
526
                )
437✔
527

437✔
528
                // If the bandwidth is not available, use the channel capacity.
437✔
529
                // This can happen when a channel is added to the graph after
437✔
530
                // we've already queried the bandwidth hints.
437✔
531
                if !ok {
679✔
532
                        bandwidth = lnwire.NewMSatFromSatoshis(channel.Capacity)
242✔
533
                }
242✔
534

535
                if bandwidth > max {
676✔
536
                        max = bandwidth
239✔
537
                }
239✔
538

539
                var overflow bool
437✔
540
                total, overflow = overflowSafeAdd(total, bandwidth)
437✔
541
                if overflow {
437✔
542
                        // If the current total and the bandwidth would
×
543
                        // overflow the maximum value, we set the total to the
×
544
                        // maximum value. Which is more milli-satoshis than are
×
545
                        // in existence anyway, so the actual value is
×
546
                        // irrelevant.
×
547
                        total = lnwire.MilliSatoshi(math.MaxUint64)
×
548
                }
×
549

550
                return nil
437✔
551
        }
552

553
        // Iterate over all channels of the to node.
554
        err := g.ForEachNodeChannel(node, cb)
184✔
555
        if err != nil {
184✔
556
                return 0, 0, err
×
557
        }
×
558
        return max, total, err
184✔
559
}
560

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

190✔
577
        // Pathfinding can be a significant portion of the total payment
190✔
578
        // latency, especially on low-powered devices. Log several metrics to
190✔
579
        // aid in the analysis performance problems in this area.
190✔
580
        start := time.Now()
190✔
581
        nodesVisited := 0
190✔
582
        edgesExpanded := 0
190✔
583
        defer func() {
380✔
584
                timeElapsed := time.Since(start)
190✔
585
                log.Debugf("Pathfinding perf metrics: nodes=%v, edges=%v, "+
190✔
586
                        "time=%v", nodesVisited, edgesExpanded, timeElapsed)
190✔
587
        }()
190✔
588

589
        // If no destination features are provided, we will load what features
590
        // we have for the target node from our graph.
591
        features := r.DestFeatures
190✔
592
        if features == nil {
312✔
593
                var err error
122✔
594
                features, err = g.graph.FetchNodeFeatures(target)
122✔
595
                if err != nil {
122✔
596
                        return nil, 0, err
×
597
                }
×
598
        }
599

600
        // Ensure that the destination's features don't include unknown
601
        // required features.
602
        err := feature.ValidateRequired(features)
190✔
603
        if err != nil {
192✔
604
                log.Warnf("Pathfinding destination node features: %v", err)
2✔
605
                return nil, 0, errUnknownRequiredFeature
2✔
606
        }
2✔
607

608
        // Ensure that all transitive dependencies are set.
609
        err = feature.ValidateDeps(features)
188✔
610
        if err != nil {
190✔
611
                log.Warnf("Pathfinding destination node features: %v", err)
2✔
612
                return nil, 0, errMissingDependentFeature
2✔
613
        }
2✔
614

615
        // Now that we know the feature vector is well-formed, we'll proceed in
616
        // checking that it supports the features we need. If the caller has a
617
        // payment address to attach, check that our destination feature vector
618
        // supports them.
619
        if r.PaymentAddr.IsSome() &&
186✔
620
                !features.HasFeature(lnwire.PaymentAddrOptional) {
188✔
621

2✔
622
                return nil, 0, errNoPaymentAddr
2✔
623
        }
2✔
624

625
        // Set up outgoing channel map for quicker access.
626
        var outgoingChanMap map[uint64]struct{}
184✔
627
        if len(r.OutgoingChannelIDs) > 0 {
190✔
628
                outgoingChanMap = make(map[uint64]struct{})
6✔
629
                for _, outChan := range r.OutgoingChannelIDs {
14✔
630
                        outgoingChanMap[outChan] = struct{}{}
8✔
631
                }
8✔
632
        }
633

634
        // If we are routing from ourselves, check that we have enough local
635
        // balance available.
636
        if source == self {
368✔
637
                max, total, err := getOutgoingBalance(
184✔
638
                        self, outgoingChanMap, g.bandwidthHints, g.graph,
184✔
639
                )
184✔
640
                if err != nil {
184✔
641
                        return nil, 0, err
×
642
                }
×
643

644
                // If the total outgoing balance isn't sufficient, it will be
645
                // impossible to complete the payment.
646
                if total < amt {
191✔
647
                        log.Warnf("Not enough outbound balance to send "+
7✔
648
                                "htlc of amount: %v, only have local "+
7✔
649
                                "balance: %v", amt, total)
7✔
650

7✔
651
                        return nil, 0, errInsufficientBalance
7✔
652
                }
7✔
653

654
                // If there is only not enough capacity on a single route, it
655
                // may still be possible to complete the payment by splitting.
656
                if max < amt {
186✔
657
                        return nil, 0, errNoPathFound
5✔
658
                }
5✔
659
        }
660

661
        // First we'll initialize an empty heap which'll help us to quickly
662
        // locate the next edge we should visit next during our graph
663
        // traversal.
664
        nodeHeap := newDistanceHeap(estimatedNodeCount)
180✔
665

180✔
666
        // Holds the current best distance for a given node.
180✔
667
        distance := make(map[route.Vertex]*nodeWithDist, estimatedNodeCount)
180✔
668

180✔
669
        additionalEdgesWithSrc := make(map[route.Vertex][]*edgePolicyWithSource)
180✔
670
        for vertex, additionalEdges := range g.additionalEdges {
204✔
671
                // Edges connected to self are always included in the graph,
24✔
672
                // therefore can be skipped. This prevents us from trying
24✔
673
                // routes to malformed hop hints.
24✔
674
                if vertex == self {
32✔
675
                        continue
8✔
676
                }
677

678
                // Build reverse lookup to find incoming edges. Needed because
679
                // search is taken place from target to source.
680
                for _, additionalEdge := range additionalEdges {
40✔
681
                        outgoingEdgePolicy := additionalEdge.EdgePolicy()
20✔
682
                        toVertex := outgoingEdgePolicy.ToNodePubKey()
20✔
683

20✔
684
                        incomingEdgePolicy := &edgePolicyWithSource{
20✔
685
                                sourceNode: vertex,
20✔
686
                                edge:       additionalEdge,
20✔
687
                        }
20✔
688

20✔
689
                        additionalEdgesWithSrc[toVertex] =
20✔
690
                                append(additionalEdgesWithSrc[toVertex],
20✔
691
                                        incomingEdgePolicy)
20✔
692
                }
20✔
693
        }
694

695
        // The payload size of the final hop differ from intermediate hops
696
        // and depends on whether the destination is blinded or not.
697
        lastHopPayloadSize := lastHopPayloadSize(r, finalHtlcExpiry, amt)
180✔
698

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

180✔
718
        // Calculate the absolute cltv limit. Use uint64 to prevent an overflow
180✔
719
        // if the cltv limit is MaxUint32.
180✔
720
        absoluteCltvLimit := uint64(r.CltvLimit) + uint64(finalHtlcExpiry)
180✔
721

180✔
722
        // Calculate the default attempt cost as configured globally.
180✔
723
        defaultAttemptCost := float64(
180✔
724
                cfg.AttemptCost +
180✔
725
                        amt*lnwire.MilliSatoshi(cfg.AttemptCostPPM)/1000000,
180✔
726
        )
180✔
727

180✔
728
        // Validate time preference value.
180✔
729
        if math.Abs(timePref) > 1 {
180✔
730
                return nil, 0, fmt.Errorf("time preference %v out of range "+
×
731
                        "[-1, 1]", timePref)
×
732
        }
×
733

734
        // Scale to avoid the extremes -1 and 1 which run into infinity issues.
735
        timePref *= 0.9
180✔
736

180✔
737
        // Apply time preference. At 0, the default attempt cost will
180✔
738
        // be used.
180✔
739
        absoluteAttemptCost := defaultAttemptCost * (1/(0.5-timePref/2) - 1)
180✔
740

180✔
741
        log.Debugf("Pathfinding absolute attempt cost: %v sats",
180✔
742
                absoluteAttemptCost/1000)
180✔
743

180✔
744
        // processEdge is a helper closure that will be used to make sure edges
180✔
745
        // satisfy our specific requirements.
180✔
746
        processEdge := func(fromVertex route.Vertex,
180✔
747
                edge *unifiedEdge, toNodeDist *nodeWithDist) {
1,436✔
748

1,256✔
749
                edgesExpanded++
1,256✔
750

1,256✔
751
                // Calculate inbound fee charged by "to" node. The exit hop
1,256✔
752
                // doesn't charge inbound fees. If the "to" node is the exit
1,256✔
753
                // hop, its inbound fees have already been set to zero by
1,256✔
754
                // nodeEdgeUnifier.
1,256✔
755
                inboundFee := edge.inboundFees.CalcFee(
1,256✔
756
                        toNodeDist.netAmountReceived,
1,256✔
757
                )
1,256✔
758

1,256✔
759
                // Make sure that the node total fee is never negative.
1,256✔
760
                // Routing nodes treat a total fee that turns out
1,256✔
761
                // negative as a zero fee and pathfinding should do the
1,256✔
762
                // same.
1,256✔
763
                minInboundFee := -int64(toNodeDist.outboundFee)
1,256✔
764
                if inboundFee < minInboundFee {
1,258✔
765
                        inboundFee = minInboundFee
2✔
766
                }
2✔
767

768
                // Calculate amount that the candidate node would have to send
769
                // out.
770
                amountToSend := toNodeDist.netAmountReceived +
1,256✔
771
                        lnwire.MilliSatoshi(inboundFee)
1,256✔
772

1,256✔
773
                // Check if accumulated fees would exceed fee limit when this
1,256✔
774
                // node would be added to the path.
1,256✔
775
                totalFee := int64(amountToSend) - int64(amt)
1,256✔
776

1,256✔
777
                log.Trace(lnutils.NewLogClosure(func() string {
1,256✔
778
                        return fmt.Sprintf(
×
779
                                "Checking fromVertex (%v) with "+
×
780
                                        "minInboundFee=%v, inboundFee=%v, "+
×
781
                                        "amountToSend=%v, amt=%v, totalFee=%v",
×
782
                                fromVertex, minInboundFee, inboundFee,
×
783
                                amountToSend, amt, totalFee,
×
784
                        )
×
785
                }))
×
786

787
                if totalFee > 0 && lnwire.MilliSatoshi(totalFee) > r.FeeLimit {
1,260✔
788
                        return
4✔
789
                }
4✔
790

791
                // Request the success probability for this edge.
792
                edgeProbability := r.ProbabilitySource(
1,252✔
793
                        fromVertex, toNodeDist.node, amountToSend,
1,252✔
794
                        edge.capacity,
1,252✔
795
                )
1,252✔
796

1,252✔
797
                log.Trace(lnutils.NewLogClosure(func() string {
1,252✔
798
                        return fmt.Sprintf("path finding probability: fromnode=%v,"+
×
799
                                " tonode=%v, amt=%v, cap=%v, probability=%v",
×
800
                                fromVertex, toNodeDist.node, amountToSend,
×
801
                                edge.capacity, edgeProbability)
×
802
                }))
×
803

804
                // If the probability is zero, there is no point in trying.
805
                if edgeProbability == 0 {
1,252✔
806
                        return
×
807
                }
×
808

809
                // Compute fee that fromVertex is charging. It is based on the
810
                // amount that needs to be sent to the next node in the route.
811
                //
812
                // Source node has no predecessor to pay a fee. Therefore set
813
                // fee to zero, because it should not be included in the fee
814
                // limit check and edge weight.
815
                //
816
                // Also determine the time lock delta that will be added to the
817
                // route if fromVertex is selected. If fromVertex is the source
818
                // node, no additional timelock is required.
819
                var (
1,252✔
820
                        timeLockDelta uint16
1,252✔
821
                        outboundFee   int64
1,252✔
822
                )
1,252✔
823

1,252✔
824
                if fromVertex != source {
2,360✔
825
                        outboundFee = int64(
1,108✔
826
                                edge.policy.ComputeFee(amountToSend),
1,108✔
827
                        )
1,108✔
828
                        timeLockDelta = edge.policy.TimeLockDelta
1,108✔
829
                }
1,108✔
830

831
                incomingCltv := toNodeDist.incomingCltv + int32(timeLockDelta)
1,252✔
832

1,252✔
833
                // Check that we are within our CLTV limit.
1,252✔
834
                if uint64(incomingCltv) > absoluteCltvLimit {
1,264✔
835
                        return
12✔
836
                }
12✔
837

838
                // netAmountToReceive is the amount that the node that is added
839
                // to the distance map needs to receive from a (to be found)
840
                // previous node in the route. The inbound fee of the receiving
841
                // node is already subtracted from this value. The previous node
842
                // will need to pay the amount that this node forwards plus the
843
                // fee it charges plus this node's inbound fee.
844
                netAmountToReceive := amountToSend +
1,240✔
845
                        lnwire.MilliSatoshi(outboundFee)
1,240✔
846

1,240✔
847
                // Calculate total probability of successfully reaching target
1,240✔
848
                // by multiplying the probabilities. Both this edge and the rest
1,240✔
849
                // of the route must succeed.
1,240✔
850
                probability := toNodeDist.probability * edgeProbability
1,240✔
851

1,240✔
852
                // If the probability is below the specified lower bound, we can
1,240✔
853
                // abandon this direction. Adding further nodes can only lower
1,240✔
854
                // the probability more.
1,240✔
855
                if probability < cfg.MinProbability {
1,345✔
856
                        return
105✔
857
                }
105✔
858

859
                // Calculate the combined fee for this edge. Dijkstra does not
860
                // support negative edge weights. Because this fee feeds into
861
                // the edge weight calculation, we don't allow it to be
862
                // negative.
863
                signedFee := inboundFee + outboundFee
1,139✔
864
                fee := lnwire.MilliSatoshi(0)
1,139✔
865
                if signedFee > 0 {
1,701✔
866
                        fee = lnwire.MilliSatoshi(signedFee)
562✔
867
                }
562✔
868

869
                // By adding fromVertex in the route, there will be an extra
870
                // weight composed of the fee that this node will charge and
871
                // the amount that will be locked for timeLockDelta blocks in
872
                // the HTLC that is handed out to fromVertex.
873
                weight := edgeWeight(amountToSend, fee, timeLockDelta)
1,139✔
874

1,139✔
875
                // Compute the tentative weight to this new channel/edge
1,139✔
876
                // which is the weight from our toNode to the target node
1,139✔
877
                // plus the weight of this edge.
1,139✔
878
                tempWeight := toNodeDist.weight + weight
1,139✔
879

1,139✔
880
                // Add an extra factor to the weight to take into account the
1,139✔
881
                // probability. Another reason why we rounded the fee up to zero
1,139✔
882
                // is to prevent a highly negative fee from cancelling out the
1,139✔
883
                // extra factor. We don't want an always-failing node to attract
1,139✔
884
                // traffic using a highly negative fee and escape penalization.
1,139✔
885
                tempDist := getProbabilityBasedDist(
1,139✔
886
                        tempWeight, probability,
1,139✔
887
                        absoluteAttemptCost,
1,139✔
888
                )
1,139✔
889

1,139✔
890
                // If there is already a best route stored, compare this
1,139✔
891
                // candidate route with the best route so far.
1,139✔
892
                current, ok := distance[fromVertex]
1,139✔
893
                if ok {
1,538✔
894
                        // If this route is worse than what we already found,
399✔
895
                        // skip this route.
399✔
896
                        if tempDist > current.dist {
708✔
897
                                return
309✔
898
                        }
309✔
899

900
                        // If the route is equally good and the probability
901
                        // isn't better, skip this route. It is important to
902
                        // also return if both cost and probability are equal,
903
                        // because otherwise the algorithm could run into an
904
                        // endless loop.
905
                        probNotBetter := probability <= current.probability
94✔
906
                        if tempDist == current.dist && probNotBetter {
157✔
907
                                return
63✔
908
                        }
63✔
909
                }
910

911
                // Calculate the total routing info size if this hop were to be
912
                // included. If we are coming from the source hop, the payload
913
                // size is zero, because the original htlc isn't in the onion
914
                // blob.
915
                var payloadSize uint64
775✔
916
                ToPubKey := edge.policy.ToNodePubKey()
775✔
917

775✔
918
                // If the edge points to the NUMS key we don't need to consider
775✔
919
                // the payload size again because we already added the size of
775✔
920
                // the dummy hop which will be removed after the route is found.
775✔
921
                // The dummy target hop has the same cipher text length as the
775✔
922
                // real last blinded target hop.
775✔
923
                if fromVertex != source &&
775✔
924
                        !isBlindedRouteNUMSTargetKey(ToPubKey[:]) {
1,408✔
925
                        // In case the unifiedEdge does not have a payload size
633✔
926
                        // function supplied we request a graceful shutdown
633✔
927
                        // because this should never happen.
633✔
928
                        if edge.hopPayloadSizeFn == nil {
633✔
NEW
929
                                log.Criticalf("No payload size function "+
×
NEW
930
                                        "available for edge=%v unable to "+
×
931
                                        "determine payload size: %v", edge,
×
932
                                        ErrNoPayLoadSizeFunc)
×
933

×
934
                                return
×
935
                        }
×
936

937
                        payloadSize = edge.hopPayloadSizeFn(
633✔
938
                                amountToSend,
633✔
939
                                uint32(toNodeDist.incomingCltv),
633✔
940
                                edge.policy.ChannelID,
633✔
941
                        )
633✔
942
                }
943

944
                routingInfoSize := toNodeDist.routingInfoSize + payloadSize
775✔
945
                // Skip paths that would exceed the maximum routing info size.
775✔
946
                if routingInfoSize > sphinx.MaxPayloadSize {
781✔
947
                        return
6✔
948
                }
6✔
949

950
                // All conditions are met and this new tentative distance is
951
                // better than the current best known distance to this node.
952
                // The new better distance is recorded, and also our "next hop"
953
                // map is populated with this edge.
954
                withDist := &nodeWithDist{
769✔
955
                        dist:              tempDist,
769✔
956
                        weight:            tempWeight,
769✔
957
                        node:              fromVertex,
769✔
958
                        netAmountReceived: netAmountToReceive,
769✔
959
                        outboundFee:       lnwire.MilliSatoshi(outboundFee),
769✔
960
                        incomingCltv:      incomingCltv,
769✔
961
                        probability:       probability,
769✔
962
                        nextHop:           edge,
769✔
963
                        routingInfoSize:   routingInfoSize,
769✔
964
                }
769✔
965
                distance[fromVertex] = withDist
769✔
966

769✔
967
                // Either push withDist onto the heap if the node
769✔
968
                // represented by fromVertex is not already on the heap OR adjust
769✔
969
                // its position within the heap via heap.Fix.
769✔
970
                nodeHeap.PushOrFix(withDist)
769✔
971
        }
972

973
        // TODO(roasbeef): also add path caching
974
        //  * similar to route caching, but doesn't factor in the amount
975

976
        // Cache features because we visit nodes multiple times.
977
        featureCache := make(map[route.Vertex]*lnwire.FeatureVector)
180✔
978

180✔
979
        // getGraphFeatures returns (cached) node features from the graph.
180✔
980
        getGraphFeatures := func(node route.Vertex) (*lnwire.FeatureVector,
180✔
981
                error) {
1,440✔
982

1,260✔
983
                // Check cache for features of the fromNode.
1,260✔
984
                fromFeatures, ok := featureCache[node]
1,260✔
985
                if ok {
1,727✔
986
                        return fromFeatures, nil
467✔
987
                }
467✔
988

989
                // Fetch node features fresh from the graph.
990
                fromFeatures, err := g.graph.FetchNodeFeatures(node)
797✔
991
                if err != nil {
797✔
992
                        return nil, err
×
993
                }
×
994

995
                // Don't route through nodes that contain unknown required
996
                // features and mark as nil in the cache.
997
                err = feature.ValidateRequired(fromFeatures)
797✔
998
                if err != nil {
799✔
999
                        featureCache[node] = nil
2✔
1000
                        return nil, nil
2✔
1001
                }
2✔
1002

1003
                // Don't route through nodes that don't properly set all
1004
                // transitive feature dependencies and mark as nil in the cache.
1005
                err = feature.ValidateDeps(fromFeatures)
795✔
1006
                if err != nil {
797✔
1007
                        featureCache[node] = nil
2✔
1008
                        return nil, nil
2✔
1009
                }
2✔
1010

1011
                // Update cache.
1012
                featureCache[node] = fromFeatures
793✔
1013

793✔
1014
                return fromFeatures, nil
793✔
1015
        }
1016

1017
        routeToSelf := source == target
180✔
1018
        for {
879✔
1019
                nodesVisited++
699✔
1020

699✔
1021
                pivot := partialPath.node
699✔
1022
                isExitHop := partialPath.nextHop == nil
699✔
1023

699✔
1024
                // Create unified policies for all incoming connections. Don't
699✔
1025
                // use inbound fees for the exit hop.
699✔
1026
                u := newNodeEdgeUnifier(
699✔
1027
                        self, pivot, !isExitHop, outgoingChanMap,
699✔
1028
                )
699✔
1029

699✔
1030
                err := u.addGraphPolicies(g.graph)
699✔
1031
                if err != nil {
699✔
1032
                        return nil, 0, err
×
1033
                }
×
1034

1035
                // We add hop hints that were supplied externally.
1036
                for _, reverseEdge := range additionalEdgesWithSrc[pivot] {
719✔
1037
                        // Assume zero inbound fees for route hints. If inbound
20✔
1038
                        // fees would apply, they couldn't be communicated in
20✔
1039
                        // bolt11 invoices currently.
20✔
1040
                        inboundFee := models.InboundFee{}
20✔
1041

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

1060
                netAmountReceived := partialPath.netAmountReceived
699✔
1061

699✔
1062
                // Expand all connections using the optimal policy for each
699✔
1063
                // connection.
699✔
1064
                for fromNode, edgeUnifier := range u.edgeUnifiers {
2,322✔
1065
                        // The target node is not recorded in the distance map.
1,623✔
1066
                        // Therefore we need to have this check to prevent
1,623✔
1067
                        // creating a cycle. Only when we intend to route to
1,623✔
1068
                        // self, we allow this cycle to form. In that case we'll
1,623✔
1069
                        // also break out of the search loop below.
1,623✔
1070
                        if !routeToSelf && fromNode == target {
1,956✔
1071
                                continue
333✔
1072
                        }
1073

1074
                        // Apply last hop restriction if set.
1075
                        if r.LastHop != nil &&
1,294✔
1076
                                pivot == target && fromNode != *r.LastHop {
1,298✔
1077

4✔
1078
                                continue
4✔
1079
                        }
1080

1081
                        edge := edgeUnifier.getEdge(
1,290✔
1082
                                netAmountReceived, g.bandwidthHints,
1,290✔
1083
                                partialPath.outboundFee,
1,290✔
1084
                        )
1,290✔
1085

1,290✔
1086
                        if edge == nil {
1,324✔
1087
                                continue
34✔
1088
                        }
1089

1090
                        // Get feature vector for fromNode.
1091
                        fromFeatures, err := getGraphFeatures(fromNode)
1,260✔
1092
                        if err != nil {
1,260✔
1093
                                return nil, 0, err
×
1094
                        }
×
1095

1096
                        // If there are no valid features, skip this node.
1097
                        if fromFeatures == nil {
1,264✔
1098
                                continue
4✔
1099
                        }
1100

1101
                        // Check if this candidate node is better than what we
1102
                        // already have.
1103
                        processEdge(fromNode, edge, partialPath)
1,256✔
1104
                }
1105

1106
                if nodeHeap.Len() == 0 {
743✔
1107
                        break
44✔
1108
                }
1109

1110
                // Fetch the node within the smallest distance from our source
1111
                // from the heap.
1112
                partialPath = heap.Pop(&nodeHeap).(*nodeWithDist)
659✔
1113

659✔
1114
                // If we've reached our source (or we don't have any incoming
659✔
1115
                // edges), then we're done here and can exit the graph
659✔
1116
                // traversal early.
659✔
1117
                if partialPath.node == source {
799✔
1118
                        break
140✔
1119
                }
1120
        }
1121

1122
        // Use the distance map to unravel the forward path from source to
1123
        // target.
1124
        var pathEdges []*unifiedEdge
180✔
1125
        currentNode := source
180✔
1126
        for {
564✔
1127
                // Determine the next hop forward using the next map.
384✔
1128
                currentNodeWithDist, ok := distance[currentNode]
384✔
1129
                if !ok {
428✔
1130
                        // If the node doesn't have a next hop it means we
44✔
1131
                        // didn't find a path.
44✔
1132
                        return nil, 0, errNoPathFound
44✔
1133
                }
44✔
1134

1135
                // Add the next hop to the list of path edges.
1136
                pathEdges = append(pathEdges, currentNodeWithDist.nextHop)
344✔
1137

344✔
1138
                // Advance current node.
344✔
1139
                currentNode = currentNodeWithDist.nextHop.policy.ToNodePubKey()
344✔
1140

344✔
1141
                // Check stop condition at the end of this loop. This prevents
344✔
1142
                // breaking out too soon for self-payments that have target set
344✔
1143
                // to source.
344✔
1144
                if currentNode == target {
484✔
1145
                        break
140✔
1146
                }
1147
        }
1148

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

140✔
1162
        log.Debugf("Found route: probability=%v, hops=%v, fee=%v",
140✔
1163
                distance[source].probability, len(pathEdges),
140✔
1164
                distance[source].netAmountReceived-amt)
140✔
1165

140✔
1166
        return pathEdges, distance[source].probability, nil
140✔
1167
}
1168

1169
// blindedPathRestrictions are a set of constraints to adhere to when
1170
// choosing a set of blinded paths to this node.
1171
type blindedPathRestrictions struct {
1172
        // minNumHops is the minimum number of hops to include in a blinded
1173
        // path. This doesn't include our node, so if the minimum is 1, then
1174
        // the path will contain at minimum our node along with an introduction
1175
        // node hop. A minimum of 0 will include paths where this node is the
1176
        // introduction node and so should be used with caution.
1177
        minNumHops uint8
1178

1179
        // maxNumHops is the maximum number of hops to include in a blinded
1180
        // path. This doesn't include our node, so if the maximum is 1, then
1181
        // the path will contain our node along with an introduction node hop.
1182
        maxNumHops uint8
1183

1184
        // nodeOmissionSet holds a set of node IDs of nodes that we should
1185
        // ignore during blinded path selection.
1186
        nodeOmissionSet fn.Set[route.Vertex]
1187
}
1188

1189
// blindedHop holds the information about a hop we have selected for a blinded
1190
// path.
1191
type blindedHop struct {
1192
        vertex       route.Vertex
1193
        channelID    uint64
1194
        edgeCapacity btcutil.Amount
1195
}
1196

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

16✔
1208
        // Sanity check the restrictions.
16✔
1209
        if restrictions.minNumHops > restrictions.maxNumHops {
16✔
1210
                return nil, fmt.Errorf("maximum number of blinded path hops "+
×
1211
                        "(%d) must be greater than or equal to the minimum "+
×
1212
                        "number of hops (%d)", restrictions.maxNumHops,
×
1213
                        restrictions.minNumHops)
×
1214
        }
×
1215

1216
        // If the node is not the destination node, then it is required that the
1217
        // node advertise the route blinding feature-bit in order for it to be
1218
        // chosen as a node on the blinded path.
1219
        supportsRouteBlinding := func(node route.Vertex) (bool, error) {
93✔
1220
                if node == target {
93✔
1221
                        return true, nil
16✔
1222
                }
16✔
1223

1224
                features, err := g.FetchNodeFeatures(node)
65✔
1225
                if err != nil {
65✔
1226
                        return false, err
×
1227
                }
×
1228

1229
                return features.HasFeature(lnwire.RouteBlindingOptional), nil
65✔
1230
        }
1231

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

1244
        // Reverse each path so that the order is correct (from introduction
1245
        // node to last hop node) and then append this node on as the
1246
        // destination of each path.
1247
        orderedPaths := make([][]blindedHop, len(paths))
16✔
1248
        for i, path := range paths {
51✔
1249
                sort.Slice(path, func(i, j int) bool {
70✔
1250
                        return j < i
35✔
1251
                })
35✔
1252

1253
                orderedPaths[i] = append(path, blindedHop{vertex: target})
35✔
1254
        }
1255

1256
        // Handle the special case that allows a blinded path with the
1257
        // introduction node as the destination node.
1258
        if restrictions.minNumHops == 0 {
22✔
1259
                singleHopPath := [][]blindedHop{{{vertex: target}}}
6✔
1260

6✔
1261
                //nolint:makezero
6✔
1262
                orderedPaths = append(
6✔
1263
                        orderedPaths, singleHopPath...,
6✔
1264
                )
6✔
1265
        }
6✔
1266

1267
        return orderedPaths, err
16✔
1268
}
1269

1270
// processNodeForBlindedPath is a recursive function that traverses the graph
1271
// in a depth first manner searching for a set of blinded paths to the given
1272
// node.
1273
func processNodeForBlindedPath(g Graph, node route.Vertex,
1274
        supportsRouteBlinding func(vertex route.Vertex) (bool, error),
1275
        alreadyVisited map[route.Vertex]bool,
1276
        restrictions *blindedPathRestrictions) ([][]blindedHop, bool, error) {
202✔
1277

202✔
1278
        // If we have already visited the maximum number of hops, then this path
202✔
1279
        // is complete and we can exit now.
202✔
1280
        if len(alreadyVisited) > int(restrictions.maxNumHops) {
298✔
1281
                return nil, false, nil
96✔
1282
        }
96✔
1283

1284
        // If we have already visited this peer on this path, then we skip
1285
        // processing it again.
1286
        if alreadyVisited[node] {
144✔
1287
                return nil, false, nil
34✔
1288
        }
34✔
1289

1290
        // If we have explicitly been told to ignore this node for blinded paths
1291
        // then we skip it too.
1292
        if restrictions.nodeOmissionSet.Contains(node) {
83✔
1293
                return nil, false, nil
3✔
1294
        }
3✔
1295

1296
        supports, err := supportsRouteBlinding(node)
77✔
1297
        if err != nil {
77✔
1298
                return nil, false, err
×
1299
        }
×
1300
        if !supports {
87✔
1301
                return nil, false, nil
10✔
1302
        }
10✔
1303

1304
        // At this point, copy the alreadyVisited map.
1305
        visited := make(map[route.Vertex]bool, len(alreadyVisited))
71✔
1306
        for r := range alreadyVisited {
159✔
1307
                visited[r] = true
88✔
1308
        }
88✔
1309

1310
        // Add this node the visited set.
1311
        visited[node] = true
71✔
1312

71✔
1313
        var (
71✔
1314
                hopSets   [][]blindedHop
71✔
1315
                chanCount int
71✔
1316
        )
71✔
1317

71✔
1318
        // Now, iterate over the node's channels in search for paths to this
71✔
1319
        // node that can be used for blinded paths
71✔
1320
        err = g.ForEachNodeChannel(node,
71✔
1321
                func(channel *graphdb.DirectedChannel) error {
261✔
1322
                        // Keep track of how many incoming channels this node
190✔
1323
                        // has. We only use a node as an introduction node if it
190✔
1324
                        // has channels other than the one that lead us to it.
190✔
1325
                        chanCount++
190✔
1326

190✔
1327
                        // Process each channel peer to gather any paths that
190✔
1328
                        // lead to the peer.
190✔
1329
                        nextPaths, hasMoreChans, err := processNodeForBlindedPath( //nolint:ll
190✔
1330
                                g, channel.OtherNode, supportsRouteBlinding,
190✔
1331
                                visited, restrictions,
190✔
1332
                        )
190✔
1333
                        if err != nil {
190✔
1334
                                return err
×
1335
                        }
×
1336

1337
                        hop := blindedHop{
190✔
1338
                                vertex:       channel.OtherNode,
190✔
1339
                                channelID:    channel.ChannelID,
190✔
1340
                                edgeCapacity: channel.Capacity,
190✔
1341
                        }
190✔
1342

190✔
1343
                        // For each of the paths returned, unwrap them and
190✔
1344
                        // append this hop to them.
190✔
1345
                        for _, path := range nextPaths {
223✔
1346
                                hopSets = append(
33✔
1347
                                        hopSets,
33✔
1348
                                        append([]blindedHop{hop}, path...),
33✔
1349
                                )
33✔
1350
                        }
33✔
1351

1352
                        // If this node does have channels other than the one
1353
                        // that lead to it, and if the hop count up to this node
1354
                        // meets the minHop requirement, then we also add a
1355
                        // path that starts at this node.
1356
                        if hasMoreChans &&
190✔
1357
                                len(visited) >= int(restrictions.minNumHops) {
225✔
1358

35✔
1359
                                hopSets = append(hopSets, []blindedHop{hop})
35✔
1360
                        }
35✔
1361

1362
                        return nil
190✔
1363
                },
1364
        )
1365
        if err != nil {
71✔
1366
                return nil, false, err
×
1367
        }
×
1368

1369
        return hopSets, chanCount > 1, nil
71✔
1370
}
1371

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

1,139✔
1408
        // Prevent divide by zero by returning early.
1,139✔
1409
        if probability == 0 {
1,139✔
1410
                return infinity
×
1411
        }
×
1412

1413
        // Calculate distance.
1414
        dist := float64(weight) + penalty/probability
1,139✔
1415

1,139✔
1416
        // Avoid cast if an overflow would occur. The maxFloat constant is
1,139✔
1417
        // chosen to stay well below the maximum float64 value that is still
1,139✔
1418
        // convertible to int64.
1,139✔
1419
        const maxFloat = 9000000000000000000
1,139✔
1420
        if dist > maxFloat {
1,139✔
1421
                return infinity
×
1422
        }
×
1423

1424
        return int64(dist)
1,139✔
1425
}
1426

1427
// lastHopPayloadSize calculates the payload size of the final hop in a route.
1428
// It depends on the tlv types which are present and also whether the hop is
1429
// part of a blinded route or not.
1430
func lastHopPayloadSize(r *RestrictParams, finalHtlcExpiry int32,
1431
        amount lnwire.MilliSatoshi) uint64 {
183✔
1432

183✔
1433
        if r.BlindedPaymentPathSet != nil {
189✔
1434
                paymentPath := r.BlindedPaymentPathSet.
6✔
1435
                        LargestLastHopPayloadPath()
6✔
1436
                blindedPath := paymentPath.BlindedPath.BlindedHops
6✔
1437
                blindedPoint := paymentPath.BlindedPath.BlindingPoint
6✔
1438

6✔
1439
                encryptedData := blindedPath[len(blindedPath)-1].CipherText
6✔
1440
                finalHop := route.Hop{
6✔
1441
                        AmtToForward:     amount,
6✔
1442
                        OutgoingTimeLock: uint32(finalHtlcExpiry),
6✔
1443
                        EncryptedData:    encryptedData,
6✔
1444
                }
6✔
1445
                if len(blindedPath) == 1 {
11✔
1446
                        finalHop.BlindingPoint = blindedPoint
5✔
1447
                }
5✔
1448

1449
                // The final hop does not have a short chanID set.
1450
                return finalHop.PayloadSize(0)
6✔
1451
        }
1452

1453
        var mpp *record.MPP
181✔
1454
        r.PaymentAddr.WhenSome(func(addr [32]byte) {
236✔
1455
                mpp = record.NewMPP(amount, addr)
55✔
1456
        })
55✔
1457

1458
        var amp *record.AMP
181✔
1459
        if r.Amp != nil {
186✔
1460
                // The AMP payload is not easy accessible at this point but we
5✔
1461
                // are only interested in the size of the payload so we just use
5✔
1462
                // the AMP record dummy.
5✔
1463
                amp = &record.MaxAmpPayLoadSize
5✔
1464
        }
5✔
1465

1466
        finalHop := route.Hop{
181✔
1467
                AmtToForward:     amount,
181✔
1468
                OutgoingTimeLock: uint32(finalHtlcExpiry),
181✔
1469
                CustomRecords:    r.DestCustomRecords,
181✔
1470
                MPP:              mpp,
181✔
1471
                AMP:              amp,
181✔
1472
                Metadata:         r.Metadata,
181✔
1473
        }
181✔
1474

181✔
1475
        // The final hop does not have a short chanID set.
181✔
1476
        return finalHop.PayloadSize(0)
181✔
1477
}
1478

1479
// overflowSafeAdd adds two MilliSatoshi values and returns the result. If an
1480
// overflow could occur, zero is returned instead and the boolean is set to
1481
// true.
1482
func overflowSafeAdd(x, y lnwire.MilliSatoshi) (lnwire.MilliSatoshi, bool) {
437✔
1483
        if y > math.MaxUint64-x {
437✔
1484
                // Overflow would occur, return 0 and set overflow flag.
×
1485
                return 0, true
×
1486
        }
×
1487

1488
        return x + y, false
437✔
1489
}
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