Repository navigation
staticaddr: map loop-in deposits to the swap that spent them - #1251
Open
claravanstaden wants to merge 4 commits into
Open
claravanstaden wants to merge 4 commits into
claravanstaden wants to merge 4 commits into
Conversation
claravanstaden
force-pushed
the
static-reclaim-swap-hash
branch
from
October 15, 2026 05:44
9e46c7a to
98b99ac
Compare
1 task done
claravanstaden
force-pushed
the
static-reclaim-swap-hash
branch
2 times, most recently
from
October 19, 2026 08:01
225407c to
a8ace71
Compare
claravanstaden
force-pushed
the
static-reclaim-swap-hash
branch
from
October 19, 2026 09:51
a8ace71 to
b22d610
Compare
1 task done
claravanstaden
force-pushed
the
static-reclaim-swap-hash
branch
3 times, most recently
from
October 19, 2026 17:41
93aba9f to
0148cf0
Compare
claravanstaden
marked this pull request as ready for review
October 20, 2026 07:52
claravanstaden
force-pushed
the
static-reclaim-swap-hash
branch
6 times, most recently
from
October 22, 2026 06:43
d79da7b to
bd8f66b
Compare
Collaborator
claravanstaden
force-pushed
the
static-reclaim-swap-hash
branch
from
October 30, 2026 07:31
bd8f66b to
9eae8ff
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.
claravanstaden
force-pushed
the
static-reclaim-swap-hash
branch
from
November 5, 2026 18:34
9eae8ff to
1bd7d35
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.
A static loop-in deposit can stay mapped in
deposits.swap_hashto a swap that didn't spend it:Impact: this needs the server to publish A's htlc tx after A's payment deadline, and other swaps to select all of A's deposits in between, so it's rare. When it happens, the deposits are listed under the other swaps, and A's on-chain cost (#1248) is missing. A still sweeps its htlc timeout path: after a restart it has no deposits mapped to it, so it uses the deposits it signed its htlc tx for.
Fixes:
MigrateRepairHtlcTimeoutDepositSwapHash) maps each deposit on the htlc timeout path to the swap whose htlc tx spent it. If lnd can't list the wallet txs, loopd still starts and the repair runs again at the next start.Both only remap if the tx that spent the deposits spends exactly the deposits the swap signed its htlc tx for: the confirmed tx at runtime, the confirmed lnd wallet tx in the repair, which must also pay to the swap's htlc, since two swaps can select the same deposits. The htlc confirmation is registered by script, so a swap's state alone isn't proof. If it can't tell, the deposit stays as it is.
Not covered:
Depositedwhen A's htlc tx spends them, and the deposit manager drops them from its active deposits. A then can't lock any of its deposits, retries forever and never sweeps, as on master, so the funds stay in A's htlc until someone sweeps them by hand. A restart doesn't help. Separate follow-up.Tests
TestMonitorInvoiceAndHtlcTxRemapsReclaimedDepositsruns the takeover with the real store (it fails on master) and checks that another tx paying to the htlc script doesn't move the deposits.TestMonitorInvoiceAndHtlcTxRemapsAfterLegacyFallbackruns it through the legacy confirmation fallback with a deposit manager that hands out store copies, like the real one.TestMonitorInvoiceAndHtlcTxMapsOnlyLockedDeposits,TestMonitorInvoiceAndHtlcTxRetriesFailedMappingandTestMonitorInvoiceAndHtlcTxNilDepositcover deposits B completed with, already locked deposits, a failed mapping and a nil deposit.TestRepairHtlcTimeoutDepositSwapHashcovers the repair, including a swap still sweeping, another tx paying to the htlc script, an unconfirmed conflicting tx, a spending tx the wallet doesn't know, two swaps that selected the same deposit, unmapped deposits, and the on-chain cost before and after.TestRepairHtlcTimeoutDepositSwapHashWalletErrorchecks that a failed wallet listing doesn't stop loopd and that the repair runs again.TestMonitorInvoiceAndHtlcTxAddsDepositsAfterRestartcovers loopd stopping between locking and mapping, with the missing deposit signed first and last, and checks the deposits are in signed order.Pull Request Checklist
docs/release-notes/release-notes-next.md, or apply theno-changeloglabel (required by CI)