Repository navigation
staticaddr: repair the swap hashes of looped-in deposits - #1253
Draft
claravanstaden wants to merge 7 commits into
Draft
claravanstaden wants to merge 7 commits into
claravanstaden wants to merge 7 commits into
Conversation
claravanstaden
force-pushed
the
static-repair-looped-in-swap-hash
branch
from
October 19, 2026 10:43
d427b0d to
76065f0
Compare
1 task done
claravanstaden
force-pushed
the
static-repair-looped-in-swap-hash
branch
6 times, most recently
from
October 22, 2026 07:16
5fcdc7a to
bbfa9ec
Compare
If a static loop-in's payment deadline passes before its htlc confirms, it gives its deposits back, and another swap can select them, which maps them to that swap. If the first swap's htlc confirms after all, it sweeps them through its htlc timeout path, but they stay mapped to the other swap. Map the deposits to the swap once they are locked for its htlc timeout sweep, and again if they already were, in case loopd stopped in between. A deposit another swap completed with can't be locked and stays with that swap. Any tx paying to the htlc script confirms it, so only map them if the confirmed tx spends exactly the deposits the swap signed its htlc tx for. After a restart the swap only loads the deposits still mapped to it, so replace them with all signed deposits, in signed order, which the htlc timeout sweep needs. If any is no longer active, none are added. A nil deposit now fails the transition. The loop-in store interface gains the existing BatchMapDepositsToSwapHashes.
A swap whose payment deadline passed gave its deposits back before its htlc confirmed after all, which left them mapped to the swap that selected them next. The previous commit maps them back when that happens from now on, but deposits that it already happened to stay mapped to the other swap. The swap that spent them reports no on-chain cost for them, and if the other swap took only some of its deposits, it loads only the rest after a restart, so it can't sweep its htlc timeout path. Add a migration that maps each deposit on the htlc timeout path to the swap whose htlc tx spent it: a swap on its own htlc timeout path whose htlc tx is the confirmed wallet tx that spent the deposit. That tx must spend exactly the deposits the swap signed its htlc tx for and pay to the swap's htlc, since the swap's state alone isn't proof and two swaps can select the same deposits. A deposit stays as it is if the wallet doesn't know that tx or no swap qualifies. If lnd can't list the wallet txs, the repair is skipped so that loopd still starts, and it runs again at the next start. A swap that still waits for its htlc timeout isn't covered: its htlc timeout sweep loads only the deposits mapped to it, which is fixed separately. The repair has its own migration id, and the lookup of the swaps by the outpoints they selected and the final write are separate helpers, so that other repairs can reuse them. The swap state check is shared with the on-chain cost of a swap.
Run the reclaimed deposit swap hash repair once, after the deposit swap hash migration. It looks up the txs that spent the deposits in the lnd wallet.
The deposit swap hash migration mapped a deposit that several static loop-ins selected to the first of them. So if a swap failed and a later swap looped in the same deposit, the deposit stayed mapped to the failed swap. The swap that looped it in then listed no deposits and reported an unknown on-chain cost. Add a migration that maps each looped-in deposit to the paid swap that selected it. A deposit stays as it is if no swap or more than one swap that selected it was paid. Both swaps are final, so this only corrects what loopd reports.
Run the looped-in deposit swap hash repair once, after the reclaimed deposit swap hash repair.
claravanstaden
force-pushed
the
static-repair-looped-in-swap-hash
branch
from
November 5, 2026 18:34
bbfa9ec to
cf6700a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Based on #1251. Only the last three commits are new here.
A looped-in static address deposit can stay mapped in
deposits.swap_hashto a swap that didn't spend it:Problem: the deposit swap hash migration mapped a deposit that several swaps selected to the first of them (comment on #1248). Both swaps are final, so nothing on chain depends on it, but swap B lists no deposits and reports an unknown on-chain cost.
Fix: loopd runs
MigrateRepairLoopedInDepositSwapHashonce at startup, with its own migration id, using the repair from #1251. It maps each looped-in deposit to the paid swap that selected it. If no swap or more than one swap was paid, it leaves the deposit and logs a warning.Tests
TestRepairLoopedInDepositSwapHashcovers the retry case, plus ambiguous, unspent, htlc timeout and already correct deposits. It checks the on-chain cost before and after the repair, and that the repair runs only once and separately from the htlc timeout repair.Pull Request Checklist
docs/release-notes/release-notes-next.md, or apply theno-changeloglabel (required by CI)