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

lightningnetwork / lnd / 17917482292

22 Sep 2025 01:50PM UTC coverage: 56.562% (-10.1%) from 66.668%
17917482292

Pull #10182

github

web-flow
Merge 9efe3bd8c into 055fb436e
Pull Request #10182: Aux feature bits

32 of 68 new or added lines in 5 files covered. (47.06%)

29734 existing lines in 467 files now uncovered.

98449 of 174056 relevant lines covered (56.56%)

1.18 hits per line

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

76.35
/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
        sphinx "github.com/lightningnetwork/lightning-onion"
11
        "github.com/lightningnetwork/lnd/fn/v2"
12
        "github.com/lightningnetwork/lnd/graph/db/models"
13
        "github.com/lightningnetwork/lnd/htlcswitch"
14
        "github.com/lightningnetwork/lnd/lntypes"
15
        "github.com/lightningnetwork/lnd/lnutils"
16
        "github.com/lightningnetwork/lnd/lnwire"
17
        paymentsdb "github.com/lightningnetwork/lnd/payments/db"
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
// switchResult is the result sent back from the switch after processing the
28
// HTLC.
29
type switchResult struct {
30
        // attempt is the HTLC sent to the switch.
31
        attempt *paymentsdb.HTLCAttempt
32

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

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

49
        // quit is closed to signal the sub goroutines of the payment lifecycle
50
        // to stop.
51
        quit chan struct{}
52

53
        // resultCollected is used to send the result returned from the switch
54
        // for a given HTLC attempt.
55
        resultCollected chan *switchResult
56

57
        // resultCollector is a function that is used to collect the result of
58
        // an HTLC attempt, which is always mounted to `p.collectResultAsync`
59
        // except in unit test, where we use a much simpler resultCollector to
60
        // decouple the test flow for the payment lifecycle.
61
        resultCollector func(attempt *paymentsdb.HTLCAttempt)
62
}
63

64
// newPaymentLifecycle initiates a new payment lifecycle and returns it.
65
func newPaymentLifecycle(r *ChannelRouter, feeLimit lnwire.MilliSatoshi,
66
        identifier lntypes.Hash, paySession PaymentSession,
67
        shardTracker shards.ShardTracker, currentHeight int32,
68
        firstHopCustomRecords lnwire.CustomRecords) *paymentLifecycle {
2✔
69

2✔
70
        p := &paymentLifecycle{
2✔
71
                router:                r,
2✔
72
                feeLimit:              feeLimit,
2✔
73
                identifier:            identifier,
2✔
74
                paySession:            paySession,
2✔
75
                shardTracker:          shardTracker,
2✔
76
                currentHeight:         currentHeight,
2✔
77
                quit:                  make(chan struct{}),
2✔
78
                resultCollected:       make(chan *switchResult, 1),
2✔
79
                firstHopCustomRecords: firstHopCustomRecords,
2✔
80
        }
2✔
81

2✔
82
        // Mount the result collector.
2✔
83
        p.resultCollector = p.collectResultAsync
2✔
84

2✔
85
        return p
2✔
86
}
2✔
87

88
// calcFeeBudget returns the available fee to be used for sending HTLC
89
// attempts.
90
func (p *paymentLifecycle) calcFeeBudget(
91
        feesPaid lnwire.MilliSatoshi) lnwire.MilliSatoshi {
2✔
92

2✔
93
        budget := p.feeLimit
2✔
94

2✔
95
        // We'll subtract the used fee from our fee budget. In case of
2✔
96
        // overflow, we need to check whether feesPaid exceeds our budget
2✔
97
        // already.
2✔
98
        if feesPaid <= budget {
4✔
99
                budget -= feesPaid
2✔
100
        } else {
4✔
101
                budget = 0
2✔
102
        }
2✔
103

104
        return budget
2✔
105
}
106

107
// stateStep defines an action to be taken in our payment lifecycle. We either
108
// quit, continue, or exit the lifecycle, see details below.
109
type stateStep uint8
110

111
const (
112
        // stepSkip is used when we need to skip the current lifecycle and jump
113
        // to the next one.
114
        stepSkip stateStep = iota
115

116
        // stepProceed is used when we can proceed the current lifecycle.
117
        stepProceed
118

119
        // stepExit is used when we need to quit the current lifecycle.
120
        stepExit
121
)
122

123
// decideNextStep is used to determine the next step in the payment lifecycle.
124
// It first checks whether the current state of the payment allows more HTLC
125
// attempts to be made. If allowed, it will return so the lifecycle can continue
126
// making new attempts. Otherwise, it checks whether we need to wait for the
127
// results of already sent attempts. If needed, it will block until one of the
128
// results is sent back. then process its result here. When there's no need to
129
// wait for results, the method will exit with `stepExit` such that the payment
130
// lifecycle loop will terminate.
131
func (p *paymentLifecycle) decideNextStep(
132
        payment paymentsdb.DBMPPayment) (stateStep, error) {
2✔
133

2✔
134
        // Check whether we could make new HTLC attempts.
2✔
135
        allow, err := payment.AllowMoreAttempts()
2✔
136
        if err != nil {
2✔
UNCOV
137
                return stepExit, err
×
UNCOV
138
        }
×
139

140
        // Exit early we need to make more attempts.
141
        if allow {
4✔
142
                return stepProceed, nil
2✔
143
        }
2✔
144

145
        // We cannot make more attempts, we now check whether we need to wait
146
        // for results.
147
        wait, err := payment.NeedWaitAttempts()
2✔
148
        if err != nil {
2✔
UNCOV
149
                return stepExit, err
×
UNCOV
150
        }
×
151

152
        // If we are not allowed to make new HTLC attempts and there's no need
153
        // to wait, the lifecycle is done and we can exit.
154
        if !wait {
4✔
155
                return stepExit, nil
2✔
156
        }
2✔
157

158
        log.Tracef("Waiting for attempt results for payment %v", p.identifier)
2✔
159

2✔
160
        // Otherwise we wait for the result for one HTLC attempt then continue
2✔
161
        // the lifecycle.
2✔
162
        select {
2✔
163
        case r := <-p.resultCollected:
2✔
164
                log.Tracef("Received attempt result for payment %v",
2✔
165
                        p.identifier)
2✔
166

2✔
167
                // Handle the result here. If there's no error, we will return
2✔
168
                // stepSkip and move to the next lifecycle iteration, which will
2✔
169
                // refresh the payment and wait for the next attempt result, if
2✔
170
                // any.
2✔
171
                _, err := p.handleAttemptResult(r.attempt, r.result)
2✔
172

2✔
173
                // We would only get a DB-related error here, which will cause
2✔
174
                // us to abort the payment flow.
2✔
175
                if err != nil {
2✔
UNCOV
176
                        return stepExit, err
×
UNCOV
177
                }
×
178

UNCOV
179
        case <-p.quit:
×
UNCOV
180
                return stepExit, ErrPaymentLifecycleExiting
×
181

182
        case <-p.router.quit:
2✔
183
                return stepExit, ErrRouterShuttingDown
2✔
184
        }
185

186
        return stepSkip, nil
2✔
187
}
188

189
// resumePayment resumes the paymentLifecycle from the current state.
190
func (p *paymentLifecycle) resumePayment(ctx context.Context) ([32]byte,
191
        *route.Route, error) {
2✔
192

2✔
193
        // When the payment lifecycle loop exits, we make sure to signal any
2✔
194
        // sub goroutine of the HTLC attempt to exit, then wait for them to
2✔
195
        // return.
2✔
196
        defer p.stop()
2✔
197

2✔
198
        // If we had any existing attempts outstanding, we'll start by spinning
2✔
199
        // up goroutines that'll collect their results and deliver them to the
2✔
200
        // lifecycle loop below.
2✔
201
        payment, err := p.reloadInflightAttempts()
2✔
202
        if err != nil {
2✔
UNCOV
203
                return [32]byte{}, nil, err
×
UNCOV
204
        }
×
205

206
        // Get the payment status.
207
        status := payment.GetStatus()
2✔
208

2✔
209
        // exitWithErr is a helper closure that logs and returns an error.
2✔
210
        exitWithErr := func(err error) ([32]byte, *route.Route, error) {
4✔
211
                // Log an error with the latest payment status.
2✔
212
                //
2✔
213
                // NOTE: this `status` variable is reassigned in the loop
2✔
214
                // below. We could also call `payment.GetStatus` here, but in a
2✔
215
                // rare case when the critical log is triggered when using
2✔
216
                // postgres as db backend, the `payment` could be nil, causing
2✔
217
                // the payment fetching to return an error.
2✔
218
                log.Errorf("Payment %v with status=%v failed: %v", p.identifier,
2✔
219
                        status, err)
2✔
220

2✔
221
                return [32]byte{}, nil, err
2✔
222
        }
2✔
223

224
        // We'll continue until either our payment succeeds, or we encounter a
225
        // critical error during path finding.
226
lifecycle:
2✔
227
        for {
4✔
228
                // Before we attempt any new shard, we'll check to see if we've
2✔
229
                // gone past the payment attempt timeout or if the context was
2✔
230
                // canceled. If the context is done, the payment is marked as
2✔
231
                // failed and we reload the latest payment state to reflect
2✔
232
                // this.
2✔
233
                //
2✔
234
                // NOTE: This can be called several times if there are more
2✔
235
                // attempts to be resolved after the timeout or context is
2✔
236
                // cancelled.
2✔
237
                if err := p.checkContext(ctx); err != nil {
2✔
UNCOV
238
                        return exitWithErr(err)
×
UNCOV
239
                }
×
240

241
                // We update the payment state on every iteration.
242
                currentPayment, ps, err := p.reloadPayment()
2✔
243
                if err != nil {
2✔
244
                        return exitWithErr(err)
×
245
                }
×
246

247
                // Reassign status so it can be read in `exitWithErr`.
248
                status = currentPayment.GetStatus()
2✔
249

2✔
250
                // Reassign payment such that when the lifecycle exits, the
2✔
251
                // latest payment can be read when we access its terminal info.
2✔
252
                payment = currentPayment
2✔
253

2✔
254
                // We now proceed our lifecycle with the following tasks in
2✔
255
                // order,
2✔
256
                //   1. request route.
2✔
257
                //   2. create HTLC attempt.
2✔
258
                //   3. send HTLC attempt.
2✔
259
                //   4. collect HTLC attempt result.
2✔
260
                //
2✔
261

2✔
262
                // Now decide the next step of the current lifecycle.
2✔
263
                step, err := p.decideNextStep(payment)
2✔
264
                if err != nil {
4✔
265
                        return exitWithErr(err)
2✔
266
                }
2✔
267

268
                switch step {
2✔
269
                // Exit the for loop and return below.
270
                case stepExit:
2✔
271
                        break lifecycle
2✔
272

273
                // Continue the for loop and skip the rest.
274
                case stepSkip:
2✔
275
                        continue lifecycle
2✔
276

277
                // Continue the for loop and proceed the rest.
278
                case stepProceed:
2✔
279

280
                // Unknown step received, exit with an error.
281
                default:
×
282
                        err = fmt.Errorf("unknown step: %v", step)
×
283
                        return exitWithErr(err)
×
284
                }
285

286
                // Now request a route to be used to create our HTLC attempt.
287
                rt, err := p.requestRoute(ps)
2✔
288
                if err != nil {
2✔
UNCOV
289
                        return exitWithErr(err)
×
UNCOV
290
                }
×
291

292
                // We may not be able to find a route for current attempt. In
293
                // that case, we continue the loop and move straight to the
294
                // next iteration in case there are results for inflight HTLCs
295
                // that still need to be collected.
296
                if rt == nil {
4✔
297
                        log.Errorf("No route found for payment %v",
2✔
298
                                p.identifier)
2✔
299

2✔
300
                        continue lifecycle
2✔
301
                }
302

303
                log.Tracef("Found route: %s", lnutils.SpewLogClosure(rt.Hops))
2✔
304

2✔
305
                // We found a route to try, create a new HTLC attempt to try.
2✔
306
                attempt, err := p.registerAttempt(rt, ps.RemainingAmt)
2✔
307
                if err != nil {
2✔
UNCOV
308
                        return exitWithErr(err)
×
UNCOV
309
                }
×
310

311
                // Once the attempt is created, send it to the htlcswitch.
312
                result, err := p.sendAttempt(attempt)
2✔
313
                if err != nil {
2✔
UNCOV
314
                        return exitWithErr(err)
×
UNCOV
315
                }
×
316

317
                // Now that the shard was successfully sent, launch a go
318
                // routine that will handle its result when its back.
319
                if result.err == nil {
4✔
320
                        p.resultCollector(attempt)
2✔
321
                }
2✔
322
        }
323

324
        // Once we are out the lifecycle loop, it means we've reached a
325
        // terminal condition. We either return the settled preimage or the
326
        // payment's failure reason.
327
        //
328
        // Optionally delete the failed attempts from the database. Depends on
329
        // the database options deleting attempts is not allowed so this will
330
        // just be a no-op.
331
        err = p.router.cfg.Control.DeleteFailedAttempts(p.identifier)
2✔
332
        if err != nil {
2✔
333
                log.Errorf("Error deleting failed htlc attempts for payment "+
×
334
                        "%v: %v", p.identifier, err)
×
335
        }
×
336

337
        htlc, failure := payment.TerminalInfo()
2✔
338
        if htlc != nil {
4✔
339
                return htlc.Settle.Preimage, &htlc.Route, nil
2✔
340
        }
2✔
341

342
        // Otherwise return the payment failure reason.
343
        return [32]byte{}, nil, *failure
2✔
344
}
345

346
// checkContext checks whether the payment context has been canceled.
347
// Cancellation occurs manually or if the context times out.
348
func (p *paymentLifecycle) checkContext(ctx context.Context) error {
2✔
349
        select {
2✔
350
        case <-ctx.Done():
2✔
351
                // If the context was canceled, we'll mark the payment as
2✔
352
                // failed. There are two cases to distinguish here: Either a
2✔
353
                // user-provided timeout was reached, or the context was
2✔
354
                // canceled, either to a manual cancellation or due to an
2✔
355
                // unknown error.
2✔
356
                var reason paymentsdb.FailureReason
2✔
357
                if errors.Is(ctx.Err(), context.DeadlineExceeded) {
2✔
UNCOV
358
                        reason = paymentsdb.FailureReasonTimeout
×
UNCOV
359
                        log.Warnf("Payment attempt not completed before "+
×
UNCOV
360
                                "context timeout, id=%s", p.identifier.String())
×
361
                } else {
2✔
362
                        reason = paymentsdb.FailureReasonCanceled
2✔
363
                        log.Warnf("Payment attempt context canceled, id=%s",
2✔
364
                                p.identifier.String())
2✔
365
                }
2✔
366

367
                // By marking the payment failed, depending on whether it has
368
                // inflight HTLCs or not, its status will now either be
369
                // `StatusInflight` or `StatusFailed`. In either case, no more
370
                // HTLCs will be attempted.
371
                err := p.router.cfg.Control.FailPayment(p.identifier, reason)
2✔
372
                if err != nil {
2✔
UNCOV
373
                        return fmt.Errorf("FailPayment got %w", err)
×
UNCOV
374
                }
×
375

UNCOV
376
        case <-p.router.quit:
×
UNCOV
377
                return fmt.Errorf("check payment timeout got: %w",
×
UNCOV
378
                        ErrRouterShuttingDown)
×
379

380
        // Fall through if we haven't hit our time limit.
381
        default:
2✔
382
        }
383

384
        return nil
2✔
385
}
386

387
// requestRoute is responsible for finding a route to be used to create an HTLC
388
// attempt.
389
func (p *paymentLifecycle) requestRoute(
390
        ps *paymentsdb.MPPaymentState) (*route.Route, error) {
2✔
391

2✔
392
        remainingFees := p.calcFeeBudget(ps.FeesPaid)
2✔
393

2✔
394
        // Query our payment session to construct a route.
2✔
395
        rt, err := p.paySession.RequestRoute(
2✔
396
                ps.RemainingAmt, remainingFees,
2✔
397
                uint32(ps.NumAttemptsInFlight), uint32(p.currentHeight),
2✔
398
                p.firstHopCustomRecords,
2✔
399
        )
2✔
400

2✔
401
        // Exit early if there's no error.
2✔
402
        if err == nil {
4✔
403
                // Allow the traffic shaper to add custom records to the
2✔
404
                // outgoing HTLC and also adjust the amount if needed.
2✔
405
                err = p.amendFirstHopData(rt)
2✔
406
                if err != nil {
2✔
407
                        return nil, err
×
408
                }
×
409

410
                return rt, nil
2✔
411
        }
412

413
        // Otherwise we need to handle the error.
414
        log.Warnf("Failed to find route for payment %v: %v", p.identifier, err)
2✔
415

2✔
416
        // If the error belongs to `noRouteError` set, it means a non-critical
2✔
417
        // error has happened during path finding, and we will mark the payment
2✔
418
        // failed with this reason. Otherwise, we'll return the critical error
2✔
419
        // found to abort the lifecycle.
2✔
420
        var routeErr noRouteError
2✔
421
        if !errors.As(err, &routeErr) {
2✔
UNCOV
422
                return nil, fmt.Errorf("requestRoute got: %w", err)
×
UNCOV
423
        }
×
424

425
        // It's the `paymentSession`'s responsibility to find a route for us
426
        // with the best effort. When it cannot find a path, we need to treat it
427
        // as a terminal condition and fail the payment no matter it has
428
        // inflight HTLCs or not.
429
        failureCode := routeErr.FailureReason()
2✔
430
        log.Warnf("Marking payment %v permanently failed with no route: %v",
2✔
431
                p.identifier, failureCode)
2✔
432

2✔
433
        err = p.router.cfg.Control.FailPayment(p.identifier, failureCode)
2✔
434
        if err != nil {
2✔
UNCOV
435
                return nil, fmt.Errorf("FailPayment got: %w", err)
×
UNCOV
436
        }
×
437

438
        // NOTE: we decide to not return the non-critical noRouteError here to
439
        // avoid terminating the payment lifecycle as there might be other
440
        // inflight HTLCs which we must wait for their results.
441
        return nil, nil
2✔
442
}
443

444
// stop signals any active shard goroutine to exit.
445
func (p *paymentLifecycle) stop() {
2✔
446
        close(p.quit)
2✔
447
}
2✔
448

449
// attemptResult holds the HTLC attempt and a possible error returned from
450
// sending it.
451
type attemptResult struct {
452
        // err is non-nil if a non-critical error was encountered when trying
453
        // to send the attempt, and we successfully updated the control tower
454
        // to reflect this error. This can be errors like not enough local
455
        // balance for the given route etc.
456
        err error
457

458
        // attempt is the attempt structure as recorded in the database.
459
        attempt *paymentsdb.HTLCAttempt
460
}
461

462
// collectResultAsync launches a goroutine that will wait for the result of the
463
// given HTLC attempt to be available then save its result in a map. Once
464
// received, it will send the result returned from the switch to channel
465
// `resultCollected`.
466
func (p *paymentLifecycle) collectResultAsync(attempt *paymentsdb.HTLCAttempt) {
2✔
467
        log.Debugf("Collecting result for attempt %v in payment %v",
2✔
468
                attempt.AttemptID, p.identifier)
2✔
469

2✔
470
        go func() {
4✔
471
                result, err := p.collectResult(attempt)
2✔
472
                if err != nil {
4✔
473
                        log.Errorf("Error collecting result for attempt %v in "+
2✔
474
                                "payment %v: %v", attempt.AttemptID,
2✔
475
                                p.identifier, err)
2✔
476

2✔
477
                        return
2✔
478
                }
2✔
479

480
                log.Debugf("Result collected for attempt %v in payment %v",
2✔
481
                        attempt.AttemptID, p.identifier)
2✔
482

2✔
483
                // Create a switch result and send it to the resultCollected
2✔
484
                // chan, which gets processed when the lifecycle is waiting for
2✔
485
                // a result to be received in decideNextStep.
2✔
486
                r := &switchResult{
2✔
487
                        attempt: attempt,
2✔
488
                        result:  result,
2✔
489
                }
2✔
490

2✔
491
                // Signal that a result has been collected.
2✔
492
                select {
2✔
493
                // Send the result so decideNextStep can proceed.
494
                case p.resultCollected <- r:
2✔
495

496
                case <-p.quit:
×
497
                        log.Debugf("Lifecycle exiting while collecting "+
×
498
                                "result for payment %v", p.identifier)
×
499

500
                case <-p.router.quit:
×
501
                }
502
        }()
503
}
504

505
// collectResult waits for the result of the given HTLC attempt to be sent by
506
// the switch and returns it.
507
func (p *paymentLifecycle) collectResult(
508
        attempt *paymentsdb.HTLCAttempt) (*htlcswitch.PaymentResult, error) {
2✔
509

2✔
510
        log.Tracef("Collecting result for attempt %v",
2✔
511
                lnutils.SpewLogClosure(attempt))
2✔
512

2✔
513
        result := &htlcswitch.PaymentResult{}
2✔
514

2✔
515
        // Regenerate the circuit for this attempt.
2✔
516
        circuit, err := attempt.Circuit()
2✔
517

2✔
518
        // TODO(yy): We generate this circuit to create the error decryptor,
2✔
519
        // which is then used in htlcswitch as the deobfuscator to decode the
2✔
520
        // error from `UpdateFailHTLC`. However, suppose it's an
2✔
521
        // `UpdateFulfillHTLC` message yet for some reason the sphinx packet is
2✔
522
        // failed to be generated, we'd miss settling it. This means we should
2✔
523
        // give it a second chance to try the settlement path in case
2✔
524
        // `GetAttemptResult` gives us back the preimage. And move the circuit
2✔
525
        // creation into htlcswitch so it's only constructed when there's a
2✔
526
        // failure message we need to decode.
2✔
527
        if err != nil {
2✔
528
                log.Debugf("Unable to generate circuit for attempt %v: %v",
×
529
                        attempt.AttemptID, err)
×
530
                return nil, err
×
531
        }
×
532

533
        // Using the created circuit, initialize the error decrypter, so we can
534
        // parse+decode any failures incurred by this payment within the
535
        // switch.
536
        errorDecryptor := &htlcswitch.SphinxErrorDecrypter{
2✔
537
                OnionErrorDecrypter: sphinx.NewOnionErrorDecrypter(circuit),
2✔
538
        }
2✔
539

2✔
540
        // Now ask the switch to return the result of the payment when
2✔
541
        // available.
2✔
542
        //
2✔
543
        // TODO(yy): consider using htlcswitch to create the `errorDecryptor`
2✔
544
        // since the htlc is already in db. This will also make the interface
2✔
545
        // `PaymentAttemptDispatcher` deeper and easier to use. Moreover, we'd
2✔
546
        // only create the decryptor when received a failure, further saving us
2✔
547
        // a few CPU cycles.
2✔
548
        resultChan, err := p.router.cfg.Payer.GetAttemptResult(
2✔
549
                attempt.AttemptID, p.identifier, errorDecryptor,
2✔
550
        )
2✔
551
        // Handle the switch error.
2✔
552
        if err != nil {
2✔
UNCOV
553
                log.Errorf("Failed getting result for attemptID %d "+
×
UNCOV
554
                        "from switch: %v", attempt.AttemptID, err)
×
UNCOV
555

×
UNCOV
556
                result.Error = err
×
UNCOV
557

×
UNCOV
558
                return result, nil
×
UNCOV
559
        }
×
560

561
        // The switch knows about this payment, we'll wait for a result to be
562
        // available.
563
        select {
2✔
564
        case r, ok := <-resultChan:
2✔
565
                if !ok {
4✔
566
                        return nil, htlcswitch.ErrSwitchExiting
2✔
567
                }
2✔
568

569
                result = r
2✔
570

UNCOV
571
        case <-p.quit:
×
UNCOV
572
                return nil, ErrPaymentLifecycleExiting
×
573

UNCOV
574
        case <-p.router.quit:
×
UNCOV
575
                return nil, ErrRouterShuttingDown
×
576
        }
577

578
        return result, nil
2✔
579
}
580

581
// registerAttempt is responsible for creating and saving an HTLC attempt in db
582
// by using the route info provided. The `remainingAmt` is used to decide
583
// whether this is the last attempt.
584
func (p *paymentLifecycle) registerAttempt(rt *route.Route,
585
        remainingAmt lnwire.MilliSatoshi) (*paymentsdb.HTLCAttempt, error) {
2✔
586

2✔
587
        // If this route will consume the last remaining amount to send
2✔
588
        // to the receiver, this will be our last shard (for now).
2✔
589
        isLastAttempt := rt.ReceiverAmt() == remainingAmt
2✔
590

2✔
591
        // Using the route received from the payment session, create a new
2✔
592
        // shard to send.
2✔
593
        attempt, err := p.createNewPaymentAttempt(rt, isLastAttempt)
2✔
594
        if err != nil {
2✔
UNCOV
595
                return nil, err
×
UNCOV
596
        }
×
597

598
        // Before sending this HTLC to the switch, we checkpoint the fresh
599
        // paymentID and route to the DB. This lets us know on startup the ID
600
        // of the payment that we attempted to send, such that we can query the
601
        // Switch for its whereabouts. The route is needed to handle the result
602
        // when it eventually comes back.
603
        err = p.router.cfg.Control.RegisterAttempt(
2✔
604
                p.identifier, &attempt.HTLCAttemptInfo,
2✔
605
        )
2✔
606

2✔
607
        return attempt, err
2✔
608
}
609

610
// createNewPaymentAttempt creates a new payment attempt from the given route.
611
func (p *paymentLifecycle) createNewPaymentAttempt(rt *route.Route,
612
        lastShard bool) (*paymentsdb.HTLCAttempt, error) {
2✔
613

2✔
614
        // Generate a new key to be used for this attempt.
2✔
615
        sessionKey, err := generateNewSessionKey()
2✔
616
        if err != nil {
2✔
617
                return nil, err
×
618
        }
×
619

620
        // We generate a new, unique payment ID that we will use for
621
        // this HTLC.
622
        attemptID, err := p.router.cfg.NextPaymentID()
2✔
623
        if err != nil {
2✔
624
                return nil, err
×
625
        }
×
626

627
        // Request a new shard from the ShardTracker. If this is an AMP
628
        // payment, and this is the last shard, the outstanding shards together
629
        // with this one will be enough for the receiver to derive all HTLC
630
        // preimages. If this a non-AMP payment, the ShardTracker will return a
631
        // simple shard with the payment's static payment hash.
632
        shard, err := p.shardTracker.NewShard(attemptID, lastShard)
2✔
633
        if err != nil {
2✔
UNCOV
634
                return nil, err
×
UNCOV
635
        }
×
636

637
        // If this shard carries MPP or AMP options, add them to the last hop
638
        // on the route.
639
        hop := rt.Hops[len(rt.Hops)-1]
2✔
640
        if shard.MPP() != nil {
4✔
641
                hop.MPP = shard.MPP()
2✔
642
        }
2✔
643

644
        if shard.AMP() != nil {
4✔
645
                hop.AMP = shard.AMP()
2✔
646
        }
2✔
647

648
        hash := shard.Hash()
2✔
649

2✔
650
        // We now have all the information needed to populate the current
2✔
651
        // attempt information.
2✔
652
        return paymentsdb.NewHtlcAttempt(
2✔
653
                attemptID, sessionKey, *rt, p.router.cfg.Clock.Now(), &hash,
2✔
654
        )
2✔
655
}
656

657
// sendAttempt attempts to send the current attempt to the switch to complete
658
// the payment. If this attempt fails, then we'll continue on to the next
659
// available route.
660
func (p *paymentLifecycle) sendAttempt(
661
        attempt *paymentsdb.HTLCAttempt) (*attemptResult, error) {
2✔
662

2✔
663
        log.Debugf("Sending HTLC attempt(id=%v, total_amt=%v, first_hop_amt=%d"+
2✔
664
                ") for payment %v", attempt.AttemptID,
2✔
665
                attempt.Route.TotalAmount, attempt.Route.FirstHopAmount.Val,
2✔
666
                p.identifier)
2✔
667

2✔
668
        rt := attempt.Route
2✔
669

2✔
670
        // Construct the first hop.
2✔
671
        firstHop := lnwire.NewShortChanIDFromInt(rt.Hops[0].ChannelID)
2✔
672

2✔
673
        // Craft an HTLC packet to send to the htlcswitch. The metadata within
2✔
674
        // this packet will be used to route the payment through the network,
2✔
675
        // starting with the first-hop.
2✔
676
        htlcAdd := &lnwire.UpdateAddHTLC{
2✔
677
                Amount:        rt.FirstHopAmount.Val.Int(),
2✔
678
                Expiry:        rt.TotalTimeLock,
2✔
679
                PaymentHash:   *attempt.Hash,
2✔
680
                CustomRecords: rt.FirstHopWireCustomRecords,
2✔
681
        }
2✔
682

2✔
683
        // Generate the raw encoded sphinx packet to be included along
2✔
684
        // with the htlcAdd message that we send directly to the
2✔
685
        // switch.
2✔
686
        onionBlob, err := attempt.OnionBlob()
2✔
687
        if err != nil {
2✔
688
                log.Errorf("Failed to create onion blob: attempt=%d in "+
×
689
                        "payment=%v, err:%v", attempt.AttemptID,
×
690
                        p.identifier, err)
×
691

×
692
                return p.failAttempt(attempt.AttemptID, err)
×
693
        }
×
694

695
        htlcAdd.OnionBlob = onionBlob
2✔
696

2✔
697
        // Send it to the Switch. When this method returns we assume
2✔
698
        // the Switch successfully has persisted the payment attempt,
2✔
699
        // such that we can resume waiting for the result after a
2✔
700
        // restart.
2✔
701
        err = p.router.cfg.Payer.SendHTLC(firstHop, attempt.AttemptID, htlcAdd)
2✔
702
        if err != nil {
4✔
703
                log.Errorf("Failed sending attempt %d for payment %v to "+
2✔
704
                        "switch: %v", attempt.AttemptID, p.identifier, err)
2✔
705

2✔
706
                return p.handleSwitchErr(attempt, err)
2✔
707
        }
2✔
708

709
        log.Debugf("Attempt %v for payment %v successfully sent to switch, "+
2✔
710
                "route: %v", attempt.AttemptID, p.identifier, &attempt.Route)
2✔
711

2✔
712
        return &attemptResult{
2✔
713
                attempt: attempt,
2✔
714
        }, nil
2✔
715
}
716

717
// amendFirstHopData is a function that calls the traffic shaper to allow it to
718
// add custom records to the outgoing HTLC and also adjust the amount if
719
// needed.
720
func (p *paymentLifecycle) amendFirstHopData(rt *route.Route) error {
2✔
721
        // The first hop amount on the route is the full route amount if not
2✔
722
        // overwritten by the traffic shaper. So we set the initial value now
2✔
723
        // and potentially overwrite it later.
2✔
724
        rt.FirstHopAmount = tlv.NewRecordT[tlv.TlvType0](
2✔
725
                tlv.NewBigSizeT(rt.TotalAmount),
2✔
726
        )
2✔
727

2✔
728
        // By default, we set the first hop custom records to the initial
2✔
729
        // value requested by the RPC. The traffic shaper may overwrite this
2✔
730
        // value.
2✔
731
        rt.FirstHopWireCustomRecords = p.firstHopCustomRecords
2✔
732

2✔
733
        if len(rt.Hops) == 0 {
2✔
734
                return fmt.Errorf("cannot amend first hop data, route length " +
×
735
                        "is zero")
×
736
        }
×
737

738
        firstHopPK := rt.Hops[0].PubKeyBytes
2✔
739

2✔
740
        // extraDataRequest is a helper struct to pass the custom records and
2✔
741
        // amount back from the traffic shaper.
2✔
742
        type extraDataRequest struct {
2✔
743
                customRecords fn.Option[lnwire.CustomRecords]
2✔
744

2✔
745
                amount fn.Option[lnwire.MilliSatoshi]
2✔
746
        }
2✔
747

2✔
748
        // If a hook exists that may affect our outgoing message, we call it now
2✔
749
        // and apply its side effects to the UpdateAddHTLC message.
2✔
750
        result, err := fn.MapOptionZ(
2✔
751
                p.router.cfg.TrafficShaper,
2✔
752
                //nolint:ll
2✔
753
                func(ts htlcswitch.AuxTrafficShaper) fn.Result[extraDataRequest] {
2✔
UNCOV
754
                        newAmt, newRecords, err := ts.ProduceHtlcExtraData(
×
UNCOV
755
                                rt.TotalAmount, p.firstHopCustomRecords,
×
UNCOV
756
                                firstHopPK,
×
UNCOV
757
                        )
×
UNCOV
758
                        if err != nil {
×
759
                                return fn.Err[extraDataRequest](err)
×
760
                        }
×
761

762
                        // Make sure we only received valid records.
UNCOV
763
                        if err := newRecords.Validate(); err != nil {
×
764
                                return fn.Err[extraDataRequest](err)
×
765
                        }
×
766

UNCOV
767
                        log.Debugf("Aux traffic shaper returned custom "+
×
UNCOV
768
                                "records %v and amount %d msat for HTLC",
×
UNCOV
769
                                lnutils.SpewLogClosure(newRecords), newAmt)
×
UNCOV
770

×
UNCOV
771
                        return fn.Ok(extraDataRequest{
×
UNCOV
772
                                customRecords: fn.Some(newRecords),
×
UNCOV
773
                                amount:        fn.Some(newAmt),
×
UNCOV
774
                        })
×
775
                },
776
        ).Unpack()
777
        if err != nil {
2✔
778
                return fmt.Errorf("traffic shaper failed to produce extra "+
×
779
                        "data: %w", err)
×
780
        }
×
781

782
        // Apply the side effects to the UpdateAddHTLC message.
783
        result.customRecords.WhenSome(func(records lnwire.CustomRecords) {
2✔
UNCOV
784
                rt.FirstHopWireCustomRecords = records
×
UNCOV
785
        })
×
786
        result.amount.WhenSome(func(amount lnwire.MilliSatoshi) {
2✔
UNCOV
787
                rt.FirstHopAmount = tlv.NewRecordT[tlv.TlvType0](
×
UNCOV
788
                        tlv.NewBigSizeT(amount),
×
UNCOV
789
                )
×
UNCOV
790
        })
×
791

792
        return nil
2✔
793
}
794

795
// failAttemptAndPayment fails both the payment and its attempt via the
796
// router's control tower, which marks the payment as failed in db.
797
func (p *paymentLifecycle) failPaymentAndAttempt(
798
        attemptID uint64, reason *paymentsdb.FailureReason,
799
        sendErr error) (*attemptResult, error) {
2✔
800

2✔
801
        log.Errorf("Payment %v failed: final_outcome=%v, raw_err=%v",
2✔
802
                p.identifier, *reason, sendErr)
2✔
803

2✔
804
        // Fail the payment via control tower.
2✔
805
        //
2✔
806
        // NOTE: we must fail the payment first before failing the attempt.
2✔
807
        // Otherwise, once the attempt is marked as failed, another goroutine
2✔
808
        // might make another attempt while we are failing the payment.
2✔
809
        err := p.router.cfg.Control.FailPayment(p.identifier, *reason)
2✔
810
        if err != nil {
2✔
811
                log.Errorf("Unable to fail payment: %v", err)
×
812
                return nil, err
×
813
        }
×
814

815
        // Fail the attempt.
816
        return p.failAttempt(attemptID, sendErr)
2✔
817
}
818

819
// handleSwitchErr inspects the given error from the Switch and determines
820
// whether we should make another payment attempt, or if it should be
821
// considered a terminal error. Terminal errors will be recorded with the
822
// control tower. It analyzes the sendErr for the payment attempt received from
823
// the switch and updates mission control and/or channel policies. Depending on
824
// the error type, the error is either the final outcome of the payment or we
825
// need to continue with an alternative route. A final outcome is indicated by
826
// a non-nil reason value.
827
func (p *paymentLifecycle) handleSwitchErr(attempt *paymentsdb.HTLCAttempt,
828
        sendErr error) (*attemptResult, error) {
2✔
829

2✔
830
        internalErrorReason := paymentsdb.FailureReasonError
2✔
831
        attemptID := attempt.AttemptID
2✔
832

2✔
833
        // reportAndFail is a helper closure that reports the failure to the
2✔
834
        // mission control, which helps us to decide whether we want to retry
2✔
835
        // the payment or not. If a non nil reason is returned from mission
2✔
836
        // control, it will further fail the payment via control tower.
2✔
837
        reportAndFail := func(srcIdx *int,
2✔
838
                msg lnwire.FailureMessage) (*attemptResult, error) {
4✔
839

2✔
840
                // Report outcome to mission control.
2✔
841
                reason, err := p.router.cfg.MissionControl.ReportPaymentFail(
2✔
842
                        attemptID, &attempt.Route, srcIdx, msg,
2✔
843
                )
2✔
844
                if err != nil {
2✔
845
                        log.Errorf("Error reporting payment result to mc: %v",
×
846
                                err)
×
847

×
848
                        reason = &internalErrorReason
×
849
                }
×
850

851
                // Fail the attempt only if there's no reason.
852
                if reason == nil {
4✔
853
                        // Fail the attempt.
2✔
854
                        return p.failAttempt(attemptID, sendErr)
2✔
855
                }
2✔
856

857
                // Otherwise fail both the payment and the attempt.
858
                return p.failPaymentAndAttempt(attemptID, reason, sendErr)
2✔
859
        }
860

861
        // If this attempt ID is unknown to the Switch, it means it was never
862
        // checkpointed and forwarded by the switch before a restart. In this
863
        // case we can safely send a new payment attempt, and wait for its
864
        // result to be available.
865
        if errors.Is(sendErr, htlcswitch.ErrPaymentIDNotFound) {
2✔
UNCOV
866
                log.Warnf("Failing attempt=%v for payment=%v as it's not "+
×
UNCOV
867
                        "found in the Switch", attempt.AttemptID, p.identifier)
×
UNCOV
868

×
UNCOV
869
                return p.failAttempt(attemptID, sendErr)
×
UNCOV
870
        }
×
871

872
        if errors.Is(sendErr, htlcswitch.ErrUnreadableFailureMessage) {
2✔
UNCOV
873
                log.Warn("Unreadable failure when sending htlc: id=%v, hash=%v",
×
UNCOV
874
                        attempt.AttemptID, attempt.Hash)
×
UNCOV
875

×
UNCOV
876
                // Since this error message cannot be decrypted, we will send a
×
UNCOV
877
                // nil error message to our mission controller and fail the
×
UNCOV
878
                // payment.
×
UNCOV
879
                return reportAndFail(nil, nil)
×
UNCOV
880
        }
×
881

882
        // If the error is a ClearTextError, we have received a valid wire
883
        // failure message, either from our own outgoing link or from a node
884
        // down the route. If the error is not related to the propagation of
885
        // our payment, we can stop trying because an internal error has
886
        // occurred.
887
        var rtErr htlcswitch.ClearTextError
2✔
888
        ok := errors.As(sendErr, &rtErr)
2✔
889
        if !ok {
2✔
UNCOV
890
                return p.failPaymentAndAttempt(
×
UNCOV
891
                        attemptID, &internalErrorReason, sendErr,
×
UNCOV
892
                )
×
UNCOV
893
        }
×
894

895
        // failureSourceIdx is the index of the node that the failure occurred
896
        // at. If the ClearTextError received is not a ForwardingError the
897
        // payment error occurred at our node, so we leave this value as 0
898
        // to indicate that the failure occurred locally. If the error is a
899
        // ForwardingError, it did not originate at our node, so we set
900
        // failureSourceIdx to the index of the node where the failure occurred.
901
        failureSourceIdx := 0
2✔
902
        var source *htlcswitch.ForwardingError
2✔
903
        ok = errors.As(rtErr, &source)
2✔
904
        if ok {
4✔
905
                failureSourceIdx = source.FailureSourceIdx
2✔
906
        }
2✔
907

908
        // Extract the wire failure and apply channel update if it contains one.
909
        // If we received an unknown failure message from a node along the
910
        // route, the failure message will be nil.
911
        failureMessage := rtErr.WireMessage()
2✔
912
        err := p.handleFailureMessage(
2✔
913
                &attempt.Route, failureSourceIdx, failureMessage,
2✔
914
        )
2✔
915
        if err != nil {
2✔
916
                return p.failPaymentAndAttempt(
×
917
                        attemptID, &internalErrorReason, sendErr,
×
918
                )
×
919
        }
×
920

921
        log.Tracef("Node=%v reported failure when sending htlc",
2✔
922
                failureSourceIdx)
2✔
923

2✔
924
        return reportAndFail(&failureSourceIdx, failureMessage)
2✔
925
}
926

927
// handleFailureMessage tries to apply a channel update present in the failure
928
// message if any.
929
func (p *paymentLifecycle) handleFailureMessage(rt *route.Route,
930
        errorSourceIdx int, failure lnwire.FailureMessage) error {
2✔
931

2✔
932
        if failure == nil {
2✔
UNCOV
933
                return nil
×
UNCOV
934
        }
×
935

936
        // It makes no sense to apply our own channel updates.
937
        if errorSourceIdx == 0 {
4✔
938
                log.Errorf("Channel update of ourselves received")
2✔
939

2✔
940
                return nil
2✔
941
        }
2✔
942

943
        // Extract channel update if the error contains one.
944
        update := p.router.extractChannelUpdate(failure)
2✔
945
        if update == nil {
4✔
946
                return nil
2✔
947
        }
2✔
948

949
        // Parse pubkey to allow validation of the channel update. This should
950
        // always succeed, otherwise there is something wrong in our
951
        // implementation. Therefore, return an error.
952
        errVertex := rt.Hops[errorSourceIdx-1].PubKeyBytes
2✔
953
        errSource, err := btcec.ParsePubKey(errVertex[:])
2✔
954
        if err != nil {
2✔
955
                log.Errorf("Cannot parse pubkey: idx=%v, pubkey=%v",
×
956
                        errorSourceIdx, errVertex)
×
957

×
958
                return err
×
959
        }
×
960

961
        var (
2✔
962
                isAdditionalEdge bool
2✔
963
                policy           *models.CachedEdgePolicy
2✔
964
        )
2✔
965

2✔
966
        // Before we apply the channel update, we need to decide whether the
2✔
967
        // update is for additional (ephemeral) edge or normal edge stored in
2✔
968
        // db.
2✔
969
        //
2✔
970
        // Note: the p.paySession might be nil here if it's called inside
2✔
971
        // SendToRoute where there's no payment lifecycle.
2✔
972
        if p.paySession != nil {
4✔
973
                policy = p.paySession.GetAdditionalEdgePolicy(
2✔
974
                        errSource, update.ShortChannelID.ToUint64(),
2✔
975
                )
2✔
976
                if policy != nil {
4✔
977
                        isAdditionalEdge = true
2✔
978
                }
2✔
979
        }
980

981
        // Apply channel update to additional edge policy.
982
        if isAdditionalEdge {
4✔
983
                if !p.paySession.UpdateAdditionalEdge(
2✔
984
                        update, errSource, policy) {
2✔
985

×
986
                        log.Debugf("Invalid channel update received: node=%v",
×
987
                                errVertex)
×
988
                }
×
989
                return nil
2✔
990
        }
991

992
        // Apply channel update to the channel edge policy in our db.
993
        if !p.router.cfg.ApplyChannelUpdate(update) {
4✔
994
                log.Debugf("Invalid channel update received: node=%v",
2✔
995
                        errVertex)
2✔
996
        }
2✔
997
        return nil
2✔
998
}
999

1000
// failAttempt calls control tower to fail the current payment attempt.
1001
func (p *paymentLifecycle) failAttempt(attemptID uint64,
1002
        sendError error) (*attemptResult, error) {
2✔
1003

2✔
1004
        log.Warnf("Attempt %v for payment %v failed: %v", attemptID,
2✔
1005
                p.identifier, sendError)
2✔
1006

2✔
1007
        failInfo := marshallError(
2✔
1008
                sendError,
2✔
1009
                p.router.cfg.Clock.Now(),
2✔
1010
        )
2✔
1011

2✔
1012
        // Now that we are failing this payment attempt, cancel the shard with
2✔
1013
        // the ShardTracker such that it can derive the correct hash for the
2✔
1014
        // next attempt.
2✔
1015
        if err := p.shardTracker.CancelShard(attemptID); err != nil {
2✔
1016
                return nil, err
×
1017
        }
×
1018

1019
        attempt, err := p.router.cfg.Control.FailAttempt(
2✔
1020
                p.identifier, attemptID, failInfo,
2✔
1021
        )
2✔
1022
        if err != nil {
2✔
UNCOV
1023
                return nil, err
×
UNCOV
1024
        }
×
1025

1026
        return &attemptResult{
2✔
1027
                attempt: attempt,
2✔
1028
                err:     sendError,
2✔
1029
        }, nil
2✔
1030
}
1031

1032
// marshallError marshall an error as received from the switch to a structure
1033
// that is suitable for database storage.
1034
func marshallError(sendError error, time time.Time) *paymentsdb.HTLCFailInfo {
2✔
1035
        response := &paymentsdb.HTLCFailInfo{
2✔
1036
                FailTime: time,
2✔
1037
        }
2✔
1038

2✔
1039
        switch {
2✔
UNCOV
1040
        case errors.Is(sendError, htlcswitch.ErrPaymentIDNotFound):
×
UNCOV
1041
                response.Reason = paymentsdb.HTLCFailInternal
×
UNCOV
1042
                return response
×
1043

UNCOV
1044
        case errors.Is(sendError, htlcswitch.ErrUnreadableFailureMessage):
×
UNCOV
1045
                response.Reason = paymentsdb.HTLCFailUnreadable
×
UNCOV
1046
                return response
×
1047
        }
1048

1049
        var rtErr htlcswitch.ClearTextError
2✔
1050
        ok := errors.As(sendError, &rtErr)
2✔
1051
        if !ok {
2✔
UNCOV
1052
                response.Reason = paymentsdb.HTLCFailInternal
×
UNCOV
1053
                return response
×
UNCOV
1054
        }
×
1055

1056
        message := rtErr.WireMessage()
2✔
1057
        if message != nil {
4✔
1058
                response.Reason = paymentsdb.HTLCFailMessage
2✔
1059
                response.Message = message
2✔
1060
        } else {
2✔
UNCOV
1061
                response.Reason = paymentsdb.HTLCFailUnknown
×
UNCOV
1062
        }
×
1063

1064
        // If the ClearTextError received is a ForwardingError, the error
1065
        // originated from a node along the route, not locally on our outgoing
1066
        // link. We set failureSourceIdx to the index of the node where the
1067
        // failure occurred. If the error is not a ForwardingError, the failure
1068
        // occurred at our node, so we leave the index as 0 to indicate that
1069
        // we failed locally.
1070
        var fErr *htlcswitch.ForwardingError
2✔
1071
        ok = errors.As(rtErr, &fErr)
2✔
1072
        if ok {
4✔
1073
                response.FailureSourceIndex = uint32(fErr.FailureSourceIdx)
2✔
1074
        }
2✔
1075

1076
        return response
2✔
1077
}
1078

1079
// patchLegacyPaymentHash will make a copy of the passed attempt and sets its
1080
// Hash field to be the payment hash if it's nil.
1081
//
1082
// NOTE: For legacy payments, which were created before the AMP feature was
1083
// enabled, the `Hash` field in their HTLC attempts is nil. In that case, we use
1084
// the payment hash as the `attempt.Hash` as they are identical.
1085
func (p *paymentLifecycle) patchLegacyPaymentHash(
1086
        a paymentsdb.HTLCAttempt) paymentsdb.HTLCAttempt {
2✔
1087

2✔
1088
        // Exit early if this is not a legacy attempt.
2✔
1089
        if a.Hash != nil {
4✔
1090
                return a
2✔
1091
        }
2✔
1092

1093
        // Log a warning if the user is still using legacy payments, which has
1094
        // weaker support.
UNCOV
1095
        log.Warnf("Found legacy htlc attempt %v in payment %v", a.AttemptID,
×
UNCOV
1096
                p.identifier)
×
UNCOV
1097

×
UNCOV
1098
        // Set the attempt's hash to be the payment hash, which is the payment's
×
UNCOV
1099
        // `PaymentHash`` in the `PaymentCreationInfo`. For legacy payments
×
UNCOV
1100
        // before AMP feature, the `Hash` field was not set so we use the
×
UNCOV
1101
        // payment hash instead.
×
UNCOV
1102
        //
×
UNCOV
1103
        // NOTE: During the router's startup, we have a similar logic in
×
UNCOV
1104
        // `resumePayments`, in which we will use the payment hash instead if
×
UNCOV
1105
        // the attempt's hash is nil.
×
UNCOV
1106
        a.Hash = &p.identifier
×
UNCOV
1107

×
UNCOV
1108
        return a
×
1109
}
1110

1111
// reloadInflightAttempts is called when the payment lifecycle is resumed after
1112
// a restart. It reloads all inflight attempts from the control tower and
1113
// collects the results of the attempts that have been sent before.
1114
func (p *paymentLifecycle) reloadInflightAttempts() (paymentsdb.DBMPPayment,
1115
        error) {
2✔
1116

2✔
1117
        payment, err := p.router.cfg.Control.FetchPayment(p.identifier)
2✔
1118
        if err != nil {
2✔
UNCOV
1119
                return nil, err
×
UNCOV
1120
        }
×
1121

1122
        for _, a := range payment.InFlightHTLCs() {
4✔
1123
                a := a
2✔
1124

2✔
1125
                log.Infof("Resuming HTLC attempt %v for payment %v",
2✔
1126
                        a.AttemptID, p.identifier)
2✔
1127

2✔
1128
                // Potentially attach the payment hash to the `Hash` field if
2✔
1129
                // it's a legacy payment.
2✔
1130
                a = p.patchLegacyPaymentHash(a)
2✔
1131

2✔
1132
                p.resultCollector(&a)
2✔
1133
        }
2✔
1134

1135
        return payment, nil
2✔
1136
}
1137

1138
// reloadPayment returns the latest payment found in the db (control tower).
1139
func (p *paymentLifecycle) reloadPayment() (paymentsdb.DBMPPayment,
1140
        *paymentsdb.MPPaymentState, error) {
2✔
1141

2✔
1142
        // Read the db to get the latest state of the payment.
2✔
1143
        payment, err := p.router.cfg.Control.FetchPayment(p.identifier)
2✔
1144
        if err != nil {
2✔
1145
                return nil, nil, err
×
1146
        }
×
1147

1148
        ps := payment.GetState()
2✔
1149
        remainingFees := p.calcFeeBudget(ps.FeesPaid)
2✔
1150

2✔
1151
        log.Debugf("Payment %v: status=%v, active_shards=%v, rem_value=%v, "+
2✔
1152
                "fee_limit=%v", p.identifier, payment.GetStatus(),
2✔
1153
                ps.NumAttemptsInFlight, ps.RemainingAmt, remainingFees)
2✔
1154

2✔
1155
        return payment, ps, nil
2✔
1156
}
1157

1158
// handleAttemptResult processes the result of an HTLC attempt returned from
1159
// the htlcswitch.
1160
func (p *paymentLifecycle) handleAttemptResult(attempt *paymentsdb.HTLCAttempt,
1161
        result *htlcswitch.PaymentResult) (*attemptResult, error) {
2✔
1162

2✔
1163
        // If the result has an error, we need to further process it by failing
2✔
1164
        // the attempt and maybe fail the payment.
2✔
1165
        if result.Error != nil {
4✔
1166
                return p.handleSwitchErr(attempt, result.Error)
2✔
1167
        }
2✔
1168

1169
        // We got an attempt settled result back from the switch.
1170
        log.Debugf("Payment(%v): attempt(%v) succeeded", p.identifier,
2✔
1171
                attempt.AttemptID)
2✔
1172

2✔
1173
        // Report success to mission control.
2✔
1174
        err := p.router.cfg.MissionControl.ReportPaymentSuccess(
2✔
1175
                attempt.AttemptID, &attempt.Route,
2✔
1176
        )
2✔
1177
        if err != nil {
2✔
1178
                log.Errorf("Error reporting payment success to mc: %v", err)
×
1179
        }
×
1180

1181
        // In case of success we atomically store settle result to the DB and
1182
        // move the shard to the settled state.
1183
        htlcAttempt, err := p.router.cfg.Control.SettleAttempt(
2✔
1184
                p.identifier, attempt.AttemptID,
2✔
1185
                &paymentsdb.HTLCSettleInfo{
2✔
1186
                        Preimage:   result.Preimage,
2✔
1187
                        SettleTime: p.router.cfg.Clock.Now(),
2✔
1188
                },
2✔
1189
        )
2✔
1190
        if err != nil {
2✔
UNCOV
1191
                log.Errorf("Error settling attempt %v for payment %v with "+
×
UNCOV
1192
                        "preimage %v: %v", attempt.AttemptID, p.identifier,
×
UNCOV
1193
                        result.Preimage, err)
×
UNCOV
1194

×
UNCOV
1195
                // We won't mark the attempt as failed since we already have
×
UNCOV
1196
                // the preimage.
×
UNCOV
1197
                return nil, err
×
UNCOV
1198
        }
×
1199

1200
        return &attemptResult{
2✔
1201
                attempt: htlcAttempt,
2✔
1202
        }, nil
2✔
1203
}
1204

1205
// collectAndHandleResult waits for the result for the given attempt to be
1206
// available from the Switch, then records the attempt outcome with the control
1207
// tower. An attemptResult is returned, indicating the final outcome of this
1208
// HTLC attempt.
1209
func (p *paymentLifecycle) collectAndHandleResult(
1210
        attempt *paymentsdb.HTLCAttempt) (*attemptResult, error) {
2✔
1211

2✔
1212
        result, err := p.collectResult(attempt)
2✔
1213
        if err != nil {
2✔
UNCOV
1214
                return nil, err
×
UNCOV
1215
        }
×
1216

1217
        return p.handleAttemptResult(attempt, result)
2✔
1218
}
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