From df58dd1f258f6dc8eee832358c2865fe33d27095 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 15 Jan 2024 17:52:48 +0000 Subject: [PATCH] lnchannel.get_close_opts: allow REQUEST_REMOTE_FCLOSE if WE_ARE_TOXIC related https://github.com/spesmilo/electrum/issues/8770 --- electrum/gui/qt/channels_list.py | 11 +++++------ electrum/lnchannel.py | 4 ++++ electrum/lnpeer.py | 12 +++++++----- electrum/lnworker.py | 8 +++----- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/electrum/gui/qt/channels_list.py b/electrum/gui/qt/channels_list.py index 1a85045cb..257256cf1 100644 --- a/electrum/gui/qt/channels_list.py +++ b/electrum/gui/qt/channels_list.py @@ -119,9 +119,6 @@ class ChannelsList(MyTreeView): def on_channel_closed(self, txid): self.main_window.show_error('Channel closed' + '\n' + txid) - def on_request_sent(self, b): - self.main_window.show_message(_('Request sent')) - def on_failure(self, exc_info): type_, e, tb = exc_info traceback.print_tb(tb) @@ -137,7 +134,7 @@ class ChannelsList(MyTreeView): on_success = self.on_channel_closed def task(): return self.network.run_from_another_thread(coro) - WaitingDialog(self, 'please wait..', task, on_success, self.on_failure) + WaitingDialog(self, _('Please wait...'), task, on_success, self.on_failure) def force_close(self, channel_id): self.save_backup = True @@ -161,7 +158,7 @@ class ChannelsList(MyTreeView): def task(): coro = self.lnworker.force_close_channel(channel_id) return self.network.run_from_another_thread(coro) - WaitingDialog(self, 'please wait..', task, self.on_channel_closed, self.on_failure) + WaitingDialog(self, _('Please wait...'), task, self.on_channel_closed, self.on_failure) def remove_channel(self, channel_id): if self.main_window.question(_('Are you sure you want to delete this channel? This will purge associated transactions from your wallet history.')): @@ -191,7 +188,9 @@ class ChannelsList(MyTreeView): def task(): coro = self.lnworker.request_force_close(channel_id) return self.network.run_from_another_thread(coro) - WaitingDialog(self, 'please wait..', task, self.on_request_sent, self.on_failure) + def on_done(b): + self.main_window.show_message(_('Request scheduled')) + WaitingDialog(self, _('Please wait...'), task, on_done, self.on_failure) def set_frozen(self, chan, *, for_sending, value): if not self.lnworker.uses_trampoline() or self.lnworker.is_trampoline_peer(chan.node_id): diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py index 3827b22e7..ed8074d8b 100644 --- a/electrum/lnchannel.py +++ b/electrum/lnchannel.py @@ -1015,6 +1015,8 @@ class Channel(AbstractChannel): def should_try_to_reestablish_peer(self) -> bool: if self.peer_state != PeerState.DISCONNECTED: return False + if self.should_request_force_close: + return True return ChannelState.PREOPENING < self._state < ChannelState.CLOSING def get_funding_address(self): @@ -1629,6 +1631,8 @@ class Channel(AbstractChannel): if not self.has_unsettled_htlcs(): ret.append(ChanCloseOption.COOP_CLOSE) ret.append(ChanCloseOption.REQUEST_REMOTE_FCLOSE) + if self.get_state() == ChannelState.WE_ARE_TOXIC: + ret.append(ChanCloseOption.REQUEST_REMOTE_FCLOSE) if not self.is_closed() or self.get_state() == ChannelState.REQUESTED_FCLOSE: ret.append(ChanCloseOption.LOCAL_FCLOSE) assert not (self.get_state() == ChannelState.WE_ARE_TOXIC and ChanCloseOption.LOCAL_FCLOSE in ret), "local force-close unsafe if we are toxic" diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py index e3db405f7..d7c3a7202 100644 --- a/electrum/lnpeer.py +++ b/electrum/lnpeer.py @@ -1113,6 +1113,7 @@ class Peer(Logger): async def request_force_close(self, channel_id: bytes): """Try to trigger the remote peer to force-close.""" await self.initialized + self.logger.info(f"trying to get remote peer to force-close chan {channel_id.hex()}") # First, we intentionally send a "channel_reestablish" msg with an old state. # Many nodes (but not all) automatically force-close when seeing this. latest_point = secret_to_pubkey(42) # we need a valid point (BOLT2) @@ -1270,6 +1271,12 @@ class Peer(Logger): async def reestablish_channel(self, chan: Channel): await self.initialized chan_id = chan.channel_id + if chan.should_request_force_close: + if chan.get_state() != ChannelState.WE_ARE_TOXIC: + chan.set_state(ChannelState.REQUESTED_FCLOSE) + await self.request_force_close(chan_id) + chan.should_request_force_close = False + return if chan.get_state() == ChannelState.WE_ARE_TOXIC: # Depending on timing, the remote might not know we are behind. # We should let them know, so that they force-close. @@ -1283,11 +1290,6 @@ class Peer(Logger): # We should let them know: self._send_channel_reestablish(chan) return - if chan.should_request_force_close: - chan.set_state(ChannelState.REQUESTED_FCLOSE) - await self.request_force_close(chan_id) - chan.should_request_force_close = False - return # if we get here, we will try to do a proper reestablish if not (ChannelState.PREOPENING < chan.get_state() < ChannelState.FORCE_CLOSING): raise Exception(f"unexpected {chan.get_state()=} for reestablish") diff --git a/electrum/lnworker.py b/electrum/lnworker.py index 17d9ccd6b..d0b034808 100644 --- a/electrum/lnworker.py +++ b/electrum/lnworker.py @@ -2904,9 +2904,8 @@ class LNWallet(LNWorker): if self._can_retry_addr(peer, urgent=True): await self._add_peer(peer.host, peer.port, peer.pubkey) for chan in self.channels.values(): - if chan.is_closed(): - continue # reestablish + # note: we delegate filtering out uninteresting chans to this: if not chan.should_try_to_reestablish_peer(): continue peer = self._peers.get(chan.node_id, None) @@ -2961,10 +2960,9 @@ class LNWallet(LNWorker): if channel_id in self.channels: chan = self.channels[channel_id] peer = self._peers.get(chan.node_id) - if not peer: - raise Exception('Peer not found') chan.should_request_force_close = True - peer.close_and_cleanup() + if peer: + peer.close_and_cleanup() # to force a reconnect elif connect_str: peer = await self.add_peer(connect_str) await peer.request_force_close(channel_id)