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

lightningnetwork / lnd / 16811814134

07 Aug 2025 05:46PM UTC coverage: 57.463% (-9.5%) from 66.947%
16811814134

Pull #9844

github

web-flow
Merge 4b08ee16d into 2269859d9
Pull Request #9844: Refactor Payment PR 3

434 of 645 new or added lines in 17 files covered. (67.29%)

28260 existing lines in 457 files now uncovered.

99053 of 172378 relevant lines covered (57.46%)

1.78 hits per line

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

64.91
/payments/db/payments.go
1
package paymentsdb
2

3
import (
4
        "bytes"
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/lntypes"
13
        "github.com/lightningnetwork/lnd/lnutils"
14
        "github.com/lightningnetwork/lnd/lnwire"
15
        "github.com/lightningnetwork/lnd/routing/route"
16
)
17

18
// HTLCAttemptInfo contains static information about a specific HTLC attempt
19
// for a payment. This information is used by the router to handle any errors
20
// coming back after an attempt is made, and to query the switch about the
21
// status of the attempt.
22
type HTLCAttemptInfo struct {
23
        // AttemptID is the unique ID used for this attempt.
24
        AttemptID uint64
25

26
        // sessionKey is the raw bytes ephemeral key used for this attempt.
27
        // These bytes are lazily read off disk to save ourselves the expensive
28
        // EC operations used by btcec.PrivKeyFromBytes.
29
        sessionKey [btcec.PrivKeyBytesLen]byte
30

31
        // cachedSessionKey is our fully deserialized sesionKey. This value
32
        // may be nil if the attempt has just been read from disk and its
33
        // session key has not been used yet.
34
        cachedSessionKey *btcec.PrivateKey
35

36
        // Route is the route attempted to send the HTLC.
37
        Route route.Route
38

39
        // AttemptTime is the time at which this HTLC was attempted.
40
        AttemptTime time.Time
41

42
        // Hash is the hash used for this single HTLC attempt. For AMP payments
43
        // this will differ across attempts, for non-AMP payments each attempt
44
        // will use the same hash. This can be nil for older payment attempts,
45
        // in which the payment's PaymentHash in the PaymentCreationInfo should
46
        // be used.
47
        Hash *lntypes.Hash
48

49
        // onionBlob is the cached value for onion blob created from the sphinx
50
        // construction.
51
        onionBlob [lnwire.OnionPacketSize]byte
52

53
        // circuit is the cached value for sphinx circuit.
54
        circuit *sphinx.Circuit
55
}
56

57
// NewHtlcAttempt creates a htlc attempt.
58
func NewHtlcAttempt(attemptID uint64, sessionKey *btcec.PrivateKey,
59
        route route.Route, attemptTime time.Time,
60
        hash *lntypes.Hash) (*HTLCAttempt, error) {
3✔
61

3✔
62
        var scratch [btcec.PrivKeyBytesLen]byte
3✔
63
        copy(scratch[:], sessionKey.Serialize())
3✔
64

3✔
65
        info := HTLCAttemptInfo{
3✔
66
                AttemptID:        attemptID,
3✔
67
                sessionKey:       scratch,
3✔
68
                cachedSessionKey: sessionKey,
3✔
69
                Route:            route,
3✔
70
                AttemptTime:      attemptTime,
3✔
71
                Hash:             hash,
3✔
72
        }
3✔
73

3✔
74
        if err := info.attachOnionBlobAndCircuit(); err != nil {
3✔
NEW
75
                return nil, err
×
NEW
76
        }
×
77

78
        return &HTLCAttempt{HTLCAttemptInfo: info}, nil
3✔
79
}
80

81
// SessionKey returns the ephemeral key used for a htlc attempt. This function
82
// performs expensive ec-ops to obtain the session key if it is not cached.
83
func (h *HTLCAttemptInfo) SessionKey() *btcec.PrivateKey {
3✔
84
        if h.cachedSessionKey == nil {
6✔
85
                h.cachedSessionKey, _ = btcec.PrivKeyFromBytes(
3✔
86
                        h.sessionKey[:],
3✔
87
                )
3✔
88
        }
3✔
89

90
        return h.cachedSessionKey
3✔
91
}
92

93
// SetSessionKey sets the session key for the HTLC attempt.
94
func (h *HTLCAttemptInfo) SetSessionKey(key [btcec.PrivKeyBytesLen]byte) {
3✔
95
        h.sessionKey = key
3✔
96
        h.cachedSessionKey = nil
3✔
97
}
3✔
98

99
// OnionBlob returns the onion blob created from the sphinx construction.
100
func (h *HTLCAttemptInfo) OnionBlob() ([lnwire.OnionPacketSize]byte, error) {
3✔
101
        var zeroBytes [lnwire.OnionPacketSize]byte
3✔
102
        if h.onionBlob == zeroBytes {
3✔
NEW
103
                if err := h.attachOnionBlobAndCircuit(); err != nil {
×
NEW
104
                        return zeroBytes, err
×
NEW
105
                }
×
106
        }
107

108
        return h.onionBlob, nil
3✔
109
}
110

111
// Circuit returns the sphinx circuit for this attempt.
112
func (h *HTLCAttemptInfo) Circuit() (*sphinx.Circuit, error) {
3✔
113
        if h.circuit == nil {
6✔
114
                if err := h.attachOnionBlobAndCircuit(); err != nil {
3✔
NEW
115
                        return nil, err
×
NEW
116
                }
×
117
        }
118

119
        return h.circuit, nil
3✔
120
}
121

122
// attachOnionBlobAndCircuit creates a sphinx packet and caches the onion blob
123
// and circuit for this attempt.
124
func (h *HTLCAttemptInfo) attachOnionBlobAndCircuit() error {
3✔
125
        onionBlob, circuit, err := generateSphinxPacket(
3✔
126
                &h.Route, h.Hash[:], h.SessionKey(),
3✔
127
        )
3✔
128
        if err != nil {
3✔
NEW
129
                return err
×
NEW
130
        }
×
131

132
        copy(h.onionBlob[:], onionBlob)
3✔
133
        h.circuit = circuit
3✔
134

3✔
135
        return nil
3✔
136
}
137

138
// AttachOnionBlobAndCircuit exports the attachOnionBlobAndCircuit method
139
// during refactoring.
NEW
140
func (h *HTLCAttemptInfo) AttachOnionBlobAndCircuit() error {
×
NEW
141
        return h.attachOnionBlobAndCircuit()
×
NEW
142
}
×
143

144
// HTLCAttempt contains information about a specific HTLC attempt for a given
145
// payment. It contains the HTLCAttemptInfo used to send the HTLC, as well
146
// as a timestamp and any known outcome of the attempt.
147
type HTLCAttempt struct {
148
        HTLCAttemptInfo
149

150
        // Settle is the preimage of a successful payment. This serves as a
151
        // proof of payment. It will only be non-nil for settled payments.
152
        //
153
        // NOTE: Can be nil if payment is not settled.
154
        Settle *HTLCSettleInfo
155

156
        // Fail is a failure reason code indicating the reason the payment
157
        // failed. It is only non-nil for failed payments.
158
        //
159
        // NOTE: Can be nil if payment is not failed.
160
        Failure *HTLCFailInfo
161
}
162

163
// HTLCSettleInfo encapsulates the information that augments an HTLCAttempt in
164
// the event that the HTLC is successful.
165
type HTLCSettleInfo struct {
166
        // Preimage is the preimage of a successful HTLC. This serves as a proof
167
        // of payment.
168
        Preimage lntypes.Preimage
169

170
        // SettleTime is the time at which this HTLC was settled.
171
        SettleTime time.Time
172
}
173

174
// HTLCFailReason is the reason an htlc failed.
175
type HTLCFailReason byte
176

177
const (
178
        // HTLCFailUnknown is recorded for htlcs that failed with an unknown
179
        // reason.
180
        HTLCFailUnknown HTLCFailReason = 0
181

182
        // HTLCFailUnreadable is recorded for htlcs that had a failure message
183
        // that couldn't be decrypted.
184
        HTLCFailUnreadable HTLCFailReason = 1
185

186
        // HTLCFailInternal is recorded for htlcs that failed because of an
187
        // internal error.
188
        HTLCFailInternal HTLCFailReason = 2
189

190
        // HTLCFailMessage is recorded for htlcs that failed with a network
191
        // failure message.
192
        HTLCFailMessage HTLCFailReason = 3
193
)
194

195
// HTLCFailInfo encapsulates the information that augments an HTLCAttempt in the
196
// event that the HTLC fails.
197
type HTLCFailInfo struct {
198
        // FailTime is the time at which this HTLC was failed.
199
        FailTime time.Time
200

201
        // Message is the wire message that failed this HTLC. This field will be
202
        // populated when the failure reason is HTLCFailMessage.
203
        Message lnwire.FailureMessage
204

205
        // Reason is the failure reason for this HTLC.
206
        Reason HTLCFailReason
207

208
        // The position in the path of the intermediate or final node that
209
        // generated the failure message. Position zero is the sender node. This
210
        // field will be populated when the failure reason is either
211
        // HTLCFailMessage or HTLCFailUnknown.
212
        FailureSourceIndex uint32
213
}
214

215
// generateSphinxPacket generates then encodes a sphinx packet which encodes
216
// the onion route specified by the passed layer 3 route. The blob returned
217
// from this function can immediately be included within an HTLC add packet to
218
// be sent to the first hop within the route.
219
func generateSphinxPacket(rt *route.Route, paymentHash []byte,
220
        sessionKey *btcec.PrivateKey) ([]byte, *sphinx.Circuit, error) {
3✔
221

3✔
222
        // Now that we know we have an actual route, we'll map the route into a
3✔
223
        // sphinx payment path which includes per-hop payloads for each hop
3✔
224
        // that give each node within the route the necessary information
3✔
225
        // (fees, CLTV value, etc.) to properly forward the payment.
3✔
226
        sphinxPath, err := rt.ToSphinxPath()
3✔
227
        if err != nil {
3✔
NEW
228
                return nil, nil, err
×
NEW
229
        }
×
230

231
        log.Tracef("Constructed per-hop payloads for payment_hash=%x: %v",
3✔
232
                paymentHash, lnutils.NewLogClosure(func() string {
3✔
NEW
233
                        path := make(
×
NEW
234
                                []sphinx.OnionHop, sphinxPath.TrueRouteLength(),
×
NEW
235
                        )
×
NEW
236
                        for i := range path {
×
NEW
237
                                hopCopy := sphinxPath[i]
×
NEW
238
                                path[i] = hopCopy
×
NEW
239
                        }
×
240

NEW
241
                        return spew.Sdump(path)
×
242
                }),
243
        )
244

245
        // Next generate the onion routing packet which allows us to perform
246
        // privacy preserving source routing across the network.
247
        sphinxPacket, err := sphinx.NewOnionPacket(
3✔
248
                sphinxPath, sessionKey, paymentHash,
3✔
249
                sphinx.DeterministicPacketFiller,
3✔
250
        )
3✔
251
        if err != nil {
3✔
NEW
252
                return nil, nil, err
×
NEW
253
        }
×
254

255
        // Finally, encode Sphinx packet using its wire representation to be
256
        // included within the HTLC add packet.
257
        var onionBlob bytes.Buffer
3✔
258
        if err := sphinxPacket.Encode(&onionBlob); err != nil {
3✔
NEW
259
                return nil, nil, err
×
NEW
260
        }
×
261

262
        log.Tracef("Generated sphinx packet: %v",
3✔
263
                lnutils.NewLogClosure(func() string {
3✔
NEW
264
                        // We make a copy of the ephemeral key and unset the
×
NEW
265
                        // internal curve here in order to keep the logs from
×
NEW
266
                        // getting noisy.
×
NEW
267
                        key := *sphinxPacket.EphemeralKey
×
NEW
268
                        packetCopy := *sphinxPacket
×
NEW
269
                        packetCopy.EphemeralKey = &key
×
NEW
270

×
NEW
271
                        return spew.Sdump(packetCopy)
×
NEW
272
                }),
×
273
        )
274

275
        return onionBlob.Bytes(), &sphinx.Circuit{
3✔
276
                SessionKey:  sessionKey,
3✔
277
                PaymentPath: sphinxPath.NodeKeys(),
3✔
278
        }, nil
3✔
279
}
280

281
// FailureReason encodes the reason a payment ultimately failed.
282
type FailureReason byte
283

284
const (
285
        // FailureReasonTimeout indicates that the payment did timeout before a
286
        // successful payment attempt was made.
287
        FailureReasonTimeout FailureReason = 0
288

289
        // FailureReasonNoRoute indicates no successful route to the
290
        // destination was found during path finding.
291
        FailureReasonNoRoute FailureReason = 1
292

293
        // FailureReasonError indicates that an unexpected error happened during
294
        // payment.
295
        FailureReasonError FailureReason = 2
296

297
        // FailureReasonPaymentDetails indicates that either the hash is unknown
298
        // or the final cltv delta or amount is incorrect.
299
        FailureReasonPaymentDetails FailureReason = 3
300

301
        // FailureReasonInsufficientBalance indicates that we didn't have enough
302
        // balance to complete the payment.
303
        FailureReasonInsufficientBalance FailureReason = 4
304

305
        // FailureReasonCanceled indicates that the payment was canceled by the
306
        // user.
307
        FailureReasonCanceled FailureReason = 5
308

309
        // TODO(joostjager): Add failure reasons for:
310
        // LocalLiquidityInsufficient, RemoteCapacityInsufficient.
311
)
312

313
// Error returns a human-readable error string for the FailureReason.
314
func (r FailureReason) Error() string {
3✔
315
        return r.String()
3✔
316
}
3✔
317

318
// String returns a human-readable FailureReason.
319
func (r FailureReason) String() string {
3✔
320
        switch r {
3✔
NEW
321
        case FailureReasonTimeout:
×
NEW
322
                return "timeout"
×
323
        case FailureReasonNoRoute:
3✔
324
                return "no_route"
3✔
NEW
325
        case FailureReasonError:
×
NEW
326
                return "error"
×
327
        case FailureReasonPaymentDetails:
3✔
328
                return "incorrect_payment_details"
3✔
329
        case FailureReasonInsufficientBalance:
3✔
330
                return "insufficient_balance"
3✔
NEW
331
        case FailureReasonCanceled:
×
NEW
332
                return "canceled"
×
333
        }
334

NEW
335
        return "unknown"
×
336
}
337

338
// MPPaymentState wraps a series of info needed for a given payment, which is
339
// used by both MPP and AMP. This is a memory representation of the payment's
340
// current state and is updated whenever the payment is read from disk.
341
type MPPaymentState struct {
342
        // NumAttemptsInFlight specifies the number of HTLCs the payment is
343
        // waiting results for.
344
        NumAttemptsInFlight int
345

346
        // RemainingAmt specifies how much more money to be sent.
347
        RemainingAmt lnwire.MilliSatoshi
348

349
        // FeesPaid specifies the total fees paid so far that can be used to
350
        // calculate remaining fee budget.
351
        FeesPaid lnwire.MilliSatoshi
352

353
        // HasSettledHTLC is true if at least one of the payment's HTLCs is
354
        // settled.
355
        HasSettledHTLC bool
356

357
        // PaymentFailed is true if the payment has been marked as failed with
358
        // a reason.
359
        PaymentFailed bool
360
}
361

362
// MPPayment is a wrapper around a payment's PaymentCreationInfo and
363
// HTLCAttempts. All payments will have the PaymentCreationInfo set, any
364
// HTLCs made in attempts to be completed will populated in the HTLCs slice.
365
// Each populated HTLCAttempt represents an attempted HTLC, each of which may
366
// have the associated Settle or Fail struct populated if the HTLC is no longer
367
// in-flight.
368
type MPPayment struct {
369
        // SequenceNum is a unique identifier used to sort the payments in
370
        // order of creation.
371
        SequenceNum uint64
372

373
        // Info holds all static information about this payment, and is
374
        // populated when the payment is initiated.
375
        Info *PaymentCreationInfo
376

377
        // HTLCs holds the information about individual HTLCs that we send in
378
        // order to make the payment.
379
        HTLCs []HTLCAttempt
380

381
        // FailureReason is the failure reason code indicating the reason the
382
        // payment failed.
383
        //
384
        // NOTE: Will only be set once the daemon has given up on the payment
385
        // altogether.
386
        FailureReason *FailureReason
387

388
        // Status is the current PaymentStatus of this payment.
389
        Status PaymentStatus
390

391
        // State is the current state of the payment that holds a number of key
392
        // insights and is used to determine what to do on each payment loop
393
        // iteration.
394
        State *MPPaymentState
395
}
396

397
// Terminated returns a bool to specify whether the payment is in a terminal
398
// state.
399
func (m *MPPayment) Terminated() bool {
3✔
400
        // If the payment is in terminal state, it cannot be updated.
3✔
401
        return m.Status.updatable() != nil
3✔
402
}
3✔
403

404
// TerminalInfo returns any HTLC settle info recorded. If no settle info is
405
// recorded, any payment level failure will be returned. If neither a settle
406
// nor a failure is recorded, both return values will be nil.
407
func (m *MPPayment) TerminalInfo() (*HTLCAttempt, *FailureReason) {
3✔
408
        for _, h := range m.HTLCs {
6✔
409
                if h.Settle != nil {
6✔
410
                        return &h, nil
3✔
411
                }
3✔
412
        }
413

414
        return nil, m.FailureReason
3✔
415
}
416

417
// SentAmt returns the sum of sent amount and fees for HTLCs that are either
418
// settled or still in flight.
419
func (m *MPPayment) SentAmt() (lnwire.MilliSatoshi, lnwire.MilliSatoshi) {
3✔
420
        var sent, fees lnwire.MilliSatoshi
3✔
421
        for _, h := range m.HTLCs {
6✔
422
                if h.Failure != nil {
6✔
423
                        continue
3✔
424
                }
425

426
                // The attempt was not failed, meaning the amount was
427
                // potentially sent to the receiver.
428
                sent += h.Route.ReceiverAmt()
3✔
429
                fees += h.Route.TotalFees()
3✔
430
        }
431

432
        return sent, fees
3✔
433
}
434

435
// InFlightHTLCs returns the HTLCs that are still in-flight, meaning they have
436
// not been settled or failed.
437
func (m *MPPayment) InFlightHTLCs() []HTLCAttempt {
3✔
438
        var inflights []HTLCAttempt
3✔
439
        for _, h := range m.HTLCs {
6✔
440
                if h.Settle != nil || h.Failure != nil {
6✔
441
                        continue
3✔
442
                }
443

444
                inflights = append(inflights, h)
3✔
445
        }
446

447
        return inflights
3✔
448
}
449

450
// GetAttempt returns the specified htlc attempt on the payment.
451
func (m *MPPayment) GetAttempt(id uint64) (*HTLCAttempt, error) {
3✔
452
        // TODO(yy): iteration can be slow, make it into a tree or use BS.
3✔
453
        for _, htlc := range m.HTLCs {
6✔
454
                htlc := htlc
3✔
455
                if htlc.AttemptID == id {
6✔
456
                        return &htlc, nil
3✔
457
                }
3✔
458
        }
459

NEW
460
        return nil, errors.New("htlc attempt not found on payment")
×
461
}
462

463
// Registrable returns an error to specify whether adding more HTLCs to the
464
// payment with its current status is allowed. A payment can accept new HTLC
465
// registrations when it's newly created, or none of its HTLCs is in a terminal
466
// state.
467
func (m *MPPayment) Registrable() error {
3✔
468
        // If updating the payment is not allowed, we can't register new HTLCs.
3✔
469
        // Otherwise, the status must be either `StatusInitiated` or
3✔
470
        // `StatusInFlight`.
3✔
471
        if err := m.Status.updatable(); err != nil {
6✔
472
                return err
3✔
473
        }
3✔
474

475
        // Exit early if this is not inflight.
476
        if m.Status != StatusInFlight {
6✔
477
                return nil
3✔
478
        }
3✔
479

480
        // There are still inflight HTLCs and we need to check whether there
481
        // are settled HTLCs or the payment is failed. If we already have
482
        // settled HTLCs, we won't allow adding more HTLCs.
483
        if m.State.HasSettledHTLC {
3✔
NEW
484
                return ErrPaymentPendingSettled
×
NEW
485
        }
×
486

487
        // If the payment is already failed, we won't allow adding more HTLCs.
488
        if m.State.PaymentFailed {
3✔
NEW
489
                return ErrPaymentPendingFailed
×
NEW
490
        }
×
491

492
        // Otherwise we can add more HTLCs.
493
        return nil
3✔
494
}
495

496
// setState creates and attaches a new MPPaymentState to the payment. It also
497
// updates the payment's status based on its current state.
498
func (m *MPPayment) setState() error {
3✔
499
        // Fetch the total amount and fees that has already been sent in
3✔
500
        // settled and still in-flight shards.
3✔
501
        sentAmt, fees := m.SentAmt()
3✔
502

3✔
503
        // Sanity check we haven't sent a value larger than the payment amount.
3✔
504
        totalAmt := m.Info.Value
3✔
505
        if sentAmt > totalAmt {
3✔
NEW
506
                return fmt.Errorf("%w: sent=%v, total=%v", ErrSentExceedsTotal,
×
NEW
507
                        sentAmt, totalAmt)
×
NEW
508
        }
×
509

510
        // Get any terminal info for this payment.
511
        settle, failure := m.TerminalInfo()
3✔
512

3✔
513
        // Now determine the payment's status.
3✔
514
        status, err := decidePaymentStatus(m.HTLCs, m.FailureReason)
3✔
515
        if err != nil {
3✔
NEW
516
                return err
×
NEW
517
        }
×
518

519
        // Update the payment state and status.
520
        m.State = &MPPaymentState{
3✔
521
                NumAttemptsInFlight: len(m.InFlightHTLCs()),
3✔
522
                RemainingAmt:        totalAmt - sentAmt,
3✔
523
                FeesPaid:            fees,
3✔
524
                HasSettledHTLC:      settle != nil,
3✔
525
                PaymentFailed:       failure != nil,
3✔
526
        }
3✔
527
        m.Status = status
3✔
528

3✔
529
        return nil
3✔
530
}
531

532
// SetState calls the internal method setState. This is a temporary method
533
// to be used by the tests in routing. Once the tests are updated to use mocks,
534
// this method can be removed.
535
//
536
// TODO(yy): delete.
537
func (m *MPPayment) SetState() error {
3✔
538
        return m.setState()
3✔
539
}
3✔
540

541
// NeedWaitAttempts decides whether we need to hold creating more HTLC attempts
542
// and wait for the results of the payment's inflight HTLCs. Return an error if
543
// the payment is in an unexpected state.
544
func (m *MPPayment) NeedWaitAttempts() (bool, error) {
3✔
545
        // Check when the remainingAmt is not zero, which means we have more
3✔
546
        // money to be sent.
3✔
547
        if m.State.RemainingAmt != 0 {
6✔
548
                switch m.Status {
3✔
549
                // If the payment is newly created, no need to wait for HTLC
550
                // results.
NEW
551
                case StatusInitiated:
×
NEW
552
                        return false, nil
×
553

554
                // If we have inflight HTLCs, we'll check if we have terminal
555
                // states to decide if we need to wait.
NEW
556
                case StatusInFlight:
×
NEW
557
                        // We still have money to send, and one of the HTLCs is
×
NEW
558
                        // settled. We'd stop sending money and wait for all
×
NEW
559
                        // inflight HTLC attempts to finish.
×
NEW
560
                        if m.State.HasSettledHTLC {
×
NEW
561
                                log.Warnf("payment=%v has remaining amount "+
×
NEW
562
                                        "%v, yet at least one of its HTLCs is "+
×
NEW
563
                                        "settled", m.Info.PaymentIdentifier,
×
NEW
564
                                        m.State.RemainingAmt)
×
NEW
565

×
NEW
566
                                return true, nil
×
NEW
567
                        }
×
568

569
                        // The payment has a failure reason though we still
570
                        // have money to send, we'd stop sending money and wait
571
                        // for all inflight HTLC attempts to finish.
NEW
572
                        if m.State.PaymentFailed {
×
NEW
573
                                return true, nil
×
NEW
574
                        }
×
575

576
                        // Otherwise we don't need to wait for inflight HTLCs
577
                        // since we still have money to be sent.
NEW
578
                        return false, nil
×
579

580
                // We need to send more money, yet the payment is already
581
                // succeeded. Return an error in this case as the receiver is
582
                // violating the protocol.
NEW
583
                case StatusSucceeded:
×
NEW
584
                        return false, fmt.Errorf("%w: parts of the payment "+
×
NEW
585
                                "already succeeded but still have remaining "+
×
NEW
586
                                "amount %v", ErrPaymentInternal,
×
NEW
587
                                m.State.RemainingAmt)
×
588

589
                // The payment is failed and we have no inflight HTLCs, no need
590
                // to wait.
591
                case StatusFailed:
3✔
592
                        return false, nil
3✔
593

594
                // Unknown payment status.
NEW
595
                default:
×
NEW
596
                        return false, fmt.Errorf("%w: %s",
×
NEW
597
                                ErrUnknownPaymentStatus, m.Status)
×
598
                }
599
        }
600

601
        // Now we determine whether we need to wait when the remainingAmt is
602
        // already zero.
603
        switch m.Status {
3✔
604
        // When the payment is newly created, yet the payment has no remaining
605
        // amount, return an error.
NEW
606
        case StatusInitiated:
×
NEW
607
                return false, fmt.Errorf("%w: %v", ErrPaymentInternal, m.Status)
×
608

609
        // If the payment is inflight, we must wait.
610
        //
611
        // NOTE: an edge case is when all HTLCs are failed while the payment is
612
        // not failed we'd still be in this inflight state. However, since the
613
        // remainingAmt is zero here, it means we cannot be in that state as
614
        // otherwise the remainingAmt would not be zero.
615
        case StatusInFlight:
3✔
616
                return true, nil
3✔
617

618
        // If the payment is already succeeded, no need to wait.
619
        case StatusSucceeded:
3✔
620
                return false, nil
3✔
621

622
        // If the payment is already failed, yet the remaining amount is zero,
623
        // return an error as this indicates an error state. We will only each
624
        // this status when there are no inflight HTLCs and the payment is
625
        // marked as failed with a reason, which means the remainingAmt must
626
        // not be zero because our sentAmt is zero.
NEW
627
        case StatusFailed:
×
NEW
628
                return false, fmt.Errorf("%w: %v", ErrPaymentInternal, m.Status)
×
629

630
        // Unknown payment status.
NEW
631
        default:
×
NEW
632
                return false, fmt.Errorf("%w: %s", ErrUnknownPaymentStatus,
×
NEW
633
                        m.Status)
×
634
        }
635
}
636

637
// GetState returns the internal state of the payment.
638
func (m *MPPayment) GetState() *MPPaymentState {
3✔
639
        return m.State
3✔
640
}
3✔
641

642
// GetStatus returns the current status of the payment.
643
func (m *MPPayment) GetStatus() PaymentStatus {
3✔
644
        return m.Status
3✔
645
}
3✔
646

647
// GetHTLCs returns all the HTLCs for this payment.
NEW
648
func (m *MPPayment) GetHTLCs() []HTLCAttempt {
×
NEW
649
        return m.HTLCs
×
NEW
650
}
×
651

652
// AllowMoreAttempts is used to decide whether we can safely attempt more HTLCs
653
// for a given payment state. Return an error if the payment is in an
654
// unexpected state.
655
func (m *MPPayment) AllowMoreAttempts() (bool, error) {
3✔
656
        // Now check whether the remainingAmt is zero or not. If we don't have
3✔
657
        // any remainingAmt, no more HTLCs should be made.
3✔
658
        if m.State.RemainingAmt == 0 {
6✔
659
                // If the payment is newly created, yet we don't have any
3✔
660
                // remainingAmt, return an error.
3✔
661
                if m.Status == StatusInitiated {
3✔
NEW
662
                        return false, fmt.Errorf("%w: initiated payment has "+
×
NEW
663
                                "zero remainingAmt",
×
NEW
664
                                ErrPaymentInternal)
×
NEW
665
                }
×
666

667
                // Otherwise, exit early since all other statuses with zero
668
                // remainingAmt indicate no more HTLCs can be made.
669
                return false, nil
3✔
670
        }
671

672
        // Otherwise, the remaining amount is not zero, we now decide whether
673
        // to make more attempts based on the payment's current status.
674
        //
675
        // If at least one of the payment's attempts is settled, yet we haven't
676
        // sent all the amount, it indicates something is wrong with the peer
677
        // as the preimage is received. In this case, return an error state.
678
        if m.Status == StatusSucceeded {
3✔
NEW
679
                return false, fmt.Errorf("%w: payment already succeeded but "+
×
NEW
680
                        "still have remaining amount %v",
×
NEW
681
                        ErrPaymentInternal, m.State.RemainingAmt)
×
NEW
682
        }
×
683

684
        // Now check if we can register a new HTLC.
685
        err := m.Registrable()
3✔
686
        if err != nil {
6✔
687
                log.Warnf("Payment(%v): cannot register HTLC attempt: %v, "+
3✔
688
                        "current status: %s", m.Info.PaymentIdentifier,
3✔
689
                        err, m.Status)
3✔
690

3✔
691
                return false, nil
3✔
692
        }
3✔
693

694
        // Now we know we can register new HTLCs.
695
        return true, nil
3✔
696
}
697

698
// PaymentCreationInfo is the information necessary to have ready when
699
// initiating a payment, moving it into state InFlight.
700
type PaymentCreationInfo struct {
701
        // PaymentIdentifier is the hash this payment is paying to in case of
702
        // non-AMP payments, and the SetID for AMP payments.
703
        PaymentIdentifier lntypes.Hash
704

705
        // Value is the amount we are paying.
706
        Value lnwire.MilliSatoshi
707

708
        // CreationTime is the time when this payment was initiated.
709
        CreationTime time.Time
710

711
        // PaymentRequest is the full payment request, if any.
712
        PaymentRequest []byte
713

714
        // FirstHopCustomRecords are the TLV records that are to be sent to the
715
        // first hop of this payment. These records will be transmitted via the
716
        // wire message only and therefore do not affect the onion payload size.
717
        FirstHopCustomRecords lnwire.CustomRecords
718
}
719

720
// String returns a human-readable description of the payment creation info.
NEW
721
func (p *PaymentCreationInfo) String() string {
×
NEW
722
        return fmt.Sprintf("payment_id=%v, amount=%v, created_at=%v",
×
NEW
723
                p.PaymentIdentifier, p.Value, p.CreationTime)
×
NEW
724
}
×
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