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

lightningnetwork / lnd / 14051962692

25 Mar 2025 04:54AM UTC coverage: 58.597% (-10.4%) from 69.02%
14051962692

Pull #9626

github

web-flow
Merge fc67963eb into 3351a1745
Pull Request #9626: payment lifecycle small fix

25 of 38 new or added lines in 2 files covered. (65.79%)

27964 existing lines in 447 files now uncovered.

96854 of 165289 relevant lines covered (58.6%)

1.82 hits per line

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

77.15
/routing/payment_lifecycle.go
1
package routing
2

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

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

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

27
// 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 *channeldb.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 *channeldb.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 {
3✔
69

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

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

3✔
85
        return p
3✔
86
}
3✔
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 {
3✔
92

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

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

104
        return budget
3✔
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 DBMPPayment) (stateStep, error) {
3✔
133

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

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

145
        // We cannot make more attempts, we now check whether we need to wait
146
        // for results.
147
        wait, err := payment.NeedWaitAttempts()
3✔
148
        if err != nil {
3✔
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 {
6✔
155
                return stepExit, nil
3✔
156
        }
3✔
157

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

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

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

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

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

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

186
        return stepSkip, nil
3✔
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) {
3✔
192

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

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

206
        // exitWithErr is a helper closure that logs and returns an error.
207
        exitWithErr := func(exitErr error) ([32]byte, *route.Route, error) {
6✔
208
                // We fetch the latest payment status here to get the latest
3✔
209
                // state of the payment.
3✔
210
                payment, err := p.router.cfg.Control.FetchPayment(p.identifier)
3✔
211
                if err != nil {
3✔
NEW
212
                        return [32]byte{}, nil, err
×
NEW
213
                }
×
214

215
                // In case the payment is still in the initiated state we exit
216
                // the lifecycle and fail the payment because it has no chance
217
                // of succeeding.
218
                if payment.GetStatus() == channeldb.StatusInitiated {
6✔
219
                        reason := channeldb.FailureReasonError
3✔
220
                        err = p.router.cfg.Control.FailPayment(
3✔
221
                                p.identifier, reason,
3✔
222
                        )
3✔
223
                        if err != nil {
3✔
NEW
224
                                return [32]byte{}, nil, err
×
NEW
225
                        }
×
226

227
                        // Update the payment to get the latest state.
228
                        payment, err = p.router.cfg.Control.FetchPayment(
3✔
229
                                p.identifier,
3✔
230
                        )
3✔
231
                        if err != nil {
3✔
NEW
232
                                return [32]byte{}, nil, err
×
NEW
233
                        }
×
234
                }
235

236
                log.Errorf("Payment with status=%v id=%v failed: %v",
3✔
237
                        payment.GetStatus(), p.identifier, exitErr)
3✔
238

3✔
239
                return [32]byte{}, nil, err
3✔
240
        }
241

242
        // We'll continue until either our payment succeeds, or we encounter a
243
        // critical error during path finding.
244
lifecycle:
3✔
245
        for {
6✔
246
                // We update the payment state on every iteration.
3✔
247
                currentPayment, ps, err := p.reloadPayment()
3✔
248
                if err != nil {
3✔
249
                        return exitWithErr(err)
×
250
                }
×
251

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

3✔
256
                // We now proceed our lifecycle with the following tasks in
3✔
257
                // order,
3✔
258
                //   1. check context.
3✔
259
                //   2. request route.
3✔
260
                //   3. create HTLC attempt.
3✔
261
                //   4. send HTLC attempt.
3✔
262
                //   5. collect HTLC attempt result.
3✔
263
                //
3✔
264
                // Before we attempt any new shard, we'll check to see if we've
3✔
265
                // gone past the payment attempt timeout, or if the context was
3✔
266
                // cancelled, or the router is exiting. In any of these cases,
3✔
267
                // we'll stop this payment attempt short.
3✔
268
                if err := p.checkContext(ctx); err != nil {
3✔
UNCOV
269
                        return exitWithErr(err)
×
UNCOV
270
                }
×
271

272
                // Now decide the next step of the current lifecycle.
273
                step, err := p.decideNextStep(payment)
3✔
274
                if err != nil {
6✔
275
                        return exitWithErr(err)
3✔
276
                }
3✔
277

278
                switch step {
3✔
279
                // Exit the for loop and return below.
280
                case stepExit:
3✔
281
                        break lifecycle
3✔
282

283
                // Continue the for loop and skip the rest.
284
                case stepSkip:
3✔
285
                        continue lifecycle
3✔
286

287
                // Continue the for loop and proceed the rest.
288
                case stepProceed:
3✔
289

290
                // Unknown step received, exit with an error.
291
                default:
×
292
                        err = fmt.Errorf("unknown step: %v", step)
×
293
                        return exitWithErr(err)
×
294
                }
295

296
                // Now request a route to be used to create our HTLC attempt.
297
                rt, err := p.requestRoute(ps)
3✔
298
                if err != nil {
6✔
299
                        return exitWithErr(err)
3✔
300
                }
3✔
301

302
                // We may not be able to find a route for current attempt. In
303
                // that case, we continue the loop and move straight to the
304
                // next iteration in case there are results for inflight HTLCs
305
                // that still need to be collected.
306
                if rt == nil {
6✔
307
                        log.Errorf("No route found for payment %v",
3✔
308
                                p.identifier)
3✔
309

3✔
310
                        continue lifecycle
3✔
311
                }
312

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

3✔
315
                // We found a route to try, create a new HTLC attempt to try.
3✔
316
                attempt, err := p.registerAttempt(rt, ps.RemainingAmt)
3✔
317
                if err != nil {
3✔
UNCOV
318
                        return exitWithErr(err)
×
UNCOV
319
                }
×
320

321
                // Once the attempt is created, send it to the htlcswitch.
322
                result, err := p.sendAttempt(attempt)
3✔
323
                if err != nil {
3✔
UNCOV
324
                        return exitWithErr(err)
×
UNCOV
325
                }
×
326

327
                // Now that the shard was successfully sent, launch a go
328
                // routine that will handle its result when its back.
329
                if result.err == nil {
6✔
330
                        p.resultCollector(attempt)
3✔
331
                }
3✔
332
        }
333

334
        // Once we are out the lifecycle loop, it means we've reached a
335
        // terminal condition. We either return the settled preimage or the
336
        // payment's failure reason.
337
        //
338
        // Optionally delete the failed attempts from the database.
339
        err = p.router.cfg.Control.DeleteFailedAttempts(p.identifier)
3✔
340
        if err != nil {
3✔
341
                log.Errorf("Error deleting failed htlc attempts for payment "+
×
342
                        "%v: %v", p.identifier, err)
×
343
        }
×
344

345
        htlc, failure := payment.TerminalInfo()
3✔
346
        if htlc != nil {
6✔
347
                return htlc.Settle.Preimage, &htlc.Route, nil
3✔
348
        }
3✔
349

350
        // At this point the payment should have a failure reason and therefore
351
        // this should never happen.
352
        if failure == nil {
3✔
NEW
353
                return [32]byte{}, nil, fmt.Errorf("payment %v has no "+
×
NEW
354
                        "failure reason", p.identifier)
×
NEW
355
        }
×
356

357
        // Otherwise return the payment failure reason.
358
        return [32]byte{}, nil, *failure
3✔
359
}
360

361
// checkContext checks whether the payment context has been canceled.
362
// Cancellation occurs manually or if the context times out.
363
func (p *paymentLifecycle) checkContext(ctx context.Context) error {
3✔
364
        select {
3✔
365
        case <-ctx.Done():
3✔
366
                // If the context was canceled, we'll mark the payment as
3✔
367
                // failed. There are two cases to distinguish here: Either a
3✔
368
                // user-provided timeout was reached, or the context was
3✔
369
                // canceled, either to a manual cancellation or due to an
3✔
370
                // unknown error.
3✔
371
                var reason channeldb.FailureReason
3✔
372
                if errors.Is(ctx.Err(), context.DeadlineExceeded) {
3✔
UNCOV
373
                        reason = channeldb.FailureReasonTimeout
×
UNCOV
374
                        log.Warnf("Payment attempt not completed before "+
×
UNCOV
375
                                "context timeout, id=%s", p.identifier.String())
×
376
                } else {
3✔
377
                        reason = channeldb.FailureReasonCanceled
3✔
378
                        log.Warnf("Payment attempt context canceled, id=%s",
3✔
379
                                p.identifier.String())
3✔
380
                }
3✔
381

382
                // By marking the payment failed, depending on whether it has
383
                // inflight HTLCs or not, its status will now either be
384
                // `StatusInflight` or `StatusFailed`. In either case, no more
385
                // HTLCs will be attempted.
386
                err := p.router.cfg.Control.FailPayment(p.identifier, reason)
3✔
387
                if err != nil {
3✔
UNCOV
388
                        return fmt.Errorf("FailPayment got %w", err)
×
UNCOV
389
                }
×
390

UNCOV
391
        case <-p.router.quit:
×
UNCOV
392
                return fmt.Errorf("check payment timeout got: %w",
×
UNCOV
393
                        ErrRouterShuttingDown)
×
394

395
        // Fall through if we haven't hit our time limit.
396
        default:
3✔
397
        }
398

399
        return nil
3✔
400
}
401

402
// requestRoute is responsible for finding a route to be used to create an HTLC
403
// attempt.
404
func (p *paymentLifecycle) requestRoute(
405
        ps *channeldb.MPPaymentState) (*route.Route, error) {
3✔
406

3✔
407
        remainingFees := p.calcFeeBudget(ps.FeesPaid)
3✔
408

3✔
409
        // Query our payment session to construct a route.
3✔
410
        rt, err := p.paySession.RequestRoute(
3✔
411
                ps.RemainingAmt, remainingFees,
3✔
412
                uint32(ps.NumAttemptsInFlight), uint32(p.currentHeight),
3✔
413
                p.firstHopCustomRecords,
3✔
414
        )
3✔
415

3✔
416
        // Exit early if there's no error.
3✔
417
        if err == nil {
6✔
418
                // Allow the traffic shaper to add custom records to the
3✔
419
                // outgoing HTLC and also adjust the amount if needed.
3✔
420
                err = p.amendFirstHopData(rt)
3✔
421
                if err != nil {
3✔
422
                        return nil, err
×
423
                }
×
424

425
                return rt, nil
3✔
426
        }
427

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

3✔
431
        // If the error belongs to `noRouteError` set, it means a non-critical
3✔
432
        // error has happened during path finding, and we will mark the payment
3✔
433
        // failed with this reason. Otherwise, we'll return the critical error
3✔
434
        // found to abort the lifecycle.
3✔
435
        var routeErr noRouteError
3✔
436
        if !errors.As(err, &routeErr) {
6✔
437
                return nil, fmt.Errorf("requestRoute got: %w", err)
3✔
438
        }
3✔
439

440
        // It's the `paymentSession`'s responsibility to find a route for us
441
        // with the best effort. When it cannot find a path, we need to treat it
442
        // as a terminal condition and fail the payment no matter it has
443
        // inflight HTLCs or not.
444
        failureCode := routeErr.FailureReason()
3✔
445
        log.Warnf("Marking payment %v permanently failed with no route: %v",
3✔
446
                p.identifier, failureCode)
3✔
447

3✔
448
        err = p.router.cfg.Control.FailPayment(p.identifier, failureCode)
3✔
449
        if err != nil {
3✔
UNCOV
450
                return nil, fmt.Errorf("FailPayment got: %w", err)
×
UNCOV
451
        }
×
452

453
        // NOTE: we decide to not return the non-critical noRouteError here to
454
        // avoid terminating the payment lifecycle as there might be other
455
        // inflight HTLCs which we must wait for their results.
456
        return nil, nil
3✔
457
}
458

459
// stop signals any active shard goroutine to exit.
460
func (p *paymentLifecycle) stop() {
3✔
461
        close(p.quit)
3✔
462
}
3✔
463

464
// attemptResult holds the HTLC attempt and a possible error returned from
465
// sending it.
466
type attemptResult struct {
467
        // err is non-nil if a non-critical error was encountered when trying
468
        // to send the attempt, and we successfully updated the control tower
469
        // to reflect this error. This can be errors like not enough local
470
        // balance for the given route etc.
471
        err error
472

473
        // attempt is the attempt structure as recorded in the database.
474
        attempt *channeldb.HTLCAttempt
475
}
476

477
// collectResultAsync launches a goroutine that will wait for the result of the
478
// given HTLC attempt to be available then save its result in a map. Once
479
// received, it will send the result returned from the switch to channel
480
// `resultCollected`.
481
func (p *paymentLifecycle) collectResultAsync(attempt *channeldb.HTLCAttempt) {
3✔
482
        log.Debugf("Collecting result for attempt %v in payment %v",
3✔
483
                attempt.AttemptID, p.identifier)
3✔
484

3✔
485
        go func() {
6✔
486
                result, err := p.collectResult(attempt)
3✔
487
                if err != nil {
6✔
488
                        log.Errorf("Error collecting result for attempt %v in "+
3✔
489
                                "payment %v: %v", attempt.AttemptID,
3✔
490
                                p.identifier, err)
3✔
491

3✔
492
                        return
3✔
493
                }
3✔
494

495
                log.Debugf("Result collected for attempt %v in payment %v",
3✔
496
                        attempt.AttemptID, p.identifier)
3✔
497

3✔
498
                // Create a switch result and send it to the resultCollected
3✔
499
                // chan, which gets processed when the lifecycle is waiting for
3✔
500
                // a result to be received in decideNextStep.
3✔
501
                r := &switchResult{
3✔
502
                        attempt: attempt,
3✔
503
                        result:  result,
3✔
504
                }
3✔
505

3✔
506
                // Signal that a result has been collected.
3✔
507
                select {
3✔
508
                // Send the result so decideNextStep can proceed.
509
                case p.resultCollected <- r:
3✔
510

511
                case <-p.quit:
×
512
                        log.Debugf("Lifecycle exiting while collecting "+
×
513
                                "result for payment %v", p.identifier)
×
514

515
                case <-p.router.quit:
×
516
                }
517
        }()
518
}
519

520
// collectResult waits for the result of the given HTLC attempt to be sent by
521
// the switch and returns it.
522
func (p *paymentLifecycle) collectResult(
523
        attempt *channeldb.HTLCAttempt) (*htlcswitch.PaymentResult, error) {
3✔
524

3✔
525
        log.Tracef("Collecting result for attempt %v", spew.Sdump(attempt))
3✔
526

3✔
527
        result := &htlcswitch.PaymentResult{}
3✔
528

3✔
529
        // Regenerate the circuit for this attempt.
3✔
530
        circuit, err := attempt.Circuit()
3✔
531

3✔
532
        // TODO(yy): We generate this circuit to create the error decryptor,
3✔
533
        // which is then used in htlcswitch as the deobfuscator to decode the
3✔
534
        // error from `UpdateFailHTLC`. However, suppose it's an
3✔
535
        // `UpdateFulfillHTLC` message yet for some reason the sphinx packet is
3✔
536
        // failed to be generated, we'd miss settling it. This means we should
3✔
537
        // give it a second chance to try the settlement path in case
3✔
538
        // `GetAttemptResult` gives us back the preimage. And move the circuit
3✔
539
        // creation into htlcswitch so it's only constructed when there's a
3✔
540
        // failure message we need to decode.
3✔
541
        if err != nil {
3✔
542
                log.Debugf("Unable to generate circuit for attempt %v: %v",
×
543
                        attempt.AttemptID, err)
×
544
                return nil, err
×
545
        }
×
546

547
        // Using the created circuit, initialize the error decrypter, so we can
548
        // parse+decode any failures incurred by this payment within the
549
        // switch.
550
        errorDecryptor := &htlcswitch.SphinxErrorDecrypter{
3✔
551
                OnionErrorDecrypter: sphinx.NewOnionErrorDecrypter(circuit),
3✔
552
        }
3✔
553

3✔
554
        // Now ask the switch to return the result of the payment when
3✔
555
        // available.
3✔
556
        //
3✔
557
        // TODO(yy): consider using htlcswitch to create the `errorDecryptor`
3✔
558
        // since the htlc is already in db. This will also make the interface
3✔
559
        // `PaymentAttemptDispatcher` deeper and easier to use. Moreover, we'd
3✔
560
        // only create the decryptor when received a failure, further saving us
3✔
561
        // a few CPU cycles.
3✔
562
        resultChan, err := p.router.cfg.Payer.GetAttemptResult(
3✔
563
                attempt.AttemptID, p.identifier, errorDecryptor,
3✔
564
        )
3✔
565
        // Handle the switch error.
3✔
566
        if err != nil {
3✔
UNCOV
567
                log.Errorf("Failed getting result for attemptID %d "+
×
UNCOV
568
                        "from switch: %v", attempt.AttemptID, err)
×
UNCOV
569

×
UNCOV
570
                result.Error = err
×
UNCOV
571

×
UNCOV
572
                return result, nil
×
UNCOV
573
        }
×
574

575
        // The switch knows about this payment, we'll wait for a result to be
576
        // available.
577
        select {
3✔
578
        case r, ok := <-resultChan:
3✔
579
                if !ok {
6✔
580
                        return nil, htlcswitch.ErrSwitchExiting
3✔
581
                }
3✔
582

583
                result = r
3✔
584

UNCOV
585
        case <-p.quit:
×
UNCOV
586
                return nil, ErrPaymentLifecycleExiting
×
587

UNCOV
588
        case <-p.router.quit:
×
UNCOV
589
                return nil, ErrRouterShuttingDown
×
590
        }
591

592
        return result, nil
3✔
593
}
594

595
// registerAttempt is responsible for creating and saving an HTLC attempt in db
596
// by using the route info provided. The `remainingAmt` is used to decide
597
// whether this is the last attempt.
598
func (p *paymentLifecycle) registerAttempt(rt *route.Route,
599
        remainingAmt lnwire.MilliSatoshi) (*channeldb.HTLCAttempt, error) {
3✔
600

3✔
601
        // If this route will consume the last remaining amount to send
3✔
602
        // to the receiver, this will be our last shard (for now).
3✔
603
        isLastAttempt := rt.ReceiverAmt() == remainingAmt
3✔
604

3✔
605
        // Using the route received from the payment session, create a new
3✔
606
        // shard to send.
3✔
607
        attempt, err := p.createNewPaymentAttempt(rt, isLastAttempt)
3✔
608
        if err != nil {
3✔
UNCOV
609
                return nil, err
×
UNCOV
610
        }
×
611

612
        // Before sending this HTLC to the switch, we checkpoint the fresh
613
        // paymentID and route to the DB. This lets us know on startup the ID
614
        // of the payment that we attempted to send, such that we can query the
615
        // Switch for its whereabouts. The route is needed to handle the result
616
        // when it eventually comes back.
617
        err = p.router.cfg.Control.RegisterAttempt(
3✔
618
                p.identifier, &attempt.HTLCAttemptInfo,
3✔
619
        )
3✔
620

3✔
621
        return attempt, err
3✔
622
}
623

624
// createNewPaymentAttempt creates a new payment attempt from the given route.
625
func (p *paymentLifecycle) createNewPaymentAttempt(rt *route.Route,
626
        lastShard bool) (*channeldb.HTLCAttempt, error) {
3✔
627

3✔
628
        // Generate a new key to be used for this attempt.
3✔
629
        sessionKey, err := generateNewSessionKey()
3✔
630
        if err != nil {
3✔
631
                return nil, err
×
632
        }
×
633

634
        // We generate a new, unique payment ID that we will use for
635
        // this HTLC.
636
        attemptID, err := p.router.cfg.NextPaymentID()
3✔
637
        if err != nil {
3✔
638
                return nil, err
×
639
        }
×
640

641
        // Request a new shard from the ShardTracker. If this is an AMP
642
        // payment, and this is the last shard, the outstanding shards together
643
        // with this one will be enough for the receiver to derive all HTLC
644
        // preimages. If this a non-AMP payment, the ShardTracker will return a
645
        // simple shard with the payment's static payment hash.
646
        shard, err := p.shardTracker.NewShard(attemptID, lastShard)
3✔
647
        if err != nil {
3✔
UNCOV
648
                return nil, err
×
UNCOV
649
        }
×
650

651
        // If this shard carries MPP or AMP options, add them to the last hop
652
        // on the route.
653
        hop := rt.Hops[len(rt.Hops)-1]
3✔
654
        if shard.MPP() != nil {
6✔
655
                hop.MPP = shard.MPP()
3✔
656
        }
3✔
657

658
        if shard.AMP() != nil {
6✔
659
                hop.AMP = shard.AMP()
3✔
660
        }
3✔
661

662
        hash := shard.Hash()
3✔
663

3✔
664
        // We now have all the information needed to populate the current
3✔
665
        // attempt information.
3✔
666
        return channeldb.NewHtlcAttempt(
3✔
667
                attemptID, sessionKey, *rt, p.router.cfg.Clock.Now(), &hash,
3✔
668
        )
3✔
669
}
670

671
// sendAttempt attempts to send the current attempt to the switch to complete
672
// the payment. If this attempt fails, then we'll continue on to the next
673
// available route.
674
func (p *paymentLifecycle) sendAttempt(
675
        attempt *channeldb.HTLCAttempt) (*attemptResult, error) {
3✔
676

3✔
677
        log.Debugf("Sending HTLC attempt(id=%v, total_amt=%v, first_hop_amt=%d"+
3✔
678
                ") for payment %v", attempt.AttemptID,
3✔
679
                attempt.Route.TotalAmount, attempt.Route.FirstHopAmount.Val,
3✔
680
                p.identifier)
3✔
681

3✔
682
        rt := attempt.Route
3✔
683

3✔
684
        // Construct the first hop.
3✔
685
        firstHop := lnwire.NewShortChanIDFromInt(rt.Hops[0].ChannelID)
3✔
686

3✔
687
        // Craft an HTLC packet to send to the htlcswitch. The metadata within
3✔
688
        // this packet will be used to route the payment through the network,
3✔
689
        // starting with the first-hop.
3✔
690
        htlcAdd := &lnwire.UpdateAddHTLC{
3✔
691
                Amount:        rt.FirstHopAmount.Val.Int(),
3✔
692
                Expiry:        rt.TotalTimeLock,
3✔
693
                PaymentHash:   *attempt.Hash,
3✔
694
                CustomRecords: rt.FirstHopWireCustomRecords,
3✔
695
        }
3✔
696

3✔
697
        // Generate the raw encoded sphinx packet to be included along
3✔
698
        // with the htlcAdd message that we send directly to the
3✔
699
        // switch.
3✔
700
        onionBlob, err := attempt.OnionBlob()
3✔
701
        if err != nil {
3✔
702
                log.Errorf("Failed to create onion blob: attempt=%d in "+
×
703
                        "payment=%v, err:%v", attempt.AttemptID,
×
704
                        p.identifier, err)
×
705

×
706
                return p.failAttempt(attempt.AttemptID, err)
×
707
        }
×
708

709
        htlcAdd.OnionBlob = onionBlob
3✔
710

3✔
711
        // Send it to the Switch. When this method returns we assume
3✔
712
        // the Switch successfully has persisted the payment attempt,
3✔
713
        // such that we can resume waiting for the result after a
3✔
714
        // restart.
3✔
715
        err = p.router.cfg.Payer.SendHTLC(firstHop, attempt.AttemptID, htlcAdd)
3✔
716
        if err != nil {
6✔
717
                log.Errorf("Failed sending attempt %d for payment %v to "+
3✔
718
                        "switch: %v", attempt.AttemptID, p.identifier, err)
3✔
719

3✔
720
                return p.handleSwitchErr(attempt, err)
3✔
721
        }
3✔
722

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

3✔
726
        return &attemptResult{
3✔
727
                attempt: attempt,
3✔
728
        }, nil
3✔
729
}
730

731
// amendFirstHopData is a function that calls the traffic shaper to allow it to
732
// add custom records to the outgoing HTLC and also adjust the amount if
733
// needed.
734
func (p *paymentLifecycle) amendFirstHopData(rt *route.Route) error {
3✔
735
        // The first hop amount on the route is the full route amount if not
3✔
736
        // overwritten by the traffic shaper. So we set the initial value now
3✔
737
        // and potentially overwrite it later.
3✔
738
        rt.FirstHopAmount = tlv.NewRecordT[tlv.TlvType0](
3✔
739
                tlv.NewBigSizeT(rt.TotalAmount),
3✔
740
        )
3✔
741

3✔
742
        // By default, we set the first hop custom records to the initial
3✔
743
        // value requested by the RPC. The traffic shaper may overwrite this
3✔
744
        // value.
3✔
745
        rt.FirstHopWireCustomRecords = p.firstHopCustomRecords
3✔
746

3✔
747
        // extraDataRequest is a helper struct to pass the custom records and
3✔
748
        // amount back from the traffic shaper.
3✔
749
        type extraDataRequest struct {
3✔
750
                customRecords fn.Option[lnwire.CustomRecords]
3✔
751

3✔
752
                amount fn.Option[lnwire.MilliSatoshi]
3✔
753
        }
3✔
754

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

768
                        // Make sure we only received valid records.
UNCOV
769
                        if err := newRecords.Validate(); err != nil {
×
770
                                return fn.Err[extraDataRequest](err)
×
771
                        }
×
772

UNCOV
773
                        log.Debugf("Aux traffic shaper returned custom "+
×
UNCOV
774
                                "records %v and amount %d msat for HTLC",
×
UNCOV
775
                                spew.Sdump(newRecords), newAmt)
×
UNCOV
776

×
UNCOV
777
                        return fn.Ok(extraDataRequest{
×
UNCOV
778
                                customRecords: fn.Some(newRecords),
×
UNCOV
779
                                amount:        fn.Some(newAmt),
×
UNCOV
780
                        })
×
781
                },
782
        ).Unpack()
783
        if err != nil {
3✔
784
                return fmt.Errorf("traffic shaper failed to produce extra "+
×
785
                        "data: %w", err)
×
786
        }
×
787

788
        // Apply the side effects to the UpdateAddHTLC message.
789
        result.customRecords.WhenSome(func(records lnwire.CustomRecords) {
3✔
UNCOV
790
                rt.FirstHopWireCustomRecords = records
×
UNCOV
791
        })
×
792
        result.amount.WhenSome(func(amount lnwire.MilliSatoshi) {
3✔
UNCOV
793
                rt.FirstHopAmount = tlv.NewRecordT[tlv.TlvType0](
×
UNCOV
794
                        tlv.NewBigSizeT(amount),
×
UNCOV
795
                )
×
UNCOV
796
        })
×
797

798
        return nil
3✔
799
}
800

801
// failAttemptAndPayment fails both the payment and its attempt via the
802
// router's control tower, which marks the payment as failed in db.
803
func (p *paymentLifecycle) failPaymentAndAttempt(
804
        attemptID uint64, reason *channeldb.FailureReason,
805
        sendErr error) (*attemptResult, error) {
3✔
806

3✔
807
        log.Errorf("Payment %v failed: final_outcome=%v, raw_err=%v",
3✔
808
                p.identifier, *reason, sendErr)
3✔
809

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

821
        // Fail the attempt.
822
        return p.failAttempt(attemptID, sendErr)
3✔
823
}
824

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

3✔
836
        internalErrorReason := channeldb.FailureReasonError
3✔
837
        attemptID := attempt.AttemptID
3✔
838

3✔
839
        // reportAndFail is a helper closure that reports the failure to the
3✔
840
        // mission control, which helps us to decide whether we want to retry
3✔
841
        // the payment or not. If a non nil reason is returned from mission
3✔
842
        // control, it will further fail the payment via control tower.
3✔
843
        reportAndFail := func(srcIdx *int,
3✔
844
                msg lnwire.FailureMessage) (*attemptResult, error) {
6✔
845

3✔
846
                // Report outcome to mission control.
3✔
847
                reason, err := p.router.cfg.MissionControl.ReportPaymentFail(
3✔
848
                        attemptID, &attempt.Route, srcIdx, msg,
3✔
849
                )
3✔
850
                if err != nil {
3✔
851
                        log.Errorf("Error reporting payment result to mc: %v",
×
852
                                err)
×
853

×
854
                        reason = &internalErrorReason
×
855
                }
×
856

857
                // Fail the attempt only if there's no reason.
858
                if reason == nil {
6✔
859
                        // Fail the attempt.
3✔
860
                        return p.failAttempt(attemptID, sendErr)
3✔
861
                }
3✔
862

863
                // Otherwise fail both the payment and the attempt.
864
                return p.failPaymentAndAttempt(attemptID, reason, sendErr)
3✔
865
        }
866

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

×
UNCOV
875
                return p.failAttempt(attemptID, sendErr)
×
UNCOV
876
        }
×
877

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

×
UNCOV
882
                // Since this error message cannot be decrypted, we will send a
×
UNCOV
883
                // nil error message to our mission controller and fail the
×
UNCOV
884
                // payment.
×
UNCOV
885
                return reportAndFail(nil, nil)
×
UNCOV
886
        }
×
887

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

901
        // failureSourceIdx is the index of the node that the failure occurred
902
        // at. If the ClearTextError received is not a ForwardingError the
903
        // payment error occurred at our node, so we leave this value as 0
904
        // to indicate that the failure occurred locally. If the error is a
905
        // ForwardingError, it did not originate at our node, so we set
906
        // failureSourceIdx to the index of the node where the failure occurred.
907
        failureSourceIdx := 0
3✔
908
        var source *htlcswitch.ForwardingError
3✔
909
        ok = errors.As(rtErr, &source)
3✔
910
        if ok {
6✔
911
                failureSourceIdx = source.FailureSourceIdx
3✔
912
        }
3✔
913

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

927
        log.Tracef("Node=%v reported failure when sending htlc",
3✔
928
                failureSourceIdx)
3✔
929

3✔
930
        return reportAndFail(&failureSourceIdx, failureMessage)
3✔
931
}
932

933
// handleFailureMessage tries to apply a channel update present in the failure
934
// message if any.
935
func (p *paymentLifecycle) handleFailureMessage(rt *route.Route,
936
        errorSourceIdx int, failure lnwire.FailureMessage) error {
3✔
937

3✔
938
        if failure == nil {
3✔
UNCOV
939
                return nil
×
UNCOV
940
        }
×
941

942
        // It makes no sense to apply our own channel updates.
943
        if errorSourceIdx == 0 {
6✔
944
                log.Errorf("Channel update of ourselves received")
3✔
945

3✔
946
                return nil
3✔
947
        }
3✔
948

949
        // Extract channel update if the error contains one.
950
        update := p.router.extractChannelUpdate(failure)
3✔
951
        if update == nil {
6✔
952
                return nil
3✔
953
        }
3✔
954

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

×
964
                return err
×
965
        }
×
966

967
        var (
3✔
968
                isAdditionalEdge bool
3✔
969
                policy           *models.CachedEdgePolicy
3✔
970
        )
3✔
971

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

987
        // Apply channel update to additional edge policy.
988
        if isAdditionalEdge {
6✔
989
                if !p.paySession.UpdateAdditionalEdge(
3✔
990
                        update, errSource, policy) {
3✔
991

×
992
                        log.Debugf("Invalid channel update received: node=%v",
×
993
                                errVertex)
×
994
                }
×
995
                return nil
3✔
996
        }
997

998
        // Apply channel update to the channel edge policy in our db.
999
        if !p.router.cfg.ApplyChannelUpdate(update) {
6✔
1000
                log.Debugf("Invalid channel update received: node=%v",
3✔
1001
                        errVertex)
3✔
1002
        }
3✔
1003
        return nil
3✔
1004
}
1005

1006
// failAttempt calls control tower to fail the current payment attempt.
1007
func (p *paymentLifecycle) failAttempt(attemptID uint64,
1008
        sendError error) (*attemptResult, error) {
3✔
1009

3✔
1010
        log.Warnf("Attempt %v for payment %v failed: %v", attemptID,
3✔
1011
                p.identifier, sendError)
3✔
1012

3✔
1013
        failInfo := marshallError(
3✔
1014
                sendError,
3✔
1015
                p.router.cfg.Clock.Now(),
3✔
1016
        )
3✔
1017

3✔
1018
        // Now that we are failing this payment attempt, cancel the shard with
3✔
1019
        // the ShardTracker such that it can derive the correct hash for the
3✔
1020
        // next attempt.
3✔
1021
        if err := p.shardTracker.CancelShard(attemptID); err != nil {
3✔
1022
                return nil, err
×
1023
        }
×
1024

1025
        attempt, err := p.router.cfg.Control.FailAttempt(
3✔
1026
                p.identifier, attemptID, failInfo,
3✔
1027
        )
3✔
1028
        if err != nil {
3✔
UNCOV
1029
                return nil, err
×
UNCOV
1030
        }
×
1031

1032
        return &attemptResult{
3✔
1033
                attempt: attempt,
3✔
1034
                err:     sendError,
3✔
1035
        }, nil
3✔
1036
}
1037

1038
// marshallError marshall an error as received from the switch to a structure
1039
// that is suitable for database storage.
1040
func marshallError(sendError error, time time.Time) *channeldb.HTLCFailInfo {
3✔
1041
        response := &channeldb.HTLCFailInfo{
3✔
1042
                FailTime: time,
3✔
1043
        }
3✔
1044

3✔
1045
        switch {
3✔
UNCOV
1046
        case errors.Is(sendError, htlcswitch.ErrPaymentIDNotFound):
×
UNCOV
1047
                response.Reason = channeldb.HTLCFailInternal
×
UNCOV
1048
                return response
×
1049

UNCOV
1050
        case errors.Is(sendError, htlcswitch.ErrUnreadableFailureMessage):
×
UNCOV
1051
                response.Reason = channeldb.HTLCFailUnreadable
×
UNCOV
1052
                return response
×
1053
        }
1054

1055
        var rtErr htlcswitch.ClearTextError
3✔
1056
        ok := errors.As(sendError, &rtErr)
3✔
1057
        if !ok {
3✔
UNCOV
1058
                response.Reason = channeldb.HTLCFailInternal
×
UNCOV
1059
                return response
×
UNCOV
1060
        }
×
1061

1062
        message := rtErr.WireMessage()
3✔
1063
        if message != nil {
6✔
1064
                response.Reason = channeldb.HTLCFailMessage
3✔
1065
                response.Message = message
3✔
1066
        } else {
3✔
UNCOV
1067
                response.Reason = channeldb.HTLCFailUnknown
×
UNCOV
1068
        }
×
1069

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

1082
        return response
3✔
1083
}
1084

1085
// reloadInflightAttempts is called when the payment lifecycle is resumed after
1086
// a restart. It reloads all inflight attempts from the control tower and
1087
// collects the results of the attempts that have been sent before.
1088
func (p *paymentLifecycle) reloadInflightAttempts() (DBMPPayment, error) {
3✔
1089
        payment, err := p.router.cfg.Control.FetchPayment(p.identifier)
3✔
1090
        if err != nil {
3✔
UNCOV
1091
                return nil, err
×
UNCOV
1092
        }
×
1093

1094
        for _, a := range payment.InFlightHTLCs() {
6✔
1095
                a := a
3✔
1096

3✔
1097
                log.Infof("Resuming HTLC attempt %v for payment %v",
3✔
1098
                        a.AttemptID, p.identifier)
3✔
1099

3✔
1100
                p.resultCollector(&a)
3✔
1101
        }
3✔
1102

1103
        return payment, nil
3✔
1104
}
1105

1106
// reloadPayment returns the latest payment found in the db (control tower).
1107
func (p *paymentLifecycle) reloadPayment() (DBMPPayment,
1108
        *channeldb.MPPaymentState, error) {
3✔
1109

3✔
1110
        // Read the db to get the latest state of the payment.
3✔
1111
        payment, err := p.router.cfg.Control.FetchPayment(p.identifier)
3✔
1112
        if err != nil {
3✔
1113
                return nil, nil, err
×
1114
        }
×
1115

1116
        ps := payment.GetState()
3✔
1117
        remainingFees := p.calcFeeBudget(ps.FeesPaid)
3✔
1118

3✔
1119
        log.Debugf("Payment %v: status=%v, active_shards=%v, rem_value=%v, "+
3✔
1120
                "fee_limit=%v", p.identifier, payment.GetStatus(),
3✔
1121
                ps.NumAttemptsInFlight, ps.RemainingAmt, remainingFees)
3✔
1122

3✔
1123
        return payment, ps, nil
3✔
1124
}
1125

1126
// handleAttemptResult processes the result of an HTLC attempt returned from
1127
// the htlcswitch.
1128
func (p *paymentLifecycle) handleAttemptResult(attempt *channeldb.HTLCAttempt,
1129
        result *htlcswitch.PaymentResult) (*attemptResult, error) {
3✔
1130

3✔
1131
        // If the result has an error, we need to further process it by failing
3✔
1132
        // the attempt and maybe fail the payment.
3✔
1133
        if result.Error != nil {
6✔
1134
                return p.handleSwitchErr(attempt, result.Error)
3✔
1135
        }
3✔
1136

1137
        // We got an attempt settled result back from the switch.
1138
        log.Debugf("Payment(%v): attempt(%v) succeeded", p.identifier,
3✔
1139
                attempt.AttemptID)
3✔
1140

3✔
1141
        // Report success to mission control.
3✔
1142
        err := p.router.cfg.MissionControl.ReportPaymentSuccess(
3✔
1143
                attempt.AttemptID, &attempt.Route,
3✔
1144
        )
3✔
1145
        if err != nil {
3✔
1146
                log.Errorf("Error reporting payment success to mc: %v", err)
×
1147
        }
×
1148

1149
        // In case of success we atomically store settle result to the DB and
1150
        // move the shard to the settled state.
1151
        htlcAttempt, err := p.router.cfg.Control.SettleAttempt(
3✔
1152
                p.identifier, attempt.AttemptID,
3✔
1153
                &channeldb.HTLCSettleInfo{
3✔
1154
                        Preimage:   result.Preimage,
3✔
1155
                        SettleTime: p.router.cfg.Clock.Now(),
3✔
1156
                },
3✔
1157
        )
3✔
1158
        if err != nil {
3✔
UNCOV
1159
                log.Errorf("Error settling attempt %v for payment %v with "+
×
UNCOV
1160
                        "preimage %v: %v", attempt.AttemptID, p.identifier,
×
UNCOV
1161
                        result.Preimage, err)
×
UNCOV
1162

×
UNCOV
1163
                // We won't mark the attempt as failed since we already have
×
UNCOV
1164
                // the preimage.
×
UNCOV
1165
                return nil, err
×
UNCOV
1166
        }
×
1167

1168
        return &attemptResult{
3✔
1169
                attempt: htlcAttempt,
3✔
1170
        }, nil
3✔
1171
}
1172

1173
// collectAndHandleResult waits for the result for the given attempt to be
1174
// available from the Switch, then records the attempt outcome with the control
1175
// tower. An attemptResult is returned, indicating the final outcome of this
1176
// HTLC attempt.
1177
func (p *paymentLifecycle) collectAndHandleResult(
1178
        attempt *channeldb.HTLCAttempt) (*attemptResult, error) {
3✔
1179

3✔
1180
        result, err := p.collectResult(attempt)
3✔
1181
        if err != nil {
3✔
UNCOV
1182
                return nil, err
×
UNCOV
1183
        }
×
1184

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