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

lightningnetwork / lnd / 9915780197

13 Jul 2024 12:30AM UTC coverage: 49.268% (-9.1%) from 58.413%
9915780197

push

github

web-flow
Merge pull request #8653 from ProofOfKeags/fn-prim

DynComms [0/n]: `fn` package additions

92837 of 188433 relevant lines covered (49.27%)

1.55 hits per line

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

78.48
/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"
8
        "github.com/davecgh/go-spew/spew"
9
        "github.com/lightningnetwork/lnd/build"
10
        "github.com/lightningnetwork/lnd/channeldb"
11
        "github.com/lightningnetwork/lnd/channeldb/models"
12
        "github.com/lightningnetwork/lnd/lnwire"
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 {
3✔
24
        if includePad {
6✔
25
                delta += BlockPadding
3✔
26
        }
3✔
27

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

33
        return nil
3✔
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 {
3✔
78
        switch e {
3✔
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:
3✔
86
                return "unable to find a path to destination"
3✔
87

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

91
        case errInsufficientBalance:
3✔
92
                return "insufficient local balance"
3✔
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 {
3✔
107
        switch e {
3✔
108
        case
109
                errNoTlvPayload,
110
                errNoPaymentAddr,
111
                errNoPathFound,
112
                errEmptyPaySession,
113
                errUnknownRequiredFeature,
114
                errMissingDependentFeature:
3✔
115

3✔
116
                return channeldb.FailureReasonNoRoute
3✔
117

118
        case errInsufficientBalance:
3✔
119
                return channeldb.FailureReasonInsufficientBalance
3✔
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) (*route.Route, error)
142

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

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

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

168
        getBandwidthHints func(routingGraph) (bandwidthHints, error)
169

170
        payment *LightningPayment
171

172
        empty bool
173

174
        pathFinder pathFinder
175

176
        getRoutingGraph func() (routingGraph, func(), error)
177

178
        // pathFindingConfig defines global parameters that control the
179
        // trade-off in path finding between fees and probability.
180
        pathFindingConfig PathFindingConfig
181

182
        missionControl MissionController
183

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

190
        // log is a payment session-specific logger.
191
        log btclog.Logger
192
}
193

194
// newPaymentSession instantiates a new payment session.
195
func newPaymentSession(p *LightningPayment,
196
        getBandwidthHints func(routingGraph) (bandwidthHints, error),
197
        getRoutingGraph func() (routingGraph, func(), error),
198
        missionControl MissionController, pathFindingConfig PathFindingConfig) (
199
        *paymentSession, error) {
3✔
200

3✔
201
        edges, err := RouteHintsToEdges(p.RouteHints, p.Target)
3✔
202
        if err != nil {
3✔
203
                return nil, err
×
204
        }
×
205

206
        logPrefix := fmt.Sprintf("PaymentSession(%x):", p.Identifier())
3✔
207

3✔
208
        return &paymentSession{
3✔
209
                additionalEdges:   edges,
3✔
210
                getBandwidthHints: getBandwidthHints,
3✔
211
                payment:           p,
3✔
212
                pathFinder:        findPath,
3✔
213
                getRoutingGraph:   getRoutingGraph,
3✔
214
                pathFindingConfig: pathFindingConfig,
3✔
215
                missionControl:    missionControl,
3✔
216
                minShardAmt:       DefaultShardMinAmt,
3✔
217
                log:               build.NewPrefixLog(logPrefix, log),
3✔
218
        }, nil
3✔
219
}
220

221
// RequestRoute returns a route which is likely to be capable for successfully
222
// routing the specified HTLC payment to the target node. Initially the first
223
// set of paths returned from this method may encounter routing failure along
224
// the way, however as more payments are sent, mission control will start to
225
// build an up to date view of the network itself. With each payment a new area
226
// will be explored, which feeds into the recommendations made for routing.
227
//
228
// NOTE: This function is safe for concurrent access.
229
// NOTE: Part of the PaymentSession interface.
230
func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi,
231
        activeShards, height uint32) (*route.Route, error) {
3✔
232

3✔
233
        if p.empty {
6✔
234
                return nil, errEmptyPaySession
3✔
235
        }
3✔
236

237
        // Add BlockPadding to the finalCltvDelta so that the receiving node
238
        // does not reject the HTLC if some blocks are mined while it's in-flight.
239
        finalCltvDelta := p.payment.FinalCLTVDelta
3✔
240
        finalCltvDelta += BlockPadding
3✔
241

3✔
242
        // We need to subtract the final delta before passing it into path
3✔
243
        // finding. The optimal path is independent of the final cltv delta and
3✔
244
        // the path finding algorithm is unaware of this value.
3✔
245
        cltvLimit := p.payment.CltvLimit - uint32(finalCltvDelta)
3✔
246

3✔
247
        // TODO(roasbeef): sync logic amongst dist sys
3✔
248

3✔
249
        // Taking into account this prune view, we'll attempt to locate a path
3✔
250
        // to our destination, respecting the recommendations from
3✔
251
        // MissionControl.
3✔
252
        restrictions := &RestrictParams{
3✔
253
                ProbabilitySource:  p.missionControl.GetProbability,
3✔
254
                FeeLimit:           feeLimit,
3✔
255
                OutgoingChannelIDs: p.payment.OutgoingChannelIDs,
3✔
256
                LastHop:            p.payment.LastHop,
3✔
257
                CltvLimit:          cltvLimit,
3✔
258
                DestCustomRecords:  p.payment.DestCustomRecords,
3✔
259
                DestFeatures:       p.payment.DestFeatures,
3✔
260
                PaymentAddr:        p.payment.PaymentAddr,
3✔
261
                Amp:                p.payment.amp,
3✔
262
                Metadata:           p.payment.Metadata,
3✔
263
        }
3✔
264

3✔
265
        finalHtlcExpiry := int32(height) + int32(finalCltvDelta)
3✔
266

3✔
267
        // Before we enter the loop below, we'll make sure to respect the max
3✔
268
        // payment shard size (if it's set), which is effectively our
3✔
269
        // client-side MTU that we'll attempt to respect at all times.
3✔
270
        maxShardActive := p.payment.MaxShardAmt != nil
3✔
271
        if maxShardActive && maxAmt > *p.payment.MaxShardAmt {
3✔
272
                p.log.Debug("Clamping payment attempt from %v to %v due to "+
×
273
                        "max shard size of %v", maxAmt,
×
274
                        *p.payment.MaxShardAmt, maxAmt)
×
275

×
276
                maxAmt = *p.payment.MaxShardAmt
×
277
        }
×
278

279
        for {
6✔
280
                // Get a routing graph.
3✔
281
                routingGraph, cleanup, err := p.getRoutingGraph()
3✔
282
                if err != nil {
3✔
283
                        return nil, err
×
284
                }
×
285

286
                // We'll also obtain a set of bandwidthHints from the lower
287
                // layer for each of our outbound channels. This will allow the
288
                // path finding to skip any links that aren't active or just
289
                // don't have enough bandwidth to carry the payment. New
290
                // bandwidth hints are queried for every new path finding
291
                // attempt, because concurrent payments may change balances.
292
                bandwidthHints, err := p.getBandwidthHints(routingGraph)
3✔
293
                if err != nil {
3✔
294
                        return nil, err
×
295
                }
×
296

297
                p.log.Debugf("pathfinding for amt=%v", maxAmt)
3✔
298

3✔
299
                sourceVertex := routingGraph.sourceNode()
3✔
300

3✔
301
                // Find a route for the current amount.
3✔
302
                path, _, err := p.pathFinder(
3✔
303
                        &graphParams{
3✔
304
                                additionalEdges: p.additionalEdges,
3✔
305
                                bandwidthHints:  bandwidthHints,
3✔
306
                                graph:           routingGraph,
3✔
307
                        },
3✔
308
                        restrictions, &p.pathFindingConfig,
3✔
309
                        sourceVertex, p.payment.Target,
3✔
310
                        maxAmt, p.payment.TimePref, finalHtlcExpiry,
3✔
311
                )
3✔
312

3✔
313
                // Close routing graph.
3✔
314
                cleanup()
3✔
315

3✔
316
                switch {
3✔
317
                case err == errNoPathFound:
3✔
318
                        // Don't split if this is a legacy payment without mpp
3✔
319
                        // record.
3✔
320
                        if p.payment.PaymentAddr == nil {
6✔
321
                                p.log.Debugf("not splitting because payment " +
3✔
322
                                        "address is unspecified")
3✔
323

3✔
324
                                return nil, errNoPathFound
3✔
325
                        }
3✔
326

327
                        if p.payment.DestFeatures == nil {
3✔
328
                                p.log.Debug("Not splitting because " +
×
329
                                        "destination DestFeatures is nil")
×
330
                                return nil, errNoPathFound
×
331
                        }
×
332

333
                        destFeatures := p.payment.DestFeatures
3✔
334
                        if !destFeatures.HasFeature(lnwire.MPPOptional) &&
3✔
335
                                !destFeatures.HasFeature(lnwire.AMPOptional) {
3✔
336

×
337
                                p.log.Debug("not splitting because " +
×
338
                                        "destination doesn't declare MPP or AMP")
×
339

×
340
                                return nil, errNoPathFound
×
341
                        }
×
342

343
                        // No splitting if this is the last shard.
344
                        isLastShard := activeShards+1 >= p.payment.MaxParts
3✔
345
                        if isLastShard {
6✔
346
                                p.log.Debugf("not splitting because shard "+
3✔
347
                                        "limit %v has been reached",
3✔
348
                                        p.payment.MaxParts)
3✔
349

3✔
350
                                return nil, errNoPathFound
3✔
351
                        }
3✔
352

353
                        // This is where the magic happens. If we can't find a
354
                        // route, try it for half the amount.
355
                        maxAmt /= 2
3✔
356

3✔
357
                        // Put a lower bound on the minimum shard size.
3✔
358
                        if maxAmt < p.minShardAmt {
6✔
359
                                p.log.Debugf("not splitting because minimum "+
3✔
360
                                        "shard amount %v has been reached",
3✔
361
                                        p.minShardAmt)
3✔
362

3✔
363
                                return nil, errNoPathFound
3✔
364
                        }
3✔
365

366
                        // Go pathfinding.
367
                        continue
3✔
368

369
                // If there isn't enough local bandwidth, there is no point in
370
                // splitting. It won't be possible to create a complete set in
371
                // any case, but the sent out partial payments would be held by
372
                // the receiver until the mpp timeout.
373
                case err == errInsufficientBalance:
3✔
374
                        p.log.Debug("not splitting because local balance " +
3✔
375
                                "is insufficient")
3✔
376

3✔
377
                        return nil, err
3✔
378

379
                case err != nil:
×
380
                        return nil, err
×
381
                }
382

383
                // With the next candidate path found, we'll attempt to turn
384
                // this into a route by applying the time-lock and fee
385
                // requirements.
386
                route, err := newRoute(
3✔
387
                        sourceVertex, path, height,
3✔
388
                        finalHopParams{
3✔
389
                                amt:         maxAmt,
3✔
390
                                totalAmt:    p.payment.Amount,
3✔
391
                                cltvDelta:   finalCltvDelta,
3✔
392
                                records:     p.payment.DestCustomRecords,
3✔
393
                                paymentAddr: p.payment.PaymentAddr,
3✔
394
                                metadata:    p.payment.Metadata,
3✔
395
                        }, nil,
3✔
396
                )
3✔
397
                if err != nil {
3✔
398
                        return nil, err
×
399
                }
×
400

401
                return route, err
3✔
402
        }
403
}
404

405
// UpdateAdditionalEdge updates the channel edge policy for a private edge. It
406
// validates the message signature and checks it's up to date, then applies the
407
// updates to the supplied policy. It returns a boolean to indicate whether
408
// there's an error when applying the updates.
409
func (p *paymentSession) UpdateAdditionalEdge(msg *lnwire.ChannelUpdate,
410
        pubKey *btcec.PublicKey, policy *models.CachedEdgePolicy) bool {
3✔
411

3✔
412
        // Validate the message signature.
3✔
413
        if err := VerifyChannelUpdateSignature(msg, pubKey); err != nil {
3✔
414
                log.Errorf(
×
415
                        "Unable to validate channel update signature: %v", err,
×
416
                )
×
417
                return false
×
418
        }
×
419

420
        // Update channel policy for the additional edge.
421
        policy.TimeLockDelta = msg.TimeLockDelta
3✔
422
        policy.FeeBaseMSat = lnwire.MilliSatoshi(msg.BaseFee)
3✔
423
        policy.FeeProportionalMillionths = lnwire.MilliSatoshi(msg.FeeRate)
3✔
424

3✔
425
        log.Debugf("New private channel update applied: %v",
3✔
426
                newLogClosure(func() string { return spew.Sdump(msg) }))
6✔
427

428
        return true
3✔
429
}
430

431
// GetAdditionalEdgePolicy uses the public key and channel ID to query the
432
// ephemeral channel edge policy for additional edges. Returns a nil if nothing
433
// found.
434
func (p *paymentSession) GetAdditionalEdgePolicy(pubKey *btcec.PublicKey,
435
        channelID uint64) *models.CachedEdgePolicy {
3✔
436

3✔
437
        target := route.NewVertex(pubKey)
3✔
438

3✔
439
        edges, ok := p.additionalEdges[target]
3✔
440
        if !ok {
6✔
441
                return nil
3✔
442
        }
3✔
443

444
        for _, edge := range edges {
6✔
445
                policy := edge.EdgePolicy()
3✔
446
                if policy.ChannelID != channelID {
3✔
447
                        continue
×
448
                }
449

450
                return policy
3✔
451
        }
452

453
        return nil
×
454
}
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