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

lightningnetwork / lnd / 13440912774

20 Feb 2025 05:14PM UTC coverage: 57.697% (-1.1%) from 58.802%
13440912774

Pull #9535

github

guggero
GitHub: remove duplicate caching

Turns out that actions/setup-go starting with @v4 also adds caching.
With that, our cache size on disk has almost doubled, leading to the
GitHub runner running out of space in certain situation.
We fix that by disabling the automated caching since we already have our
own, custom-tailored version.
Pull Request #9535: GitHub: remove duplicate caching

103519 of 179417 relevant lines covered (57.7%)

24825.3 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✔
101
                budget = 0
×
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✔
466
                        log.Errorf("Error collecting result for attempt %v in "+
×
467
                                "payment %v: %v", attempt.AttemptID,
×
468
                                p.identifier, err)
×
469

×
470
                        return
×
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✔
637
                hop.AMP = shard.AMP()
×
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✔
922
                log.Errorf("Channel update of ourselves received")
×
923

×
924
                return nil
×
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✔
1073
                a := a
×
1074

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

×
1078
                p.resultCollector(&a)
×
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