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

lightningnetwork / lnd / 16806404896

07 Aug 2025 01:49PM UTC coverage: 57.477% (-9.5%) from 66.947%
16806404896

Pull #10137

github

web-flow
Merge 8437f5b16 into 2269859d9
Pull Request #10137: Design Proposal Fixing Stuck Payments

54 of 77 new or added lines in 1 file covered. (70.13%)

28355 existing lines in 457 files now uncovered.

99069 of 172364 relevant lines covered (57.48%)

1.79 hits per line

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

75.93
/routing/payment_lifecycle.go
1
package routing
2

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

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

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

29
// switchResult is the result sent back from the switch after processing the
30
// HTLC.
31
type switchResult struct {
32
        // attempt is the HTLC sent to the switch.
33
        attempt *channeldb.HTLCAttempt
34

35
        // result is sent from the switch which contains either a preimage if
36
        // ths HTLC is settled or an error if it's failed.
37
        result *htlcswitch.PaymentResult
38
}
39

40
// paymentLifecycle holds all information about the current state of a payment
41
// needed to resume if from any point.
42
type paymentLifecycle struct {
43
        router                *ChannelRouter
44
        feeLimit              lnwire.MilliSatoshi
45
        identifier            lntypes.Hash
46
        paySession            PaymentSession
47
        shardTracker          shards.ShardTracker
48
        currentHeight         int32
49
        firstHopCustomRecords lnwire.CustomRecords
50

51
        // wg is used to wait for all result collectors to finish before the
52
        // payment lifecycle exits.
53
        wg sync.WaitGroup
54

55
        // quit is closed to signal the sub goroutines of the payment lifecycle
56
        // to stop.
57
        quit chan struct{}
58

59
        // resultCollected is used to send the result returned from the switch
60
        // for a given HTLC attempt.
61
        resultCollected chan *switchResult
62

63
        // resultCollector is a function that is used to collect the result of
64
        // an HTLC attempt, which is always mounted to `p.collectResultAsync`
65
        // except in unit test, where we use a much simpler resultCollector to
66
        // decouple the test flow for the payment lifecycle.
67
        resultCollector func(attempt *channeldb.HTLCAttempt)
68
}
69

70
// newPaymentLifecycle initiates a new payment lifecycle and returns it.
71
func newPaymentLifecycle(r *ChannelRouter, feeLimit lnwire.MilliSatoshi,
72
        identifier lntypes.Hash, paySession PaymentSession,
73
        shardTracker shards.ShardTracker, currentHeight int32,
74
        firstHopCustomRecords lnwire.CustomRecords) *paymentLifecycle {
3✔
75

3✔
76
        p := &paymentLifecycle{
3✔
77
                router:                r,
3✔
78
                feeLimit:              feeLimit,
3✔
79
                identifier:            identifier,
3✔
80
                paySession:            paySession,
3✔
81
                shardTracker:          shardTracker,
3✔
82
                currentHeight:         currentHeight,
3✔
83
                quit:                  make(chan struct{}),
3✔
84
                resultCollected:       make(chan *switchResult, 1),
3✔
85
                firstHopCustomRecords: firstHopCustomRecords,
3✔
86
        }
3✔
87

3✔
88
        // Mount the result collector.
3✔
89
        p.resultCollector = p.collectResultAsync
3✔
90

3✔
91
        return p
3✔
92
}
3✔
93

94
// waitForOutstandingResults is a dedicated goroutine that handles HTLC attempt
95
// results. It makes sure that if the resumePayment exits we still collect all
96
// outstanding results. This is only a temporary solution and should be
97
// separating the attempt handling out of the payment lifecyle.
98
//
99
// NOTE: must be run in a goroutine.
100
func (p *paymentLifecycle) waitForOutstandingResults() {
3✔
101
        log.Debugf("Payment %v: starting outstanding results collector",
3✔
102
                p.identifier)
3✔
103

3✔
104
        defer log.Debugf("Payment %v: outstanding results collector stopped",
3✔
105
                p.identifier)
3✔
106

3✔
107
        for {
6✔
108
                select {
3✔
NEW
109
                case result := <-p.resultCollected:
×
NEW
110
                        if result == nil {
×
NEW
111
                                log.Debugf("Payment %v: received nil "+
×
NEW
112
                                        "result, stopping collector",
×
NEW
113
                                        p.identifier)
×
NEW
114

×
NEW
115
                                return
×
NEW
116
                        }
×
117

NEW
118
                        log.Debugf("Payment %v: processing result for "+
×
NEW
119
                                "attempt %v", p.identifier,
×
NEW
120
                                result.attempt.AttemptID)
×
NEW
121

×
NEW
122
                        // Handle the result. This will update the payment
×
NEW
123
                        // in the database.
×
NEW
124
                        _, err := p.handleAttemptResult(
×
NEW
125
                                result.attempt, result.result,
×
NEW
126
                        )
×
NEW
127
                        if err != nil {
×
NEW
128
                                log.Errorf("Payment %v: failed to handle "+
×
NEW
129
                                        "result for attempt %v: %v",
×
NEW
130
                                        p.identifier, result.attempt.AttemptID,
×
NEW
131
                                        err)
×
NEW
132
                        }
×
133

134
                case <-p.quit:
3✔
135
                        log.Debugf("Payment %v: quit signal received in "+
3✔
136
                                "result collector", p.identifier)
3✔
137

3✔
138
                        return
3✔
139

140
                case <-p.router.quit:
3✔
141
                        log.Debugf("Payment %v: router quit signal received "+
3✔
142
                                "in result collector", p.identifier)
3✔
143

3✔
144
                        return
3✔
145
                }
146
        }
147
}
148

149
// calcFeeBudget returns the available fee to be used for sending HTLC
150
// attempts.
151
func (p *paymentLifecycle) calcFeeBudget(
152
        feesPaid lnwire.MilliSatoshi) lnwire.MilliSatoshi {
3✔
153

3✔
154
        budget := p.feeLimit
3✔
155

3✔
156
        // We'll subtract the used fee from our fee budget. In case of
3✔
157
        // overflow, we need to check whether feesPaid exceeds our budget
3✔
158
        // already.
3✔
159
        if feesPaid <= budget {
6✔
160
                budget -= feesPaid
3✔
161
        } else {
6✔
162
                budget = 0
3✔
163
        }
3✔
164

165
        return budget
3✔
166
}
167

168
// stateStep defines an action to be taken in our payment lifecycle. We either
169
// quit, continue, or exit the lifecycle, see details below.
170
type stateStep uint8
171

172
const (
173
        // stepSkip is used when we need to skip the current lifecycle and jump
174
        // to the next one.
175
        stepSkip stateStep = iota
176

177
        // stepProceed is used when we can proceed the current lifecycle.
178
        stepProceed
179

180
        // stepExit is used when we need to quit the current lifecycle.
181
        stepExit
182
)
183

184
// decideNextStep is used to determine the next step in the payment lifecycle.
185
// It first checks whether the current state of the payment allows more HTLC
186
// attempts to be made. If allowed, it will return so the lifecycle can continue
187
// making new attempts. Otherwise, it checks whether we need to wait for the
188
// results of already sent attempts. If needed, it will block until one of the
189
// results is sent back. then process its result here. When there's no need to
190
// wait for results, the method will exit with `stepExit` such that the payment
191
// lifecycle loop will terminate.
192
func (p *paymentLifecycle) decideNextStep(
193
        payment DBMPPayment) (stateStep, error) {
3✔
194

3✔
195
        // Check whether we could make new HTLC attempts.
3✔
196
        allow, err := payment.AllowMoreAttempts()
3✔
197
        if err != nil {
3✔
UNCOV
198
                return stepExit, err
×
UNCOV
199
        }
×
200

201
        // Exit early we need to make more attempts.
202
        if allow {
6✔
203
                return stepProceed, nil
3✔
204
        }
3✔
205

206
        // We cannot make more attempts, we now check whether we need to wait
207
        // for results.
208
        wait, err := payment.NeedWaitAttempts()
3✔
209
        if err != nil {
3✔
UNCOV
210
                return stepExit, err
×
UNCOV
211
        }
×
212

213
        // If we are not allowed to make new HTLC attempts and there's no need
214
        // to wait, the lifecycle is done and we can exit.
215
        if !wait {
6✔
216
                return stepExit, nil
3✔
217
        }
3✔
218

219
        log.Tracef("Waiting for attempt results for payment %v", p.identifier)
3✔
220

3✔
221
        // Otherwise we wait for the result for one HTLC attempt then continue
3✔
222
        // the lifecycle.
3✔
223
        select {
3✔
224
        case r := <-p.resultCollected:
3✔
225
                log.Tracef("Received attempt result for payment %v",
3✔
226
                        p.identifier)
3✔
227

3✔
228
                // Handle the result here. If there's no error, we will return
3✔
229
                // stepSkip and move to the next lifecycle iteration, which will
3✔
230
                // refresh the payment and wait for the next attempt result, if
3✔
231
                // any.
3✔
232
                _, err := p.handleAttemptResult(r.attempt, r.result)
3✔
233

3✔
234
                // We would only get a DB-related error here, which will cause
3✔
235
                // us to abort the payment flow.
3✔
236
                if err != nil {
3✔
UNCOV
237
                        return stepExit, err
×
UNCOV
238
                }
×
239

UNCOV
240
        case <-p.quit:
×
UNCOV
241
                return stepExit, ErrPaymentLifecycleExiting
×
242

243
        case <-p.router.quit:
3✔
244
                return stepExit, ErrRouterShuttingDown
3✔
245
        }
246

247
        return stepSkip, nil
3✔
248
}
249

250
// resumePayment resumes the paymentLifecycle from the current state.
251
func (p *paymentLifecycle) resumePayment(ctx context.Context) ([32]byte,
252
        *route.Route, error) {
3✔
253

3✔
254
        // When the payment lifecycle loop exits, we make sure to signal any
3✔
255
        // sub goroutine of the HTLC attempt to exit, then wait for them to
3✔
256
        // return.
3✔
257
        defer p.stop()
3✔
258

3✔
259
        // If we had any existing attempts outstanding, we'll start by spinning
3✔
260
        // up goroutines that'll collect their results and deliver them to the
3✔
261
        // lifecycle loop below.
3✔
262
        payment, err := p.reloadInflightAttempts()
3✔
263
        if err != nil {
3✔
UNCOV
264
                return [32]byte{}, nil, err
×
UNCOV
265
        }
×
266

267
        // Get the payment status.
268
        status := payment.GetStatus()
3✔
269

3✔
270
        // exitWithErr is a helper closure that logs and returns an error.
3✔
271
        exitWithErr := func(err error) ([32]byte, *route.Route, error) {
6✔
272
                // Log an error with the latest payment status.
3✔
273
                //
3✔
274
                // NOTE: this `status` variable is reassigned in the loop
3✔
275
                // below. We could also call `payment.GetStatus` here, but in a
3✔
276
                // rare case when the critical log is triggered when using
3✔
277
                // postgres as db backend, the `payment` could be nil, causing
3✔
278
                // the payment fetching to return an error.
3✔
279
                log.Errorf("Payment %v with status=%v failed: %v", p.identifier,
3✔
280
                        status, err)
3✔
281

3✔
282
                // We need to wait for all outstanding results to be collected
3✔
283
                // before exiting.
3✔
284
                //
3✔
285
                // NOTE: This is only a temporary solution and should be
3✔
286
                // separating the attempt handling out of the payment lifecyle.
3✔
287
                go p.waitForOutstandingResults()
3✔
288

3✔
289
                return [32]byte{}, nil, err
3✔
290
        }
3✔
291

292
        // We'll continue until either our payment succeeds, or we encounter a
293
        // critical error during path finding.
294
lifecycle:
3✔
295
        for {
6✔
296
                // We need to check the context before reloading the payment
3✔
297
                // state.
3✔
298
                if err := p.checkContext(ctx); err != nil {
6✔
299
                        return exitWithErr(err)
3✔
300
                }
3✔
301

302
                // We update the payment state on every iteration.
303
                currentPayment, ps, err := p.reloadPayment()
3✔
304
                if err != nil {
3✔
305
                        return exitWithErr(err)
×
306
                }
×
307

308
                // Reassign status so it can be read in `exitWithErr`.
309
                status = currentPayment.GetStatus()
3✔
310

3✔
311
                // Reassign payment such that when the lifecycle exits, the
3✔
312
                // latest payment can be read when we access its terminal info.
3✔
313
                payment = currentPayment
3✔
314

3✔
315
                // We now proceed our lifecycle with the following tasks in
3✔
316
                // order,
3✔
317
                //   1. check context.
3✔
318
                //   2. request route.
3✔
319
                //   3. create HTLC attempt.
3✔
320
                //   4. send HTLC attempt.
3✔
321
                //   5. collect HTLC attempt result.
3✔
322

3✔
323
                // Now decide the next step of the current lifecycle.
3✔
324
                step, err := p.decideNextStep(payment)
3✔
325
                if err != nil {
6✔
326
                        return exitWithErr(err)
3✔
327
                }
3✔
328

329
                switch step {
3✔
330
                // Exit the for loop and return below.
331
                case stepExit:
3✔
332
                        break lifecycle
3✔
333

334
                // Continue the for loop and skip the rest.
335
                case stepSkip:
3✔
336
                        continue lifecycle
3✔
337

338
                // Continue the for loop and proceed the rest.
339
                case stepProceed:
3✔
340

341
                // Unknown step received, exit with an error.
342
                default:
×
343
                        err = fmt.Errorf("unknown step: %v", step)
×
344
                        return exitWithErr(err)
×
345
                }
346

347
                // Now request a route to be used to create our HTLC attempt.
348
                rt, err := p.requestRoute(ps)
3✔
349
                if err != nil {
3✔
UNCOV
350
                        return exitWithErr(err)
×
UNCOV
351
                }
×
352

353
                // We may not be able to find a route for current attempt. In
354
                // that case, we continue the loop and move straight to the
355
                // next iteration in case there are results for inflight HTLCs
356
                // that still need to be collected.
357
                if rt == nil {
6✔
358
                        log.Errorf("No route found for payment %v",
3✔
359
                                p.identifier)
3✔
360

3✔
361
                        continue lifecycle
3✔
362
                }
363

364
                log.Tracef("Found route: %s", spew.Sdump(rt.Hops))
3✔
365

3✔
366
                // We found a route to try, create a new HTLC attempt to try.
3✔
367
                attempt, err := p.registerAttempt(rt, ps.RemainingAmt)
3✔
368
                if err != nil {
3✔
UNCOV
369
                        return exitWithErr(err)
×
UNCOV
370
                }
×
371

372
                // Once the attempt is created, send it to the htlcswitch.
373
                result, err := p.sendAttempt(attempt)
3✔
374
                if err != nil {
3✔
UNCOV
375
                        return exitWithErr(err)
×
UNCOV
376
                }
×
377

378
                // Now that the shard was successfully sent, launch a go
379
                // routine that will handle its result when its back.
380
                if result.err == nil {
6✔
381
                        p.resultCollector(attempt)
3✔
382
                }
3✔
383
        }
384

385
        // Once we are out the lifecycle loop, it means we've reached a
386
        // terminal condition. We either return the settled preimage or the
387
        // payment's failure reason.
388
        //
389
        // Optionally delete the failed attempts from the database.
390
        err = p.router.cfg.Control.DeleteFailedAttempts(p.identifier)
3✔
391
        if err != nil {
3✔
392
                log.Errorf("Error deleting failed htlc attempts for payment "+
×
393
                        "%v: %v", p.identifier, err)
×
394
        }
×
395

396
        htlc, failure := payment.TerminalInfo()
3✔
397
        if htlc != nil {
6✔
398
                return htlc.Settle.Preimage, &htlc.Route, nil
3✔
399
        }
3✔
400

401
        // Otherwise return the payment failure reason.
402
        return [32]byte{}, nil, *failure
3✔
403
}
404

405
// checkContext checks whether the payment context has been canceled.
406
// Cancellation occurs manually or if the context times out.
407
func (p *paymentLifecycle) checkContext(ctx context.Context) error {
3✔
408
        select {
3✔
409
        case <-ctx.Done():
3✔
410
                // If the context was canceled, we'll mark the payment as
3✔
411
                // failed. There are two cases to distinguish here: Either a
3✔
412
                // user-provided timeout was reached, or the context was
3✔
413
                // canceled, either to a manual cancellation or due to an
3✔
414
                // unknown error.
3✔
415
                var reason channeldb.FailureReason
3✔
416
                if errors.Is(ctx.Err(), context.DeadlineExceeded) {
3✔
UNCOV
417
                        reason = channeldb.FailureReasonTimeout
×
UNCOV
418
                        log.Warnf("Payment attempt not completed before "+
×
UNCOV
419
                                "context timeout, id=%s", p.identifier.String())
×
420
                } else {
3✔
421
                        reason = channeldb.FailureReasonCanceled
3✔
422
                        log.Warnf("Payment attempt context canceled, id=%s",
3✔
423
                                p.identifier.String())
3✔
424
                }
3✔
425

426
                // By marking the payment failed, depending on whether it has
427
                // inflight HTLCs or not, its status will now either be
428
                // `StatusInflight` or `StatusFailed`. In either case, no more
429
                // HTLCs will be attempted.
430
                err := p.router.cfg.Control.FailPayment(p.identifier, reason)
3✔
431
                if err != nil {
3✔
UNCOV
432
                        return fmt.Errorf("FailPayment got %w", err)
×
UNCOV
433
                }
×
434

435
                return fmt.Errorf("payment %v failed with reason: %v",
3✔
436
                        p.identifier, reason)
3✔
437

UNCOV
438
        case <-p.router.quit:
×
UNCOV
439
                return fmt.Errorf("check payment timeout got: %w",
×
UNCOV
440
                        ErrRouterShuttingDown)
×
441

442
        // Fall through if we haven't hit our time limit.
443
        default:
3✔
444
        }
445

446
        return nil
3✔
447
}
448

449
// requestRoute is responsible for finding a route to be used to create an HTLC
450
// attempt.
451
func (p *paymentLifecycle) requestRoute(
452
        ps *channeldb.MPPaymentState) (*route.Route, error) {
3✔
453

3✔
454
        remainingFees := p.calcFeeBudget(ps.FeesPaid)
3✔
455

3✔
456
        // Query our payment session to construct a route.
3✔
457
        rt, err := p.paySession.RequestRoute(
3✔
458
                ps.RemainingAmt, remainingFees,
3✔
459
                uint32(ps.NumAttemptsInFlight), uint32(p.currentHeight),
3✔
460
                p.firstHopCustomRecords,
3✔
461
        )
3✔
462

3✔
463
        // Exit early if there's no error.
3✔
464
        if err == nil {
6✔
465
                // Allow the traffic shaper to add custom records to the
3✔
466
                // outgoing HTLC and also adjust the amount if needed.
3✔
467
                err = p.amendFirstHopData(rt)
3✔
468
                if err != nil {
3✔
469
                        return nil, err
×
470
                }
×
471

472
                return rt, nil
3✔
473
        }
474

475
        // Otherwise we need to handle the error.
476
        log.Warnf("Failed to find route for payment %v: %v", p.identifier, err)
3✔
477

3✔
478
        // If the error belongs to `noRouteError` set, it means a non-critical
3✔
479
        // error has happened during path finding, and we will mark the payment
3✔
480
        // failed with this reason. Otherwise, we'll return the critical error
3✔
481
        // found to abort the lifecycle.
3✔
482
        var routeErr noRouteError
3✔
483
        if !errors.As(err, &routeErr) {
3✔
UNCOV
484
                return nil, fmt.Errorf("requestRoute got: %w", err)
×
UNCOV
485
        }
×
486

487
        // It's the `paymentSession`'s responsibility to find a route for us
488
        // with the best effort. When it cannot find a path, we need to treat it
489
        // as a terminal condition and fail the payment no matter it has
490
        // inflight HTLCs or not.
491
        failureCode := routeErr.FailureReason()
3✔
492
        log.Warnf("Marking payment %v permanently failed with no route: %v",
3✔
493
                p.identifier, failureCode)
3✔
494

3✔
495
        err = p.router.cfg.Control.FailPayment(p.identifier, failureCode)
3✔
496
        if err != nil {
3✔
UNCOV
497
                return nil, fmt.Errorf("FailPayment got: %w", err)
×
UNCOV
498
        }
×
499

500
        // NOTE: we decide to not return the non-critical noRouteError here to
501
        // avoid terminating the payment lifecycle as there might be other
502
        // inflight HTLCs which we must wait for their results.
503
        return nil, nil
3✔
504
}
505

506
// stop signals any active shard goroutine to exit.
507
func (p *paymentLifecycle) stop() {
3✔
508
        log.Debugf("Stopping payment lifecycle for payment %v ...",
3✔
509
                p.identifier)
3✔
510

3✔
511
        // We still wait for all result collectors to finish before exiting
3✔
512
        // the payment lifecycle.
3✔
513
        //
3✔
514
        // NOTE: This is only a temporary solution and should be separating the
3✔
515
        // attempt handling out of the payment lifecyle.
3✔
516
        p.wg.Wait()
3✔
517

3✔
518
        close(p.quit)
3✔
519
}
3✔
520

521
// attemptResult holds the HTLC attempt and a possible error returned from
522
// sending it.
523
type attemptResult struct {
524
        // err is non-nil if a non-critical error was encountered when trying
525
        // to send the attempt, and we successfully updated the control tower
526
        // to reflect this error. This can be errors like not enough local
527
        // balance for the given route etc.
528
        err error
529

530
        // attempt is the attempt structure as recorded in the database.
531
        attempt *channeldb.HTLCAttempt
532
}
533

534
// collectResultAsync launches a goroutine that will wait for the result of the
535
// given HTLC attempt to be available then save its result in a map. Once
536
// received, it will send the result returned from the switch to channel
537
// `resultCollected`.
538
func (p *paymentLifecycle) collectResultAsync(attempt *channeldb.HTLCAttempt) {
3✔
539
        log.Debugf("Collecting result for attempt %v in payment %v",
3✔
540
                attempt.AttemptID, p.identifier)
3✔
541

3✔
542
        p.wg.Add(1)
3✔
543
        go func() {
6✔
544
                defer p.wg.Done()
3✔
545

3✔
546
                result, err := p.collectResult(attempt)
3✔
547
                if err != nil {
6✔
548
                        log.Errorf("Error collecting result for attempt %v in "+
3✔
549
                                "payment %v: %v", attempt.AttemptID,
3✔
550
                                p.identifier, err)
3✔
551

3✔
552
                        return
3✔
553
                }
3✔
554

555
                log.Debugf("Result collected for attempt %v in payment %v",
3✔
556
                        attempt.AttemptID, p.identifier)
3✔
557

3✔
558
                // Create a switch result and send it to the resultCollected
3✔
559
                // chan, which gets processed when the lifecycle is waiting for
3✔
560
                // a result to be received in decideNextStep.
3✔
561
                r := &switchResult{
3✔
562
                        attempt: attempt,
3✔
563
                        result:  result,
3✔
564
                }
3✔
565

3✔
566
                // Signal that a result has been collected.
3✔
567
                //
3✔
568
                // NOTE: We don't listen to the payment lifecycle quit channel
3✔
569
                // here, because we always resolve the result collector before
3✔
570
                // exiting the payment lifecycle which is guaranteed by the
3✔
571
                // wait group.
3✔
572
                //
3✔
573
                // NOTE: This is only a temporary solution and should be
3✔
574
                // separating the attempt handling out of the payment lifecyle.
3✔
575
                select {
3✔
576
                // Send the result so decideNextStep can proceed.
577
                case p.resultCollected <- r:
3✔
578

579
                case <-p.router.quit:
×
580
                }
581
        }()
582
}
583

584
// collectResult waits for the result of the given HTLC attempt to be sent by
585
// the switch and returns it.
586
func (p *paymentLifecycle) collectResult(
587
        attempt *channeldb.HTLCAttempt) (*htlcswitch.PaymentResult, error) {
3✔
588

3✔
589
        log.Tracef("Collecting result for attempt %v",
3✔
590
                lnutils.SpewLogClosure(attempt))
3✔
591

3✔
592
        result := &htlcswitch.PaymentResult{}
3✔
593

3✔
594
        // Regenerate the circuit for this attempt.
3✔
595
        circuit, err := attempt.Circuit()
3✔
596

3✔
597
        // TODO(yy): We generate this circuit to create the error decryptor,
3✔
598
        // which is then used in htlcswitch as the deobfuscator to decode the
3✔
599
        // error from `UpdateFailHTLC`. However, suppose it's an
3✔
600
        // `UpdateFulfillHTLC` message yet for some reason the sphinx packet is
3✔
601
        // failed to be generated, we'd miss settling it. This means we should
3✔
602
        // give it a second chance to try the settlement path in case
3✔
603
        // `GetAttemptResult` gives us back the preimage. And move the circuit
3✔
604
        // creation into htlcswitch so it's only constructed when there's a
3✔
605
        // failure message we need to decode.
3✔
606
        if err != nil {
3✔
607
                log.Debugf("Unable to generate circuit for attempt %v: %v",
×
608
                        attempt.AttemptID, err)
×
609
                return nil, err
×
610
        }
×
611

612
        // Using the created circuit, initialize the error decrypter, so we can
613
        // parse+decode any failures incurred by this payment within the
614
        // switch.
615
        errorDecryptor := &htlcswitch.SphinxErrorDecrypter{
3✔
616
                OnionErrorDecrypter: sphinx.NewOnionErrorDecrypter(circuit),
3✔
617
        }
3✔
618

3✔
619
        // Now ask the switch to return the result of the payment when
3✔
620
        // available.
3✔
621
        //
3✔
622
        // TODO(yy): consider using htlcswitch to create the `errorDecryptor`
3✔
623
        // since the htlc is already in db. This will also make the interface
3✔
624
        // `PaymentAttemptDispatcher` deeper and easier to use. Moreover, we'd
3✔
625
        // only create the decryptor when received a failure, further saving us
3✔
626
        // a few CPU cycles.
3✔
627
        resultChan, err := p.router.cfg.Payer.GetAttemptResult(
3✔
628
                attempt.AttemptID, p.identifier, errorDecryptor,
3✔
629
        )
3✔
630
        // Handle the switch error.
3✔
631
        if err != nil {
3✔
UNCOV
632
                log.Errorf("Failed getting result for attemptID %d "+
×
UNCOV
633
                        "from switch: %v", attempt.AttemptID, err)
×
UNCOV
634

×
UNCOV
635
                result.Error = err
×
UNCOV
636

×
UNCOV
637
                return result, nil
×
UNCOV
638
        }
×
639

640
        // The switch knows about this payment, we'll wait for a result to be
641
        // available.
642
        select {
3✔
643
        case r, ok := <-resultChan:
3✔
644
                if !ok {
6✔
645
                        return nil, htlcswitch.ErrSwitchExiting
3✔
646
                }
3✔
647

648
                result = r
3✔
649

UNCOV
650
        case <-p.quit:
×
UNCOV
651
                return nil, ErrPaymentLifecycleExiting
×
652

UNCOV
653
        case <-p.router.quit:
×
UNCOV
654
                return nil, ErrRouterShuttingDown
×
655
        }
656

657
        return result, nil
3✔
658
}
659

660
// registerAttempt is responsible for creating and saving an HTLC attempt in db
661
// by using the route info provided. The `remainingAmt` is used to decide
662
// whether this is the last attempt.
663
func (p *paymentLifecycle) registerAttempt(rt *route.Route,
664
        remainingAmt lnwire.MilliSatoshi) (*channeldb.HTLCAttempt, error) {
3✔
665

3✔
666
        // If this route will consume the last remaining amount to send
3✔
667
        // to the receiver, this will be our last shard (for now).
3✔
668
        isLastAttempt := rt.ReceiverAmt() == remainingAmt
3✔
669

3✔
670
        // Using the route received from the payment session, create a new
3✔
671
        // shard to send.
3✔
672
        attempt, err := p.createNewPaymentAttempt(rt, isLastAttempt)
3✔
673
        if err != nil {
3✔
UNCOV
674
                return nil, err
×
UNCOV
675
        }
×
676

677
        // Before sending this HTLC to the switch, we checkpoint the fresh
678
        // paymentID and route to the DB. This lets us know on startup the ID
679
        // of the payment that we attempted to send, such that we can query the
680
        // Switch for its whereabouts. The route is needed to handle the result
681
        // when it eventually comes back.
682
        err = p.router.cfg.Control.RegisterAttempt(
3✔
683
                p.identifier, &attempt.HTLCAttemptInfo,
3✔
684
        )
3✔
685

3✔
686
        return attempt, err
3✔
687
}
688

689
// createNewPaymentAttempt creates a new payment attempt from the given route.
690
func (p *paymentLifecycle) createNewPaymentAttempt(rt *route.Route,
691
        lastShard bool) (*channeldb.HTLCAttempt, error) {
3✔
692

3✔
693
        // Generate a new key to be used for this attempt.
3✔
694
        sessionKey, err := generateNewSessionKey()
3✔
695
        if err != nil {
3✔
696
                return nil, err
×
697
        }
×
698

699
        // We generate a new, unique payment ID that we will use for
700
        // this HTLC.
701
        attemptID, err := p.router.cfg.NextPaymentID()
3✔
702
        if err != nil {
3✔
703
                return nil, err
×
704
        }
×
705

706
        // Request a new shard from the ShardTracker. If this is an AMP
707
        // payment, and this is the last shard, the outstanding shards together
708
        // with this one will be enough for the receiver to derive all HTLC
709
        // preimages. If this a non-AMP payment, the ShardTracker will return a
710
        // simple shard with the payment's static payment hash.
711
        shard, err := p.shardTracker.NewShard(attemptID, lastShard)
3✔
712
        if err != nil {
3✔
UNCOV
713
                return nil, err
×
UNCOV
714
        }
×
715

716
        // If this shard carries MPP or AMP options, add them to the last hop
717
        // on the route.
718
        hop := rt.Hops[len(rt.Hops)-1]
3✔
719
        if shard.MPP() != nil {
6✔
720
                hop.MPP = shard.MPP()
3✔
721
        }
3✔
722

723
        if shard.AMP() != nil {
6✔
724
                hop.AMP = shard.AMP()
3✔
725
        }
3✔
726

727
        hash := shard.Hash()
3✔
728

3✔
729
        // We now have all the information needed to populate the current
3✔
730
        // attempt information.
3✔
731
        return channeldb.NewHtlcAttempt(
3✔
732
                attemptID, sessionKey, *rt, p.router.cfg.Clock.Now(), &hash,
3✔
733
        )
3✔
734
}
735

736
// sendAttempt attempts to send the current attempt to the switch to complete
737
// the payment. If this attempt fails, then we'll continue on to the next
738
// available route.
739
func (p *paymentLifecycle) sendAttempt(
740
        attempt *channeldb.HTLCAttempt) (*attemptResult, error) {
3✔
741

3✔
742
        log.Debugf("Sending HTLC attempt(id=%v, total_amt=%v, first_hop_amt=%d"+
3✔
743
                ") for payment %v", attempt.AttemptID,
3✔
744
                attempt.Route.TotalAmount, attempt.Route.FirstHopAmount.Val,
3✔
745
                p.identifier)
3✔
746

3✔
747
        rt := attempt.Route
3✔
748

3✔
749
        // Construct the first hop.
3✔
750
        firstHop := lnwire.NewShortChanIDFromInt(rt.Hops[0].ChannelID)
3✔
751

3✔
752
        // Craft an HTLC packet to send to the htlcswitch. The metadata within
3✔
753
        // this packet will be used to route the payment through the network,
3✔
754
        // starting with the first-hop.
3✔
755
        htlcAdd := &lnwire.UpdateAddHTLC{
3✔
756
                Amount:        rt.FirstHopAmount.Val.Int(),
3✔
757
                Expiry:        rt.TotalTimeLock,
3✔
758
                PaymentHash:   *attempt.Hash,
3✔
759
                CustomRecords: rt.FirstHopWireCustomRecords,
3✔
760
        }
3✔
761

3✔
762
        // Generate the raw encoded sphinx packet to be included along
3✔
763
        // with the htlcAdd message that we send directly to the
3✔
764
        // switch.
3✔
765
        onionBlob, err := attempt.OnionBlob()
3✔
766
        if err != nil {
3✔
767
                log.Errorf("Failed to create onion blob: attempt=%d in "+
×
768
                        "payment=%v, err:%v", attempt.AttemptID,
×
769
                        p.identifier, err)
×
770

×
771
                return p.failAttempt(attempt.AttemptID, err)
×
772
        }
×
773

774
        htlcAdd.OnionBlob = onionBlob
3✔
775

3✔
776
        // Send it to the Switch. When this method returns we assume
3✔
777
        // the Switch successfully has persisted the payment attempt,
3✔
778
        // such that we can resume waiting for the result after a
3✔
779
        // restart.
3✔
780
        err = p.router.cfg.Payer.SendHTLC(firstHop, attempt.AttemptID, htlcAdd)
3✔
781
        if err != nil {
6✔
782
                log.Errorf("Failed sending attempt %d for payment %v to "+
3✔
783
                        "switch: %v", attempt.AttemptID, p.identifier, err)
3✔
784

3✔
785
                return p.handleSwitchErr(attempt, err)
3✔
786
        }
3✔
787

788
        log.Debugf("Attempt %v for payment %v successfully sent to switch, "+
3✔
789
                "route: %v", attempt.AttemptID, p.identifier, &attempt.Route)
3✔
790

3✔
791
        return &attemptResult{
3✔
792
                attempt: attempt,
3✔
793
        }, nil
3✔
794
}
795

796
// amendFirstHopData is a function that calls the traffic shaper to allow it to
797
// add custom records to the outgoing HTLC and also adjust the amount if
798
// needed.
799
func (p *paymentLifecycle) amendFirstHopData(rt *route.Route) error {
3✔
800
        // The first hop amount on the route is the full route amount if not
3✔
801
        // overwritten by the traffic shaper. So we set the initial value now
3✔
802
        // and potentially overwrite it later.
3✔
803
        rt.FirstHopAmount = tlv.NewRecordT[tlv.TlvType0](
3✔
804
                tlv.NewBigSizeT(rt.TotalAmount),
3✔
805
        )
3✔
806

3✔
807
        // By default, we set the first hop custom records to the initial
3✔
808
        // value requested by the RPC. The traffic shaper may overwrite this
3✔
809
        // value.
3✔
810
        rt.FirstHopWireCustomRecords = p.firstHopCustomRecords
3✔
811

3✔
812
        if len(rt.Hops) == 0 {
3✔
813
                return fmt.Errorf("cannot amend first hop data, route length " +
×
814
                        "is zero")
×
815
        }
×
816

817
        firstHopPK := rt.Hops[0].PubKeyBytes
3✔
818

3✔
819
        // extraDataRequest is a helper struct to pass the custom records and
3✔
820
        // amount back from the traffic shaper.
3✔
821
        type extraDataRequest struct {
3✔
822
                customRecords fn.Option[lnwire.CustomRecords]
3✔
823

3✔
824
                amount fn.Option[lnwire.MilliSatoshi]
3✔
825
        }
3✔
826

3✔
827
        // If a hook exists that may affect our outgoing message, we call it now
3✔
828
        // and apply its side effects to the UpdateAddHTLC message.
3✔
829
        result, err := fn.MapOptionZ(
3✔
830
                p.router.cfg.TrafficShaper,
3✔
831
                //nolint:ll
3✔
832
                func(ts htlcswitch.AuxTrafficShaper) fn.Result[extraDataRequest] {
3✔
UNCOV
833
                        newAmt, newRecords, err := ts.ProduceHtlcExtraData(
×
UNCOV
834
                                rt.TotalAmount, p.firstHopCustomRecords,
×
UNCOV
835
                                firstHopPK,
×
UNCOV
836
                        )
×
UNCOV
837
                        if err != nil {
×
838
                                return fn.Err[extraDataRequest](err)
×
839
                        }
×
840

841
                        // Make sure we only received valid records.
UNCOV
842
                        if err := newRecords.Validate(); err != nil {
×
843
                                return fn.Err[extraDataRequest](err)
×
844
                        }
×
845

UNCOV
846
                        log.Debugf("Aux traffic shaper returned custom "+
×
UNCOV
847
                                "records %v and amount %d msat for HTLC",
×
UNCOV
848
                                spew.Sdump(newRecords), newAmt)
×
UNCOV
849

×
UNCOV
850
                        return fn.Ok(extraDataRequest{
×
UNCOV
851
                                customRecords: fn.Some(newRecords),
×
UNCOV
852
                                amount:        fn.Some(newAmt),
×
UNCOV
853
                        })
×
854
                },
855
        ).Unpack()
856
        if err != nil {
3✔
857
                return fmt.Errorf("traffic shaper failed to produce extra "+
×
858
                        "data: %w", err)
×
859
        }
×
860

861
        // Apply the side effects to the UpdateAddHTLC message.
862
        result.customRecords.WhenSome(func(records lnwire.CustomRecords) {
3✔
UNCOV
863
                rt.FirstHopWireCustomRecords = records
×
UNCOV
864
        })
×
865
        result.amount.WhenSome(func(amount lnwire.MilliSatoshi) {
3✔
UNCOV
866
                rt.FirstHopAmount = tlv.NewRecordT[tlv.TlvType0](
×
UNCOV
867
                        tlv.NewBigSizeT(amount),
×
UNCOV
868
                )
×
UNCOV
869
        })
×
870

871
        return nil
3✔
872
}
873

874
// failAttemptAndPayment fails both the payment and its attempt via the
875
// router's control tower, which marks the payment as failed in db.
876
func (p *paymentLifecycle) failPaymentAndAttempt(
877
        attemptID uint64, reason *channeldb.FailureReason,
878
        sendErr error) (*attemptResult, error) {
3✔
879

3✔
880
        log.Errorf("Payment %v failed: final_outcome=%v, raw_err=%v",
3✔
881
                p.identifier, *reason, sendErr)
3✔
882

3✔
883
        // Fail the payment via control tower.
3✔
884
        //
3✔
885
        // NOTE: we must fail the payment first before failing the attempt.
3✔
886
        // Otherwise, once the attempt is marked as failed, another goroutine
3✔
887
        // might make another attempt while we are failing the payment.
3✔
888
        err := p.router.cfg.Control.FailPayment(p.identifier, *reason)
3✔
889
        if err != nil {
3✔
890
                log.Errorf("Unable to fail payment: %v", err)
×
891
                return nil, err
×
892
        }
×
893

894
        // Fail the attempt.
895
        return p.failAttempt(attemptID, sendErr)
3✔
896
}
897

898
// handleSwitchErr inspects the given error from the Switch and determines
899
// whether we should make another payment attempt, or if it should be
900
// considered a terminal error. Terminal errors will be recorded with the
901
// control tower. It analyzes the sendErr for the payment attempt received from
902
// the switch and updates mission control and/or channel policies. Depending on
903
// the error type, the error is either the final outcome of the payment or we
904
// need to continue with an alternative route. A final outcome is indicated by
905
// a non-nil reason value.
906
func (p *paymentLifecycle) handleSwitchErr(attempt *channeldb.HTLCAttempt,
907
        sendErr error) (*attemptResult, error) {
3✔
908

3✔
909
        internalErrorReason := channeldb.FailureReasonError
3✔
910
        attemptID := attempt.AttemptID
3✔
911

3✔
912
        // reportAndFail is a helper closure that reports the failure to the
3✔
913
        // mission control, which helps us to decide whether we want to retry
3✔
914
        // the payment or not. If a non nil reason is returned from mission
3✔
915
        // control, it will further fail the payment via control tower.
3✔
916
        reportAndFail := func(srcIdx *int,
3✔
917
                msg lnwire.FailureMessage) (*attemptResult, error) {
6✔
918

3✔
919
                // Report outcome to mission control.
3✔
920
                reason, err := p.router.cfg.MissionControl.ReportPaymentFail(
3✔
921
                        attemptID, &attempt.Route, srcIdx, msg,
3✔
922
                )
3✔
923
                if err != nil {
3✔
924
                        log.Errorf("Error reporting payment result to mc: %v",
×
925
                                err)
×
926

×
927
                        reason = &internalErrorReason
×
928
                }
×
929

930
                // Fail the attempt only if there's no reason.
931
                if reason == nil {
6✔
932
                        // Fail the attempt.
3✔
933
                        return p.failAttempt(attemptID, sendErr)
3✔
934
                }
3✔
935

936
                // Otherwise fail both the payment and the attempt.
937
                return p.failPaymentAndAttempt(attemptID, reason, sendErr)
3✔
938
        }
939

940
        // If this attempt ID is unknown to the Switch, it means it was never
941
        // checkpointed and forwarded by the switch before a restart. In this
942
        // case we can safely send a new payment attempt, and wait for its
943
        // result to be available.
944
        if errors.Is(sendErr, htlcswitch.ErrPaymentIDNotFound) {
3✔
UNCOV
945
                log.Warnf("Failing attempt=%v for payment=%v as it's not "+
×
UNCOV
946
                        "found in the Switch", attempt.AttemptID, p.identifier)
×
UNCOV
947

×
UNCOV
948
                return p.failAttempt(attemptID, sendErr)
×
UNCOV
949
        }
×
950

951
        if errors.Is(sendErr, htlcswitch.ErrUnreadableFailureMessage) {
3✔
UNCOV
952
                log.Warn("Unreadable failure when sending htlc: id=%v, hash=%v",
×
UNCOV
953
                        attempt.AttemptID, attempt.Hash)
×
UNCOV
954

×
UNCOV
955
                // Since this error message cannot be decrypted, we will send a
×
UNCOV
956
                // nil error message to our mission controller and fail the
×
UNCOV
957
                // payment.
×
UNCOV
958
                return reportAndFail(nil, nil)
×
UNCOV
959
        }
×
960

961
        // If the error is a ClearTextError, we have received a valid wire
962
        // failure message, either from our own outgoing link or from a node
963
        // down the route. If the error is not related to the propagation of
964
        // our payment, we can stop trying because an internal error has
965
        // occurred.
966
        var rtErr htlcswitch.ClearTextError
3✔
967
        ok := errors.As(sendErr, &rtErr)
3✔
968
        if !ok {
3✔
UNCOV
969
                return p.failPaymentAndAttempt(
×
UNCOV
970
                        attemptID, &internalErrorReason, sendErr,
×
UNCOV
971
                )
×
UNCOV
972
        }
×
973

974
        // failureSourceIdx is the index of the node that the failure occurred
975
        // at. If the ClearTextError received is not a ForwardingError the
976
        // payment error occurred at our node, so we leave this value as 0
977
        // to indicate that the failure occurred locally. If the error is a
978
        // ForwardingError, it did not originate at our node, so we set
979
        // failureSourceIdx to the index of the node where the failure occurred.
980
        failureSourceIdx := 0
3✔
981
        var source *htlcswitch.ForwardingError
3✔
982
        ok = errors.As(rtErr, &source)
3✔
983
        if ok {
6✔
984
                failureSourceIdx = source.FailureSourceIdx
3✔
985
        }
3✔
986

987
        // Extract the wire failure and apply channel update if it contains one.
988
        // If we received an unknown failure message from a node along the
989
        // route, the failure message will be nil.
990
        failureMessage := rtErr.WireMessage()
3✔
991
        err := p.handleFailureMessage(
3✔
992
                &attempt.Route, failureSourceIdx, failureMessage,
3✔
993
        )
3✔
994
        if err != nil {
3✔
995
                return p.failPaymentAndAttempt(
×
996
                        attemptID, &internalErrorReason, sendErr,
×
997
                )
×
998
        }
×
999

1000
        log.Tracef("Node=%v reported failure when sending htlc",
3✔
1001
                failureSourceIdx)
3✔
1002

3✔
1003
        return reportAndFail(&failureSourceIdx, failureMessage)
3✔
1004
}
1005

1006
// handleFailureMessage tries to apply a channel update present in the failure
1007
// message if any.
1008
func (p *paymentLifecycle) handleFailureMessage(rt *route.Route,
1009
        errorSourceIdx int, failure lnwire.FailureMessage) error {
3✔
1010

3✔
1011
        if failure == nil {
3✔
UNCOV
1012
                return nil
×
UNCOV
1013
        }
×
1014

1015
        // It makes no sense to apply our own channel updates.
1016
        if errorSourceIdx == 0 {
6✔
1017
                log.Errorf("Channel update of ourselves received")
3✔
1018

3✔
1019
                return nil
3✔
1020
        }
3✔
1021

1022
        // Extract channel update if the error contains one.
1023
        update := p.router.extractChannelUpdate(failure)
3✔
1024
        if update == nil {
6✔
1025
                return nil
3✔
1026
        }
3✔
1027

1028
        // Parse pubkey to allow validation of the channel update. This should
1029
        // always succeed, otherwise there is something wrong in our
1030
        // implementation. Therefore, return an error.
1031
        errVertex := rt.Hops[errorSourceIdx-1].PubKeyBytes
3✔
1032
        errSource, err := btcec.ParsePubKey(errVertex[:])
3✔
1033
        if err != nil {
3✔
1034
                log.Errorf("Cannot parse pubkey: idx=%v, pubkey=%v",
×
1035
                        errorSourceIdx, errVertex)
×
1036

×
1037
                return err
×
1038
        }
×
1039

1040
        var (
3✔
1041
                isAdditionalEdge bool
3✔
1042
                policy           *models.CachedEdgePolicy
3✔
1043
        )
3✔
1044

3✔
1045
        // Before we apply the channel update, we need to decide whether the
3✔
1046
        // update is for additional (ephemeral) edge or normal edge stored in
3✔
1047
        // db.
3✔
1048
        //
3✔
1049
        // Note: the p.paySession might be nil here if it's called inside
3✔
1050
        // SendToRoute where there's no payment lifecycle.
3✔
1051
        if p.paySession != nil {
6✔
1052
                policy = p.paySession.GetAdditionalEdgePolicy(
3✔
1053
                        errSource, update.ShortChannelID.ToUint64(),
3✔
1054
                )
3✔
1055
                if policy != nil {
6✔
1056
                        isAdditionalEdge = true
3✔
1057
                }
3✔
1058
        }
1059

1060
        // Apply channel update to additional edge policy.
1061
        if isAdditionalEdge {
6✔
1062
                if !p.paySession.UpdateAdditionalEdge(
3✔
1063
                        update, errSource, policy) {
3✔
1064

×
1065
                        log.Debugf("Invalid channel update received: node=%v",
×
1066
                                errVertex)
×
1067
                }
×
1068
                return nil
3✔
1069
        }
1070

1071
        // Apply channel update to the channel edge policy in our db.
1072
        if !p.router.cfg.ApplyChannelUpdate(update) {
6✔
1073
                log.Debugf("Invalid channel update received: node=%v",
3✔
1074
                        errVertex)
3✔
1075
        }
3✔
1076
        return nil
3✔
1077
}
1078

1079
// failAttempt calls control tower to fail the current payment attempt.
1080
func (p *paymentLifecycle) failAttempt(attemptID uint64,
1081
        sendError error) (*attemptResult, error) {
3✔
1082

3✔
1083
        log.Warnf("Attempt %v for payment %v failed: %v", attemptID,
3✔
1084
                p.identifier, sendError)
3✔
1085

3✔
1086
        failInfo := marshallError(
3✔
1087
                sendError,
3✔
1088
                p.router.cfg.Clock.Now(),
3✔
1089
        )
3✔
1090

3✔
1091
        // Now that we are failing this payment attempt, cancel the shard with
3✔
1092
        // the ShardTracker such that it can derive the correct hash for the
3✔
1093
        // next attempt.
3✔
1094
        if err := p.shardTracker.CancelShard(attemptID); err != nil {
3✔
1095
                return nil, err
×
1096
        }
×
1097

1098
        attempt, err := p.router.cfg.Control.FailAttempt(
3✔
1099
                p.identifier, attemptID, failInfo,
3✔
1100
        )
3✔
1101
        if err != nil {
3✔
UNCOV
1102
                return nil, err
×
UNCOV
1103
        }
×
1104

1105
        return &attemptResult{
3✔
1106
                attempt: attempt,
3✔
1107
                err:     sendError,
3✔
1108
        }, nil
3✔
1109
}
1110

1111
// marshallError marshall an error as received from the switch to a structure
1112
// that is suitable for database storage.
1113
func marshallError(sendError error, time time.Time) *channeldb.HTLCFailInfo {
3✔
1114
        response := &channeldb.HTLCFailInfo{
3✔
1115
                FailTime: time,
3✔
1116
        }
3✔
1117

3✔
1118
        switch {
3✔
UNCOV
1119
        case errors.Is(sendError, htlcswitch.ErrPaymentIDNotFound):
×
UNCOV
1120
                response.Reason = channeldb.HTLCFailInternal
×
UNCOV
1121
                return response
×
1122

UNCOV
1123
        case errors.Is(sendError, htlcswitch.ErrUnreadableFailureMessage):
×
UNCOV
1124
                response.Reason = channeldb.HTLCFailUnreadable
×
UNCOV
1125
                return response
×
1126
        }
1127

1128
        var rtErr htlcswitch.ClearTextError
3✔
1129
        ok := errors.As(sendError, &rtErr)
3✔
1130
        if !ok {
3✔
UNCOV
1131
                response.Reason = channeldb.HTLCFailInternal
×
UNCOV
1132
                return response
×
UNCOV
1133
        }
×
1134

1135
        message := rtErr.WireMessage()
3✔
1136
        if message != nil {
6✔
1137
                response.Reason = channeldb.HTLCFailMessage
3✔
1138
                response.Message = message
3✔
1139
        } else {
3✔
UNCOV
1140
                response.Reason = channeldb.HTLCFailUnknown
×
UNCOV
1141
        }
×
1142

1143
        // If the ClearTextError received is a ForwardingError, the error
1144
        // originated from a node along the route, not locally on our outgoing
1145
        // link. We set failureSourceIdx to the index of the node where the
1146
        // failure occurred. If the error is not a ForwardingError, the failure
1147
        // occurred at our node, so we leave the index as 0 to indicate that
1148
        // we failed locally.
1149
        var fErr *htlcswitch.ForwardingError
3✔
1150
        ok = errors.As(rtErr, &fErr)
3✔
1151
        if ok {
6✔
1152
                response.FailureSourceIndex = uint32(fErr.FailureSourceIdx)
3✔
1153
        }
3✔
1154

1155
        return response
3✔
1156
}
1157

1158
// patchLegacyPaymentHash will make a copy of the passed attempt and sets its
1159
// Hash field to be the payment hash if it's nil.
1160
//
1161
// NOTE: For legacy payments, which were created before the AMP feature was
1162
// enabled, the `Hash` field in their HTLC attempts is nil. In that case, we use
1163
// the payment hash as the `attempt.Hash` as they are identical.
1164
func (p *paymentLifecycle) patchLegacyPaymentHash(
1165
        a channeldb.HTLCAttempt) channeldb.HTLCAttempt {
3✔
1166

3✔
1167
        // Exit early if this is not a legacy attempt.
3✔
1168
        if a.Hash != nil {
6✔
1169
                return a
3✔
1170
        }
3✔
1171

1172
        // Log a warning if the user is still using legacy payments, which has
1173
        // weaker support.
UNCOV
1174
        log.Warnf("Found legacy htlc attempt %v in payment %v", a.AttemptID,
×
UNCOV
1175
                p.identifier)
×
UNCOV
1176

×
UNCOV
1177
        // Set the attempt's hash to be the payment hash, which is the payment's
×
UNCOV
1178
        // `PaymentHash`` in the `PaymentCreationInfo`. For legacy payments
×
UNCOV
1179
        // before AMP feature, the `Hash` field was not set so we use the
×
UNCOV
1180
        // payment hash instead.
×
UNCOV
1181
        //
×
UNCOV
1182
        // NOTE: During the router's startup, we have a similar logic in
×
UNCOV
1183
        // `resumePayments`, in which we will use the payment hash instead if
×
UNCOV
1184
        // the attempt's hash is nil.
×
UNCOV
1185
        a.Hash = &p.identifier
×
UNCOV
1186

×
UNCOV
1187
        return a
×
1188
}
1189

1190
// reloadInflightAttempts is called when the payment lifecycle is resumed after
1191
// a restart. It reloads all inflight attempts from the control tower and
1192
// collects the results of the attempts that have been sent before.
1193
func (p *paymentLifecycle) reloadInflightAttempts() (DBMPPayment, error) {
3✔
1194
        payment, err := p.router.cfg.Control.FetchPayment(p.identifier)
3✔
1195
        if err != nil {
3✔
UNCOV
1196
                return nil, err
×
UNCOV
1197
        }
×
1198

1199
        for _, a := range payment.InFlightHTLCs() {
6✔
1200
                a := a
3✔
1201

3✔
1202
                log.Infof("Resuming HTLC attempt %v for payment %v",
3✔
1203
                        a.AttemptID, p.identifier)
3✔
1204

3✔
1205
                // Potentially attach the payment hash to the `Hash` field if
3✔
1206
                // it's a legacy payment.
3✔
1207
                a = p.patchLegacyPaymentHash(a)
3✔
1208

3✔
1209
                p.resultCollector(&a)
3✔
1210
        }
3✔
1211

1212
        return payment, nil
3✔
1213
}
1214

1215
// reloadPayment returns the latest payment found in the db (control tower).
1216
func (p *paymentLifecycle) reloadPayment() (DBMPPayment,
1217
        *channeldb.MPPaymentState, error) {
3✔
1218

3✔
1219
        // Read the db to get the latest state of the payment.
3✔
1220
        payment, err := p.router.cfg.Control.FetchPayment(p.identifier)
3✔
1221
        if err != nil {
3✔
1222
                return nil, nil, err
×
1223
        }
×
1224

1225
        ps := payment.GetState()
3✔
1226
        remainingFees := p.calcFeeBudget(ps.FeesPaid)
3✔
1227

3✔
1228
        log.Debugf("Payment %v: status=%v, active_shards=%v, rem_value=%v, "+
3✔
1229
                "fee_limit=%v", p.identifier, payment.GetStatus(),
3✔
1230
                ps.NumAttemptsInFlight, ps.RemainingAmt, remainingFees)
3✔
1231

3✔
1232
        return payment, ps, nil
3✔
1233
}
1234

1235
// handleAttemptResult processes the result of an HTLC attempt returned from
1236
// the htlcswitch.
1237
func (p *paymentLifecycle) handleAttemptResult(attempt *channeldb.HTLCAttempt,
1238
        result *htlcswitch.PaymentResult) (*attemptResult, error) {
3✔
1239

3✔
1240
        // If the result has an error, we need to further process it by failing
3✔
1241
        // the attempt and maybe fail the payment.
3✔
1242
        if result.Error != nil {
6✔
1243
                return p.handleSwitchErr(attempt, result.Error)
3✔
1244
        }
3✔
1245

1246
        // We got an attempt settled result back from the switch.
1247
        log.Debugf("Payment(%v): attempt(%v) succeeded", p.identifier,
3✔
1248
                attempt.AttemptID)
3✔
1249

3✔
1250
        // Report success to mission control.
3✔
1251
        err := p.router.cfg.MissionControl.ReportPaymentSuccess(
3✔
1252
                attempt.AttemptID, &attempt.Route,
3✔
1253
        )
3✔
1254
        if err != nil {
3✔
1255
                log.Errorf("Error reporting payment success to mc: %v", err)
×
1256
        }
×
1257

1258
        // In case of success we atomically store settle result to the DB and
1259
        // move the shard to the settled state.
1260
        htlcAttempt, err := p.router.cfg.Control.SettleAttempt(
3✔
1261
                p.identifier, attempt.AttemptID,
3✔
1262
                &channeldb.HTLCSettleInfo{
3✔
1263
                        Preimage:   result.Preimage,
3✔
1264
                        SettleTime: p.router.cfg.Clock.Now(),
3✔
1265
                },
3✔
1266
        )
3✔
1267
        if err != nil {
3✔
UNCOV
1268
                log.Errorf("Error settling attempt %v for payment %v with "+
×
UNCOV
1269
                        "preimage %v: %v", attempt.AttemptID, p.identifier,
×
UNCOV
1270
                        result.Preimage, err)
×
UNCOV
1271

×
UNCOV
1272
                // We won't mark the attempt as failed since we already have
×
UNCOV
1273
                // the preimage.
×
UNCOV
1274
                return nil, err
×
UNCOV
1275
        }
×
1276

1277
        return &attemptResult{
3✔
1278
                attempt: htlcAttempt,
3✔
1279
        }, nil
3✔
1280
}
1281

1282
// collectAndHandleResult waits for the result for the given attempt to be
1283
// available from the Switch, then records the attempt outcome with the control
1284
// tower. An attemptResult is returned, indicating the final outcome of this
1285
// HTLC attempt.
1286
func (p *paymentLifecycle) collectAndHandleResult(
1287
        attempt *channeldb.HTLCAttempt) (*attemptResult, error) {
3✔
1288

3✔
1289
        result, err := p.collectResult(attempt)
3✔
1290
        if err != nil {
3✔
UNCOV
1291
                return nil, err
×
UNCOV
1292
        }
×
1293

1294
        return p.handleAttemptResult(attempt, result)
3✔
1295
}
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