-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
update.go
463 lines (387 loc) · 14 KB
/
update.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
package invoices
import (
"encoding/hex"
"errors"
"github.com/lightningnetwork/lnd/amp"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/record"
)
// invoiceUpdateCtx is an object that describes the context for the invoice
// update to be carried out.
type invoiceUpdateCtx struct {
hash lntypes.Hash
circuitKey CircuitKey
amtPaid lnwire.MilliSatoshi
expiry uint32
currentHeight int32
finalCltvRejectDelta int32
customRecords record.CustomSet
mpp *record.MPP
amp *record.AMP
metadata []byte
}
// invoiceRef returns an identifier that can be used to lookup or update the
// invoice this HTLC is targeting.
func (i *invoiceUpdateCtx) invoiceRef() InvoiceRef {
switch {
case i.amp != nil && i.mpp != nil:
payAddr := i.mpp.PaymentAddr()
return InvoiceRefByAddr(payAddr)
case i.mpp != nil:
payAddr := i.mpp.PaymentAddr()
return InvoiceRefByHashAndAddr(i.hash, payAddr)
default:
return InvoiceRefByHash(i.hash)
}
}
// setID returns an identifier that identifies other possible HTLCs that this
// particular one is related to. If nil is returned this means the HTLC is an
// MPP or legacy payment, otherwise the HTLC belongs AMP payment.
func (i invoiceUpdateCtx) setID() *[32]byte {
if i.amp != nil {
setID := i.amp.SetID()
return &setID
}
return nil
}
// log logs a message specific to this update context.
func (i *invoiceUpdateCtx) log(s string) {
// Don't use %x in the log statement below, because it doesn't
// distinguish between nil and empty metadata.
metadata := "<nil>"
if i.metadata != nil {
metadata = hex.EncodeToString(i.metadata)
}
log.Debugf("Invoice%v: %v, amt=%v, expiry=%v, circuit=%v, mpp=%v, "+
"amp=%v, metadata=%v", i.invoiceRef(), s, i.amtPaid, i.expiry,
i.circuitKey, i.mpp, i.amp, metadata)
}
// failRes is a helper function which creates a failure resolution with
// the information contained in the invoiceUpdateCtx and the fail resolution
// result provided.
func (i invoiceUpdateCtx) failRes(outcome FailResolutionResult) *HtlcFailResolution {
return NewFailResolution(i.circuitKey, i.currentHeight, outcome)
}
// settleRes is a helper function which creates a settle resolution with
// the information contained in the invoiceUpdateCtx and the preimage and
// the settle resolution result provided.
func (i invoiceUpdateCtx) settleRes(preimage lntypes.Preimage,
outcome SettleResolutionResult) *HtlcSettleResolution {
return NewSettleResolution(
preimage, i.circuitKey, i.currentHeight, outcome,
)
}
// acceptRes is a helper function which creates an accept resolution with
// the information contained in the invoiceUpdateCtx and the accept resolution
// result provided.
func (i invoiceUpdateCtx) acceptRes(outcome acceptResolutionResult) *htlcAcceptResolution {
return newAcceptResolution(i.circuitKey, outcome)
}
// updateInvoice is a callback for DB.UpdateInvoice that contains the invoice
// settlement logic. It returns a hltc resolution that indicates what the
// outcome of the update was.
func updateInvoice(ctx *invoiceUpdateCtx, inv *Invoice) (
*InvoiceUpdateDesc, HtlcResolution, error) {
// Don't update the invoice when this is a replayed htlc.
htlc, ok := inv.Htlcs[ctx.circuitKey]
if ok {
switch htlc.State {
case HtlcStateCanceled:
return nil, ctx.failRes(ResultReplayToCanceled), nil
case HtlcStateAccepted:
return nil, ctx.acceptRes(resultReplayToAccepted), nil
case HtlcStateSettled:
pre := inv.Terms.PaymentPreimage
// Terms.PaymentPreimage will be nil for AMP invoices.
// Set it to the HTLC's AMP Preimage instead.
if pre == nil {
pre = htlc.AMP.Preimage
}
return nil, ctx.settleRes(
*pre,
ResultReplayToSettled,
), nil
default:
return nil, nil, errors.New("unknown htlc state")
}
}
// If no MPP payload was provided, then we expect this to be a keysend,
// or a payment to an invoice created before we started to require the
// MPP payload.
if ctx.mpp == nil {
return updateLegacy(ctx, inv)
}
return updateMpp(ctx, inv)
}
// updateMpp is a callback for DB.UpdateInvoice that contains the invoice
// settlement logic for mpp payments.
func updateMpp(ctx *invoiceUpdateCtx, inv *Invoice) (*InvoiceUpdateDesc,
HtlcResolution, error) {
// Reject HTLCs to AMP invoices if they are missing an AMP payload, and
// HTLCs to MPP invoices if they have an AMP payload.
switch {
case inv.Terms.Features.RequiresFeature(lnwire.AMPRequired) &&
ctx.amp == nil:
return nil, ctx.failRes(ResultHtlcInvoiceTypeMismatch), nil
case !inv.Terms.Features.RequiresFeature(lnwire.AMPRequired) &&
ctx.amp != nil:
return nil, ctx.failRes(ResultHtlcInvoiceTypeMismatch), nil
}
setID := ctx.setID()
// Start building the accept descriptor.
acceptDesc := &HtlcAcceptDesc{
Amt: ctx.amtPaid,
Expiry: ctx.expiry,
AcceptHeight: ctx.currentHeight,
MppTotalAmt: ctx.mpp.TotalMsat(),
CustomRecords: ctx.customRecords,
}
if ctx.amp != nil {
acceptDesc.AMP = &InvoiceHtlcAMPData{
Record: *ctx.amp,
Hash: ctx.hash,
Preimage: nil,
}
}
// Only accept payments to open invoices. This behaviour differs from
// non-mpp payments that are accepted even after the invoice is settled.
// Because non-mpp payments don't have a payment address, this is needed
// to thwart probing.
if inv.State != ContractOpen {
return nil, ctx.failRes(ResultInvoiceNotOpen), nil
}
// Check the payment address that authorizes the payment.
if ctx.mpp.PaymentAddr() != inv.Terms.PaymentAddr {
return nil, ctx.failRes(ResultAddressMismatch), nil
}
// Don't accept zero-valued sets.
if ctx.mpp.TotalMsat() == 0 {
return nil, ctx.failRes(ResultHtlcSetTotalTooLow), nil
}
// Check that the total amt of the htlc set is high enough. In case this
// is a zero-valued invoice, it will always be enough.
if ctx.mpp.TotalMsat() < inv.Terms.Value {
return nil, ctx.failRes(ResultHtlcSetTotalTooLow), nil
}
htlcSet := inv.HTLCSet(setID, HtlcStateAccepted)
// Check whether total amt matches other htlcs in the set.
var newSetTotal lnwire.MilliSatoshi
for _, htlc := range htlcSet {
if ctx.mpp.TotalMsat() != htlc.MppTotalAmt {
return nil, ctx.failRes(ResultHtlcSetTotalMismatch), nil
}
newSetTotal += htlc.Amt
}
// Add amount of new htlc.
newSetTotal += ctx.amtPaid
// Make sure the communicated set total isn't overpaid.
if newSetTotal > ctx.mpp.TotalMsat() {
return nil, ctx.failRes(ResultHtlcSetOverpayment), nil
}
// The invoice is still open. Check the expiry.
if ctx.expiry < uint32(ctx.currentHeight+ctx.finalCltvRejectDelta) {
return nil, ctx.failRes(ResultExpiryTooSoon), nil
}
if ctx.expiry < uint32(ctx.currentHeight+inv.Terms.FinalCltvDelta) {
return nil, ctx.failRes(ResultExpiryTooSoon), nil
}
if setID != nil && *setID == BlankPayAddr {
return nil, ctx.failRes(ResultAmpError), nil
}
// Record HTLC in the invoice database.
newHtlcs := map[CircuitKey]*HtlcAcceptDesc{
ctx.circuitKey: acceptDesc,
}
update := InvoiceUpdateDesc{
AddHtlcs: newHtlcs,
}
// If the invoice cannot be settled yet, only record the htlc.
setComplete := newSetTotal == ctx.mpp.TotalMsat()
if !setComplete {
return &update, ctx.acceptRes(resultPartialAccepted), nil
}
// Check to see if we can settle or this is an hold invoice and
// we need to wait for the preimage.
if inv.HodlInvoice {
update.State = &InvoiceStateUpdateDesc{
NewState: ContractAccepted,
SetID: setID,
}
return &update, ctx.acceptRes(resultAccepted), nil
}
var (
htlcPreimages map[CircuitKey]lntypes.Preimage
htlcPreimage lntypes.Preimage
)
if ctx.amp != nil {
var failRes *HtlcFailResolution
htlcPreimages, failRes = reconstructAMPPreimages(ctx, htlcSet)
if failRes != nil {
update.State = &InvoiceStateUpdateDesc{
NewState: ContractCanceled,
SetID: setID,
}
return &update, failRes, nil
}
// The preimage for _this_ HTLC will be the one with context's
// circuit key.
htlcPreimage = htlcPreimages[ctx.circuitKey]
} else {
htlcPreimage = *inv.Terms.PaymentPreimage
}
update.State = &InvoiceStateUpdateDesc{
NewState: ContractSettled,
Preimage: inv.Terms.PaymentPreimage,
HTLCPreimages: htlcPreimages,
SetID: setID,
}
return &update, ctx.settleRes(htlcPreimage, ResultSettled), nil
}
// HTLCSet is a map of CircuitKey to InvoiceHTLC.
type HTLCSet = map[CircuitKey]*InvoiceHTLC
// HTLCPreimages is a map of CircuitKey to preimage.
type HTLCPreimages = map[CircuitKey]lntypes.Preimage
// reconstructAMPPreimages reconstructs the root seed for an AMP HTLC set and
// verifies that all derived child hashes match the payment hashes of the HTLCs
// in the set. This method is meant to be called after receiving the full amount
// committed to via mpp_total_msat. This method will return a fail resolution if
// any of the child hashes fail to match their corresponding HTLCs.
func reconstructAMPPreimages(ctx *invoiceUpdateCtx,
htlcSet HTLCSet) (HTLCPreimages, *HtlcFailResolution) {
// Create a slice containing all the child descriptors to be used for
// reconstruction. This should include all HTLCs currently in the HTLC
// set, plus the incoming HTLC.
childDescs := make([]amp.ChildDesc, 0, 1+len(htlcSet))
// Add the new HTLC's child descriptor at index 0.
childDescs = append(childDescs, amp.ChildDesc{
Share: ctx.amp.RootShare(),
Index: ctx.amp.ChildIndex(),
})
// Next, construct an index mapping the position in childDescs to a
// circuit key for all preexisting HTLCs.
indexToCircuitKey := make(map[int]CircuitKey)
// Add the child descriptor for each HTLC in the HTLC set, recording
// it's position within the slice.
var htlcSetIndex int
for circuitKey, htlc := range htlcSet {
childDescs = append(childDescs, amp.ChildDesc{
Share: htlc.AMP.Record.RootShare(),
Index: htlc.AMP.Record.ChildIndex(),
})
indexToCircuitKey[htlcSetIndex] = circuitKey
htlcSetIndex++
}
// Using the child descriptors, reconstruct the root seed and derive the
// child hash/preimage pairs for each of the HTLCs.
children := amp.ReconstructChildren(childDescs...)
// Validate that the derived child preimages match the hash of each
// HTLC's respective hash.
if ctx.hash != children[0].Hash {
return nil, ctx.failRes(ResultAmpReconstruction)
}
for idx, child := range children[1:] {
circuitKey := indexToCircuitKey[idx]
htlc := htlcSet[circuitKey]
if htlc.AMP.Hash != child.Hash {
return nil, ctx.failRes(ResultAmpReconstruction)
}
}
// Finally, construct the map of learned preimages indexed by circuit
// key, so that they can be persisted along with each HTLC when updating
// the invoice.
htlcPreimages := make(map[CircuitKey]lntypes.Preimage)
htlcPreimages[ctx.circuitKey] = children[0].Preimage
for idx, child := range children[1:] {
circuitKey := indexToCircuitKey[idx]
htlcPreimages[circuitKey] = child.Preimage
}
return htlcPreimages, nil
}
// updateLegacy is a callback for DB.UpdateInvoice that contains the invoice
// settlement logic for legacy payments.
//
// NOTE: This function is only kept in place in order to be able to handle key
// send payments and any invoices we created in the past that are valid and
// still had the optional mpp bit set.
func updateLegacy(ctx *invoiceUpdateCtx,
inv *Invoice) (*InvoiceUpdateDesc, HtlcResolution, error) {
// If the invoice is already canceled, there is no further
// checking to do.
if inv.State == ContractCanceled {
return nil, ctx.failRes(ResultInvoiceAlreadyCanceled), nil
}
// If an invoice amount is specified, check that enough is paid. Also
// check this for duplicate payments if the invoice is already settled
// or accepted. In case this is a zero-valued invoice, it will always be
// enough.
if ctx.amtPaid < inv.Terms.Value {
return nil, ctx.failRes(ResultAmountTooLow), nil
}
// If the invoice had the required feature bit set at this point, then
// if we're in this method it means that the remote party didn't supply
// the expected payload. However if this is a keysend payment, then
// we'll permit it to pass.
_, isKeySend := ctx.customRecords[record.KeySendType]
invoiceFeatures := inv.Terms.Features
paymentAddrRequired := invoiceFeatures.RequiresFeature(
lnwire.PaymentAddrRequired,
)
if !isKeySend && paymentAddrRequired {
log.Warnf("Payment to pay_hash=%v doesn't include MPP "+
"payload, rejecting", ctx.hash)
return nil, ctx.failRes(ResultAddressMismatch), nil
}
// Don't allow settling the invoice with an old style
// htlc if we are already in the process of gathering an
// mpp set.
for _, htlc := range inv.HTLCSet(nil, HtlcStateAccepted) {
if htlc.MppTotalAmt > 0 {
return nil, ctx.failRes(ResultMppInProgress), nil
}
}
// The invoice is still open. Check the expiry.
if ctx.expiry < uint32(ctx.currentHeight+ctx.finalCltvRejectDelta) {
return nil, ctx.failRes(ResultExpiryTooSoon), nil
}
if ctx.expiry < uint32(ctx.currentHeight+inv.Terms.FinalCltvDelta) {
return nil, ctx.failRes(ResultExpiryTooSoon), nil
}
// Record HTLC in the invoice database.
newHtlcs := map[CircuitKey]*HtlcAcceptDesc{
ctx.circuitKey: {
Amt: ctx.amtPaid,
Expiry: ctx.expiry,
AcceptHeight: ctx.currentHeight,
CustomRecords: ctx.customRecords,
},
}
update := InvoiceUpdateDesc{
AddHtlcs: newHtlcs,
}
// Don't update invoice state if we are accepting a duplicate payment.
// We do accept or settle the HTLC.
switch inv.State {
case ContractAccepted:
return &update, ctx.acceptRes(resultDuplicateToAccepted), nil
case ContractSettled:
return &update, ctx.settleRes(
*inv.Terms.PaymentPreimage, ResultDuplicateToSettled,
), nil
}
// Check to see if we can settle or this is an hold invoice and we need
// to wait for the preimage.
if inv.HodlInvoice {
update.State = &InvoiceStateUpdateDesc{
NewState: ContractAccepted,
}
return &update, ctx.acceptRes(resultAccepted), nil
}
update.State = &InvoiceStateUpdateDesc{
NewState: ContractSettled,
Preimage: inv.Terms.PaymentPreimage,
}
return &update, ctx.settleRes(
*inv.Terms.PaymentPreimage, ResultSettled,
), nil
}