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

lightningnetwork / lnd / 13200467477

07 Feb 2025 01:06PM UTC coverage: 58.803% (-0.02%) from 58.818%
13200467477

Pull #9150

github

yyforyongyu
fixup! routing: always update payment in the same goroutine
Pull Request #9150: routing+htlcswitch: fix stuck inflight payments

176 of 218 new or added lines in 4 files covered. (80.73%)

81 existing lines in 24 files now uncovered.

136214 of 231646 relevant lines covered (58.8%)

19314.56 hits per line

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

91.82
/routing/payment_lifecycle.go
1
package routing
2

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

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

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

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

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

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

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

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

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

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

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

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

53✔
85
        return p
53✔
86
}
53✔
87

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

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

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

104
        return budget
106✔
105
}
106

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

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

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

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

123
// decideNextStep is used to determine the next step in the payment lifecycle.
124
func (p *paymentLifecycle) decideNextStep(
125
        payment DBMPPayment) (stateStep, error) {
79✔
126

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

133
        if !allow {
123✔
134
                // Check whether we need to wait for results.
46✔
135
                wait, err := payment.NeedWaitAttempts()
46✔
136
                if err != nil {
47✔
137
                        return stepExit, err
1✔
138
                }
1✔
139

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

146
                log.Tracef("Waiting for attempt results for payment %v",
28✔
147
                        p.identifier)
28✔
148

28✔
149
                // Otherwise we wait for the result for one HTLC attempt then
28✔
150
                // continue the lifecycle.
28✔
151
                select {
28✔
152
                case r := <-p.resultCollected:
26✔
153
                        log.Tracef("Received attempt result for payment %v",
26✔
154
                                p.identifier)
26✔
155

26✔
156
                        // Handle the result here. If there's no error, we will
26✔
157
                        // return stepSkip and move to the next lifecycle
26✔
158
                        // iteration, which will refresh the payment and wait
26✔
159
                        // for the next attempt result, if any.
26✔
160
                        _, err := p.handleAttemptResult(r.attempt, r.result)
26✔
161

26✔
162
                        // We would only get a DB-related error here, which will
26✔
163
                        // cause us to abort the payment flow.
26✔
164
                        if err != nil {
27✔
165
                                return stepExit, err
1✔
166
                        }
1✔
167

168
                case <-p.quit:
1✔
169
                        return stepExit, ErrPaymentLifecycleExiting
1✔
170

171
                case <-p.router.quit:
4✔
172
                        return stepExit, ErrRouterShuttingDown
4✔
173
                }
174

175
                return stepSkip, nil
25✔
176
        }
177

178
        // Otherwise we need to make more attempts.
179
        return stepProceed, nil
34✔
180
}
181

182
// resumePayment resumes the paymentLifecycle from the current state.
183
func (p *paymentLifecycle) resumePayment(ctx context.Context) ([32]byte,
184
        *route.Route, error) {
25✔
185

25✔
186
        // When the payment lifecycle loop exits, we make sure to signal any
25✔
187
        // sub goroutine of the HTLC attempt to exit, then wait for them to
25✔
188
        // return.
25✔
189
        defer p.stop()
25✔
190

25✔
191
        // If we had any existing attempts outstanding, we'll start by spinning
25✔
192
        // up goroutines that'll collect their results and deliver them to the
25✔
193
        // lifecycle loop below.
25✔
194
        payment, err := p.reloadInflightAttempts()
25✔
195
        if err != nil {
26✔
196
                return [32]byte{}, nil, err
1✔
197
        }
1✔
198

199
        // Get the payment status.
200
        status := payment.GetStatus()
24✔
201

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

8✔
214
                return [32]byte{}, nil, err
8✔
215
        }
8✔
216

217
        // We'll continue until either our payment succeeds, or we encounter a
218
        // critical error during path finding.
219
lifecycle:
24✔
220
        for {
96✔
221
                // We update the payment state on every iteration.
72✔
222
                currentPayment, ps, err := p.reloadPayment()
72✔
223
                if err != nil {
72✔
224
                        return exitWithErr(err)
×
225
                }
×
226

227
                // Reassign status so it can be read in `exitWithErr`.
228
                status = currentPayment.GetStatus()
72✔
229

72✔
230
                // Reassign payment such that when the lifecycle exits, the
72✔
231
                // latest payment can be read when we access its terminal info.
72✔
232
                payment = currentPayment
72✔
233

72✔
234
                // We now proceed our lifecycle with the following tasks in
72✔
235
                // order,
72✔
236
                //   1. check context.
72✔
237
                //   2. request route.
72✔
238
                //   3. create HTLC attempt.
72✔
239
                //   4. send HTLC attempt.
72✔
240
                //   5. collect HTLC attempt result.
72✔
241
                //
72✔
242
                // Before we attempt any new shard, we'll check to see if we've
72✔
243
                // gone past the payment attempt timeout, or if the context was
72✔
244
                // cancelled, or the router is exiting. In any of these cases,
72✔
245
                // we'll stop this payment attempt short.
72✔
246
                if err := p.checkContext(ctx); err != nil {
73✔
247
                        return exitWithErr(err)
1✔
248
                }
1✔
249

250
                // Now decide the next step of the current lifecycle.
251
                step, err := p.decideNextStep(payment)
71✔
252
                if err != nil {
75✔
253
                        return exitWithErr(err)
4✔
254
                }
4✔
255

256
                switch step {
70✔
257
                // Exit the for loop and return below.
258
                case stepExit:
19✔
259
                        break lifecycle
19✔
260

261
                // Continue the for loop and skip the rest.
262
                case stepSkip:
24✔
263
                        continue lifecycle
24✔
264

265
                // Continue the for loop and proceed the rest.
266
                case stepProceed:
33✔
267

268
                // Unknown step received, exit with an error.
269
                default:
×
270
                        err = fmt.Errorf("unknown step: %v", step)
×
271
                        return exitWithErr(err)
×
272
                }
273

274
                // Now request a route to be used to create our HTLC attempt.
275
                rt, err := p.requestRoute(ps)
33✔
276
                if err != nil {
34✔
277
                        return exitWithErr(err)
1✔
278
                }
1✔
279

280
                // We may not be able to find a route for current attempt. In
281
                // that case, we continue the loop and move straight to the
282
                // next iteration in case there are results for inflight HTLCs
283
                // that still need to be collected.
284
                if rt == nil {
37✔
285
                        log.Errorf("No route found for payment %v",
5✔
286
                                p.identifier)
5✔
287

5✔
288
                        continue lifecycle
5✔
289
                }
290

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

30✔
293
                // We found a route to try, create a new HTLC attempt to try.
30✔
294
                attempt, err := p.registerAttempt(rt, ps.RemainingAmt)
30✔
295
                if err != nil {
31✔
296
                        return exitWithErr(err)
1✔
297
                }
1✔
298

299
                // Once the attempt is created, send it to the htlcswitch.
300
                result, err := p.sendAttempt(attempt)
29✔
301
                if err != nil {
30✔
302
                        return exitWithErr(err)
1✔
303
                }
1✔
304

305
                // Now that the shard was successfully sent, launch a go
306
                // routine that will handle its result when its back.
307
                if result.err == nil {
55✔
308
                        p.resultCollector(attempt)
27✔
309
                }
27✔
310
        }
311

312
        // Once we are out the lifecycle loop, it means we've reached a
313
        // terminal condition. We either return the settled preimage or the
314
        // payment's failure reason.
315
        //
316
        // Optionally delete the failed attempts from the database.
317
        err = p.router.cfg.Control.DeleteFailedAttempts(p.identifier)
19✔
318
        if err != nil {
19✔
319
                log.Errorf("Error deleting failed htlc attempts for payment "+
×
320
                        "%v: %v", p.identifier, err)
×
321
        }
×
322

323
        htlc, failure := payment.TerminalInfo()
19✔
324
        if htlc != nil {
34✔
325
                return htlc.Settle.Preimage, &htlc.Route, nil
15✔
326
        }
15✔
327

328
        // Otherwise return the payment failure reason.
329
        return [32]byte{}, nil, *failure
7✔
330
}
331

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

353
                // By marking the payment failed, depending on whether it has
354
                // inflight HTLCs or not, its status will now either be
355
                // `StatusInflight` or `StatusFailed`. In either case, no more
356
                // HTLCs will be attempted.
357
                err := p.router.cfg.Control.FailPayment(p.identifier, reason)
7✔
358
                if err != nil {
8✔
359
                        return fmt.Errorf("FailPayment got %w", err)
1✔
360
                }
1✔
361

362
        case <-p.router.quit:
2✔
363
                return fmt.Errorf("check payment timeout got: %w",
2✔
364
                        ErrRouterShuttingDown)
2✔
365

366
        // Fall through if we haven't hit our time limit.
367
        default:
69✔
368
        }
369

370
        return nil
72✔
371
}
372

373
// requestRoute is responsible for finding a route to be used to create an HTLC
374
// attempt.
375
func (p *paymentLifecycle) requestRoute(
376
        ps *channeldb.MPPaymentState) (*route.Route, error) {
37✔
377

37✔
378
        remainingFees := p.calcFeeBudget(ps.FeesPaid)
37✔
379

37✔
380
        // Query our payment session to construct a route.
37✔
381
        rt, err := p.paySession.RequestRoute(
37✔
382
                ps.RemainingAmt, remainingFees,
37✔
383
                uint32(ps.NumAttemptsInFlight), uint32(p.currentHeight),
37✔
384
                p.firstHopCustomRecords,
37✔
385
        )
37✔
386

37✔
387
        // Exit early if there's no error.
37✔
388
        if err == nil {
68✔
389
                // Allow the traffic shaper to add custom records to the
31✔
390
                // outgoing HTLC and also adjust the amount if needed.
31✔
391
                err = p.amendFirstHopData(rt)
31✔
392
                if err != nil {
31✔
NEW
393
                        return nil, err
×
NEW
394
                }
×
395

396
                return rt, nil
31✔
397
        }
398

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

9✔
402
        // If the error belongs to `noRouteError` set, it means a non-critical
9✔
403
        // error has happened during path finding, and we will mark the payment
9✔
404
        // failed with this reason. Otherwise, we'll return the critical error
9✔
405
        // found to abort the lifecycle.
9✔
406
        var routeErr noRouteError
9✔
407
        if !errors.As(err, &routeErr) {
11✔
408
                return nil, fmt.Errorf("requestRoute got: %w", err)
2✔
409
        }
2✔
410

411
        // It's the `paymentSession`'s responsibility to find a route for us
412
        // with the best effort. When it cannot find a path, we need to treat it
413
        // as a terminal condition and fail the payment no matter it has
414
        // inflight HTLCs or not.
415
        failureCode := routeErr.FailureReason()
7✔
416
        log.Warnf("Marking payment %v permanently failed with no route: %v",
7✔
417
                p.identifier, failureCode)
7✔
418

7✔
419
        err = p.router.cfg.Control.FailPayment(p.identifier, failureCode)
7✔
420
        if err != nil {
8✔
421
                return nil, fmt.Errorf("FailPayment got: %w", err)
1✔
422
        }
1✔
423

424
        // NOTE: we decide to not return the non-critical noRouteError here to
425
        // avoid terminating the payment lifecycle as there might be other
426
        // inflight HTLCs which we must wait for their results.
427
        return nil, nil
6✔
428
}
429

430
// stop signals any active shard goroutine to exit.
431
func (p *paymentLifecycle) stop() {
26✔
432
        close(p.quit)
26✔
433
}
26✔
434

435
// attemptResult holds the HTLC attempt and a possible error returned from
436
// sending it.
437
type attemptResult struct {
438
        // err is non-nil if a non-critical error was encountered when trying
439
        // to send the attempt, and we successfully updated the control tower
440
        // to reflect this error. This can be errors like not enough local
441
        // balance for the given route etc.
442
        err error
443

444
        // attempt is the attempt structure as recorded in the database.
445
        attempt *channeldb.HTLCAttempt
446
}
447

448
// collectResultAsync launches a goroutine that will wait for the result of the
449
// given HTLC attempt to be available then save its result in a map. Once
450
// received, it will send the result returned from the switch to channel
451
// `resultCollected`.
452
func (p *paymentLifecycle) collectResultAsync(attempt *channeldb.HTLCAttempt) {
25✔
453
        log.Debugf("Collecting result for attempt %v in payment %v",
25✔
454
                attempt.AttemptID, p.identifier)
25✔
455

25✔
456
        go func() {
50✔
457
                result, err := p.collectResult(attempt)
25✔
458
                if err != nil {
28✔
459
                        log.Errorf("Error collecting result for attempt %v in "+
3✔
460
                                "payment %v: %v", attempt.AttemptID,
3✔
461
                                p.identifier, err)
3✔
462

3✔
463
                        return
3✔
464
                }
3✔
465

466
                log.Debugf("Result collected for attempt %v in payment %v",
25✔
467
                        attempt.AttemptID, p.identifier)
25✔
468

25✔
469
                // Create a switch result and send it to the resultCollected
25✔
470
                // chan, which gets processed when the lifecycle is waiting for
25✔
471
                // a result to be received in decideNextStep.
25✔
472
                r := &switchResult{
25✔
473
                        attempt: attempt,
25✔
474
                        result:  result,
25✔
475
                }
25✔
476

25✔
477
                // Signal that a result has been collected.
25✔
478
                select {
25✔
479
                // Send the result so decideNextStep can proceed.
480
                case p.resultCollected <- r:
25✔
481

UNCOV
482
                case <-p.quit:
×
UNCOV
483
                        log.Debugf("Lifecycle exiting while collecting "+
×
UNCOV
484
                                "result for payment %v", p.identifier)
×
485

UNCOV
486
                case <-p.router.quit:
×
487
                }
488
        }()
489
}
490

491
// collectResult waits for the result of the given HTLC attempt to be sent by
492
// the switch and returns it.
493
func (p *paymentLifecycle) collectResult(
494
        attempt *channeldb.HTLCAttempt) (*htlcswitch.PaymentResult, error) {
37✔
495

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

37✔
498
        result := &htlcswitch.PaymentResult{}
37✔
499

37✔
500
        // Regenerate the circuit for this attempt.
37✔
501
        circuit, err := attempt.Circuit()
37✔
502

37✔
503
        // TODO(yy): We generate this circuit to create the error decryptor,
37✔
504
        // which is then used in htlcswitch as the deobfuscator to decode the
37✔
505
        // error from `UpdateFailHTLC`. However, suppose it's an
37✔
506
        // `UpdateFulfillHTLC` message yet for some reason the sphinx packet is
37✔
507
        // failed to be generated, we'd miss settling it. This means we should
37✔
508
        // give it a second chance to try the settlement path in case
37✔
509
        // `GetAttemptResult` gives us back the preimage. And move the circuit
37✔
510
        // creation into htlcswitch so it's only constructed when there's a
37✔
511
        // failure message we need to decode.
37✔
512
        if err != nil {
37✔
513
                log.Debugf("Unable to generate circuit for attempt %v: %v",
×
514
                        attempt.AttemptID, err)
×
NEW
515
                return nil, err
×
516
        }
×
517

518
        // Using the created circuit, initialize the error decrypter, so we can
519
        // parse+decode any failures incurred by this payment within the
520
        // switch.
521
        errorDecryptor := &htlcswitch.SphinxErrorDecrypter{
37✔
522
                OnionErrorDecrypter: sphinx.NewOnionErrorDecrypter(circuit),
37✔
523
        }
37✔
524

37✔
525
        // Now ask the switch to return the result of the payment when
37✔
526
        // available.
37✔
527
        //
37✔
528
        // TODO(yy): consider using htlcswitch to create the `errorDecryptor`
37✔
529
        // since the htlc is already in db. This will also make the interface
37✔
530
        // `PaymentAttemptDispatcher` deeper and easier to use. Moreover, we'd
37✔
531
        // only create the decryptor when received a failure, further saving us
37✔
532
        // a few CPU cycles.
37✔
533
        resultChan, err := p.router.cfg.Payer.GetAttemptResult(
37✔
534
                attempt.AttemptID, p.identifier, errorDecryptor,
37✔
535
        )
37✔
536
        // Handle the switch error.
37✔
537
        if err != nil {
38✔
538
                log.Errorf("Failed getting result for attemptID %d "+
1✔
539
                        "from switch: %v", attempt.AttemptID, err)
1✔
540

1✔
541
                result.Error = err
1✔
542

1✔
543
                return result, nil
1✔
544
        }
1✔
545

546
        // The switch knows about this payment, we'll wait for a result to be
547
        // available.
548
        select {
36✔
549
        case r, ok := <-resultChan:
34✔
550
                if !ok {
38✔
551
                        return nil, htlcswitch.ErrSwitchExiting
4✔
552
                }
4✔
553

554
                result = r
33✔
555

556
        case <-p.quit:
1✔
557
                return nil, ErrPaymentLifecycleExiting
1✔
558

559
        case <-p.router.quit:
1✔
560
                return nil, ErrRouterShuttingDown
1✔
561
        }
562

563
        return result, nil
33✔
564
}
565

566
// registerAttempt is responsible for creating and saving an HTLC attempt in db
567
// by using the route info provided. The `remainingAmt` is used to decide
568
// whether this is the last attempt.
569
func (p *paymentLifecycle) registerAttempt(rt *route.Route,
570
        remainingAmt lnwire.MilliSatoshi) (*channeldb.HTLCAttempt, error) {
39✔
571

39✔
572
        // If this route will consume the last remaining amount to send
39✔
573
        // to the receiver, this will be our last shard (for now).
39✔
574
        isLastAttempt := rt.ReceiverAmt() == remainingAmt
39✔
575

39✔
576
        // Using the route received from the payment session, create a new
39✔
577
        // shard to send.
39✔
578
        attempt, err := p.createNewPaymentAttempt(rt, isLastAttempt)
39✔
579
        if err != nil {
41✔
580
                return nil, err
2✔
581
        }
2✔
582

583
        // Before sending this HTLC to the switch, we checkpoint the fresh
584
        // paymentID and route to the DB. This lets us know on startup the ID
585
        // of the payment that we attempted to send, such that we can query the
586
        // Switch for its whereabouts. The route is needed to handle the result
587
        // when it eventually comes back.
588
        err = p.router.cfg.Control.RegisterAttempt(
37✔
589
                p.identifier, &attempt.HTLCAttemptInfo,
37✔
590
        )
37✔
591

37✔
592
        return attempt, err
37✔
593
}
594

595
// createNewPaymentAttempt creates a new payment attempt from the given route.
596
func (p *paymentLifecycle) createNewPaymentAttempt(rt *route.Route,
597
        lastShard bool) (*channeldb.HTLCAttempt, error) {
39✔
598

39✔
599
        // Generate a new key to be used for this attempt.
39✔
600
        sessionKey, err := generateNewSessionKey()
39✔
601
        if err != nil {
39✔
602
                return nil, err
×
603
        }
×
604

605
        // We generate a new, unique payment ID that we will use for
606
        // this HTLC.
607
        attemptID, err := p.router.cfg.NextPaymentID()
39✔
608
        if err != nil {
39✔
609
                return nil, err
×
610
        }
×
611

612
        // Request a new shard from the ShardTracker. If this is an AMP
613
        // payment, and this is the last shard, the outstanding shards together
614
        // with this one will be enough for the receiver to derive all HTLC
615
        // preimages. If this a non-AMP payment, the ShardTracker will return a
616
        // simple shard with the payment's static payment hash.
617
        shard, err := p.shardTracker.NewShard(attemptID, lastShard)
39✔
618
        if err != nil {
40✔
619
                return nil, err
1✔
620
        }
1✔
621

622
        // If this shard carries MPP or AMP options, add them to the last hop
623
        // on the route.
624
        hop := rt.Hops[len(rt.Hops)-1]
38✔
625
        if shard.MPP() != nil {
45✔
626
                hop.MPP = shard.MPP()
7✔
627
        }
7✔
628

629
        if shard.AMP() != nil {
41✔
630
                hop.AMP = shard.AMP()
3✔
631
        }
3✔
632

633
        hash := shard.Hash()
38✔
634

38✔
635
        // We now have all the information needed to populate the current
38✔
636
        // attempt information.
38✔
637
        return channeldb.NewHtlcAttempt(
38✔
638
                attemptID, sessionKey, *rt, p.router.cfg.Clock.Now(), &hash,
38✔
639
        )
38✔
640
}
641

642
// sendAttempt attempts to send the current attempt to the switch to complete
643
// the payment. If this attempt fails, then we'll continue on to the next
644
// available route.
645
func (p *paymentLifecycle) sendAttempt(
646
        attempt *channeldb.HTLCAttempt) (*attemptResult, error) {
37✔
647

37✔
648
        log.Debugf("Sending HTLC attempt(id=%v, total_amt=%v, first_hop_amt=%d"+
37✔
649
                ") for payment %v", attempt.AttemptID,
37✔
650
                attempt.Route.TotalAmount, attempt.Route.FirstHopAmount.Val,
37✔
651
                p.identifier)
37✔
652

37✔
653
        rt := attempt.Route
37✔
654

37✔
655
        // Construct the first hop.
37✔
656
        firstHop := lnwire.NewShortChanIDFromInt(rt.Hops[0].ChannelID)
37✔
657

37✔
658
        // Craft an HTLC packet to send to the htlcswitch. The metadata within
37✔
659
        // this packet will be used to route the payment through the network,
37✔
660
        // starting with the first-hop.
37✔
661
        htlcAdd := &lnwire.UpdateAddHTLC{
37✔
662
                Amount:        rt.FirstHopAmount.Val.Int(),
37✔
663
                Expiry:        rt.TotalTimeLock,
37✔
664
                PaymentHash:   *attempt.Hash,
37✔
665
                CustomRecords: rt.FirstHopWireCustomRecords,
37✔
666
        }
37✔
667

37✔
668
        // Generate the raw encoded sphinx packet to be included along
37✔
669
        // with the htlcAdd message that we send directly to the
37✔
670
        // switch.
37✔
671
        onionBlob, err := attempt.OnionBlob()
37✔
672
        if err != nil {
37✔
UNCOV
673
                log.Errorf("Failed to create onion blob: attempt=%d in "+
×
UNCOV
674
                        "payment=%v, err:%v", attempt.AttemptID,
×
UNCOV
675
                        p.identifier, err)
×
UNCOV
676

×
UNCOV
677
                return p.failAttempt(attempt.AttemptID, err)
×
UNCOV
678
        }
×
679

680
        htlcAdd.OnionBlob = onionBlob
37✔
681

37✔
682
        // Send it to the Switch. When this method returns we assume
37✔
683
        // the Switch successfully has persisted the payment attempt,
37✔
684
        // such that we can resume waiting for the result after a
37✔
685
        // restart.
37✔
686
        err = p.router.cfg.Payer.SendHTLC(firstHop, attempt.AttemptID, htlcAdd)
37✔
687
        if err != nil {
45✔
688
                log.Errorf("Failed sending attempt %d for payment %v to "+
8✔
689
                        "switch: %v", attempt.AttemptID, p.identifier, err)
8✔
690

8✔
691
                return p.handleSwitchErr(attempt, err)
8✔
692
        }
8✔
693

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

32✔
697
        return &attemptResult{
32✔
698
                attempt: attempt,
32✔
699
        }, nil
32✔
700
}
701

702
// amendFirstHopData is a function that calls the traffic shaper to allow it to
703
// add custom records to the outgoing HTLC and also adjust the amount if
704
// needed.
705
func (p *paymentLifecycle) amendFirstHopData(rt *route.Route) error {
40✔
706
        // The first hop amount on the route is the full route amount if not
40✔
707
        // overwritten by the traffic shaper. So we set the initial value now
40✔
708
        // and potentially overwrite it later.
40✔
709
        rt.FirstHopAmount = tlv.NewRecordT[tlv.TlvType0](
40✔
710
                tlv.NewBigSizeT(rt.TotalAmount),
40✔
711
        )
40✔
712

40✔
713
        // By default, we set the first hop custom records to the initial
40✔
714
        // value requested by the RPC. The traffic shaper may overwrite this
40✔
715
        // value.
40✔
716
        rt.FirstHopWireCustomRecords = p.firstHopCustomRecords
40✔
717

40✔
718
        // extraDataRequest is a helper struct to pass the custom records and
40✔
719
        // amount back from the traffic shaper.
40✔
720
        type extraDataRequest struct {
40✔
721
                customRecords fn.Option[lnwire.CustomRecords]
40✔
722

40✔
723
                amount fn.Option[lnwire.MilliSatoshi]
40✔
724
        }
40✔
725

40✔
726
        // If a hook exists that may affect our outgoing message, we call it now
40✔
727
        // and apply its side effects to the UpdateAddHTLC message.
40✔
728
        result, err := fn.MapOptionZ(
40✔
729
                p.router.cfg.TrafficShaper,
40✔
730
                //nolint:ll
40✔
731
                func(ts htlcswitch.AuxTrafficShaper) fn.Result[extraDataRequest] {
77✔
732
                        newAmt, newRecords, err := ts.ProduceHtlcExtraData(
37✔
733
                                rt.TotalAmount, p.firstHopCustomRecords,
37✔
734
                        )
37✔
735
                        if err != nil {
37✔
736
                                return fn.Err[extraDataRequest](err)
×
737
                        }
×
738

739
                        // Make sure we only received valid records.
740
                        if err := newRecords.Validate(); err != nil {
37✔
741
                                return fn.Err[extraDataRequest](err)
×
742
                        }
×
743

744
                        log.Debugf("Aux traffic shaper returned custom "+
37✔
745
                                "records %v and amount %d msat for HTLC",
37✔
746
                                spew.Sdump(newRecords), newAmt)
37✔
747

37✔
748
                        return fn.Ok(extraDataRequest{
37✔
749
                                customRecords: fn.Some(newRecords),
37✔
750
                                amount:        fn.Some(newAmt),
37✔
751
                        })
37✔
752
                },
753
        ).Unpack()
754
        if err != nil {
40✔
755
                return fmt.Errorf("traffic shaper failed to produce extra "+
×
756
                        "data: %w", err)
×
757
        }
×
758

759
        // Apply the side effects to the UpdateAddHTLC message.
760
        result.customRecords.WhenSome(func(records lnwire.CustomRecords) {
77✔
761
                rt.FirstHopWireCustomRecords = records
37✔
762
        })
37✔
763
        result.amount.WhenSome(func(amount lnwire.MilliSatoshi) {
77✔
764
                rt.FirstHopAmount = tlv.NewRecordT[tlv.TlvType0](
37✔
765
                        tlv.NewBigSizeT(amount),
37✔
766
                )
37✔
767
        })
37✔
768

769
        return nil
40✔
770
}
771

772
// failAttemptAndPayment fails both the payment and its attempt via the
773
// router's control tower, which marks the payment as failed in db.
774
func (p *paymentLifecycle) failPaymentAndAttempt(
775
        attemptID uint64, reason *channeldb.FailureReason,
776
        sendErr error) (*attemptResult, error) {
8✔
777

8✔
778
        log.Errorf("Payment %v failed: final_outcome=%v, raw_err=%v",
8✔
779
                p.identifier, *reason, sendErr)
8✔
780

8✔
781
        // Fail the payment via control tower.
8✔
782
        //
8✔
783
        // NOTE: we must fail the payment first before failing the attempt.
8✔
784
        // Otherwise, once the attempt is marked as failed, another goroutine
8✔
785
        // might make another attempt while we are failing the payment.
8✔
786
        err := p.router.cfg.Control.FailPayment(p.identifier, *reason)
8✔
787
        if err != nil {
8✔
788
                log.Errorf("Unable to fail payment: %v", err)
×
789
                return nil, err
×
790
        }
×
791

792
        // Fail the attempt.
793
        return p.failAttempt(attemptID, sendErr)
8✔
794
}
795

796
// handleSwitchErr inspects the given error from the Switch and determines
797
// whether we should make another payment attempt, or if it should be
798
// considered a terminal error. Terminal errors will be recorded with the
799
// control tower. It analyzes the sendErr for the payment attempt received from
800
// the switch and updates mission control and/or channel policies. Depending on
801
// the error type, the error is either the final outcome of the payment or we
802
// need to continue with an alternative route. A final outcome is indicated by
803
// a non-nil reason value.
804
func (p *paymentLifecycle) handleSwitchErr(attempt *channeldb.HTLCAttempt,
805
        sendErr error) (*attemptResult, error) {
26✔
806

26✔
807
        internalErrorReason := channeldb.FailureReasonError
26✔
808
        attemptID := attempt.AttemptID
26✔
809

26✔
810
        // reportAndFail is a helper closure that reports the failure to the
26✔
811
        // mission control, which helps us to decide whether we want to retry
26✔
812
        // the payment or not. If a non nil reason is returned from mission
26✔
813
        // control, it will further fail the payment via control tower.
26✔
814
        reportAndFail := func(srcIdx *int,
26✔
815
                msg lnwire.FailureMessage) (*attemptResult, error) {
48✔
816

22✔
817
                // Report outcome to mission control.
22✔
818
                reason, err := p.router.cfg.MissionControl.ReportPaymentFail(
22✔
819
                        attemptID, &attempt.Route, srcIdx, msg,
22✔
820
                )
22✔
821
                if err != nil {
22✔
822
                        log.Errorf("Error reporting payment result to mc: %v",
×
823
                                err)
×
824

×
825
                        reason = &internalErrorReason
×
826
                }
×
827

828
                // Fail the attempt only if there's no reason.
829
                if reason == nil {
42✔
830
                        // Fail the attempt.
20✔
831
                        return p.failAttempt(attemptID, sendErr)
20✔
832
                }
20✔
833

834
                // Otherwise fail both the payment and the attempt.
835
                return p.failPaymentAndAttempt(attemptID, reason, sendErr)
5✔
836
        }
837

838
        // If this attempt ID is unknown to the Switch, it means it was never
839
        // checkpointed and forwarded by the switch before a restart. In this
840
        // case we can safely send a new payment attempt, and wait for its
841
        // result to be available.
842
        if errors.Is(sendErr, htlcswitch.ErrPaymentIDNotFound) {
27✔
843
                log.Warnf("Failing attempt=%v for payment=%v as it's not "+
1✔
844
                        "found in the Switch", attempt.AttemptID, p.identifier)
1✔
845

1✔
846
                return p.failAttempt(attemptID, sendErr)
1✔
847
        }
1✔
848

849
        if errors.Is(sendErr, htlcswitch.ErrUnreadableFailureMessage) {
26✔
850
                log.Warn("Unreadable failure when sending htlc: id=%v, hash=%v",
1✔
851
                        attempt.AttemptID, attempt.Hash)
1✔
852

1✔
853
                // Since this error message cannot be decrypted, we will send a
1✔
854
                // nil error message to our mission controller and fail the
1✔
855
                // payment.
1✔
856
                return reportAndFail(nil, nil)
1✔
857
        }
1✔
858

859
        // If the error is a ClearTextError, we have received a valid wire
860
        // failure message, either from our own outgoing link or from a node
861
        // down the route. If the error is not related to the propagation of
862
        // our payment, we can stop trying because an internal error has
863
        // occurred.
864
        var rtErr htlcswitch.ClearTextError
24✔
865
        ok := errors.As(sendErr, &rtErr)
24✔
866
        if !ok {
27✔
867
                return p.failPaymentAndAttempt(
3✔
868
                        attemptID, &internalErrorReason, sendErr,
3✔
869
                )
3✔
870
        }
3✔
871

872
        // failureSourceIdx is the index of the node that the failure occurred
873
        // at. If the ClearTextError received is not a ForwardingError the
874
        // payment error occurred at our node, so we leave this value as 0
875
        // to indicate that the failure occurred locally. If the error is a
876
        // ForwardingError, it did not originate at our node, so we set
877
        // failureSourceIdx to the index of the node where the failure occurred.
878
        failureSourceIdx := 0
21✔
879
        var source *htlcswitch.ForwardingError
21✔
880
        ok = errors.As(rtErr, &source)
21✔
881
        if ok {
42✔
882
                failureSourceIdx = source.FailureSourceIdx
21✔
883
        }
21✔
884

885
        // Extract the wire failure and apply channel update if it contains one.
886
        // If we received an unknown failure message from a node along the
887
        // route, the failure message will be nil.
888
        failureMessage := rtErr.WireMessage()
21✔
889
        err := p.handleFailureMessage(
21✔
890
                &attempt.Route, failureSourceIdx, failureMessage,
21✔
891
        )
21✔
892
        if err != nil {
21✔
893
                return p.failPaymentAndAttempt(
×
894
                        attemptID, &internalErrorReason, sendErr,
×
895
                )
×
896
        }
×
897

898
        log.Tracef("Node=%v reported failure when sending htlc",
21✔
899
                failureSourceIdx)
21✔
900

21✔
901
        return reportAndFail(&failureSourceIdx, failureMessage)
21✔
902
}
903

904
// handleFailureMessage tries to apply a channel update present in the failure
905
// message if any.
906
func (p *paymentLifecycle) handleFailureMessage(rt *route.Route,
907
        errorSourceIdx int, failure lnwire.FailureMessage) error {
21✔
908

21✔
909
        if failure == nil {
22✔
910
                return nil
1✔
911
        }
1✔
912

913
        // It makes no sense to apply our own channel updates.
914
        if errorSourceIdx == 0 {
23✔
915
                log.Errorf("Channel update of ourselves received")
3✔
916

3✔
917
                return nil
3✔
918
        }
3✔
919

920
        // Extract channel update if the error contains one.
921
        update := p.router.extractChannelUpdate(failure)
20✔
922
        if update == nil {
32✔
923
                return nil
12✔
924
        }
12✔
925

926
        // Parse pubkey to allow validation of the channel update. This should
927
        // always succeed, otherwise there is something wrong in our
928
        // implementation. Therefore, return an error.
929
        errVertex := rt.Hops[errorSourceIdx-1].PubKeyBytes
11✔
930
        errSource, err := btcec.ParsePubKey(errVertex[:])
11✔
931
        if err != nil {
11✔
932
                log.Errorf("Cannot parse pubkey: idx=%v, pubkey=%v",
×
933
                        errorSourceIdx, errVertex)
×
934

×
935
                return err
×
936
        }
×
937

938
        var (
11✔
939
                isAdditionalEdge bool
11✔
940
                policy           *models.CachedEdgePolicy
11✔
941
        )
11✔
942

11✔
943
        // Before we apply the channel update, we need to decide whether the
11✔
944
        // update is for additional (ephemeral) edge or normal edge stored in
11✔
945
        // db.
11✔
946
        //
11✔
947
        // Note: the p.paySession might be nil here if it's called inside
11✔
948
        // SendToRoute where there's no payment lifecycle.
11✔
949
        if p.paySession != nil {
19✔
950
                policy = p.paySession.GetAdditionalEdgePolicy(
8✔
951
                        errSource, update.ShortChannelID.ToUint64(),
8✔
952
                )
8✔
953
                if policy != nil {
13✔
954
                        isAdditionalEdge = true
5✔
955
                }
5✔
956
        }
957

958
        // Apply channel update to additional edge policy.
959
        if isAdditionalEdge {
16✔
960
                if !p.paySession.UpdateAdditionalEdge(
5✔
961
                        update, errSource, policy) {
5✔
962

×
963
                        log.Debugf("Invalid channel update received: node=%v",
×
964
                                errVertex)
×
965
                }
×
966
                return nil
5✔
967
        }
968

969
        // Apply channel update to the channel edge policy in our db.
970
        if !p.router.cfg.ApplyChannelUpdate(update) {
14✔
971
                log.Debugf("Invalid channel update received: node=%v",
5✔
972
                        errVertex)
5✔
973
        }
5✔
974
        return nil
9✔
975
}
976

977
// failAttempt calls control tower to fail the current payment attempt.
978
func (p *paymentLifecycle) failAttempt(attemptID uint64,
979
        sendError error) (*attemptResult, error) {
26✔
980

26✔
981
        log.Warnf("Attempt %v for payment %v failed: %v", attemptID,
26✔
982
                p.identifier, sendError)
26✔
983

26✔
984
        failInfo := marshallError(
26✔
985
                sendError,
26✔
986
                p.router.cfg.Clock.Now(),
26✔
987
        )
26✔
988

26✔
989
        // Now that we are failing this payment attempt, cancel the shard with
26✔
990
        // the ShardTracker such that it can derive the correct hash for the
26✔
991
        // next attempt.
26✔
992
        if err := p.shardTracker.CancelShard(attemptID); err != nil {
26✔
993
                return nil, err
×
994
        }
×
995

996
        attempt, err := p.router.cfg.Control.FailAttempt(
26✔
997
                p.identifier, attemptID, failInfo,
26✔
998
        )
26✔
999
        if err != nil {
30✔
1000
                return nil, err
4✔
1001
        }
4✔
1002

1003
        return &attemptResult{
22✔
1004
                attempt: attempt,
22✔
1005
                err:     sendError,
22✔
1006
        }, nil
22✔
1007
}
1008

1009
// marshallError marshall an error as received from the switch to a structure
1010
// that is suitable for database storage.
1011
func marshallError(sendError error, time time.Time) *channeldb.HTLCFailInfo {
26✔
1012
        response := &channeldb.HTLCFailInfo{
26✔
1013
                FailTime: time,
26✔
1014
        }
26✔
1015

26✔
1016
        switch {
26✔
1017
        case errors.Is(sendError, htlcswitch.ErrPaymentIDNotFound):
1✔
1018
                response.Reason = channeldb.HTLCFailInternal
1✔
1019
                return response
1✔
1020

1021
        case errors.Is(sendError, htlcswitch.ErrUnreadableFailureMessage):
1✔
1022
                response.Reason = channeldb.HTLCFailUnreadable
1✔
1023
                return response
1✔
1024
        }
1025

1026
        var rtErr htlcswitch.ClearTextError
24✔
1027
        ok := errors.As(sendError, &rtErr)
24✔
1028
        if !ok {
27✔
1029
                response.Reason = channeldb.HTLCFailInternal
3✔
1030
                return response
3✔
1031
        }
3✔
1032

1033
        message := rtErr.WireMessage()
21✔
1034
        if message != nil {
41✔
1035
                response.Reason = channeldb.HTLCFailMessage
20✔
1036
                response.Message = message
20✔
1037
        } else {
21✔
1038
                response.Reason = channeldb.HTLCFailUnknown
1✔
1039
        }
1✔
1040

1041
        // If the ClearTextError received is a ForwardingError, the error
1042
        // originated from a node along the route, not locally on our outgoing
1043
        // link. We set failureSourceIdx to the index of the node where the
1044
        // failure occurred. If the error is not a ForwardingError, the failure
1045
        // occurred at our node, so we leave the index as 0 to indicate that
1046
        // we failed locally.
1047
        var fErr *htlcswitch.ForwardingError
21✔
1048
        ok = errors.As(rtErr, &fErr)
21✔
1049
        if ok {
42✔
1050
                response.FailureSourceIndex = uint32(fErr.FailureSourceIdx)
21✔
1051
        }
21✔
1052

1053
        return response
21✔
1054
}
1055

1056
// reloadInflightAttempts is called when the payment lifecycle is resumed after
1057
// a restart. It reloads all inflight attempts from the control tower and
1058
// collects the results of the attempts that have been sent before.
1059
func (p *paymentLifecycle) reloadInflightAttempts() (DBMPPayment, error) {
25✔
1060
        payment, err := p.router.cfg.Control.FetchPayment(p.identifier)
25✔
1061
        if err != nil {
26✔
1062
                return nil, err
1✔
1063
        }
1✔
1064

1065
        for _, a := range payment.InFlightHTLCs() {
27✔
1066
                a := a
3✔
1067

3✔
1068
                log.Infof("Resuming HTLC attempt %v for payment %v",
3✔
1069
                        a.AttemptID, p.identifier)
3✔
1070

3✔
1071
                p.resultCollector(&a)
3✔
1072
        }
3✔
1073

1074
        return payment, nil
24✔
1075
}
1076

1077
// reloadPayment returns the latest payment found in the db (control tower).
1078
func (p *paymentLifecycle) reloadPayment() (DBMPPayment,
1079
        *channeldb.MPPaymentState, error) {
72✔
1080

72✔
1081
        // Read the db to get the latest state of the payment.
72✔
1082
        payment, err := p.router.cfg.Control.FetchPayment(p.identifier)
72✔
1083
        if err != nil {
72✔
NEW
1084
                return nil, nil, err
×
NEW
1085
        }
×
1086

1087
        ps := payment.GetState()
72✔
1088
        remainingFees := p.calcFeeBudget(ps.FeesPaid)
72✔
1089

72✔
1090
        log.Debugf("Payment %v: status=%v, active_shards=%v, rem_value=%v, "+
72✔
1091
                "fee_limit=%v", p.identifier, payment.GetStatus(),
72✔
1092
                ps.NumAttemptsInFlight, ps.RemainingAmt, remainingFees)
72✔
1093

72✔
1094
        return payment, ps, nil
72✔
1095
}
1096

1097
// handleAttemptResult processes the result of an HTLC attempt returned from
1098
// the htlcswitch.
1099
func (p *paymentLifecycle) handleAttemptResult(attempt *channeldb.HTLCAttempt,
1100
        result *htlcswitch.PaymentResult) (*attemptResult, error) {
37✔
1101

37✔
1102
        // If the result has an error, we need to further process it by failing
37✔
1103
        // the attempt and maybe fail the payment.
37✔
1104
        if result.Error != nil {
58✔
1105
                return p.handleSwitchErr(attempt, result.Error)
21✔
1106
        }
21✔
1107

1108
        // We got an attempt settled result back from the switch.
1109
        log.Debugf("Payment(%v): attempt(%v) succeeded", p.identifier,
19✔
1110
                attempt.AttemptID)
19✔
1111

19✔
1112
        // Report success to mission control.
19✔
1113
        err := p.router.cfg.MissionControl.ReportPaymentSuccess(
19✔
1114
                attempt.AttemptID, &attempt.Route,
19✔
1115
        )
19✔
1116
        if err != nil {
19✔
NEW
1117
                log.Errorf("Error reporting payment success to mc: %v", err)
×
NEW
1118
        }
×
1119

1120
        // In case of success we atomically store settle result to the DB and
1121
        // move the shard to the settled state.
1122
        htlcAttempt, err := p.router.cfg.Control.SettleAttempt(
19✔
1123
                p.identifier, attempt.AttemptID,
19✔
1124
                &channeldb.HTLCSettleInfo{
19✔
1125
                        Preimage:   result.Preimage,
19✔
1126
                        SettleTime: p.router.cfg.Clock.Now(),
19✔
1127
                },
19✔
1128
        )
19✔
1129
        if err != nil {
21✔
1130
                log.Errorf("Error settling attempt %v for payment %v with "+
2✔
1131
                        "preimage %v: %v", attempt.AttemptID, p.identifier,
2✔
1132
                        result.Preimage, err)
2✔
1133

2✔
1134
                // We won't mark the attempt as failed since we already have
2✔
1135
                // the preimage.
2✔
1136
                return nil, err
2✔
1137
        }
2✔
1138

1139
        return &attemptResult{
17✔
1140
                attempt: htlcAttempt,
17✔
1141
        }, nil
17✔
1142
}
1143

1144
// collectAndHandleResult waits for the result for the given attempt to be
1145
// available from the Switch, then records the attempt outcome with the control
1146
// tower. An attemptResult is returned, indicating the final outcome of this
1147
// HTLC attempt.
1148
func (p *paymentLifecycle) collectAndHandleResult(
1149
        attempt *channeldb.HTLCAttempt) (*attemptResult, error) {
15✔
1150

15✔
1151
        result, err := p.collectResult(attempt)
15✔
1152
        if err != nil {
18✔
1153
                return nil, err
3✔
1154
        }
3✔
1155

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