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

lightningnetwork / lnd / 13236757158

10 Feb 2025 08:39AM UTC coverage: 57.649% (-1.2%) from 58.815%
13236757158

Pull #9493

github

ziggie1984
lncli: for some cmds we don't replace the data of the response.

For some cmds it is not very practical to replace the json output
because we might pipe it into other commands. For example when
creating the route we want to pipe it into sendtoRoute.
Pull Request #9493: For some lncli cmds we should not replace the content with other data

0 of 9 new or added lines in 2 files covered. (0.0%)

19535 existing lines in 252 files now uncovered.

103517 of 179563 relevant lines covered (57.65%)

24878.49 hits per line

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

79.07
/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 {
70✔
19

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

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

70✔
29
        // Mark the updates as needing to be written to disk.
70✔
30
        return updater.UpdateAmpState(setID, newAmpState, circuitKey)
70✔
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 {
30✔
39

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

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

52
        invoice.AMPState[setID] = newAmpState
30✔
53

30✔
54
        // Mark the updates as needing to be written to disk.
30✔
55
        err = updater.UpdateAmpState(setID, newAmpState, circuitKey)
30✔
56
        if err != nil {
30✔
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 {
60✔
63
                return updateInvoiceAmtPaid(
30✔
64
                        invoice, invoice.AmtPaid-htlc.Amt, updater,
30✔
65
                )
30✔
66
        }
30✔
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 {
33✔
76

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

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

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

33✔
90
        // Mark the updates as needing to be written to disk.
33✔
91
        return updater.UpdateAmpState(setID, newAmpState, circuitKey)
33✔
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) {
1,397✔
99

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

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

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

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

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

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

139
        case CancelInvoiceUpdate:
113✔
140
                err := cancelInvoice(
113✔
141
                        invoice, hash, updateTime, update.State, updater,
113✔
142
                )
113✔
143
                if err != nil {
128✔
144
                        return nil, err
15✔
145
                }
15✔
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 {
1,309✔
153
                return nil, err
1✔
154
        }
1✔
155

156
        return invoice, nil
1,307✔
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 {
36✔
165

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

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

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

180
                err = resolveHtlc(
36✔
181
                        key, htlc, HtlcStateCanceled, updateTime,
36✔
182
                        updater,
36✔
183
                )
36✔
184
                if err != nil {
36✔
UNCOV
185
                        return err
×
UNCOV
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() {
60✔
191
                        err := cancelHtlcsAmp(invoice, key, htlc, updater)
24✔
192
                        if err != nil {
24✔
193
                                return err
×
194
                        }
×
195
                }
196
        }
197

198
        return nil
36✔
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 {
1,122✔
206

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

213
        for key, htlcUpdate := range update.AddHtlcs {
2,217✔
214
                if _, exists := invoice.Htlcs[key]; exists {
1,095✔
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 {
1,095✔
221
                        return errors.New("nil custom records map")
×
222
                }
×
223

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

1,095✔
234
                if invoiceIsAMP {
1,167✔
235
                        if htlcUpdate.AMP == nil {
72✔
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()
72✔
242
                }
243

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

248
                invoice.Htlcs[key] = htlc
1,093✔
249

1,093✔
250
                // Collect the set of new HTLCs so we can write them properly
1,093✔
251
                // below, but only if this is an AMP invoice.
1,093✔
252
                if invoiceIsAMP {
1,163✔
253
                        err := acceptHtlcsAmp(
70✔
254
                                invoice, htlcUpdate.AMP.Record.SetID(), key,
70✔
255
                                htlc, updater,
70✔
256
                        )
70✔
257
                        if err != nil {
70✔
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 {
1,871✔
269
                newState, err := getUpdatedInvoiceState(
751✔
270
                        invoice, hash, *update.State,
751✔
271
                )
751✔
272
                if err != nil {
760✔
273
                        return err
9✔
274
                }
9✔
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 {
1,429✔
282
                        err := updater.UpdateInvoiceState(*newState, nil)
687✔
283
                        if err != nil {
687✔
284
                                return err
×
285
                        }
×
286
                        invoice.State = *newState
687✔
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
1,111✔
293
        if update.State != nil {
1,853✔
294
                settleEligibleAMP = len(update.State.HTLCPreimages) != 0
742✔
295
        }
742✔
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
1,111✔
301

1,111✔
302
        for key, htlc := range invoice.Htlcs {
2,579✔
303
                // Set the HTLC preimage for any AMP HTLCs.
1,468✔
304
                if setID != nil && update.State != nil {
1,535✔
305
                        preimage, ok := update.State.HTLCPreimages[key]
67✔
306
                        switch {
67✔
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:
18✔
310
                                err := updater.AddAmpHtlcPreimage(
18✔
311
                                        htlc.AMP.Record.SetID(), key, preimage,
18✔
312
                                )
18✔
313
                                if err != nil {
18✔
314
                                        return err
×
315
                                }
×
316
                                htlc.AMP.Preimage = &preimage
18✔
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:
3✔
322
                                return ErrHTLCPreimageAlreadyExists
3✔
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
1,465✔
336
                if settleEligibleAMP {
1,498✔
337
                        htlcContextState = ContractSettled
33✔
338
                }
33✔
339
                htlcStateChanged, htlcState, err := getUpdatedHtlcState(
1,465✔
340
                        htlc, htlcContextState, setID,
1,465✔
341
                )
1,465✔
342
                if err != nil {
1,465✔
343
                        return err
×
344
                }
×
345

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

355
                htlcSettled := htlcStateChanged &&
1,465✔
356
                        htlcState == HtlcStateSettled
1,465✔
357

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

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

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

1,005✔
379
                                amtPaid += htlc.Amt
1,005✔
380
                        }
1,005✔
381
                } else {
118✔
382
                        // For AMP invoices, since we won't always be reading
118✔
383
                        // out the total invoice set each time, we'll instead
118✔
384
                        // accumulate newly added invoices to the total amount
118✔
385
                        // paid.
118✔
386
                        if _, ok := update.AddHtlcs[key]; !ok {
166✔
387
                                continue
48✔
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 &&
70✔
394
                                invoiceStateReady {
140✔
395

70✔
396
                                amtPaid += htlc.Amt
70✔
397
                        }
70✔
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 {
1,196✔
405
                amtPaid += invoice.AmtPaid
88✔
406
        }
88✔
407

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

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

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

1,077✔
422
        return nil
1,077✔
423
}
424

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

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

1,204✔
434
        return nil
1,204✔
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 {
66✔
443

66✔
444
        if !invoice.HodlInvoice {
66✔
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 {
66✔
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(
66✔
465
                invoice, hash, *update,
66✔
466
        )
66✔
467
        if err != nil {
66✔
468
                return err
×
469
        }
×
470

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

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

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

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

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

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

509
        return updateInvoiceAmtPaid(invoice, amtPaid, updater)
66✔
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 {
113✔
517

113✔
518
        switch {
113✔
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 (
113✔
528
                setID        *[32]byte
113✔
529
                invoiceIsAMP bool
113✔
530
        )
113✔
531

113✔
532
        invoiceIsAMP = invoice.IsAMP()
113✔
533
        if invoiceIsAMP {
126✔
534
                setID = update.SetID
13✔
535
        }
13✔
536

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

542
        if newState == nil || *newState != ContractCanceled {
99✔
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)
99✔
549
        if err != nil {
100✔
550
                return err
1✔
551
        }
1✔
552
        invoice.State = ContractCanceled
98✔
553

98✔
554
        for key, htlc := range invoice.Htlcs {
147✔
555
                // We might not have a setID here in case we are cancelling
49✔
556
                // an AMP invoice however the setID is only important when
49✔
557
                // settling an AMP HTLC.
49✔
558
                canceled, _, err := getUpdatedHtlcState(
49✔
559
                        htlc, ContractCanceled, setID,
49✔
560
                )
49✔
561
                if err != nil {
49✔
562
                        return err
×
563
                }
×
564

565
                if canceled {
77✔
566
                        err = resolveHtlc(
28✔
567
                                key, htlc, HtlcStateCanceled, updateTime,
28✔
568
                                updater,
28✔
569
                        )
28✔
570
                        if err != nil {
28✔
571
                                return err
×
572
                        }
×
573

574
                        // If its an AMP HTLC we need to make sure we persist
575
                        // this new state otherwise AMP HTLCs are not updated
576
                        // on disk because HTLCs for AMP invoices are stored
577
                        // separately.
578
                        if htlc.AMP != nil {
34✔
579
                                err := cancelHtlcsAmp(
6✔
580
                                        invoice, key, htlc, updater,
6✔
581
                                )
6✔
582
                                if err != nil {
6✔
583
                                        return err
×
584
                                }
×
585
                        }
586
                }
587
        }
588

589
        return nil
98✔
590
}
591

592
// getUpdatedInvoiceState validates and processes an invoice state update. The
593
// new state to transition to is returned, so the caller is able to select
594
// exactly how the invoice state is updated. Note that for AMP invoices this
595
// function is only used to validate the state transition if we're cancelling
596
// the invoice.
597
func getUpdatedInvoiceState(invoice *Invoice, hash *lntypes.Hash,
598
        update InvoiceStateUpdateDesc) (*ContractState, error) {
930✔
599

930✔
600
        // Returning to open is never allowed from any state.
930✔
601
        if update.NewState == ContractOpen {
930✔
602
                return nil, ErrInvoiceCannotOpen
×
603
        }
×
604

605
        switch invoice.State {
930✔
606
        // Once a contract is accepted, we can only transition to settled or
607
        // canceled. Forbid transitioning back into this state. Otherwise this
608
        // state is identical to ContractOpen, so we fallthrough to apply the
609
        // same checks that we apply to open invoices.
610
        case ContractAccepted:
82✔
611
                if update.NewState == ContractAccepted {
82✔
612
                        return nil, ErrInvoiceCannotAccept
×
613
                }
×
614

615
                fallthrough
82✔
616

617
        // If a contract is open, permit a state transition to accepted, settled
618
        // or canceled. The only restriction is on transitioning to settled
619
        // where we ensure the preimage is valid.
620
        case ContractOpen:
916✔
621
                if update.NewState == ContractCanceled {
1,015✔
622
                        return &update.NewState, nil
99✔
623
                }
99✔
624

625
                // Sanity check that the user isn't trying to settle or accept a
626
                // non-existent HTLC set.
627
                set := invoice.HTLCSet(update.SetID, HtlcStateAccepted)
817✔
628
                if len(set) == 0 {
823✔
629
                        return nil, ErrEmptyHTLCSet
6✔
630
                }
6✔
631

632
                // For AMP invoices, there are no invoice-level preimage checks.
633
                // However, we still sanity check that we aren't trying to
634
                // settle an AMP invoice with a preimage.
635
                if update.SetID != nil {
866✔
636
                        if update.Preimage != nil {
55✔
637
                                return nil, errors.New("AMP set cannot have " +
×
638
                                        "preimage")
×
639
                        }
×
640

641
                        return &update.NewState, nil
55✔
642
                }
643

644
                switch {
756✔
645
                // If an invoice-level preimage was supplied, but the InvoiceRef
646
                // doesn't specify a hash (e.g. AMP invoices) we fail.
647
                case update.Preimage != nil && hash == nil:
3✔
648
                        return nil, ErrUnexpectedInvoicePreimage
3✔
649

650
                // Validate the supplied preimage for non-AMP invoices.
651
                case update.Preimage != nil:
671✔
652
                        if update.Preimage.Hash() != *hash {
671✔
653
                                return nil, ErrInvoicePreimageMismatch
×
654
                        }
×
655

656
                // Permit non-AMP invoices to be accepted without knowing the
657
                // preimage. When trying to settle we'll have to pass through
658
                // the above check in order to not hit the one below.
659
                case update.NewState == ContractAccepted:
82✔
660

661
                // Fail if we still don't have a preimage when transitioning to
662
                // settle the non-AMP invoice.
663
                case update.NewState == ContractSettled &&
664
                        invoice.Terms.PaymentPreimage == nil:
×
665

×
666
                        return nil, errors.New("unknown preimage")
×
667
                }
668

669
                return &update.NewState, nil
753✔
670

671
        // Once settled, we are in a terminal state.
672
        case ContractSettled:
11✔
673
                return nil, ErrInvoiceAlreadySettled
11✔
674

675
        // Once canceled, we are in a terminal state.
676
        case ContractCanceled:
3✔
677
                return nil, ErrInvoiceAlreadyCanceled
3✔
678

679
        default:
×
680
                return nil, errors.New("unknown state transition")
×
681
        }
682
}
683

684
// getUpdatedInvoiceAmpState returns the AMP state of an invoice (without
685
// applying it), given the new state, and the amount of the HTLC that is
686
// being updated.
687
func getUpdatedInvoiceAmpState(invoice *Invoice, setID SetID,
688
        circuitKey models.CircuitKey, state HtlcState,
689
        amt lnwire.MilliSatoshi) (InvoiceStateAMP, error) {
133✔
690

133✔
691
        // Retrieve the AMP state for this set ID.
133✔
692
        ampState, ok := invoice.AMPState[setID]
133✔
693

133✔
694
        // If the state is accepted then we may need to create a new entry for
133✔
695
        // this set ID, otherwise we expect that the entry already exists and
133✔
696
        // we can update it.
133✔
697
        if !ok && state != HtlcStateAccepted {
133✔
698
                return InvoiceStateAMP{},
×
699
                        fmt.Errorf("unable to update AMP state for setID=%x ",
×
700
                                setID)
×
701
        }
×
702

703
        switch state {
133✔
704
        case HtlcStateAccepted:
70✔
705
                if !ok {
119✔
706
                        // If an entry for this set ID doesn't already exist,
49✔
707
                        // then we'll need to create it.
49✔
708
                        ampState = InvoiceStateAMP{
49✔
709
                                State: HtlcStateAccepted,
49✔
710
                                InvoiceKeys: make(
49✔
711
                                        map[models.CircuitKey]struct{},
49✔
712
                                ),
49✔
713
                        }
49✔
714
                }
49✔
715

716
                ampState.AmtPaid += amt
70✔
717

718
        case HtlcStateCanceled:
30✔
719
                ampState.State = HtlcStateCanceled
30✔
720
                ampState.AmtPaid -= amt
30✔
721

722
        case HtlcStateSettled:
33✔
723
                ampState.State = HtlcStateSettled
33✔
724
        }
725

726
        ampState.InvoiceKeys[circuitKey] = struct{}{}
133✔
727

133✔
728
        return ampState, nil
133✔
729
}
730

731
// canCancelSingleHtlc validates cancellation of a single HTLC. If nil is
732
// returned, then the HTLC can be cancelled.
733
func canCancelSingleHtlc(htlc *InvoiceHTLC,
734
        invoiceState ContractState) error {
36✔
735

36✔
736
        // It is only possible to cancel individual htlcs on an open invoice.
36✔
737
        if invoiceState != ContractOpen {
36✔
738
                return fmt.Errorf("htlc canceled on invoice in state %v",
×
739
                        invoiceState)
×
740
        }
×
741

742
        // It is only possible if the htlc is still pending.
743
        if htlc.State != HtlcStateAccepted {
36✔
744
                return fmt.Errorf("htlc canceled in state %v", htlc.State)
×
745
        }
×
746

747
        return nil
36✔
748
}
749

750
// getUpdatedHtlcState aligns the state of an htlc with the given invoice state.
751
// A boolean indicating whether the HTLCs state need to be updated, along with
752
// the new state (or old state if no change is needed) is returned.
753
func getUpdatedHtlcState(htlc *InvoiceHTLC,
754
        invoiceState ContractState, setID *[32]byte) (
755
        bool, HtlcState, error) {
1,602✔
756

1,602✔
757
        trySettle := func(persist bool) (bool, HtlcState, error) {
2,729✔
758
                if htlc.State != HtlcStateAccepted {
1,139✔
759
                        return false, htlc.State, nil
12✔
760
                }
12✔
761

762
                // Settle the HTLC if it matches the settled set id. If
763
                // there're other HTLCs with distinct setIDs, then we'll leave
764
                // them, as they may eventually be settled as we permit
765
                // multiple settles to a single pay_addr for AMP.
766
                settled := false
1,115✔
767
                if htlc.IsInHTLCSet(setID) {
2,228✔
768
                        // Non-AMP HTLCs can be settled immediately since we
1,113✔
769
                        // already know the preimage is valid due to checks at
1,113✔
770
                        // the invoice level. For AMP HTLCs, verify that the
1,113✔
771
                        // per-HTLC preimage-hash pair is valid.
1,113✔
772
                        switch {
1,113✔
773
                        // Non-AMP HTLCs can be settle immediately since we
774
                        // already know the preimage is valid due to checks at
775
                        // the invoice level.
776
                        case setID == nil:
1,074✔
777

778
                        // At this point, the setID is non-nil, meaning this is
779
                        // an AMP HTLC. We know that htlc.AMP cannot be nil,
780
                        // otherwise IsInHTLCSet would have returned false.
781
                        //
782
                        // Fail if an accepted AMP HTLC has no preimage.
783
                        case htlc.AMP.Preimage == nil:
2✔
784
                                return false, htlc.State,
2✔
785
                                        ErrHTLCPreimageMissing
2✔
786

787
                        // Fail if the accepted AMP HTLC has an invalid
788
                        // preimage.
789
                        case !htlc.AMP.Preimage.Matches(htlc.AMP.Hash):
2✔
790
                                return false, htlc.State,
2✔
791
                                        ErrHTLCPreimageMismatch
2✔
792
                        }
793

794
                        settled = true
1,109✔
795
                }
796

797
                // Only persist the changes if the invoice is moving to the
798
                // settled state, and we're actually updating the state to
799
                // settled.
800
                newState := htlc.State
1,111✔
801
                if settled {
2,220✔
802
                        newState = HtlcStateSettled
1,109✔
803
                }
1,109✔
804

805
                return persist && settled, newState, nil
1,111✔
806
        }
807

808
        if invoiceState == ContractSettled {
2,628✔
809
                // Check that we can settle the HTLCs. For legacy and MPP HTLCs
1,026✔
810
                // this will be a NOP, but for AMP HTLCs this asserts that we
1,026✔
811
                // have a valid hash/preimage pair. Passing true permits the
1,026✔
812
                // method to update the HTLC to HtlcStateSettled.
1,026✔
813
                return trySettle(true)
1,026✔
814
        }
1,026✔
815

816
        // We should never find a settled HTLC on an invoice that isn't in
817
        // ContractSettled.
818
        if htlc.State == HtlcStateSettled {
578✔
819
                return false, htlc.State, ErrHTLCAlreadySettled
2✔
820
        }
2✔
821

822
        switch invoiceState {
574✔
823
        case ContractCanceled:
52✔
824
                htlcAlreadyCanceled := htlc.State == HtlcStateCanceled
52✔
825
                return !htlcAlreadyCanceled, HtlcStateCanceled, nil
52✔
826

827
        // TODO(roasbeef): never fully passed thru now?
828
        case ContractAccepted:
101✔
829
                // Check that we can settle the HTLCs. For legacy and MPP HTLCs
101✔
830
                // this will be a NOP, but for AMP HTLCs this asserts that we
101✔
831
                // have a valid hash/preimage pair. Passing false prevents the
101✔
832
                // method from putting the HTLC in HtlcStateSettled, leaving it
101✔
833
                // in HtlcStateAccepted.
101✔
834
                return trySettle(false)
101✔
835

836
        case ContractOpen:
421✔
837
                return false, htlc.State, nil
421✔
838

839
        default:
×
840
                return false, htlc.State, errors.New("unknown state transition")
×
841
        }
842
}
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