diff --git a/channeldb/invoice_test.go b/channeldb/invoice_test.go index 38674b6be..1520fcee5 100644 --- a/channeldb/invoice_test.go +++ b/channeldb/invoice_test.go @@ -479,6 +479,7 @@ func TestInvoiceCancelSingleHtlc(t *testing.T) { invoice *invpkg.Invoice) (*invpkg.InvoiceUpdateDesc, error) { return &invpkg.InvoiceUpdateDesc{ + UpdateType: invpkg.CancelHTLCsUpdate, CancelHtlcs: map[models.CircuitKey]struct{}{ key: {}, }, @@ -554,6 +555,7 @@ func TestInvoiceCancelSingleHtlcAMP(t *testing.T) { invoice *invpkg.Invoice) (*invpkg.InvoiceUpdateDesc, error) { return &invpkg.InvoiceUpdateDesc{ + UpdateType: invpkg.CancelHTLCsUpdate, CancelHtlcs: map[models.CircuitKey]struct{}{ {HtlcID: 0}: {}, }, @@ -616,6 +618,7 @@ func TestInvoiceCancelSingleHtlcAMP(t *testing.T) { error) { return &invpkg.InvoiceUpdateDesc{ + UpdateType: invpkg.CancelHTLCsUpdate, CancelHtlcs: map[models.CircuitKey]struct{}{ {HtlcID: 1}: {}, }, @@ -643,6 +646,7 @@ func TestInvoiceCancelSingleHtlcAMP(t *testing.T) { invoice *invpkg.Invoice) (*invpkg.InvoiceUpdateDesc, error) { return &invpkg.InvoiceUpdateDesc{ + UpdateType: invpkg.CancelHTLCsUpdate, CancelHtlcs: map[models.CircuitKey]struct{}{ {HtlcID: 2}: {}, }, diff --git a/channeldb/invoices.go b/channeldb/invoices.go index b4ce3ba39..4ab378042 100644 --- a/channeldb/invoices.go +++ b/channeldb/invoices.go @@ -1893,9 +1893,13 @@ func (d *DB) updateInvoice(hash *lntypes.Hash, refSetID *invpkg.SetID, invoices, return &invoice, nil } + switch update.UpdateType { + case invpkg.CancelHTLCsUpdate: + return d.cancelHTLCs(invoices, invoiceNum, &invoice, update) + } + var ( - newState = invoice.State - setID *[32]byte + setID *[32]byte ) // We can either get the set ID from the main state update (if the @@ -1903,7 +1907,6 @@ func (d *DB) updateInvoice(hash *lntypes.Hash, refSetID *invpkg.SetID, invoices, // call back. if update.State != nil { setID = update.State.SetID - newState = update.State.NewState } else if update.SetID != nil { // When we go to cancel HTLCs, there's no new state, but the // set of HTLCs to be cancelled along with the setID affected @@ -1968,48 +1971,6 @@ func (d *DB) updateInvoice(hash *lntypes.Hash, refSetID *invpkg.SetID, invoices, } } - // Process cancel actions from update descriptor. - cancelHtlcs := update.CancelHtlcs - for key, htlc := range invoice.Htlcs { - htlc := htlc - - // Check whether this htlc needs to be canceled. If it does, - // update the htlc state to Canceled. - _, cancel := cancelHtlcs[key] - if !cancel { - continue - } - - // Consistency check to verify that there is no overlap between - // the add and cancel sets. - if _, added := update.AddHtlcs[key]; added { - return nil, fmt.Errorf("added htlc %v canceled", key) - } - - err := cancelSingleHtlc(now, htlc, newState) - if err != nil { - return nil, err - } - - // Delete processed cancel action, so that we can check later - // that there are no actions left. - delete(cancelHtlcs, key) - - // Tally this into the set of HTLCs that need to be updated on - // disk, but once again, only if this is an AMP invoice. - if invoiceIsAMP { - cancelHtlcsAmp( - &invoice, htlcsAmpUpdate, htlc, key, - ) - } - } - - // Verify that we didn't get an action for htlcs that are not present on - // the invoice. - if len(cancelHtlcs) > 0 { - return nil, errors.New("cancel action on non-existent htlc(s)") - } - // At this point, the set of accepted HTLCs should be fully // populated with added HTLCs or removed of canceled ones. Update // invoice state if the update descriptor indicates an invoice state @@ -2186,6 +2147,78 @@ func (d *DB) updateInvoice(hash *lntypes.Hash, refSetID *invpkg.SetID, invoices, return &invoice, nil } +// cancelHTLCs tries to cancel the htlcs in the given InvoiceUpdateDesc. +// +// NOTE: cancelHTLCs updates will only use the `CancelHtlcs` field in the +// InvoiceUpdateDesc. +func (d *DB) cancelHTLCs(invoices kvdb.RwBucket, invoiceNum []byte, + invoice *invpkg.Invoice, + update *invpkg.InvoiceUpdateDesc) (*invpkg.Invoice, error) { + + timestamp := d.clock.Now() + + // Process add actions from update descriptor. + htlcsAmpUpdate := make(map[invpkg.SetID]map[models.CircuitKey]*invpkg.InvoiceHTLC) //nolint:lll + + // Process cancel actions from update descriptor. + cancelHtlcs := update.CancelHtlcs + for key, htlc := range invoice.Htlcs { + htlc := htlc + + // Check whether this htlc needs to be canceled. If it does, + // update the htlc state to Canceled. + _, cancel := cancelHtlcs[key] + if !cancel { + continue + } + + err := cancelSingleHtlc(timestamp, htlc, invoice.State) + if err != nil { + return nil, err + } + + // Delete processed cancel action, so that we can check later + // that there are no actions left. + delete(cancelHtlcs, key) + + // Tally this into the set of HTLCs that need to be updated on + // disk, but once again, only if this is an AMP invoice. + if invoice.IsAMP() { + cancelHtlcsAmp( + invoice, htlcsAmpUpdate, htlc, key, + ) + } + } + + // Verify that we didn't get an action for htlcs that are not present on + // the invoice. + if len(cancelHtlcs) > 0 { + return nil, errors.New("cancel action on non-existent htlc(s)") + } + + // Reserialize and update invoice. + var buf bytes.Buffer + if err := serializeInvoice(&buf, invoice); err != nil { + return nil, err + } + + if err := invoices.Put(invoiceNum, buf.Bytes()); err != nil { + return nil, err + } + + // If this is an AMP invoice, then we'll actually store the rest of the + // HTLCs in-line with the invoice, using the invoice ID as a prefix, + // and the AMP key as a suffix: invoiceNum || setID. + if invoice.IsAMP() { + err := updateAMPInvoices(invoices, invoiceNum, htlcsAmpUpdate) + if err != nil { + return nil, err + } + } + + return invoice, nil +} + // updateInvoiceState validates and processes an invoice state update. The new // state to transition to is returned, so the caller is able to select exactly // how the invoice state is updated. diff --git a/invoices/invoiceregistry.go b/invoices/invoiceregistry.go index a80a85c8d..e84f10c16 100644 --- a/invoices/invoiceregistry.go +++ b/invoices/invoiceregistry.go @@ -711,6 +711,7 @@ func (i *InvoiceRegistry) cancelSingleHtlc(invoiceRef InvoiceRef, } return &InvoiceUpdateDesc{ + UpdateType: CancelHTLCsUpdate, CancelHtlcs: canceledHtlcs, SetID: setID, }, nil