What changed, and why it matters
This is a small cleanup change in Electrum's submarine swap (atomic swap) code. It replaces hardcoded transaction height numbers with named constants and slightly adjusts when a claim transaction can be broadcast. The change allows a claim transaction to be considered 'already broadcast' even if its parent transaction is still unconfirmed. This is a follow-up to a previous change and appears aimed at preventing duplicate broadcasts or premature broadcasting, not at introducing a vulnerability.
No immediate action required. This appears to be a defensive follow-up fix. Users running submarine swaps should ensure they are on a version including this commit and the preceding PR 10303. If reviewing for security, verify that the claim tx broadcast conditions correctly handle all edge cases (unconfirmed parent, local transactions, future transactions).
Security signals we found
Submarine swap claim transaction broadcast logic changed
Hardcoded transaction height integers replaced with named constants
Claim tx now considered already broadcast if parent is unconfirmed (height -1)
Prevents potential premature or duplicate claim transaction broadcast
Evidence from the diff
In electrum/submarine_swaps.py, the patch imports two additional height constants (TX_HEIGHT_UNCONFIRMED = -1, TX_HEIGHT_UNCONF_PARENT = -2) and replaces raw integer comparisons. The logic changes from: can_be_broadcast = funding height > 0 and already_broadcast = claim tx height >= 0, to: funding_tx_confirmed = funding height > TX_HEIGHT_UNCONFIRMED (-1) and already_broadcast = claim tx height >= TX_HEIGHT_UNCONF_PARENT (-2). This means a claim tx with an unconfirmed parent (height -1) is now treated as already_broadcast, whereas before it would not have been (height -1 < 0). The change is conservative: it prevents broadcasting a claim tx when its parent is unconfirmed, which is a follow-up fix to PR 10303.
Changed components
electrum/submarine_swaps.pySwapManager._claim_to_output()Submarine swap / atomic swap functionalityInspect captured patch +6 / −5
diff --git a/electrum/submarine_swaps.py b/electrum/submarine_swaps.py
index 3705aa5..128bb1f 100644
--- a/electrum/submarine_swaps.py
+++ b/electrum/submarine_swaps.py
@@ -42,7 +42,8 @@ from .lnutil import (hex_to_bytes, REDEEM_AFTER_DOUBLE_SPENT_DELAY, Keypair,
from .lnaddr import lndecode
from .json_db import StoredObject, stored_in
from . import constants
-from .address_synchronizer import TX_HEIGHT_LOCAL, TX_HEIGHT_FUTURE
+from .address_synchronizer import (TX_HEIGHT_LOCAL, TX_HEIGHT_FUTURE, TX_HEIGHT_UNCONFIRMED,
+ TX_HEIGHT_UNCONF_PARENT)
from .fee_policy import FeePolicy
from .invoices import Invoice, PR_PAID
from .lnonion import OnionRoutingFailure, OnionFailureCode
@@ -553,9 +554,9 @@ class SwapManager(Logger):
assert swap.claim_to_output, swap
txout = PartialTxOutput.from_address_and_value(swap.claim_to_output[0], swap.claim_to_output[1])
tx = PartialTransaction.from_io([claim_txin], [txout])
- can_be_broadcast = self.wallet.adb.get_tx_height(swap.funding_txid).height() > 0
- already_broadcast = self.wallet.adb.get_tx_height(tx.txid()).height() >= 0
- self.logger.debug(f"_claim_to_output: {can_be_broadcast=} {already_broadcast=}")
+ funding_tx_confirmed = self.wallet.adb.get_tx_height(swap.funding_txid).height() > TX_HEIGHT_UNCONFIRMED
+ already_broadcast = self.wallet.adb.get_tx_height(tx.txid()).height() >= TX_HEIGHT_UNCONF_PARENT
+ self.logger.debug(f"_claim_to_output: {funding_tx_confirmed=} {already_broadcast=}")
# add tx to db so it can be shown as future tx
if not self.wallet.adb.get_transaction(tx.txid()):
@@ -572,7 +573,7 @@ class SwapManager(Logger):
if not already_broadcast and self.wallet.adb.future_tx.get(tx.txid(), 0) < wanted_height:
self.wallet.adb.set_future_tx(tx.txid(), wanted_height=wanted_height)
- if can_be_broadcast and not already_broadcast:
+ if funding_tx_confirmed and not already_broadcast:
tx = self.wallet.sign_transaction(tx, password=None, ignore_warnings=True)
assert tx and tx.is_complete(), tx
try:
Why this scored 35/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.