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

lightningnetwork / lnd / 12199391122

06 Dec 2024 01:10PM UTC coverage: 49.807% (-9.1%) from 58.933%
12199391122

push

github

web-flow
Merge pull request #9337 from Guayaba221/patch-1

chore: fix typo in ruby.md

100137 of 201051 relevant lines covered (49.81%)

2.07 hits per line

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

75.31
/routing/payment_session.go
1
package routing
2

3
import (
4
        "fmt"
5

6
        "github.com/btcsuite/btcd/btcec/v2"
7
        "github.com/btcsuite/btclog/v2"
8
        "github.com/lightningnetwork/lnd/channeldb"
9
        "github.com/lightningnetwork/lnd/graph/db/models"
10
        "github.com/lightningnetwork/lnd/lnutils"
11
        "github.com/lightningnetwork/lnd/lnwire"
12
        "github.com/lightningnetwork/lnd/netann"
13
        "github.com/lightningnetwork/lnd/routing/route"
14
)
15

16
// BlockPadding is used to increment the finalCltvDelta value for the last hop
17
// to prevent an HTLC being failed if some blocks are mined while it's in-flight.
18
const BlockPadding uint16 = 3
19

20
// ValidateCLTVLimit is a helper function that validates that the cltv limit is
21
// greater than the final cltv delta parameter, optionally including the
22
// BlockPadding in this calculation.
23
func ValidateCLTVLimit(limit uint32, delta uint16, includePad bool) error {
4✔
24
        if includePad {
8✔
25
                delta += BlockPadding
4✔
26
        }
4✔
27

28
        if limit <= uint32(delta) {
4✔
29
                return fmt.Errorf("cltv limit %v should be greater than %v",
×
30
                        limit, delta)
×
31
        }
×
32

33
        return nil
4✔
34
}
35

36
// noRouteError encodes a non-critical error encountered during path finding.
37
type noRouteError uint8
38

39
const (
40
        // errNoTlvPayload is returned when the destination hop does not support
41
        // a tlv payload.
42
        errNoTlvPayload noRouteError = iota
43

44
        // errNoPaymentAddr is returned when the destination hop does not
45
        // support payment addresses.
46
        errNoPaymentAddr
47

48
        // errNoPathFound is returned when a path to the target destination does
49
        // not exist in the graph.
50
        errNoPathFound
51

52
        // errInsufficientLocalBalance is returned when none of the local
53
        // channels have enough balance for the payment.
54
        errInsufficientBalance
55

56
        // errEmptyPaySession is returned when the empty payment session is
57
        // queried for a route.
58
        errEmptyPaySession
59

60
        // errUnknownRequiredFeature is returned when the destination node
61
        // requires an unknown feature.
62
        errUnknownRequiredFeature
63

64
        // errMissingDependentFeature is returned when the destination node
65
        // misses a feature that a feature that we require depends on.
66
        errMissingDependentFeature
67
)
68

69
var (
70
        // DefaultShardMinAmt is the default amount beyond which we won't try to
71
        // further split the payment if no route is found. It is the minimum
72
        // amount that we use as the shard size when splitting.
73
        DefaultShardMinAmt = lnwire.NewMSatFromSatoshis(10000)
74
)
75

76
// Error returns the string representation of the noRouteError.
77
func (e noRouteError) Error() string {
4✔
78
        switch e {
4✔
79
        case errNoTlvPayload:
×
80
                return "destination hop doesn't understand new TLV payloads"
×
81

82
        case errNoPaymentAddr:
×
83
                return "destination hop doesn't understand payment addresses"
×
84

85
        case errNoPathFound:
4✔
86
                return "unable to find a path to destination"
4✔
87

88
        case errEmptyPaySession:
4✔
89
                return "empty payment session"
4✔
90

91
        case errInsufficientBalance:
4✔
92
                return "insufficient local balance"
4✔
93

94
        case errUnknownRequiredFeature:
×
95
                return "unknown required feature"
×
96

97
        case errMissingDependentFeature:
×
98
                return "missing dependent feature"
×
99

100
        default:
×
101
                return "unknown no-route error"
×
102
        }
103
}
104

105
// FailureReason converts a path finding error into a payment-level failure.
106
func (e noRouteError) FailureReason() channeldb.FailureReason {
4✔
107
        switch e {
4✔
108
        case
109
                errNoTlvPayload,
110
                errNoPaymentAddr,
111
                errNoPathFound,
112
                errEmptyPaySession,
113
                errUnknownRequiredFeature,
114
                errMissingDependentFeature:
4✔
115

4✔
116
                return channeldb.FailureReasonNoRoute
4✔
117

118
        case errInsufficientBalance:
4✔
119
                return channeldb.FailureReasonInsufficientBalance
4✔
120

121
        default:
×
122
                return channeldb.FailureReasonError
×
123
        }
124
}
125

126
// PaymentSession is used during SendPayment attempts to provide routes to
127
// attempt. It also defines methods to give the PaymentSession additional
128
// information learned during the previous attempts.
129
type PaymentSession interface {
130
        // RequestRoute returns the next route to attempt for routing the
131
        // specified HTLC payment to the target node. The returned route should
132
        // carry at most maxAmt to the target node, and pay at most feeLimit in
133
        // fees. It can carry less if the payment is MPP. The activeShards
134
        // argument should be set to instruct the payment session about the
135
        // number of in flight HTLCS for the payment, such that it can choose
136
        // splitting strategy accordingly.
137
        //
138
        // A noRouteError is returned if a non-critical error is encountered
139
        // during path finding.
140
        RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi,
141
                activeShards, height uint32,
142
                firstHopCustomRecords lnwire.CustomRecords) (*route.Route,
143
                error)
144

145
        // UpdateAdditionalEdge takes an additional channel edge policy
146
        // (private channels) and applies the update from the message. Returns
147
        // a boolean to indicate whether the update has been applied without
148
        // error.
149
        UpdateAdditionalEdge(msg *lnwire.ChannelUpdate1,
150
                pubKey *btcec.PublicKey, policy *models.CachedEdgePolicy) bool
151

152
        // GetAdditionalEdgePolicy uses the public key and channel ID to query
153
        // the ephemeral channel edge policy for additional edges. Returns a nil
154
        // if nothing found.
155
        GetAdditionalEdgePolicy(pubKey *btcec.PublicKey,
156
                channelID uint64) *models.CachedEdgePolicy
157
}
158

159
// paymentSession is used during an HTLC routings session to prune the local
160
// chain view in response to failures, and also report those failures back to
161
// MissionController. The snapshot copied for this session will only ever grow,
162
// and will now be pruned after a decay like the main view within mission
163
// control. We do this as we want to avoid the case where we continually try a
164
// bad edge or route multiple times in a session. This can lead to an infinite
165
// loop if payment attempts take long enough. An additional set of edges can
166
// also be provided to assist in reaching the payment's destination.
167
type paymentSession struct {
168
        selfNode route.Vertex
169

170
        additionalEdges map[route.Vertex][]AdditionalEdge
171

172
        getBandwidthHints func(Graph) (bandwidthHints, error)
173

174
        payment *LightningPayment
175

176
        empty bool
177

178
        pathFinder pathFinder
179

180
        graphSessFactory GraphSessionFactory
181

182
        // pathFindingConfig defines global parameters that control the
183
        // trade-off in path finding between fees and probability.
184
        pathFindingConfig PathFindingConfig
185

186
        missionControl MissionControlQuerier
187

188
        // minShardAmt is the amount beyond which we won't try to further split
189
        // the payment if no route is found. If the maximum number of htlcs
190
        // specified in the payment is one, under no circumstances splitting
191
        // will happen and this value remains unused.
192
        minShardAmt lnwire.MilliSatoshi
193

194
        // log is a payment session-specific logger.
195
        log btclog.Logger
196
}
197

198
// newPaymentSession instantiates a new payment session.
199
func newPaymentSession(p *LightningPayment, selfNode route.Vertex,
200
        getBandwidthHints func(Graph) (bandwidthHints, error),
201
        graphSessFactory GraphSessionFactory,
202
        missionControl MissionControlQuerier,
203
        pathFindingConfig PathFindingConfig) (*paymentSession, error) {
4✔
204

4✔
205
        edges, err := RouteHintsToEdges(p.RouteHints, p.Target)
4✔
206
        if err != nil {
4✔
207
                return nil, err
×
208
        }
×
209

210
        if p.BlindedPathSet != nil {
8✔
211
                if len(edges) != 0 {
4✔
212
                        return nil, fmt.Errorf("cannot have both route hints " +
×
213
                                "and blinded path")
×
214
                }
×
215

216
                edges, err = p.BlindedPathSet.ToRouteHints()
4✔
217
                if err != nil {
4✔
218
                        return nil, err
×
219
                }
×
220
        }
221

222
        logPrefix := fmt.Sprintf("PaymentSession(%x):", p.Identifier())
4✔
223

4✔
224
        return &paymentSession{
4✔
225
                selfNode:          selfNode,
4✔
226
                additionalEdges:   edges,
4✔
227
                getBandwidthHints: getBandwidthHints,
4✔
228
                payment:           p,
4✔
229
                pathFinder:        findPath,
4✔
230
                graphSessFactory:  graphSessFactory,
4✔
231
                pathFindingConfig: pathFindingConfig,
4✔
232
                missionControl:    missionControl,
4✔
233
                minShardAmt:       DefaultShardMinAmt,
4✔
234
                log:               log.WithPrefix(logPrefix),
4✔
235
        }, nil
4✔
236
}
237

238
// RequestRoute returns a route which is likely to be capable for successfully
239
// routing the specified HTLC payment to the target node. Initially the first
240
// set of paths returned from this method may encounter routing failure along
241
// the way, however as more payments are sent, mission control will start to
242
// build an up to date view of the network itself. With each payment a new area
243
// will be explored, which feeds into the recommendations made for routing.
244
//
245
// NOTE: This function is safe for concurrent access.
246
// NOTE: Part of the PaymentSession interface.
247
func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi,
248
        activeShards, height uint32,
249
        firstHopCustomRecords lnwire.CustomRecords) (*route.Route, error) {
4✔
250

4✔
251
        if p.empty {
8✔
252
                return nil, errEmptyPaySession
4✔
253
        }
4✔
254

255
        // Add BlockPadding to the finalCltvDelta so that the receiving node
256
        // does not reject the HTLC if some blocks are mined while it's in-flight.
257
        finalCltvDelta := p.payment.FinalCLTVDelta
4✔
258
        finalCltvDelta += BlockPadding
4✔
259

4✔
260
        // We need to subtract the final delta before passing it into path
4✔
261
        // finding. The optimal path is independent of the final cltv delta and
4✔
262
        // the path finding algorithm is unaware of this value.
4✔
263
        cltvLimit := p.payment.CltvLimit - uint32(finalCltvDelta)
4✔
264

4✔
265
        // TODO(roasbeef): sync logic amongst dist sys
4✔
266

4✔
267
        // Taking into account this prune view, we'll attempt to locate a path
4✔
268
        // to our destination, respecting the recommendations from
4✔
269
        // MissionController.
4✔
270
        restrictions := &RestrictParams{
4✔
271
                ProbabilitySource:     p.missionControl.GetProbability,
4✔
272
                FeeLimit:              feeLimit,
4✔
273
                OutgoingChannelIDs:    p.payment.OutgoingChannelIDs,
4✔
274
                LastHop:               p.payment.LastHop,
4✔
275
                CltvLimit:             cltvLimit,
4✔
276
                DestCustomRecords:     p.payment.DestCustomRecords,
4✔
277
                DestFeatures:          p.payment.DestFeatures,
4✔
278
                PaymentAddr:           p.payment.PaymentAddr,
4✔
279
                Amp:                   p.payment.amp,
4✔
280
                Metadata:              p.payment.Metadata,
4✔
281
                FirstHopCustomRecords: firstHopCustomRecords,
4✔
282
        }
4✔
283

4✔
284
        finalHtlcExpiry := int32(height) + int32(finalCltvDelta)
4✔
285

4✔
286
        // Before we enter the loop below, we'll make sure to respect the max
4✔
287
        // payment shard size (if it's set), which is effectively our
4✔
288
        // client-side MTU that we'll attempt to respect at all times.
4✔
289
        maxShardActive := p.payment.MaxShardAmt != nil
4✔
290
        if maxShardActive && maxAmt > *p.payment.MaxShardAmt {
4✔
291
                p.log.Debugf("Clamping payment attempt from %v to %v due to "+
×
292
                        "max shard size of %v", maxAmt, *p.payment.MaxShardAmt,
×
293
                        maxAmt)
×
294

×
295
                maxAmt = *p.payment.MaxShardAmt
×
296
        }
×
297

298
        for {
8✔
299
                // Get a routing graph session.
4✔
300
                graph, closeGraph, err := p.graphSessFactory.NewGraphSession()
4✔
301
                if err != nil {
4✔
302
                        return nil, err
×
303
                }
×
304

305
                // We'll also obtain a set of bandwidthHints from the lower
306
                // layer for each of our outbound channels. This will allow the
307
                // path finding to skip any links that aren't active or just
308
                // don't have enough bandwidth to carry the payment. New
309
                // bandwidth hints are queried for every new path finding
310
                // attempt, because concurrent payments may change balances.
311
                bandwidthHints, err := p.getBandwidthHints(graph)
4✔
312
                if err != nil {
4✔
313
                        // Close routing graph session.
×
314
                        if graphErr := closeGraph(); graphErr != nil {
×
315
                                log.Errorf("could not close graph session: %v",
×
316
                                        graphErr)
×
317
                        }
×
318

319
                        return nil, err
×
320
                }
321

322
                p.log.Debugf("pathfinding for amt=%v", maxAmt)
4✔
323

4✔
324
                // Find a route for the current amount.
4✔
325
                path, _, err := p.pathFinder(
4✔
326
                        &graphParams{
4✔
327
                                additionalEdges: p.additionalEdges,
4✔
328
                                bandwidthHints:  bandwidthHints,
4✔
329
                                graph:           graph,
4✔
330
                        },
4✔
331
                        restrictions, &p.pathFindingConfig,
4✔
332
                        p.selfNode, p.selfNode, p.payment.Target,
4✔
333
                        maxAmt, p.payment.TimePref, finalHtlcExpiry,
4✔
334
                )
4✔
335

4✔
336
                // Close routing graph session.
4✔
337
                if err := closeGraph(); err != nil {
4✔
338
                        log.Errorf("could not close graph session: %v", err)
×
339
                }
×
340

341
                switch {
4✔
342
                case err == errNoPathFound:
4✔
343
                        // Don't split if this is a legacy payment without mpp
4✔
344
                        // record. If it has a blinded path though, then we
4✔
345
                        // can split. Split payments to blinded paths won't have
4✔
346
                        // MPP records.
4✔
347
                        if p.payment.PaymentAddr.IsNone() &&
4✔
348
                                p.payment.BlindedPathSet == nil {
8✔
349

4✔
350
                                p.log.Debugf("not splitting because payment " +
4✔
351
                                        "address is unspecified")
4✔
352

4✔
353
                                return nil, errNoPathFound
4✔
354
                        }
4✔
355

356
                        if p.payment.DestFeatures == nil {
4✔
357
                                p.log.Debug("Not splitting because " +
×
358
                                        "destination DestFeatures is nil")
×
359
                                return nil, errNoPathFound
×
360
                        }
×
361

362
                        destFeatures := p.payment.DestFeatures
4✔
363
                        if !destFeatures.HasFeature(lnwire.MPPOptional) &&
4✔
364
                                !destFeatures.HasFeature(lnwire.AMPOptional) {
4✔
365

×
366
                                p.log.Debug("not splitting because " +
×
367
                                        "destination doesn't declare MPP or " +
×
368
                                        "AMP")
×
369

×
370
                                return nil, errNoPathFound
×
371
                        }
×
372

373
                        // No splitting if this is the last shard.
374
                        isLastShard := activeShards+1 >= p.payment.MaxParts
4✔
375
                        if isLastShard {
8✔
376
                                p.log.Debugf("not splitting because shard "+
4✔
377
                                        "limit %v has been reached",
4✔
378
                                        p.payment.MaxParts)
4✔
379

4✔
380
                                return nil, errNoPathFound
4✔
381
                        }
4✔
382

383
                        // This is where the magic happens. If we can't find a
384
                        // route, try it for half the amount.
385
                        maxAmt /= 2
4✔
386

4✔
387
                        // Put a lower bound on the minimum shard size.
4✔
388
                        if maxAmt < p.minShardAmt {
8✔
389
                                p.log.Debugf("not splitting because minimum "+
4✔
390
                                        "shard amount %v has been reached",
4✔
391
                                        p.minShardAmt)
4✔
392

4✔
393
                                return nil, errNoPathFound
4✔
394
                        }
4✔
395

396
                        // Go pathfinding.
397
                        continue
4✔
398

399
                // If there isn't enough local bandwidth, there is no point in
400
                // splitting. It won't be possible to create a complete set in
401
                // any case, but the sent out partial payments would be held by
402
                // the receiver until the mpp timeout.
403
                case err == errInsufficientBalance:
4✔
404
                        p.log.Debug("not splitting because local balance " +
4✔
405
                                "is insufficient")
4✔
406

4✔
407
                        return nil, err
4✔
408

409
                case err != nil:
×
410
                        return nil, err
×
411
                }
412

413
                // With the next candidate path found, we'll attempt to turn
414
                // this into a route by applying the time-lock and fee
415
                // requirements.
416
                route, err := newRoute(
4✔
417
                        p.selfNode, path, height,
4✔
418
                        finalHopParams{
4✔
419
                                amt:         maxAmt,
4✔
420
                                totalAmt:    p.payment.Amount,
4✔
421
                                cltvDelta:   finalCltvDelta,
4✔
422
                                records:     p.payment.DestCustomRecords,
4✔
423
                                paymentAddr: p.payment.PaymentAddr,
4✔
424
                                metadata:    p.payment.Metadata,
4✔
425
                        }, p.payment.BlindedPathSet,
4✔
426
                )
4✔
427
                if err != nil {
4✔
428
                        return nil, err
×
429
                }
×
430

431
                return route, err
4✔
432
        }
433
}
434

435
// UpdateAdditionalEdge updates the channel edge policy for a private edge. It
436
// validates the message signature and checks it's up to date, then applies the
437
// updates to the supplied policy. It returns a boolean to indicate whether
438
// there's an error when applying the updates.
439
func (p *paymentSession) UpdateAdditionalEdge(msg *lnwire.ChannelUpdate1,
440
        pubKey *btcec.PublicKey, policy *models.CachedEdgePolicy) bool {
4✔
441

4✔
442
        // Validate the message signature.
4✔
443
        if err := netann.VerifyChannelUpdateSignature(msg, pubKey); err != nil {
4✔
444
                log.Errorf(
×
445
                        "Unable to validate channel update signature: %v", err,
×
446
                )
×
447
                return false
×
448
        }
×
449

450
        // Update channel policy for the additional edge.
451
        policy.TimeLockDelta = msg.TimeLockDelta
4✔
452
        policy.FeeBaseMSat = lnwire.MilliSatoshi(msg.BaseFee)
4✔
453
        policy.FeeProportionalMillionths = lnwire.MilliSatoshi(msg.FeeRate)
4✔
454

4✔
455
        log.Debugf("New private channel update applied: %v",
4✔
456
                lnutils.SpewLogClosure(msg))
4✔
457

4✔
458
        return true
4✔
459
}
460

461
// GetAdditionalEdgePolicy uses the public key and channel ID to query the
462
// ephemeral channel edge policy for additional edges. Returns a nil if nothing
463
// found.
464
func (p *paymentSession) GetAdditionalEdgePolicy(pubKey *btcec.PublicKey,
465
        channelID uint64) *models.CachedEdgePolicy {
4✔
466

4✔
467
        target := route.NewVertex(pubKey)
4✔
468

4✔
469
        edges, ok := p.additionalEdges[target]
4✔
470
        if !ok {
8✔
471
                return nil
4✔
472
        }
4✔
473

474
        for _, edge := range edges {
8✔
475
                policy := edge.EdgePolicy()
4✔
476
                if policy.ChannelID != channelID {
4✔
477
                        continue
×
478
                }
479

480
                return policy
4✔
481
        }
482

483
        return nil
×
484
}
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