swaps: only set swap redeemed if preimage is available
What changed, and why it matters
This change fixes a logic bug in Electrum's submarine swap handling. Previously, a forward swap could be marked as 'redeemed' and stop being watched before the secret payment proof (preimage) was actually extracted. Without the preimage, the wallet cannot claim the funds it is owed. The patch adds a check so the swap is only marked redeemed if the preimage is already known. This prevents premature cleanup that could strand funds or break swap accounting.
Review related swap lifecycle logic to ensure no other state transitions rely on spent_height without verifying preimage availability. Consider adding tests covering the forward-swap redemption path where the preimage is extracted from the spending transaction. Users running submarine swaps should upgrade to a version containing this fix.
Security signals we found
state-machine logic flaw
missing preimage validation before marking redemption
premature cleanup of watched swap
potential fund stranding in submarine swaps
Evidence from the diff
In electrum/submarine_swaps.py, the SwapManager monitors lockup transactions being spent. The old condition marked a swap as redeemed whenever the spending transaction had a positive confirmation height and enough blocks had passed. The patch adds an additional requirement: swap.preimage must be set. For forward swaps, the preimage is learned only when the counterparty reveals it by spending the lockup output. If the wallet marked the swap redeemed before extracting the preimage from that spend, it would stop watching and might never learn the preimage, leaving it unable to redeem its own side of the swap. Reverse swaps are unaffected because the wallet already knows the preimage.
Changed components
electrum/submarine_swaps.pySwapManagerforward submarine swapsInspect captured patch +1 / −1
diff --git a/electrum/submarine_swaps.py b/electrum/submarine_swaps.py
index bf006ac..0adf5d8 100644
--- a/electrum/submarine_swaps.py
+++ b/electrum/submarine_swaps.py
@@ -464,7 +464,7 @@ class SwapManager(Logger):
if spent_height in [TX_HEIGHT_LOCAL, TX_HEIGHT_FUTURE]:
spent_height = None
if spent_height is not None:
- if spent_height > 0:
+ if spent_height > 0 and swap.preimage:
if current_height - spent_height > REDEEM_AFTER_DOUBLE_SPENT_DELAY:
self.logger.info(f'stop watching swap {swap.lockup_address}')
swap.is_redeemed = True
Why this scored 46/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.