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

lightningnetwork / lnd / 9915780197

13 Jul 2024 12:30AM UTC coverage: 49.268% (-9.1%) from 58.413%
9915780197

push

github

web-flow
Merge pull request #8653 from ProofOfKeags/fn-prim

DynComms [0/n]: `fn` package additions

92837 of 188433 relevant lines covered (49.27%)

1.55 hits per line

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

78.01
/invoices/update.go
1
package invoices
2

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

7
        "github.com/lightningnetwork/lnd/amp"
8
        "github.com/lightningnetwork/lnd/lntypes"
9
        "github.com/lightningnetwork/lnd/lnwire"
10
        "github.com/lightningnetwork/lnd/record"
11
)
12

13
// invoiceUpdateCtx is an object that describes the context for the invoice
14
// update to be carried out.
15
type invoiceUpdateCtx struct {
16
        hash                 lntypes.Hash
17
        circuitKey           CircuitKey
18
        amtPaid              lnwire.MilliSatoshi
19
        expiry               uint32
20
        currentHeight        int32
21
        finalCltvRejectDelta int32
22
        customRecords        record.CustomSet
23
        mpp                  *record.MPP
24
        amp                  *record.AMP
25
        metadata             []byte
26
}
27

28
// invoiceRef returns an identifier that can be used to lookup or update the
29
// invoice this HTLC is targeting.
30
func (i *invoiceUpdateCtx) invoiceRef() InvoiceRef {
3✔
31
        switch {
3✔
32
        case i.amp != nil && i.mpp != nil:
3✔
33
                payAddr := i.mpp.PaymentAddr()
3✔
34
                return InvoiceRefByAddr(payAddr)
3✔
35

36
        case i.mpp != nil:
3✔
37
                payAddr := i.mpp.PaymentAddr()
3✔
38
                return InvoiceRefByHashAndAddr(i.hash, payAddr)
3✔
39

40
        default:
3✔
41
                return InvoiceRefByHash(i.hash)
3✔
42
        }
43
}
44

45
// setID returns an identifier that identifies other possible HTLCs that this
46
// particular one is related to. If nil is returned this means the HTLC is an
47
// MPP or legacy payment, otherwise the HTLC belongs AMP payment.
48
func (i invoiceUpdateCtx) setID() *[32]byte {
3✔
49
        if i.amp != nil {
6✔
50
                setID := i.amp.SetID()
3✔
51
                return &setID
3✔
52
        }
3✔
53
        return nil
3✔
54
}
55

56
// log logs a message specific to this update context.
57
func (i *invoiceUpdateCtx) log(s string) {
3✔
58
        // Don't use %x in the log statement below, because it doesn't
3✔
59
        // distinguish between nil and empty metadata.
3✔
60
        metadata := "<nil>"
3✔
61
        if i.metadata != nil {
3✔
62
                metadata = hex.EncodeToString(i.metadata)
×
63
        }
×
64

65
        log.Debugf("Invoice%v: %v, amt=%v, expiry=%v, circuit=%v, mpp=%v, "+
3✔
66
                "amp=%v, metadata=%v", i.invoiceRef(), s, i.amtPaid, i.expiry,
3✔
67
                i.circuitKey, i.mpp, i.amp, metadata)
3✔
68
}
69

70
// failRes is a helper function which creates a failure resolution with
71
// the information contained in the invoiceUpdateCtx and the fail resolution
72
// result provided.
73
func (i invoiceUpdateCtx) failRes(outcome FailResolutionResult) *HtlcFailResolution {
3✔
74
        return NewFailResolution(i.circuitKey, i.currentHeight, outcome)
3✔
75
}
3✔
76

77
// settleRes is a helper function which creates a settle resolution with
78
// the information contained in the invoiceUpdateCtx and the preimage and
79
// the settle resolution result provided.
80
func (i invoiceUpdateCtx) settleRes(preimage lntypes.Preimage,
81
        outcome SettleResolutionResult) *HtlcSettleResolution {
3✔
82

3✔
83
        return NewSettleResolution(
3✔
84
                preimage, i.circuitKey, i.currentHeight, outcome,
3✔
85
        )
3✔
86
}
3✔
87

88
// acceptRes is a helper function which creates an accept resolution with
89
// the information contained in the invoiceUpdateCtx and the accept resolution
90
// result provided.
91
func (i invoiceUpdateCtx) acceptRes(outcome acceptResolutionResult) *htlcAcceptResolution {
3✔
92
        return newAcceptResolution(i.circuitKey, outcome)
3✔
93
}
3✔
94

95
// updateInvoice is a callback for DB.UpdateInvoice that contains the invoice
96
// settlement logic. It returns a hltc resolution that indicates what the
97
// outcome of the update was.
98
func updateInvoice(ctx *invoiceUpdateCtx, inv *Invoice) (
99
        *InvoiceUpdateDesc, HtlcResolution, error) {
3✔
100

3✔
101
        // Don't update the invoice when this is a replayed htlc.
3✔
102
        htlc, ok := inv.Htlcs[ctx.circuitKey]
3✔
103
        if ok {
6✔
104
                switch htlc.State {
3✔
105
                case HtlcStateCanceled:
3✔
106
                        return nil, ctx.failRes(ResultReplayToCanceled), nil
3✔
107

108
                case HtlcStateAccepted:
3✔
109
                        return nil, ctx.acceptRes(resultReplayToAccepted), nil
3✔
110

111
                case HtlcStateSettled:
3✔
112
                        pre := inv.Terms.PaymentPreimage
3✔
113

3✔
114
                        // Terms.PaymentPreimage will be nil for AMP invoices.
3✔
115
                        // Set it to the HTLC's AMP Preimage instead.
3✔
116
                        if pre == nil {
3✔
117
                                pre = htlc.AMP.Preimage
×
118
                        }
×
119

120
                        return nil, ctx.settleRes(
3✔
121
                                *pre,
3✔
122
                                ResultReplayToSettled,
3✔
123
                        ), nil
3✔
124

125
                default:
×
126
                        return nil, nil, errors.New("unknown htlc state")
×
127
                }
128
        }
129

130
        // If no MPP payload was provided, then we expect this to be a keysend,
131
        // or a payment to an invoice created before we started to require the
132
        // MPP payload.
133
        if ctx.mpp == nil {
6✔
134
                return updateLegacy(ctx, inv)
3✔
135
        }
3✔
136

137
        return updateMpp(ctx, inv)
3✔
138
}
139

140
// updateMpp is a callback for DB.UpdateInvoice that contains the invoice
141
// settlement logic for mpp payments.
142
func updateMpp(ctx *invoiceUpdateCtx, inv *Invoice) (*InvoiceUpdateDesc,
143
        HtlcResolution, error) {
3✔
144

3✔
145
        // Reject HTLCs to AMP invoices if they are missing an AMP payload, and
3✔
146
        // HTLCs to MPP invoices if they have an AMP payload.
3✔
147
        switch {
3✔
148
        case inv.Terms.Features.RequiresFeature(lnwire.AMPRequired) &&
149
                ctx.amp == nil:
×
150

×
151
                return nil, ctx.failRes(ResultHtlcInvoiceTypeMismatch), nil
×
152

153
        case !inv.Terms.Features.RequiresFeature(lnwire.AMPRequired) &&
154
                ctx.amp != nil:
×
155

×
156
                return nil, ctx.failRes(ResultHtlcInvoiceTypeMismatch), nil
×
157
        }
158

159
        setID := ctx.setID()
3✔
160

3✔
161
        // Start building the accept descriptor.
3✔
162
        acceptDesc := &HtlcAcceptDesc{
3✔
163
                Amt:           ctx.amtPaid,
3✔
164
                Expiry:        ctx.expiry,
3✔
165
                AcceptHeight:  ctx.currentHeight,
3✔
166
                MppTotalAmt:   ctx.mpp.TotalMsat(),
3✔
167
                CustomRecords: ctx.customRecords,
3✔
168
        }
3✔
169

3✔
170
        if ctx.amp != nil {
6✔
171
                acceptDesc.AMP = &InvoiceHtlcAMPData{
3✔
172
                        Record:   *ctx.amp,
3✔
173
                        Hash:     ctx.hash,
3✔
174
                        Preimage: nil,
3✔
175
                }
3✔
176
        }
3✔
177

178
        // Only accept payments to open invoices. This behaviour differs from
179
        // non-mpp payments that are accepted even after the invoice is settled.
180
        // Because non-mpp payments don't have a payment address, this is needed
181
        // to thwart probing.
182
        if inv.State != ContractOpen {
3✔
183
                return nil, ctx.failRes(ResultInvoiceNotOpen), nil
×
184
        }
×
185

186
        // Check the payment address that authorizes the payment.
187
        if ctx.mpp.PaymentAddr() != inv.Terms.PaymentAddr {
3✔
188
                return nil, ctx.failRes(ResultAddressMismatch), nil
×
189
        }
×
190

191
        // Don't accept zero-valued sets.
192
        if ctx.mpp.TotalMsat() == 0 {
3✔
193
                return nil, ctx.failRes(ResultHtlcSetTotalTooLow), nil
×
194
        }
×
195

196
        // Check that the total amt of the htlc set is high enough. In case this
197
        // is a zero-valued invoice, it will always be enough.
198
        if ctx.mpp.TotalMsat() < inv.Terms.Value {
3✔
199
                return nil, ctx.failRes(ResultHtlcSetTotalTooLow), nil
×
200
        }
×
201

202
        htlcSet := inv.HTLCSet(setID, HtlcStateAccepted)
3✔
203

3✔
204
        // Check whether total amt matches other htlcs in the set.
3✔
205
        var newSetTotal lnwire.MilliSatoshi
3✔
206
        for _, htlc := range htlcSet {
6✔
207
                if ctx.mpp.TotalMsat() != htlc.MppTotalAmt {
3✔
208
                        return nil, ctx.failRes(ResultHtlcSetTotalMismatch), nil
×
209
                }
×
210

211
                newSetTotal += htlc.Amt
3✔
212
        }
213

214
        // Add amount of new htlc.
215
        newSetTotal += ctx.amtPaid
3✔
216

3✔
217
        // The invoice is still open. Check the expiry.
3✔
218
        if ctx.expiry < uint32(ctx.currentHeight+ctx.finalCltvRejectDelta) {
3✔
219
                return nil, ctx.failRes(ResultExpiryTooSoon), nil
×
220
        }
×
221

222
        if ctx.expiry < uint32(ctx.currentHeight+inv.Terms.FinalCltvDelta) {
3✔
223
                return nil, ctx.failRes(ResultExpiryTooSoon), nil
×
224
        }
×
225

226
        if setID != nil && *setID == BlankPayAddr {
3✔
227
                return nil, ctx.failRes(ResultAmpError), nil
×
228
        }
×
229

230
        // Record HTLC in the invoice database.
231
        newHtlcs := map[CircuitKey]*HtlcAcceptDesc{
3✔
232
                ctx.circuitKey: acceptDesc,
3✔
233
        }
3✔
234

3✔
235
        update := InvoiceUpdateDesc{
3✔
236
                UpdateType: AddHTLCsUpdate,
3✔
237
                AddHtlcs:   newHtlcs,
3✔
238
        }
3✔
239

3✔
240
        // If the invoice cannot be settled yet, only record the htlc.
3✔
241
        setComplete := newSetTotal >= ctx.mpp.TotalMsat()
3✔
242
        if !setComplete {
6✔
243
                return &update, ctx.acceptRes(resultPartialAccepted), nil
3✔
244
        }
3✔
245

246
        // Check to see if we can settle or this is an hold invoice and
247
        // we need to wait for the preimage.
248
        if inv.HodlInvoice {
6✔
249
                update.State = &InvoiceStateUpdateDesc{
3✔
250
                        NewState: ContractAccepted,
3✔
251
                }
3✔
252
                return &update, ctx.acceptRes(resultAccepted), nil
3✔
253
        }
3✔
254

255
        var (
3✔
256
                htlcPreimages map[CircuitKey]lntypes.Preimage
3✔
257
                htlcPreimage  lntypes.Preimage
3✔
258
        )
3✔
259
        if ctx.amp != nil {
6✔
260
                var failRes *HtlcFailResolution
3✔
261
                htlcPreimages, failRes = reconstructAMPPreimages(ctx, htlcSet)
3✔
262
                if failRes != nil {
3✔
263
                        update.UpdateType = CancelInvoiceUpdate
×
264
                        update.State = &InvoiceStateUpdateDesc{
×
265
                                NewState: ContractCanceled,
×
266
                                SetID:    setID,
×
267
                        }
×
268
                        return &update, failRes, nil
×
269
                }
×
270

271
                // The preimage for _this_ HTLC will be the one with context's
272
                // circuit key.
273
                htlcPreimage = htlcPreimages[ctx.circuitKey]
3✔
274
        } else {
3✔
275
                htlcPreimage = *inv.Terms.PaymentPreimage
3✔
276
        }
3✔
277

278
        update.State = &InvoiceStateUpdateDesc{
3✔
279
                NewState:      ContractSettled,
3✔
280
                Preimage:      inv.Terms.PaymentPreimage,
3✔
281
                HTLCPreimages: htlcPreimages,
3✔
282
                SetID:         setID,
3✔
283
        }
3✔
284

3✔
285
        return &update, ctx.settleRes(htlcPreimage, ResultSettled), nil
3✔
286
}
287

288
// HTLCSet is a map of CircuitKey to InvoiceHTLC.
289
type HTLCSet = map[CircuitKey]*InvoiceHTLC
290

291
// HTLCPreimages is a map of CircuitKey to preimage.
292
type HTLCPreimages = map[CircuitKey]lntypes.Preimage
293

294
// reconstructAMPPreimages reconstructs the root seed for an AMP HTLC set and
295
// verifies that all derived child hashes match the payment hashes of the HTLCs
296
// in the set. This method is meant to be called after receiving the full amount
297
// committed to via mpp_total_msat. This method will return a fail resolution if
298
// any of the child hashes fail to match their corresponding HTLCs.
299
func reconstructAMPPreimages(ctx *invoiceUpdateCtx,
300
        htlcSet HTLCSet) (HTLCPreimages, *HtlcFailResolution) {
3✔
301

3✔
302
        // Create a slice containing all the child descriptors to be used for
3✔
303
        // reconstruction. This should include all HTLCs currently in the HTLC
3✔
304
        // set, plus the incoming HTLC.
3✔
305
        childDescs := make([]amp.ChildDesc, 0, 1+len(htlcSet))
3✔
306

3✔
307
        // Add the new HTLC's child descriptor at index 0.
3✔
308
        childDescs = append(childDescs, amp.ChildDesc{
3✔
309
                Share: ctx.amp.RootShare(),
3✔
310
                Index: ctx.amp.ChildIndex(),
3✔
311
        })
3✔
312

3✔
313
        // Next, construct an index mapping the position in childDescs to a
3✔
314
        // circuit key for all preexisting HTLCs.
3✔
315
        indexToCircuitKey := make(map[int]CircuitKey)
3✔
316

3✔
317
        // Add the child descriptor for each HTLC in the HTLC set, recording
3✔
318
        // it's position within the slice.
3✔
319
        var htlcSetIndex int
3✔
320
        for circuitKey, htlc := range htlcSet {
6✔
321
                childDescs = append(childDescs, amp.ChildDesc{
3✔
322
                        Share: htlc.AMP.Record.RootShare(),
3✔
323
                        Index: htlc.AMP.Record.ChildIndex(),
3✔
324
                })
3✔
325
                indexToCircuitKey[htlcSetIndex] = circuitKey
3✔
326
                htlcSetIndex++
3✔
327
        }
3✔
328

329
        // Using the child descriptors, reconstruct the root seed and derive the
330
        // child hash/preimage pairs for each of the HTLCs.
331
        children := amp.ReconstructChildren(childDescs...)
3✔
332

3✔
333
        // Validate that the derived child preimages match the hash of each
3✔
334
        // HTLC's respective hash.
3✔
335
        if ctx.hash != children[0].Hash {
3✔
336
                return nil, ctx.failRes(ResultAmpReconstruction)
×
337
        }
×
338
        for idx, child := range children[1:] {
6✔
339
                circuitKey := indexToCircuitKey[idx]
3✔
340
                htlc := htlcSet[circuitKey]
3✔
341
                if htlc.AMP.Hash != child.Hash {
3✔
342
                        return nil, ctx.failRes(ResultAmpReconstruction)
×
343
                }
×
344
        }
345

346
        // Finally, construct the map of learned preimages indexed by circuit
347
        // key, so that they can be persisted along with each HTLC when updating
348
        // the invoice.
349
        htlcPreimages := make(map[CircuitKey]lntypes.Preimage)
3✔
350
        htlcPreimages[ctx.circuitKey] = children[0].Preimage
3✔
351
        for idx, child := range children[1:] {
6✔
352
                circuitKey := indexToCircuitKey[idx]
3✔
353
                htlcPreimages[circuitKey] = child.Preimage
3✔
354
        }
3✔
355

356
        return htlcPreimages, nil
3✔
357
}
358

359
// updateLegacy is a callback for DB.UpdateInvoice that contains the invoice
360
// settlement logic for legacy payments.
361
//
362
// NOTE: This function is only kept in place in order to be able to handle key
363
// send payments and any invoices we created in the past that are valid and
364
// still had the optional mpp bit set.
365
func updateLegacy(ctx *invoiceUpdateCtx,
366
        inv *Invoice) (*InvoiceUpdateDesc, HtlcResolution, error) {
3✔
367

3✔
368
        // If the invoice is already canceled, there is no further
3✔
369
        // checking to do.
3✔
370
        if inv.State == ContractCanceled {
3✔
371
                return nil, ctx.failRes(ResultInvoiceAlreadyCanceled), nil
×
372
        }
×
373

374
        // If an invoice amount is specified, check that enough is paid. Also
375
        // check this for duplicate payments if the invoice is already settled
376
        // or accepted. In case this is a zero-valued invoice, it will always be
377
        // enough.
378
        if ctx.amtPaid < inv.Terms.Value {
6✔
379
                return nil, ctx.failRes(ResultAmountTooLow), nil
3✔
380
        }
3✔
381

382
        // If the invoice had the required feature bit set at this point, then
383
        // if we're in this method it means that the remote party didn't supply
384
        // the expected payload. However if this is a keysend payment, then
385
        // we'll permit it to pass.
386
        _, isKeySend := ctx.customRecords[record.KeySendType]
3✔
387
        invoiceFeatures := inv.Terms.Features
3✔
388
        paymentAddrRequired := invoiceFeatures.RequiresFeature(
3✔
389
                lnwire.PaymentAddrRequired,
3✔
390
        )
3✔
391
        if !isKeySend && paymentAddrRequired {
3✔
392
                log.Warnf("Payment to pay_hash=%v doesn't include MPP "+
×
393
                        "payload, rejecting", ctx.hash)
×
394
                return nil, ctx.failRes(ResultAddressMismatch), nil
×
395
        }
×
396

397
        // Don't allow settling the invoice with an old style
398
        // htlc if we are already in the process of gathering an
399
        // mpp set.
400
        for _, htlc := range inv.HTLCSet(nil, HtlcStateAccepted) {
3✔
401
                if htlc.MppTotalAmt > 0 {
×
402
                        return nil, ctx.failRes(ResultMppInProgress), nil
×
403
                }
×
404
        }
405

406
        // The invoice is still open. Check the expiry.
407
        if ctx.expiry < uint32(ctx.currentHeight+ctx.finalCltvRejectDelta) {
3✔
408
                return nil, ctx.failRes(ResultExpiryTooSoon), nil
×
409
        }
×
410

411
        if ctx.expiry < uint32(ctx.currentHeight+inv.Terms.FinalCltvDelta) {
3✔
412
                return nil, ctx.failRes(ResultExpiryTooSoon), nil
×
413
        }
×
414

415
        // Record HTLC in the invoice database.
416
        newHtlcs := map[CircuitKey]*HtlcAcceptDesc{
3✔
417
                ctx.circuitKey: {
3✔
418
                        Amt:           ctx.amtPaid,
3✔
419
                        Expiry:        ctx.expiry,
3✔
420
                        AcceptHeight:  ctx.currentHeight,
3✔
421
                        CustomRecords: ctx.customRecords,
3✔
422
                },
3✔
423
        }
3✔
424

3✔
425
        update := InvoiceUpdateDesc{
3✔
426
                AddHtlcs:   newHtlcs,
3✔
427
                UpdateType: AddHTLCsUpdate,
3✔
428
        }
3✔
429

3✔
430
        // Don't update invoice state if we are accepting a duplicate payment.
3✔
431
        // We do accept or settle the HTLC.
3✔
432
        switch inv.State {
3✔
433
        case ContractAccepted:
×
434
                return &update, ctx.acceptRes(resultDuplicateToAccepted), nil
×
435

436
        case ContractSettled:
×
437
                return &update, ctx.settleRes(
×
438
                        *inv.Terms.PaymentPreimage, ResultDuplicateToSettled,
×
439
                ), nil
×
440
        }
441

442
        // Check to see if we can settle or this is an hold invoice and we need
443
        // to wait for the preimage.
444
        if inv.HodlInvoice {
3✔
445
                update.State = &InvoiceStateUpdateDesc{
×
446
                        NewState: ContractAccepted,
×
447
                }
×
448

×
449
                return &update, ctx.acceptRes(resultAccepted), nil
×
450
        }
×
451

452
        update.State = &InvoiceStateUpdateDesc{
3✔
453
                NewState: ContractSettled,
3✔
454
                Preimage: inv.Terms.PaymentPreimage,
3✔
455
        }
3✔
456

3✔
457
        return &update, ctx.settleRes(
3✔
458
                *inv.Terms.PaymentPreimage, ResultSettled,
3✔
459
        ), nil
3✔
460
}
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