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

lightningnetwork / lnd / 13536249039

26 Feb 2025 03:42AM UTC coverage: 57.462% (-1.4%) from 58.835%
13536249039

Pull #8453

github

Roasbeef
peer: update chooseDeliveryScript to gen script if needed

In this commit, we update `chooseDeliveryScript` to generate a new
script if needed. This allows us to fold in a few other lines that
always followed this function into this expanded function.

The tests have been updated accordingly.
Pull Request #8453: [4/4] - multi: integrate new rbf coop close FSM into the existing peer flow

275 of 1318 new or added lines in 22 files covered. (20.86%)

19521 existing lines in 257 files now uncovered.

103858 of 180741 relevant lines covered (57.46%)

24750.23 hits per line

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

89.04
/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 {
50✔
69

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

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

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

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

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

104
        return budget
103✔
105
}
106

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

186
        return stepSkip, nil
22✔
187
}
188

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

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

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

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

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

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

224
        // We'll continue until either our payment succeeds, or we encounter a
225
        // critical error during path finding.
226
lifecycle:
21✔
227
        for {
90✔
228
                // We update the payment state on every iteration.
69✔
229
                currentPayment, ps, err := p.reloadPayment()
69✔
230
                if err != nil {
69✔
231
                        return exitWithErr(err)
×
232
                }
×
233

234
                // Reassign status so it can be read in `exitWithErr`.
235
                status = currentPayment.GetStatus()
69✔
236

69✔
237
                // Reassign payment such that when the lifecycle exits, the
69✔
238
                // latest payment can be read when we access its terminal info.
69✔
239
                payment = currentPayment
69✔
240

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

257
                // Now decide the next step of the current lifecycle.
258
                step, err := p.decideNextStep(payment)
68✔
259
                if err != nil {
69✔
260
                        return exitWithErr(err)
1✔
261
                }
1✔
262

263
                switch step {
67✔
264
                // Exit the for loop and return below.
265
                case stepExit:
16✔
266
                        break lifecycle
16✔
267

268
                // Continue the for loop and skip the rest.
269
                case stepSkip:
21✔
270
                        continue lifecycle
21✔
271

272
                // Continue the for loop and proceed the rest.
273
                case stepProceed:
30✔
274

275
                // Unknown step received, exit with an error.
276
                default:
×
277
                        err = fmt.Errorf("unknown step: %v", step)
×
278
                        return exitWithErr(err)
×
279
                }
280

281
                // Now request a route to be used to create our HTLC attempt.
282
                rt, err := p.requestRoute(ps)
30✔
283
                if err != nil {
31✔
284
                        return exitWithErr(err)
1✔
285
                }
1✔
286

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

2✔
295
                        continue lifecycle
2✔
296
                }
297

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

27✔
300
                // We found a route to try, create a new HTLC attempt to try.
27✔
301
                attempt, err := p.registerAttempt(rt, ps.RemainingAmt)
27✔
302
                if err != nil {
28✔
303
                        return exitWithErr(err)
1✔
304
                }
1✔
305

306
                // Once the attempt is created, send it to the htlcswitch.
307
                result, err := p.sendAttempt(attempt)
26✔
308
                if err != nil {
27✔
309
                        return exitWithErr(err)
1✔
310
                }
1✔
311

312
                // Now that the shard was successfully sent, launch a go
313
                // routine that will handle its result when its back.
314
                if result.err == nil {
49✔
315
                        p.resultCollector(attempt)
24✔
316
                }
24✔
317
        }
318

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

330
        htlc, failure := payment.TerminalInfo()
16✔
331
        if htlc != nil {
28✔
332
                return htlc.Settle.Preimage, &htlc.Route, nil
12✔
333
        }
12✔
334

335
        // Otherwise return the payment failure reason.
336
        return [32]byte{}, nil, *failure
4✔
337
}
338

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

360
                // By marking the payment failed, depending on whether it has
361
                // inflight HTLCs or not, its status will now either be
362
                // `StatusInflight` or `StatusFailed`. In either case, no more
363
                // HTLCs will be attempted.
364
                err := p.router.cfg.Control.FailPayment(p.identifier, reason)
4✔
365
                if err != nil {
5✔
366
                        return fmt.Errorf("FailPayment got %w", err)
1✔
367
                }
1✔
368

369
        case <-p.router.quit:
2✔
370
                return fmt.Errorf("check payment timeout got: %w",
2✔
371
                        ErrRouterShuttingDown)
2✔
372

373
        // Fall through if we haven't hit our time limit.
374
        default:
66✔
375
        }
376

377
        return nil
69✔
378
}
379

380
// requestRoute is responsible for finding a route to be used to create an HTLC
381
// attempt.
382
func (p *paymentLifecycle) requestRoute(
383
        ps *channeldb.MPPaymentState) (*route.Route, error) {
34✔
384

34✔
385
        remainingFees := p.calcFeeBudget(ps.FeesPaid)
34✔
386

34✔
387
        // Query our payment session to construct a route.
34✔
388
        rt, err := p.paySession.RequestRoute(
34✔
389
                ps.RemainingAmt, remainingFees,
34✔
390
                uint32(ps.NumAttemptsInFlight), uint32(p.currentHeight),
34✔
391
                p.firstHopCustomRecords,
34✔
392
        )
34✔
393

34✔
394
        // Exit early if there's no error.
34✔
395
        if err == nil {
62✔
396
                // Allow the traffic shaper to add custom records to the
28✔
397
                // outgoing HTLC and also adjust the amount if needed.
28✔
398
                err = p.amendFirstHopData(rt)
28✔
399
                if err != nil {
28✔
400
                        return nil, err
×
401
                }
×
402

403
                return rt, nil
28✔
404
        }
405

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

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

418
        // It's the `paymentSession`'s responsibility to find a route for us
419
        // with the best effort. When it cannot find a path, we need to treat it
420
        // as a terminal condition and fail the payment no matter it has
421
        // inflight HTLCs or not.
422
        failureCode := routeErr.FailureReason()
4✔
423
        log.Warnf("Marking payment %v permanently failed with no route: %v",
4✔
424
                p.identifier, failureCode)
4✔
425

4✔
426
        err = p.router.cfg.Control.FailPayment(p.identifier, failureCode)
4✔
427
        if err != nil {
5✔
428
                return nil, fmt.Errorf("FailPayment got: %w", err)
1✔
429
        }
1✔
430

431
        // NOTE: we decide to not return the non-critical noRouteError here to
432
        // avoid terminating the payment lifecycle as there might be other
433
        // inflight HTLCs which we must wait for their results.
434
        return nil, nil
3✔
435
}
436

437
// stop signals any active shard goroutine to exit.
438
func (p *paymentLifecycle) stop() {
23✔
439
        close(p.quit)
23✔
440
}
23✔
441

442
// attemptResult holds the HTLC attempt and a possible error returned from
443
// sending it.
444
type attemptResult struct {
445
        // err is non-nil if a non-critical error was encountered when trying
446
        // to send the attempt, and we successfully updated the control tower
447
        // to reflect this error. This can be errors like not enough local
448
        // balance for the given route etc.
449
        err error
450

451
        // attempt is the attempt structure as recorded in the database.
452
        attempt *channeldb.HTLCAttempt
453
}
454

455
// collectResultAsync launches a goroutine that will wait for the result of the
456
// given HTLC attempt to be available then save its result in a map. Once
457
// received, it will send the result returned from the switch to channel
458
// `resultCollected`.
459
func (p *paymentLifecycle) collectResultAsync(attempt *channeldb.HTLCAttempt) {
22✔
460
        log.Debugf("Collecting result for attempt %v in payment %v",
22✔
461
                attempt.AttemptID, p.identifier)
22✔
462

22✔
463
        go func() {
44✔
464
                result, err := p.collectResult(attempt)
22✔
465
                if err != nil {
22✔
UNCOV
466
                        log.Errorf("Error collecting result for attempt %v in "+
×
UNCOV
467
                                "payment %v: %v", attempt.AttemptID,
×
UNCOV
468
                                p.identifier, err)
×
UNCOV
469

×
UNCOV
470
                        return
×
UNCOV
471
                }
×
472

473
                log.Debugf("Result collected for attempt %v in payment %v",
22✔
474
                        attempt.AttemptID, p.identifier)
22✔
475

22✔
476
                // Create a switch result and send it to the resultCollected
22✔
477
                // chan, which gets processed when the lifecycle is waiting for
22✔
478
                // a result to be received in decideNextStep.
22✔
479
                r := &switchResult{
22✔
480
                        attempt: attempt,
22✔
481
                        result:  result,
22✔
482
                }
22✔
483

22✔
484
                // Signal that a result has been collected.
22✔
485
                select {
22✔
486
                // Send the result so decideNextStep can proceed.
487
                case p.resultCollected <- r:
22✔
488

489
                case <-p.quit:
×
490
                        log.Debugf("Lifecycle exiting while collecting "+
×
491
                                "result for payment %v", p.identifier)
×
492

493
                case <-p.router.quit:
×
494
                }
495
        }()
496
}
497

498
// collectResult waits for the result of the given HTLC attempt to be sent by
499
// the switch and returns it.
500
func (p *paymentLifecycle) collectResult(
501
        attempt *channeldb.HTLCAttempt) (*htlcswitch.PaymentResult, error) {
34✔
502

34✔
503
        log.Tracef("Collecting result for attempt %v", spew.Sdump(attempt))
34✔
504

34✔
505
        result := &htlcswitch.PaymentResult{}
34✔
506

34✔
507
        // Regenerate the circuit for this attempt.
34✔
508
        circuit, err := attempt.Circuit()
34✔
509

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

525
        // Using the created circuit, initialize the error decrypter, so we can
526
        // parse+decode any failures incurred by this payment within the
527
        // switch.
528
        errorDecryptor := &htlcswitch.SphinxErrorDecrypter{
34✔
529
                OnionErrorDecrypter: sphinx.NewOnionErrorDecrypter(circuit),
34✔
530
        }
34✔
531

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

1✔
548
                result.Error = err
1✔
549

1✔
550
                return result, nil
1✔
551
        }
1✔
552

553
        // The switch knows about this payment, we'll wait for a result to be
554
        // available.
555
        select {
33✔
556
        case r, ok := <-resultChan:
31✔
557
                if !ok {
32✔
558
                        return nil, htlcswitch.ErrSwitchExiting
1✔
559
                }
1✔
560

561
                result = r
30✔
562

563
        case <-p.quit:
1✔
564
                return nil, ErrPaymentLifecycleExiting
1✔
565

566
        case <-p.router.quit:
1✔
567
                return nil, ErrRouterShuttingDown
1✔
568
        }
569

570
        return result, nil
30✔
571
}
572

573
// registerAttempt is responsible for creating and saving an HTLC attempt in db
574
// by using the route info provided. The `remainingAmt` is used to decide
575
// whether this is the last attempt.
576
func (p *paymentLifecycle) registerAttempt(rt *route.Route,
577
        remainingAmt lnwire.MilliSatoshi) (*channeldb.HTLCAttempt, error) {
36✔
578

36✔
579
        // If this route will consume the last remaining amount to send
36✔
580
        // to the receiver, this will be our last shard (for now).
36✔
581
        isLastAttempt := rt.ReceiverAmt() == remainingAmt
36✔
582

36✔
583
        // Using the route received from the payment session, create a new
36✔
584
        // shard to send.
36✔
585
        attempt, err := p.createNewPaymentAttempt(rt, isLastAttempt)
36✔
586
        if err != nil {
38✔
587
                return nil, err
2✔
588
        }
2✔
589

590
        // Before sending this HTLC to the switch, we checkpoint the fresh
591
        // paymentID and route to the DB. This lets us know on startup the ID
592
        // of the payment that we attempted to send, such that we can query the
593
        // Switch for its whereabouts. The route is needed to handle the result
594
        // when it eventually comes back.
595
        err = p.router.cfg.Control.RegisterAttempt(
34✔
596
                p.identifier, &attempt.HTLCAttemptInfo,
34✔
597
        )
34✔
598

34✔
599
        return attempt, err
34✔
600
}
601

602
// createNewPaymentAttempt creates a new payment attempt from the given route.
603
func (p *paymentLifecycle) createNewPaymentAttempt(rt *route.Route,
604
        lastShard bool) (*channeldb.HTLCAttempt, error) {
36✔
605

36✔
606
        // Generate a new key to be used for this attempt.
36✔
607
        sessionKey, err := generateNewSessionKey()
36✔
608
        if err != nil {
36✔
609
                return nil, err
×
610
        }
×
611

612
        // We generate a new, unique payment ID that we will use for
613
        // this HTLC.
614
        attemptID, err := p.router.cfg.NextPaymentID()
36✔
615
        if err != nil {
36✔
616
                return nil, err
×
617
        }
×
618

619
        // Request a new shard from the ShardTracker. If this is an AMP
620
        // payment, and this is the last shard, the outstanding shards together
621
        // with this one will be enough for the receiver to derive all HTLC
622
        // preimages. If this a non-AMP payment, the ShardTracker will return a
623
        // simple shard with the payment's static payment hash.
624
        shard, err := p.shardTracker.NewShard(attemptID, lastShard)
36✔
625
        if err != nil {
37✔
626
                return nil, err
1✔
627
        }
1✔
628

629
        // If this shard carries MPP or AMP options, add them to the last hop
630
        // on the route.
631
        hop := rt.Hops[len(rt.Hops)-1]
35✔
632
        if shard.MPP() != nil {
39✔
633
                hop.MPP = shard.MPP()
4✔
634
        }
4✔
635

636
        if shard.AMP() != nil {
35✔
UNCOV
637
                hop.AMP = shard.AMP()
×
UNCOV
638
        }
×
639

640
        hash := shard.Hash()
35✔
641

35✔
642
        // We now have all the information needed to populate the current
35✔
643
        // attempt information.
35✔
644
        return channeldb.NewHtlcAttempt(
35✔
645
                attemptID, sessionKey, *rt, p.router.cfg.Clock.Now(), &hash,
35✔
646
        )
35✔
647
}
648

649
// sendAttempt attempts to send the current attempt to the switch to complete
650
// the payment. If this attempt fails, then we'll continue on to the next
651
// available route.
652
func (p *paymentLifecycle) sendAttempt(
653
        attempt *channeldb.HTLCAttempt) (*attemptResult, error) {
34✔
654

34✔
655
        log.Debugf("Sending HTLC attempt(id=%v, total_amt=%v, first_hop_amt=%d"+
34✔
656
                ") for payment %v", attempt.AttemptID,
34✔
657
                attempt.Route.TotalAmount, attempt.Route.FirstHopAmount.Val,
34✔
658
                p.identifier)
34✔
659

34✔
660
        rt := attempt.Route
34✔
661

34✔
662
        // Construct the first hop.
34✔
663
        firstHop := lnwire.NewShortChanIDFromInt(rt.Hops[0].ChannelID)
34✔
664

34✔
665
        // Craft an HTLC packet to send to the htlcswitch. The metadata within
34✔
666
        // this packet will be used to route the payment through the network,
34✔
667
        // starting with the first-hop.
34✔
668
        htlcAdd := &lnwire.UpdateAddHTLC{
34✔
669
                Amount:        rt.FirstHopAmount.Val.Int(),
34✔
670
                Expiry:        rt.TotalTimeLock,
34✔
671
                PaymentHash:   *attempt.Hash,
34✔
672
                CustomRecords: rt.FirstHopWireCustomRecords,
34✔
673
        }
34✔
674

34✔
675
        // Generate the raw encoded sphinx packet to be included along
34✔
676
        // with the htlcAdd message that we send directly to the
34✔
677
        // switch.
34✔
678
        onionBlob, err := attempt.OnionBlob()
34✔
679
        if err != nil {
34✔
680
                log.Errorf("Failed to create onion blob: attempt=%d in "+
×
681
                        "payment=%v, err:%v", attempt.AttemptID,
×
682
                        p.identifier, err)
×
683

×
684
                return p.failAttempt(attempt.AttemptID, err)
×
685
        }
×
686

687
        htlcAdd.OnionBlob = onionBlob
34✔
688

34✔
689
        // Send it to the Switch. When this method returns we assume
34✔
690
        // the Switch successfully has persisted the payment attempt,
34✔
691
        // such that we can resume waiting for the result after a
34✔
692
        // restart.
34✔
693
        err = p.router.cfg.Payer.SendHTLC(firstHop, attempt.AttemptID, htlcAdd)
34✔
694
        if err != nil {
39✔
695
                log.Errorf("Failed sending attempt %d for payment %v to "+
5✔
696
                        "switch: %v", attempt.AttemptID, p.identifier, err)
5✔
697

5✔
698
                return p.handleSwitchErr(attempt, err)
5✔
699
        }
5✔
700

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

29✔
704
        return &attemptResult{
29✔
705
                attempt: attempt,
29✔
706
        }, nil
29✔
707
}
708

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

37✔
720
        // By default, we set the first hop custom records to the initial
37✔
721
        // value requested by the RPC. The traffic shaper may overwrite this
37✔
722
        // value.
37✔
723
        rt.FirstHopWireCustomRecords = p.firstHopCustomRecords
37✔
724

37✔
725
        // extraDataRequest is a helper struct to pass the custom records and
37✔
726
        // amount back from the traffic shaper.
37✔
727
        type extraDataRequest struct {
37✔
728
                customRecords fn.Option[lnwire.CustomRecords]
37✔
729

37✔
730
                amount fn.Option[lnwire.MilliSatoshi]
37✔
731
        }
37✔
732

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

746
                        // Make sure we only received valid records.
747
                        if err := newRecords.Validate(); err != nil {
37✔
748
                                return fn.Err[extraDataRequest](err)
×
749
                        }
×
750

751
                        log.Debugf("Aux traffic shaper returned custom "+
37✔
752
                                "records %v and amount %d msat for HTLC",
37✔
753
                                spew.Sdump(newRecords), newAmt)
37✔
754

37✔
755
                        return fn.Ok(extraDataRequest{
37✔
756
                                customRecords: fn.Some(newRecords),
37✔
757
                                amount:        fn.Some(newAmt),
37✔
758
                        })
37✔
759
                },
760
        ).Unpack()
761
        if err != nil {
37✔
762
                return fmt.Errorf("traffic shaper failed to produce extra "+
×
763
                        "data: %w", err)
×
764
        }
×
765

766
        // Apply the side effects to the UpdateAddHTLC message.
767
        result.customRecords.WhenSome(func(records lnwire.CustomRecords) {
74✔
768
                rt.FirstHopWireCustomRecords = records
37✔
769
        })
37✔
770
        result.amount.WhenSome(func(amount lnwire.MilliSatoshi) {
74✔
771
                rt.FirstHopAmount = tlv.NewRecordT[tlv.TlvType0](
37✔
772
                        tlv.NewBigSizeT(amount),
37✔
773
                )
37✔
774
        })
37✔
775

776
        return nil
37✔
777
}
778

779
// failAttemptAndPayment fails both the payment and its attempt via the
780
// router's control tower, which marks the payment as failed in db.
781
func (p *paymentLifecycle) failPaymentAndAttempt(
782
        attemptID uint64, reason *channeldb.FailureReason,
783
        sendErr error) (*attemptResult, error) {
5✔
784

5✔
785
        log.Errorf("Payment %v failed: final_outcome=%v, raw_err=%v",
5✔
786
                p.identifier, *reason, sendErr)
5✔
787

5✔
788
        // Fail the payment via control tower.
5✔
789
        //
5✔
790
        // NOTE: we must fail the payment first before failing the attempt.
5✔
791
        // Otherwise, once the attempt is marked as failed, another goroutine
5✔
792
        // might make another attempt while we are failing the payment.
5✔
793
        err := p.router.cfg.Control.FailPayment(p.identifier, *reason)
5✔
794
        if err != nil {
5✔
795
                log.Errorf("Unable to fail payment: %v", err)
×
796
                return nil, err
×
797
        }
×
798

799
        // Fail the attempt.
800
        return p.failAttempt(attemptID, sendErr)
5✔
801
}
802

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

23✔
814
        internalErrorReason := channeldb.FailureReasonError
23✔
815
        attemptID := attempt.AttemptID
23✔
816

23✔
817
        // reportAndFail is a helper closure that reports the failure to the
23✔
818
        // mission control, which helps us to decide whether we want to retry
23✔
819
        // the payment or not. If a non nil reason is returned from mission
23✔
820
        // control, it will further fail the payment via control tower.
23✔
821
        reportAndFail := func(srcIdx *int,
23✔
822
                msg lnwire.FailureMessage) (*attemptResult, error) {
42✔
823

19✔
824
                // Report outcome to mission control.
19✔
825
                reason, err := p.router.cfg.MissionControl.ReportPaymentFail(
19✔
826
                        attemptID, &attempt.Route, srcIdx, msg,
19✔
827
                )
19✔
828
                if err != nil {
19✔
829
                        log.Errorf("Error reporting payment result to mc: %v",
×
830
                                err)
×
831

×
832
                        reason = &internalErrorReason
×
833
                }
×
834

835
                // Fail the attempt only if there's no reason.
836
                if reason == nil {
36✔
837
                        // Fail the attempt.
17✔
838
                        return p.failAttempt(attemptID, sendErr)
17✔
839
                }
17✔
840

841
                // Otherwise fail both the payment and the attempt.
842
                return p.failPaymentAndAttempt(attemptID, reason, sendErr)
2✔
843
        }
844

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

1✔
853
                return p.failAttempt(attemptID, sendErr)
1✔
854
        }
1✔
855

856
        if errors.Is(sendErr, htlcswitch.ErrUnreadableFailureMessage) {
23✔
857
                log.Warn("Unreadable failure when sending htlc: id=%v, hash=%v",
1✔
858
                        attempt.AttemptID, attempt.Hash)
1✔
859

1✔
860
                // Since this error message cannot be decrypted, we will send a
1✔
861
                // nil error message to our mission controller and fail the
1✔
862
                // payment.
1✔
863
                return reportAndFail(nil, nil)
1✔
864
        }
1✔
865

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

879
        // failureSourceIdx is the index of the node that the failure occurred
880
        // at. If the ClearTextError received is not a ForwardingError the
881
        // payment error occurred at our node, so we leave this value as 0
882
        // to indicate that the failure occurred locally. If the error is a
883
        // ForwardingError, it did not originate at our node, so we set
884
        // failureSourceIdx to the index of the node where the failure occurred.
885
        failureSourceIdx := 0
18✔
886
        var source *htlcswitch.ForwardingError
18✔
887
        ok = errors.As(rtErr, &source)
18✔
888
        if ok {
36✔
889
                failureSourceIdx = source.FailureSourceIdx
18✔
890
        }
18✔
891

892
        // Extract the wire failure and apply channel update if it contains one.
893
        // If we received an unknown failure message from a node along the
894
        // route, the failure message will be nil.
895
        failureMessage := rtErr.WireMessage()
18✔
896
        err := p.handleFailureMessage(
18✔
897
                &attempt.Route, failureSourceIdx, failureMessage,
18✔
898
        )
18✔
899
        if err != nil {
18✔
900
                return p.failPaymentAndAttempt(
×
901
                        attemptID, &internalErrorReason, sendErr,
×
902
                )
×
903
        }
×
904

905
        log.Tracef("Node=%v reported failure when sending htlc",
18✔
906
                failureSourceIdx)
18✔
907

18✔
908
        return reportAndFail(&failureSourceIdx, failureMessage)
18✔
909
}
910

911
// handleFailureMessage tries to apply a channel update present in the failure
912
// message if any.
913
func (p *paymentLifecycle) handleFailureMessage(rt *route.Route,
914
        errorSourceIdx int, failure lnwire.FailureMessage) error {
18✔
915

18✔
916
        if failure == nil {
19✔
917
                return nil
1✔
918
        }
1✔
919

920
        // It makes no sense to apply our own channel updates.
921
        if errorSourceIdx == 0 {
17✔
UNCOV
922
                log.Errorf("Channel update of ourselves received")
×
UNCOV
923

×
UNCOV
924
                return nil
×
UNCOV
925
        }
×
926

927
        // Extract channel update if the error contains one.
928
        update := p.router.extractChannelUpdate(failure)
17✔
929
        if update == nil {
26✔
930
                return nil
9✔
931
        }
9✔
932

933
        // Parse pubkey to allow validation of the channel update. This should
934
        // always succeed, otherwise there is something wrong in our
935
        // implementation. Therefore, return an error.
936
        errVertex := rt.Hops[errorSourceIdx-1].PubKeyBytes
8✔
937
        errSource, err := btcec.ParsePubKey(errVertex[:])
8✔
938
        if err != nil {
8✔
939
                log.Errorf("Cannot parse pubkey: idx=%v, pubkey=%v",
×
940
                        errorSourceIdx, errVertex)
×
941

×
942
                return err
×
943
        }
×
944

945
        var (
8✔
946
                isAdditionalEdge bool
8✔
947
                policy           *models.CachedEdgePolicy
8✔
948
        )
8✔
949

8✔
950
        // Before we apply the channel update, we need to decide whether the
8✔
951
        // update is for additional (ephemeral) edge or normal edge stored in
8✔
952
        // db.
8✔
953
        //
8✔
954
        // Note: the p.paySession might be nil here if it's called inside
8✔
955
        // SendToRoute where there's no payment lifecycle.
8✔
956
        if p.paySession != nil {
13✔
957
                policy = p.paySession.GetAdditionalEdgePolicy(
5✔
958
                        errSource, update.ShortChannelID.ToUint64(),
5✔
959
                )
5✔
960
                if policy != nil {
7✔
961
                        isAdditionalEdge = true
2✔
962
                }
2✔
963
        }
964

965
        // Apply channel update to additional edge policy.
966
        if isAdditionalEdge {
10✔
967
                if !p.paySession.UpdateAdditionalEdge(
2✔
968
                        update, errSource, policy) {
2✔
969

×
970
                        log.Debugf("Invalid channel update received: node=%v",
×
971
                                errVertex)
×
972
                }
×
973
                return nil
2✔
974
        }
975

976
        // Apply channel update to the channel edge policy in our db.
977
        if !p.router.cfg.ApplyChannelUpdate(update) {
8✔
978
                log.Debugf("Invalid channel update received: node=%v",
2✔
979
                        errVertex)
2✔
980
        }
2✔
981
        return nil
6✔
982
}
983

984
// failAttempt calls control tower to fail the current payment attempt.
985
func (p *paymentLifecycle) failAttempt(attemptID uint64,
986
        sendError error) (*attemptResult, error) {
23✔
987

23✔
988
        log.Warnf("Attempt %v for payment %v failed: %v", attemptID,
23✔
989
                p.identifier, sendError)
23✔
990

23✔
991
        failInfo := marshallError(
23✔
992
                sendError,
23✔
993
                p.router.cfg.Clock.Now(),
23✔
994
        )
23✔
995

23✔
996
        // Now that we are failing this payment attempt, cancel the shard with
23✔
997
        // the ShardTracker such that it can derive the correct hash for the
23✔
998
        // next attempt.
23✔
999
        if err := p.shardTracker.CancelShard(attemptID); err != nil {
23✔
1000
                return nil, err
×
1001
        }
×
1002

1003
        attempt, err := p.router.cfg.Control.FailAttempt(
23✔
1004
                p.identifier, attemptID, failInfo,
23✔
1005
        )
23✔
1006
        if err != nil {
27✔
1007
                return nil, err
4✔
1008
        }
4✔
1009

1010
        return &attemptResult{
19✔
1011
                attempt: attempt,
19✔
1012
                err:     sendError,
19✔
1013
        }, nil
19✔
1014
}
1015

1016
// marshallError marshall an error as received from the switch to a structure
1017
// that is suitable for database storage.
1018
func marshallError(sendError error, time time.Time) *channeldb.HTLCFailInfo {
23✔
1019
        response := &channeldb.HTLCFailInfo{
23✔
1020
                FailTime: time,
23✔
1021
        }
23✔
1022

23✔
1023
        switch {
23✔
1024
        case errors.Is(sendError, htlcswitch.ErrPaymentIDNotFound):
1✔
1025
                response.Reason = channeldb.HTLCFailInternal
1✔
1026
                return response
1✔
1027

1028
        case errors.Is(sendError, htlcswitch.ErrUnreadableFailureMessage):
1✔
1029
                response.Reason = channeldb.HTLCFailUnreadable
1✔
1030
                return response
1✔
1031
        }
1032

1033
        var rtErr htlcswitch.ClearTextError
21✔
1034
        ok := errors.As(sendError, &rtErr)
21✔
1035
        if !ok {
24✔
1036
                response.Reason = channeldb.HTLCFailInternal
3✔
1037
                return response
3✔
1038
        }
3✔
1039

1040
        message := rtErr.WireMessage()
18✔
1041
        if message != nil {
35✔
1042
                response.Reason = channeldb.HTLCFailMessage
17✔
1043
                response.Message = message
17✔
1044
        } else {
18✔
1045
                response.Reason = channeldb.HTLCFailUnknown
1✔
1046
        }
1✔
1047

1048
        // If the ClearTextError received is a ForwardingError, the error
1049
        // originated from a node along the route, not locally on our outgoing
1050
        // link. We set failureSourceIdx to the index of the node where the
1051
        // failure occurred. If the error is not a ForwardingError, the failure
1052
        // occurred at our node, so we leave the index as 0 to indicate that
1053
        // we failed locally.
1054
        var fErr *htlcswitch.ForwardingError
18✔
1055
        ok = errors.As(rtErr, &fErr)
18✔
1056
        if ok {
36✔
1057
                response.FailureSourceIndex = uint32(fErr.FailureSourceIdx)
18✔
1058
        }
18✔
1059

1060
        return response
18✔
1061
}
1062

1063
// reloadInflightAttempts is called when the payment lifecycle is resumed after
1064
// a restart. It reloads all inflight attempts from the control tower and
1065
// collects the results of the attempts that have been sent before.
1066
func (p *paymentLifecycle) reloadInflightAttempts() (DBMPPayment, error) {
22✔
1067
        payment, err := p.router.cfg.Control.FetchPayment(p.identifier)
22✔
1068
        if err != nil {
23✔
1069
                return nil, err
1✔
1070
        }
1✔
1071

1072
        for _, a := range payment.InFlightHTLCs() {
21✔
UNCOV
1073
                a := a
×
UNCOV
1074

×
UNCOV
1075
                log.Infof("Resuming HTLC attempt %v for payment %v",
×
UNCOV
1076
                        a.AttemptID, p.identifier)
×
UNCOV
1077

×
UNCOV
1078
                p.resultCollector(&a)
×
UNCOV
1079
        }
×
1080

1081
        return payment, nil
21✔
1082
}
1083

1084
// reloadPayment returns the latest payment found in the db (control tower).
1085
func (p *paymentLifecycle) reloadPayment() (DBMPPayment,
1086
        *channeldb.MPPaymentState, error) {
69✔
1087

69✔
1088
        // Read the db to get the latest state of the payment.
69✔
1089
        payment, err := p.router.cfg.Control.FetchPayment(p.identifier)
69✔
1090
        if err != nil {
69✔
1091
                return nil, nil, err
×
1092
        }
×
1093

1094
        ps := payment.GetState()
69✔
1095
        remainingFees := p.calcFeeBudget(ps.FeesPaid)
69✔
1096

69✔
1097
        log.Debugf("Payment %v: status=%v, active_shards=%v, rem_value=%v, "+
69✔
1098
                "fee_limit=%v", p.identifier, payment.GetStatus(),
69✔
1099
                ps.NumAttemptsInFlight, ps.RemainingAmt, remainingFees)
69✔
1100

69✔
1101
        return payment, ps, nil
69✔
1102
}
1103

1104
// handleAttemptResult processes the result of an HTLC attempt returned from
1105
// the htlcswitch.
1106
func (p *paymentLifecycle) handleAttemptResult(attempt *channeldb.HTLCAttempt,
1107
        result *htlcswitch.PaymentResult) (*attemptResult, error) {
34✔
1108

34✔
1109
        // If the result has an error, we need to further process it by failing
34✔
1110
        // the attempt and maybe fail the payment.
34✔
1111
        if result.Error != nil {
52✔
1112
                return p.handleSwitchErr(attempt, result.Error)
18✔
1113
        }
18✔
1114

1115
        // We got an attempt settled result back from the switch.
1116
        log.Debugf("Payment(%v): attempt(%v) succeeded", p.identifier,
16✔
1117
                attempt.AttemptID)
16✔
1118

16✔
1119
        // Report success to mission control.
16✔
1120
        err := p.router.cfg.MissionControl.ReportPaymentSuccess(
16✔
1121
                attempt.AttemptID, &attempt.Route,
16✔
1122
        )
16✔
1123
        if err != nil {
16✔
1124
                log.Errorf("Error reporting payment success to mc: %v", err)
×
1125
        }
×
1126

1127
        // In case of success we atomically store settle result to the DB and
1128
        // move the shard to the settled state.
1129
        htlcAttempt, err := p.router.cfg.Control.SettleAttempt(
16✔
1130
                p.identifier, attempt.AttemptID,
16✔
1131
                &channeldb.HTLCSettleInfo{
16✔
1132
                        Preimage:   result.Preimage,
16✔
1133
                        SettleTime: p.router.cfg.Clock.Now(),
16✔
1134
                },
16✔
1135
        )
16✔
1136
        if err != nil {
18✔
1137
                log.Errorf("Error settling attempt %v for payment %v with "+
2✔
1138
                        "preimage %v: %v", attempt.AttemptID, p.identifier,
2✔
1139
                        result.Preimage, err)
2✔
1140

2✔
1141
                // We won't mark the attempt as failed since we already have
2✔
1142
                // the preimage.
2✔
1143
                return nil, err
2✔
1144
        }
2✔
1145

1146
        return &attemptResult{
14✔
1147
                attempt: htlcAttempt,
14✔
1148
        }, nil
14✔
1149
}
1150

1151
// collectAndHandleResult waits for the result for the given attempt to be
1152
// available from the Switch, then records the attempt outcome with the control
1153
// tower. An attemptResult is returned, indicating the final outcome of this
1154
// HTLC attempt.
1155
func (p *paymentLifecycle) collectAndHandleResult(
1156
        attempt *channeldb.HTLCAttempt) (*attemptResult, error) {
12✔
1157

12✔
1158
        result, err := p.collectResult(attempt)
12✔
1159
        if err != nil {
15✔
1160
                return nil, err
3✔
1161
        }
3✔
1162

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