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

lightningnetwork / lnd / 13035292482

29 Jan 2025 03:59PM UTC coverage: 49.3% (-9.5%) from 58.777%
13035292482

Pull #9456

github

mohamedawnallah
docs: update release-notes-0.19.0.md

In this commit, we warn users about the removal
of RPCs `SendToRoute`, `SendToRouteSync`, `SendPayment`,
and `SendPaymentSync` in the next release 0.20.
Pull Request #9456: lnrpc+docs: deprecate warning `SendToRoute`, `SendToRouteSync`, `SendPayment`, and `SendPaymentSync` in Release 0.19

100634 of 204126 relevant lines covered (49.3%)

1.54 hits per line

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

63.05
/invoices/update_invoice.go
1
package invoices
2

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

8
        "github.com/lightningnetwork/lnd/graph/db/models"
9
        "github.com/lightningnetwork/lnd/lntypes"
10
        "github.com/lightningnetwork/lnd/lnwire"
11
)
12

13
// updateHtlcsAmp takes an invoice, and a new HTLC to be added (along with its
14
// set ID), and updates the internal AMP state of an invoice, and also tallies
15
// the set of HTLCs to be updated on disk.
16
func acceptHtlcsAmp(invoice *Invoice, setID SetID,
17
        circuitKey models.CircuitKey, htlc *InvoiceHTLC,
18
        updater InvoiceUpdater) error {
3✔
19

3✔
20
        newAmpState, err := getUpdatedInvoiceAmpState(
3✔
21
                invoice, setID, circuitKey, HtlcStateAccepted, htlc.Amt,
3✔
22
        )
3✔
23
        if err != nil {
3✔
24
                return err
×
25
        }
×
26

27
        invoice.AMPState[setID] = newAmpState
3✔
28

3✔
29
        // Mark the updates as needing to be written to disk.
3✔
30
        return updater.UpdateAmpState(setID, newAmpState, circuitKey)
3✔
31
}
32

33
// cancelHtlcsAmp processes a cancellation of an HTLC that belongs to an AMP
34
// HTLC set. We'll need to update the meta data in the  main invoice, and also
35
// apply the new update to the update MAP, since all the HTLCs for a given HTLC
36
// set need to be written in-line with each other.
37
func cancelHtlcsAmp(invoice *Invoice, circuitKey models.CircuitKey,
38
        htlc *InvoiceHTLC, updater InvoiceUpdater) error {
×
39

×
40
        setID := htlc.AMP.Record.SetID()
×
41

×
42
        // First, we'll update the state of the entire HTLC set
×
43
        // to cancelled.
×
44
        newAmpState, err := getUpdatedInvoiceAmpState(
×
45
                invoice, setID, circuitKey, HtlcStateCanceled,
×
46
                htlc.Amt,
×
47
        )
×
48
        if err != nil {
×
49
                return err
×
50
        }
×
51

52
        invoice.AMPState[setID] = newAmpState
×
53

×
54
        // Mark the updates as needing to be written to disk.
×
55
        err = updater.UpdateAmpState(setID, newAmpState, circuitKey)
×
56
        if err != nil {
×
57
                return err
×
58
        }
×
59

60
        // We'll only decrement the total amount paid if the invoice was
61
        // already in the accepted state.
62
        if invoice.AmtPaid != 0 {
×
63
                return updateInvoiceAmtPaid(
×
64
                        invoice, invoice.AmtPaid-htlc.Amt, updater,
×
65
                )
×
66
        }
×
67

68
        return nil
×
69
}
70

71
// settleHtlcsAmp processes a new settle operation on an HTLC set for an AMP
72
// invoice. We'll update some meta data in the main invoice, and also signal
73
// that this HTLC set needs to be re-written back to disk.
74
func settleHtlcsAmp(invoice *Invoice, circuitKey models.CircuitKey,
75
        htlc *InvoiceHTLC, updater InvoiceUpdater) error {
3✔
76

3✔
77
        setID := htlc.AMP.Record.SetID()
3✔
78

3✔
79
        // Next update the main AMP meta-data to indicate that this HTLC set
3✔
80
        // has been fully settled.
3✔
81
        newAmpState, err := getUpdatedInvoiceAmpState(
3✔
82
                invoice, setID, circuitKey, HtlcStateSettled, 0,
3✔
83
        )
3✔
84
        if err != nil {
3✔
85
                return err
×
86
        }
×
87

88
        invoice.AMPState[setID] = newAmpState
3✔
89

3✔
90
        // Mark the updates as needing to be written to disk.
3✔
91
        return updater.UpdateAmpState(setID, newAmpState, circuitKey)
3✔
92
}
93

94
// UpdateInvoice fetches the invoice, obtains the update descriptor from the
95
// callback and applies the updates in a single db transaction.
96
func UpdateInvoice(hash *lntypes.Hash, invoice *Invoice,
97
        updateTime time.Time, callback InvoiceUpdateCallback,
98
        updater InvoiceUpdater) (*Invoice, error) {
3✔
99

3✔
100
        // Create deep copy to prevent any accidental modification in the
3✔
101
        // callback.
3✔
102
        invoiceCopy, err := CopyInvoice(invoice)
3✔
103
        if err != nil {
3✔
104
                return nil, err
×
105
        }
×
106

107
        // Call the callback and obtain the update descriptor.
108
        update, err := callback(invoiceCopy)
3✔
109
        if err != nil {
3✔
110
                return invoice, err
×
111
        }
×
112

113
        // If there is nothing to update, return early.
114
        if update == nil {
6✔
115
                return invoice, nil
3✔
116
        }
3✔
117

118
        switch update.UpdateType {
3✔
119
        case CancelHTLCsUpdate:
×
120
                err := cancelHTLCs(invoice, updateTime, update, updater)
×
121
                if err != nil {
×
122
                        return nil, err
×
123
                }
×
124

125
        case AddHTLCsUpdate:
3✔
126
                err := addHTLCs(invoice, hash, updateTime, update, updater)
3✔
127
                if err != nil {
3✔
128
                        return nil, err
×
129
                }
×
130

131
        case SettleHodlInvoiceUpdate:
3✔
132
                err := settleHodlInvoice(
3✔
133
                        invoice, hash, updateTime, update.State, updater,
3✔
134
                )
3✔
135
                if err != nil {
3✔
136
                        return nil, err
×
137
                }
×
138

139
        case CancelInvoiceUpdate:
3✔
140
                err := cancelInvoice(
3✔
141
                        invoice, hash, updateTime, update.State, updater,
3✔
142
                )
3✔
143
                if err != nil {
6✔
144
                        return nil, err
3✔
145
                }
3✔
146

147
        default:
×
148
                return nil, fmt.Errorf("unknown update type: %s",
×
149
                        update.UpdateType)
×
150
        }
151

152
        if err := updater.Finalize(update.UpdateType); err != nil {
3✔
153
                return nil, err
×
154
        }
×
155

156
        return invoice, nil
3✔
157
}
158

159
// cancelHTLCs tries to cancel the htlcs in the given InvoiceUpdateDesc.
160
//
161
// NOTE: cancelHTLCs updates will only use the `CancelHtlcs` field in the
162
// InvoiceUpdateDesc.
163
func cancelHTLCs(invoice *Invoice, updateTime time.Time,
164
        update *InvoiceUpdateDesc, updater InvoiceUpdater) error {
×
165

×
166
        for key := range update.CancelHtlcs {
×
167
                htlc, exists := invoice.Htlcs[key]
×
168

×
169
                // Verify that we don't get an action for htlcs that are not
×
170
                // present on the invoice.
×
171
                if !exists {
×
172
                        return fmt.Errorf("cancel of non-existent htlc")
×
173
                }
×
174

175
                err := canCancelSingleHtlc(htlc, invoice.State)
×
176
                if err != nil {
×
177
                        return err
×
178
                }
×
179

180
                err = resolveHtlc(
×
181
                        key, htlc, HtlcStateCanceled, updateTime,
×
182
                        updater,
×
183
                )
×
184
                if err != nil {
×
185
                        return err
×
186
                }
×
187

188
                // Tally this into the set of HTLCs that need to be updated on
189
                // disk, but once again, only if this is an AMP invoice.
190
                if invoice.IsAMP() {
×
191
                        err := cancelHtlcsAmp(invoice, key, htlc, updater)
×
192
                        if err != nil {
×
193
                                return err
×
194
                        }
×
195
                }
196
        }
197

198
        return nil
×
199
}
200

201
// addHTLCs tries to add the htlcs in the given InvoiceUpdateDesc.
202
//
203
//nolint:funlen
204
func addHTLCs(invoice *Invoice, hash *lntypes.Hash, updateTime time.Time,
205
        update *InvoiceUpdateDesc, updater InvoiceUpdater) error {
3✔
206

3✔
207
        var setID *[32]byte
3✔
208
        invoiceIsAMP := invoice.IsAMP()
3✔
209
        if invoiceIsAMP && update.State != nil {
6✔
210
                setID = update.State.SetID
3✔
211
        }
3✔
212

213
        for key, htlcUpdate := range update.AddHtlcs {
6✔
214
                if _, exists := invoice.Htlcs[key]; exists {
3✔
215
                        return fmt.Errorf("duplicate add of htlc %v", key)
×
216
                }
×
217

218
                // Force caller to supply htlc without custom records in a
219
                // consistent way.
220
                if htlcUpdate.CustomRecords == nil {
3✔
221
                        return errors.New("nil custom records map")
×
222
                }
×
223

224
                htlc := &InvoiceHTLC{
3✔
225
                        Amt:           htlcUpdate.Amt,
3✔
226
                        MppTotalAmt:   htlcUpdate.MppTotalAmt,
3✔
227
                        Expiry:        htlcUpdate.Expiry,
3✔
228
                        AcceptHeight:  uint32(htlcUpdate.AcceptHeight),
3✔
229
                        AcceptTime:    updateTime,
3✔
230
                        State:         HtlcStateAccepted,
3✔
231
                        CustomRecords: htlcUpdate.CustomRecords,
3✔
232
                }
3✔
233

3✔
234
                if invoiceIsAMP {
6✔
235
                        if htlcUpdate.AMP == nil {
3✔
236
                                return fmt.Errorf("unable to add htlc "+
×
237
                                        "without AMP data to AMP invoice(%v)",
×
238
                                        invoice.AddIndex)
×
239
                        }
×
240

241
                        htlc.AMP = htlcUpdate.AMP.Copy()
3✔
242
                }
243

244
                if err := updater.AddHtlc(key, htlc); err != nil {
3✔
245
                        return err
×
246
                }
×
247

248
                invoice.Htlcs[key] = htlc
3✔
249

3✔
250
                // Collect the set of new HTLCs so we can write them properly
3✔
251
                // below, but only if this is an AMP invoice.
3✔
252
                if invoiceIsAMP {
6✔
253
                        err := acceptHtlcsAmp(
3✔
254
                                invoice, htlcUpdate.AMP.Record.SetID(), key,
3✔
255
                                htlc, updater,
3✔
256
                        )
3✔
257
                        if err != nil {
3✔
258
                                return err
×
259
                        }
×
260
                }
261
        }
262

263
        // At this point, the set of accepted HTLCs should be fully
264
        // populated with added HTLCs or removed of canceled ones. Update
265
        // invoice state if the update descriptor indicates an invoice state
266
        // change, which depends on having an accurate view of the accepted
267
        // HTLCs.
268
        if update.State != nil {
6✔
269
                newState, err := getUpdatedInvoiceState(
3✔
270
                        invoice, hash, *update.State,
3✔
271
                )
3✔
272
                if err != nil {
3✔
273
                        return err
×
274
                }
×
275

276
                // If this isn't an AMP invoice, then we'll go ahead and update
277
                // the invoice state directly here. For AMP invoices, we instead
278
                // will keep the top-level invoice open, and update the state of
279
                // each _htlc set_ instead. However, we'll allow the invoice to
280
                // transition to the cancelled state regardless.
281
                if !invoiceIsAMP || *newState == ContractCanceled {
6✔
282
                        err := updater.UpdateInvoiceState(*newState, nil)
3✔
283
                        if err != nil {
3✔
284
                                return err
×
285
                        }
×
286
                        invoice.State = *newState
3✔
287
                }
288
        }
289

290
        // The set of HTLC pre-images will only be set if we were actually able
291
        // to reconstruct all the AMP pre-images.
292
        var settleEligibleAMP bool
3✔
293
        if update.State != nil {
6✔
294
                settleEligibleAMP = len(update.State.HTLCPreimages) != 0
3✔
295
        }
3✔
296

297
        // With any invoice level state transitions recorded, we'll now
298
        // finalize the process by updating the state transitions for
299
        // individual HTLCs
300
        var amtPaid lnwire.MilliSatoshi
3✔
301

3✔
302
        for key, htlc := range invoice.Htlcs {
6✔
303
                // Set the HTLC preimage for any AMP HTLCs.
3✔
304
                if setID != nil && update.State != nil {
6✔
305
                        preimage, ok := update.State.HTLCPreimages[key]
3✔
306
                        switch {
3✔
307
                        // If we don't already have a preimage for this HTLC, we
308
                        // can set it now.
309
                        case ok && htlc.AMP.Preimage == nil:
3✔
310
                                err := updater.AddAmpHtlcPreimage(
3✔
311
                                        htlc.AMP.Record.SetID(), key, preimage,
3✔
312
                                )
3✔
313
                                if err != nil {
3✔
314
                                        return err
×
315
                                }
×
316
                                htlc.AMP.Preimage = &preimage
3✔
317

318
                        // Otherwise, prevent over-writing an existing
319
                        // preimage.  Ignore the case where the preimage is
320
                        // identical.
321
                        case ok && *htlc.AMP.Preimage != preimage:
×
322
                                return ErrHTLCPreimageAlreadyExists
×
323
                        }
324
                }
325

326
                // The invoice state may have changed and this could have
327
                // implications for the states of the individual htlcs. Align
328
                // the htlc state with the current invoice state.
329
                //
330
                // If we have all the pre-images for an AMP invoice, then we'll
331
                // act as if we're able to settle the entire invoice. We need
332
                // to do this since it's possible for us to settle AMP invoices
333
                // while the contract state (on disk) is still in the accept
334
                // state.
335
                htlcContextState := invoice.State
3✔
336
                if settleEligibleAMP {
6✔
337
                        htlcContextState = ContractSettled
3✔
338
                }
3✔
339
                htlcStateChanged, htlcState, err := getUpdatedHtlcState(
3✔
340
                        htlc, htlcContextState, setID,
3✔
341
                )
3✔
342
                if err != nil {
3✔
343
                        return err
×
344
                }
×
345

346
                if htlcStateChanged {
6✔
347
                        err = resolveHtlc(
3✔
348
                                key, htlc, htlcState, updateTime, updater,
3✔
349
                        )
3✔
350
                        if err != nil {
3✔
351
                                return err
×
352
                        }
×
353
                }
354

355
                htlcSettled := htlcStateChanged &&
3✔
356
                        htlcState == HtlcStateSettled
3✔
357

3✔
358
                // If the HTLC has being settled for the first time, and this
3✔
359
                // is an AMP invoice, then we'll need to update some additional
3✔
360
                // meta data state.
3✔
361
                if htlcSettled && invoiceIsAMP {
6✔
362
                        err = settleHtlcsAmp(invoice, key, htlc, updater)
3✔
363
                        if err != nil {
3✔
364
                                return err
×
365
                        }
×
366
                }
367

368
                accepted := htlc.State == HtlcStateAccepted
3✔
369
                settled := htlc.State == HtlcStateSettled
3✔
370
                invoiceStateReady := accepted || settled
3✔
371

3✔
372
                if !invoiceIsAMP {
6✔
373
                        // Update the running amount paid to this invoice. We
3✔
374
                        // don't include accepted htlcs when the invoice is
3✔
375
                        // still open.
3✔
376
                        if invoice.State != ContractOpen &&
3✔
377
                                invoiceStateReady {
6✔
378

3✔
379
                                amtPaid += htlc.Amt
3✔
380
                        }
3✔
381
                } else {
3✔
382
                        // For AMP invoices, since we won't always be reading
3✔
383
                        // out the total invoice set each time, we'll instead
3✔
384
                        // accumulate newly added invoices to the total amount
3✔
385
                        // paid.
3✔
386
                        if _, ok := update.AddHtlcs[key]; !ok {
6✔
387
                                continue
3✔
388
                        }
389

390
                        // Update the running amount paid to this invoice. AMP
391
                        // invoices never go to the settled state, so if it's
392
                        // open, then we tally the HTLC.
393
                        if invoice.State == ContractOpen &&
3✔
394
                                invoiceStateReady {
6✔
395

3✔
396
                                amtPaid += htlc.Amt
3✔
397
                        }
3✔
398
                }
399
        }
400

401
        // For non-AMP invoices we recalculate the amount paid from scratch
402
        // each time, while for AMP invoices, we'll accumulate only based on
403
        // newly added HTLCs.
404
        if invoiceIsAMP {
6✔
405
                amtPaid += invoice.AmtPaid
3✔
406
        }
3✔
407

408
        return updateInvoiceAmtPaid(invoice, amtPaid, updater)
3✔
409
}
410

411
func resolveHtlc(circuitKey models.CircuitKey, htlc *InvoiceHTLC,
412
        state HtlcState, resolveTime time.Time,
413
        updater InvoiceUpdater) error {
3✔
414

3✔
415
        err := updater.ResolveHtlc(circuitKey, state, resolveTime)
3✔
416
        if err != nil {
3✔
417
                return err
×
418
        }
×
419
        htlc.State = state
3✔
420
        htlc.ResolveTime = resolveTime
3✔
421

3✔
422
        return nil
3✔
423
}
424

425
func updateInvoiceAmtPaid(invoice *Invoice, amt lnwire.MilliSatoshi,
426
        updater InvoiceUpdater) error {
3✔
427

3✔
428
        err := updater.UpdateInvoiceAmtPaid(amt)
3✔
429
        if err != nil {
3✔
430
                return err
×
431
        }
×
432
        invoice.AmtPaid = amt
3✔
433

3✔
434
        return nil
3✔
435
}
436

437
// settleHodlInvoice marks a hodl invoice as settled.
438
//
439
// NOTE: Currently it is not possible to have HODL AMP invoices.
440
func settleHodlInvoice(invoice *Invoice, hash *lntypes.Hash,
441
        updateTime time.Time, update *InvoiceStateUpdateDesc,
442
        updater InvoiceUpdater) error {
3✔
443

3✔
444
        if !invoice.HodlInvoice {
3✔
445
                return fmt.Errorf("unable to settle hodl invoice: %v is "+
×
446
                        "not a hodl invoice", invoice.AddIndex)
×
447
        }
×
448

449
        // TODO(positiveblue): because NewState can only be ContractSettled we
450
        // can remove it from the API and set it here directly.
451
        switch {
3✔
452
        case update == nil:
×
453
                fallthrough
×
454

455
        case update.NewState != ContractSettled:
×
456
                return fmt.Errorf("unable to settle hodl invoice: "+
×
457
                        "not valid InvoiceUpdateDesc.State: %v", update)
×
458

459
        case update.Preimage == nil:
×
460
                return fmt.Errorf("unable to settle hodl invoice: " +
×
461
                        "preimage is nil")
×
462
        }
463

464
        newState, err := getUpdatedInvoiceState(
3✔
465
                invoice, hash, *update,
3✔
466
        )
3✔
467
        if err != nil {
3✔
468
                return err
×
469
        }
×
470

471
        if newState == nil || *newState != ContractSettled {
3✔
472
                return fmt.Errorf("unable to settle hodl invoice: "+
×
473
                        "new computed state is not settled: %s", newState)
×
474
        }
×
475

476
        err = updater.UpdateInvoiceState(
3✔
477
                ContractSettled, update.Preimage,
3✔
478
        )
3✔
479
        if err != nil {
3✔
480
                return err
×
481
        }
×
482

483
        invoice.State = ContractSettled
3✔
484
        invoice.Terms.PaymentPreimage = update.Preimage
3✔
485

3✔
486
        // TODO(positiveblue): this logic can be further simplified.
3✔
487
        var amtPaid lnwire.MilliSatoshi
3✔
488
        for key, htlc := range invoice.Htlcs {
6✔
489
                settled, _, err := getUpdatedHtlcState(
3✔
490
                        htlc, ContractSettled, nil,
3✔
491
                )
3✔
492
                if err != nil {
3✔
493
                        return err
×
494
                }
×
495

496
                if settled {
6✔
497
                        err = resolveHtlc(
3✔
498
                                key, htlc, HtlcStateSettled, updateTime,
3✔
499
                                updater,
3✔
500
                        )
3✔
501
                        if err != nil {
3✔
502
                                return err
×
503
                        }
×
504

505
                        amtPaid += htlc.Amt
3✔
506
                }
507
        }
508

509
        return updateInvoiceAmtPaid(invoice, amtPaid, updater)
3✔
510
}
511

512
// cancelInvoice attempts to cancel the given invoice. That includes changing
513
// the invoice state and the state of any relevant HTLC.
514
func cancelInvoice(invoice *Invoice, hash *lntypes.Hash,
515
        updateTime time.Time, update *InvoiceStateUpdateDesc,
516
        updater InvoiceUpdater) error {
3✔
517

3✔
518
        switch {
3✔
519
        case update == nil:
×
520
                fallthrough
×
521

522
        case update.NewState != ContractCanceled:
×
523
                return fmt.Errorf("unable to cancel invoice: "+
×
524
                        "InvoiceUpdateDesc.State not valid: %v", update)
×
525
        }
526

527
        var (
3✔
528
                setID        *[32]byte
3✔
529
                invoiceIsAMP bool
3✔
530
        )
3✔
531

3✔
532
        invoiceIsAMP = invoice.IsAMP()
3✔
533
        if invoiceIsAMP {
3✔
534
                setID = update.SetID
×
535
        }
×
536

537
        newState, err := getUpdatedInvoiceState(invoice, hash, *update)
3✔
538
        if err != nil {
6✔
539
                return err
3✔
540
        }
3✔
541

542
        if newState == nil || *newState != ContractCanceled {
3✔
543
                return fmt.Errorf("unable to cancel invoice(%v): new "+
×
544
                        "computed state is not canceled: %s", invoice.AddIndex,
×
545
                        newState)
×
546
        }
×
547

548
        err = updater.UpdateInvoiceState(ContractCanceled, nil)
3✔
549
        if err != nil {
3✔
550
                return err
×
551
        }
×
552
        invoice.State = ContractCanceled
3✔
553

3✔
554
        for key, htlc := range invoice.Htlcs {
6✔
555
                canceled, _, err := getUpdatedHtlcState(
3✔
556
                        htlc, ContractCanceled, setID,
3✔
557
                )
3✔
558
                if err != nil {
3✔
559
                        return err
×
560
                }
×
561

562
                if canceled {
6✔
563
                        err = resolveHtlc(
3✔
564
                                key, htlc, HtlcStateCanceled, updateTime,
3✔
565
                                updater,
3✔
566
                        )
3✔
567
                        if err != nil {
3✔
568
                                return err
×
569
                        }
×
570
                }
571
        }
572

573
        return nil
3✔
574
}
575

576
// getUpdatedInvoiceState validates and processes an invoice state update. The
577
// new state to transition to is returned, so the caller is able to select
578
// exactly how the invoice state is updated. Note that for AMP invoices this
579
// function is only used to validate the state transition if we're cancelling
580
// the invoice.
581
func getUpdatedInvoiceState(invoice *Invoice, hash *lntypes.Hash,
582
        update InvoiceStateUpdateDesc) (*ContractState, error) {
3✔
583

3✔
584
        // Returning to open is never allowed from any state.
3✔
585
        if update.NewState == ContractOpen {
3✔
586
                return nil, ErrInvoiceCannotOpen
×
587
        }
×
588

589
        switch invoice.State {
3✔
590
        // Once a contract is accepted, we can only transition to settled or
591
        // canceled. Forbid transitioning back into this state. Otherwise this
592
        // state is identical to ContractOpen, so we fallthrough to apply the
593
        // same checks that we apply to open invoices.
594
        case ContractAccepted:
3✔
595
                if update.NewState == ContractAccepted {
3✔
596
                        return nil, ErrInvoiceCannotAccept
×
597
                }
×
598

599
                fallthrough
3✔
600

601
        // If a contract is open, permit a state transition to accepted, settled
602
        // or canceled. The only restriction is on transitioning to settled
603
        // where we ensure the preimage is valid.
604
        case ContractOpen:
3✔
605
                if update.NewState == ContractCanceled {
6✔
606
                        return &update.NewState, nil
3✔
607
                }
3✔
608

609
                // Sanity check that the user isn't trying to settle or accept a
610
                // non-existent HTLC set.
611
                set := invoice.HTLCSet(update.SetID, HtlcStateAccepted)
3✔
612
                if len(set) == 0 {
3✔
613
                        return nil, ErrEmptyHTLCSet
×
614
                }
×
615

616
                // For AMP invoices, there are no invoice-level preimage checks.
617
                // However, we still sanity check that we aren't trying to
618
                // settle an AMP invoice with a preimage.
619
                if update.SetID != nil {
6✔
620
                        if update.Preimage != nil {
3✔
621
                                return nil, errors.New("AMP set cannot have " +
×
622
                                        "preimage")
×
623
                        }
×
624

625
                        return &update.NewState, nil
3✔
626
                }
627

628
                switch {
3✔
629
                // If an invoice-level preimage was supplied, but the InvoiceRef
630
                // doesn't specify a hash (e.g. AMP invoices) we fail.
631
                case update.Preimage != nil && hash == nil:
×
632
                        return nil, ErrUnexpectedInvoicePreimage
×
633

634
                // Validate the supplied preimage for non-AMP invoices.
635
                case update.Preimage != nil:
3✔
636
                        if update.Preimage.Hash() != *hash {
3✔
637
                                return nil, ErrInvoicePreimageMismatch
×
638
                        }
×
639

640
                // Permit non-AMP invoices to be accepted without knowing the
641
                // preimage. When trying to settle we'll have to pass through
642
                // the above check in order to not hit the one below.
643
                case update.NewState == ContractAccepted:
3✔
644

645
                // Fail if we still don't have a preimage when transitioning to
646
                // settle the non-AMP invoice.
647
                case update.NewState == ContractSettled &&
648
                        invoice.Terms.PaymentPreimage == nil:
×
649

×
650
                        return nil, errors.New("unknown preimage")
×
651
                }
652

653
                return &update.NewState, nil
3✔
654

655
        // Once settled, we are in a terminal state.
656
        case ContractSettled:
3✔
657
                return nil, ErrInvoiceAlreadySettled
3✔
658

659
        // Once canceled, we are in a terminal state.
660
        case ContractCanceled:
×
661
                return nil, ErrInvoiceAlreadyCanceled
×
662

663
        default:
×
664
                return nil, errors.New("unknown state transition")
×
665
        }
666
}
667

668
// getUpdatedInvoiceAmpState returns the AMP state of an invoice (without
669
// applying it), given the new state, and the amount of the HTLC that is
670
// being updated.
671
func getUpdatedInvoiceAmpState(invoice *Invoice, setID SetID,
672
        circuitKey models.CircuitKey, state HtlcState,
673
        amt lnwire.MilliSatoshi) (InvoiceStateAMP, error) {
3✔
674

3✔
675
        // Retrieve the AMP state for this set ID.
3✔
676
        ampState, ok := invoice.AMPState[setID]
3✔
677

3✔
678
        // If the state is accepted then we may need to create a new entry for
3✔
679
        // this set ID, otherwise we expect that the entry already exists and
3✔
680
        // we can update it.
3✔
681
        if !ok && state != HtlcStateAccepted {
3✔
682
                return InvoiceStateAMP{},
×
683
                        fmt.Errorf("unable to update AMP state for setID=%x ",
×
684
                                setID)
×
685
        }
×
686

687
        switch state {
3✔
688
        case HtlcStateAccepted:
3✔
689
                if !ok {
6✔
690
                        // If an entry for this set ID doesn't already exist,
3✔
691
                        // then we'll need to create it.
3✔
692
                        ampState = InvoiceStateAMP{
3✔
693
                                State: HtlcStateAccepted,
3✔
694
                                InvoiceKeys: make(
3✔
695
                                        map[models.CircuitKey]struct{},
3✔
696
                                ),
3✔
697
                        }
3✔
698
                }
3✔
699

700
                ampState.AmtPaid += amt
3✔
701

702
        case HtlcStateCanceled:
×
703
                ampState.State = HtlcStateCanceled
×
704
                ampState.AmtPaid -= amt
×
705

706
        case HtlcStateSettled:
3✔
707
                ampState.State = HtlcStateSettled
3✔
708
        }
709

710
        ampState.InvoiceKeys[circuitKey] = struct{}{}
3✔
711

3✔
712
        return ampState, nil
3✔
713
}
714

715
// canCancelSingleHtlc validates cancellation of a single HTLC. If nil is
716
// returned, then the HTLC can be cancelled.
717
func canCancelSingleHtlc(htlc *InvoiceHTLC,
718
        invoiceState ContractState) error {
×
719

×
720
        // It is only possible to cancel individual htlcs on an open invoice.
×
721
        if invoiceState != ContractOpen {
×
722
                return fmt.Errorf("htlc canceled on invoice in state %v",
×
723
                        invoiceState)
×
724
        }
×
725

726
        // It is only possible if the htlc is still pending.
727
        if htlc.State != HtlcStateAccepted {
×
728
                return fmt.Errorf("htlc canceled in state %v", htlc.State)
×
729
        }
×
730

731
        return nil
×
732
}
733

734
// getUpdatedHtlcState aligns the state of an htlc with the given invoice state.
735
// A boolean indicating whether the HTLCs state need to be updated, along with
736
// the new state (or old state if no change is needed) is returned.
737
func getUpdatedHtlcState(htlc *InvoiceHTLC,
738
        invoiceState ContractState, setID *[32]byte) (
739
        bool, HtlcState, error) {
3✔
740

3✔
741
        trySettle := func(persist bool) (bool, HtlcState, error) {
6✔
742
                if htlc.State != HtlcStateAccepted {
3✔
743
                        return false, htlc.State, nil
×
744
                }
×
745

746
                // Settle the HTLC if it matches the settled set id. If
747
                // there're other HTLCs with distinct setIDs, then we'll leave
748
                // them, as they may eventually be settled as we permit
749
                // multiple settles to a single pay_addr for AMP.
750
                settled := false
3✔
751
                if htlc.IsInHTLCSet(setID) {
6✔
752
                        // Non-AMP HTLCs can be settled immediately since we
3✔
753
                        // already know the preimage is valid due to checks at
3✔
754
                        // the invoice level. For AMP HTLCs, verify that the
3✔
755
                        // per-HTLC preimage-hash pair is valid.
3✔
756
                        switch {
3✔
757
                        // Non-AMP HTLCs can be settle immediately since we
758
                        // already know the preimage is valid due to checks at
759
                        // the invoice level.
760
                        case setID == nil:
3✔
761

762
                        // At this point, the setID is non-nil, meaning this is
763
                        // an AMP HTLC. We know that htlc.AMP cannot be nil,
764
                        // otherwise IsInHTLCSet would have returned false.
765
                        //
766
                        // Fail if an accepted AMP HTLC has no preimage.
767
                        case htlc.AMP.Preimage == nil:
×
768
                                return false, htlc.State,
×
769
                                        ErrHTLCPreimageMissing
×
770

771
                        // Fail if the accepted AMP HTLC has an invalid
772
                        // preimage.
773
                        case !htlc.AMP.Preimage.Matches(htlc.AMP.Hash):
×
774
                                return false, htlc.State,
×
775
                                        ErrHTLCPreimageMismatch
×
776
                        }
777

778
                        settled = true
3✔
779
                }
780

781
                // Only persist the changes if the invoice is moving to the
782
                // settled state, and we're actually updating the state to
783
                // settled.
784
                newState := htlc.State
3✔
785
                if settled {
6✔
786
                        newState = HtlcStateSettled
3✔
787
                }
3✔
788

789
                return persist && settled, newState, nil
3✔
790
        }
791

792
        if invoiceState == ContractSettled {
6✔
793
                // Check that we can settle the HTLCs. For legacy and MPP HTLCs
3✔
794
                // this will be a NOP, but for AMP HTLCs this asserts that we
3✔
795
                // have a valid hash/preimage pair. Passing true permits the
3✔
796
                // method to update the HTLC to HtlcStateSettled.
3✔
797
                return trySettle(true)
3✔
798
        }
3✔
799

800
        // We should never find a settled HTLC on an invoice that isn't in
801
        // ContractSettled.
802
        if htlc.State == HtlcStateSettled {
3✔
803
                return false, htlc.State, ErrHTLCAlreadySettled
×
804
        }
×
805

806
        switch invoiceState {
3✔
807
        case ContractCanceled:
3✔
808
                htlcAlreadyCanceled := htlc.State == HtlcStateCanceled
3✔
809
                return !htlcAlreadyCanceled, HtlcStateCanceled, nil
3✔
810

811
        // TODO(roasbeef): never fully passed thru now?
812
        case ContractAccepted:
3✔
813
                // Check that we can settle the HTLCs. For legacy and MPP HTLCs
3✔
814
                // this will be a NOP, but for AMP HTLCs this asserts that we
3✔
815
                // have a valid hash/preimage pair. Passing false prevents the
3✔
816
                // method from putting the HTLC in HtlcStateSettled, leaving it
3✔
817
                // in HtlcStateAccepted.
3✔
818
                return trySettle(false)
3✔
819

820
        case ContractOpen:
3✔
821
                return false, htlc.State, nil
3✔
822

823
        default:
×
824
                return false, htlc.State, errors.New("unknown state transition")
×
825
        }
826
}
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