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

lightningnetwork / lnd / 17236841262

26 Aug 2025 11:33AM UTC coverage: 66.228% (+8.9%) from 57.321%
17236841262

Pull #9147

github

web-flow
Merge 39fde1801 into 0c2f045f5
Pull Request #9147: [Part 1|3] Introduce SQL Payment schema into LND

129 of 1847 new or added lines in 13 files covered. (6.98%)

20 existing lines in 7 files now uncovered.

136069 of 205456 relevant lines covered (66.23%)

21357.68 hits per line

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

88.08
/payments/db/payment.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
// FailureReason encodes the reason a payment ultimately failed.
19
type FailureReason byte
20

21
const (
22
        // FailureReasonTimeout indicates that the payment did timeout before a
23
        // successful payment attempt was made.
24
        FailureReasonTimeout FailureReason = 0
25

26
        // FailureReasonNoRoute indicates no successful route to the
27
        // destination was found during path finding.
28
        FailureReasonNoRoute FailureReason = 1
29

30
        // FailureReasonError indicates that an unexpected error happened during
31
        // payment.
32
        FailureReasonError FailureReason = 2
33

34
        // FailureReasonPaymentDetails indicates that either the hash is unknown
35
        // or the final cltv delta or amount is incorrect.
36
        FailureReasonPaymentDetails FailureReason = 3
37

38
        // FailureReasonInsufficientBalance indicates that we didn't have enough
39
        // balance to complete the payment.
40
        FailureReasonInsufficientBalance FailureReason = 4
41

42
        // FailureReasonCanceled indicates that the payment was canceled by the
43
        // user.
44
        FailureReasonCanceled FailureReason = 5
45

46
        // TODO(joostjager): Add failure reasons for:
47
        // LocalLiquidityInsufficient, RemoteCapacityInsufficient.
48
)
49

50
// Error returns a human-readable error string for the FailureReason.
51
func (r FailureReason) Error() string {
43✔
52
        return r.String()
43✔
53
}
43✔
54

55
// String returns a human-readable FailureReason.
56
func (r FailureReason) String() string {
43✔
57
        switch r {
43✔
58
        case FailureReasonTimeout:
12✔
59
                return "timeout"
12✔
60
        case FailureReasonNoRoute:
11✔
61
                return "no_route"
11✔
62
        case FailureReasonError:
14✔
63
                return "error"
14✔
64
        case FailureReasonPaymentDetails:
5✔
65
                return "incorrect_payment_details"
5✔
66
        case FailureReasonInsufficientBalance:
3✔
67
                return "insufficient_balance"
3✔
68
        case FailureReasonCanceled:
7✔
69
                return "canceled"
7✔
70
        }
71

72
        return "unknown"
×
73
}
74

75
// PaymentCreationInfo is the information necessary to have ready when
76
// initiating a payment, moving it into state InFlight.
77
type PaymentCreationInfo struct {
78
        // PaymentIdentifier is the hash this payment is paying to in case of
79
        // non-AMP payments, and the SetID for AMP payments.
80
        PaymentIdentifier lntypes.Hash
81

82
        // Value is the amount we are paying.
83
        Value lnwire.MilliSatoshi
84

85
        // CreationTime is the time when this payment was initiated.
86
        CreationTime time.Time
87

88
        // PaymentRequest is the full payment request, if any.
89
        PaymentRequest []byte
90

91
        // FirstHopCustomRecords are the TLV records that are to be sent to the
92
        // first hop of this payment. These records will be transmitted via the
93
        // wire message only and therefore do not affect the onion payload size.
94
        FirstHopCustomRecords lnwire.CustomRecords
95
}
96

97
// String returns a human-readable description of the payment creation info.
98
func (p *PaymentCreationInfo) String() string {
8✔
99
        return fmt.Sprintf("payment_id=%v, amount=%v, created_at=%v",
8✔
100
                p.PaymentIdentifier, p.Value, p.CreationTime)
8✔
101
}
8✔
102

103
// HTLCAttemptInfo contains static information about a specific HTLC attempt
104
// for a payment. This information is used by the router to handle any errors
105
// coming back after an attempt is made, and to query the switch about the
106
// status of the attempt.
107
type HTLCAttemptInfo struct {
108
        // AttemptID is the unique ID used for this attempt.
109
        AttemptID uint64
110

111
        // sessionKey is the raw bytes ephemeral key used for this attempt.
112
        // These bytes are lazily read off disk to save ourselves the expensive
113
        // EC operations used by btcec.PrivKeyFromBytes.
114
        sessionKey [btcec.PrivKeyBytesLen]byte
115

116
        // cachedSessionKey is our fully deserialized sesionKey. This value
117
        // may be nil if the attempt has just been read from disk and its
118
        // session key has not been used yet.
119
        cachedSessionKey *btcec.PrivateKey
120

121
        // Route is the route attempted to send the HTLC.
122
        Route route.Route
123

124
        // AttemptTime is the time at which this HTLC was attempted.
125
        AttemptTime time.Time
126

127
        // Hash is the hash used for this single HTLC attempt. For AMP payments
128
        // this will differ across attempts, for non-AMP payments each attempt
129
        // will use the same hash. This can be nil for older payment attempts,
130
        // in which the payment's PaymentHash in the PaymentCreationInfo should
131
        // be used.
132
        Hash *lntypes.Hash
133

134
        // onionBlob is the cached value for onion blob created from the sphinx
135
        // construction.
136
        onionBlob [lnwire.OnionPacketSize]byte
137

138
        // circuit is the cached value for sphinx circuit.
139
        circuit *sphinx.Circuit
140
}
141

142
// NewHtlcAttempt creates a htlc attempt.
143
func NewHtlcAttempt(attemptID uint64, sessionKey *btcec.PrivateKey,
144
        route route.Route, attemptTime time.Time,
145
        hash *lntypes.Hash) (*HTLCAttempt, error) {
87✔
146

87✔
147
        var scratch [btcec.PrivKeyBytesLen]byte
87✔
148
        copy(scratch[:], sessionKey.Serialize())
87✔
149

87✔
150
        info := HTLCAttemptInfo{
87✔
151
                AttemptID:        attemptID,
87✔
152
                sessionKey:       scratch,
87✔
153
                cachedSessionKey: sessionKey,
87✔
154
                Route:            route,
87✔
155
                AttemptTime:      attemptTime,
87✔
156
                Hash:             hash,
87✔
157
        }
87✔
158

87✔
159
        if err := info.attachOnionBlobAndCircuit(); err != nil {
88✔
160
                return nil, err
1✔
161
        }
1✔
162

163
        return &HTLCAttempt{HTLCAttemptInfo: info}, nil
86✔
164
}
165

166
// SessionKey returns the ephemeral key used for a htlc attempt. This function
167
// performs expensive ec-ops to obtain the session key if it is not cached.
168
func (h *HTLCAttemptInfo) SessionKey() *btcec.PrivateKey {
99✔
169
        if h.cachedSessionKey == nil {
113✔
170
                h.cachedSessionKey, _ = btcec.PrivKeyFromBytes(
14✔
171
                        h.sessionKey[:],
14✔
172
                )
14✔
173
        }
14✔
174

175
        return h.cachedSessionKey
99✔
176
}
177

178
// setSessionKey sets the session key for the htlc attempt.
179
//
180
// NOTE: Only used for testing.
181
func (h *HTLCAttemptInfo) setSessionKey(sessionKey *btcec.PrivateKey) {
23✔
182
        h.cachedSessionKey = sessionKey
23✔
183

23✔
184
        // Also set the session key as a raw bytes.
23✔
185
        var scratch [btcec.PrivKeyBytesLen]byte
23✔
186
        copy(scratch[:], sessionKey.Serialize())
23✔
187
        h.sessionKey = scratch
23✔
188
}
23✔
189

190
// OnionBlob returns the onion blob created from the sphinx construction.
191
func (h *HTLCAttemptInfo) OnionBlob() ([lnwire.OnionPacketSize]byte, error) {
37✔
192
        var zeroBytes [lnwire.OnionPacketSize]byte
37✔
193
        if h.onionBlob == zeroBytes {
37✔
194
                if err := h.attachOnionBlobAndCircuit(); err != nil {
×
195
                        return zeroBytes, err
×
196
                }
×
197
        }
198

199
        return h.onionBlob, nil
37✔
200
}
201

202
// Circuit returns the sphinx circuit for this attempt.
203
func (h *HTLCAttemptInfo) Circuit() (*sphinx.Circuit, error) {
38✔
204
        if h.circuit == nil {
50✔
205
                if err := h.attachOnionBlobAndCircuit(); err != nil {
12✔
206
                        return nil, err
×
207
                }
×
208
        }
209

210
        return h.circuit, nil
38✔
211
}
212

213
// attachOnionBlobAndCircuit creates a sphinx packet and caches the onion blob
214
// and circuit for this attempt.
215
func (h *HTLCAttemptInfo) attachOnionBlobAndCircuit() error {
97✔
216
        onionBlob, circuit, err := generateSphinxPacket(
97✔
217
                &h.Route, h.Hash[:], h.SessionKey(),
97✔
218
        )
97✔
219
        if err != nil {
98✔
220
                return err
1✔
221
        }
1✔
222

223
        copy(h.onionBlob[:], onionBlob)
96✔
224
        h.circuit = circuit
96✔
225

96✔
226
        return nil
96✔
227
}
228

229
// HTLCAttempt contains information about a specific HTLC attempt for a given
230
// payment. It contains the HTLCAttemptInfo used to send the HTLC, as well
231
// as a timestamp and any known outcome of the attempt.
232
type HTLCAttempt struct {
233
        HTLCAttemptInfo
234

235
        // Settle is the preimage of a successful payment. This serves as a
236
        // proof of payment. It will only be non-nil for settled payments.
237
        //
238
        // NOTE: Can be nil if payment is not settled.
239
        Settle *HTLCSettleInfo
240

241
        // Fail is a failure reason code indicating the reason the payment
242
        // failed. It is only non-nil for failed payments.
243
        //
244
        // NOTE: Can be nil if payment is not failed.
245
        Failure *HTLCFailInfo
246
}
247

248
// HTLCSettleInfo encapsulates the information that augments an HTLCAttempt in
249
// the event that the HTLC is successful.
250
type HTLCSettleInfo struct {
251
        // Preimage is the preimage of a successful HTLC. This serves as a proof
252
        // of payment.
253
        Preimage lntypes.Preimage
254

255
        // SettleTime is the time at which this HTLC was settled.
256
        SettleTime time.Time
257
}
258

259
// HTLCFailReason is the reason an htlc failed.
260
type HTLCFailReason byte
261

262
const (
263
        // HTLCFailUnknown is recorded for htlcs that failed with an unknown
264
        // reason.
265
        HTLCFailUnknown HTLCFailReason = 0
266

267
        // HTLCFailUnreadable is recorded for htlcs that had a failure message
268
        // that couldn't be decrypted.
269
        HTLCFailUnreadable HTLCFailReason = 1
270

271
        // HTLCFailInternal is recorded for htlcs that failed because of an
272
        // internal error.
273
        HTLCFailInternal HTLCFailReason = 2
274

275
        // HTLCFailMessage is recorded for htlcs that failed with a network
276
        // failure message.
277
        HTLCFailMessage HTLCFailReason = 3
278
)
279

280
// HTLCFailInfo encapsulates the information that augments an HTLCAttempt in the
281
// event that the HTLC fails.
282
type HTLCFailInfo struct {
283
        // FailTime is the time at which this HTLC was failed.
284
        FailTime time.Time
285

286
        // Message is the wire message that failed this HTLC. This field will be
287
        // populated when the failure reason is HTLCFailMessage.
288
        Message lnwire.FailureMessage
289

290
        // Reason is the failure reason for this HTLC.
291
        Reason HTLCFailReason
292

293
        // The position in the path of the intermediate or final node that
294
        // generated the failure message. Position zero is the sender node. This
295
        // field will be populated when the failure reason is either
296
        // HTLCFailMessage or HTLCFailUnknown.
297
        FailureSourceIndex uint32
298
}
299

300
// MPPaymentState wraps a series of info needed for a given payment, which is
301
// used by both MPP and AMP. This is a memory representation of the payment's
302
// current state and is updated whenever the payment is read from disk.
303
type MPPaymentState struct {
304
        // NumAttemptsInFlight specifies the number of HTLCs the payment is
305
        // waiting results for.
306
        NumAttemptsInFlight int
307

308
        // RemainingAmt specifies how much more money to be sent.
309
        RemainingAmt lnwire.MilliSatoshi
310

311
        // FeesPaid specifies the total fees paid so far that can be used to
312
        // calculate remaining fee budget.
313
        FeesPaid lnwire.MilliSatoshi
314

315
        // HasSettledHTLC is true if at least one of the payment's HTLCs is
316
        // settled.
317
        HasSettledHTLC bool
318

319
        // PaymentFailed is true if the payment has been marked as failed with
320
        // a reason.
321
        PaymentFailed bool
322
}
323

324
// MPPayment is a wrapper around a payment's PaymentCreationInfo and
325
// HTLCAttempts. All payments will have the PaymentCreationInfo set, any
326
// HTLCs made in attempts to be completed will populated in the HTLCs slice.
327
// Each populated HTLCAttempt represents an attempted HTLC, each of which may
328
// have the associated Settle or Fail struct populated if the HTLC is no longer
329
// in-flight.
330
type MPPayment struct {
331
        // SequenceNum is a unique identifier used to sort the payments in
332
        // order of creation.
333
        SequenceNum uint64
334

335
        // Info holds all static information about this payment, and is
336
        // populated when the payment is initiated.
337
        Info *PaymentCreationInfo
338

339
        // HTLCs holds the information about individual HTLCs that we send in
340
        // order to make the payment.
341
        HTLCs []HTLCAttempt
342

343
        // FailureReason is the failure reason code indicating the reason the
344
        // payment failed.
345
        //
346
        // NOTE: Will only be set once the daemon has given up on the payment
347
        // altogether.
348
        FailureReason *FailureReason
349

350
        // Status is the current PaymentStatus of this payment.
351
        Status PaymentStatus
352

353
        // State is the current state of the payment that holds a number of key
354
        // insights and is used to determine what to do on each payment loop
355
        // iteration.
356
        State *MPPaymentState
357
}
358

359
// Terminated returns a bool to specify whether the payment is in a terminal
360
// state.
361
func (m *MPPayment) Terminated() bool {
56✔
362
        // If the payment is in terminal state, it cannot be updated.
56✔
363
        return m.Status.updatable() != nil
56✔
364
}
56✔
365

366
// TerminalInfo returns any HTLC settle info recorded. If no settle info is
367
// recorded, any payment level failure will be returned. If neither a settle
368
// nor a failure is recorded, both return values will be nil.
369
func (m *MPPayment) TerminalInfo() (*HTLCAttempt, *FailureReason) {
741✔
370
        for _, h := range m.HTLCs {
1,493✔
371
                if h.Settle != nil {
862✔
372
                        return &h, nil
110✔
373
                }
110✔
374
        }
375

376
        return nil, m.FailureReason
634✔
377
}
378

379
// SentAmt returns the sum of sent amount and fees for HTLCs that are either
380
// settled or still in flight.
381
func (m *MPPayment) SentAmt() (lnwire.MilliSatoshi, lnwire.MilliSatoshi) {
783✔
382
        var sent, fees lnwire.MilliSatoshi
783✔
383
        for _, h := range m.HTLCs {
1,603✔
384
                if h.Failure != nil {
1,177✔
385
                        continue
357✔
386
                }
387

388
                // The attempt was not failed, meaning the amount was
389
                // potentially sent to the receiver.
390
                sent += h.Route.ReceiverAmt()
466✔
391
                fees += h.Route.TotalFees()
466✔
392
        }
393

394
        return sent, fees
783✔
395
}
396

397
// InFlightHTLCs returns the HTLCs that are still in-flight, meaning they have
398
// not been settled or failed.
399
func (m *MPPayment) InFlightHTLCs() []HTLCAttempt {
802✔
400
        var inflights []HTLCAttempt
802✔
401
        for _, h := range m.HTLCs {
1,629✔
402
                if h.Settle != nil || h.Failure != nil {
1,281✔
403
                        continue
454✔
404
                }
405

406
                inflights = append(inflights, h)
376✔
407
        }
408

409
        return inflights
802✔
410
}
411

412
// GetAttempt returns the specified htlc attempt on the payment.
413
func (m *MPPayment) GetAttempt(id uint64) (*HTLCAttempt, error) {
9✔
414
        // TODO(yy): iteration can be slow, make it into a tree or use BS.
9✔
415
        for _, htlc := range m.HTLCs {
18✔
416
                htlc := htlc
9✔
417
                if htlc.AttemptID == id {
18✔
418
                        return &htlc, nil
9✔
419
                }
9✔
420
        }
421

422
        return nil, errors.New("htlc attempt not found on payment")
×
423
}
424

425
// Registrable returns an error to specify whether adding more HTLCs to the
426
// payment with its current status is allowed. A payment can accept new HTLC
427
// registrations when it's newly created, or none of its HTLCs is in a terminal
428
// state.
429
func (m *MPPayment) Registrable() error {
130✔
430
        // If updating the payment is not allowed, we can't register new HTLCs.
130✔
431
        // Otherwise, the status must be either `StatusInitiated` or
130✔
432
        // `StatusInFlight`.
130✔
433
        if err := m.Status.updatable(); err != nil {
149✔
434
                return err
19✔
435
        }
19✔
436

437
        // Exit early if this is not inflight.
438
        if m.Status != StatusInFlight {
163✔
439
                return nil
49✔
440
        }
49✔
441

442
        // There are still inflight HTLCs and we need to check whether there
443
        // are settled HTLCs or the payment is failed. If we already have
444
        // settled HTLCs, we won't allow adding more HTLCs.
445
        if m.State.HasSettledHTLC {
75✔
446
                return ErrPaymentPendingSettled
7✔
447
        }
7✔
448

449
        // If the payment is already failed, we won't allow adding more HTLCs.
450
        if m.State.PaymentFailed {
67✔
451
                return ErrPaymentPendingFailed
6✔
452
        }
6✔
453

454
        // Otherwise we can add more HTLCs.
455
        return nil
55✔
456
}
457

458
// setState creates and attaches a new MPPaymentState to the payment. It also
459
// updates the payment's status based on its current state.
460
func (m *MPPayment) setState() error {
721✔
461
        // Fetch the total amount and fees that has already been sent in
721✔
462
        // settled and still in-flight shards.
721✔
463
        sentAmt, fees := m.SentAmt()
721✔
464

721✔
465
        // Sanity check we haven't sent a value larger than the payment amount.
721✔
466
        totalAmt := m.Info.Value
721✔
467
        if sentAmt > totalAmt {
722✔
468
                return fmt.Errorf("%w: sent=%v, total=%v",
1✔
469
                        ErrSentExceedsTotal, sentAmt, totalAmt)
1✔
470
        }
1✔
471

472
        // Get any terminal info for this payment.
473
        settle, failure := m.TerminalInfo()
720✔
474

720✔
475
        // Now determine the payment's status.
720✔
476
        status, err := decidePaymentStatus(m.HTLCs, m.FailureReason)
720✔
477
        if err != nil {
720✔
478
                return err
×
479
        }
×
480

481
        // Update the payment state and status.
482
        m.State = &MPPaymentState{
720✔
483
                NumAttemptsInFlight: len(m.InFlightHTLCs()),
720✔
484
                RemainingAmt:        totalAmt - sentAmt,
720✔
485
                FeesPaid:            fees,
720✔
486
                HasSettledHTLC:      settle != nil,
720✔
487
                PaymentFailed:       failure != nil,
720✔
488
        }
720✔
489
        m.Status = status
720✔
490

720✔
491
        return nil
720✔
492
}
493

494
// SetState calls the internal method setState. This is a temporary method
495
// to be used by the tests in routing. Once the tests are updated to use mocks,
496
// this method can be removed.
497
//
498
// TODO(yy): delete.
499
func (m *MPPayment) SetState() error {
74✔
500
        return m.setState()
74✔
501
}
74✔
502

503
// NeedWaitAttempts decides whether we need to hold creating more HTLC attempts
504
// and wait for the results of the payment's inflight HTLCs. Return an error if
505
// the payment is in an unexpected state.
506
func (m *MPPayment) NeedWaitAttempts() (bool, error) {
48✔
507
        // Check when the remainingAmt is not zero, which means we have more
48✔
508
        // money to be sent.
48✔
509
        if m.State.RemainingAmt != 0 {
60✔
510
                switch m.Status {
12✔
511
                // If the payment is newly created, no need to wait for HTLC
512
                // results.
513
                case StatusInitiated:
1✔
514
                        return false, nil
1✔
515

516
                // If we have inflight HTLCs, we'll check if we have terminal
517
                // states to decide if we need to wait.
518
                case StatusInFlight:
3✔
519
                        // We still have money to send, and one of the HTLCs is
3✔
520
                        // settled. We'd stop sending money and wait for all
3✔
521
                        // inflight HTLC attempts to finish.
3✔
522
                        if m.State.HasSettledHTLC {
4✔
523
                                log.Warnf("payment=%v has remaining amount "+
1✔
524
                                        "%v, yet at least one of its HTLCs is "+
1✔
525
                                        "settled", m.Info.PaymentIdentifier,
1✔
526
                                        m.State.RemainingAmt)
1✔
527

1✔
528
                                return true, nil
1✔
529
                        }
1✔
530

531
                        // The payment has a failure reason though we still
532
                        // have money to send, we'd stop sending money and wait
533
                        // for all inflight HTLC attempts to finish.
534
                        if m.State.PaymentFailed {
3✔
535
                                return true, nil
1✔
536
                        }
1✔
537

538
                        // Otherwise we don't need to wait for inflight HTLCs
539
                        // since we still have money to be sent.
540
                        return false, nil
1✔
541

542
                // We need to send more money, yet the payment is already
543
                // succeeded. Return an error in this case as the receiver is
544
                // violating the protocol.
545
                case StatusSucceeded:
1✔
546
                        return false, fmt.Errorf("%w: parts of the payment "+
1✔
547
                                "already succeeded but still have remaining "+
1✔
548
                                "amount %v", ErrPaymentInternal,
1✔
549
                                m.State.RemainingAmt)
1✔
550

551
                // The payment is failed and we have no inflight HTLCs, no need
552
                // to wait.
553
                case StatusFailed:
6✔
554
                        return false, nil
6✔
555

556
                // Unknown payment status.
557
                default:
1✔
558
                        return false, fmt.Errorf("%w: %s",
1✔
559
                                ErrUnknownPaymentStatus, m.Status)
1✔
560
                }
561
        }
562

563
        // Now we determine whether we need to wait when the remainingAmt is
564
        // already zero.
565
        switch m.Status {
39✔
566
        // When the payment is newly created, yet the payment has no remaining
567
        // amount, return an error.
568
        case StatusInitiated:
1✔
569
                return false, fmt.Errorf("%w: %v",
1✔
570
                        ErrPaymentInternal, m.Status)
1✔
571

572
        // If the payment is inflight, we must wait.
573
        //
574
        // NOTE: an edge case is when all HTLCs are failed while the payment is
575
        // not failed we'd still be in this inflight state. However, since the
576
        // remainingAmt is zero here, it means we cannot be in that state as
577
        // otherwise the remainingAmt would not be zero.
578
        case StatusInFlight:
25✔
579
                return true, nil
25✔
580

581
        // If the payment is already succeeded, no need to wait.
582
        case StatusSucceeded:
14✔
583
                return false, nil
14✔
584

585
        // If the payment is already failed, yet the remaining amount is zero,
586
        // return an error as this indicates an error state. We will only each
587
        // this status when there are no inflight HTLCs and the payment is
588
        // marked as failed with a reason, which means the remainingAmt must
589
        // not be zero because our sentAmt is zero.
590
        case StatusFailed:
1✔
591
                return false, fmt.Errorf("%w: %v",
1✔
592
                        ErrPaymentInternal, m.Status)
1✔
593

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

601
// GetState returns the internal state of the payment.
602
func (m *MPPayment) GetState() *MPPaymentState {
60✔
603
        return m.State
60✔
604
}
60✔
605

606
// GetStatus returns the current status of the payment.
607
func (m *MPPayment) GetStatus() PaymentStatus {
161✔
608
        return m.Status
161✔
609
}
161✔
610

611
// GetHTLCs returns all the HTLCs for this payment.
612
func (m *MPPayment) GetHTLCs() []HTLCAttempt {
1✔
613
        return m.HTLCs
1✔
614
}
1✔
615

616
// AllowMoreAttempts is used to decide whether we can safely attempt more HTLCs
617
// for a given payment state. Return an error if the payment is in an
618
// unexpected state.
619
func (m *MPPayment) AllowMoreAttempts() (bool, error) {
75✔
620
        // Now check whether the remainingAmt is zero or not. If we don't have
75✔
621
        // any remainingAmt, no more HTLCs should be made.
75✔
622
        if m.State.RemainingAmt == 0 {
116✔
623
                // If the payment is newly created, yet we don't have any
41✔
624
                // remainingAmt, return an error.
41✔
625
                if m.Status == StatusInitiated {
42✔
626
                        return false, fmt.Errorf("%w: initiated payment has "+
1✔
627
                                "zero remainingAmt",
1✔
628
                                ErrPaymentInternal)
1✔
629
                }
1✔
630

631
                // Otherwise, exit early since all other statuses with zero
632
                // remainingAmt indicate no more HTLCs can be made.
633
                return false, nil
40✔
634
        }
635

636
        // Otherwise, the remaining amount is not zero, we now decide whether
637
        // to make more attempts based on the payment's current status.
638
        //
639
        // If at least one of the payment's attempts is settled, yet we haven't
640
        // sent all the amount, it indicates something is wrong with the peer
641
        // as the preimage is received. In this case, return an error state.
642
        if m.Status == StatusSucceeded {
38✔
643
                return false, fmt.Errorf("%w: payment already succeeded but "+
1✔
644
                        "still have remaining amount %v",
1✔
645
                        ErrPaymentInternal, m.State.RemainingAmt)
1✔
646
        }
1✔
647

648
        // Now check if we can register a new HTLC.
649
        err := m.Registrable()
36✔
650
        if err != nil {
46✔
651
                log.Warnf("Payment(%v): cannot register HTLC attempt: %v, "+
10✔
652
                        "current status: %s", m.Info.PaymentIdentifier,
10✔
653
                        err, m.Status)
10✔
654

10✔
655
                return false, nil
10✔
656
        }
10✔
657

658
        // Now we know we can register new HTLCs.
659
        return true, nil
29✔
660
}
661

662
// generateSphinxPacket generates then encodes a sphinx packet which encodes
663
// the onion route specified by the passed layer 3 route. The blob returned
664
// from this function can immediately be included within an HTLC add packet to
665
// be sent to the first hop within the route.
666
func generateSphinxPacket(rt *route.Route, paymentHash []byte,
667
        sessionKey *btcec.PrivateKey) ([]byte, *sphinx.Circuit, error) {
98✔
668

98✔
669
        // Now that we know we have an actual route, we'll map the route into a
98✔
670
        // sphinx payment path which includes per-hop payloads for each hop
98✔
671
        // that give each node within the route the necessary information
98✔
672
        // (fees, CLTV value, etc.) to properly forward the payment.
98✔
673
        sphinxPath, err := rt.ToSphinxPath()
98✔
674
        if err != nil {
100✔
675
                return nil, nil, err
2✔
676
        }
2✔
677

678
        log.Tracef("Constructed per-hop payloads for payment_hash=%x: %v",
96✔
679
                paymentHash, lnutils.NewLogClosure(func() string {
96✔
680
                        path := make(
×
681
                                []sphinx.OnionHop, sphinxPath.TrueRouteLength(),
×
682
                        )
×
683
                        for i := range path {
×
684
                                hopCopy := sphinxPath[i]
×
685
                                path[i] = hopCopy
×
686
                        }
×
687

688
                        return spew.Sdump(path)
×
689
                }),
690
        )
691

692
        // Next generate the onion routing packet which allows us to perform
693
        // privacy preserving source routing across the network.
694
        sphinxPacket, err := sphinx.NewOnionPacket(
96✔
695
                sphinxPath, sessionKey, paymentHash,
96✔
696
                sphinx.DeterministicPacketFiller,
96✔
697
        )
96✔
698
        if err != nil {
96✔
699
                return nil, nil, err
×
700
        }
×
701

702
        // Finally, encode Sphinx packet using its wire representation to be
703
        // included within the HTLC add packet.
704
        var onionBlob bytes.Buffer
96✔
705
        if err := sphinxPacket.Encode(&onionBlob); err != nil {
96✔
706
                return nil, nil, err
×
707
        }
×
708

709
        log.Tracef("Generated sphinx packet: %v",
96✔
710
                lnutils.NewLogClosure(func() string {
96✔
711
                        // We make a copy of the ephemeral key and unset the
×
712
                        // internal curve here in order to keep the logs from
×
713
                        // getting noisy.
×
714
                        key := *sphinxPacket.EphemeralKey
×
715
                        packetCopy := *sphinxPacket
×
716
                        packetCopy.EphemeralKey = &key
×
717

×
718
                        return spew.Sdump(packetCopy)
×
719
                }),
×
720
        )
721

722
        return onionBlob.Bytes(), &sphinx.Circuit{
96✔
723
                SessionKey:  sessionKey,
96✔
724
                PaymentPath: sphinxPath.NodeKeys(),
96✔
725
        }, nil
96✔
726
}
727

728
// verifyAttempt validates that a new HTLC attempt is compatible with the
729
// existing payment and its in-flight HTLCs. This function checks:
730
//  1. MPP (Multi-Path Payment) compatibility between attempts
731
//  2. Blinded payment consistency
732
//  3. Amount validation
733
//  4. Total payment amount limits
734
func verifyAttempt(payment *MPPayment, attempt *HTLCAttemptInfo) error {
73✔
735
        // If the final hop has encrypted data, then we know this is a
73✔
736
        // blinded payment. In blinded payments, MPP records are not set
73✔
737
        // for split payments and the recipient is responsible for using
73✔
738
        // a consistent PathID across the various encrypted data
73✔
739
        // payloads that we received from them for this payment. All we
73✔
740
        // need to check is that the total amount field for each HTLC
73✔
741
        // in the split payment is correct.
73✔
742
        isBlinded := len(attempt.Route.FinalHop().EncryptedData) != 0
73✔
743

73✔
744
        // Make sure any existing shards match the new one with regards
73✔
745
        // to MPP options.
73✔
746
        mpp := attempt.Route.FinalHop().MPP
73✔
747

73✔
748
        // MPP records should not be set for attempts to blinded paths.
73✔
749
        if isBlinded && mpp != nil {
73✔
NEW
750
                return ErrMPPRecordInBlindedPayment
×
NEW
751
        }
×
752

753
        for _, h := range payment.InFlightHTLCs() {
120✔
754
                hMpp := h.Route.FinalHop().MPP
47✔
755

47✔
756
                // If this is a blinded payment, then no existing HTLCs
47✔
757
                // should have MPP records.
47✔
758
                if isBlinded && hMpp != nil {
47✔
NEW
759
                        return ErrMPPRecordInBlindedPayment
×
NEW
760
                }
×
761

762
                // If this is a blinded payment, then we just need to
763
                // check that the TotalAmtMsat field for this shard
764
                // is equal to that of any other shard in the same
765
                // payment.
766
                if isBlinded {
50✔
767
                        if attempt.Route.FinalHop().TotalAmtMsat !=
3✔
768
                                h.Route.FinalHop().TotalAmtMsat {
3✔
NEW
769

×
NEW
770
                                return ErrBlindedPaymentTotalAmountMismatch
×
NEW
771
                        }
×
772

773
                        continue
3✔
774
                }
775

776
                switch {
47✔
777
                // We tried to register a non-MPP attempt for a MPP
778
                // payment.
779
                case mpp == nil && hMpp != nil:
2✔
780
                        return ErrMPPayment
2✔
781

782
                // We tried to register a MPP shard for a non-MPP
783
                // payment.
784
                case mpp != nil && hMpp == nil:
2✔
785
                        return ErrNonMPPayment
2✔
786

787
                // Non-MPP payment, nothing more to validate.
NEW
788
                case mpp == nil:
×
NEW
789
                        continue
×
790
                }
791

792
                // Check that MPP options match.
793
                if mpp.PaymentAddr() != hMpp.PaymentAddr() {
45✔
794
                        return ErrMPPPaymentAddrMismatch
2✔
795
                }
2✔
796

797
                if mpp.TotalMsat() != hMpp.TotalMsat() {
43✔
798
                        return ErrMPPTotalAmountMismatch
2✔
799
                }
2✔
800
        }
801

802
        // If this is a non-MPP attempt, it must match the total amount
803
        // exactly. Note that a blinded payment is considered an MPP
804
        // attempt.
805
        amt := attempt.Route.ReceiverAmt()
65✔
806
        if !isBlinded && mpp == nil && amt != payment.Info.Value {
65✔
NEW
807
                return ErrValueMismatch
×
NEW
808
        }
×
809

810
        // Ensure we aren't sending more than the total payment amount.
811
        sentAmt, _ := payment.SentAmt()
65✔
812
        if sentAmt+amt > payment.Info.Value {
73✔
813
                return fmt.Errorf("%w: attempted=%v, payment amount=%v",
8✔
814
                        ErrValueExceedsAmt, sentAmt+amt, payment.Info.Value)
8✔
815
        }
8✔
816

817
        return nil
57✔
818
}
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