invoices: fix htlc timer deadlock
This commit is contained in:
parent
51324ac7ae
commit
0042a1ffeb
@ -807,8 +807,53 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
|
||||
}
|
||||
}
|
||||
|
||||
// Execute locked notify exit hop logic.
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
resolution, err := i.notifyExitHopHtlcLocked(&updateCtx, hodlChan)
|
||||
i.Unlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch r := resolution.(type) {
|
||||
|
||||
// A direct resolution was received for this htlc.
|
||||
case *HtlcResolution:
|
||||
return r, nil
|
||||
|
||||
// The htlc is held. Start a timer outside the lock if the htlc should
|
||||
// be auto-released, because otherwise a deadlock may happen with the
|
||||
// main event loop.
|
||||
case *acceptResolution:
|
||||
if r.autoRelease {
|
||||
err := i.startHtlcTimer(rHash, circuitKey, r.acceptTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
|
||||
default:
|
||||
return nil, errors.New("invalid resolution type")
|
||||
}
|
||||
}
|
||||
|
||||
// acceptResolution is returned when the htlc should be held.
|
||||
type acceptResolution struct {
|
||||
// autoRelease signals that the htlc should be automatically released
|
||||
// after a timeout.
|
||||
autoRelease bool
|
||||
|
||||
// acceptTime is the time at which this htlc was accepted.
|
||||
acceptTime time.Time
|
||||
}
|
||||
|
||||
// notifyExitHopHtlcLocked is the internal implementation of NotifyExitHopHtlc
|
||||
// that should be executed inside the registry lock.
|
||||
func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
|
||||
ctx *invoiceUpdateCtx, hodlChan chan<- interface{}) (
|
||||
interface{}, error) {
|
||||
|
||||
// We'll attempt to settle an invoice matching this rHash on disk (if
|
||||
// one exists). The callback will update the invoice state and/or htlcs.
|
||||
@ -817,11 +862,11 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
|
||||
updateSubscribers bool
|
||||
)
|
||||
invoice, err := i.cdb.UpdateInvoice(
|
||||
rHash,
|
||||
ctx.hash,
|
||||
func(inv *channeldb.Invoice) (
|
||||
*channeldb.InvoiceUpdateDesc, error) {
|
||||
|
||||
updateDesc, res, err := updateInvoice(&updateCtx, inv)
|
||||
updateDesc, res, err := updateInvoice(ctx, inv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -841,29 +886,30 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
|
||||
// If the invoice was not found, return a failure resolution
|
||||
// with an invoice not found result.
|
||||
return NewFailureResolution(
|
||||
circuitKey, currentHeight, ResultInvoiceNotFound,
|
||||
ctx.circuitKey, ctx.currentHeight,
|
||||
ResultInvoiceNotFound,
|
||||
), nil
|
||||
|
||||
case nil:
|
||||
|
||||
default:
|
||||
updateCtx.log(err.Error())
|
||||
ctx.log(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updateCtx.log(result.String())
|
||||
ctx.log(result.String())
|
||||
|
||||
if updateSubscribers {
|
||||
i.notifyClients(rHash, invoice, invoice.State)
|
||||
i.notifyClients(ctx.hash, invoice, invoice.State)
|
||||
}
|
||||
|
||||
// Inspect latest htlc state on the invoice.
|
||||
invoiceHtlc, ok := invoice.Htlcs[circuitKey]
|
||||
invoiceHtlc, ok := invoice.Htlcs[ctx.circuitKey]
|
||||
|
||||
// If it isn't recorded, cancel htlc.
|
||||
if !ok {
|
||||
return NewFailureResolution(
|
||||
circuitKey, currentHeight, result,
|
||||
ctx.circuitKey, ctx.currentHeight, result,
|
||||
), nil
|
||||
}
|
||||
|
||||
@ -876,7 +922,7 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
|
||||
switch invoiceHtlc.State {
|
||||
case channeldb.HtlcStateCanceled:
|
||||
return NewFailureResolution(
|
||||
circuitKey, acceptHeight, result,
|
||||
ctx.circuitKey, acceptHeight, result,
|
||||
), nil
|
||||
|
||||
case channeldb.HtlcStateSettled:
|
||||
@ -902,27 +948,25 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
|
||||
}
|
||||
|
||||
resolution := NewSettleResolution(
|
||||
invoice.Terms.PaymentPreimage, circuitKey,
|
||||
invoice.Terms.PaymentPreimage, ctx.circuitKey,
|
||||
acceptHeight, result,
|
||||
)
|
||||
return resolution, nil
|
||||
|
||||
case channeldb.HtlcStateAccepted:
|
||||
// (Re)start the htlc timer if the invoice is still open. It can
|
||||
var resolution acceptResolution
|
||||
|
||||
// Auto-release the htlc if the invoice is still open. It can
|
||||
// only happen for mpp payments that there are htlcs in state
|
||||
// Accepted while the invoice is Open.
|
||||
if invoice.State == channeldb.ContractOpen {
|
||||
err := i.startHtlcTimer(
|
||||
rHash, circuitKey,
|
||||
invoiceHtlc.AcceptTime,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resolution.acceptTime = invoiceHtlc.AcceptTime
|
||||
resolution.autoRelease = true
|
||||
|
||||
}
|
||||
|
||||
i.hodlSubscribe(hodlChan, circuitKey)
|
||||
return nil, nil
|
||||
i.hodlSubscribe(hodlChan, ctx.circuitKey)
|
||||
return &resolution, nil
|
||||
|
||||
default:
|
||||
panic("unknown action")
|
||||
|
Loading…
Reference in New Issue
Block a user