diff --git a/electrum/gui/kivy/uix/dialogs/lightning_channels.py b/electrum/gui/kivy/uix/dialogs/lightning_channels.py index d5090d1e1..f71f6d3f9 100644 --- a/electrum/gui/kivy/uix/dialogs/lightning_channels.py +++ b/electrum/gui/kivy/uix/dialogs/lightning_channels.py @@ -15,8 +15,10 @@ from .question import Question from electrum.transaction import PartialTxOutput, Transaction from electrum.util import NotEnoughFunds, NoDynamicFeeEstimates, format_fee_satoshis, quantize_feerate from electrum.lnutil import ln_dummy_address +from electrum.gui import messages from .qr_dialog import QRDialog +from .choice_dialog import ChoiceDialog if TYPE_CHECKING: from ...main_window import ElectrumWindow @@ -437,7 +439,7 @@ class ChannelBackupPopup(Popup, Logger): coro = asyncio.run_coroutine_threadsafe(self.app.wallet.lnworker.request_force_close_from_backup(self.chan.channel_id), loop) try: coro.result(5) - self.app.show_info(_('Channel closed')) + self.app.show_info(_('Request sent')) except Exception as e: self.logger.exception("Could not close channel") self.app.show_info(_('Could not close channel: ') + repr(e)) # repr because str(Exception()) == '' @@ -490,16 +492,26 @@ class ChannelDetailsPopup(Popup, Logger): self.warning = '' if self.app.wallet.lnworker.channel_db or self.app.wallet.lnworker.is_trampoline_peer(chan.node_id) else _('Warning') + ': ' + msg def close(self): - Question(_('Close channel?'), self._close).open() - - def _close(self, b): - if not b: - return + dialog = ChoiceDialog( + title=_('Close channel'), + choices={0:_('Cooperative close'), 1:_('Request force-close')}, key=0, + callback=self._close, + description=_(messages.MSG_REQUEST_FORCE_CLOSE), + keep_choice_order=True) + dialog.open() + + def _close(self, choice): loop = self.app.wallet.network.asyncio_loop - coro = asyncio.run_coroutine_threadsafe(self.app.wallet.lnworker.close_channel(self.chan.channel_id), loop) + if choice == 1: + coro = self.app.wallet.lnworker.request_force_close_from_backup(self.chan.channel_id) + msg = _('Request sent') + else: + coro = self.app.wallet.lnworker.close_channel(self.chan.channel_id) + msg = _('Channel closed') + f = asyncio.run_coroutine_threadsafe(coro, loop) try: - coro.result(5) - self.app.show_info(_('Channel closed')) + f.result(5) + self.app.show_info(msg) except Exception as e: self.logger.exception("Could not close channel") self.app.show_info(_('Could not close channel: ') + repr(e)) # repr because str(Exception()) == '' diff --git a/electrum/gui/messages.py b/electrum/gui/messages.py index 6b28dc6e2..6f98367a3 100644 --- a/electrum/gui/messages.py +++ b/electrum/gui/messages.py @@ -1,14 +1,11 @@ # note: qt and kivy use different i18n methods MSG_RECOVERABLE_CHANNELS = """ -Add extra data to your channel funding transactions, so that a static backup can be -recovered from your seed. +Add extra data to your channel funding transactions, so that a static backup can be recovered from your seed. -Note that static backups only allow you to request a force-close with the remote node. -This assumes that the remote node is still online, did not lose its data, and accepts -to force close the channel. +Note that static backups only allow you to request a force-close with the remote node. This assumes that the remote node is still online, did not lose its data, and accepts to force close the channel. -If this is enabled, other nodes cannot open a channel to you. Channel recovery data -is encrypted, so that only your wallet can decrypt it. However, blockchain analysis -will be able to tell that the transaction was probably created by Electrum. +If this is enabled, other nodes cannot open a channel to you. Channel recovery data is encrypted, so that only your wallet can decrypt it. However, blockchain analysis will be able to tell that the transaction was probably created by Electrum. """ + +MSG_REQUEST_FORCE_CLOSE = """If you choose to request force-close, your node will pretend that it has lost its data and ask the remote node to broadcast their latest state. Doing so from time to time helps make sure that nodes are honest, because your node can punish them if they broadcast a revoked state.""" diff --git a/electrum/gui/qt/channels_list.py b/electrum/gui/qt/channels_list.py index 03c0cd6b6..5b858ebb1 100644 --- a/electrum/gui/qt/channels_list.py +++ b/electrum/gui/qt/channels_list.py @@ -16,6 +16,7 @@ from electrum.wallet import Abstract_Wallet from electrum.lnutil import LOCAL, REMOTE, format_short_channel_id, LN_MAX_FUNDING_SAT from electrum.lnworker import LNWallet from electrum import ecc +from electrum.gui import messages from .util import (MyTreeView, WindowModalDialog, Buttons, OkButton, CancelButton, EnterButton, WaitingDialog, MONOSPACE_FONT, ColorScheme) @@ -111,9 +112,7 @@ class ChannelsList(MyTreeView): self.is_force_close = False msg = _('Close channel?') force_cb = QCheckBox('Request force close from remote peer') - tooltip = _( - 'If you check this option, your node will pretend that it has lost its data and ask the remote node to broadcast their latest state. ' - 'Doing so from time to time helps make sure that nodes are honest, because your node can punish them if they broadcast a revoked state.') + tooltip = _(messages.MSG_REQUEST_FORCE_CLOSE) tooltip = '' + tooltip + '' # rich text is word wrapped def on_checked(b): self.is_force_close = bool(b)