From a5cf5f75fce2f06e4dbbc09745cbe672a37cefaf Mon Sep 17 00:00:00 2001 From: f321x Date: Tue, 28 Oct 2025 15:32:58 +0100 Subject: [PATCH] lnpeer: await init in main_loop Because `LNPeer.initialized` was awaited in `LNPeer._query_gossip()` instead of the main loop the other tasks got spawned concurrently and each task on its own has to wait for the initialization. In `LNPeer._send_own_gossip()` this was missing, instead there is a fixed 10 sec sleep. If the connection was not initialized but the 10 sec are exceeded `_send_own_gossip()` tries to send gossip and causes this exception as the `LNTransport` is not ready: ``` 2.13 | E | lnpeer.Peer.[LNWallet, 0288fa27c0-bc1900c8] | Exception in main_loop: AttributeError("'LNTransport' object has no attribute 'sk'") Traceback (most recent call last): File "/home/user/code/electrum-fork/electrum/util.py", line 1232, in wrapper return await func(*args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/user/code/electrum-fork/electrum/lnpeer.py", line 511, in wrapper_func return await func(self, *args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/user/code/electrum-fork/electrum/lnpeer.py", line 525, in main_loop async with self.taskgroup as group: ^^^^^^^^^^^^^^ File "/home/user/code/electrum-fork/env/lib/python3.14/site-packages/aiorpcx/curio.py", line 304, in __aexit__ await self.join() File "/home/user/code/electrum-fork/electrum/util.py", line 1420, in join task.result() ~~~~~~~~~~~^^ File "/home/user/code/electrum-fork/electrum/lnpeer.py", line 573, in _send_own_gossip self.send_node_announcement(alias, color) ~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^ File "/home/user/code/electrum-fork/electrum/lnpeer.py", line 1830, in send_node_announcement self.transport.send_bytes(raw_msg) ~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^ File "/home/user/code/electrum-fork/electrum/lntransport.py", line 225, in send_bytes lc = aead_encrypt(self.sk, self.sn(), b'', l) ^^^^^^^ AttributeError: 'LNTransport' object has no attribute 'sk'. Did you mean: 'sn'? ``` By awaiting the initialization directly in the `main_loop` it is more clear that the task getting spawned subsequently depend on the transport being available and separates the initialization more clearly these other functions. --- electrum/lnpeer.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py index a30a85bfd..7424cb47a 100644 --- a/electrum/lnpeer.py +++ b/electrum/lnpeer.py @@ -523,7 +523,11 @@ class Peer(Logger, EventListener): @handle_disconnect async def main_loop(self): async with self.taskgroup as group: - await group.spawn(self._message_loop()) + await group.spawn(self._message_loop()) # initializes connection + try: + await util.wait_for2(self.initialized, LN_P2P_NETWORK_TIMEOUT) + except Exception as e: + raise GracefulDisconnect(f"Failed to initialize: {e!r}") from e await group.spawn(self._query_gossip()) await group.spawn(self._process_gossip()) await group.spawn(self._send_own_gossip()) @@ -563,6 +567,7 @@ class Peer(Logger, EventListener): async def _send_own_gossip(self): if self.lnworker == self.lnworker.network.lngossip: return + assert self.is_initialized() await asyncio.sleep(10) while True: public_channels = [chan for chan in self.lnworker.channels.values() if chan.is_public()] @@ -583,6 +588,7 @@ class Peer(Logger, EventListener): return False async def _forward_gossip(self): + assert self.is_initialized() if not self._should_forward_gossip(): return @@ -632,10 +638,7 @@ class Peer(Logger, EventListener): return amount_sent async def _query_gossip(self): - try: - await util.wait_for2(self.initialized, LN_P2P_NETWORK_TIMEOUT) - except Exception as e: - raise GracefulDisconnect(f"Failed to initialize: {e!r}") from e + assert self.is_initialized() if self.lnworker == self.lnworker.network.lngossip: if not self.their_features.supports(LnFeatures.GOSSIP_QUERIES_OPT): raise GracefulDisconnect("remote does not support gossip_queries, which we need")