mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-09-08 14:57:38 +02:00
contractcourt: persist timed out incoming htlc resolver reports
Incoming htlcs that are timed out or failed (invalid htlc or invoice condition not met), save a single on chain resolution because we don't need to take any actions on them ourselves (we don't need to worry about 2 stage claims since this is the success path for our peer).
This commit is contained in:
@@ -81,7 +81,14 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
|
||||
// present itself when we crash before processRemoteAdds in the
|
||||
// link has ran.
|
||||
h.resolved = true
|
||||
return nil, nil
|
||||
|
||||
// We write a report to disk that indicates we could not decode
|
||||
// the htlc.
|
||||
resReport := h.report().resolverReport(
|
||||
nil, channeldb.ResolverTypeIncomingHtlc,
|
||||
channeldb.ResolverOutcomeAbandoned,
|
||||
)
|
||||
return nil, h.PutResolverReport(nil, resReport)
|
||||
}
|
||||
|
||||
// Register for block epochs. After registration, the current height
|
||||
@@ -120,7 +127,14 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
|
||||
"abandoning", h, h.htlcResolution.ClaimOutpoint,
|
||||
h.htlcExpiry, currentHeight)
|
||||
h.resolved = true
|
||||
return nil, h.Checkpoint(h)
|
||||
|
||||
// Finally, get our report and checkpoint our resolver with a
|
||||
// timeout outcome report.
|
||||
report := h.report().resolverReport(
|
||||
nil, channeldb.ResolverTypeIncomingHtlc,
|
||||
channeldb.ResolverOutcomeTimeout,
|
||||
)
|
||||
return nil, h.Checkpoint(h, report)
|
||||
}
|
||||
|
||||
// applyPreimage is a helper function that will populate our internal
|
||||
@@ -158,16 +172,6 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If the HTLC hasn't expired yet, then we may still be able to claim
|
||||
// it if we learn of the pre-image, so we'll subscribe to the preimage
|
||||
// database to see if it turns up, or the HTLC times out.
|
||||
//
|
||||
// NOTE: This is done BEFORE opportunistically querying the db, to
|
||||
// ensure the preimage can't be delivered between querying and
|
||||
// registering for the preimage subscription.
|
||||
preimageSubscription := h.PreimageDB.SubscribeUpdates()
|
||||
defer preimageSubscription.CancelSubscription()
|
||||
|
||||
// Define a closure to process htlc resolutions either directly or
|
||||
// triggered by future notifications.
|
||||
processHtlcResolution := func(e invoices.HtlcResolution) (
|
||||
@@ -196,7 +200,14 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
|
||||
h.htlcExpiry, currentHeight)
|
||||
|
||||
h.resolved = true
|
||||
return nil, h.Checkpoint(h)
|
||||
|
||||
// Checkpoint our resolver with an abandoned outcome
|
||||
// because we take no further action on this htlc.
|
||||
report := h.report().resolverReport(
|
||||
nil, channeldb.ResolverTypeIncomingHtlc,
|
||||
channeldb.ResolverOutcomeAbandoned,
|
||||
)
|
||||
return nil, h.Checkpoint(h, report)
|
||||
|
||||
// Error if the resolution type is unknown, we are only
|
||||
// expecting settles and fails.
|
||||
@@ -206,71 +217,91 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Create a buffered hodl chan to prevent deadlock.
|
||||
hodlChan := make(chan interface{}, 1)
|
||||
|
||||
// Notify registry that we are potentially resolving as an exit hop
|
||||
// on-chain. If this HTLC indeed pays to an existing invoice, the
|
||||
// invoice registry will tell us what to do with the HTLC. This is
|
||||
// identical to HTLC resolution in the link.
|
||||
circuitKey := channeldb.CircuitKey{
|
||||
ChanID: h.ShortChanID,
|
||||
HtlcID: h.htlc.HtlcIndex,
|
||||
}
|
||||
|
||||
resolution, err := h.Registry.NotifyExitHopHtlc(
|
||||
h.htlc.RHash, h.htlc.Amt, h.htlcExpiry, currentHeight,
|
||||
circuitKey, hodlChan, payload,
|
||||
var (
|
||||
hodlChan chan interface{}
|
||||
witnessUpdates <-chan lntypes.Preimage
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if payload.FwdInfo.NextHop == hop.Exit {
|
||||
// Create a buffered hodl chan to prevent deadlock.
|
||||
hodlChan = make(chan interface{}, 1)
|
||||
|
||||
defer h.Registry.HodlUnsubscribeAll(hodlChan)
|
||||
|
||||
// Take action based on the resolution we received. If the htlc was
|
||||
// settled, or a htlc for a known invoice failed we can resolve it
|
||||
// directly. If the resolution is nil, the htlc was neither accepted
|
||||
// nor failed, so we cannot take action yet.
|
||||
switch res := resolution.(type) {
|
||||
case *invoices.HtlcFailResolution:
|
||||
// In the case where the htlc failed, but the invoice was known
|
||||
// to the registry, we can directly resolve the htlc.
|
||||
if res.Outcome != invoices.ResultInvoiceNotFound {
|
||||
return processHtlcResolution(resolution)
|
||||
// Notify registry that we are potentially resolving as an exit
|
||||
// hop on-chain. If this HTLC indeed pays to an existing
|
||||
// invoice, the invoice registry will tell us what to do with
|
||||
// the HTLC. This is identical to HTLC resolution in the link.
|
||||
circuitKey := channeldb.CircuitKey{
|
||||
ChanID: h.ShortChanID,
|
||||
HtlcID: h.htlc.HtlcIndex,
|
||||
}
|
||||
|
||||
// If we settled the htlc, we can resolve it.
|
||||
case *invoices.HtlcSettleResolution:
|
||||
return processHtlcResolution(resolution)
|
||||
|
||||
// If the resolution is nil, the htlc was neither settled nor failed so
|
||||
// we cannot take action at present.
|
||||
case nil:
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown htlc resolution type: %T",
|
||||
resolution)
|
||||
}
|
||||
|
||||
// With the epochs and preimage subscriptions initialized, we'll query
|
||||
// to see if we already know the preimage.
|
||||
preimage, ok := h.PreimageDB.LookupPreimage(h.htlc.RHash)
|
||||
if ok {
|
||||
// If we do, then this means we can claim the HTLC! However,
|
||||
// we don't know how to ourselves, so we'll return our inner
|
||||
// resolver which has the knowledge to do so.
|
||||
if err := applyPreimage(preimage); err != nil {
|
||||
resolution, err := h.Registry.NotifyExitHopHtlc(
|
||||
h.htlc.RHash, h.htlc.Amt, h.htlcExpiry, currentHeight,
|
||||
circuitKey, hodlChan, payload,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &h.htlcSuccessResolver, nil
|
||||
defer h.Registry.HodlUnsubscribeAll(hodlChan)
|
||||
|
||||
// Take action based on the resolution we received. If the htlc
|
||||
// was settled, or a htlc for a known invoice failed we can
|
||||
// resolve it directly. If the resolution is nil, the htlc was
|
||||
// neither accepted nor failed, so we cannot take action yet.
|
||||
switch res := resolution.(type) {
|
||||
case *invoices.HtlcFailResolution:
|
||||
// In the case where the htlc failed, but the invoice
|
||||
// was known to the registry, we can directly resolve
|
||||
// the htlc.
|
||||
if res.Outcome != invoices.ResultInvoiceNotFound {
|
||||
return processHtlcResolution(resolution)
|
||||
}
|
||||
|
||||
// If we settled the htlc, we can resolve it.
|
||||
case *invoices.HtlcSettleResolution:
|
||||
return processHtlcResolution(resolution)
|
||||
|
||||
// If the resolution is nil, the htlc was neither settled nor
|
||||
// failed so we cannot take action at present.
|
||||
case nil:
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown htlc resolution type: %T",
|
||||
resolution)
|
||||
}
|
||||
} else {
|
||||
// If the HTLC hasn't expired yet, then we may still be able to
|
||||
// claim it if we learn of the pre-image, so we'll subscribe to
|
||||
// the preimage database to see if it turns up, or the HTLC
|
||||
// times out.
|
||||
//
|
||||
// NOTE: This is done BEFORE opportunistically querying the db,
|
||||
// to ensure the preimage can't be delivered between querying
|
||||
// and registering for the preimage subscription.
|
||||
preimageSubscription := h.PreimageDB.SubscribeUpdates()
|
||||
defer preimageSubscription.CancelSubscription()
|
||||
|
||||
// With the epochs and preimage subscriptions initialized, we'll
|
||||
// query to see if we already know the preimage.
|
||||
preimage, ok := h.PreimageDB.LookupPreimage(h.htlc.RHash)
|
||||
if ok {
|
||||
// If we do, then this means we can claim the HTLC!
|
||||
// However, we don't know how to ourselves, so we'll
|
||||
// return our inner resolver which has the knowledge to
|
||||
// do so.
|
||||
if err := applyPreimage(preimage); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &h.htlcSuccessResolver, nil
|
||||
}
|
||||
|
||||
witnessUpdates = preimageSubscription.WitnessUpdates
|
||||
}
|
||||
|
||||
for {
|
||||
|
||||
select {
|
||||
case preimage := <-preimageSubscription.WitnessUpdates:
|
||||
case preimage := <-witnessUpdates:
|
||||
// We received a new preimage, but we need to ignore
|
||||
// all except the preimage we are waiting for.
|
||||
if !preimage.Matches(h.htlc.RHash) {
|
||||
@@ -305,7 +336,13 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
|
||||
h.htlcResolution.ClaimOutpoint,
|
||||
h.htlcExpiry, currentHeight)
|
||||
h.resolved = true
|
||||
return nil, h.Checkpoint(h)
|
||||
|
||||
report := h.report().resolverReport(
|
||||
nil,
|
||||
channeldb.ResolverTypeIncomingHtlc,
|
||||
channeldb.ResolverOutcomeTimeout,
|
||||
)
|
||||
return nil, h.Checkpoint(h, report)
|
||||
}
|
||||
|
||||
case <-h.quit:
|
||||
|
Reference in New Issue
Block a user