From 495d8d6faee2a20e60d251160950c75c49445383 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Wed, 16 Nov 2022 16:50:23 +0100 Subject: [PATCH] qml: add import channel backup --- electrum/gui/qml/components/Channels.qml | 26 ++++ .../components/ImportChannelBackupDialog.qml | 118 ++++++++++++++++++ .../components/controls/ChannelDelegate.qml | 5 +- electrum/gui/qml/qewallet.py | 20 +++ 4 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 electrum/gui/qml/components/ImportChannelBackupDialog.qml diff --git a/electrum/gui/qml/components/Channels.qml b/electrum/gui/qml/components/Channels.qml index e5bc282a8..7bcd16609 100644 --- a/electrum/gui/qml/components/Channels.qml +++ b/electrum/gui/qml/components/Channels.qml @@ -144,10 +144,36 @@ Pane { icon.source: '../../icons/lightning.png' } + FlatButton { + Layout.fillWidth: true + text: qsTr('Import channel backup') + onClicked: { + var dialog = importChannelBackupDialog.createObject(root) + dialog.open() + } + icon.source: '../../icons/file.png' + } + + } + + Connections { + target: Daemon.currentWallet + function onImportChannelBackupFailed(message) { + var dialog = app.messageDialog.createObject(root, { text: message }) + dialog.open() + } } Component { id: swapDialog SwapDialog {} } + + Component { + id: importChannelBackupDialog + ImportChannelBackupDialog { + onClosed: destroy() + } + } + } diff --git a/electrum/gui/qml/components/ImportChannelBackupDialog.qml b/electrum/gui/qml/components/ImportChannelBackupDialog.qml new file mode 100644 index 000000000..effd04371 --- /dev/null +++ b/electrum/gui/qml/components/ImportChannelBackupDialog.qml @@ -0,0 +1,118 @@ +import QtQuick 2.6 +import QtQuick.Layouts 1.0 +import QtQuick.Controls 2.3 + +import org.electrum 1.0 + +import "controls" + +ElDialog { + id: root + + property bool valid: false + + standardButtons: Dialog.Close + modal: true + parent: Overlay.overlay + Overlay.modal: Rectangle { + color: "#aa000000" + } + width: parent.width + height: parent.height + + padding: 0 + + title: qsTr('Import channel backup') + + function verifyChannelBackup(text) { + return valid = Daemon.currentWallet.isValidChannelBackup(text) + } + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + RowLayout { + Layout.fillWidth: true + Layout.leftMargin: constants.paddingLarge + + TextArea { + id: channelbackup_ta + Layout.fillWidth: true + Layout.minimumHeight: 80 + font.family: FixedFont + focus: true + wrapMode: TextEdit.WrapAnywhere + onTextChanged: verifyChannelBackup(text) + } + ColumnLayout { + ToolButton { + icon.source: '../../icons/paste.png' + icon.height: constants.iconSizeMedium + icon.width: constants.iconSizeMedium + onClicked: { + channelbackup_ta.text = AppController.clipboardToText() + } + } + ToolButton { + icon.source: '../../icons/qrcode.png' + icon.height: constants.iconSizeMedium + icon.width: constants.iconSizeMedium + scale: 1.2 + onClicked: { + var scan = qrscan.createObject(root.contentItem) + scan.onFound.connect(function() { + channelbackup_ta.text = scan.scanData + scan.destroy() + }) + } + } + } + } + + TextArea { + id: validationtext + visible: text + Layout.fillWidth: true + Layout.leftMargin: constants.paddingLarge + + readOnly: true + wrapMode: TextInput.WordWrap + background: Rectangle { + color: 'transparent' + } + } + + Item { Layout.preferredWidth: 1; Layout.fillHeight: true } + + FlatButton { + Layout.fillWidth: true + enabled: valid + text: qsTr('Import') + onClicked: { + Daemon.currentWallet.importChannelBackup(channelbackup_ta.text) + root.accept() + } + } + } + + Component { + id: qrscan + QRScan { + width: root.contentItem.width + height: root.contentItem.height + + ToolButton { + icon.source: '../../icons/closebutton.png' + icon.height: constants.iconSizeMedium + icon.width: constants.iconSizeMedium + anchors.right: parent.right + anchors.top: parent.top + onClicked: { + parent.destroy() + } + } + } + } + +} diff --git a/electrum/gui/qml/components/controls/ChannelDelegate.qml b/electrum/gui/qml/components/controls/ChannelDelegate.qml index 34d6b39c3..4ae0ffd49 100644 --- a/electrum/gui/qml/components/controls/ChannelDelegate.qml +++ b/electrum/gui/qml/components/controls/ChannelDelegate.qml @@ -60,14 +60,17 @@ ItemDelegate { Label { Layout.fillWidth: true text: model.node_alias ? model.node_alias : model.node_id + font.family: model.node_alias ? app.font.family : FixedFont + font.pixelSize: model.node_alias ? constants.fontSizeMedium : constants.fontSizeSmall elide: Text.ElideRight wrapMode: Text.Wrap - maximumLineCount: 2 + maximumLineCount: model.node_alias ? 2 : 1 color: _closed ? constants.mutedForeground : Material.foreground } Label { text: model.state + font.pixelSize: constants.fontSizeMedium color: _closed ? constants.mutedForeground : model.state == 'OPEN' diff --git a/electrum/gui/qml/qewallet.py b/electrum/gui/qml/qewallet.py index e60191a5d..d8b6612a5 100644 --- a/electrum/gui/qml/qewallet.py +++ b/electrum/gui/qml/qewallet.py @@ -16,6 +16,7 @@ from electrum.transaction import PartialTxOutput from electrum.util import (parse_max_spend, InvalidPassword, event_listener) from electrum.plugin import run_hook from electrum.wallet import Multisig_Wallet +from electrum.crypto import pw_decode_with_version_and_mac from .auth import AuthMixin, auth_protect from .qeaddresslistmodel import QEAddressListModel @@ -65,6 +66,7 @@ class QEWallet(AuthMixin, QObject, QtEventListener): transactionSigned = pyqtSignal([str], arguments=['txid']) broadcastSucceeded = pyqtSignal([str], arguments=['txid']) broadcastFailed = pyqtSignal([str,str,str], arguments=['txid','code','reason']) + importChannelBackupFailed = pyqtSignal([str], arguments=['message']) labelsUpdated = pyqtSignal() otpRequested = pyqtSignal() otpSuccess = pyqtSignal() @@ -676,3 +678,21 @@ class QEWallet(AuthMixin, QObject, QtEventListener): def importPrivateKeys(self, keyslist): self.wallet.import_private_keys(keyslist.split(), self.password) + @pyqtSlot(str) + def importChannelBackup(self, backup_str): + try: + self.wallet.lnworker.import_channel_backup(backup_str) + except Exception as e: + self._logger.debug(f'could not import channel backup: {repr(e)}') + self.importChannelBackupFailed.emit(f'Failed to import backup:\n\n{str(e)}') + + @pyqtSlot(str, result=bool) + def isValidChannelBackup(self, backup_str): + try: + assert backup_str.startswith('channel_backup:') + encrypted = backup_str[15:] + xpub = self.wallet.get_fingerprint() + decrypted = pw_decode_with_version_and_mac(encrypted, xpub) + return True + except Exception as e: + return False