swaps: destroy swap transport on failed initialization
What changed, and why it matters
This commit fixes a resource leak in Electrum's submarine-swap feature. When a connection to a swap server failed to start, the program was not properly cleaning up the connection object, so each failed attempt could leave behind leftover background tasks and memory. The patch now explicitly destroys the failed transport object before discarding it. This is a reliability bug rather than an active security vulnerability, though resource leaks can in some cases contribute to denial-of-service conditions.
Apply the patch. Users concerned about resource exhaustion from repeated failed swap attempts should update to the fixed version. No immediate exploit mitigation is required beyond normal patching.
Security signals we found
Resource leak on error path
Missing cleanup of background tasks after failed initialization
Potential accumulation of leaked transports/connections per failed swap attempt
Evidence from the diff
In electrum/gui/common_qt/swaps.py, the transport_initialize_done callback previously set self.swap_transport = None when initialization was cancelled or raised an exception, without first calling destroy(). Because the transport object owns background tasks (notably ongoing_connection_attempt), merely dropping the reference left those tasks running, causing a leak for every failed connection attempt. The patch adds self.swap_transport.destroy() before nulling the reference. A secondary change in electrum/submarine_swaps.py adds an Optional type hint to ongoing_connection_attempt and is cosmetic.
Changed components
electrum/gui/common_qt/swaps.pyelectrum/submarine_swaps.pySubmarineSwapMixin Qt GUISwapServerTransportInspect captured patch +4 / −2
diff --git a/electrum/gui/common_qt/swaps.py b/electrum/gui/common_qt/swaps.py
index a45a854..18761d5 100644
--- a/electrum/gui/common_qt/swaps.py
+++ b/electrum/gui/common_qt/swaps.py
@@ -81,7 +81,9 @@ class SubmarineSwapMixin(QtEventListener):
def transport_initialize_done(future: Future):
if future.cancelled() or future.exception() is not None:
- self.swap_transport = None
+ if self.swap_transport is not None:
+ self.swap_transport.destroy()
+ self.swap_transport = None
self.swapAvailabilityChanged.emit()
self.swap_transport.initialize(transport_initialize_done)
diff --git a/electrum/submarine_swaps.py b/electrum/submarine_swaps.py
index c36abb0..62aa1ad 100644
--- a/electrum/submarine_swaps.py
+++ b/electrum/submarine_swaps.py
@@ -1658,7 +1658,7 @@ class SwapServerTransport(Logger):
self.config = config
self.is_connected = asyncio.Event()
self.connect_timeout = 10 if self.uses_proxy else 5
- self.ongoing_connection_attempt: Future = None
+ self.ongoing_connection_attempt: Optional[Future] = None
def __enter__(self):
pass
Why this scored 26/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.