diff --git a/electrum/gui/kivy/uix/dialogs/lightning_channels.py b/electrum/gui/kivy/uix/dialogs/lightning_channels.py index 136f36984..244d8d150 100644 --- a/electrum/gui/kivy/uix/dialogs/lightning_channels.py +++ b/electrum/gui/kivy/uix/dialogs/lightning_channels.py @@ -493,10 +493,10 @@ class ChannelDetailsPopup(Popup, Logger): def update_action_dropdown(self): action_dropdown = self.ids.action_dropdown # type: ActionDropdown + close_options = self.chan.get_close_options() options = [ ActionButtonOption(text=_('Backup'), func=lambda btn: self.export_backup()), - ActionButtonOption(text=_('Close channel'), func=lambda btn: self.close(), enabled=ChanCloseOption.COOP_CLOSE in self.chan.get_close_options()), - ActionButtonOption(text=_('Force-close'), func=lambda btn: self.force_close(), enabled=ChanCloseOption.LOCAL_FCLOSE in self.chan.get_close_options()), + ActionButtonOption(text=_('Close channel'), func=lambda btn: self.close(close_options), enabled=close_options), ActionButtonOption(text=_('Delete'), func=lambda btn: self.remove_channel(), enabled=self.can_be_deleted), ] if not self.chan.is_closed(): @@ -510,10 +510,18 @@ class ChannelDetailsPopup(Popup, Logger): options.append(ActionButtonOption(text=_("Unfreeze") + "\n(for receiving)", func=lambda btn: self.freeze_for_receiving())) action_dropdown.update(options=options) - def close(self): + def close(self, close_options): + choices = {} + if ChanCloseOption.COOP_CLOSE in close_options: + choices[0] = _('Cooperative close') + if ChanCloseOption.REQUEST_REMOTE_FCLOSE in close_options: + choices[1] = _('Request force-close') + if ChanCloseOption.LOCAL_FCLOSE in close_options: + choices[2] = _('Local force-close') dialog = ChoiceDialog( title=_('Close channel'), - choices={0:_('Cooperative close'), 1:_('Request force-close')}, key=0, + choices=choices, + key = min(choices.keys()), callback=self._close, description=_(messages.MSG_REQUEST_FORCE_CLOSE), keep_choice_order=True) @@ -521,12 +529,15 @@ class ChannelDetailsPopup(Popup, Logger): def _close(self, choice): loop = self.app.wallet.network.asyncio_loop - if choice == 1: - coro = self.app.wallet.lnworker.request_force_close(self.chan.channel_id) - msg = _('Request sent') - else: + if choice == 0: coro = self.app.wallet.lnworker.close_channel(self.chan.channel_id) msg = _('Channel closed') + elif choice == 1: + coro = self.app.wallet.lnworker.request_force_close(self.chan.channel_id) + msg = _('Request sent') + elif choice == 2: + self.force_close() + return f = asyncio.run_coroutine_threadsafe(coro, loop) try: f.result(5) diff --git a/electrum/gui/messages.py b/electrum/gui/messages.py index eb1d58098..839c06e59 100644 --- a/electrum/gui/messages.py +++ b/electrum/gui/messages.py @@ -12,9 +12,10 @@ Note that static backups only allow you to request a force-close with the remote 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 = """ -You may choose to initiate a cooperative close, or request the remote peer to force close the channel. +MSG_COOPERATIVE_CLOSE = """ +Your node will negotiate the transaction fee with the remote node. This method of closing the channel usually results in the lowest fees.""" +MSG_REQUEST_FORCE_CLOSE = """ If you request a 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.""" MSG_CREATED_NON_RECOVERABLE_CHANNEL = """ diff --git a/electrum/gui/qml/components/CloseChannelDialog.qml b/electrum/gui/qml/components/CloseChannelDialog.qml index 2bd2c34aa..66029c150 100644 --- a/electrum/gui/qml/components/CloseChannelDialog.qml +++ b/electrum/gui/qml/components/CloseChannelDialog.qml @@ -72,10 +72,16 @@ Dialog { } RadioButton { ButtonGroup.group: closetypegroup - property string closetype: 'force' + property string closetype: 'remote_force' enabled: !closing && channeldetails.canForceClose text: qsTr('Request Force-close') } + RadioButton { + ButtonGroup.group: closetypegroup + property string closetype: 'local_force' + enabled: !closing && channeldetails.canForceClose + text: qsTr('Local Force-close') + } } Button { diff --git a/electrum/gui/qml/qechanneldetails.py b/electrum/gui/qml/qechanneldetails.py index 020a17568..781b48fba 100644 --- a/electrum/gui/qml/qechanneldetails.py +++ b/electrum/gui/qml/qechanneldetails.py @@ -164,8 +164,10 @@ class QEChannelDetails(QObject, QtEventListener): def close_channel(self, closetype): async def do_close(closetype, channel_id): try: - if closetype == 'force': + if closetype == 'remote_force': await self._wallet.wallet.lnworker.request_force_close(channel_id) + elif closetype == 'local_force': + await self._wallet.wallet.lnworker.force_close_channel(channel_id) else: await self._wallet.wallet.lnworker.close_channel(channel_id) self.channelCloseSuccess.emit() diff --git a/electrum/gui/qt/channels_list.py b/electrum/gui/qt/channels_list.py index 59031072e..d044d64b1 100644 --- a/electrum/gui/qt/channels_list.py +++ b/electrum/gui/qt/channels_list.py @@ -120,22 +120,12 @@ class ChannelsList(MyTreeView): def close_channel(self, channel_id): self.is_force_close = False - msg = _('Close channel?') - force_cb = QCheckBox('Request force close from remote peer') - tooltip = _(messages.MSG_REQUEST_FORCE_CLOSE) - tooltip = messages.to_rtf(tooltip) - def on_checked(b): - self.is_force_close = bool(b) - force_cb.stateChanged.connect(on_checked) - force_cb.setToolTip(tooltip) - if not self.parent.question(msg, checkbox=force_cb): + msg = _('Cooperative close?') + msg += '\n' + _(messages.MSG_COOPERATIVE_CLOSE) + if not self.parent.question(msg): return - if self.is_force_close: - coro = self.lnworker.request_force_close(channel_id) - on_success = self.on_request_sent - else: - coro = self.lnworker.close_channel(channel_id) - on_success = self.on_channel_closed + coro = self.lnworker.close_channel(channel_id) + 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) @@ -184,6 +174,7 @@ class ChannelsList(MyTreeView): def request_force_close(self, channel_id): msg = _('Request force-close from remote peer?') + msg += '\n' + _(messages.MSG_REQUEST_FORCE_CLOSE) if not self.parent.question(msg): return def task():