From c4e8869c1a624b8d6a2e51258794b2925620a272 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Tue, 4 Jul 2023 14:05:10 +0200 Subject: [PATCH] qml: add PIN auth to close channel operation. --- .../gui/qml/components/CloseChannelDialog.qml | 41 +++++++------------ electrum/gui/qml/qechanneldetails.py | 25 +++++++++-- 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/electrum/gui/qml/components/CloseChannelDialog.qml b/electrum/gui/qml/components/CloseChannelDialog.qml index 6c7279baa..924173469 100644 --- a/electrum/gui/qml/components/CloseChannelDialog.qml +++ b/electrum/gui/qml/components/CloseChannelDialog.qml @@ -18,7 +18,6 @@ ElDialog { title: qsTr('Close Channel') iconSource: Qt.resolvedUrl('../../icons/lightning_disconnected.png') - property bool _closing: false property string _closing_method closePolicy: Popup.NoAutoClose @@ -114,21 +113,21 @@ ElDialog { id: closetypeCoop ButtonGroup.group: closetypegroup property string closetype: 'cooperative' - enabled: !_closing && channeldetails.canCoopClose + enabled: !channeldetails.isClosing && channeldetails.canCoopClose text: qsTr('Cooperative close') } RadioButton { id: closetypeRemoteForce ButtonGroup.group: closetypegroup property string closetype: 'remote_force' - enabled: !_closing && channeldetails.canForceClose + enabled: !channeldetails.isClosing && channeldetails.canForceClose text: qsTr('Request Force-close') } RadioButton { id: closetypeLocalForce ButtonGroup.group: closetypegroup property string closetype: 'local_force' - enabled: !_closing && channeldetails.canForceClose && !channeldetails.isBackup + enabled: !channeldetails.isClosing && channeldetails.canForceClose && !channeldetails.isBackup text: qsTr('Local Force-close') } } @@ -141,17 +140,17 @@ ElDialog { id: errorText Layout.alignment: Qt.AlignHCenter Layout.maximumWidth: parent.width - visible: !_closing && errorText.text + visible: !channeldetails.isClosing && errorText.text iconStyle: InfoTextArea.IconStyle.Error } Label { Layout.alignment: Qt.AlignHCenter text: qsTr('Closing...') - visible: _closing + visible: channeldetails.isClosing } BusyIndicator { Layout.alignment: Qt.AlignHCenter - visible: _closing + visible: channeldetails.isClosing } } } @@ -162,10 +161,10 @@ ElDialog { Layout.fillWidth: true text: qsTr('Close channel') icon.source: '../../icons/closebutton.png' - enabled: !_closing + enabled: !channeldetails.isClosing onClicked: { if (closetypegroup.checkedButton.closetype == 'local_force') { - showBackupThenConfirmClose() + showBackupThenClose() } else { doCloseChannel() } @@ -173,7 +172,7 @@ ElDialog { } } - function showBackupThenConfirmClose() { + function showBackupThenClose() { var sharedialog = app.genericShareDialog.createObject(app, { title: qsTr('Save channel backup and force close'), text_qr: channeldetails.channelBackup(), @@ -181,24 +180,12 @@ ElDialog { helpTextIconStyle: InfoTextArea.IconStyle.Warn }) sharedialog.closed.connect(function() { - confirmCloseChannel() - }) - sharedialog.open() - } - - function confirmCloseChannel() { - var confirmdialog = app.messageDialog.createObject(app, { - title: qsTr('Confirm force close?'), - yesno: true - }) - confirmdialog.accepted.connect(function() { doCloseChannel() }) - confirmdialog.open() + sharedialog.open() } function doCloseChannel() { - _closing = true _closing_method = closetypegroup.checkedButton.closetype channeldetails.closeChannel(_closing_method) } @@ -215,8 +202,12 @@ ElDialog { wallet: Daemon.currentWallet channelid: dialog.channelid + onAuthRequired: { + app.handleAuthRequired(channeldetails, method, authMessage) + } + onChannelChanged: { - if (!channeldetails.canClose) + if (!channeldetails.canClose || channeldetails.isClosing) return // init default choice @@ -227,7 +218,6 @@ ElDialog { } onChannelCloseSuccess: { - _closing = false if (_closing_method == 'local_force') { showCloseMessage(qsTr('Channel closed. You may need to wait at least %1 blocks, because of CSV delays').arg(channeldetails.toSelfDelay)) } else if (_closing_method == 'remote_force') { @@ -239,7 +229,6 @@ ElDialog { } onChannelCloseFailed: { - _closing = false errorText.text = message } } diff --git a/electrum/gui/qml/qechanneldetails.py b/electrum/gui/qml/qechanneldetails.py index 48585e9a9..af929dd0a 100644 --- a/electrum/gui/qml/qechanneldetails.py +++ b/electrum/gui/qml/qechanneldetails.py @@ -1,4 +1,3 @@ -import asyncio import threading from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, Q_ENUMS @@ -9,12 +8,13 @@ from electrum.logging import get_logger from electrum.lnutil import LOCAL, REMOTE from electrum.lnchannel import ChanCloseOption, ChannelState +from .auth import AuthMixin, auth_protect from .qewallet import QEWallet from .qetypes import QEAmount -from .util import QtEventListener, qt_event_listener, event_listener +from .util import QtEventListener, event_listener -class QEChannelDetails(QObject, QtEventListener): +class QEChannelDetails(AuthMixin, QObject, QtEventListener): _logger = get_logger(__name__) class State: # subset, only ones we currently need in UI @@ -26,6 +26,7 @@ class QEChannelDetails(QObject, QtEventListener): channelChanged = pyqtSignal() channelCloseSuccess = pyqtSignal() channelCloseFailed = pyqtSignal([str], arguments=['message']) + isClosingChanged = pyqtSignal() def __init__(self, parent=None): super().__init__(parent) @@ -39,6 +40,7 @@ class QEChannelDetails(QObject, QtEventListener): self._remote_capacity = QEAmount() self._can_receive = QEAmount() self._can_send = QEAmount() + self._is_closing = False self.register_callbacks() self.destroyed.connect(lambda: self.on_destroy()) @@ -192,6 +194,12 @@ class QEChannelDetails(QObject, QtEventListener): def toSelfDelay(self): return self._channel.config[REMOTE].to_self_delay + @pyqtProperty(bool, notify=isClosingChanged) + def isClosing(self): + # Note: isClosing only applies to a closing action started by this instance, not + # whether the channel is closing + return self._is_closing + @pyqtSlot() def freezeForSending(self): lnworker = self._channel.lnworker @@ -212,19 +220,30 @@ class QEChannelDetails(QObject, QtEventListener): @pyqtSlot(str) def closeChannel(self, closetype): + self.do_close_channel(closetype) + + @auth_protect(message=_('Close Lightning channel?')) + def do_close_channel(self, closetype): channel_id = self._channel.channel_id + def do_close(): try: + self._is_closing = True + self.isClosingChanged.emit() if closetype == 'remote_force': self._wallet.wallet.network.run_from_another_thread(self._wallet.wallet.lnworker.request_force_close(channel_id)) elif closetype == 'local_force': self._wallet.wallet.network.run_from_another_thread(self._wallet.wallet.lnworker.force_close_channel(channel_id)) else: self._wallet.wallet.network.run_from_another_thread(self._wallet.wallet.lnworker.close_channel(channel_id)) + self._logger.debug('Channel close successful') self.channelCloseSuccess.emit() except Exception as e: self._logger.exception("Could not close channel: " + repr(e)) self.channelCloseFailed.emit(_('Could not close channel: ') + repr(e)) + finally: + self._is_closing = False + self.isClosingChanged.emit() threading.Thread(target=do_close, daemon=True).start()