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

lightningnetwork / lnd / 13211764208

08 Feb 2025 03:08AM UTC coverage: 49.288% (-9.5%) from 58.815%
13211764208

Pull #9489

github

calvinrzachman
itest: verify switchrpc server enforces send then track

We prevent the rpc server from allowing onion dispatches for
attempt IDs which have already been tracked by rpc clients.

This helps protect the client from leaking a duplicate onion
attempt. NOTE: This is not the only method for solving this
issue! The issue could be addressed via careful client side
programming which accounts for the uncertainty and async
nature of dispatching onions to a remote process via RPC.
This would require some lnd ChannelRouter changes for how
we intend to use these RPCs though.
Pull Request #9489: multi: add BuildOnion, SendOnion, and TrackOnion RPCs

474 of 990 new or added lines in 11 files covered. (47.88%)

27321 existing lines in 435 files now uncovered.

101192 of 205306 relevant lines covered (49.29%)

1.54 hits per line

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

63.93
/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,
UNCOV
38
        htlc *InvoiceHTLC, updater InvoiceUpdater) error {
×
UNCOV
39

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

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

UNCOV
52
        invoice.AMPState[setID] = newAmpState
×
UNCOV
53

×
UNCOV
54
        // Mark the updates as needing to be written to disk.
×
UNCOV
55
        err = updater.UpdateAmpState(setID, newAmpState, circuitKey)
×
UNCOV
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.
UNCOV
62
        if invoice.AmtPaid != 0 {
×
UNCOV
63
                return updateInvoiceAmtPaid(
×
UNCOV
64
                        invoice, invoice.AmtPaid-htlc.Amt, updater,
×
UNCOV
65
                )
×
UNCOV
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✔
UNCOV
110
                return invoice, err
×
UNCOV
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:
3✔
120
                err := cancelHTLCs(invoice, updateTime, update, updater)
3✔
121
                if err != nil {
3✔
UNCOV
122
                        return nil, err
×
UNCOV
123
                }
×
124

125
        case AddHTLCsUpdate:
3✔
126
                err := addHTLCs(invoice, hash, updateTime, update, updater)
3✔
127
                if err != nil {
3✔
UNCOV
128
                        return nil, err
×
UNCOV
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✔
UNCOV
153
                return nil, err
×
UNCOV
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 {
3✔
165

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

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

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

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

198
        return nil
3✔
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✔
UNCOV
245
                        return err
×
UNCOV
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✔
UNCOV
273
                        return err
×
UNCOV
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.
UNCOV
321
                        case ok && *htlc.AMP.Preimage != preimage:
×
UNCOV
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✔
UNCOV
417
                return err
×
UNCOV
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✔
UNCOV
534
                setID = update.SetID
×
UNCOV
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
                // We might not have a setID here in case we are cancelling
3✔
556
                // an AMP invoice however the setID is only important when
3✔
557
                // settling an AMP HTLC.
3✔
558
                canceled, _, err := getUpdatedHtlcState(
3✔
559
                        htlc, ContractCanceled, setID,
3✔
560
                )
3✔
561
                if err != nil {
3✔
562
                        return err
×
563
                }
×
564

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

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

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

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

615
                fallthrough
3✔
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:
3✔
621
                if update.NewState == ContractCanceled {
6✔
622
                        return &update.NewState, nil
3✔
623
                }
3✔
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)
3✔
628
                if len(set) == 0 {
3✔
UNCOV
629
                        return nil, ErrEmptyHTLCSet
×
UNCOV
630
                }
×
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 {
6✔
636
                        if update.Preimage != nil {
3✔
637
                                return nil, errors.New("AMP set cannot have " +
×
638
                                        "preimage")
×
639
                        }
×
640

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

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

650
                // Validate the supplied preimage for non-AMP invoices.
651
                case update.Preimage != nil:
3✔
652
                        if update.Preimage.Hash() != *hash {
3✔
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:
3✔
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
3✔
670

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

675
        // Once canceled, we are in a terminal state.
UNCOV
676
        case ContractCanceled:
×
UNCOV
677
                return nil, ErrInvoiceAlreadyCanceled
×
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) {
3✔
690

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

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

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

716
                ampState.AmtPaid += amt
3✔
717

UNCOV
718
        case HtlcStateCanceled:
×
UNCOV
719
                ampState.State = HtlcStateCanceled
×
UNCOV
720
                ampState.AmtPaid -= amt
×
721

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

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

3✔
728
        return ampState, nil
3✔
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,
UNCOV
734
        invoiceState ContractState) error {
×
UNCOV
735

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

UNCOV
747
        return nil
×
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) {
3✔
756

3✔
757
        trySettle := func(persist bool) (bool, HtlcState, error) {
6✔
758
                if htlc.State != HtlcStateAccepted {
3✔
UNCOV
759
                        return false, htlc.State, nil
×
UNCOV
760
                }
×
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
3✔
767
                if htlc.IsInHTLCSet(setID) {
6✔
768
                        // Non-AMP HTLCs can be settled immediately since we
3✔
769
                        // already know the preimage is valid due to checks at
3✔
770
                        // the invoice level. For AMP HTLCs, verify that the
3✔
771
                        // per-HTLC preimage-hash pair is valid.
3✔
772
                        switch {
3✔
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:
3✔
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.
UNCOV
783
                        case htlc.AMP.Preimage == nil:
×
UNCOV
784
                                return false, htlc.State,
×
UNCOV
785
                                        ErrHTLCPreimageMissing
×
786

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

794
                        settled = true
3✔
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
3✔
801
                if settled {
6✔
802
                        newState = HtlcStateSettled
3✔
803
                }
3✔
804

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

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

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

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

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

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