• 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

88.07
/routing/payment_lifecycle.go
1
package routing
2

3
import (
4
        "context"
5
        "errors"
6
        "fmt"
7
        "time"
8

9
        "github.com/btcsuite/btcd/btcec/v2"
10
        "github.com/davecgh/go-spew/spew"
11
        sphinx "github.com/lightningnetwork/lightning-onion"
12
        "github.com/lightningnetwork/lnd/channeldb"
13
        "github.com/lightningnetwork/lnd/fn/v2"
14
        "github.com/lightningnetwork/lnd/graph/db/models"
15
        "github.com/lightningnetwork/lnd/htlcswitch"
16
        "github.com/lightningnetwork/lnd/lntypes"
17
        "github.com/lightningnetwork/lnd/lnwire"
18
        "github.com/lightningnetwork/lnd/routing/route"
19
        "github.com/lightningnetwork/lnd/routing/shards"
20
        "github.com/lightningnetwork/lnd/tlv"
21
)
22

23
// ErrPaymentLifecycleExiting is used when waiting for htlc attempt result, but
24
// the payment lifecycle is exiting .
25
var ErrPaymentLifecycleExiting = errors.New("payment lifecycle exiting")
26

27
// paymentLifecycle holds all information about the current state of a payment
28
// needed to resume if from any point.
29
type paymentLifecycle struct {
30
        router                *ChannelRouter
31
        feeLimit              lnwire.MilliSatoshi
32
        identifier            lntypes.Hash
33
        paySession            PaymentSession
34
        shardTracker          shards.ShardTracker
35
        currentHeight         int32
36
        firstHopCustomRecords lnwire.CustomRecords
37

38
        // quit is closed to signal the sub goroutines of the payment lifecycle
39
        // to stop.
40
        quit chan struct{}
41

42
        // resultCollected is used to signal that the result of an attempt has
43
        // been collected. A nil error means the attempt is either successful
44
        // or failed with temporary error. Otherwise, we should exit the
45
        // lifecycle loop as a terminal error has occurred.
46
        resultCollected chan error
47

48
        // resultCollector is a function that is used to collect the result of
49
        // an HTLC attempt, which is always mounted to `p.collectResultAsync`
50
        // except in unit test, where we use a much simpler resultCollector to
51
        // decouple the test flow for the payment lifecycle.
52
        resultCollector func(attempt *channeldb.HTLCAttempt)
53
}
54

55
// newPaymentLifecycle initiates a new payment lifecycle and returns it.
56
func newPaymentLifecycle(r *ChannelRouter, feeLimit lnwire.MilliSatoshi,
57
        identifier lntypes.Hash, paySession PaymentSession,
58
        shardTracker shards.ShardTracker, currentHeight int32,
59
        firstHopCustomRecords lnwire.CustomRecords) *paymentLifecycle {
40✔
60

40✔
61
        p := &paymentLifecycle{
40✔
62
                router:                r,
40✔
63
                feeLimit:              feeLimit,
40✔
64
                identifier:            identifier,
40✔
65
                paySession:            paySession,
40✔
66
                shardTracker:          shardTracker,
40✔
67
                currentHeight:         currentHeight,
40✔
68
                quit:                  make(chan struct{}),
40✔
69
                resultCollected:       make(chan error, 1),
40✔
70
                firstHopCustomRecords: firstHopCustomRecords,
40✔
71
        }
40✔
72

40✔
73
        // Mount the result collector.
40✔
74
        p.resultCollector = p.collectResultAsync
40✔
75

40✔
76
        return p
40✔
77
}
40✔
78

79
// calcFeeBudget returns the available fee to be used for sending HTLC
80
// attempts.
81
func (p *paymentLifecycle) calcFeeBudget(
82
        feesPaid lnwire.MilliSatoshi) lnwire.MilliSatoshi {
103✔
83

103✔
84
        budget := p.feeLimit
103✔
85

103✔
86
        // We'll subtract the used fee from our fee budget. In case of
103✔
87
        // overflow, we need to check whether feesPaid exceeds our budget
103✔
88
        // already.
103✔
89
        if feesPaid <= budget {
206✔
90
                budget -= feesPaid
103✔
91
        } else {
103✔
UNCOV
92
                budget = 0
×
UNCOV
93
        }
×
94

95
        return budget
103✔
96
}
97

98
// stateStep defines an action to be taken in our payment lifecycle. We either
99
// quit, continue, or exit the lifecycle, see details below.
100
type stateStep uint8
101

102
const (
103
        // stepSkip is used when we need to skip the current lifecycle and jump
104
        // to the next one.
105
        stepSkip stateStep = iota
106

107
        // stepProceed is used when we can proceed the current lifecycle.
108
        stepProceed
109

110
        // stepExit is used when we need to quit the current lifecycle.
111
        stepExit
112
)
113

114
// decideNextStep is used to determine the next step in the payment lifecycle.
115
func (p *paymentLifecycle) decideNextStep(
116
        payment DBMPPayment) (stateStep, error) {
74✔
117

74✔
118
        // Check whether we could make new HTLC attempts.
74✔
119
        allow, err := payment.AllowMoreAttempts()
74✔
120
        if err != nil {
76✔
121
                return stepExit, err
2✔
122
        }
2✔
123

124
        if !allow {
113✔
125
                // Check whether we need to wait for results.
41✔
126
                wait, err := payment.NeedWaitAttempts()
41✔
127
                if err != nil {
42✔
128
                        return stepExit, err
1✔
129
                }
1✔
130

131
                // If we are not allowed to make new HTLC attempts and there's
132
                // no need to wait, the lifecycle is done and we can exit.
133
                if !wait {
57✔
134
                        return stepExit, nil
17✔
135
                }
17✔
136

137
                log.Tracef("Waiting for attempt results for payment %v",
23✔
138
                        p.identifier)
23✔
139

23✔
140
                // Otherwise we wait for one HTLC attempt then continue
23✔
141
                // the lifecycle.
23✔
142
                //
23✔
143
                // NOTE: we don't check `p.quit` since `decideNextStep` is
23✔
144
                // running in the same goroutine as `resumePayment`.
23✔
145
                select {
23✔
146
                case err := <-p.resultCollected:
22✔
147
                        // If an error is returned, exit with it.
22✔
148
                        if err != nil {
22✔
UNCOV
149
                                return stepExit, err
×
UNCOV
150
                        }
×
151

152
                        log.Tracef("Received attempt result for payment %v",
22✔
153
                                p.identifier)
22✔
154

155
                case <-p.router.quit:
1✔
156
                        return stepExit, ErrRouterShuttingDown
1✔
157
                }
158

159
                return stepSkip, nil
22✔
160
        }
161

162
        // Otherwise we need to make more attempts.
163
        return stepProceed, nil
31✔
164
}
165

166
// resumePayment resumes the paymentLifecycle from the current state.
167
func (p *paymentLifecycle) resumePayment(ctx context.Context) ([32]byte,
168
        *route.Route, error) {
22✔
169

22✔
170
        // When the payment lifecycle loop exits, we make sure to signal any
22✔
171
        // sub goroutine of the HTLC attempt to exit, then wait for them to
22✔
172
        // return.
22✔
173
        defer p.stop()
22✔
174

22✔
175
        // If we had any existing attempts outstanding, we'll start by spinning
22✔
176
        // up goroutines that'll collect their results and deliver them to the
22✔
177
        // lifecycle loop below.
22✔
178
        payment, err := p.router.cfg.Control.FetchPayment(p.identifier)
22✔
179
        if err != nil {
23✔
180
                return [32]byte{}, nil, err
1✔
181
        }
1✔
182

183
        for _, a := range payment.InFlightHTLCs() {
21✔
UNCOV
184
                a := a
×
UNCOV
185

×
UNCOV
186
                log.Infof("Resuming HTLC attempt %v for payment %v",
×
UNCOV
187
                        a.AttemptID, p.identifier)
×
UNCOV
188

×
UNCOV
189
                p.resultCollector(&a)
×
UNCOV
190
        }
×
191

192
        // Get the payment status.
193
        status := payment.GetStatus()
21✔
194

21✔
195
        // exitWithErr is a helper closure that logs and returns an error.
21✔
196
        exitWithErr := func(err error) ([32]byte, *route.Route, error) {
26✔
197
                // Log an error with the latest payment status.
5✔
198
                //
5✔
199
                // NOTE: this `status` variable is reassigned in the loop
5✔
200
                // below. We could also call `payment.GetStatus` here, but in a
5✔
201
                // rare case when the critical log is triggered when using
5✔
202
                // postgres as db backend, the `payment` could be nil, causing
5✔
203
                // the payment fetching to return an error.
5✔
204
                log.Errorf("Payment %v with status=%v failed: %v", p.identifier,
5✔
205
                        status, err)
5✔
206

5✔
207
                return [32]byte{}, nil, err
5✔
208
        }
5✔
209

210
        // We'll continue until either our payment succeeds, or we encounter a
211
        // critical error during path finding.
212
lifecycle:
21✔
213
        for {
90✔
214
                // We update the payment state on every iteration. Since the
69✔
215
                // payment state is affected by multiple goroutines (ie,
69✔
216
                // collectResultAsync), it is NOT guaranteed that we always
69✔
217
                // have the latest state here. This is fine as long as the
69✔
218
                // state is consistent as a whole.
69✔
219
                payment, err = p.router.cfg.Control.FetchPayment(p.identifier)
69✔
220
                if err != nil {
69✔
221
                        return exitWithErr(err)
×
222
                }
×
223

224
                ps := payment.GetState()
69✔
225
                remainingFees := p.calcFeeBudget(ps.FeesPaid)
69✔
226

69✔
227
                status = payment.GetStatus()
69✔
228
                log.Debugf("Payment %v: status=%v, active_shards=%v, "+
69✔
229
                        "rem_value=%v, fee_limit=%v", p.identifier, status,
69✔
230
                        ps.NumAttemptsInFlight, ps.RemainingAmt, remainingFees)
69✔
231

69✔
232
                // We now proceed our lifecycle with the following tasks in
69✔
233
                // order,
69✔
234
                //   1. check context.
69✔
235
                //   2. request route.
69✔
236
                //   3. create HTLC attempt.
69✔
237
                //   4. send HTLC attempt.
69✔
238
                //   5. collect HTLC attempt result.
69✔
239
                //
69✔
240
                // Before we attempt any new shard, we'll check to see if we've
69✔
241
                // gone past the payment attempt timeout, or if the context was
69✔
242
                // cancelled, or the router is exiting. In any of these cases,
69✔
243
                // we'll stop this payment attempt short.
69✔
244
                if err := p.checkContext(ctx); err != nil {
70✔
245
                        return exitWithErr(err)
1✔
246
                }
1✔
247

248
                // Now decide the next step of the current lifecycle.
249
                step, err := p.decideNextStep(payment)
68✔
250
                if err != nil {
69✔
251
                        return exitWithErr(err)
1✔
252
                }
1✔
253

254
                switch step {
67✔
255
                // Exit the for loop and return below.
256
                case stepExit:
16✔
257
                        break lifecycle
16✔
258

259
                // Continue the for loop and skip the rest.
260
                case stepSkip:
21✔
261
                        continue lifecycle
21✔
262

263
                // Continue the for loop and proceed the rest.
264
                case stepProceed:
30✔
265

266
                // Unknown step received, exit with an error.
267
                default:
×
268
                        err = fmt.Errorf("unknown step: %v", step)
×
269
                        return exitWithErr(err)
×
270
                }
271

272
                // Now request a route to be used to create our HTLC attempt.
273
                rt, err := p.requestRoute(ps)
30✔
274
                if err != nil {
31✔
275
                        return exitWithErr(err)
1✔
276
                }
1✔
277

278
                // We may not be able to find a route for current attempt. In
279
                // that case, we continue the loop and move straight to the
280
                // next iteration in case there are results for inflight HTLCs
281
                // that still need to be collected.
282
                if rt == nil {
31✔
283
                        log.Errorf("No route found for payment %v",
2✔
284
                                p.identifier)
2✔
285

2✔
286
                        continue lifecycle
2✔
287
                }
288

289
                log.Tracef("Found route: %s", spew.Sdump(rt.Hops))
27✔
290

27✔
291
                // Allow the traffic shaper to add custom records to the
27✔
292
                // outgoing HTLC and also adjust the amount if needed.
27✔
293
                err = p.amendFirstHopData(rt)
27✔
294
                if err != nil {
27✔
295
                        return exitWithErr(err)
×
296
                }
×
297

298
                // We found a route to try, create a new HTLC attempt to try.
299
                attempt, err := p.registerAttempt(rt, ps.RemainingAmt)
27✔
300
                if err != nil {
28✔
301
                        return exitWithErr(err)
1✔
302
                }
1✔
303

304
                // Once the attempt is created, send it to the htlcswitch.
305
                result, err := p.sendAttempt(attempt)
26✔
306
                if err != nil {
27✔
307
                        return exitWithErr(err)
1✔
308
                }
1✔
309

310
                // Now that the shard was successfully sent, launch a go
311
                // routine that will handle its result when its back.
312
                if result.err == nil {
49✔
313
                        p.resultCollector(attempt)
24✔
314
                }
24✔
315
        }
316

317
        // Once we are out the lifecycle loop, it means we've reached a
318
        // terminal condition. We either return the settled preimage or the
319
        // payment's failure reason.
320
        //
321
        // Optionally delete the failed attempts from the database.
322
        err = p.router.cfg.Control.DeleteFailedAttempts(p.identifier)
16✔
323
        if err != nil {
16✔
324
                log.Errorf("Error deleting failed htlc attempts for payment "+
×
325
                        "%v: %v", p.identifier, err)
×
326
        }
×
327

328
        htlc, failure := payment.TerminalInfo()
16✔
329
        if htlc != nil {
28✔
330
                return htlc.Settle.Preimage, &htlc.Route, nil
12✔
331
        }
12✔
332

333
        // Otherwise return the payment failure reason.
334
        return [32]byte{}, nil, *failure
4✔
335
}
336

337
// checkContext checks whether the payment context has been canceled.
338
// Cancellation occurs manually or if the context times out.
339
func (p *paymentLifecycle) checkContext(ctx context.Context) error {
72✔
340
        select {
72✔
341
        case <-ctx.Done():
4✔
342
                // If the context was canceled, we'll mark the payment as
4✔
343
                // failed. There are two cases to distinguish here: Either a
4✔
344
                // user-provided timeout was reached, or the context was
4✔
345
                // canceled, either to a manual cancellation or due to an
4✔
346
                // unknown error.
4✔
347
                var reason channeldb.FailureReason
4✔
348
                if errors.Is(ctx.Err(), context.DeadlineExceeded) {
7✔
349
                        reason = channeldb.FailureReasonTimeout
3✔
350
                        log.Warnf("Payment attempt not completed before "+
3✔
351
                                "context timeout, id=%s", p.identifier.String())
3✔
352
                } else {
4✔
353
                        reason = channeldb.FailureReasonCanceled
1✔
354
                        log.Warnf("Payment attempt context canceled, id=%s",
1✔
355
                                p.identifier.String())
1✔
356
                }
1✔
357

358
                // By marking the payment failed, depending on whether it has
359
                // inflight HTLCs or not, its status will now either be
360
                // `StatusInflight` or `StatusFailed`. In either case, no more
361
                // HTLCs will be attempted.
362
                err := p.router.cfg.Control.FailPayment(p.identifier, reason)
4✔
363
                if err != nil {
5✔
364
                        return fmt.Errorf("FailPayment got %w", err)
1✔
365
                }
1✔
366

367
        case <-p.router.quit:
2✔
368
                return fmt.Errorf("check payment timeout got: %w",
2✔
369
                        ErrRouterShuttingDown)
2✔
370

371
        // Fall through if we haven't hit our time limit.
372
        default:
66✔
373
        }
374

375
        return nil
69✔
376
}
377

378
// requestRoute is responsible for finding a route to be used to create an HTLC
379
// attempt.
380
func (p *paymentLifecycle) requestRoute(
381
        ps *channeldb.MPPaymentState) (*route.Route, error) {
34✔
382

34✔
383
        remainingFees := p.calcFeeBudget(ps.FeesPaid)
34✔
384

34✔
385
        // Query our payment session to construct a route.
34✔
386
        rt, err := p.paySession.RequestRoute(
34✔
387
                ps.RemainingAmt, remainingFees,
34✔
388
                uint32(ps.NumAttemptsInFlight), uint32(p.currentHeight),
34✔
389
                p.firstHopCustomRecords,
34✔
390
        )
34✔
391

34✔
392
        // Exit early if there's no error.
34✔
393
        if err == nil {
62✔
394
                return rt, nil
28✔
395
        }
28✔
396

397
        // Otherwise we need to handle the error.
398
        log.Warnf("Failed to find route for payment %v: %v", p.identifier, err)
6✔
399

6✔
400
        // If the error belongs to `noRouteError` set, it means a non-critical
6✔
401
        // error has happened during path finding, and we will mark the payment
6✔
402
        // failed with this reason. Otherwise, we'll return the critical error
6✔
403
        // found to abort the lifecycle.
6✔
404
        var routeErr noRouteError
6✔
405
        if !errors.As(err, &routeErr) {
8✔
406
                return nil, fmt.Errorf("requestRoute got: %w", err)
2✔
407
        }
2✔
408

409
        // It's the `paymentSession`'s responsibility to find a route for us
410
        // with the best effort. When it cannot find a path, we need to treat it
411
        // as a terminal condition and fail the payment no matter it has
412
        // inflight HTLCs or not.
413
        failureCode := routeErr.FailureReason()
4✔
414
        log.Warnf("Marking payment %v permanently failed with no route: %v",
4✔
415
                p.identifier, failureCode)
4✔
416

4✔
417
        err = p.router.cfg.Control.FailPayment(p.identifier, failureCode)
4✔
418
        if err != nil {
5✔
419
                return nil, fmt.Errorf("FailPayment got: %w", err)
1✔
420
        }
1✔
421

422
        // NOTE: we decide to not return the non-critical noRouteError here to
423
        // avoid terminating the payment lifecycle as there might be other
424
        // inflight HTLCs which we must wait for their results.
425
        return nil, nil
3✔
426
}
427

428
// stop signals any active shard goroutine to exit.
429
func (p *paymentLifecycle) stop() {
23✔
430
        close(p.quit)
23✔
431
}
23✔
432

433
// attemptResult holds the HTLC attempt and a possible error returned from
434
// sending it.
435
type attemptResult struct {
436
        // err is non-nil if a non-critical error was encountered when trying
437
        // to send the attempt, and we successfully updated the control tower
438
        // to reflect this error. This can be errors like not enough local
439
        // balance for the given route etc.
440
        err error
441

442
        // attempt is the attempt structure as recorded in the database.
443
        attempt *channeldb.HTLCAttempt
444
}
445

446
// collectResultAsync launches a goroutine that will wait for the result of the
447
// given HTLC attempt to be available then handle its result. Once received, it
448
// will send a nil error to channel `resultCollected` to indicate there's a
449
// result.
450
func (p *paymentLifecycle) collectResultAsync(attempt *channeldb.HTLCAttempt) {
22✔
451
        log.Debugf("Collecting result for attempt %v in payment %v",
22✔
452
                attempt.AttemptID, p.identifier)
22✔
453

22✔
454
        go func() {
44✔
455
                // Block until the result is available.
22✔
456
                _, err := p.collectResult(attempt)
22✔
457
                if err != nil {
22✔
UNCOV
458
                        log.Errorf("Error collecting result for attempt %v "+
×
UNCOV
459
                                "in payment %v: %v", attempt.AttemptID,
×
UNCOV
460
                                p.identifier, err)
×
UNCOV
461
                }
×
462

463
                log.Debugf("Result collected for attempt %v in payment %v",
22✔
464
                        attempt.AttemptID, p.identifier)
22✔
465

22✔
466
                // Once the result is collected, we signal it by writing the
22✔
467
                // error to `resultCollected`.
22✔
468
                select {
22✔
469
                // Send the signal or quit.
470
                case p.resultCollected <- err:
22✔
471

UNCOV
472
                case <-p.quit:
×
UNCOV
473
                        log.Debugf("Lifecycle exiting while collecting "+
×
UNCOV
474
                                "result for payment %v", p.identifier)
×
475

UNCOV
476
                case <-p.router.quit:
×
UNCOV
477
                        return
×
478
                }
479
        }()
480
}
481

482
// collectResult waits for the result for the given attempt to be available
483
// from the Switch, then records the attempt outcome with the control tower.
484
// An attemptResult is returned, indicating the final outcome of this HTLC
485
// attempt.
486
func (p *paymentLifecycle) collectResult(attempt *channeldb.HTLCAttempt) (
487
        *attemptResult, error) {
34✔
488

34✔
489
        log.Tracef("Collecting result for attempt %v", spew.Sdump(attempt))
34✔
490

34✔
491
        // We'll retrieve the hash specific to this shard from the
34✔
492
        // shardTracker, since it will be needed to regenerate the circuit
34✔
493
        // below.
34✔
494
        hash, err := p.shardTracker.GetHash(attempt.AttemptID)
34✔
495
        if err != nil {
34✔
496
                return p.failAttempt(attempt.AttemptID, err)
×
497
        }
×
498

499
        // Regenerate the circuit for this attempt.
500
        _, circuit, err := generateSphinxPacket(
34✔
501
                &attempt.Route, hash[:], attempt.SessionKey(),
34✔
502
        )
34✔
503
        // TODO(yy): We generate this circuit to create the error decryptor,
34✔
504
        // which is then used in htlcswitch as the deobfuscator to decode the
34✔
505
        // error from `UpdateFailHTLC`. However, suppose it's an
34✔
506
        // `UpdateFulfillHTLC` message yet for some reason the sphinx packet is
34✔
507
        // failed to be generated, we'd miss settling it. This means we should
34✔
508
        // give it a second chance to try the settlement path in case
34✔
509
        // `GetAttemptResult` gives us back the preimage. And move the circuit
34✔
510
        // creation into htlcswitch so it's only constructed when there's a
34✔
511
        // failure message we need to decode.
34✔
512
        if err != nil {
34✔
513
                log.Debugf("Unable to generate circuit for attempt %v: %v",
×
514
                        attempt.AttemptID, err)
×
515

×
516
                return p.failAttempt(attempt.AttemptID, err)
×
517
        }
×
518

519
        // Using the created circuit, initialize the error decrypter, so we can
520
        // parse+decode any failures incurred by this payment within the
521
        // switch.
522
        errorDecryptor := &htlcswitch.SphinxErrorDecrypter{
34✔
523
                OnionErrorDecrypter: sphinx.NewOnionErrorDecrypter(circuit),
34✔
524
        }
34✔
525

34✔
526
        // Now ask the switch to return the result of the payment when
34✔
527
        // available.
34✔
528
        //
34✔
529
        // TODO(yy): consider using htlcswitch to create the `errorDecryptor`
34✔
530
        // since the htlc is already in db. This will also make the interface
34✔
531
        // `PaymentAttemptDispatcher` deeper and easier to use. Moreover, we'd
34✔
532
        // only create the decryptor when received a failure, further saving us
34✔
533
        // a few CPU cycles.
34✔
534
        resultChan, err := p.router.cfg.Payer.GetAttemptResult(
34✔
535
                attempt.AttemptID, p.identifier, errorDecryptor,
34✔
536
        )
34✔
537
        // Handle the switch error.
34✔
538
        if err != nil {
35✔
539
                log.Errorf("Failed getting result for attemptID %d "+
1✔
540
                        "from switch: %v", attempt.AttemptID, err)
1✔
541

1✔
542
                return p.handleSwitchErr(attempt, err)
1✔
543
        }
1✔
544

545
        // The switch knows about this payment, we'll wait for a result to be
546
        // available.
547
        var (
33✔
548
                result *htlcswitch.PaymentResult
33✔
549
                ok     bool
33✔
550
        )
33✔
551

33✔
552
        select {
33✔
553
        case result, ok = <-resultChan:
31✔
554
                if !ok {
32✔
555
                        return nil, htlcswitch.ErrSwitchExiting
1✔
556
                }
1✔
557

558
        case <-p.quit:
1✔
559
                return nil, ErrPaymentLifecycleExiting
1✔
560

561
        case <-p.router.quit:
1✔
562
                return nil, ErrRouterShuttingDown
1✔
563
        }
564

565
        // In case of a payment failure, fail the attempt with the control
566
        // tower and return.
567
        if result.Error != nil {
46✔
568
                return p.handleSwitchErr(attempt, result.Error)
16✔
569
        }
16✔
570

571
        // We successfully got a payment result back from the switch.
572
        log.Debugf("Payment %v succeeded with pid=%v",
14✔
573
                p.identifier, attempt.AttemptID)
14✔
574

14✔
575
        // Report success to mission control.
14✔
576
        err = p.router.cfg.MissionControl.ReportPaymentSuccess(
14✔
577
                attempt.AttemptID, &attempt.Route,
14✔
578
        )
14✔
579
        if err != nil {
14✔
580
                log.Errorf("Error reporting payment success to mc: %v", err)
×
581
        }
×
582

583
        // In case of success we atomically store settle result to the DB move
584
        // the shard to the settled state.
585
        htlcAttempt, err := p.router.cfg.Control.SettleAttempt(
14✔
586
                p.identifier, attempt.AttemptID,
14✔
587
                &channeldb.HTLCSettleInfo{
14✔
588
                        Preimage:   result.Preimage,
14✔
589
                        SettleTime: p.router.cfg.Clock.Now(),
14✔
590
                },
14✔
591
        )
14✔
592
        if err != nil {
15✔
593
                log.Errorf("Error settling attempt %v for payment %v with "+
1✔
594
                        "preimage %v: %v", attempt.AttemptID, p.identifier,
1✔
595
                        result.Preimage, err)
1✔
596

1✔
597
                // We won't mark the attempt as failed since we already have
1✔
598
                // the preimage.
1✔
599
                return nil, err
1✔
600
        }
1✔
601

602
        return &attemptResult{
13✔
603
                attempt: htlcAttempt,
13✔
604
        }, nil
13✔
605
}
606

607
// registerAttempt is responsible for creating and saving an HTLC attempt in db
608
// by using the route info provided. The `remainingAmt` is used to decide
609
// whether this is the last attempt.
610
func (p *paymentLifecycle) registerAttempt(rt *route.Route,
611
        remainingAmt lnwire.MilliSatoshi) (*channeldb.HTLCAttempt, error) {
36✔
612

36✔
613
        // If this route will consume the last remaining amount to send
36✔
614
        // to the receiver, this will be our last shard (for now).
36✔
615
        isLastAttempt := rt.ReceiverAmt() == remainingAmt
36✔
616

36✔
617
        // Using the route received from the payment session, create a new
36✔
618
        // shard to send.
36✔
619
        attempt, err := p.createNewPaymentAttempt(rt, isLastAttempt)
36✔
620
        if err != nil {
37✔
621
                return nil, err
1✔
622
        }
1✔
623

624
        // Before sending this HTLC to the switch, we checkpoint the fresh
625
        // paymentID and route to the DB. This lets us know on startup the ID
626
        // of the payment that we attempted to send, such that we can query the
627
        // Switch for its whereabouts. The route is needed to handle the result
628
        // when it eventually comes back.
629
        err = p.router.cfg.Control.RegisterAttempt(
35✔
630
                p.identifier, &attempt.HTLCAttemptInfo,
35✔
631
        )
35✔
632

35✔
633
        return attempt, err
35✔
634
}
635

636
// createNewPaymentAttempt creates a new payment attempt from the given route.
637
func (p *paymentLifecycle) createNewPaymentAttempt(rt *route.Route,
638
        lastShard bool) (*channeldb.HTLCAttempt, error) {
36✔
639

36✔
640
        // Generate a new key to be used for this attempt.
36✔
641
        sessionKey, err := generateNewSessionKey()
36✔
642
        if err != nil {
36✔
643
                return nil, err
×
644
        }
×
645

646
        // We generate a new, unique payment ID that we will use for
647
        // this HTLC.
648
        attemptID, err := p.router.cfg.NextPaymentID()
36✔
649
        if err != nil {
36✔
650
                return nil, err
×
651
        }
×
652

653
        // Request a new shard from the ShardTracker. If this is an AMP
654
        // payment, and this is the last shard, the outstanding shards together
655
        // with this one will be enough for the receiver to derive all HTLC
656
        // preimages. If this a non-AMP payment, the ShardTracker will return a
657
        // simple shard with the payment's static payment hash.
658
        shard, err := p.shardTracker.NewShard(attemptID, lastShard)
36✔
659
        if err != nil {
37✔
660
                return nil, err
1✔
661
        }
1✔
662

663
        // If this shard carries MPP or AMP options, add them to the last hop
664
        // on the route.
665
        hop := rt.Hops[len(rt.Hops)-1]
35✔
666
        if shard.MPP() != nil {
39✔
667
                hop.MPP = shard.MPP()
4✔
668
        }
4✔
669

670
        if shard.AMP() != nil {
35✔
UNCOV
671
                hop.AMP = shard.AMP()
×
UNCOV
672
        }
×
673

674
        hash := shard.Hash()
35✔
675

35✔
676
        // We now have all the information needed to populate the current
35✔
677
        // attempt information.
35✔
678
        attempt := channeldb.NewHtlcAttempt(
35✔
679
                attemptID, sessionKey, *rt, p.router.cfg.Clock.Now(), &hash,
35✔
680
        )
35✔
681

35✔
682
        return attempt, nil
35✔
683
}
684

685
// sendAttempt attempts to send the current attempt to the switch to complete
686
// the payment. If this attempt fails, then we'll continue on to the next
687
// available route.
688
func (p *paymentLifecycle) sendAttempt(
689
        attempt *channeldb.HTLCAttempt) (*attemptResult, error) {
35✔
690

35✔
691
        log.Debugf("Sending HTLC attempt(id=%v, total_amt=%v, first_hop_amt=%d"+
35✔
692
                ") for payment %v", attempt.AttemptID,
35✔
693
                attempt.Route.TotalAmount, attempt.Route.FirstHopAmount.Val,
35✔
694
                p.identifier)
35✔
695

35✔
696
        rt := attempt.Route
35✔
697

35✔
698
        // Construct the first hop.
35✔
699
        firstHop := lnwire.NewShortChanIDFromInt(rt.Hops[0].ChannelID)
35✔
700

35✔
701
        // Craft an HTLC packet to send to the htlcswitch. The metadata within
35✔
702
        // this packet will be used to route the payment through the network,
35✔
703
        // starting with the first-hop.
35✔
704
        htlcAdd := &lnwire.UpdateAddHTLC{
35✔
705
                Amount:        rt.FirstHopAmount.Val.Int(),
35✔
706
                Expiry:        rt.TotalTimeLock,
35✔
707
                PaymentHash:   *attempt.Hash,
35✔
708
                CustomRecords: rt.FirstHopWireCustomRecords,
35✔
709
        }
35✔
710

35✔
711
        // Generate the raw encoded sphinx packet to be included along
35✔
712
        // with the htlcAdd message that we send directly to the
35✔
713
        // switch.
35✔
714
        onionBlob, _, err := generateSphinxPacket(
35✔
715
                &rt, attempt.Hash[:], attempt.SessionKey(),
35✔
716
        )
35✔
717
        if err != nil {
36✔
718
                log.Errorf("Failed to create onion blob: attempt=%d in "+
1✔
719
                        "payment=%v, err:%v", attempt.AttemptID,
1✔
720
                        p.identifier, err)
1✔
721

1✔
722
                return p.failAttempt(attempt.AttemptID, err)
1✔
723
        }
1✔
724

725
        copy(htlcAdd.OnionBlob[:], onionBlob)
34✔
726

34✔
727
        // Send it to the Switch. When this method returns we assume
34✔
728
        // the Switch successfully has persisted the payment attempt,
34✔
729
        // such that we can resume waiting for the result after a
34✔
730
        // restart.
34✔
731
        err = p.router.cfg.Payer.SendHTLC(firstHop, attempt.AttemptID, htlcAdd)
34✔
732
        if err != nil {
39✔
733
                log.Errorf("Failed sending attempt %d for payment %v to "+
5✔
734
                        "switch: %v", attempt.AttemptID, p.identifier, err)
5✔
735

5✔
736
                return p.handleSwitchErr(attempt, err)
5✔
737
        }
5✔
738

739
        log.Debugf("Attempt %v for payment %v successfully sent to switch, "+
29✔
740
                "route: %v", attempt.AttemptID, p.identifier, &attempt.Route)
29✔
741

29✔
742
        return &attemptResult{
29✔
743
                attempt: attempt,
29✔
744
        }, nil
29✔
745
}
746

747
// amendFirstHopData is a function that calls the traffic shaper to allow it to
748
// add custom records to the outgoing HTLC and also adjust the amount if
749
// needed.
750
func (p *paymentLifecycle) amendFirstHopData(rt *route.Route) error {
36✔
751
        // The first hop amount on the route is the full route amount if not
36✔
752
        // overwritten by the traffic shaper. So we set the initial value now
36✔
753
        // and potentially overwrite it later.
36✔
754
        rt.FirstHopAmount = tlv.NewRecordT[tlv.TlvType0](
36✔
755
                tlv.NewBigSizeT(rt.TotalAmount),
36✔
756
        )
36✔
757

36✔
758
        // By default, we set the first hop custom records to the initial
36✔
759
        // value requested by the RPC. The traffic shaper may overwrite this
36✔
760
        // value.
36✔
761
        rt.FirstHopWireCustomRecords = p.firstHopCustomRecords
36✔
762

36✔
763
        // extraDataRequest is a helper struct to pass the custom records and
36✔
764
        // amount back from the traffic shaper.
36✔
765
        type extraDataRequest struct {
36✔
766
                customRecords fn.Option[lnwire.CustomRecords]
36✔
767

36✔
768
                amount fn.Option[lnwire.MilliSatoshi]
36✔
769
        }
36✔
770

36✔
771
        // If a hook exists that may affect our outgoing message, we call it now
36✔
772
        // and apply its side effects to the UpdateAddHTLC message.
36✔
773
        result, err := fn.MapOptionZ(
36✔
774
                p.router.cfg.TrafficShaper,
36✔
775
                //nolint:ll
36✔
776
                func(ts htlcswitch.AuxTrafficShaper) fn.Result[extraDataRequest] {
72✔
777
                        newAmt, newRecords, err := ts.ProduceHtlcExtraData(
36✔
778
                                rt.TotalAmount, p.firstHopCustomRecords,
36✔
779
                        )
36✔
780
                        if err != nil {
36✔
781
                                return fn.Err[extraDataRequest](err)
×
782
                        }
×
783

784
                        // Make sure we only received valid records.
785
                        if err := newRecords.Validate(); err != nil {
36✔
786
                                return fn.Err[extraDataRequest](err)
×
787
                        }
×
788

789
                        log.Debugf("Aux traffic shaper returned custom "+
36✔
790
                                "records %v and amount %d msat for HTLC",
36✔
791
                                spew.Sdump(newRecords), newAmt)
36✔
792

36✔
793
                        return fn.Ok(extraDataRequest{
36✔
794
                                customRecords: fn.Some(newRecords),
36✔
795
                                amount:        fn.Some(newAmt),
36✔
796
                        })
36✔
797
                },
798
        ).Unpack()
799
        if err != nil {
36✔
800
                return fmt.Errorf("traffic shaper failed to produce extra "+
×
801
                        "data: %w", err)
×
802
        }
×
803

804
        // Apply the side effects to the UpdateAddHTLC message.
805
        result.customRecords.WhenSome(func(records lnwire.CustomRecords) {
72✔
806
                rt.FirstHopWireCustomRecords = records
36✔
807
        })
36✔
808
        result.amount.WhenSome(func(amount lnwire.MilliSatoshi) {
72✔
809
                rt.FirstHopAmount = tlv.NewRecordT[tlv.TlvType0](
36✔
810
                        tlv.NewBigSizeT(amount),
36✔
811
                )
36✔
812
        })
36✔
813

814
        return nil
36✔
815
}
816

817
// failAttemptAndPayment fails both the payment and its attempt via the
818
// router's control tower, which marks the payment as failed in db.
819
func (p *paymentLifecycle) failPaymentAndAttempt(
820
        attemptID uint64, reason *channeldb.FailureReason,
821
        sendErr error) (*attemptResult, error) {
5✔
822

5✔
823
        log.Errorf("Payment %v failed: final_outcome=%v, raw_err=%v",
5✔
824
                p.identifier, *reason, sendErr)
5✔
825

5✔
826
        // Fail the payment via control tower.
5✔
827
        //
5✔
828
        // NOTE: we must fail the payment first before failing the attempt.
5✔
829
        // Otherwise, once the attempt is marked as failed, another goroutine
5✔
830
        // might make another attempt while we are failing the payment.
5✔
831
        err := p.router.cfg.Control.FailPayment(p.identifier, *reason)
5✔
832
        if err != nil {
5✔
833
                log.Errorf("Unable to fail payment: %v", err)
×
834
                return nil, err
×
835
        }
×
836

837
        // Fail the attempt.
838
        return p.failAttempt(attemptID, sendErr)
5✔
839
}
840

841
// handleSwitchErr inspects the given error from the Switch and determines
842
// whether we should make another payment attempt, or if it should be
843
// considered a terminal error. Terminal errors will be recorded with the
844
// control tower. It analyzes the sendErr for the payment attempt received from
845
// the switch and updates mission control and/or channel policies. Depending on
846
// the error type, the error is either the final outcome of the payment or we
847
// need to continue with an alternative route. A final outcome is indicated by
848
// a non-nil reason value.
849
func (p *paymentLifecycle) handleSwitchErr(attempt *channeldb.HTLCAttempt,
850
        sendErr error) (*attemptResult, error) {
22✔
851

22✔
852
        internalErrorReason := channeldb.FailureReasonError
22✔
853
        attemptID := attempt.AttemptID
22✔
854

22✔
855
        // reportAndFail is a helper closure that reports the failure to the
22✔
856
        // mission control, which helps us to decide whether we want to retry
22✔
857
        // the payment or not. If a non nil reason is returned from mission
22✔
858
        // control, it will further fail the payment via control tower.
22✔
859
        reportAndFail := func(srcIdx *int,
22✔
860
                msg lnwire.FailureMessage) (*attemptResult, error) {
41✔
861

19✔
862
                // Report outcome to mission control.
19✔
863
                reason, err := p.router.cfg.MissionControl.ReportPaymentFail(
19✔
864
                        attemptID, &attempt.Route, srcIdx, msg,
19✔
865
                )
19✔
866
                if err != nil {
19✔
867
                        log.Errorf("Error reporting payment result to mc: %v",
×
868
                                err)
×
869

×
870
                        reason = &internalErrorReason
×
871
                }
×
872

873
                // Fail the attempt only if there's no reason.
874
                if reason == nil {
36✔
875
                        // Fail the attempt.
17✔
876
                        return p.failAttempt(attemptID, sendErr)
17✔
877
                }
17✔
878

879
                // Otherwise fail both the payment and the attempt.
880
                return p.failPaymentAndAttempt(attemptID, reason, sendErr)
2✔
881
        }
882

883
        // If this attempt ID is unknown to the Switch, it means it was never
884
        // checkpointed and forwarded by the switch before a restart. In this
885
        // case we can safely send a new payment attempt, and wait for its
886
        // result to be available.
887
        if errors.Is(sendErr, htlcswitch.ErrPaymentIDNotFound) {
22✔
888
                log.Debugf("Attempt ID %v for payment %v not found in the "+
×
889
                        "Switch, retrying.", attempt.AttemptID, p.identifier)
×
890

×
891
                return p.failAttempt(attemptID, sendErr)
×
892
        }
×
893

894
        if errors.Is(sendErr, htlcswitch.ErrUnreadableFailureMessage) {
23✔
895
                log.Warn("Unreadable failure when sending htlc: id=%v, hash=%v",
1✔
896
                        attempt.AttemptID, attempt.Hash)
1✔
897

1✔
898
                // Since this error message cannot be decrypted, we will send a
1✔
899
                // nil error message to our mission controller and fail the
1✔
900
                // payment.
1✔
901
                return reportAndFail(nil, nil)
1✔
902
        }
1✔
903

904
        // If the error is a ClearTextError, we have received a valid wire
905
        // failure message, either from our own outgoing link or from a node
906
        // down the route. If the error is not related to the propagation of
907
        // our payment, we can stop trying because an internal error has
908
        // occurred.
909
        var rtErr htlcswitch.ClearTextError
21✔
910
        ok := errors.As(sendErr, &rtErr)
21✔
911
        if !ok {
24✔
912
                return p.failPaymentAndAttempt(
3✔
913
                        attemptID, &internalErrorReason, sendErr,
3✔
914
                )
3✔
915
        }
3✔
916

917
        // failureSourceIdx is the index of the node that the failure occurred
918
        // at. If the ClearTextError received is not a ForwardingError the
919
        // payment error occurred at our node, so we leave this value as 0
920
        // to indicate that the failure occurred locally. If the error is a
921
        // ForwardingError, it did not originate at our node, so we set
922
        // failureSourceIdx to the index of the node where the failure occurred.
923
        failureSourceIdx := 0
18✔
924
        var source *htlcswitch.ForwardingError
18✔
925
        ok = errors.As(rtErr, &source)
18✔
926
        if ok {
36✔
927
                failureSourceIdx = source.FailureSourceIdx
18✔
928
        }
18✔
929

930
        // Extract the wire failure and apply channel update if it contains one.
931
        // If we received an unknown failure message from a node along the
932
        // route, the failure message will be nil.
933
        failureMessage := rtErr.WireMessage()
18✔
934
        err := p.handleFailureMessage(
18✔
935
                &attempt.Route, failureSourceIdx, failureMessage,
18✔
936
        )
18✔
937
        if err != nil {
18✔
938
                return p.failPaymentAndAttempt(
×
939
                        attemptID, &internalErrorReason, sendErr,
×
940
                )
×
941
        }
×
942

943
        log.Tracef("Node=%v reported failure when sending htlc",
18✔
944
                failureSourceIdx)
18✔
945

18✔
946
        return reportAndFail(&failureSourceIdx, failureMessage)
18✔
947
}
948

949
// handleFailureMessage tries to apply a channel update present in the failure
950
// message if any.
951
func (p *paymentLifecycle) handleFailureMessage(rt *route.Route,
952
        errorSourceIdx int, failure lnwire.FailureMessage) error {
18✔
953

18✔
954
        if failure == nil {
19✔
955
                return nil
1✔
956
        }
1✔
957

958
        // It makes no sense to apply our own channel updates.
959
        if errorSourceIdx == 0 {
17✔
UNCOV
960
                log.Errorf("Channel update of ourselves received")
×
UNCOV
961

×
UNCOV
962
                return nil
×
UNCOV
963
        }
×
964

965
        // Extract channel update if the error contains one.
966
        update := p.router.extractChannelUpdate(failure)
17✔
967
        if update == nil {
26✔
968
                return nil
9✔
969
        }
9✔
970

971
        // Parse pubkey to allow validation of the channel update. This should
972
        // always succeed, otherwise there is something wrong in our
973
        // implementation. Therefore, return an error.
974
        errVertex := rt.Hops[errorSourceIdx-1].PubKeyBytes
8✔
975
        errSource, err := btcec.ParsePubKey(errVertex[:])
8✔
976
        if err != nil {
8✔
977
                log.Errorf("Cannot parse pubkey: idx=%v, pubkey=%v",
×
978
                        errorSourceIdx, errVertex)
×
979

×
980
                return err
×
981
        }
×
982

983
        var (
8✔
984
                isAdditionalEdge bool
8✔
985
                policy           *models.CachedEdgePolicy
8✔
986
        )
8✔
987

8✔
988
        // Before we apply the channel update, we need to decide whether the
8✔
989
        // update is for additional (ephemeral) edge or normal edge stored in
8✔
990
        // db.
8✔
991
        //
8✔
992
        // Note: the p.paySession might be nil here if it's called inside
8✔
993
        // SendToRoute where there's no payment lifecycle.
8✔
994
        if p.paySession != nil {
13✔
995
                policy = p.paySession.GetAdditionalEdgePolicy(
5✔
996
                        errSource, update.ShortChannelID.ToUint64(),
5✔
997
                )
5✔
998
                if policy != nil {
7✔
999
                        isAdditionalEdge = true
2✔
1000
                }
2✔
1001
        }
1002

1003
        // Apply channel update to additional edge policy.
1004
        if isAdditionalEdge {
10✔
1005
                if !p.paySession.UpdateAdditionalEdge(
2✔
1006
                        update, errSource, policy) {
2✔
1007

×
1008
                        log.Debugf("Invalid channel update received: node=%v",
×
1009
                                errVertex)
×
1010
                }
×
1011
                return nil
2✔
1012
        }
1013

1014
        // Apply channel update to the channel edge policy in our db.
1015
        if !p.router.cfg.ApplyChannelUpdate(update) {
8✔
1016
                log.Debugf("Invalid channel update received: node=%v",
2✔
1017
                        errVertex)
2✔
1018
        }
2✔
1019
        return nil
6✔
1020
}
1021

1022
// failAttempt calls control tower to fail the current payment attempt.
1023
func (p *paymentLifecycle) failAttempt(attemptID uint64,
1024
        sendError error) (*attemptResult, error) {
23✔
1025

23✔
1026
        log.Warnf("Attempt %v for payment %v failed: %v", attemptID,
23✔
1027
                p.identifier, sendError)
23✔
1028

23✔
1029
        failInfo := marshallError(
23✔
1030
                sendError,
23✔
1031
                p.router.cfg.Clock.Now(),
23✔
1032
        )
23✔
1033

23✔
1034
        // Now that we are failing this payment attempt, cancel the shard with
23✔
1035
        // the ShardTracker such that it can derive the correct hash for the
23✔
1036
        // next attempt.
23✔
1037
        if err := p.shardTracker.CancelShard(attemptID); err != nil {
23✔
1038
                return nil, err
×
1039
        }
×
1040

1041
        attempt, err := p.router.cfg.Control.FailAttempt(
23✔
1042
                p.identifier, attemptID, failInfo,
23✔
1043
        )
23✔
1044
        if err != nil {
26✔
1045
                return nil, err
3✔
1046
        }
3✔
1047

1048
        return &attemptResult{
20✔
1049
                attempt: attempt,
20✔
1050
                err:     sendError,
20✔
1051
        }, nil
20✔
1052
}
1053

1054
// marshallError marshall an error as received from the switch to a structure
1055
// that is suitable for database storage.
1056
func marshallError(sendError error, time time.Time) *channeldb.HTLCFailInfo {
23✔
1057
        response := &channeldb.HTLCFailInfo{
23✔
1058
                FailTime: time,
23✔
1059
        }
23✔
1060

23✔
1061
        switch {
23✔
1062
        case errors.Is(sendError, htlcswitch.ErrPaymentIDNotFound):
×
1063
                response.Reason = channeldb.HTLCFailInternal
×
1064
                return response
×
1065

1066
        case errors.Is(sendError, htlcswitch.ErrUnreadableFailureMessage):
1✔
1067
                response.Reason = channeldb.HTLCFailUnreadable
1✔
1068
                return response
1✔
1069
        }
1070

1071
        var rtErr htlcswitch.ClearTextError
22✔
1072
        ok := errors.As(sendError, &rtErr)
22✔
1073
        if !ok {
26✔
1074
                response.Reason = channeldb.HTLCFailInternal
4✔
1075
                return response
4✔
1076
        }
4✔
1077

1078
        message := rtErr.WireMessage()
18✔
1079
        if message != nil {
35✔
1080
                response.Reason = channeldb.HTLCFailMessage
17✔
1081
                response.Message = message
17✔
1082
        } else {
18✔
1083
                response.Reason = channeldb.HTLCFailUnknown
1✔
1084
        }
1✔
1085

1086
        // If the ClearTextError received is a ForwardingError, the error
1087
        // originated from a node along the route, not locally on our outgoing
1088
        // link. We set failureSourceIdx to the index of the node where the
1089
        // failure occurred. If the error is not a ForwardingError, the failure
1090
        // occurred at our node, so we leave the index as 0 to indicate that
1091
        // we failed locally.
1092
        var fErr *htlcswitch.ForwardingError
18✔
1093
        ok = errors.As(rtErr, &fErr)
18✔
1094
        if ok {
36✔
1095
                response.FailureSourceIndex = uint32(fErr.FailureSourceIdx)
18✔
1096
        }
18✔
1097

1098
        return response
18✔
1099
}
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