lightninglabs / lightninglabs/loop

staticaddr/loopin: make canceled-invoice timeout unlock recoverable

Open
#1,167 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Go
Stars
595
Forks
135
Avg merge
1d 8h
Merged PRs (30d)
12

Description

Problem

A static-address loop-in can reach the canceled-invoice recovery path without a durable guarantee that its deposits were unlocked.

Current monitor flow:

  1. the payment deadline path cancels the invoice and then unlocks deposits:

    cancelInvoice()
    err = f.unlockDeposits(ctx)
    
  2. those are separate side effects. If loopd shuts down, crashes, or the deposit transition fails after invoice cancellation succeeds but before deposits are moved back to Deposited, recovery sees LookupInvoice return ContractCanceled;

  3. in that recovered state no new deadlineChan is created, so the deadline unlock path is not retried;

  4. if the HTLC never confirmed and the CLTV height arrives, MonitorInvoiceAndHtlcTxAction returns OnSwapTimedOut and the loop-in moves to Failed, while deposits may remain stuck in LoopingIn.

This was discussed during PR #1161: https://github.com/lightninglabs/loop/pull/1161#discussion_r3539797010

The issue appears to predate PR #1161, but that PR touched the surrounding recovery/lifecycle code and made the invariant visible.

Potential fix

Route the no-HTLC-confirmation timeout path through a recoverable state-machine unlock state instead of transitioning directly to Failed.

One possible shape:

  • add a success event such as OnDepositsUnlocked;
  • route MonitorInvoiceAndHtlcTx -> OnSwapTimedOut -> UnlockState (or a more specific UnlockDepositsBeforeFail state);
  • make UnlockState retry on OnRecover;
  • only transition UnlockState -> OnDepositsUnlocked -> Failed after unlockDeposits succeeds;
  • if unlockDeposits fails, keep the loop-in in the unlock state so restart/recovery retries cleanup rather than marking the swap failed with deposits possibly still locked.

The important invariant is: a no-confirmation timeout should not become final Failed until deposit unlock has succeeded or is known to be unnecessary.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with MonitorInvoiceAndHtlcTxAction and the MonitorInvoiceAndHtlcTx transition on OnSwapTimedOut, then inspect UnlockState, OnRecover, and unlockDeposits. Review the recovery and lifecycle discussion in PR #1161. Done means a no-confirmation timeout remains recoverable and reaches Failed only after deposits are unlocked or known to be unnecessary.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
backend, payments
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.