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

lightningnetwork / lnd / 11216766535

07 Oct 2024 01:37PM UTC coverage: 57.817% (-1.0%) from 58.817%
11216766535

Pull #9148

github

ProofOfKeags
lnwire: remove kickoff feerate from propose/commit
Pull Request #9148: DynComms [2/n]: lnwire: add authenticated wire messages for Dyn*

571 of 879 new or added lines in 16 files covered. (64.96%)

23253 existing lines in 251 files now uncovered.

99022 of 171268 relevant lines covered (57.82%)

38420.67 hits per line

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

90.52
/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/fn"
18
        "github.com/lightningnetwork/lnd/lnutils"
19
        "github.com/lightningnetwork/lnd/lnwire"
20
        "github.com/lightningnetwork/lnd/record"
21
        "github.com/lightningnetwork/lnd/routing/route"
22
)
23

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

234
                        // Otherwise attach the mpp record if it exists.
235
                        // TODO(halseth): move this to payment life cycle,
236
                        // where AMP options are set.
237
                        finalHop.paymentAddr.WhenSome(func(addr [32]byte) {
135✔
238
                                mpp = record.NewMPP(finalHop.totalAmt, addr)
42✔
239
                        })
42✔
240

241
                        metadata = finalHop.metadata
93✔
242

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

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

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

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

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

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

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

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

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

317
                var (
1✔
318
                        inBlindedRoute bool
1✔
319
                        dataIndex      = 0
1✔
320

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

1✔
326
                        introVertex = route.NewVertex(
1✔
327
                                blindedPath.IntroductionPoint,
1✔
328
                        )
1✔
329
                )
1✔
330

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

339
                        // We don't need to modify edges outside of our blinded
340
                        // route.
341
                        if !inBlindedRoute {
5✔
342
                                continue
1✔
343
                        }
344

345
                        payload := blindedPath.BlindedHops[dataIndex].CipherText
3✔
346
                        hop.EncryptedData = payload
3✔
347

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

360
                        dataIndex++
3✔
361
                }
362
        }
363

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

373
        return newRoute, nil
93✔
374
}
375

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

1,125✔
391
        return int64(fee) + timeLockPenalty
1,125✔
392
}
1,125✔
393

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

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

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

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

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

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

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

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

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

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

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

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

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

463
        // BlindedPaymentPathSet is necessary to determine the hop size of the
464
        // last/exit hop.
465
        BlindedPaymentPathSet *BlindedPaymentPathSet
466

467
        // FirstHopCustomRecords includes any records that should be included in
468
        // the update_add_htlc message towards our peer.
469
        FirstHopCustomRecords lnwire.CustomRecords
470
}
471

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

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

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

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

178✔
498
        var max, total lnwire.MilliSatoshi
178✔
499
        cb := func(channel *channeldb.DirectedChannel) error {
615✔
500
                if !channel.OutPolicySet {
437✔
501
                        return nil
×
502
                }
×
503

504
                chanID := channel.ChannelID
437✔
505

437✔
506
                // Enforce outgoing channel restriction.
437✔
507
                if outgoingChans != nil {
457✔
508
                        if _, ok := outgoingChans[chanID]; !ok {
32✔
509
                                return nil
12✔
510
                        }
12✔
511
                }
512

513
                bandwidth, ok := bandwidthHints.availableChanBandwidth(
425✔
514
                        chanID, 0,
425✔
515
                )
425✔
516

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

524
                if bandwidth > max {
650✔
525
                        max = bandwidth
225✔
526
                }
225✔
527

528
                var overflow bool
425✔
529
                total, overflow = overflowSafeAdd(total, bandwidth)
425✔
530
                if overflow {
425✔
531
                        // If the current total and the bandwidth would
×
532
                        // overflow the maximum value, we set the total to the
×
533
                        // maximum value. Which is more milli-satoshis than are
×
534
                        // in existence anyway, so the actual value is
×
535
                        // irrelevant.
×
536
                        total = lnwire.MilliSatoshi(math.MaxUint64)
×
537
                }
×
538

539
                return nil
425✔
540
        }
541

542
        // Iterate over all channels of the to node.
543
        err := g.ForEachNodeChannel(node, cb)
178✔
544
        if err != nil {
178✔
545
                return 0, 0, err
×
546
        }
×
547
        return max, total, err
178✔
548
}
549

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

184✔
566
        // Pathfinding can be a significant portion of the total payment
184✔
567
        // latency, especially on low-powered devices. Log several metrics to
184✔
568
        // aid in the analysis performance problems in this area.
184✔
569
        start := time.Now()
184✔
570
        nodesVisited := 0
184✔
571
        edgesExpanded := 0
184✔
572
        defer func() {
368✔
573
                timeElapsed := time.Since(start)
184✔
574
                log.Debugf("Pathfinding perf metrics: nodes=%v, edges=%v, "+
184✔
575
                        "time=%v", nodesVisited, edgesExpanded, timeElapsed)
184✔
576
        }()
184✔
577

578
        // If no destination features are provided, we will load what features
579
        // we have for the target node from our graph.
580
        features := r.DestFeatures
184✔
581
        if features == nil {
300✔
582
                var err error
116✔
583
                features, err = g.graph.FetchNodeFeatures(target)
116✔
584
                if err != nil {
116✔
585
                        return nil, 0, err
×
586
                }
×
587
        }
588

589
        // Ensure that the destination's features don't include unknown
590
        // required features.
591
        err := feature.ValidateRequired(features)
184✔
592
        if err != nil {
186✔
593
                log.Warnf("Pathfinding destination node features: %v", err)
2✔
594
                return nil, 0, errUnknownRequiredFeature
2✔
595
        }
2✔
596

597
        // Ensure that all transitive dependencies are set.
598
        err = feature.ValidateDeps(features)
182✔
599
        if err != nil {
184✔
600
                log.Warnf("Pathfinding destination node features: %v", err)
2✔
601
                return nil, 0, errMissingDependentFeature
2✔
602
        }
2✔
603

604
        // Now that we know the feature vector is well-formed, we'll proceed in
605
        // checking that it supports the features we need. If the caller has a
606
        // payment address to attach, check that our destination feature vector
607
        // supports them.
608
        if r.PaymentAddr.IsSome() &&
180✔
609
                !features.HasFeature(lnwire.PaymentAddrOptional) {
182✔
610

2✔
611
                return nil, 0, errNoPaymentAddr
2✔
612
        }
2✔
613

614
        // Set up outgoing channel map for quicker access.
615
        var outgoingChanMap map[uint64]struct{}
178✔
616
        if len(r.OutgoingChannelIDs) > 0 {
184✔
617
                outgoingChanMap = make(map[uint64]struct{})
6✔
618
                for _, outChan := range r.OutgoingChannelIDs {
14✔
619
                        outgoingChanMap[outChan] = struct{}{}
8✔
620
                }
8✔
621
        }
622

623
        // If we are routing from ourselves, check that we have enough local
624
        // balance available.
625
        if source == self {
356✔
626
                max, total, err := getOutgoingBalance(
178✔
627
                        self, outgoingChanMap, g.bandwidthHints, g.graph,
178✔
628
                )
178✔
629
                if err != nil {
178✔
630
                        return nil, 0, err
×
631
                }
×
632

633
                // If the total outgoing balance isn't sufficient, it will be
634
                // impossible to complete the payment.
635
                if total < amt {
181✔
636
                        log.Warnf("Not enough outbound balance to send "+
3✔
637
                                "htlc of amount: %v, only have local "+
3✔
638
                                "balance: %v", amt, total)
3✔
639

3✔
640
                        return nil, 0, errInsufficientBalance
3✔
641
                }
3✔
642

643
                // If there is only not enough capacity on a single route, it
644
                // may still be possible to complete the payment by splitting.
645
                if max < amt {
176✔
646
                        return nil, 0, errNoPathFound
1✔
647
                }
1✔
648
        }
649

650
        // First we'll initialize an empty heap which'll help us to quickly
651
        // locate the next edge we should visit next during our graph
652
        // traversal.
653
        nodeHeap := newDistanceHeap(estimatedNodeCount)
174✔
654

174✔
655
        // Holds the current best distance for a given node.
174✔
656
        distance := make(map[route.Vertex]*nodeWithDist, estimatedNodeCount)
174✔
657

174✔
658
        additionalEdgesWithSrc := make(map[route.Vertex][]*edgePolicyWithSource)
174✔
659
        for vertex, additionalEdges := range g.additionalEdges {
186✔
660
                // Edges connected to self are always included in the graph,
12✔
661
                // therefore can be skipped. This prevents us from trying
12✔
662
                // routes to malformed hop hints.
12✔
663
                if vertex == self {
16✔
664
                        continue
4✔
665
                }
666

667
                // Build reverse lookup to find incoming edges. Needed because
668
                // search is taken place from target to source.
669
                for _, additionalEdge := range additionalEdges {
16✔
670
                        outgoingEdgePolicy := additionalEdge.EdgePolicy()
8✔
671
                        toVertex := outgoingEdgePolicy.ToNodePubKey()
8✔
672

8✔
673
                        incomingEdgePolicy := &edgePolicyWithSource{
8✔
674
                                sourceNode: vertex,
8✔
675
                                edge:       additionalEdge,
8✔
676
                        }
8✔
677

8✔
678
                        additionalEdgesWithSrc[toVertex] =
8✔
679
                                append(additionalEdgesWithSrc[toVertex],
8✔
680
                                        incomingEdgePolicy)
8✔
681
                }
8✔
682
        }
683

684
        // The payload size of the final hop differ from intermediate hops
685
        // and depends on whether the destination is blinded or not.
686
        lastHopPayloadSize := lastHopPayloadSize(r, finalHtlcExpiry, amt)
174✔
687

174✔
688
        // We can't always assume that the end destination is publicly
174✔
689
        // advertised to the network so we'll manually include the target node.
174✔
690
        // The target node charges no fee. Distance is set to 0, because this is
174✔
691
        // the starting point of the graph traversal. We are searching backwards
174✔
692
        // to get the fees first time right and correctly match channel
174✔
693
        // bandwidth.
174✔
694
        //
174✔
695
        // Don't record the initial partial path in the distance map and reserve
174✔
696
        // that key for the source key in the case we route to ourselves.
174✔
697
        partialPath := &nodeWithDist{
174✔
698
                dist:              0,
174✔
699
                weight:            0,
174✔
700
                node:              target,
174✔
701
                netAmountReceived: amt,
174✔
702
                incomingCltv:      finalHtlcExpiry,
174✔
703
                probability:       1,
174✔
704
                routingInfoSize:   lastHopPayloadSize,
174✔
705
        }
174✔
706

174✔
707
        // Calculate the absolute cltv limit. Use uint64 to prevent an overflow
174✔
708
        // if the cltv limit is MaxUint32.
174✔
709
        absoluteCltvLimit := uint64(r.CltvLimit) + uint64(finalHtlcExpiry)
174✔
710

174✔
711
        // Calculate the default attempt cost as configured globally.
174✔
712
        defaultAttemptCost := float64(
174✔
713
                cfg.AttemptCost +
174✔
714
                        amt*lnwire.MilliSatoshi(cfg.AttemptCostPPM)/1000000,
174✔
715
        )
174✔
716

174✔
717
        // Validate time preference value.
174✔
718
        if math.Abs(timePref) > 1 {
174✔
719
                return nil, 0, fmt.Errorf("time preference %v out of range "+
×
720
                        "[-1, 1]", timePref)
×
721
        }
×
722

723
        // Scale to avoid the extremes -1 and 1 which run into infinity issues.
724
        timePref *= 0.9
174✔
725

174✔
726
        // Apply time preference. At 0, the default attempt cost will
174✔
727
        // be used.
174✔
728
        absoluteAttemptCost := defaultAttemptCost * (1/(0.5-timePref/2) - 1)
174✔
729

174✔
730
        log.Debugf("Pathfinding absolute attempt cost: %v sats",
174✔
731
                absoluteAttemptCost/1000)
174✔
732

174✔
733
        // processEdge is a helper closure that will be used to make sure edges
174✔
734
        // satisfy our specific requirements.
174✔
735
        processEdge := func(fromVertex route.Vertex,
174✔
736
                edge *unifiedEdge, toNodeDist *nodeWithDist) {
1,416✔
737

1,242✔
738
                edgesExpanded++
1,242✔
739

1,242✔
740
                // Calculate inbound fee charged by "to" node. The exit hop
1,242✔
741
                // doesn't charge inbound fees. If the "to" node is the exit
1,242✔
742
                // hop, its inbound fees have already been set to zero by
1,242✔
743
                // nodeEdgeUnifier.
1,242✔
744
                inboundFee := edge.inboundFees.CalcFee(
1,242✔
745
                        toNodeDist.netAmountReceived,
1,242✔
746
                )
1,242✔
747

1,242✔
748
                // Make sure that the node total fee is never negative.
1,242✔
749
                // Routing nodes treat a total fee that turns out
1,242✔
750
                // negative as a zero fee and pathfinding should do the
1,242✔
751
                // same.
1,242✔
752
                minInboundFee := -int64(toNodeDist.outboundFee)
1,242✔
753
                if inboundFee < minInboundFee {
1,244✔
754
                        inboundFee = minInboundFee
2✔
755
                }
2✔
756

757
                // Calculate amount that the candidate node would have to send
758
                // out.
759
                amountToSend := toNodeDist.netAmountReceived +
1,242✔
760
                        lnwire.MilliSatoshi(inboundFee)
1,242✔
761

1,242✔
762
                // Check if accumulated fees would exceed fee limit when this
1,242✔
763
                // node would be added to the path.
1,242✔
764
                totalFee := int64(amountToSend) - int64(amt)
1,242✔
765

1,242✔
766
                log.Trace(lnutils.NewLogClosure(func() string {
1,242✔
767
                        return fmt.Sprintf(
×
768
                                "Checking fromVertex (%v) with "+
×
769
                                        "minInboundFee=%v, inboundFee=%v, "+
×
770
                                        "amountToSend=%v, amt=%v, totalFee=%v",
×
771
                                fromVertex, minInboundFee, inboundFee,
×
772
                                amountToSend, amt, totalFee,
×
773
                        )
×
774
                }))
×
775

776
                if totalFee > 0 && lnwire.MilliSatoshi(totalFee) > r.FeeLimit {
1,246✔
777
                        return
4✔
778
                }
4✔
779

780
                // Request the success probability for this edge.
781
                edgeProbability := r.ProbabilitySource(
1,238✔
782
                        fromVertex, toNodeDist.node, amountToSend,
1,238✔
783
                        edge.capacity,
1,238✔
784
                )
1,238✔
785

1,238✔
786
                log.Trace(lnutils.NewLogClosure(func() string {
1,238✔
787
                        return fmt.Sprintf("path finding probability: fromnode=%v,"+
×
788
                                " tonode=%v, amt=%v, cap=%v, probability=%v",
×
789
                                fromVertex, toNodeDist.node, amountToSend,
×
790
                                edge.capacity, edgeProbability)
×
791
                }))
×
792

793
                // If the probability is zero, there is no point in trying.
794
                if edgeProbability == 0 {
1,238✔
795
                        return
×
796
                }
×
797

798
                // Compute fee that fromVertex is charging. It is based on the
799
                // amount that needs to be sent to the next node in the route.
800
                //
801
                // Source node has no predecessor to pay a fee. Therefore set
802
                // fee to zero, because it should not be included in the fee
803
                // limit check and edge weight.
804
                //
805
                // Also determine the time lock delta that will be added to the
806
                // route if fromVertex is selected. If fromVertex is the source
807
                // node, no additional timelock is required.
808
                var (
1,238✔
809
                        timeLockDelta uint16
1,238✔
810
                        outboundFee   int64
1,238✔
811
                )
1,238✔
812

1,238✔
813
                if fromVertex != source {
2,334✔
814
                        outboundFee = int64(
1,096✔
815
                                edge.policy.ComputeFee(amountToSend),
1,096✔
816
                        )
1,096✔
817
                        timeLockDelta = edge.policy.TimeLockDelta
1,096✔
818
                }
1,096✔
819

820
                incomingCltv := toNodeDist.incomingCltv + int32(timeLockDelta)
1,238✔
821

1,238✔
822
                // Check that we are within our CLTV limit.
1,238✔
823
                if uint64(incomingCltv) > absoluteCltvLimit {
1,250✔
824
                        return
12✔
825
                }
12✔
826

827
                // netAmountToReceive is the amount that the node that is added
828
                // to the distance map needs to receive from a (to be found)
829
                // previous node in the route. The inbound fee of the receiving
830
                // node is already subtracted from this value. The previous node
831
                // will need to pay the amount that this node forwards plus the
832
                // fee it charges plus this node's inbound fee.
833
                netAmountToReceive := amountToSend +
1,226✔
834
                        lnwire.MilliSatoshi(outboundFee)
1,226✔
835

1,226✔
836
                // Calculate total probability of successfully reaching target
1,226✔
837
                // by multiplying the probabilities. Both this edge and the rest
1,226✔
838
                // of the route must succeed.
1,226✔
839
                probability := toNodeDist.probability * edgeProbability
1,226✔
840

1,226✔
841
                // If the probability is below the specified lower bound, we can
1,226✔
842
                // abandon this direction. Adding further nodes can only lower
1,226✔
843
                // the probability more.
1,226✔
844
                if probability < cfg.MinProbability {
1,327✔
845
                        return
101✔
846
                }
101✔
847

848
                // Calculate the combined fee for this edge. Dijkstra does not
849
                // support negative edge weights. Because this fee feeds into
850
                // the edge weight calculation, we don't allow it to be
851
                // negative.
852
                signedFee := inboundFee + outboundFee
1,125✔
853
                fee := lnwire.MilliSatoshi(0)
1,125✔
854
                if signedFee > 0 {
1,681✔
855
                        fee = lnwire.MilliSatoshi(signedFee)
556✔
856
                }
556✔
857

858
                // By adding fromVertex in the route, there will be an extra
859
                // weight composed of the fee that this node will charge and
860
                // the amount that will be locked for timeLockDelta blocks in
861
                // the HTLC that is handed out to fromVertex.
862
                weight := edgeWeight(amountToSend, fee, timeLockDelta)
1,125✔
863

1,125✔
864
                // Compute the tentative weight to this new channel/edge
1,125✔
865
                // which is the weight from our toNode to the target node
1,125✔
866
                // plus the weight of this edge.
1,125✔
867
                tempWeight := toNodeDist.weight + weight
1,125✔
868

1,125✔
869
                // Add an extra factor to the weight to take into account the
1,125✔
870
                // probability. Another reason why we rounded the fee up to zero
1,125✔
871
                // is to prevent a highly negative fee from cancelling out the
1,125✔
872
                // extra factor. We don't want an always-failing node to attract
1,125✔
873
                // traffic using a highly negative fee and escape penalization.
1,125✔
874
                tempDist := getProbabilityBasedDist(
1,125✔
875
                        tempWeight, probability,
1,125✔
876
                        absoluteAttemptCost,
1,125✔
877
                )
1,125✔
878

1,125✔
879
                // If there is already a best route stored, compare this
1,125✔
880
                // candidate route with the best route so far.
1,125✔
881
                current, ok := distance[fromVertex]
1,125✔
882
                if ok {
1,520✔
883
                        // If this route is worse than what we already found,
395✔
884
                        // skip this route.
395✔
885
                        if tempDist > current.dist {
700✔
886
                                return
305✔
887
                        }
305✔
888

889
                        // If the route is equally good and the probability
890
                        // isn't better, skip this route. It is important to
891
                        // also return if both cost and probability are equal,
892
                        // because otherwise the algorithm could run into an
893
                        // endless loop.
894
                        probNotBetter := probability <= current.probability
90✔
895
                        if tempDist == current.dist && probNotBetter {
149✔
896
                                return
59✔
897
                        }
59✔
898
                }
899

900
                // Calculate the total routing info size if this hop were to be
901
                // included. If we are coming from the source hop, the payload
902
                // size is zero, because the original htlc isn't in the onion
903
                // blob.
904
                var payloadSize uint64
761✔
905
                if fromVertex != source {
1,384✔
906
                        // In case the unifiedEdge does not have a payload size
623✔
907
                        // function supplied we request a graceful shutdown
623✔
908
                        // because this should never happen.
623✔
909
                        if edge.hopPayloadSizeFn == nil {
623✔
910
                                log.Criticalf("No payload size function "+
×
911
                                        "available for edge=%v unable to "+
×
912
                                        "determine payload size: %v", edge,
×
913
                                        ErrNoPayLoadSizeFunc)
×
914

×
915
                                return
×
916
                        }
×
917

918
                        payloadSize = edge.hopPayloadSizeFn(
623✔
919
                                amountToSend,
623✔
920
                                uint32(toNodeDist.incomingCltv),
623✔
921
                                edge.policy.ChannelID,
623✔
922
                        )
623✔
923
                }
924

925
                routingInfoSize := toNodeDist.routingInfoSize + payloadSize
761✔
926
                // Skip paths that would exceed the maximum routing info size.
761✔
927
                if routingInfoSize > sphinx.MaxPayloadSize {
767✔
928
                        return
6✔
929
                }
6✔
930

931
                // All conditions are met and this new tentative distance is
932
                // better than the current best known distance to this node.
933
                // The new better distance is recorded, and also our "next hop"
934
                // map is populated with this edge.
935
                withDist := &nodeWithDist{
755✔
936
                        dist:              tempDist,
755✔
937
                        weight:            tempWeight,
755✔
938
                        node:              fromVertex,
755✔
939
                        netAmountReceived: netAmountToReceive,
755✔
940
                        outboundFee:       lnwire.MilliSatoshi(outboundFee),
755✔
941
                        incomingCltv:      incomingCltv,
755✔
942
                        probability:       probability,
755✔
943
                        nextHop:           edge,
755✔
944
                        routingInfoSize:   routingInfoSize,
755✔
945
                }
755✔
946
                distance[fromVertex] = withDist
755✔
947

755✔
948
                // Either push withDist onto the heap if the node
755✔
949
                // represented by fromVertex is not already on the heap OR adjust
755✔
950
                // its position within the heap via heap.Fix.
755✔
951
                nodeHeap.PushOrFix(withDist)
755✔
952
        }
953

954
        // TODO(roasbeef): also add path caching
955
        //  * similar to route caching, but doesn't factor in the amount
956

957
        // Cache features because we visit nodes multiple times.
958
        featureCache := make(map[route.Vertex]*lnwire.FeatureVector)
174✔
959

174✔
960
        // getGraphFeatures returns (cached) node features from the graph.
174✔
961
        getGraphFeatures := func(node route.Vertex) (*lnwire.FeatureVector,
174✔
962
                error) {
1,420✔
963

1,246✔
964
                // Check cache for features of the fromNode.
1,246✔
965
                fromFeatures, ok := featureCache[node]
1,246✔
966
                if ok {
1,709✔
967
                        return fromFeatures, nil
463✔
968
                }
463✔
969

970
                // Fetch node features fresh from the graph.
971
                fromFeatures, err := g.graph.FetchNodeFeatures(node)
783✔
972
                if err != nil {
783✔
973
                        return nil, err
×
974
                }
×
975

976
                // Don't route through nodes that contain unknown required
977
                // features and mark as nil in the cache.
978
                err = feature.ValidateRequired(fromFeatures)
783✔
979
                if err != nil {
785✔
980
                        featureCache[node] = nil
2✔
981
                        return nil, nil
2✔
982
                }
2✔
983

984
                // Don't route through nodes that don't properly set all
985
                // transitive feature dependencies and mark as nil in the cache.
986
                err = feature.ValidateDeps(fromFeatures)
781✔
987
                if err != nil {
783✔
988
                        featureCache[node] = nil
2✔
989
                        return nil, nil
2✔
990
                }
2✔
991

992
                // Update cache.
993
                featureCache[node] = fromFeatures
779✔
994

779✔
995
                return fromFeatures, nil
779✔
996
        }
997

998
        routeToSelf := source == target
174✔
999
        for {
861✔
1000
                nodesVisited++
687✔
1001

687✔
1002
                pivot := partialPath.node
687✔
1003
                isExitHop := partialPath.nextHop == nil
687✔
1004

687✔
1005
                // Create unified policies for all incoming connections. Don't
687✔
1006
                // use inbound fees for the exit hop.
687✔
1007
                u := newNodeEdgeUnifier(
687✔
1008
                        self, pivot, !isExitHop, outgoingChanMap,
687✔
1009
                )
687✔
1010

687✔
1011
                err := u.addGraphPolicies(g.graph)
687✔
1012
                if err != nil {
687✔
1013
                        return nil, 0, err
×
1014
                }
×
1015

1016
                // We add hop hints that were supplied externally.
1017
                for _, reverseEdge := range additionalEdgesWithSrc[pivot] {
695✔
1018
                        // Assume zero inbound fees for route hints. If inbound
8✔
1019
                        // fees would apply, they couldn't be communicated in
8✔
1020
                        // bolt11 invoices currently.
8✔
1021
                        inboundFee := models.InboundFee{}
8✔
1022

8✔
1023
                        // Hop hints don't contain a capacity. We set one here,
8✔
1024
                        // since a capacity is needed for probability
8✔
1025
                        // calculations. We set a high capacity to act as if
8✔
1026
                        // there is enough liquidity, otherwise the hint would
8✔
1027
                        // not have been added by a wallet.
8✔
1028
                        // We also pass the payload size function to the
8✔
1029
                        // graph data so that we calculate the exact payload
8✔
1030
                        // size when evaluating this hop for a route.
8✔
1031
                        u.addPolicy(
8✔
1032
                                reverseEdge.sourceNode,
8✔
1033
                                reverseEdge.edge.EdgePolicy(),
8✔
1034
                                inboundFee,
8✔
1035
                                fakeHopHintCapacity,
8✔
1036
                                reverseEdge.edge.IntermediatePayloadSize,
8✔
1037
                                reverseEdge.edge.BlindedPayment(),
8✔
1038
                        )
8✔
1039
                }
8✔
1040

1041
                netAmountReceived := partialPath.netAmountReceived
687✔
1042

687✔
1043
                // Expand all connections using the optimal policy for each
687✔
1044
                // connection.
687✔
1045
                for fromNode, edgeUnifier := range u.edgeUnifiers {
2,294✔
1046
                        // The target node is not recorded in the distance map.
1,607✔
1047
                        // Therefore we need to have this check to prevent
1,607✔
1048
                        // creating a cycle. Only when we intend to route to
1,607✔
1049
                        // self, we allow this cycle to form. In that case we'll
1,607✔
1050
                        // also break out of the search loop below.
1,607✔
1051
                        if !routeToSelf && fromNode == target {
1,934✔
1052
                                continue
327✔
1053
                        }
1054

1055
                        // Apply last hop restriction if set.
1056
                        if r.LastHop != nil &&
1,280✔
1057
                                pivot == target && fromNode != *r.LastHop {
1,284✔
1058

4✔
1059
                                continue
4✔
1060
                        }
1061

1062
                        edge := edgeUnifier.getEdge(
1,276✔
1063
                                netAmountReceived, g.bandwidthHints,
1,276✔
1064
                                partialPath.outboundFee,
1,276✔
1065
                        )
1,276✔
1066

1,276✔
1067
                        if edge == nil {
1,306✔
1068
                                continue
30✔
1069
                        }
1070

1071
                        // Get feature vector for fromNode.
1072
                        fromFeatures, err := getGraphFeatures(fromNode)
1,246✔
1073
                        if err != nil {
1,246✔
1074
                                return nil, 0, err
×
1075
                        }
×
1076

1077
                        // If there are no valid features, skip this node.
1078
                        if fromFeatures == nil {
1,250✔
1079
                                continue
4✔
1080
                        }
1081

1082
                        // Check if this candidate node is better than what we
1083
                        // already have.
1084
                        processEdge(fromNode, edge, partialPath)
1,242✔
1085
                }
1086

1087
                if nodeHeap.Len() == 0 {
727✔
1088
                        break
40✔
1089
                }
1090

1091
                // Fetch the node within the smallest distance from our source
1092
                // from the heap.
1093
                partialPath = heap.Pop(&nodeHeap).(*nodeWithDist)
647✔
1094

647✔
1095
                // If we've reached our source (or we don't have any incoming
647✔
1096
                // edges), then we're done here and can exit the graph
647✔
1097
                // traversal early.
647✔
1098
                if partialPath.node == source {
781✔
1099
                        break
134✔
1100
                }
1101
        }
1102

1103
        // Use the distance map to unravel the forward path from source to
1104
        // target.
1105
        var pathEdges []*unifiedEdge
174✔
1106
        currentNode := source
174✔
1107
        for {
548✔
1108
                // Determine the next hop forward using the next map.
374✔
1109
                currentNodeWithDist, ok := distance[currentNode]
374✔
1110
                if !ok {
414✔
1111
                        // If the node doesn't have a next hop it means we
40✔
1112
                        // didn't find a path.
40✔
1113
                        return nil, 0, errNoPathFound
40✔
1114
                }
40✔
1115

1116
                // Add the next hop to the list of path edges.
1117
                pathEdges = append(pathEdges, currentNodeWithDist.nextHop)
334✔
1118

334✔
1119
                // Advance current node.
334✔
1120
                currentNode = currentNodeWithDist.nextHop.policy.ToNodePubKey()
334✔
1121

334✔
1122
                // Check stop condition at the end of this loop. This prevents
334✔
1123
                // breaking out too soon for self-payments that have target set
334✔
1124
                // to source.
334✔
1125
                if currentNode == target {
468✔
1126
                        break
134✔
1127
                }
1128
        }
1129

1130
        // For the final hop, we'll set the node features to those determined
1131
        // above. These are either taken from the destination features, e.g.
1132
        // virtual or invoice features, or loaded as a fallback from the graph.
1133
        // The transitive dependencies were already validated above, so no need
1134
        // to do so now.
1135
        //
1136
        // NOTE: This may overwrite features loaded from the graph if
1137
        // destination features were provided. This is fine though, since our
1138
        // route construction does not care where the features are actually
1139
        // taken from. In the future we may wish to do route construction within
1140
        // findPath, and avoid using ChannelEdgePolicy altogether.
1141
        pathEdges[len(pathEdges)-1].policy.ToNodeFeatures = features
134✔
1142

134✔
1143
        log.Debugf("Found route: probability=%v, hops=%v, fee=%v",
134✔
1144
                distance[source].probability, len(pathEdges),
134✔
1145
                distance[source].netAmountReceived-amt)
134✔
1146

134✔
1147
        return pathEdges, distance[source].probability, nil
134✔
1148
}
1149

1150
// blindedPathRestrictions are a set of constraints to adhere to when
1151
// choosing a set of blinded paths to this node.
1152
type blindedPathRestrictions struct {
1153
        // minNumHops is the minimum number of hops to include in a blinded
1154
        // path. This doesn't include our node, so if the minimum is 1, then
1155
        // the path will contain at minimum our node along with an introduction
1156
        // node hop. A minimum of 0 will include paths where this node is the
1157
        // introduction node and so should be used with caution.
1158
        minNumHops uint8
1159

1160
        // maxNumHops is the maximum number of hops to include in a blinded
1161
        // path. This doesn't include our node, so if the maximum is 1, then
1162
        // the path will contain our node along with an introduction node hop.
1163
        maxNumHops uint8
1164

1165
        // nodeOmissionSet holds a set of node IDs of nodes that we should
1166
        // ignore during blinded path selection.
1167
        nodeOmissionSet fn.Set[route.Vertex]
1168
}
1169

1170
// blindedHop holds the information about a hop we have selected for a blinded
1171
// path.
1172
type blindedHop struct {
1173
        vertex       route.Vertex
1174
        channelID    uint64
1175
        edgeCapacity btcutil.Amount
1176
}
1177

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

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

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

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

1210
                return features.HasFeature(lnwire.RouteBlindingOptional), nil
61✔
1211
        }
1212

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

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

1234
                orderedPaths[i] = append(path, blindedHop{vertex: target})
31✔
1235
        }
1236

1237
        // Handle the special case that allows a blinded path with the
1238
        // introduction node as the destination node.
1239
        if restrictions.minNumHops == 0 {
14✔
1240
                singleHopPath := [][]blindedHop{{{vertex: target}}}
2✔
1241

2✔
1242
                //nolint:makezero
2✔
1243
                orderedPaths = append(
2✔
1244
                        orderedPaths, singleHopPath...,
2✔
1245
                )
2✔
1246
        }
2✔
1247

1248
        return orderedPaths, err
12✔
1249
}
1250

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

198✔
1259
        // If we have already visited the maximum number of hops, then this path
198✔
1260
        // is complete and we can exit now.
198✔
1261
        if len(alreadyVisited) > int(restrictions.maxNumHops) {
290✔
1262
                return nil, false, nil
92✔
1263
        }
92✔
1264

1265
        // If we have already visited this peer on this path, then we skip
1266
        // processing it again.
1267
        if alreadyVisited[node] {
136✔
1268
                return nil, false, nil
30✔
1269
        }
30✔
1270

1271
        // If we have explicitly been told to ignore this node for blinded paths
1272
        // then we skip it too.
1273
        if restrictions.nodeOmissionSet.Contains(node) {
79✔
1274
                return nil, false, nil
3✔
1275
        }
3✔
1276

1277
        supports, err := supportsRouteBlinding(node)
73✔
1278
        if err != nil {
73✔
1279
                return nil, false, err
×
1280
        }
×
1281
        if !supports {
79✔
1282
                return nil, false, nil
6✔
1283
        }
6✔
1284

1285
        // At this point, copy the alreadyVisited map.
1286
        visited := make(map[route.Vertex]bool, len(alreadyVisited))
67✔
1287
        for r := range alreadyVisited {
151✔
1288
                visited[r] = true
84✔
1289
        }
84✔
1290

1291
        // Add this node the visited set.
1292
        visited[node] = true
67✔
1293

67✔
1294
        var (
67✔
1295
                hopSets   [][]blindedHop
67✔
1296
                chanCount int
67✔
1297
        )
67✔
1298

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

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

1318
                        hop := blindedHop{
186✔
1319
                                vertex:       channel.OtherNode,
186✔
1320
                                channelID:    channel.ChannelID,
186✔
1321
                                edgeCapacity: channel.Capacity,
186✔
1322
                        }
186✔
1323

186✔
1324
                        // For each of the paths returned, unwrap them and
186✔
1325
                        // append this hop to them.
186✔
1326
                        for _, path := range nextPaths {
215✔
1327
                                hopSets = append(
29✔
1328
                                        hopSets,
29✔
1329
                                        append([]blindedHop{hop}, path...),
29✔
1330
                                )
29✔
1331
                        }
29✔
1332

1333
                        // If this node does have channels other than the one
1334
                        // that lead to it, and if the hop count up to this node
1335
                        // meets the minHop requirement, then we also add a
1336
                        // path that starts at this node.
1337
                        if hasMoreChans &&
186✔
1338
                                len(visited) >= int(restrictions.minNumHops) {
217✔
1339

31✔
1340
                                hopSets = append(hopSets, []blindedHop{hop})
31✔
1341
                        }
31✔
1342

1343
                        return nil
186✔
1344
                },
1345
        )
1346
        if err != nil {
67✔
1347
                return nil, false, err
×
1348
        }
×
1349

1350
        return hopSets, chanCount > 1, nil
67✔
1351
}
1352

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

1,125✔
1389
        // Prevent divide by zero by returning early.
1,125✔
1390
        if probability == 0 {
1,125✔
1391
                return infinity
×
1392
        }
×
1393

1394
        // Calculate distance.
1395
        dist := float64(weight) + penalty/probability
1,125✔
1396

1,125✔
1397
        // Avoid cast if an overflow would occur. The maxFloat constant is
1,125✔
1398
        // chosen to stay well below the maximum float64 value that is still
1,125✔
1399
        // convertible to int64.
1,125✔
1400
        const maxFloat = 9000000000000000000
1,125✔
1401
        if dist > maxFloat {
1,125✔
1402
                return infinity
×
1403
        }
×
1404

1405
        return int64(dist)
1,125✔
1406
}
1407

1408
// lastHopPayloadSize calculates the payload size of the final hop in a route.
1409
// It depends on the tlv types which are present and also whether the hop is
1410
// part of a blinded route or not.
1411
func lastHopPayloadSize(r *RestrictParams, finalHtlcExpiry int32,
1412
        amount lnwire.MilliSatoshi) uint64 {
177✔
1413

177✔
1414
        if r.BlindedPaymentPathSet != nil {
179✔
1415
                paymentPath := r.BlindedPaymentPathSet.
2✔
1416
                        LargestLastHopPayloadPath()
2✔
1417
                blindedPath := paymentPath.BlindedPath.BlindedHops
2✔
1418
                blindedPoint := paymentPath.BlindedPath.BlindingPoint
2✔
1419

2✔
1420
                encryptedData := blindedPath[len(blindedPath)-1].CipherText
2✔
1421
                finalHop := route.Hop{
2✔
1422
                        AmtToForward:     amount,
2✔
1423
                        OutgoingTimeLock: uint32(finalHtlcExpiry),
2✔
1424
                        EncryptedData:    encryptedData,
2✔
1425
                }
2✔
1426
                if len(blindedPath) == 1 {
3✔
1427
                        finalHop.BlindingPoint = blindedPoint
1✔
1428
                }
1✔
1429

1430
                // The final hop does not have a short chanID set.
1431
                return finalHop.PayloadSize(0)
2✔
1432
        }
1433

1434
        var mpp *record.MPP
175✔
1435
        r.PaymentAddr.WhenSome(func(addr [32]byte) {
226✔
1436
                mpp = record.NewMPP(amount, addr)
51✔
1437
        })
51✔
1438

1439
        var amp *record.AMP
175✔
1440
        if r.Amp != nil {
176✔
1441
                // The AMP payload is not easy accessible at this point but we
1✔
1442
                // are only interested in the size of the payload so we just use
1✔
1443
                // the AMP record dummy.
1✔
1444
                amp = &record.MaxAmpPayLoadSize
1✔
1445
        }
1✔
1446

1447
        finalHop := route.Hop{
175✔
1448
                AmtToForward:     amount,
175✔
1449
                OutgoingTimeLock: uint32(finalHtlcExpiry),
175✔
1450
                CustomRecords:    r.DestCustomRecords,
175✔
1451
                MPP:              mpp,
175✔
1452
                AMP:              amp,
175✔
1453
                Metadata:         r.Metadata,
175✔
1454
        }
175✔
1455

175✔
1456
        // The final hop does not have a short chanID set.
175✔
1457
        return finalHop.PayloadSize(0)
175✔
1458
}
1459

1460
// overflowSafeAdd adds two MilliSatoshi values and returns the result. If an
1461
// overflow could occur, zero is returned instead and the boolean is set to
1462
// true.
1463
func overflowSafeAdd(x, y lnwire.MilliSatoshi) (lnwire.MilliSatoshi, bool) {
425✔
1464
        if y > math.MaxUint64-x {
425✔
1465
                // Overflow would occur, return 0 and set overflow flag.
×
1466
                return 0, true
×
1467
        }
×
1468

1469
        return x + y, false
425✔
1470
}
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