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

lightningnetwork / lnd / 12628700106

06 Jan 2025 07:54AM UTC coverage: 58.708% (+0.1%) from 58.598%
12628700106

Pull #9150

github

yyforyongyu
x
Pull Request #9150: routing+htlcswitch: fix stuck inflight payments

196 of 237 new or added lines in 6 files covered. (82.7%)

39 existing lines in 13 files now uncovered.

135285 of 230439 relevant lines covered (58.71%)

19155.8 hits per line

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

91.67
/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/lnutils"
18
        "github.com/lightningnetwork/lnd/lnwire"
19
        "github.com/lightningnetwork/lnd/routing/route"
20
        "github.com/lightningnetwork/lnd/routing/shards"
21
        "github.com/lightningnetwork/lnd/tlv"
22
)
23

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

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

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

43
        // resultCollected is used to signal that the result of an attempt has
44
        // been collected.
45
        resultCollected chan struct{}
46

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

53
        // switchResults is a map that holds the results for HTLC attempts
54
        // returned from the htlcswitch.
55
        switchResults lnutils.SyncMap[*channeldb.HTLCAttempt,
56
                *htlcswitch.PaymentResult]
57
}
58

59
// newPaymentLifecycle initiates a new payment lifecycle and returns it.
60
func newPaymentLifecycle(r *ChannelRouter, feeLimit lnwire.MilliSatoshi,
61
        identifier lntypes.Hash, paySession PaymentSession,
62
        shardTracker shards.ShardTracker, currentHeight int32,
63
        firstHopCustomRecords lnwire.CustomRecords) *paymentLifecycle {
46✔
64

46✔
65
        p := &paymentLifecycle{
46✔
66
                router:                r,
46✔
67
                feeLimit:              feeLimit,
46✔
68
                identifier:            identifier,
46✔
69
                paySession:            paySession,
46✔
70
                shardTracker:          shardTracker,
46✔
71
                currentHeight:         currentHeight,
46✔
72
                quit:                  make(chan struct{}),
46✔
73
                resultCollected:       make(chan struct{}, 1),
46✔
74
                firstHopCustomRecords: firstHopCustomRecords,
46✔
75
                switchResults: lnutils.SyncMap[*channeldb.HTLCAttempt,
46✔
76
                        *htlcswitch.PaymentResult]{},
46✔
77
        }
46✔
78

46✔
79
        // Mount the result collector.
46✔
80
        p.resultCollector = p.collectResultAsync
46✔
81

46✔
82
        return p
46✔
83
}
46✔
84

85
// calcFeeBudget returns the available fee to be used for sending HTLC
86
// attempts.
87
func (p *paymentLifecycle) calcFeeBudget(
88
        feesPaid lnwire.MilliSatoshi) lnwire.MilliSatoshi {
106✔
89

106✔
90
        budget := p.feeLimit
106✔
91

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

101
        return budget
106✔
102
}
103

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

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

113
        // stepProceed is used when we can proceed the current lifecycle.
114
        stepProceed
115

116
        // stepExit is used when we need to quit the current lifecycle.
117
        stepExit
118
)
119

120
// decideNextStep is used to determine the next step in the payment lifecycle.
121
func (p *paymentLifecycle) decideNextStep(
122
        payment DBMPPayment) (stateStep, error) {
77✔
123

77✔
124
        // Check whether we could make new HTLC attempts.
77✔
125
        allow, err := payment.AllowMoreAttempts()
77✔
126
        if err != nil {
79✔
127
                return stepExit, err
2✔
128
        }
2✔
129

130
        if !allow {
119✔
131
                // Check whether we need to wait for results.
44✔
132
                wait, err := payment.NeedWaitAttempts()
44✔
133
                if err != nil {
45✔
134
                        return stepExit, err
1✔
135
                }
1✔
136

137
                // If we are not allowed to make new HTLC attempts and there's
138
                // no need to wait, the lifecycle is done and we can exit.
139
                if !wait {
63✔
140
                        return stepExit, nil
20✔
141
                }
20✔
142

143
                log.Tracef("Waiting for attempt results for payment %v",
26✔
144
                        p.identifier)
26✔
145

26✔
146
                // Otherwise we wait for one HTLC attempt then continue
26✔
147
                // the lifecycle.
26✔
148
                //
26✔
149
                // NOTE: we don't check `p.quit` since `decideNextStep` is
26✔
150
                // running in the same goroutine as `resumePayment`.
26✔
151
                select {
26✔
152
                case <-p.resultCollected:
25✔
153
                        log.Tracef("Received attempt result for payment %v",
25✔
154
                                p.identifier)
25✔
155

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

160
                return stepSkip, nil
25✔
161
        }
162

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

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

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

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

184
        // Get the payment status.
185
        status := payment.GetStatus()
24✔
186

24✔
187
        // exitWithErr is a helper closure that logs and returns an error.
24✔
188
        exitWithErr := func(err error) ([32]byte, *route.Route, error) {
32✔
189
                // Log an error with the latest payment status.
8✔
190
                //
8✔
191
                // NOTE: this `status` variable is reassigned in the loop
8✔
192
                // below. We could also call `payment.GetStatus` here, but in a
8✔
193
                // rare case when the critical log is triggered when using
8✔
194
                // postgres as db backend, the `payment` could be nil, causing
8✔
195
                // the payment fetching to return an error.
8✔
196
                log.Errorf("Payment %v with status=%v failed: %v", p.identifier,
8✔
197
                        status, err)
8✔
198

8✔
199
                return [32]byte{}, nil, err
8✔
200
        }
8✔
201

202
        // We'll continue until either our payment succeeds, or we encounter a
203
        // critical error during path finding.
204
lifecycle:
24✔
205
        for {
96✔
206
                // We update the payment state on every iteration.
72✔
207
                currentPayment, ps, err := p.processResultsAndReloadPayment()
72✔
208
                if err != nil {
72✔
209
                        return exitWithErr(err)
×
210
                }
×
211

212
                status = currentPayment.GetStatus()
72✔
213
                payment = currentPayment
72✔
214

72✔
215
                // We now proceed our lifecycle with the following tasks in
72✔
216
                // order,
72✔
217
                //   1. check context.
72✔
218
                //   2. request route.
72✔
219
                //   3. create HTLC attempt.
72✔
220
                //   4. send HTLC attempt.
72✔
221
                //   5. collect HTLC attempt result.
72✔
222
                //
72✔
223
                // Before we attempt any new shard, we'll check to see if we've
72✔
224
                // gone past the payment attempt timeout, or if the context was
72✔
225
                // cancelled, or the router is exiting. In any of these cases,
72✔
226
                // we'll stop this payment attempt short.
72✔
227
                if err := p.checkContext(ctx); err != nil {
73✔
228
                        return exitWithErr(err)
1✔
229
                }
1✔
230

231
                // Now decide the next step of the current lifecycle.
232
                step, err := p.decideNextStep(payment)
71✔
233
                if err != nil {
75✔
234
                        return exitWithErr(err)
4✔
235
                }
4✔
236

237
                switch step {
70✔
238
                // Exit the for loop and return below.
239
                case stepExit:
19✔
240
                        break lifecycle
19✔
241

242
                // Continue the for loop and skip the rest.
243
                case stepSkip:
24✔
244
                        continue lifecycle
24✔
245

246
                // Continue the for loop and proceed the rest.
247
                case stepProceed:
33✔
248

249
                // Unknown step received, exit with an error.
250
                default:
×
251
                        err = fmt.Errorf("unknown step: %v", step)
×
252
                        return exitWithErr(err)
×
253
                }
254

255
                // Now request a route to be used to create our HTLC attempt.
256
                rt, err := p.requestRoute(ps)
33✔
257
                if err != nil {
34✔
258
                        return exitWithErr(err)
1✔
259
                }
1✔
260

261
                // We may not be able to find a route for current attempt. In
262
                // that case, we continue the loop and move straight to the
263
                // next iteration in case there are results for inflight HTLCs
264
                // that still need to be collected.
265
                if rt == nil {
37✔
266
                        log.Errorf("No route found for payment %v",
5✔
267
                                p.identifier)
5✔
268

5✔
269
                        continue lifecycle
5✔
270
                }
271

272
                log.Tracef("Found route: %s", spew.Sdump(rt.Hops))
30✔
273

30✔
274
                // We found a route to try, create a new HTLC attempt to try.
30✔
275
                attempt, err := p.registerAttempt(rt, ps.RemainingAmt)
30✔
276
                if err != nil {
31✔
277
                        // If the error is due to we cannot register another
1✔
278
                        // HTLC, we will skip this iteration and continue to
1✔
279
                        // the next one in case there are inflight HTLCs.
1✔
280
                        //
1✔
281
                        // TODO(yy): remove this check once we have a finer
1✔
282
                        // control over errors returned from the switch.
1✔
283
                        if errors.Is(err, channeldb.ErrRegisterAttempt) {
1✔
NEW
284
                                continue lifecycle
×
285
                        }
286

287
                        return exitWithErr(err)
1✔
288
                }
289

290
                // Once the attempt is created, send it to the htlcswitch.
291
                result, err := p.sendAttempt(attempt)
29✔
292
                if err != nil {
30✔
293
                        return exitWithErr(err)
1✔
294
                }
1✔
295

296
                // Now that the shard was successfully sent, launch a go
297
                // routine that will handle its result when its back.
298
                if result.err == nil {
55✔
299
                        p.resultCollector(attempt)
27✔
300
                }
27✔
301
        }
302

303
        // Once we are out the lifecycle loop, it means we've reached a
304
        // terminal condition. We either return the settled preimage or the
305
        // payment's failure reason.
306
        //
307
        // Optionally delete the failed attempts from the database.
308
        err = p.router.cfg.Control.DeleteFailedAttempts(p.identifier)
19✔
309
        if err != nil {
19✔
310
                log.Errorf("Error deleting failed htlc attempts for payment "+
×
311
                        "%v: %v", p.identifier, err)
×
312
        }
×
313

314
        htlc, failure := payment.TerminalInfo()
19✔
315
        if htlc != nil {
34✔
316
                return htlc.Settle.Preimage, &htlc.Route, nil
15✔
317
        }
15✔
318

319
        // Otherwise return the payment failure reason.
320
        return [32]byte{}, nil, *failure
7✔
321
}
322

323
// checkContext checks whether the payment context has been canceled.
324
// Cancellation occurs manually or if the context times out.
325
func (p *paymentLifecycle) checkContext(ctx context.Context) error {
75✔
326
        select {
75✔
327
        case <-ctx.Done():
7✔
328
                // If the context was canceled, we'll mark the payment as
7✔
329
                // failed. There are two cases to distinguish here: Either a
7✔
330
                // user-provided timeout was reached, or the context was
7✔
331
                // canceled, either to a manual cancellation or due to an
7✔
332
                // unknown error.
7✔
333
                var reason channeldb.FailureReason
7✔
334
                if errors.Is(ctx.Err(), context.DeadlineExceeded) {
10✔
335
                        reason = channeldb.FailureReasonTimeout
3✔
336
                        log.Warnf("Payment attempt not completed before "+
3✔
337
                                "context timeout, id=%s", p.identifier.String())
3✔
338
                } else {
7✔
339
                        reason = channeldb.FailureReasonCanceled
4✔
340
                        log.Warnf("Payment attempt context canceled, id=%s",
4✔
341
                                p.identifier.String())
4✔
342
                }
4✔
343

344
                // By marking the payment failed, depending on whether it has
345
                // inflight HTLCs or not, its status will now either be
346
                // `StatusInflight` or `StatusFailed`. In either case, no more
347
                // HTLCs will be attempted.
348
                err := p.router.cfg.Control.FailPayment(p.identifier, reason)
7✔
349
                if err != nil {
8✔
350
                        return fmt.Errorf("FailPayment got %w", err)
1✔
351
                }
1✔
352

353
        case <-p.router.quit:
2✔
354
                return fmt.Errorf("check payment timeout got: %w",
2✔
355
                        ErrRouterShuttingDown)
2✔
356

357
        // Fall through if we haven't hit our time limit.
358
        default:
69✔
359
        }
360

361
        return nil
72✔
362
}
363

364
// requestRoute is responsible for finding a route to be used to create an HTLC
365
// attempt.
366
func (p *paymentLifecycle) requestRoute(
367
        ps *channeldb.MPPaymentState) (*route.Route, error) {
37✔
368

37✔
369
        remainingFees := p.calcFeeBudget(ps.FeesPaid)
37✔
370

37✔
371
        // Query our payment session to construct a route.
37✔
372
        rt, err := p.paySession.RequestRoute(
37✔
373
                ps.RemainingAmt, remainingFees,
37✔
374
                uint32(ps.NumAttemptsInFlight), uint32(p.currentHeight),
37✔
375
                p.firstHopCustomRecords,
37✔
376
        )
37✔
377

37✔
378
        // Exit early if there's no error.
37✔
379
        if err == nil {
68✔
380
                // Allow the traffic shaper to add custom records to the
31✔
381
                // outgoing HTLC and also adjust the amount if needed.
31✔
382
                err = p.amendFirstHopData(rt)
31✔
383
                if err != nil {
31✔
NEW
384
                        return nil, err
×
NEW
385
                }
×
386

387
                return rt, nil
31✔
388
        }
389

390
        // Otherwise we need to handle the error.
391
        log.Warnf("Failed to find route for payment %v: %v", p.identifier, err)
9✔
392

9✔
393
        // If the error belongs to `noRouteError` set, it means a non-critical
9✔
394
        // error has happened during path finding, and we will mark the payment
9✔
395
        // failed with this reason. Otherwise, we'll return the critical error
9✔
396
        // found to abort the lifecycle.
9✔
397
        var routeErr noRouteError
9✔
398
        if !errors.As(err, &routeErr) {
11✔
399
                return nil, fmt.Errorf("requestRoute got: %w", err)
2✔
400
        }
2✔
401

402
        // It's the `paymentSession`'s responsibility to find a route for us
403
        // with the best effort. When it cannot find a path, we need to treat it
404
        // as a terminal condition and fail the payment no matter it has
405
        // inflight HTLCs or not.
406
        failureCode := routeErr.FailureReason()
7✔
407
        log.Warnf("Marking payment %v permanently failed with no route: %v",
7✔
408
                p.identifier, failureCode)
7✔
409

7✔
410
        err = p.router.cfg.Control.FailPayment(p.identifier, failureCode)
7✔
411
        if err != nil {
8✔
412
                return nil, fmt.Errorf("FailPayment got: %w", err)
1✔
413
        }
1✔
414

415
        // NOTE: we decide to not return the non-critical noRouteError here to
416
        // avoid terminating the payment lifecycle as there might be other
417
        // inflight HTLCs which we must wait for their results.
418
        return nil, nil
6✔
419
}
420

421
// stop signals any active shard goroutine to exit.
422
func (p *paymentLifecycle) stop() {
26✔
423
        close(p.quit)
26✔
424
}
26✔
425

426
// attemptResult holds the HTLC attempt and a possible error returned from
427
// sending it.
428
type attemptResult struct {
429
        // err is non-nil if a non-critical error was encountered when trying
430
        // to send the attempt, and we successfully updated the control tower
431
        // to reflect this error. This can be errors like not enough local
432
        // balance for the given route etc.
433
        err error
434

435
        // attempt is the attempt structure as recorded in the database.
436
        attempt *channeldb.HTLCAttempt
437
}
438

439
// collectResultAsync launches a goroutine that will wait for the result of the
440
// given HTLC attempt to be available then save its result in a map. Once
441
// received, it will send a signal to channel `resultCollected` to indicate
442
// there's a result.
443
func (p *paymentLifecycle) collectResultAsync(attempt *channeldb.HTLCAttempt) {
25✔
444
        log.Debugf("Collecting result for attempt %v in payment %v",
25✔
445
                attempt.AttemptID, p.identifier)
25✔
446

25✔
447
        go func() {
50✔
448
                result, err := p.collectResult(attempt)
25✔
449
                if err != nil {
28✔
450
                        log.Errorf("Error collecting result for attempt %v in "+
3✔
451
                                "payment %v: %v", attempt.AttemptID,
3✔
452
                                p.identifier, err)
3✔
453

3✔
454
                        return
3✔
455
                }
3✔
456

457
                log.Debugf("Result collected for attempt %v in payment %v",
25✔
458
                        attempt.AttemptID, p.identifier)
25✔
459

25✔
460
                // Save the result and process it in the next main loop.
25✔
461
                p.switchResults.Store(attempt, result)
25✔
462

25✔
463
                // Signal that a result has been collected.
25✔
464
                select {
25✔
465
                // Send the signal or quit.
466
                case p.resultCollected <- struct{}{}:
25✔
467

468
                case <-p.quit:
×
469
                        log.Debugf("Lifecycle exiting while collecting "+
×
470
                                "result for payment %v", p.identifier)
×
471

UNCOV
472
                case <-p.router.quit:
×
473
                }
474
        }()
475
}
476

477
// collectResult waits for the result of the given HTLC attempt to be sent by
478
// the switch and returns it.
479
func (p *paymentLifecycle) collectResult(
480
        attempt *channeldb.HTLCAttempt) (*htlcswitch.PaymentResult, error) {
37✔
481

37✔
482
        log.Tracef("Collecting result for attempt %v", spew.Sdump(attempt))
37✔
483

37✔
484
        result := &htlcswitch.PaymentResult{}
37✔
485

37✔
486
        // Regenerate the circuit for this attempt.
37✔
487
        circuit, err := attempt.Circuit()
37✔
488

37✔
489
        // TODO(yy): We generate this circuit to create the error decryptor,
37✔
490
        // which is then used in htlcswitch as the deobfuscator to decode the
37✔
491
        // error from `UpdateFailHTLC`. However, suppose it's an
37✔
492
        // `UpdateFulfillHTLC` message yet for some reason the sphinx packet is
37✔
493
        // failed to be generated, we'd miss settling it. This means we should
37✔
494
        // give it a second chance to try the settlement path in case
37✔
495
        // `GetAttemptResult` gives us back the preimage. And move the circuit
37✔
496
        // creation into htlcswitch so it's only constructed when there's a
37✔
497
        // failure message we need to decode.
37✔
498
        if err != nil {
37✔
499
                log.Debugf("Unable to generate circuit for attempt %v: %v",
×
500
                        attempt.AttemptID, err)
×
NEW
501
                return nil, err
×
502
        }
×
503

504
        // Using the created circuit, initialize the error decrypter, so we can
505
        // parse+decode any failures incurred by this payment within the
506
        // switch.
507
        errorDecryptor := &htlcswitch.SphinxErrorDecrypter{
37✔
508
                OnionErrorDecrypter: sphinx.NewOnionErrorDecrypter(circuit),
37✔
509
        }
37✔
510

37✔
511
        // Now ask the switch to return the result of the payment when
37✔
512
        // available.
37✔
513
        //
37✔
514
        // TODO(yy): consider using htlcswitch to create the `errorDecryptor`
37✔
515
        // since the htlc is already in db. This will also make the interface
37✔
516
        // `PaymentAttemptDispatcher` deeper and easier to use. Moreover, we'd
37✔
517
        // only create the decryptor when received a failure, further saving us
37✔
518
        // a few CPU cycles.
37✔
519
        resultChan, err := p.router.cfg.Payer.GetAttemptResult(
37✔
520
                attempt.AttemptID, p.identifier, errorDecryptor,
37✔
521
        )
37✔
522
        // Handle the switch error.
37✔
523
        if err != nil {
38✔
524
                log.Errorf("Failed getting result for attemptID %d "+
1✔
525
                        "from switch: %v", attempt.AttemptID, err)
1✔
526

1✔
527
                result.Error = err
1✔
528

1✔
529
                return result, nil
1✔
530
        }
1✔
531

532
        // The switch knows about this payment, we'll wait for a result to be
533
        // available.
534
        select {
36✔
535
        case r, ok := <-resultChan:
34✔
536
                if !ok {
38✔
537
                        return nil, htlcswitch.ErrSwitchExiting
4✔
538
                }
4✔
539

540
                result = r
33✔
541

542
        case <-p.quit:
1✔
543
                return nil, ErrPaymentLifecycleExiting
1✔
544

545
        case <-p.router.quit:
1✔
546
                return nil, ErrRouterShuttingDown
1✔
547
        }
548

549
        return result, nil
33✔
550
}
551

552
// registerAttempt is responsible for creating and saving an HTLC attempt in db
553
// by using the route info provided. The `remainingAmt` is used to decide
554
// whether this is the last attempt.
555
func (p *paymentLifecycle) registerAttempt(rt *route.Route,
556
        remainingAmt lnwire.MilliSatoshi) (*channeldb.HTLCAttempt, error) {
39✔
557

39✔
558
        // If this route will consume the last remaining amount to send
39✔
559
        // to the receiver, this will be our last shard (for now).
39✔
560
        isLastAttempt := rt.ReceiverAmt() == remainingAmt
39✔
561

39✔
562
        // Using the route received from the payment session, create a new
39✔
563
        // shard to send.
39✔
564
        attempt, err := p.createNewPaymentAttempt(rt, isLastAttempt)
39✔
565
        if err != nil {
41✔
566
                return nil, err
2✔
567
        }
2✔
568

569
        // Before sending this HTLC to the switch, we checkpoint the fresh
570
        // paymentID and route to the DB. This lets us know on startup the ID
571
        // of the payment that we attempted to send, such that we can query the
572
        // Switch for its whereabouts. The route is needed to handle the result
573
        // when it eventually comes back.
574
        err = p.router.cfg.Control.RegisterAttempt(
37✔
575
                p.identifier, &attempt.HTLCAttemptInfo,
37✔
576
        )
37✔
577

37✔
578
        return attempt, err
37✔
579
}
580

581
// createNewPaymentAttempt creates a new payment attempt from the given route.
582
func (p *paymentLifecycle) createNewPaymentAttempt(rt *route.Route,
583
        lastShard bool) (*channeldb.HTLCAttempt, error) {
39✔
584

39✔
585
        // Generate a new key to be used for this attempt.
39✔
586
        sessionKey, err := generateNewSessionKey()
39✔
587
        if err != nil {
39✔
588
                return nil, err
×
589
        }
×
590

591
        // We generate a new, unique payment ID that we will use for
592
        // this HTLC.
593
        attemptID, err := p.router.cfg.NextPaymentID()
39✔
594
        if err != nil {
39✔
595
                return nil, err
×
596
        }
×
597

598
        // Request a new shard from the ShardTracker. If this is an AMP
599
        // payment, and this is the last shard, the outstanding shards together
600
        // with this one will be enough for the receiver to derive all HTLC
601
        // preimages. If this a non-AMP payment, the ShardTracker will return a
602
        // simple shard with the payment's static payment hash.
603
        shard, err := p.shardTracker.NewShard(attemptID, lastShard)
39✔
604
        if err != nil {
40✔
605
                return nil, err
1✔
606
        }
1✔
607

608
        // If this shard carries MPP or AMP options, add them to the last hop
609
        // on the route.
610
        hop := rt.Hops[len(rt.Hops)-1]
38✔
611
        if shard.MPP() != nil {
45✔
612
                hop.MPP = shard.MPP()
7✔
613
        }
7✔
614

615
        if shard.AMP() != nil {
41✔
616
                hop.AMP = shard.AMP()
3✔
617
        }
3✔
618

619
        hash := shard.Hash()
38✔
620

38✔
621
        // We now have all the information needed to populate the current
38✔
622
        // attempt information.
38✔
623
        return channeldb.NewHtlcAttempt(
38✔
624
                attemptID, sessionKey, *rt, p.router.cfg.Clock.Now(), &hash,
38✔
625
        )
38✔
626
}
627

628
// sendAttempt attempts to send the current attempt to the switch to complete
629
// the payment. If this attempt fails, then we'll continue on to the next
630
// available route.
631
func (p *paymentLifecycle) sendAttempt(
632
        attempt *channeldb.HTLCAttempt) (*attemptResult, error) {
37✔
633

37✔
634
        log.Debugf("Sending HTLC attempt(id=%v, total_amt=%v, first_hop_amt=%d"+
37✔
635
                ") for payment %v", attempt.AttemptID,
37✔
636
                attempt.Route.TotalAmount, attempt.Route.FirstHopAmount.Val,
37✔
637
                p.identifier)
37✔
638

37✔
639
        rt := attempt.Route
37✔
640

37✔
641
        // Construct the first hop.
37✔
642
        firstHop := lnwire.NewShortChanIDFromInt(rt.Hops[0].ChannelID)
37✔
643

37✔
644
        // Craft an HTLC packet to send to the htlcswitch. The metadata within
37✔
645
        // this packet will be used to route the payment through the network,
37✔
646
        // starting with the first-hop.
37✔
647
        htlcAdd := &lnwire.UpdateAddHTLC{
37✔
648
                Amount:        rt.FirstHopAmount.Val.Int(),
37✔
649
                Expiry:        rt.TotalTimeLock,
37✔
650
                PaymentHash:   *attempt.Hash,
37✔
651
                CustomRecords: rt.FirstHopWireCustomRecords,
37✔
652
        }
37✔
653

37✔
654
        // Generate the raw encoded sphinx packet to be included along
37✔
655
        // with the htlcAdd message that we send directly to the
37✔
656
        // switch.
37✔
657
        onionBlob, err := attempt.OnionBlob()
37✔
658
        if err != nil {
37✔
UNCOV
659
                log.Errorf("Failed to create onion blob: attempt=%d in "+
×
UNCOV
660
                        "payment=%v, err:%v", attempt.AttemptID,
×
UNCOV
661
                        p.identifier, err)
×
UNCOV
662

×
UNCOV
663
                return p.failAttempt(attempt.AttemptID, err)
×
UNCOV
664
        }
×
665

666
        htlcAdd.OnionBlob = onionBlob
37✔
667

37✔
668
        // Send it to the Switch. When this method returns we assume
37✔
669
        // the Switch successfully has persisted the payment attempt,
37✔
670
        // such that we can resume waiting for the result after a
37✔
671
        // restart.
37✔
672
        err = p.router.cfg.Payer.SendHTLC(firstHop, attempt.AttemptID, htlcAdd)
37✔
673
        if err != nil {
45✔
674
                log.Errorf("Failed sending attempt %d for payment %v to "+
8✔
675
                        "switch: %v", attempt.AttemptID, p.identifier, err)
8✔
676

8✔
677
                return p.handleSwitchErr(attempt, err)
8✔
678
        }
8✔
679

680
        log.Debugf("Attempt %v for payment %v successfully sent to switch, "+
32✔
681
                "route: %v", attempt.AttemptID, p.identifier, &attempt.Route)
32✔
682

32✔
683
        return &attemptResult{
32✔
684
                attempt: attempt,
32✔
685
        }, nil
32✔
686
}
687

688
// amendFirstHopData is a function that calls the traffic shaper to allow it to
689
// add custom records to the outgoing HTLC and also adjust the amount if
690
// needed.
691
func (p *paymentLifecycle) amendFirstHopData(rt *route.Route) error {
40✔
692
        // The first hop amount on the route is the full route amount if not
40✔
693
        // overwritten by the traffic shaper. So we set the initial value now
40✔
694
        // and potentially overwrite it later.
40✔
695
        rt.FirstHopAmount = tlv.NewRecordT[tlv.TlvType0](
40✔
696
                tlv.NewBigSizeT(rt.TotalAmount),
40✔
697
        )
40✔
698

40✔
699
        // By default, we set the first hop custom records to the initial
40✔
700
        // value requested by the RPC. The traffic shaper may overwrite this
40✔
701
        // value.
40✔
702
        rt.FirstHopWireCustomRecords = p.firstHopCustomRecords
40✔
703

40✔
704
        // extraDataRequest is a helper struct to pass the custom records and
40✔
705
        // amount back from the traffic shaper.
40✔
706
        type extraDataRequest struct {
40✔
707
                customRecords fn.Option[lnwire.CustomRecords]
40✔
708

40✔
709
                amount fn.Option[lnwire.MilliSatoshi]
40✔
710
        }
40✔
711

40✔
712
        // If a hook exists that may affect our outgoing message, we call it now
40✔
713
        // and apply its side effects to the UpdateAddHTLC message.
40✔
714
        result, err := fn.MapOptionZ(
40✔
715
                p.router.cfg.TrafficShaper,
40✔
716
                //nolint:ll
40✔
717
                func(ts htlcswitch.AuxTrafficShaper) fn.Result[extraDataRequest] {
77✔
718
                        newAmt, newRecords, err := ts.ProduceHtlcExtraData(
37✔
719
                                rt.TotalAmount, p.firstHopCustomRecords,
37✔
720
                        )
37✔
721
                        if err != nil {
37✔
722
                                return fn.Err[extraDataRequest](err)
×
723
                        }
×
724

725
                        // Make sure we only received valid records.
726
                        if err := newRecords.Validate(); err != nil {
37✔
727
                                return fn.Err[extraDataRequest](err)
×
728
                        }
×
729

730
                        log.Debugf("Aux traffic shaper returned custom "+
37✔
731
                                "records %v and amount %d msat for HTLC",
37✔
732
                                spew.Sdump(newRecords), newAmt)
37✔
733

37✔
734
                        return fn.Ok(extraDataRequest{
37✔
735
                                customRecords: fn.Some(newRecords),
37✔
736
                                amount:        fn.Some(newAmt),
37✔
737
                        })
37✔
738
                },
739
        ).Unpack()
740
        if err != nil {
40✔
741
                return fmt.Errorf("traffic shaper failed to produce extra "+
×
742
                        "data: %w", err)
×
743
        }
×
744

745
        // Apply the side effects to the UpdateAddHTLC message.
746
        result.customRecords.WhenSome(func(records lnwire.CustomRecords) {
77✔
747
                rt.FirstHopWireCustomRecords = records
37✔
748
        })
37✔
749
        result.amount.WhenSome(func(amount lnwire.MilliSatoshi) {
77✔
750
                rt.FirstHopAmount = tlv.NewRecordT[tlv.TlvType0](
37✔
751
                        tlv.NewBigSizeT(amount),
37✔
752
                )
37✔
753
        })
37✔
754

755
        return nil
40✔
756
}
757

758
// failAttemptAndPayment fails both the payment and its attempt via the
759
// router's control tower, which marks the payment as failed in db.
760
func (p *paymentLifecycle) failPaymentAndAttempt(
761
        attemptID uint64, reason *channeldb.FailureReason,
762
        sendErr error) (*attemptResult, error) {
8✔
763

8✔
764
        log.Errorf("Payment %v failed: final_outcome=%v, raw_err=%v",
8✔
765
                p.identifier, *reason, sendErr)
8✔
766

8✔
767
        // Fail the payment via control tower.
8✔
768
        //
8✔
769
        // NOTE: we must fail the payment first before failing the attempt.
8✔
770
        // Otherwise, once the attempt is marked as failed, another goroutine
8✔
771
        // might make another attempt while we are failing the payment.
8✔
772
        err := p.router.cfg.Control.FailPayment(p.identifier, *reason)
8✔
773
        if err != nil {
8✔
774
                log.Errorf("Unable to fail payment: %v", err)
×
775
                return nil, err
×
776
        }
×
777

778
        // Fail the attempt.
779
        return p.failAttempt(attemptID, sendErr)
8✔
780
}
781

782
// handleSwitchErr inspects the given error from the Switch and determines
783
// whether we should make another payment attempt, or if it should be
784
// considered a terminal error. Terminal errors will be recorded with the
785
// control tower. It analyzes the sendErr for the payment attempt received from
786
// the switch and updates mission control and/or channel policies. Depending on
787
// the error type, the error is either the final outcome of the payment or we
788
// need to continue with an alternative route. A final outcome is indicated by
789
// a non-nil reason value.
790
func (p *paymentLifecycle) handleSwitchErr(attempt *channeldb.HTLCAttempt,
791
        sendErr error) (*attemptResult, error) {
27✔
792

27✔
793
        internalErrorReason := channeldb.FailureReasonError
27✔
794
        attemptID := attempt.AttemptID
27✔
795

27✔
796
        // reportAndFail is a helper closure that reports the failure to the
27✔
797
        // mission control, which helps us to decide whether we want to retry
27✔
798
        // the payment or not. If a non nil reason is returned from mission
27✔
799
        // control, it will further fail the payment via control tower.
27✔
800
        reportAndFail := func(srcIdx *int,
27✔
801
                msg lnwire.FailureMessage) (*attemptResult, error) {
49✔
802

22✔
803
                // Report outcome to mission control.
22✔
804
                reason, err := p.router.cfg.MissionControl.ReportPaymentFail(
22✔
805
                        attemptID, &attempt.Route, srcIdx, msg,
22✔
806
                )
22✔
807
                if err != nil {
22✔
808
                        log.Errorf("Error reporting payment result to mc: %v",
×
809
                                err)
×
810

×
811
                        reason = &internalErrorReason
×
812
                }
×
813

814
                // Fail the attempt only if there's no reason.
815
                if reason == nil {
42✔
816
                        // Fail the attempt.
20✔
817
                        return p.failAttempt(attemptID, sendErr)
20✔
818
                }
20✔
819

820
                // Otherwise fail both the payment and the attempt.
821
                return p.failPaymentAndAttempt(attemptID, reason, sendErr)
5✔
822
        }
823

824
        // If this attempt ID is unknown to the Switch, it means it was never
825
        // checkpointed and forwarded by the switch before a restart. In this
826
        // case we can safely send a new payment attempt, and wait for its
827
        // result to be available.
828
        if errors.Is(sendErr, htlcswitch.ErrPaymentIDNotFound) {
29✔
829
                log.Warnf("Failing attempt=%v for payment=%v as it's not "+
2✔
830
                        "found in the Switch", attempt.AttemptID, p.identifier)
2✔
831

2✔
832
                return p.failAttempt(attemptID, sendErr)
2✔
833
        }
2✔
834

835
        if errors.Is(sendErr, htlcswitch.ErrUnreadableFailureMessage) {
26✔
836
                log.Warn("Unreadable failure when sending htlc: id=%v, hash=%v",
1✔
837
                        attempt.AttemptID, attempt.Hash)
1✔
838

1✔
839
                // Since this error message cannot be decrypted, we will send a
1✔
840
                // nil error message to our mission controller and fail the
1✔
841
                // payment.
1✔
842
                return reportAndFail(nil, nil)
1✔
843
        }
1✔
844

845
        // If the error is a ClearTextError, we have received a valid wire
846
        // failure message, either from our own outgoing link or from a node
847
        // down the route. If the error is not related to the propagation of
848
        // our payment, we can stop trying because an internal error has
849
        // occurred.
850
        var rtErr htlcswitch.ClearTextError
24✔
851
        ok := errors.As(sendErr, &rtErr)
24✔
852
        if !ok {
27✔
853
                return p.failPaymentAndAttempt(
3✔
854
                        attemptID, &internalErrorReason, sendErr,
3✔
855
                )
3✔
856
        }
3✔
857

858
        // failureSourceIdx is the index of the node that the failure occurred
859
        // at. If the ClearTextError received is not a ForwardingError the
860
        // payment error occurred at our node, so we leave this value as 0
861
        // to indicate that the failure occurred locally. If the error is a
862
        // ForwardingError, it did not originate at our node, so we set
863
        // failureSourceIdx to the index of the node where the failure occurred.
864
        failureSourceIdx := 0
21✔
865
        var source *htlcswitch.ForwardingError
21✔
866
        ok = errors.As(rtErr, &source)
21✔
867
        if ok {
42✔
868
                failureSourceIdx = source.FailureSourceIdx
21✔
869
        }
21✔
870

871
        // Extract the wire failure and apply channel update if it contains one.
872
        // If we received an unknown failure message from a node along the
873
        // route, the failure message will be nil.
874
        failureMessage := rtErr.WireMessage()
21✔
875
        err := p.handleFailureMessage(
21✔
876
                &attempt.Route, failureSourceIdx, failureMessage,
21✔
877
        )
21✔
878
        if err != nil {
21✔
879
                return p.failPaymentAndAttempt(
×
880
                        attemptID, &internalErrorReason, sendErr,
×
881
                )
×
882
        }
×
883

884
        log.Tracef("Node=%v reported failure when sending htlc",
21✔
885
                failureSourceIdx)
21✔
886

21✔
887
        return reportAndFail(&failureSourceIdx, failureMessage)
21✔
888
}
889

890
// handleFailureMessage tries to apply a channel update present in the failure
891
// message if any.
892
func (p *paymentLifecycle) handleFailureMessage(rt *route.Route,
893
        errorSourceIdx int, failure lnwire.FailureMessage) error {
21✔
894

21✔
895
        if failure == nil {
22✔
896
                return nil
1✔
897
        }
1✔
898

899
        // It makes no sense to apply our own channel updates.
900
        if errorSourceIdx == 0 {
23✔
901
                log.Errorf("Channel update of ourselves received")
3✔
902

3✔
903
                return nil
3✔
904
        }
3✔
905

906
        // Extract channel update if the error contains one.
907
        update := p.router.extractChannelUpdate(failure)
20✔
908
        if update == nil {
32✔
909
                return nil
12✔
910
        }
12✔
911

912
        // Parse pubkey to allow validation of the channel update. This should
913
        // always succeed, otherwise there is something wrong in our
914
        // implementation. Therefore, return an error.
915
        errVertex := rt.Hops[errorSourceIdx-1].PubKeyBytes
11✔
916
        errSource, err := btcec.ParsePubKey(errVertex[:])
11✔
917
        if err != nil {
11✔
918
                log.Errorf("Cannot parse pubkey: idx=%v, pubkey=%v",
×
919
                        errorSourceIdx, errVertex)
×
920

×
921
                return err
×
922
        }
×
923

924
        var (
11✔
925
                isAdditionalEdge bool
11✔
926
                policy           *models.CachedEdgePolicy
11✔
927
        )
11✔
928

11✔
929
        // Before we apply the channel update, we need to decide whether the
11✔
930
        // update is for additional (ephemeral) edge or normal edge stored in
11✔
931
        // db.
11✔
932
        //
11✔
933
        // Note: the p.paySession might be nil here if it's called inside
11✔
934
        // SendToRoute where there's no payment lifecycle.
11✔
935
        if p.paySession != nil {
19✔
936
                policy = p.paySession.GetAdditionalEdgePolicy(
8✔
937
                        errSource, update.ShortChannelID.ToUint64(),
8✔
938
                )
8✔
939
                if policy != nil {
13✔
940
                        isAdditionalEdge = true
5✔
941
                }
5✔
942
        }
943

944
        // Apply channel update to additional edge policy.
945
        if isAdditionalEdge {
16✔
946
                if !p.paySession.UpdateAdditionalEdge(
5✔
947
                        update, errSource, policy) {
5✔
948

×
949
                        log.Debugf("Invalid channel update received: node=%v",
×
950
                                errVertex)
×
951
                }
×
952
                return nil
5✔
953
        }
954

955
        // Apply channel update to the channel edge policy in our db.
956
        if !p.router.cfg.ApplyChannelUpdate(update) {
14✔
957
                log.Debugf("Invalid channel update received: node=%v",
5✔
958
                        errVertex)
5✔
959
        }
5✔
960
        return nil
9✔
961
}
962

963
// failAttempt calls control tower to fail the current payment attempt.
964
func (p *paymentLifecycle) failAttempt(attemptID uint64,
965
        sendError error) (*attemptResult, error) {
27✔
966

27✔
967
        log.Warnf("Attempt %v for payment %v failed: %v", attemptID,
27✔
968
                p.identifier, sendError)
27✔
969

27✔
970
        failInfo := marshallError(
27✔
971
                sendError,
27✔
972
                p.router.cfg.Clock.Now(),
27✔
973
        )
27✔
974

27✔
975
        // Now that we are failing this payment attempt, cancel the shard with
27✔
976
        // the ShardTracker such that it can derive the correct hash for the
27✔
977
        // next attempt.
27✔
978
        if err := p.shardTracker.CancelShard(attemptID); err != nil {
27✔
979
                return nil, err
×
980
        }
×
981

982
        attempt, err := p.router.cfg.Control.FailAttempt(
27✔
983
                p.identifier, attemptID, failInfo,
27✔
984
        )
27✔
985
        if err != nil {
32✔
986
                return nil, err
5✔
987
        }
5✔
988

989
        return &attemptResult{
22✔
990
                attempt: attempt,
22✔
991
                err:     sendError,
22✔
992
        }, nil
22✔
993
}
994

995
// marshallError marshall an error as received from the switch to a structure
996
// that is suitable for database storage.
997
func marshallError(sendError error, time time.Time) *channeldb.HTLCFailInfo {
27✔
998
        response := &channeldb.HTLCFailInfo{
27✔
999
                FailTime: time,
27✔
1000
        }
27✔
1001

27✔
1002
        switch {
27✔
1003
        case errors.Is(sendError, htlcswitch.ErrPaymentIDNotFound):
2✔
1004
                response.Reason = channeldb.HTLCFailInternal
2✔
1005
                return response
2✔
1006

1007
        case errors.Is(sendError, htlcswitch.ErrUnreadableFailureMessage):
1✔
1008
                response.Reason = channeldb.HTLCFailUnreadable
1✔
1009
                return response
1✔
1010
        }
1011

1012
        var rtErr htlcswitch.ClearTextError
24✔
1013
        ok := errors.As(sendError, &rtErr)
24✔
1014
        if !ok {
27✔
1015
                response.Reason = channeldb.HTLCFailInternal
3✔
1016
                return response
3✔
1017
        }
3✔
1018

1019
        message := rtErr.WireMessage()
21✔
1020
        if message != nil {
41✔
1021
                response.Reason = channeldb.HTLCFailMessage
20✔
1022
                response.Message = message
20✔
1023
        } else {
21✔
1024
                response.Reason = channeldb.HTLCFailUnknown
1✔
1025
        }
1✔
1026

1027
        // If the ClearTextError received is a ForwardingError, the error
1028
        // originated from a node along the route, not locally on our outgoing
1029
        // link. We set failureSourceIdx to the index of the node where the
1030
        // failure occurred. If the error is not a ForwardingError, the failure
1031
        // occurred at our node, so we leave the index as 0 to indicate that
1032
        // we failed locally.
1033
        var fErr *htlcswitch.ForwardingError
21✔
1034
        ok = errors.As(rtErr, &fErr)
21✔
1035
        if ok {
42✔
1036
                response.FailureSourceIndex = uint32(fErr.FailureSourceIdx)
21✔
1037
        }
21✔
1038

1039
        return response
21✔
1040
}
1041

1042
// reloadInflightAttempts is called when the payment lifecycle is resumed after
1043
// a restart. It reloads all inflight attempts from the control tower and
1044
// collects the results of the attempts that have been sent before.
1045
func (p *paymentLifecycle) reloadInflightAttempts() (DBMPPayment, error) {
25✔
1046
        payment, err := p.router.cfg.Control.FetchPayment(p.identifier)
25✔
1047
        if err != nil {
26✔
1048
                return nil, err
1✔
1049
        }
1✔
1050

1051
        for _, a := range payment.InFlightHTLCs() {
27✔
1052
                a := a
3✔
1053

3✔
1054
                log.Infof("Resuming HTLC attempt %v for payment %v",
3✔
1055
                        a.AttemptID, p.identifier)
3✔
1056

3✔
1057
                p.resultCollector(&a)
3✔
1058
        }
3✔
1059

1060
        return payment, nil
24✔
1061
}
1062

1063
// processResultsAndReloadPayment returns the latest payment found in the db
1064
// (control tower) after all its attempt results are processed.
1065
func (p *paymentLifecycle) processResultsAndReloadPayment() (DBMPPayment,
1066
        *channeldb.MPPaymentState, error) {
72✔
1067

72✔
1068
        // Process the stored results first as they will affect the state of
72✔
1069
        // the payment.
72✔
1070
        if err := p.processSwitchResults(); err != nil {
72✔
NEW
1071
                return nil, nil, err
×
NEW
1072
        }
×
1073

1074
        // Read the db to get the latest state of the payment.
1075
        payment, err := p.router.cfg.Control.FetchPayment(p.identifier)
72✔
1076
        if err != nil {
72✔
NEW
1077
                return nil, nil, err
×
NEW
1078
        }
×
1079

1080
        ps := payment.GetState()
72✔
1081
        remainingFees := p.calcFeeBudget(ps.FeesPaid)
72✔
1082

72✔
1083
        log.Debugf("Payment %v: status=%v, active_shards=%v, rem_value=%v, "+
72✔
1084
                "fee_limit=%v", p.identifier, payment.GetStatus(),
72✔
1085
                ps.NumAttemptsInFlight, ps.RemainingAmt, remainingFees)
72✔
1086

72✔
1087
        return payment, ps, nil
72✔
1088
}
1089

1090
// processSwitchResults reads the `p.results` map and process the results
1091
// returned from the htlcswitch.
1092
func (p *paymentLifecycle) processSwitchResults() error {
73✔
1093
        // Create a slice to remember the results of the attempts that we have
73✔
1094
        // processed.
73✔
1095
        attempts := make([]*channeldb.HTLCAttempt, 0, p.switchResults.Len())
73✔
1096

73✔
1097
        var errReturned error
73✔
1098

73✔
1099
        // Range over the map to process all the results.
73✔
1100
        p.switchResults.Range(func(a *channeldb.HTLCAttempt,
73✔
1101
                result *htlcswitch.PaymentResult) bool {
99✔
1102

26✔
1103
                // Save the keys so we know which items to delete from the map.
26✔
1104
                attempts = append(attempts, a)
26✔
1105

26✔
1106
                // Handle the attempt result. If an error is returned here, it
26✔
1107
                // means the payment lifecycle needs to be terminated.
26✔
1108
                _, err := p.handleAttemptResult(a, result)
26✔
1109
                if err != nil {
27✔
1110
                        log.Errorf("Error handling result for attempt=%v in "+
1✔
1111
                                "payment%v: %v", a.AttemptID, p.identifier, err)
1✔
1112

1✔
1113
                        errReturned = err
1✔
1114
                }
1✔
1115

1116
                // Always return true so we will process all results.
1117
                return true
26✔
1118
        })
1119

1120
        // Clean up the processed results.
1121
        for _, a := range attempts {
99✔
1122
                p.switchResults.Delete(a)
26✔
1123
        }
26✔
1124

1125
        return errReturned
73✔
1126
}
1127

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

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

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

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

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

1✔
1165
                // We won't mark the attempt as failed since we already have
1✔
1166
                // the preimage.
1✔
1167
                return nil, err
1✔
1168
        }
1✔
1169

1170
        return &attemptResult{
17✔
1171
                attempt: htlcAttempt,
17✔
1172
        }, nil
17✔
1173
}
1174

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

15✔
1182
        result, err := p.collectResult(attempt)
15✔
1183
        if err != nil {
18✔
1184
                return nil, err
3✔
1185
        }
3✔
1186

1187
        return p.handleAttemptResult(attempt, result)
12✔
1188
}
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