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

lightningnetwork / lnd / 14035127674

24 Mar 2025 12:21PM UTC coverage: 68.999% (+0.01%) from 68.989%
14035127674

Pull #9631

github

web-flow
Merge 06c7cd82e into 5235f3b24
Pull Request #9631: rpcserver: warn if sendcoins default conf target is used

1 of 1 new or added line in 1 file covered. (100.0%)

79 existing lines in 23 files now uncovered.

132917 of 192637 relevant lines covered (69.0%)

22283.53 hits per line

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

79.81
/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 {
73✔
19

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

198
        return nil
37✔
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,125✔
206

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

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

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

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

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

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

1,096✔
250
                // Collect the set of new HTLCs so we can write them properly
1,096✔
251
                // below, but only if this is an AMP invoice.
1,096✔
252
                if invoiceIsAMP {
1,169✔
253
                        err := acceptHtlcsAmp(
73✔
254
                                invoice, htlcUpdate.AMP.Record.SetID(), key,
73✔
255
                                htlc, updater,
73✔
256
                        )
73✔
257
                        if err != nil {
73✔
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,877✔
269
                newState, err := getUpdatedInvoiceState(
754✔
270
                        invoice, hash, *update.State,
754✔
271
                )
754✔
272
                if err != nil {
763✔
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,435✔
282
                        err := updater.UpdateInvoiceState(*newState, nil)
690✔
283
                        if err != nil {
690✔
284
                                return err
×
285
                        }
×
286
                        invoice.State = *newState
690✔
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,114✔
293
        if update.State != nil {
1,859✔
294
                settleEligibleAMP = len(update.State.HTLCPreimages) != 0
745✔
295
        }
745✔
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,114✔
301

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

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

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

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

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

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

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

73✔
396
                                amtPaid += htlc.Amt
73✔
397
                        }
73✔
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,202✔
405
                amtPaid += invoice.AmtPaid
91✔
406
        }
91✔
407

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

115✔
532
        invoiceIsAMP = invoice.IsAMP()
115✔
533
        if invoiceIsAMP {
127✔
534
                setID = update.SetID
12✔
535
        }
12✔
536

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

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

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

565
                if canceled {
85✔
566
                        err = resolveHtlc(
33✔
567
                                key, htlc, HtlcStateCanceled, updateTime,
33✔
568
                                updater,
33✔
569
                        )
33✔
570
                        if err != nil {
33✔
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 {
41✔
579
                                err := cancelHtlcsAmp(
8✔
580
                                        invoice, key, htlc, updater,
8✔
581
                                )
8✔
582
                                if err != nil {
8✔
583
                                        return err
×
584
                                }
×
585
                        }
586
                }
587
        }
588

589
        return nil
101✔
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) {
932✔
599

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

605
        switch invoice.State {
932✔
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:
85✔
611
                if update.NewState == ContractAccepted {
85✔
612
                        return nil, ErrInvoiceCannotAccept
×
613
                }
×
614

615
                fallthrough
85✔
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:
918✔
621
                if update.NewState == ContractCanceled {
1,019✔
622
                        return &update.NewState, nil
101✔
623
                }
101✔
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)
820✔
628
                if len(set) == 0 {
826✔
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 {
872✔
636
                        if update.Preimage != nil {
58✔
637
                                return nil, errors.New("AMP set cannot have " +
×
638
                                        "preimage")
×
639
                        }
×
640

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

644
                switch {
759✔
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:
674✔
652
                        if update.Preimage.Hash() != *hash {
674✔
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:
85✔
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
756✔
670

671
        // Once settled, we are in a terminal state.
672
        case ContractSettled:
14✔
673
                return nil, ErrInvoiceAlreadySettled
14✔
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) {
136✔
690

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

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

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

716
                ampState.AmtPaid += amt
73✔
717

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

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

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

136✔
728
        return ampState, nil
136✔
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 {
35✔
735

35✔
736
        // It is only possible to cancel individual htlcs on an open invoice.
35✔
737
        if invoiceState != ContractOpen {
35✔
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 {
35✔
744
                return fmt.Errorf("htlc canceled in state %v", htlc.State)
×
745
        }
×
746

747
        return nil
35✔
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,605✔
756

1,605✔
757
        trySettle := func(persist bool) (bool, HtlcState, error) {
2,735✔
758
                if htlc.State != HtlcStateAccepted {
1,142✔
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,118✔
767
                if htlc.IsInHTLCSet(setID) {
2,234✔
768
                        // Non-AMP HTLCs can be settled immediately since we
1,116✔
769
                        // already know the preimage is valid due to checks at
1,116✔
770
                        // the invoice level. For AMP HTLCs, verify that the
1,116✔
771
                        // per-HTLC preimage-hash pair is valid.
1,116✔
772
                        switch {
1,116✔
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,077✔
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,112✔
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,114✔
801
                if settled {
2,226✔
802
                        newState = HtlcStateSettled
1,112✔
803
                }
1,112✔
804

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

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

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

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

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

836
        case ContractOpen:
424✔
837
                return false, htlc.State, nil
424✔
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