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

lightningnetwork / lnd / 13236757158

10 Feb 2025 08:39AM UTC coverage: 57.649% (-1.2%) from 58.815%
13236757158

Pull #9493

github

ziggie1984
lncli: for some cmds we don't replace the data of the response.

For some cmds it is not very practical to replace the json output
because we might pipe it into other commands. For example when
creating the route we want to pipe it into sendtoRoute.
Pull Request #9493: For some lncli cmds we should not replace the content with other data

0 of 9 new or added lines in 2 files covered. (0.0%)

19535 existing lines in 252 files now uncovered.

103517 of 179563 relevant lines covered (57.65%)

24878.49 hits per line

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

77.37
/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 {
6✔
25
                delta += BlockPadding
2✔
26
        }
2✔
27

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

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

UNCOV
88
        case errEmptyPaySession:
×
UNCOV
89
                return "empty payment session"
×
90

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

5✔
116
                return channeldb.FailureReasonNoRoute
5✔
117

UNCOV
118
        case errInsufficientBalance:
×
UNCOV
119
                return channeldb.FailureReasonInsufficientBalance
×
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) {
23✔
204

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

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

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

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

23✔
224
        return &paymentSession{
23✔
225
                selfNode:          selfNode,
23✔
226
                additionalEdges:   edges,
23✔
227
                getBandwidthHints: getBandwidthHints,
23✔
228
                payment:           p,
23✔
229
                pathFinder:        findPath,
23✔
230
                graphSessFactory:  graphSessFactory,
23✔
231
                pathFindingConfig: pathFindingConfig,
23✔
232
                missionControl:    missionControl,
23✔
233
                minShardAmt:       DefaultShardMinAmt,
23✔
234
                log:               log.WithPrefix(logPrefix),
23✔
235
        }, nil
23✔
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) {
63✔
250

63✔
251
        if p.empty {
63✔
UNCOV
252
                return nil, errEmptyPaySession
×
UNCOV
253
        }
×
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
63✔
258
        finalCltvDelta += BlockPadding
63✔
259

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

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

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

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

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

2✔
295
                maxAmt = *p.payment.MaxShardAmt
2✔
296
        }
2✔
297

298
        for {
136✔
299
                // Get a routing graph session.
73✔
300
                graph, closeGraph, err := p.graphSessFactory.NewGraphSession()
73✔
301
                if err != nil {
73✔
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)
73✔
312
                if err != nil {
73✔
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)
73✔
323

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

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

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

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

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

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

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

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

1✔
370
                                return nil, errNoPathFound
1✔
371
                        }
1✔
372

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

1✔
380
                                return nil, errNoPathFound
1✔
381
                        }
1✔
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
11✔
386

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

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

396
                        // Go pathfinding.
397
                        continue
10✔
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:
1✔
404
                        p.log.Debug("not splitting because local balance " +
1✔
405
                                "is insufficient")
1✔
406

1✔
407
                        return nil, err
1✔
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(
57✔
417
                        p.selfNode, path, height,
57✔
418
                        finalHopParams{
57✔
419
                                amt:         maxAmt,
57✔
420
                                totalAmt:    p.payment.Amount,
57✔
421
                                cltvDelta:   finalCltvDelta,
57✔
422
                                records:     p.payment.DestCustomRecords,
57✔
423
                                paymentAddr: p.payment.PaymentAddr,
57✔
424
                                metadata:    p.payment.Metadata,
57✔
425
                        }, p.payment.BlindedPathSet,
57✔
426
                )
57✔
427
                if err != nil {
57✔
428
                        return nil, err
×
429
                }
×
430

431
                return route, err
57✔
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 {
3✔
441

3✔
442
        // Validate the message signature.
3✔
443
        if err := netann.VerifyChannelUpdateSignature(msg, pubKey); err != nil {
3✔
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
3✔
452
        policy.FeeBaseMSat = lnwire.MilliSatoshi(msg.BaseFee)
3✔
453
        policy.FeeProportionalMillionths = lnwire.MilliSatoshi(msg.FeeRate)
3✔
454

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

3✔
458
        return true
3✔
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 {
5✔
466

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

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

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

480
                return policy
2✔
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