From 8402e346f56dfa2d3398518312eb37c8dbc1443a Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 6 May 2021 09:17:19 -0700 Subject: [PATCH] channeldb/invoice: fail extra HTLC sets immeidately --- channeldb/invoices.go | 25 +++++++++++++++++++++++++ invoices/invoiceregistry.go | 17 +++++++++++++++++ invoices/resolution_result.go | 7 +++++++ 3 files changed, 49 insertions(+) diff --git a/channeldb/invoices.go b/channeldb/invoices.go index 292a972e..8968507b 100644 --- a/channeldb/invoices.go +++ b/channeldb/invoices.go @@ -494,6 +494,31 @@ func (i *Invoice) HTLCSet(setID *[32]byte, state HtlcState) map[CircuitKey]*Invo return htlcSet } +// HTLCSetCompliment returns the set of all HTLCs not belonging to setID that +// are in the target state. Passing a nil setID will return no invoices, since +// all MPP HTLCs are part of the same HTLC set. +func (i *Invoice) HTLCSetCompliment(setID *[32]byte, + state HtlcState) map[CircuitKey]*InvoiceHTLC { + + htlcSet := make(map[CircuitKey]*InvoiceHTLC) + for key, htlc := range i.Htlcs { + // Only add HTLCs that are in the requested HtlcState. + if htlc.State != state { + continue + } + + // We are constructing the compliment, so filter anything that + // matches this set id. + if htlc.IsInHTLCSet(setID) { + continue + } + + htlcSet[key] = htlc + } + + return htlcSet +} + // HtlcState defines the states an htlc paying to an invoice can be in. type HtlcState uint8 diff --git a/invoices/invoiceregistry.go b/invoices/invoiceregistry.go index c2193642..03b07862 100644 --- a/invoices/invoiceregistry.go +++ b/invoices/invoiceregistry.go @@ -1063,6 +1063,23 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked( i.notifyHodlSubscribers(htlcSettleResolution) } + // If concurrent payments were attempted to this invoice before + // the current one was ultimately settled, cancel back any of + // the HTLCs immediately. As a result of the settle, the HTLCs + // in other HTLC sets are automatically converted to a canceled + // state when updating the invoice. + canceledHtlcSet := invoice.HTLCSetCompliment( + setID, channeldb.HtlcStateCanceled, + ) + for key, htlc := range canceledHtlcSet { + htlcFailResolution := NewFailResolution( + key, int32(htlc.AcceptHeight), + ResultInvoiceAlreadySettled, + ) + + i.notifyHodlSubscribers(htlcFailResolution) + } + // If we accepted the htlc, subscribe to the hodl invoice and return // an accept resolution with the htlc's accept time on it. case *htlcAcceptResolution: diff --git a/invoices/resolution_result.go b/invoices/resolution_result.go index 810f2053..8e2e4327 100644 --- a/invoices/resolution_result.go +++ b/invoices/resolution_result.go @@ -61,6 +61,10 @@ const ( // invoice that is already canceled. ResultInvoiceAlreadyCanceled + // ResultInvoiceAlreadySettled is returned when trying to pay an invoice + // that is already settled. + ResultInvoiceAlreadySettled + // ResultAmountTooLow is returned when an invoice is underpaid. ResultAmountTooLow @@ -133,6 +137,9 @@ func (f FailResolutionResult) FailureString() string { case ResultInvoiceAlreadyCanceled: return "invoice already canceled" + case ResultInvoiceAlreadySettled: + return "invoice alread settled" + case ResultAmountTooLow: return "amount too low"