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

lightningnetwork / lnd / 11216766535

07 Oct 2024 01:37PM UTC coverage: 57.817% (-1.0%) from 58.817%
11216766535

Pull #9148

github

ProofOfKeags
lnwire: remove kickoff feerate from propose/commit
Pull Request #9148: DynComms [2/n]: lnwire: add authenticated wire messages for Dyn*

571 of 879 new or added lines in 16 files covered. (64.96%)

23253 existing lines in 251 files now uncovered.

99022 of 171268 relevant lines covered (57.82%)

38420.67 hits per line

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

88.01
/invoices/update.go
1
package invoices
2

3
import (
4
        "bytes"
5
        "encoding/hex"
6
        "errors"
7

8
        "github.com/btcsuite/btcd/chaincfg/chainhash"
9
        "github.com/lightningnetwork/lnd/amp"
10
        "github.com/lightningnetwork/lnd/lntypes"
11
        "github.com/lightningnetwork/lnd/lnwire"
12
        "github.com/lightningnetwork/lnd/record"
13
)
14

15
// invoiceUpdateCtx is an object that describes the context for the invoice
16
// update to be carried out.
17
type invoiceUpdateCtx struct {
18
        hash                 lntypes.Hash
19
        circuitKey           CircuitKey
20
        amtPaid              lnwire.MilliSatoshi
21
        expiry               uint32
22
        currentHeight        int32
23
        finalCltvRejectDelta int32
24

25
        // wireCustomRecords are the custom records that were included with the
26
        // HTLC wire message.
27
        wireCustomRecords lnwire.CustomRecords
28

29
        // customRecords is a map of custom records that were included with the
30
        // HTLC onion payload.
31
        customRecords record.CustomSet
32

33
        mpp          *record.MPP
34
        amp          *record.AMP
35
        metadata     []byte
36
        pathID       *chainhash.Hash
37
        totalAmtMsat lnwire.MilliSatoshi
38
}
39

40
// invoiceRef returns an identifier that can be used to lookup or update the
41
// invoice this HTLC is targeting.
42
func (i *invoiceUpdateCtx) invoiceRef() InvoiceRef {
3,048✔
43
        switch {
3,048✔
UNCOV
44
        case i.pathID != nil:
×
UNCOV
45
                return InvoiceRefByHashAndAddr(i.hash, *i.pathID)
×
46

47
        case i.amp != nil && i.mpp != nil:
57✔
48
                payAddr := i.mpp.PaymentAddr()
57✔
49
                return InvoiceRefByAddr(payAddr)
57✔
50

51
        case i.mpp != nil:
1,578✔
52
                payAddr := i.mpp.PaymentAddr()
1,578✔
53
                return InvoiceRefByHashAndAddr(i.hash, payAddr)
1,578✔
54

55
        default:
1,413✔
56
                return InvoiceRefByHash(i.hash)
1,413✔
57
        }
58
}
59

60
// setID returns an identifier that identifies other possible HTLCs that this
61
// particular one is related to. If nil is returned this means the HTLC is an
62
// MPP or legacy payment, otherwise the HTLC belongs AMP payment.
63
func (i invoiceUpdateCtx) setID() *[32]byte {
2,982✔
64
        if i.amp != nil {
3,069✔
65
                setID := i.amp.SetID()
87✔
66
                return &setID
87✔
67
        }
87✔
68
        return nil
2,895✔
69
}
70

71
// log logs a message specific to this update context.
72
func (i *invoiceUpdateCtx) log(s string) {
1,357✔
73
        // Don't use %x in the log statement below, because it doesn't
1,357✔
74
        // distinguish between nil and empty metadata.
1,357✔
75
        metadata := "<nil>"
1,357✔
76
        if i.metadata != nil {
1,357✔
77
                metadata = hex.EncodeToString(i.metadata)
×
78
        }
×
79

80
        log.Debugf("Invoice%v: %v, amt=%v, expiry=%v, circuit=%v, mpp=%v, "+
1,357✔
81
                "amp=%v, metadata=%v", i.invoiceRef(), s, i.amtPaid, i.expiry,
1,357✔
82
                i.circuitKey, i.mpp, i.amp, metadata)
1,357✔
83
}
84

85
// failRes is a helper function which creates a failure resolution with
86
// the information contained in the invoiceUpdateCtx and the fail resolution
87
// result provided.
88
func (i invoiceUpdateCtx) failRes(outcome FailResolutionResult) *HtlcFailResolution {
26✔
89
        return NewFailResolution(i.circuitKey, i.currentHeight, outcome)
26✔
90
}
26✔
91

92
// settleRes is a helper function which creates a settle resolution with
93
// the information contained in the invoiceUpdateCtx and the preimage and
94
// the settle resolution result provided.
95
func (i invoiceUpdateCtx) settleRes(preimage lntypes.Preimage,
96
        outcome SettleResolutionResult) *HtlcSettleResolution {
473✔
97

473✔
98
        return NewSettleResolution(
473✔
99
                preimage, i.circuitKey, i.currentHeight, outcome,
473✔
100
        )
473✔
101
}
473✔
102

103
// acceptRes is a helper function which creates an accept resolution with
104
// the information contained in the invoiceUpdateCtx and the accept resolution
105
// result provided.
106
func (i invoiceUpdateCtx) acceptRes(
107
        outcome acceptResolutionResult) *htlcAcceptResolution {
852✔
108

852✔
109
        return newAcceptResolution(i.circuitKey, outcome)
852✔
110
}
852✔
111

112
// updateInvoice is a callback for DB.UpdateInvoice that contains the invoice
113
// settlement logic. It returns a HTLC resolution that indicates what the
114
// outcome of the update was.
115
func updateInvoice(ctx *invoiceUpdateCtx, inv *Invoice) (
116
        *InvoiceUpdateDesc, HtlcResolution, error) {
1,351✔
117

1,351✔
118
        // Don't update the invoice when this is a replayed htlc.
1,351✔
119
        htlc, ok := inv.Htlcs[ctx.circuitKey]
1,351✔
120
        if ok {
1,367✔
121
                switch htlc.State {
16✔
122
                case HtlcStateCanceled:
3✔
123
                        return nil, ctx.failRes(ResultReplayToCanceled), nil
3✔
124

125
                case HtlcStateAccepted:
7✔
126
                        return nil, ctx.acceptRes(resultReplayToAccepted), nil
7✔
127

128
                case HtlcStateSettled:
6✔
129
                        pre := inv.Terms.PaymentPreimage
6✔
130

6✔
131
                        // Terms.PaymentPreimage will be nil for AMP invoices.
6✔
132
                        // Set it to the HTLCs AMP Preimage instead.
6✔
133
                        if pre == nil {
6✔
134
                                pre = htlc.AMP.Preimage
×
135
                        }
×
136

137
                        return nil, ctx.settleRes(
6✔
138
                                *pre,
6✔
139
                                ResultReplayToSettled,
6✔
140
                        ), nil
6✔
141

142
                default:
×
143
                        return nil, nil, errors.New("unknown htlc state")
×
144
                }
145
        }
146

147
        // If no MPP payload was provided, then we expect this to be a keysend,
148
        // or a payment to an invoice created before we started to require the
149
        // MPP payload.
150
        if ctx.mpp == nil && ctx.pathID == nil {
2,016✔
151
                return updateLegacy(ctx, inv)
681✔
152
        }
681✔
153

154
        return updateMpp(ctx, inv)
654✔
155
}
156

157
// updateMpp is a callback for DB.UpdateInvoice that contains the invoice
158
// settlement logic for mpp payments.
159
func updateMpp(ctx *invoiceUpdateCtx, inv *Invoice) (*InvoiceUpdateDesc,
160
        HtlcResolution, error) {
654✔
161

654✔
162
        // Reject HTLCs to AMP invoices if they are missing an AMP payload, and
654✔
163
        // HTLCs to MPP invoices if they have an AMP payload.
654✔
164
        switch {
654✔
165
        case inv.Terms.Features.RequiresFeature(lnwire.AMPRequired) &&
166
                ctx.amp == nil:
×
167

×
168
                return nil, ctx.failRes(ResultHtlcInvoiceTypeMismatch), nil
×
169

170
        case !inv.Terms.Features.RequiresFeature(lnwire.AMPRequired) &&
171
                ctx.amp != nil:
×
172

×
173
                return nil, ctx.failRes(ResultHtlcInvoiceTypeMismatch), nil
×
174
        }
175

176
        setID := ctx.setID()
654✔
177

654✔
178
        var (
654✔
179
                totalAmt    = ctx.totalAmtMsat
654✔
180
                paymentAddr []byte
654✔
181
        )
654✔
182
        // If an MPP record is present, then the payment address and total
654✔
183
        // payment amount is extracted from it. Otherwise, the pathID is used
654✔
184
        // to extract the payment address.
654✔
185
        if ctx.mpp != nil {
1,308✔
186
                totalAmt = ctx.mpp.TotalMsat()
654✔
187
                payAddr := ctx.mpp.PaymentAddr()
654✔
188
                paymentAddr = payAddr[:]
654✔
189
        } else {
654✔
UNCOV
190
                paymentAddr = ctx.pathID[:]
×
UNCOV
191
        }
×
192

193
        // For storage, we don't really care where the custom records came from.
194
        // So we merge them together and store them in the same field.
195
        customRecords := lnwire.CustomRecords(
654✔
196
                ctx.customRecords,
654✔
197
        ).MergedCopy(ctx.wireCustomRecords)
654✔
198

654✔
199
        // Start building the accept descriptor.
654✔
200
        acceptDesc := &HtlcAcceptDesc{
654✔
201
                Amt:           ctx.amtPaid,
654✔
202
                Expiry:        ctx.expiry,
654✔
203
                AcceptHeight:  ctx.currentHeight,
654✔
204
                MppTotalAmt:   totalAmt,
654✔
205
                CustomRecords: record.CustomSet(customRecords),
654✔
206
        }
654✔
207

654✔
208
        if ctx.amp != nil {
678✔
209
                acceptDesc.AMP = &InvoiceHtlcAMPData{
24✔
210
                        Record:   *ctx.amp,
24✔
211
                        Hash:     ctx.hash,
24✔
212
                        Preimage: nil,
24✔
213
                }
24✔
214
        }
24✔
215

216
        // Only accept payments to open invoices. This behaviour differs from
217
        // non-mpp payments that are accepted even after the invoice is settled.
218
        // Because non-mpp payments don't have a payment address, this is needed
219
        // to thwart probing.
220
        if inv.State != ContractOpen {
654✔
221
                return nil, ctx.failRes(ResultInvoiceNotOpen), nil
×
222
        }
×
223

224
        // Check the payment address that authorizes the payment.
225
        if !bytes.Equal(paymentAddr, inv.Terms.PaymentAddr[:]) {
654✔
226
                return nil, ctx.failRes(ResultAddressMismatch), nil
×
227
        }
×
228

229
        // Don't accept zero-valued sets.
230
        if totalAmt == 0 {
654✔
231
                return nil, ctx.failRes(ResultHtlcSetTotalTooLow), nil
×
232
        }
×
233

234
        // Check that the total amt of the htlc set is high enough. In case this
235
        // is a zero-valued invoice, it will always be enough.
236
        if totalAmt < inv.Terms.Value {
654✔
237
                return nil, ctx.failRes(ResultHtlcSetTotalTooLow), nil
×
238
        }
×
239

240
        htlcSet := inv.HTLCSet(setID, HtlcStateAccepted)
654✔
241

654✔
242
        // Check whether total amt matches other HTLCs in the set.
654✔
243
        var newSetTotal lnwire.MilliSatoshi
654✔
244
        for _, htlc := range htlcSet {
984✔
245
                if totalAmt != htlc.MppTotalAmt {
330✔
246
                        return nil, ctx.failRes(ResultHtlcSetTotalMismatch), nil
×
247
                }
×
248

249
                newSetTotal += htlc.Amt
330✔
250
        }
251

252
        // Add amount of new htlc.
253
        newSetTotal += ctx.amtPaid
654✔
254

654✔
255
        // The invoice is still open. Check the expiry.
654✔
256
        if ctx.expiry < uint32(ctx.currentHeight+ctx.finalCltvRejectDelta) {
654✔
257
                return nil, ctx.failRes(ResultExpiryTooSoon), nil
×
258
        }
×
259

260
        if ctx.expiry < uint32(ctx.currentHeight+inv.Terms.FinalCltvDelta) {
654✔
261
                return nil, ctx.failRes(ResultExpiryTooSoon), nil
×
262
        }
×
263

264
        if setID != nil && *setID == BlankPayAddr {
654✔
265
                return nil, ctx.failRes(ResultAmpError), nil
×
266
        }
×
267

268
        // Record HTLC in the invoice database.
269
        newHtlcs := map[CircuitKey]*HtlcAcceptDesc{
654✔
270
                ctx.circuitKey: acceptDesc,
654✔
271
        }
654✔
272

654✔
273
        update := InvoiceUpdateDesc{
654✔
274
                UpdateType: AddHTLCsUpdate,
654✔
275
                AddHtlcs:   newHtlcs,
654✔
276
        }
654✔
277

654✔
278
        // If the invoice cannot be settled yet, only record the htlc.
654✔
279
        setComplete := newSetTotal >= totalAmt
654✔
280
        if !setComplete {
984✔
281
                return &update, ctx.acceptRes(resultPartialAccepted), nil
330✔
282
        }
330✔
283

284
        // Check to see if we can settle or this is a hold invoice, and
285
        // we need to wait for the preimage.
286
        if inv.HodlInvoice {
333✔
287
                update.State = &InvoiceStateUpdateDesc{
9✔
288
                        NewState: ContractAccepted,
9✔
289
                }
9✔
290
                return &update, ctx.acceptRes(resultAccepted), nil
9✔
291
        }
9✔
292

293
        var (
315✔
294
                htlcPreimages map[CircuitKey]lntypes.Preimage
315✔
295
                htlcPreimage  lntypes.Preimage
315✔
296
        )
315✔
297
        if ctx.amp != nil {
327✔
298
                var failRes *HtlcFailResolution
12✔
299
                htlcPreimages, failRes = reconstructAMPPreimages(ctx, htlcSet)
12✔
300
                if failRes != nil {
18✔
301
                        update.UpdateType = CancelInvoiceUpdate
6✔
302
                        update.State = &InvoiceStateUpdateDesc{
6✔
303
                                NewState: ContractCanceled,
6✔
304
                                SetID:    setID,
6✔
305
                        }
6✔
306
                        return &update, failRes, nil
6✔
307
                }
6✔
308

309
                // The preimage for _this_ HTLC will be the one with context's
310
                // circuit key.
311
                htlcPreimage = htlcPreimages[ctx.circuitKey]
6✔
312
        } else {
303✔
313
                htlcPreimage = *inv.Terms.PaymentPreimage
303✔
314
        }
303✔
315

316
        update.State = &InvoiceStateUpdateDesc{
309✔
317
                NewState:      ContractSettled,
309✔
318
                Preimage:      inv.Terms.PaymentPreimage,
309✔
319
                HTLCPreimages: htlcPreimages,
309✔
320
                SetID:         setID,
309✔
321
        }
309✔
322

309✔
323
        return &update, ctx.settleRes(htlcPreimage, ResultSettled), nil
309✔
324
}
325

326
// HTLCSet is a map of CircuitKey to InvoiceHTLC.
327
type HTLCSet = map[CircuitKey]*InvoiceHTLC
328

329
// HTLCPreimages is a map of CircuitKey to preimage.
330
type HTLCPreimages = map[CircuitKey]lntypes.Preimage
331

332
// reconstructAMPPreimages reconstructs the root seed for an AMP HTLC set and
333
// verifies that all derived child hashes match the payment hashes of the HTLCs
334
// in the set. This method is meant to be called after receiving the full amount
335
// committed to via mpp_total_msat. This method will return a fail resolution if
336
// any of the child hashes fail to match their corresponding HTLCs.
337
func reconstructAMPPreimages(ctx *invoiceUpdateCtx,
338
        htlcSet HTLCSet) (HTLCPreimages, *HtlcFailResolution) {
12✔
339

12✔
340
        // Create a slice containing all the child descriptors to be used for
12✔
341
        // reconstruction. This should include all HTLCs currently in the HTLC
12✔
342
        // set, plus the incoming HTLC.
12✔
343
        childDescs := make([]amp.ChildDesc, 0, 1+len(htlcSet))
12✔
344

12✔
345
        // Add the new HTLC's child descriptor at index 0.
12✔
346
        childDescs = append(childDescs, amp.ChildDesc{
12✔
347
                Share: ctx.amp.RootShare(),
12✔
348
                Index: ctx.amp.ChildIndex(),
12✔
349
        })
12✔
350

12✔
351
        // Next, construct an index mapping the position in childDescs to a
12✔
352
        // circuit key for all preexisting HTLCs.
12✔
353
        indexToCircuitKey := make(map[int]CircuitKey)
12✔
354

12✔
355
        // Add the child descriptor for each HTLC in the HTLC set, recording
12✔
356
        // it's position within the slice.
12✔
357
        var htlcSetIndex int
12✔
358
        for circuitKey, htlc := range htlcSet {
24✔
359
                childDescs = append(childDescs, amp.ChildDesc{
12✔
360
                        Share: htlc.AMP.Record.RootShare(),
12✔
361
                        Index: htlc.AMP.Record.ChildIndex(),
12✔
362
                })
12✔
363
                indexToCircuitKey[htlcSetIndex] = circuitKey
12✔
364
                htlcSetIndex++
12✔
365
        }
12✔
366

367
        // Using the child descriptors, reconstruct the root seed and derive the
368
        // child hash/preimage pairs for each of the HTLCs.
369
        children := amp.ReconstructChildren(childDescs...)
12✔
370

12✔
371
        // Validate that the derived child preimages match the hash of each
12✔
372
        // HTLC's respective hash.
12✔
373
        if ctx.hash != children[0].Hash {
18✔
374
                return nil, ctx.failRes(ResultAmpReconstruction)
6✔
375
        }
6✔
376
        for idx, child := range children[1:] {
12✔
377
                circuitKey := indexToCircuitKey[idx]
6✔
378
                htlc := htlcSet[circuitKey]
6✔
379
                if htlc.AMP.Hash != child.Hash {
6✔
380
                        return nil, ctx.failRes(ResultAmpReconstruction)
×
381
                }
×
382
        }
383

384
        // Finally, construct the map of learned preimages indexed by circuit
385
        // key, so that they can be persisted along with each HTLC when updating
386
        // the invoice.
387
        htlcPreimages := make(map[CircuitKey]lntypes.Preimage)
6✔
388
        htlcPreimages[ctx.circuitKey] = children[0].Preimage
6✔
389
        for idx, child := range children[1:] {
12✔
390
                circuitKey := indexToCircuitKey[idx]
6✔
391
                htlcPreimages[circuitKey] = child.Preimage
6✔
392
        }
6✔
393

394
        return htlcPreimages, nil
6✔
395
}
396

397
// updateLegacy is a callback for DB.UpdateInvoice that contains the invoice
398
// settlement logic for legacy payments.
399
//
400
// NOTE: This function is only kept in place in order to be able to handle key
401
// send payments and any invoices we created in the past that are valid and
402
// still had the optional mpp bit set.
403
func updateLegacy(ctx *invoiceUpdateCtx,
404
        inv *Invoice) (*InvoiceUpdateDesc, HtlcResolution, error) {
681✔
405

681✔
406
        // If the invoice is already canceled, there is no further
681✔
407
        // checking to do.
681✔
408
        if inv.State == ContractCanceled {
685✔
409
                return nil, ctx.failRes(ResultInvoiceAlreadyCanceled), nil
4✔
410
        }
4✔
411

412
        // If an invoice amount is specified, check that enough is paid. Also
413
        // check this for duplicate payments if the invoice is already settled
414
        // or accepted. In case this is a zero-valued invoice, it will always be
415
        // enough.
416
        if ctx.amtPaid < inv.Terms.Value {
680✔
417
                return nil, ctx.failRes(ResultAmountTooLow), nil
3✔
418
        }
3✔
419

420
        // If the invoice had the required feature bit set at this point, then
421
        // if we're in this method it means that the remote party didn't supply
422
        // the expected payload. However if this is a keysend payment, then
423
        // we'll permit it to pass.
424
        _, isKeySend := ctx.customRecords[record.KeySendType]
674✔
425
        invoiceFeatures := inv.Terms.Features
674✔
426
        paymentAddrRequired := invoiceFeatures.RequiresFeature(
674✔
427
                lnwire.PaymentAddrRequired,
674✔
428
        )
674✔
429
        if !isKeySend && paymentAddrRequired {
677✔
430
                log.Warnf("Payment to pay_hash=%v doesn't include MPP "+
3✔
431
                        "payload, rejecting", ctx.hash)
3✔
432
                return nil, ctx.failRes(ResultAddressMismatch), nil
3✔
433
        }
3✔
434

435
        // Don't allow settling the invoice with an old style
436
        // htlc if we are already in the process of gathering an
437
        // mpp set.
438
        for _, htlc := range inv.HTLCSet(nil, HtlcStateAccepted) {
674✔
439
                if htlc.MppTotalAmt > 0 {
3✔
440
                        return nil, ctx.failRes(ResultMppInProgress), nil
×
441
                }
×
442
        }
443

444
        // The invoice is still open. Check the expiry.
445
        if ctx.expiry < uint32(ctx.currentHeight+ctx.finalCltvRejectDelta) {
677✔
446
                return nil, ctx.failRes(ResultExpiryTooSoon), nil
6✔
447
        }
6✔
448

449
        if ctx.expiry < uint32(ctx.currentHeight+inv.Terms.FinalCltvDelta) {
666✔
450
                return nil, ctx.failRes(ResultExpiryTooSoon), nil
1✔
451
        }
1✔
452

453
        // For storage, we don't really care where the custom records came from.
454
        // So we merge them together and store them in the same field.
455
        customRecords := lnwire.CustomRecords(
664✔
456
                ctx.customRecords,
664✔
457
        ).MergedCopy(ctx.wireCustomRecords)
664✔
458

664✔
459
        // Record HTLC in the invoice database.
664✔
460
        newHtlcs := map[CircuitKey]*HtlcAcceptDesc{
664✔
461
                ctx.circuitKey: {
664✔
462
                        Amt:           ctx.amtPaid,
664✔
463
                        Expiry:        ctx.expiry,
664✔
464
                        AcceptHeight:  ctx.currentHeight,
664✔
465
                        CustomRecords: record.CustomSet(customRecords),
664✔
466
                },
664✔
467
        }
664✔
468

664✔
469
        update := InvoiceUpdateDesc{
664✔
470
                AddHtlcs:   newHtlcs,
664✔
471
                UpdateType: AddHTLCsUpdate,
664✔
472
        }
664✔
473

664✔
474
        // Don't update invoice state if we are accepting a duplicate payment.
664✔
475
        // We do accept or settle the HTLC.
664✔
476
        switch inv.State {
664✔
477
        case ContractAccepted:
×
478
                return &update, ctx.acceptRes(resultDuplicateToAccepted), nil
×
479

480
        case ContractSettled:
3✔
481
                return &update, ctx.settleRes(
3✔
482
                        *inv.Terms.PaymentPreimage, ResultDuplicateToSettled,
3✔
483
                ), nil
3✔
484
        }
485

486
        // Check to see if we can settle or this is an hold invoice and we need
487
        // to wait for the preimage.
488
        if inv.HodlInvoice {
1,167✔
489
                update.State = &InvoiceStateUpdateDesc{
506✔
490
                        NewState: ContractAccepted,
506✔
491
                }
506✔
492

506✔
493
                return &update, ctx.acceptRes(resultAccepted), nil
506✔
494
        }
506✔
495

496
        update.State = &InvoiceStateUpdateDesc{
155✔
497
                NewState: ContractSettled,
155✔
498
                Preimage: inv.Terms.PaymentPreimage,
155✔
499
        }
155✔
500

155✔
501
        return &update, ctx.settleRes(
155✔
502
                *inv.Terms.PaymentPreimage, ResultSettled,
155✔
503
        ), nil
155✔
504
}
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