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

lightningnetwork / lnd / 17494608790

05 Sep 2025 01:29PM UTC coverage: 54.616% (-12.0%) from 66.638%
17494608790

Pull #10200

github

web-flow
Merge 696bf5825 into 9d74ec477
Pull Request #10200: github: change to form-based issue template

109118 of 199792 relevant lines covered (54.62%)

21924.22 hits per line

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

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

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

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

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

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

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

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

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

51✔
83
        // Mount the result collector.
51✔
84
        p.resultCollector = p.collectResultAsync
51✔
85

51✔
86
        return p
51✔
87
}
51✔
88

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

102✔
94
        budget := p.feeLimit
102✔
95

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

105
        return budget
102✔
106
}
107

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

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

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

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

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

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

141
        // Exit early we need to make more attempts.
142
        if allow {
105✔
143
                return stepProceed, nil
31✔
144
        }
31✔
145

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

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

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

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

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

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

180
        case <-p.quit:
1✔
181
                return stepExit, ErrPaymentLifecycleExiting
1✔
182

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

187
        return stepSkip, nil
22✔
188
}
189

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

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

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

207
        // Get the payment status.
208
        status := payment.GetStatus()
21✔
209

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

5✔
222
                return [32]byte{}, nil, err
5✔
223
        }
5✔
224

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

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

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

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

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

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

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

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

278
                // Continue the for loop and proceed the rest.
279
                case stepProceed:
30✔
280

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

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

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

2✔
301
                        continue lifecycle
2✔
302
                }
303

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

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

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

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

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

338
        htlc, failure := payment.TerminalInfo()
16✔
339
        if htlc != nil {
28✔
340
                return htlc.Settle.Preimage, &htlc.Route, nil
12✔
341
        }
12✔
342

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

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

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

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

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

385
        return nil
69✔
386
}
387

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

34✔
393
        remainingFees := p.calcFeeBudget(ps.FeesPaid)
34✔
394

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

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

411
                return rt, nil
28✔
412
        }
413

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

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

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

4✔
434
        err = p.router.cfg.Control.FailPayment(p.identifier, failureCode)
4✔
435
        if err != nil {
5✔
436
                return nil, fmt.Errorf("FailPayment got: %w", err)
1✔
437
        }
1✔
438

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

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

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

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

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

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

×
478
                        return
×
479
                }
×
480

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

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

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

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

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

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

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

35✔
514
        result := &htlcswitch.PaymentResult{}
35✔
515

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

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

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

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

1✔
557
                result.Error = err
1✔
558

1✔
559
                return result, nil
1✔
560
        }
1✔
561

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

570
                result = r
31✔
571

572
        case <-p.quit:
1✔
573
                return nil, ErrPaymentLifecycleExiting
1✔
574

575
        case <-p.router.quit:
1✔
576
                return nil, ErrRouterShuttingDown
1✔
577
        }
578

579
        return result, nil
31✔
580
}
581

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

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

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

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

34✔
608
        return attempt, err
34✔
609
}
610

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

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

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

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

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

645
        if shard.AMP() != nil {
35✔
646
                hop.AMP = shard.AMP()
×
647
        }
×
648

649
        hash := shard.Hash()
35✔
650

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

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

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

34✔
669
        rt := attempt.Route
34✔
670

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

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

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

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

696
        htlcAdd.OnionBlob = onionBlob
34✔
697

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

5✔
707
                return p.handleSwitchErr(attempt, err)
5✔
708
        }
5✔
709

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

29✔
713
        return &attemptResult{
29✔
714
                attempt: attempt,
29✔
715
        }, nil
29✔
716
}
717

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

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

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

739
        firstHopPK := rt.Hops[0].PubKeyBytes
37✔
740

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

37✔
746
                amount fn.Option[lnwire.MilliSatoshi]
37✔
747
        }
37✔
748

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

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

768
                        log.Debugf("Aux traffic shaper returned custom "+
37✔
769
                                "records %v and amount %d msat for HTLC",
37✔
770
                                spew.Sdump(newRecords), newAmt)
37✔
771

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

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

793
        return nil
37✔
794
}
795

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

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

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

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

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

23✔
831
        internalErrorReason := paymentsdb.FailureReasonError
23✔
832
        attemptID := attempt.AttemptID
23✔
833

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

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

×
849
                        reason = &internalErrorReason
×
850
                }
×
851

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

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

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

1✔
870
                return p.failAttempt(attemptID, sendErr)
1✔
871
        }
1✔
872

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

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

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

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

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

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

18✔
925
        return reportAndFail(&failureSourceIdx, failureMessage)
18✔
926
}
927

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

18✔
933
        if failure == nil {
19✔
934
                return nil
1✔
935
        }
1✔
936

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

×
941
                return nil
×
942
        }
×
943

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

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

×
959
                return err
×
960
        }
×
961

962
        var (
8✔
963
                isAdditionalEdge bool
8✔
964
                policy           *models.CachedEdgePolicy
8✔
965
        )
8✔
966

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

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

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

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

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

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

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

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

1020
        attempt, err := p.router.cfg.Control.FailAttempt(
23✔
1021
                p.identifier, attemptID, failInfo,
23✔
1022
        )
23✔
1023
        if err != nil {
27✔
1024
                return nil, err
4✔
1025
        }
4✔
1026

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

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

23✔
1040
        switch {
23✔
1041
        case errors.Is(sendError, htlcswitch.ErrPaymentIDNotFound):
1✔
1042
                response.Reason = paymentsdb.HTLCFailInternal
1✔
1043
                return response
1✔
1044

1045
        case errors.Is(sendError, htlcswitch.ErrUnreadableFailureMessage):
1✔
1046
                response.Reason = paymentsdb.HTLCFailUnreadable
1✔
1047
                return response
1✔
1048
        }
1049

1050
        var rtErr htlcswitch.ClearTextError
21✔
1051
        ok := errors.As(sendError, &rtErr)
21✔
1052
        if !ok {
24✔
1053
                response.Reason = paymentsdb.HTLCFailInternal
3✔
1054
                return response
3✔
1055
        }
3✔
1056

1057
        message := rtErr.WireMessage()
18✔
1058
        if message != nil {
35✔
1059
                response.Reason = paymentsdb.HTLCFailMessage
17✔
1060
                response.Message = message
17✔
1061
        } else {
18✔
1062
                response.Reason = paymentsdb.HTLCFailUnknown
1✔
1063
        }
1✔
1064

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

1077
        return response
18✔
1078
}
1079

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

1✔
1089
        // Exit early if this is not a legacy attempt.
1✔
1090
        if a.Hash != nil {
1✔
1091
                return a
×
1092
        }
×
1093

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

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

1✔
1109
        return a
1✔
1110
}
1111

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

23✔
1118
        payment, err := p.router.cfg.Control.FetchPayment(p.identifier)
23✔
1119
        if err != nil {
24✔
1120
                return nil, err
1✔
1121
        }
1✔
1122

1123
        for _, a := range payment.InFlightHTLCs() {
23✔
1124
                a := a
1✔
1125

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

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

1✔
1133
                p.resultCollector(&a)
1✔
1134
        }
1✔
1135

1136
        return payment, nil
22✔
1137
}
1138

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

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

1149
        ps := payment.GetState()
68✔
1150
        remainingFees := p.calcFeeBudget(ps.FeesPaid)
68✔
1151

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

68✔
1156
        return payment, ps, nil
68✔
1157
}
1158

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

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

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

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

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

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

1201
        return &attemptResult{
14✔
1202
                attempt: htlcAttempt,
14✔
1203
        }, nil
14✔
1204
}
1205

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

12✔
1213
        result, err := p.collectResult(attempt)
12✔
1214
        if err != nil {
15✔
1215
                return nil, err
3✔
1216
        }
3✔
1217

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