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

lightningnetwork / lnd / 10190851021

01 Aug 2024 02:21AM UTC coverage: 58.627% (-0.01%) from 58.641%
10190851021

push

github

web-flow
Merge pull request #8764 from ellemouton/rb-send-via-multi-path

[3/4] Route Blinding: send MPP over multiple blinded paths

197 of 248 new or added lines in 7 files covered. (79.44%)

249 existing lines in 19 files now uncovered.

125259 of 213655 relevant lines covered (58.63%)

28116.45 hits per line

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

91.93
/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/channeldb"
15
        "github.com/lightningnetwork/lnd/channeldb/models"
16
        "github.com/lightningnetwork/lnd/feature"
17
        "github.com/lightningnetwork/lnd/lnutils"
18
        "github.com/lightningnetwork/lnd/lnwire"
19
        "github.com/lightningnetwork/lnd/record"
20
        "github.com/lightningnetwork/lnd/routing/route"
21
)
22

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

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

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

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

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

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

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

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

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

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

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

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

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

112
        records     record.CustomSet
113
        paymentAddr *[32]byte
114

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

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

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

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

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

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

93✔
159
        pathLength := len(pathEdges)
93✔
160
        for i := pathLength - 1; i >= 0; i-- {
306✔
161
                // Now we'll start to calculate the items within the per-hop
213✔
162
                // payload for the hop this edge is leading to.
213✔
163
                edge := pathEdges[i].policy
213✔
164

213✔
165
                // If this is an edge from a blinded path and the
213✔
166
                // blindedPayment variable has not been set yet, then set it now
213✔
167
                // by extracting the corresponding blinded payment from the
213✔
168
                // edge.
213✔
169
                isBlindedEdge := pathEdges[i].blindedPayment != nil
213✔
170
                if isBlindedEdge && blindedPayment == nil {
217✔
171
                        blindedPayment = pathEdges[i].blindedPayment
4✔
172
                }
4✔
173

174
                // We'll calculate the amounts, timelocks, and fees for each hop
175
                // in the route. The base case is the final hop which includes
176
                // their amount and timelocks. These values will accumulate
177
                // contributions from the preceding hops back to the sender as
178
                // we compute the route in reverse.
179
                var (
213✔
180
                        amtToForward        lnwire.MilliSatoshi
213✔
181
                        fee                 int64
213✔
182
                        totalAmtMsatBlinded lnwire.MilliSatoshi
213✔
183
                        outgoingTimeLock    uint32
213✔
184
                        customRecords       record.CustomSet
213✔
185
                        mpp                 *record.MPP
213✔
186
                        metadata            []byte
213✔
187
                )
213✔
188

213✔
189
                // Define a helper function that checks this edge's feature
213✔
190
                // vector for support for a given feature. We assume at this
213✔
191
                // point that the feature vectors transitive dependencies have
213✔
192
                // been validated.
213✔
193
                supports := func(feature lnwire.FeatureBit) bool {
306✔
194
                        // If this edge comes from router hints, the features
93✔
195
                        // could be nil.
93✔
196
                        if edge.ToNodeFeatures == nil {
93✔
197
                                return false
×
198
                        }
×
199
                        return edge.ToNodeFeatures.HasFeature(feature)
93✔
200
                }
201

202
                if i == len(pathEdges)-1 {
306✔
203
                        // If this is the last hop, then the hop payload will
93✔
204
                        // contain the exact amount. In BOLT #4: Onion Routing
93✔
205
                        // Protocol / "Payload for the Last Node", this is
93✔
206
                        // detailed.
93✔
207
                        amtToForward = finalHop.amt
93✔
208

93✔
209
                        // Fee is not part of the hop payload, but only used for
93✔
210
                        // reporting through RPC. Set to zero for the final hop.
93✔
211
                        fee = 0
93✔
212

93✔
213
                        if blindedPathSet == nil {
185✔
214
                                totalTimeLock += uint32(finalHop.cltvDelta)
92✔
215
                        } else {
96✔
216
                                totalTimeLock += uint32(
4✔
217
                                        blindedPathSet.FinalCLTVDelta(),
4✔
218
                                )
4✔
219
                        }
4✔
220
                        outgoingTimeLock = totalTimeLock
93✔
221

93✔
222
                        // Attach any custom records to the final hop.
93✔
223
                        customRecords = finalHop.records
93✔
224

93✔
225
                        // If we're attaching a payment addr but the receiver
93✔
226
                        // doesn't support both TLV and payment addrs, fail.
93✔
227
                        payAddr := supports(lnwire.PaymentAddrOptional)
93✔
228
                        if !payAddr && finalHop.paymentAddr != nil {
93✔
229
                                return nil, errors.New("cannot attach " +
×
230
                                        "payment addr")
×
231
                        }
×
232

233
                        // Otherwise attach the mpp record if it exists.
234
                        // TODO(halseth): move this to payment life cycle,
235
                        // where AMP options are set.
236
                        if finalHop.paymentAddr != nil {
135✔
237
                                mpp = record.NewMPP(
42✔
238
                                        finalHop.totalAmt,
42✔
239
                                        *finalHop.paymentAddr,
42✔
240
                                )
42✔
241
                        }
42✔
242

243
                        metadata = finalHop.metadata
93✔
244

93✔
245
                        if blindedPathSet != nil {
97✔
246
                                totalAmtMsatBlinded = finalHop.totalAmt
4✔
247
                        }
4✔
248
                } else {
123✔
249
                        // The amount that the current hop needs to forward is
123✔
250
                        // equal to the incoming amount of the next hop.
123✔
251
                        amtToForward = nextIncomingAmount
123✔
252

123✔
253
                        // The fee that needs to be paid to the current hop is
123✔
254
                        // based on the amount that this hop needs to forward
123✔
255
                        // and its policy for the outgoing channel. This policy
123✔
256
                        // is stored as part of the incoming channel of
123✔
257
                        // the next hop.
123✔
258
                        outboundFee := pathEdges[i+1].policy.ComputeFee(
123✔
259
                                amtToForward,
123✔
260
                        )
123✔
261

123✔
262
                        inboundFee := pathEdges[i].inboundFees.CalcFee(
123✔
263
                                amtToForward + outboundFee,
123✔
264
                        )
123✔
265

123✔
266
                        fee = int64(outboundFee) + inboundFee
123✔
267
                        if fee < 0 {
125✔
268
                                fee = 0
2✔
269
                        }
2✔
270

271
                        // We'll take the total timelock of the preceding hop as
272
                        // the outgoing timelock or this hop. Then we'll
273
                        // increment the total timelock incurred by this hop.
274
                        outgoingTimeLock = totalTimeLock
123✔
275
                        totalTimeLock += uint32(
123✔
276
                                pathEdges[i+1].policy.TimeLockDelta,
123✔
277
                        )
123✔
278
                }
279

280
                // Since we're traversing the path backwards atm, we prepend
281
                // each new hop such that, the final slice of hops will be in
282
                // the forwards order.
283
                currentHop := &route.Hop{
213✔
284
                        PubKeyBytes:      edge.ToNodePubKey(),
213✔
285
                        ChannelID:        edge.ChannelID,
213✔
286
                        AmtToForward:     amtToForward,
213✔
287
                        OutgoingTimeLock: outgoingTimeLock,
213✔
288
                        CustomRecords:    customRecords,
213✔
289
                        MPP:              mpp,
213✔
290
                        Metadata:         metadata,
213✔
291
                        TotalAmtMsat:     totalAmtMsatBlinded,
213✔
292
                }
213✔
293

213✔
294
                hops = append([]*route.Hop{currentHop}, hops...)
213✔
295

213✔
296
                // Finally, we update the amount that needs to flow into the
213✔
297
                // *next* hop, which is the amount this hop needs to forward,
213✔
298
                // accounting for the fee that it takes.
213✔
299
                nextIncomingAmount = amtToForward + lnwire.MilliSatoshi(fee)
213✔
300
        }
301

302
        // If we are creating a route to a blinded path, we need to add some
303
        // additional data to the route that is required for blinded forwarding.
304
        // We do another pass on our edges to append this data.
305
        if blindedPathSet != nil {
97✔
306
                // If the passed in BlindedPaymentPathSet is non-nil but no
4✔
307
                // edge had a BlindedPayment attached, it means that the path
4✔
308
                // chosen was an introduction-node-only path. So in this case,
4✔
309
                // we can assume the relevant payment is the only one in the
4✔
310
                // payment set.
4✔
311
                if blindedPayment == nil {
7✔
312
                        var err error
3✔
313
                        blindedPayment, err = blindedPathSet.IntroNodeOnlyPath()
3✔
314
                        if err != nil {
3✔
NEW
315
                                return nil, err
×
NEW
316
                        }
×
317
                }
318

319
                var (
4✔
320
                        inBlindedRoute bool
4✔
321
                        dataIndex      = 0
4✔
322

4✔
323
                        blindedPath = blindedPayment.BlindedPath
4✔
324
                        numHops     = len(blindedPath.BlindedHops)
4✔
325
                        realFinal   = blindedPath.BlindedHops[numHops-1].
4✔
326
                                        BlindedNodePub
4✔
327

4✔
328
                        introVertex = route.NewVertex(
4✔
329
                                blindedPath.IntroductionPoint,
4✔
330
                        )
4✔
331
                )
4✔
332

4✔
333
                for i, hop := range hops {
11✔
334
                        // Once we locate our introduction node, we know that
7✔
335
                        // every hop after this is part of the blinded route.
7✔
336
                        if bytes.Equal(hop.PubKeyBytes[:], introVertex[:]) {
11✔
337
                                inBlindedRoute = true
4✔
338
                                hop.BlindingPoint = blindedPath.BlindingPoint
4✔
339
                        }
4✔
340

341
                        // We don't need to modify edges outside of our blinded
342
                        // route.
343
                        if !inBlindedRoute {
11✔
344
                                continue
4✔
345
                        }
346

347
                        payload := blindedPath.BlindedHops[dataIndex].CipherText
6✔
348
                        hop.EncryptedData = payload
6✔
349

6✔
350
                        // All of the hops in a blinded route *except* the
6✔
351
                        // final hop should have zero amounts / time locks.
6✔
352
                        if i != len(hops)-1 {
11✔
353
                                hop.AmtToForward = 0
5✔
354
                                hop.OutgoingTimeLock = 0
5✔
355
                        } else {
9✔
356
                                // For the final hop, we swap out the pub key
4✔
357
                                // bytes to the original destination node pub
4✔
358
                                // key for that payment path.
4✔
359
                                hop.PubKeyBytes = route.NewVertex(realFinal)
4✔
360
                        }
4✔
361

362
                        dataIndex++
6✔
363
                }
364
        }
365

366
        // With the base routing data expressed as hops, build the full route
367
        newRoute, err := route.NewRouteFromHops(
93✔
368
                nextIncomingAmount, totalTimeLock, route.Vertex(sourceVertex),
93✔
369
                hops,
93✔
370
        )
93✔
371
        if err != nil {
93✔
372
                return nil, err
×
373
        }
×
374

375
        return newRoute, nil
93✔
376
}
377

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

1,128✔
393
        return int64(fee) + timeLockPenalty
1,128✔
394
}
1,128✔
395

396
// graphParams wraps the set of graph parameters passed to findPath.
397
type graphParams struct {
398
        // graph is the ChannelGraph to be used during path finding.
399
        graph Graph
400

401
        // additionalEdges is an optional set of edges that should be
402
        // considered during path finding, that is not already found in the
403
        // channel graph. These can either be private edges for bolt 11 invoices
404
        // or blinded edges when a payment to a blinded path is made.
405
        additionalEdges map[route.Vertex][]AdditionalEdge
406

407
        // bandwidthHints is an interface that provides bandwidth hints that
408
        // can provide a better estimate of the current channel bandwidth than
409
        // what is found in the graph. It will override the capacities and
410
        // disabled flags found in the graph for local channels when doing
411
        // path finding if it has updated values for that channel. In
412
        // particular, it should be set to the current available sending
413
        // bandwidth for active local channels, and 0 for inactive channels.
414
        bandwidthHints bandwidthHints
415
}
416

417
// RestrictParams wraps the set of restrictions passed to findPath that the
418
// found path must adhere to.
419
type RestrictParams struct {
420
        // ProbabilitySource is a callback that is expected to return the
421
        // success probability of traversing the channel from the node.
422
        ProbabilitySource func(route.Vertex, route.Vertex,
423
                lnwire.MilliSatoshi, btcutil.Amount) float64
424

425
        // FeeLimit is a maximum fee amount allowed to be used on the path from
426
        // the source to the target.
427
        FeeLimit lnwire.MilliSatoshi
428

429
        // OutgoingChannelIDs is the list of channels that are allowed for the
430
        // first hop. If nil, any channel may be used.
431
        OutgoingChannelIDs []uint64
432

433
        // LastHop is the pubkey of the last node before the final destination
434
        // is reached. If nil, any node may be used.
435
        LastHop *route.Vertex
436

437
        // CltvLimit is the maximum time lock of the route excluding the final
438
        // ctlv. After path finding is complete, the caller needs to increase
439
        // all cltv expiry heights with the required final cltv delta.
440
        CltvLimit uint32
441

442
        // DestCustomRecords contains the custom records to drop off at the
443
        // final hop, if any.
444
        DestCustomRecords record.CustomSet
445

446
        // DestFeatures is a feature vector describing what the final hop
447
        // supports. If none are provided, pathfinding will try to inspect any
448
        // features on the node announcement instead.
449
        DestFeatures *lnwire.FeatureVector
450

451
        // PaymentAddr is a random 32-byte value generated by the receiver to
452
        // mitigate probing vectors and payment sniping attacks on overpaid
453
        // invoices.
454
        PaymentAddr *[32]byte
455

456
        // Amp signals to the pathfinder that this payment is an AMP payment
457
        // and therefore it needs to account for additional AMP data in the
458
        // final hop payload size calculation.
459
        Amp *AMPOptions
460

461
        // Metadata is additional data that is sent along with the payment to
462
        // the payee.
463
        Metadata []byte
464

465
        // BlindedPaymentPathSet is necessary to determine the hop size of the
466
        // last/exit hop.
467
        BlindedPaymentPathSet *BlindedPaymentPathSet
468
}
469

470
// PathFindingConfig defines global parameters that control the trade-off in
471
// path finding between fees and probability.
472
type PathFindingConfig struct {
473
        // AttemptCost is the fixed virtual cost in path finding of a failed
474
        // payment attempt. It is used to trade off potentially better routes
475
        // against their probability of succeeding.
476
        AttemptCost lnwire.MilliSatoshi
477

478
        // AttemptCostPPM is the proportional virtual cost in path finding of a
479
        // failed payment attempt. It is used to trade off potentially better
480
        // routes against their probability of succeeding. This parameter is
481
        // expressed in parts per million of the total payment amount.
482
        AttemptCostPPM int64
483

484
        // MinProbability defines the minimum success probability of the
485
        // returned route.
486
        MinProbability float64
487
}
488

489
// getOutgoingBalance returns the maximum available balance in any of the
490
// channels of the given node. The second return parameters is the total
491
// available balance.
492
func getOutgoingBalance(node route.Vertex, outgoingChans map[uint64]struct{},
493
        bandwidthHints bandwidthHints,
494
        g Graph) (lnwire.MilliSatoshi, lnwire.MilliSatoshi, error) {
181✔
495

181✔
496
        var max, total lnwire.MilliSatoshi
181✔
497
        cb := func(channel *channeldb.DirectedChannel) error {
621✔
498
                if !channel.OutPolicySet {
440✔
499
                        return nil
×
500
                }
×
501

502
                chanID := channel.ChannelID
440✔
503

440✔
504
                // Enforce outgoing channel restriction.
440✔
505
                if outgoingChans != nil {
460✔
506
                        if _, ok := outgoingChans[chanID]; !ok {
32✔
507
                                return nil
12✔
508
                        }
12✔
509
                }
510

511
                bandwidth, ok := bandwidthHints.availableChanBandwidth(
428✔
512
                        chanID, 0,
428✔
513
                )
428✔
514

428✔
515
                // If the bandwidth is not available, use the channel capacity.
428✔
516
                // This can happen when a channel is added to the graph after
428✔
517
                // we've already queried the bandwidth hints.
428✔
518
                if !ok {
662✔
519
                        bandwidth = lnwire.NewMSatFromSatoshis(channel.Capacity)
234✔
520
                }
234✔
521

522
                if bandwidth > max {
654✔
523
                        max = bandwidth
226✔
524
                }
226✔
525

526
                total += bandwidth
428✔
527

428✔
528
                return nil
428✔
529
        }
530

531
        // Iterate over all channels of the to node.
532
        err := g.ForEachNodeChannel(node, cb)
181✔
533
        if err != nil {
181✔
534
                return 0, 0, err
×
535
        }
×
536
        return max, total, err
181✔
537
}
538

539
// findPath attempts to find a path from the source node within the ChannelGraph
540
// to the target node that's capable of supporting a payment of `amt` value. The
541
// current approach implemented is modified version of Dijkstra's algorithm to
542
// find a single shortest path between the source node and the destination. The
543
// distance metric used for edges is related to the time-lock+fee costs along a
544
// particular edge. If a path is found, this function returns a slice of
545
// ChannelHop structs which encoded the chosen path from the target to the
546
// source. The search is performed backwards from destination node back to
547
// source. This is to properly accumulate fees that need to be paid along the
548
// path and accurately check the amount to forward at every node against the
549
// available bandwidth.
550
func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
551
        self, source, target route.Vertex, amt lnwire.MilliSatoshi,
552
        timePref float64, finalHtlcExpiry int32) ([]*unifiedEdge, float64,
553
        error) {
187✔
554

187✔
555
        // Pathfinding can be a significant portion of the total payment
187✔
556
        // latency, especially on low-powered devices. Log several metrics to
187✔
557
        // aid in the analysis performance problems in this area.
187✔
558
        start := time.Now()
187✔
559
        nodesVisited := 0
187✔
560
        edgesExpanded := 0
187✔
561
        defer func() {
374✔
562
                timeElapsed := time.Since(start)
187✔
563
                log.Debugf("Pathfinding perf metrics: nodes=%v, edges=%v, "+
187✔
564
                        "time=%v", nodesVisited, edgesExpanded, timeElapsed)
187✔
565
        }()
187✔
566

567
        // If no destination features are provided, we will load what features
568
        // we have for the target node from our graph.
569
        features := r.DestFeatures
187✔
570
        if features == nil {
306✔
571
                var err error
119✔
572
                features, err = g.graph.FetchNodeFeatures(target)
119✔
573
                if err != nil {
119✔
574
                        return nil, 0, err
×
575
                }
×
576
        }
577

578
        // Ensure that the destination's features don't include unknown
579
        // required features.
580
        err := feature.ValidateRequired(features)
187✔
581
        if err != nil {
189✔
582
                log.Warnf("Pathfinding destination node features: %v", err)
2✔
583
                return nil, 0, errUnknownRequiredFeature
2✔
584
        }
2✔
585

586
        // Ensure that all transitive dependencies are set.
587
        err = feature.ValidateDeps(features)
185✔
588
        if err != nil {
187✔
589
                log.Warnf("Pathfinding destination node features: %v", err)
2✔
590
                return nil, 0, errMissingDependentFeature
2✔
591
        }
2✔
592

593
        // Now that we know the feature vector is well-formed, we'll proceed in
594
        // checking that it supports the features we need. If the caller has a
595
        // payment address to attach, check that our destination feature vector
596
        // supports them.
597
        if r.PaymentAddr != nil &&
183✔
598
                !features.HasFeature(lnwire.PaymentAddrOptional) {
185✔
599

2✔
600
                return nil, 0, errNoPaymentAddr
2✔
601
        }
2✔
602

603
        // Set up outgoing channel map for quicker access.
604
        var outgoingChanMap map[uint64]struct{}
181✔
605
        if len(r.OutgoingChannelIDs) > 0 {
187✔
606
                outgoingChanMap = make(map[uint64]struct{})
6✔
607
                for _, outChan := range r.OutgoingChannelIDs {
14✔
608
                        outgoingChanMap[outChan] = struct{}{}
8✔
609
                }
8✔
610
        }
611

612
        // If we are routing from ourselves, check that we have enough local
613
        // balance available.
614
        if source == self {
362✔
615
                max, total, err := getOutgoingBalance(
181✔
616
                        self, outgoingChanMap, g.bandwidthHints, g.graph,
181✔
617
                )
181✔
618
                if err != nil {
181✔
619
                        return nil, 0, err
×
620
                }
×
621

622
                // If the total outgoing balance isn't sufficient, it will be
623
                // impossible to complete the payment.
624
                if total < amt {
187✔
625
                        log.Warnf("Not enough outbound balance to send "+
6✔
626
                                "htlc of amount: %v, only have local "+
6✔
627
                                "balance: %v", amt, total)
6✔
628

6✔
629
                        return nil, 0, errInsufficientBalance
6✔
630
                }
6✔
631

632
                // If there is only not enough capacity on a single route, it
633
                // may still be possible to complete the payment by splitting.
634
                if max < amt {
182✔
635
                        return nil, 0, errNoPathFound
4✔
636
                }
4✔
637
        }
638

639
        // First we'll initialize an empty heap which'll help us to quickly
640
        // locate the next edge we should visit next during our graph
641
        // traversal.
642
        nodeHeap := newDistanceHeap(estimatedNodeCount)
177✔
643

177✔
644
        // Holds the current best distance for a given node.
177✔
645
        distance := make(map[route.Vertex]*nodeWithDist, estimatedNodeCount)
177✔
646

177✔
647
        additionalEdgesWithSrc := make(map[route.Vertex][]*edgePolicyWithSource)
177✔
648
        for vertex, additionalEdges := range g.additionalEdges {
192✔
649
                // Edges connected to self are always included in the graph,
15✔
650
                // therefore can be skipped. This prevents us from trying
15✔
651
                // routes to malformed hop hints.
15✔
652
                if vertex == self {
22✔
653
                        continue
7✔
654
                }
655

656
                // Build reverse lookup to find incoming edges. Needed because
657
                // search is taken place from target to source.
658
                for _, additionalEdge := range additionalEdges {
22✔
659
                        outgoingEdgePolicy := additionalEdge.EdgePolicy()
11✔
660
                        toVertex := outgoingEdgePolicy.ToNodePubKey()
11✔
661

11✔
662
                        incomingEdgePolicy := &edgePolicyWithSource{
11✔
663
                                sourceNode: vertex,
11✔
664
                                edge:       additionalEdge,
11✔
665
                        }
11✔
666

11✔
667
                        additionalEdgesWithSrc[toVertex] =
11✔
668
                                append(additionalEdgesWithSrc[toVertex],
11✔
669
                                        incomingEdgePolicy)
11✔
670
                }
11✔
671
        }
672

673
        // The payload size of the final hop differ from intermediate hops
674
        // and depends on whether the destination is blinded or not.
675
        lastHopPayloadSize := lastHopPayloadSize(r, finalHtlcExpiry, amt)
177✔
676

177✔
677
        // We can't always assume that the end destination is publicly
177✔
678
        // advertised to the network so we'll manually include the target node.
177✔
679
        // The target node charges no fee. Distance is set to 0, because this is
177✔
680
        // the starting point of the graph traversal. We are searching backwards
177✔
681
        // to get the fees first time right and correctly match channel
177✔
682
        // bandwidth.
177✔
683
        //
177✔
684
        // Don't record the initial partial path in the distance map and reserve
177✔
685
        // that key for the source key in the case we route to ourselves.
177✔
686
        partialPath := &nodeWithDist{
177✔
687
                dist:              0,
177✔
688
                weight:            0,
177✔
689
                node:              target,
177✔
690
                netAmountReceived: amt,
177✔
691
                incomingCltv:      finalHtlcExpiry,
177✔
692
                probability:       1,
177✔
693
                routingInfoSize:   lastHopPayloadSize,
177✔
694
        }
177✔
695

177✔
696
        // Calculate the absolute cltv limit. Use uint64 to prevent an overflow
177✔
697
        // if the cltv limit is MaxUint32.
177✔
698
        absoluteCltvLimit := uint64(r.CltvLimit) + uint64(finalHtlcExpiry)
177✔
699

177✔
700
        // Calculate the default attempt cost as configured globally.
177✔
701
        defaultAttemptCost := float64(
177✔
702
                cfg.AttemptCost +
177✔
703
                        amt*lnwire.MilliSatoshi(cfg.AttemptCostPPM)/1000000,
177✔
704
        )
177✔
705

177✔
706
        // Validate time preference value.
177✔
707
        if math.Abs(timePref) > 1 {
177✔
708
                return nil, 0, fmt.Errorf("time preference %v out of range "+
×
709
                        "[-1, 1]", timePref)
×
710
        }
×
711

712
        // Scale to avoid the extremes -1 and 1 which run into infinity issues.
713
        timePref *= 0.9
177✔
714

177✔
715
        // Apply time preference. At 0, the default attempt cost will
177✔
716
        // be used.
177✔
717
        absoluteAttemptCost := defaultAttemptCost * (1/(0.5-timePref/2) - 1)
177✔
718

177✔
719
        log.Debugf("Pathfinding absolute attempt cost: %v sats",
177✔
720
                absoluteAttemptCost/1000)
177✔
721

177✔
722
        // processEdge is a helper closure that will be used to make sure edges
177✔
723
        // satisfy our specific requirements.
177✔
724
        processEdge := func(fromVertex route.Vertex,
177✔
725
                edge *unifiedEdge, toNodeDist *nodeWithDist) {
1,422✔
726

1,245✔
727
                edgesExpanded++
1,245✔
728

1,245✔
729
                // Calculate inbound fee charged by "to" node. The exit hop
1,245✔
730
                // doesn't charge inbound fees. If the "to" node is the exit
1,245✔
731
                // hop, its inbound fees have already been set to zero by
1,245✔
732
                // nodeEdgeUnifier.
1,245✔
733
                inboundFee := edge.inboundFees.CalcFee(
1,245✔
734
                        toNodeDist.netAmountReceived,
1,245✔
735
                )
1,245✔
736

1,245✔
737
                // Make sure that the node total fee is never negative.
1,245✔
738
                // Routing nodes treat a total fee that turns out
1,245✔
739
                // negative as a zero fee and pathfinding should do the
1,245✔
740
                // same.
1,245✔
741
                minInboundFee := -int64(toNodeDist.outboundFee)
1,245✔
742
                if inboundFee < minInboundFee {
1,247✔
743
                        inboundFee = minInboundFee
2✔
744
                }
2✔
745

746
                // Calculate amount that the candidate node would have to send
747
                // out.
748
                amountToSend := toNodeDist.netAmountReceived +
1,245✔
749
                        lnwire.MilliSatoshi(inboundFee)
1,245✔
750

1,245✔
751
                // Check if accumulated fees would exceed fee limit when this
1,245✔
752
                // node would be added to the path.
1,245✔
753
                totalFee := int64(amountToSend) - int64(amt)
1,245✔
754

1,245✔
755
                log.Trace(lnutils.NewLogClosure(func() string {
1,245✔
756
                        return fmt.Sprintf(
×
757
                                "Checking fromVertex (%v) with "+
×
758
                                        "minInboundFee=%v, inboundFee=%v, "+
×
759
                                        "amountToSend=%v, amt=%v, totalFee=%v",
×
760
                                fromVertex, minInboundFee, inboundFee,
×
761
                                amountToSend, amt, totalFee,
×
762
                        )
×
763
                }))
×
764

765
                if totalFee > 0 && lnwire.MilliSatoshi(totalFee) > r.FeeLimit {
1,249✔
766
                        return
4✔
767
                }
4✔
768

769
                // Request the success probability for this edge.
770
                edgeProbability := r.ProbabilitySource(
1,241✔
771
                        fromVertex, toNodeDist.node, amountToSend,
1,241✔
772
                        edge.capacity,
1,241✔
773
                )
1,241✔
774

1,241✔
775
                log.Trace(lnutils.NewLogClosure(func() string {
1,241✔
776
                        return fmt.Sprintf("path finding probability: fromnode=%v,"+
×
777
                                " tonode=%v, amt=%v, cap=%v, probability=%v",
×
778
                                fromVertex, toNodeDist.node, amountToSend,
×
779
                                edge.capacity, edgeProbability)
×
780
                }))
×
781

782
                // If the probability is zero, there is no point in trying.
783
                if edgeProbability == 0 {
1,241✔
784
                        return
×
785
                }
×
786

787
                // Compute fee that fromVertex is charging. It is based on the
788
                // amount that needs to be sent to the next node in the route.
789
                //
790
                // Source node has no predecessor to pay a fee. Therefore set
791
                // fee to zero, because it should not be included in the fee
792
                // limit check and edge weight.
793
                //
794
                // Also determine the time lock delta that will be added to the
795
                // route if fromVertex is selected. If fromVertex is the source
796
                // node, no additional timelock is required.
797
                var (
1,241✔
798
                        timeLockDelta uint16
1,241✔
799
                        outboundFee   int64
1,241✔
800
                )
1,241✔
801

1,241✔
802
                if fromVertex != source {
2,340✔
803
                        outboundFee = int64(
1,099✔
804
                                edge.policy.ComputeFee(amountToSend),
1,099✔
805
                        )
1,099✔
806
                        timeLockDelta = edge.policy.TimeLockDelta
1,099✔
807
                }
1,099✔
808

809
                incomingCltv := toNodeDist.incomingCltv + int32(timeLockDelta)
1,241✔
810

1,241✔
811
                // Check that we are within our CLTV limit.
1,241✔
812
                if uint64(incomingCltv) > absoluteCltvLimit {
1,253✔
813
                        return
12✔
814
                }
12✔
815

816
                // netAmountToReceive is the amount that the node that is added
817
                // to the distance map needs to receive from a (to be found)
818
                // previous node in the route. The inbound fee of the receiving
819
                // node is already subtracted from this value. The previous node
820
                // will need to pay the amount that this node forwards plus the
821
                // fee it charges plus this node's inbound fee.
822
                netAmountToReceive := amountToSend +
1,229✔
823
                        lnwire.MilliSatoshi(outboundFee)
1,229✔
824

1,229✔
825
                // Calculate total probability of successfully reaching target
1,229✔
826
                // by multiplying the probabilities. Both this edge and the rest
1,229✔
827
                // of the route must succeed.
1,229✔
828
                probability := toNodeDist.probability * edgeProbability
1,229✔
829

1,229✔
830
                // If the probability is below the specified lower bound, we can
1,229✔
831
                // abandon this direction. Adding further nodes can only lower
1,229✔
832
                // the probability more.
1,229✔
833
                if probability < cfg.MinProbability {
1,333✔
834
                        return
104✔
835
                }
104✔
836

837
                // Calculate the combined fee for this edge. Dijkstra does not
838
                // support negative edge weights. Because this fee feeds into
839
                // the edge weight calculation, we don't allow it to be
840
                // negative.
841
                signedFee := inboundFee + outboundFee
1,128✔
842
                fee := lnwire.MilliSatoshi(0)
1,128✔
843
                if signedFee > 0 {
1,687✔
844
                        fee = lnwire.MilliSatoshi(signedFee)
559✔
845
                }
559✔
846

847
                // By adding fromVertex in the route, there will be an extra
848
                // weight composed of the fee that this node will charge and
849
                // the amount that will be locked for timeLockDelta blocks in
850
                // the HTLC that is handed out to fromVertex.
851
                weight := edgeWeight(amountToSend, fee, timeLockDelta)
1,128✔
852

1,128✔
853
                // Compute the tentative weight to this new channel/edge
1,128✔
854
                // which is the weight from our toNode to the target node
1,128✔
855
                // plus the weight of this edge.
1,128✔
856
                tempWeight := toNodeDist.weight + weight
1,128✔
857

1,128✔
858
                // Add an extra factor to the weight to take into account the
1,128✔
859
                // probability. Another reason why we rounded the fee up to zero
1,128✔
860
                // is to prevent a highly negative fee from cancelling out the
1,128✔
861
                // extra factor. We don't want an always-failing node to attract
1,128✔
862
                // traffic using a highly negative fee and escape penalization.
1,128✔
863
                tempDist := getProbabilityBasedDist(
1,128✔
864
                        tempWeight, probability,
1,128✔
865
                        absoluteAttemptCost,
1,128✔
866
                )
1,128✔
867

1,128✔
868
                // If there is already a best route stored, compare this
1,128✔
869
                // candidate route with the best route so far.
1,128✔
870
                current, ok := distance[fromVertex]
1,128✔
871
                if ok {
1,526✔
872
                        // If this route is worse than what we already found,
398✔
873
                        // skip this route.
398✔
874
                        if tempDist > current.dist {
706✔
875
                                return
308✔
876
                        }
308✔
877

878
                        // If the route is equally good and the probability
879
                        // isn't better, skip this route. It is important to
880
                        // also return if both cost and probability are equal,
881
                        // because otherwise the algorithm could run into an
882
                        // endless loop.
883
                        probNotBetter := probability <= current.probability
93✔
884
                        if tempDist == current.dist && probNotBetter {
155✔
885
                                return
62✔
886
                        }
62✔
887
                }
888

889
                // Calculate the total routing info size if this hop were to be
890
                // included. If we are coming from the source hop, the payload
891
                // size is zero, because the original htlc isn't in the onion
892
                // blob.
893
                var payloadSize uint64
764✔
894
                if fromVertex != source {
1,390✔
895
                        // In case the unifiedEdge does not have a payload size
626✔
896
                        // function supplied we request a graceful shutdown
626✔
897
                        // because this should never happen.
626✔
898
                        if edge.hopPayloadSizeFn == nil {
626✔
899
                                log.Criticalf("No payload size function "+
×
900
                                        "available for edge=%v unable to "+
×
901
                                        "determine payload size: %v", edge,
×
902
                                        ErrNoPayLoadSizeFunc)
×
903

×
904
                                return
×
905
                        }
×
906

907
                        payloadSize = edge.hopPayloadSizeFn(
626✔
908
                                amountToSend,
626✔
909
                                uint32(toNodeDist.incomingCltv),
626✔
910
                                edge.policy.ChannelID,
626✔
911
                        )
626✔
912
                }
913

914
                routingInfoSize := toNodeDist.routingInfoSize + payloadSize
764✔
915
                // Skip paths that would exceed the maximum routing info size.
764✔
916
                if routingInfoSize > sphinx.MaxPayloadSize {
770✔
917
                        return
6✔
918
                }
6✔
919

920
                // All conditions are met and this new tentative distance is
921
                // better than the current best known distance to this node.
922
                // The new better distance is recorded, and also our "next hop"
923
                // map is populated with this edge.
924
                withDist := &nodeWithDist{
758✔
925
                        dist:              tempDist,
758✔
926
                        weight:            tempWeight,
758✔
927
                        node:              fromVertex,
758✔
928
                        netAmountReceived: netAmountToReceive,
758✔
929
                        outboundFee:       lnwire.MilliSatoshi(outboundFee),
758✔
930
                        incomingCltv:      incomingCltv,
758✔
931
                        probability:       probability,
758✔
932
                        nextHop:           edge,
758✔
933
                        routingInfoSize:   routingInfoSize,
758✔
934
                }
758✔
935
                distance[fromVertex] = withDist
758✔
936

758✔
937
                // Either push withDist onto the heap if the node
758✔
938
                // represented by fromVertex is not already on the heap OR adjust
758✔
939
                // its position within the heap via heap.Fix.
758✔
940
                nodeHeap.PushOrFix(withDist)
758✔
941
        }
942

943
        // TODO(roasbeef): also add path caching
944
        //  * similar to route caching, but doesn't factor in the amount
945

946
        // Cache features because we visit nodes multiple times.
947
        featureCache := make(map[route.Vertex]*lnwire.FeatureVector)
177✔
948

177✔
949
        // getGraphFeatures returns (cached) node features from the graph.
177✔
950
        getGraphFeatures := func(node route.Vertex) (*lnwire.FeatureVector,
177✔
951
                error) {
1,426✔
952

1,249✔
953
                // Check cache for features of the fromNode.
1,249✔
954
                fromFeatures, ok := featureCache[node]
1,249✔
955
                if ok {
1,715✔
956
                        return fromFeatures, nil
466✔
957
                }
466✔
958

959
                // Fetch node features fresh from the graph.
960
                fromFeatures, err := g.graph.FetchNodeFeatures(node)
786✔
961
                if err != nil {
786✔
962
                        return nil, err
×
963
                }
×
964

965
                // Don't route through nodes that contain unknown required
966
                // features and mark as nil in the cache.
967
                err = feature.ValidateRequired(fromFeatures)
786✔
968
                if err != nil {
788✔
969
                        featureCache[node] = nil
2✔
970
                        return nil, nil
2✔
971
                }
2✔
972

973
                // Don't route through nodes that don't properly set all
974
                // transitive feature dependencies and mark as nil in the cache.
975
                err = feature.ValidateDeps(fromFeatures)
784✔
976
                if err != nil {
786✔
977
                        featureCache[node] = nil
2✔
978
                        return nil, nil
2✔
979
                }
2✔
980

981
                // Update cache.
982
                featureCache[node] = fromFeatures
782✔
983

782✔
984
                return fromFeatures, nil
782✔
985
        }
986

987
        routeToSelf := source == target
177✔
988
        for {
867✔
989
                nodesVisited++
690✔
990

690✔
991
                pivot := partialPath.node
690✔
992
                isExitHop := partialPath.nextHop == nil
690✔
993

690✔
994
                // Create unified policies for all incoming connections. Don't
690✔
995
                // use inbound fees for the exit hop.
690✔
996
                u := newNodeEdgeUnifier(
690✔
997
                        self, pivot, !isExitHop, outgoingChanMap,
690✔
998
                )
690✔
999

690✔
1000
                err := u.addGraphPolicies(g.graph)
690✔
1001
                if err != nil {
690✔
1002
                        return nil, 0, err
×
1003
                }
×
1004

1005
                // We add hop hints that were supplied externally.
1006
                for _, reverseEdge := range additionalEdgesWithSrc[pivot] {
701✔
1007
                        // Assume zero inbound fees for route hints. If inbound
11✔
1008
                        // fees would apply, they couldn't be communicated in
11✔
1009
                        // bolt11 invoices currently.
11✔
1010
                        inboundFee := models.InboundFee{}
11✔
1011

11✔
1012
                        // Hop hints don't contain a capacity. We set one here,
11✔
1013
                        // since a capacity is needed for probability
11✔
1014
                        // calculations. We set a high capacity to act as if
11✔
1015
                        // there is enough liquidity, otherwise the hint would
11✔
1016
                        // not have been added by a wallet.
11✔
1017
                        // We also pass the payload size function to the
11✔
1018
                        // graph data so that we calculate the exact payload
11✔
1019
                        // size when evaluating this hop for a route.
11✔
1020
                        u.addPolicy(
11✔
1021
                                reverseEdge.sourceNode,
11✔
1022
                                reverseEdge.edge.EdgePolicy(),
11✔
1023
                                inboundFee,
11✔
1024
                                fakeHopHintCapacity,
11✔
1025
                                reverseEdge.edge.IntermediatePayloadSize,
11✔
1026
                                reverseEdge.edge.BlindedPayment(),
11✔
1027
                        )
11✔
1028
                }
11✔
1029

1030
                netAmountReceived := partialPath.netAmountReceived
690✔
1031

690✔
1032
                // Expand all connections using the optimal policy for each
690✔
1033
                // connection.
690✔
1034
                for fromNode, edgeUnifier := range u.edgeUnifiers {
2,300✔
1035
                        // The target node is not recorded in the distance map.
1,610✔
1036
                        // Therefore we need to have this check to prevent
1,610✔
1037
                        // creating a cycle. Only when we intend to route to
1,610✔
1038
                        // self, we allow this cycle to form. In that case we'll
1,610✔
1039
                        // also break out of the search loop below.
1,610✔
1040
                        if !routeToSelf && fromNode == target {
1,940✔
1041
                                continue
330✔
1042
                        }
1043

1044
                        // Apply last hop restriction if set.
1045
                        if r.LastHop != nil &&
1,283✔
1046
                                pivot == target && fromNode != *r.LastHop {
1,287✔
1047

4✔
1048
                                continue
4✔
1049
                        }
1050

1051
                        edge := edgeUnifier.getEdge(
1,279✔
1052
                                netAmountReceived, g.bandwidthHints,
1,279✔
1053
                                partialPath.outboundFee,
1,279✔
1054
                        )
1,279✔
1055

1,279✔
1056
                        if edge == nil {
1,312✔
1057
                                continue
33✔
1058
                        }
1059

1060
                        // Get feature vector for fromNode.
1061
                        fromFeatures, err := getGraphFeatures(fromNode)
1,249✔
1062
                        if err != nil {
1,249✔
1063
                                return nil, 0, err
×
1064
                        }
×
1065

1066
                        // If there are no valid features, skip this node.
1067
                        if fromFeatures == nil {
1,253✔
1068
                                continue
4✔
1069
                        }
1070

1071
                        // Check if this candidate node is better than what we
1072
                        // already have.
1073
                        processEdge(fromNode, edge, partialPath)
1,245✔
1074
                }
1075

1076
                if nodeHeap.Len() == 0 {
733✔
1077
                        break
43✔
1078
                }
1079

1080
                // Fetch the node within the smallest distance from our source
1081
                // from the heap.
1082
                partialPath = heap.Pop(&nodeHeap).(*nodeWithDist)
650✔
1083

650✔
1084
                // If we've reached our source (or we don't have any incoming
650✔
1085
                // edges), then we're done here and can exit the graph
650✔
1086
                // traversal early.
650✔
1087
                if partialPath.node == source {
787✔
1088
                        break
137✔
1089
                }
1090
        }
1091

1092
        // Use the distance map to unravel the forward path from source to
1093
        // target.
1094
        var pathEdges []*unifiedEdge
177✔
1095
        currentNode := source
177✔
1096
        for {
554✔
1097
                // Determine the next hop forward using the next map.
377✔
1098
                currentNodeWithDist, ok := distance[currentNode]
377✔
1099
                if !ok {
420✔
1100
                        // If the node doesn't have a next hop it means we
43✔
1101
                        // didn't find a path.
43✔
1102
                        return nil, 0, errNoPathFound
43✔
1103
                }
43✔
1104

1105
                // Add the next hop to the list of path edges.
1106
                pathEdges = append(pathEdges, currentNodeWithDist.nextHop)
337✔
1107

337✔
1108
                // Advance current node.
337✔
1109
                currentNode = currentNodeWithDist.nextHop.policy.ToNodePubKey()
337✔
1110

337✔
1111
                // Check stop condition at the end of this loop. This prevents
337✔
1112
                // breaking out too soon for self-payments that have target set
337✔
1113
                // to source.
337✔
1114
                if currentNode == target {
474✔
1115
                        break
137✔
1116
                }
1117
        }
1118

1119
        // For the final hop, we'll set the node features to those determined
1120
        // above. These are either taken from the destination features, e.g.
1121
        // virtual or invoice features, or loaded as a fallback from the graph.
1122
        // The transitive dependencies were already validated above, so no need
1123
        // to do so now.
1124
        //
1125
        // NOTE: This may overwrite features loaded from the graph if
1126
        // destination features were provided. This is fine though, since our
1127
        // route construction does not care where the features are actually
1128
        // taken from. In the future we may wish to do route construction within
1129
        // findPath, and avoid using ChannelEdgePolicy altogether.
1130
        pathEdges[len(pathEdges)-1].policy.ToNodeFeatures = features
137✔
1131

137✔
1132
        log.Debugf("Found route: probability=%v, hops=%v, fee=%v",
137✔
1133
                distance[source].probability, len(pathEdges),
137✔
1134
                distance[source].netAmountReceived-amt)
137✔
1135

137✔
1136
        return pathEdges, distance[source].probability, nil
137✔
1137
}
1138

1139
// blindedPathRestrictions are a set of constraints to adhere to when
1140
// choosing a set of blinded paths to this node.
1141
type blindedPathRestrictions struct {
1142
        // minNumHops is the minimum number of hops to include in a blinded
1143
        // path. This doesn't include our node, so if the minimum is 1, then
1144
        // the path will contain at minimum our node along with an introduction
1145
        // node hop. A minimum of 0 will include paths where this node is the
1146
        // introduction node and so should be used with caution.
1147
        minNumHops uint8
1148

1149
        // maxNumHops is the maximum number of hops to include in a blinded
1150
        // path. This doesn't include our node, so if the maximum is 1, then
1151
        // the path will contain our node along with an introduction node hop.
1152
        maxNumHops uint8
1153
}
1154

1155
// blindedHop holds the information about a hop we have selected for a blinded
1156
// path.
1157
type blindedHop struct {
1158
        vertex       route.Vertex
1159
        channelID    uint64
1160
        edgeCapacity btcutil.Amount
1161
}
1162

1163
// findBlindedPaths does a depth first search from the target node to find a set
1164
// of blinded paths to the target node given the set of restrictions. This
1165
// function will select and return any candidate path. A candidate path is a
1166
// path to the target node with a size determined by the given hop number
1167
// constraints where all the nodes on the path signal the route blinding feature
1168
// _and_ the introduction node for the path has more than one public channel.
1169
// Any filtering of paths based on payment value or success probabilities is
1170
// left to the caller.
1171
func findBlindedPaths(g Graph, target route.Vertex,
1172
        restrictions *blindedPathRestrictions) ([][]blindedHop, error) {
13✔
1173

13✔
1174
        // Sanity check the restrictions.
13✔
1175
        if restrictions.minNumHops > restrictions.maxNumHops {
13✔
1176
                return nil, fmt.Errorf("maximum number of blinded path hops "+
×
1177
                        "(%d) must be greater than or equal to the minimum "+
×
1178
                        "number of hops (%d)", restrictions.maxNumHops,
×
1179
                        restrictions.minNumHops)
×
1180
        }
×
1181

1182
        // If the node is not the destination node, then it is required that the
1183
        // node advertise the route blinding feature-bit in order for it to be
1184
        // chosen as a node on the blinded path.
1185
        supportsRouteBlinding := func(node route.Vertex) (bool, error) {
77✔
1186
                if node == target {
77✔
1187
                        return true, nil
13✔
1188
                }
13✔
1189

1190
                features, err := g.FetchNodeFeatures(node)
54✔
1191
                if err != nil {
54✔
1192
                        return false, err
×
1193
                }
×
1194

1195
                return features.HasFeature(lnwire.RouteBlindingOptional), nil
54✔
1196
        }
1197

1198
        // This function will have some recursion. We will spin out from the
1199
        // target node & append edges to the paths until we reach various exit
1200
        // conditions such as: The maxHops number being reached or reaching
1201
        // a node that doesn't have any other edges - in that final case, the
1202
        // whole path should be ignored.
1203
        paths, _, err := processNodeForBlindedPath(
13✔
1204
                g, target, supportsRouteBlinding, nil, restrictions,
13✔
1205
        )
13✔
1206
        if err != nil {
13✔
1207
                return nil, err
×
1208
        }
×
1209

1210
        // Reverse each path so that the order is correct (from introduction
1211
        // node to last hop node) and then append this node on as the
1212
        // destination of each path.
1213
        orderedPaths := make([][]blindedHop, len(paths))
13✔
1214
        for i, path := range paths {
43✔
1215
                sort.Slice(path, func(i, j int) bool {
60✔
1216
                        return j < i
30✔
1217
                })
30✔
1218

1219
                orderedPaths[i] = append(path, blindedHop{vertex: target})
30✔
1220
        }
1221

1222
        // Handle the special case that allows a blinded path with the
1223
        // introduction node as the destination node.
1224
        if restrictions.minNumHops == 0 {
18✔
1225
                singleHopPath := [][]blindedHop{{{vertex: target}}}
5✔
1226

5✔
1227
                //nolint:makezero
5✔
1228
                orderedPaths = append(
5✔
1229
                        orderedPaths, singleHopPath...,
5✔
1230
                )
5✔
1231
        }
5✔
1232

1233
        return orderedPaths, err
13✔
1234
}
1235

1236
// processNodeForBlindedPath is a recursive function that traverses the graph
1237
// in a depth first manner searching for a set of blinded paths to the given
1238
// node.
1239
func processNodeForBlindedPath(g Graph, node route.Vertex,
1240
        supportsRouteBlinding func(vertex route.Vertex) (bool, error),
1241
        alreadyVisited map[route.Vertex]bool,
1242
        restrictions *blindedPathRestrictions) ([][]blindedHop, bool, error) {
172✔
1243

172✔
1244
        // If we have already visited the maximum number of hops, then this path
172✔
1245
        // is complete and we can exit now.
172✔
1246
        if len(alreadyVisited) > int(restrictions.maxNumHops) {
259✔
1247
                return nil, false, nil
87✔
1248
        }
87✔
1249

1250
        // If we have already visited this peer on this path, then we skip
1251
        // processing it again.
1252
        if alreadyVisited[node] {
115✔
1253
                return nil, false, nil
27✔
1254
        }
27✔
1255

1256
        supports, err := supportsRouteBlinding(node)
64✔
1257
        if err != nil {
64✔
1258
                return nil, false, err
×
1259
        }
×
1260
        if !supports {
71✔
1261
                return nil, false, nil
7✔
1262
        }
7✔
1263

1264
        // At this point, copy the alreadyVisited map.
1265
        visited := make(map[route.Vertex]bool, len(alreadyVisited))
60✔
1266
        for r := range alreadyVisited {
135✔
1267
                visited[r] = true
75✔
1268
        }
75✔
1269

1270
        // Add this node the visited set.
1271
        visited[node] = true
60✔
1272

60✔
1273
        var (
60✔
1274
                hopSets   [][]blindedHop
60✔
1275
                chanCount int
60✔
1276
        )
60✔
1277

60✔
1278
        // Now, iterate over the node's channels in search for paths to this
60✔
1279
        // node that can be used for blinded paths
60✔
1280
        err = g.ForEachNodeChannel(node,
60✔
1281
                func(channel *channeldb.DirectedChannel) error {
222✔
1282
                        // Keep track of how many incoming channels this node
162✔
1283
                        // has. We only use a node as an introduction node if it
162✔
1284
                        // has channels other than the one that lead us to it.
162✔
1285
                        chanCount++
162✔
1286

162✔
1287
                        // Process each channel peer to gather any paths that
162✔
1288
                        // lead to the peer.
162✔
1289
                        nextPaths, hasMoreChans, err := processNodeForBlindedPath( //nolint:lll
162✔
1290
                                g, channel.OtherNode, supportsRouteBlinding,
162✔
1291
                                visited, restrictions,
162✔
1292
                        )
162✔
1293
                        if err != nil {
162✔
1294
                                return err
×
1295
                        }
×
1296

1297
                        hop := blindedHop{
162✔
1298
                                vertex:       channel.OtherNode,
162✔
1299
                                channelID:    channel.ChannelID,
162✔
1300
                                edgeCapacity: channel.Capacity,
162✔
1301
                        }
162✔
1302

162✔
1303
                        // For each of the paths returned, unwrap them and
162✔
1304
                        // append this hop to them.
162✔
1305
                        for _, path := range nextPaths {
190✔
1306
                                hopSets = append(
28✔
1307
                                        hopSets,
28✔
1308
                                        append([]blindedHop{hop}, path...),
28✔
1309
                                )
28✔
1310
                        }
28✔
1311

1312
                        // If this node does have channels other than the one
1313
                        // that lead to it, and if the hop count up to this node
1314
                        // meets the minHop requirement, then we also add a
1315
                        // path that starts at this node.
1316
                        if hasMoreChans &&
162✔
1317
                                len(visited) >= int(restrictions.minNumHops) {
192✔
1318

30✔
1319
                                hopSets = append(hopSets, []blindedHop{hop})
30✔
1320
                        }
30✔
1321

1322
                        return nil
162✔
1323
                },
1324
        )
1325
        if err != nil {
60✔
1326
                return nil, false, err
×
1327
        }
×
1328

1329
        return hopSets, chanCount > 1, nil
60✔
1330
}
1331

1332
// getProbabilityBasedDist converts a weight into a distance that takes into
1333
// account the success probability and the (virtual) cost of a failed payment
1334
// attempt.
1335
//
1336
// Derivation:
1337
//
1338
// Suppose there are two routes A and B with fees Fa and Fb and success
1339
// probabilities Pa and Pb.
1340
//
1341
// Is the expected cost of trying route A first and then B lower than trying the
1342
// other way around?
1343
//
1344
// The expected cost of A-then-B is: Pa*Fa + (1-Pa)*Pb*(c+Fb)
1345
//
1346
// The expected cost of B-then-A is: Pb*Fb + (1-Pb)*Pa*(c+Fa)
1347
//
1348
// In these equations, the term representing the case where both A and B fail is
1349
// left out because its value would be the same in both cases.
1350
//
1351
// Pa*Fa + (1-Pa)*Pb*(c+Fb) < Pb*Fb + (1-Pb)*Pa*(c+Fa)
1352
//
1353
// Pa*Fa + Pb*c + Pb*Fb - Pa*Pb*c - Pa*Pb*Fb < Pb*Fb + Pa*c + Pa*Fa - Pa*Pb*c - Pa*Pb*Fa
1354
//
1355
// Removing terms that cancel out:
1356
// Pb*c - Pa*Pb*Fb < Pa*c - Pa*Pb*Fa
1357
//
1358
// Divide by Pa*Pb:
1359
// c/Pa - Fb < c/Pb - Fa
1360
//
1361
// Move terms around:
1362
// Fa + c/Pa < Fb + c/Pb
1363
//
1364
// So the value of F + c/P can be used to compare routes.
1365
func getProbabilityBasedDist(weight int64, probability float64,
1366
        penalty float64) int64 {
1,128✔
1367

1,128✔
1368
        // Prevent divide by zero by returning early.
1,128✔
1369
        if probability == 0 {
1,128✔
1370
                return infinity
×
1371
        }
×
1372

1373
        // Calculate distance.
1374
        dist := float64(weight) + penalty/probability
1,128✔
1375

1,128✔
1376
        // Avoid cast if an overflow would occur. The maxFloat constant is
1,128✔
1377
        // chosen to stay well below the maximum float64 value that is still
1,128✔
1378
        // convertible to int64.
1,128✔
1379
        const maxFloat = 9000000000000000000
1,128✔
1380
        if dist > maxFloat {
1,128✔
1381
                return infinity
×
1382
        }
×
1383

1384
        return int64(dist)
1,128✔
1385
}
1386

1387
// lastHopPayloadSize calculates the payload size of the final hop in a route.
1388
// It depends on the tlv types which are present and also whether the hop is
1389
// part of a blinded route or not.
1390
func lastHopPayloadSize(r *RestrictParams, finalHtlcExpiry int32,
1391
        amount lnwire.MilliSatoshi) uint64 {
180✔
1392

180✔
1393
        if r.BlindedPaymentPathSet != nil {
185✔
1394
                paymentPath := r.BlindedPaymentPathSet.
5✔
1395
                        LargestLastHopPayloadPath()
5✔
1396
                blindedPath := paymentPath.BlindedPath.BlindedHops
5✔
1397
                blindedPoint := paymentPath.BlindedPath.BlindingPoint
5✔
1398

5✔
1399
                encryptedData := blindedPath[len(blindedPath)-1].CipherText
5✔
1400
                finalHop := route.Hop{
5✔
1401
                        AmtToForward:     amount,
5✔
1402
                        OutgoingTimeLock: uint32(finalHtlcExpiry),
5✔
1403
                        EncryptedData:    encryptedData,
5✔
1404
                }
5✔
1405
                if len(blindedPath) == 1 {
6✔
1406
                        finalHop.BlindingPoint = blindedPoint
1✔
1407
                }
1✔
1408

1409
                // The final hop does not have a short chanID set.
1410
                return finalHop.PayloadSize(0)
5✔
1411
        }
1412

1413
        var mpp *record.MPP
178✔
1414
        if r.PaymentAddr != nil {
232✔
1415
                mpp = record.NewMPP(amount, *r.PaymentAddr)
54✔
1416
        }
54✔
1417

1418
        var amp *record.AMP
178✔
1419
        if r.Amp != nil {
182✔
1420
                // The AMP payload is not easy accessible at this point but we
4✔
1421
                // are only interested in the size of the payload so we just use
4✔
1422
                // the AMP record dummy.
4✔
1423
                amp = &record.MaxAmpPayLoadSize
4✔
1424
        }
4✔
1425

1426
        finalHop := route.Hop{
178✔
1427
                AmtToForward:     amount,
178✔
1428
                OutgoingTimeLock: uint32(finalHtlcExpiry),
178✔
1429
                CustomRecords:    r.DestCustomRecords,
178✔
1430
                MPP:              mpp,
178✔
1431
                AMP:              amp,
178✔
1432
                Metadata:         r.Metadata,
178✔
1433
        }
178✔
1434

178✔
1435
        // The final hop does not have a short chanID set.
178✔
1436
        return finalHop.PayloadSize(0)
178✔
1437
}
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