diff --git a/electrum/daemon.py b/electrum/daemon.py index 3dd4e263b..818b32786 100644 --- a/electrum/daemon.py +++ b/electrum/daemon.py @@ -564,6 +564,8 @@ class Daemon(Logger): if os.path.exists(path): os.unlink(path) self.update_recently_opened_wallets(path, remove=True) + if self.config.CURRENT_WALLET == path: + self.config.CURRENT_WALLET = None return True return False @@ -665,11 +667,12 @@ class Daemon(Logger): asyncio.run_coroutine_threadsafe(self.stop(), self.asyncio_loop).result() @with_wallet_lock - def _check_password_for_directory(self, *, old_password, new_password=None, wallet_dir: str) -> Tuple[bool, bool]: + def check_password_for_directory(self, *, old_password, new_password=None, wallet_dir: str) -> Tuple[bool, bool, list[str]]: """Checks password against all wallets (in dir), returns whether they can be unified and whether they are already. If new_password is not None, update all wallet passwords to new_password. """ assert os.path.exists(wallet_dir), f"path {wallet_dir!r} does not exist" + succeeded = [] failed = [] is_unified = True for filename in os.listdir(wallet_dir): @@ -708,9 +711,11 @@ class Daemon(Logger): if new_password: self.logger.info(f'updating password for wallet: {path!r}') wallet.update_password(old_password_real, new_password, encrypt_storage=True) + succeeded.append(path) + can_be_unified = failed == [] is_unified = can_be_unified and is_unified - return can_be_unified, is_unified + return can_be_unified, is_unified, succeeded @with_wallet_lock def update_password_for_directory( @@ -726,13 +731,13 @@ class Daemon(Logger): return False if wallet_dir is None: wallet_dir = os.path.dirname(self.config.get_wallet_path()) - can_be_unified, is_unified = self._check_password_for_directory( + can_be_unified, is_unified, _ = self.check_password_for_directory( old_password=old_password, new_password=None, wallet_dir=wallet_dir) if not can_be_unified: return False if is_unified and old_password == new_password: return True - self._check_password_for_directory( + self.check_password_for_directory( old_password=old_password, new_password=new_password, wallet_dir=wallet_dir) return True diff --git a/electrum/gui/qml/components/LoadingWalletDialog.qml b/electrum/gui/qml/components/LoadingWalletDialog.qml index 2969cdc4d..a680a8ef1 100644 --- a/electrum/gui/qml/components/LoadingWalletDialog.qml +++ b/electrum/gui/qml/components/LoadingWalletDialog.qml @@ -44,7 +44,13 @@ ElDialog { console.log('daemon loading ' + Daemon.loading) if (!Daemon.loading) { showTimer.stop() - dialog.close() + if (dialog.visible) { + dialog.close() + } else { + // if the dialog wasn't visible its onClosed callbacks don't get called, so it + // needs to be destroyed manually + Qt.callLater(function() { dialog.destroy() }) + } } } } diff --git a/electrum/gui/qml/components/OpenWalletDialog.qml b/electrum/gui/qml/components/OpenWalletDialog.qml index b6209dec5..51ec37e61 100644 --- a/electrum/gui/qml/components/OpenWalletDialog.qml +++ b/electrum/gui/qml/components/OpenWalletDialog.qml @@ -12,6 +12,7 @@ ElDialog { property string name property string path + property bool isStartup property bool _invalidPassword: false property bool _unlockClicked: false @@ -40,7 +41,7 @@ ElDialog { InfoTextArea { id: notice - text: Daemon.singlePasswordEnabled || !Daemon.currentWallet + text: Daemon.singlePasswordEnabled || isStartup ? qsTr('Please enter password') : qsTr('Wallet %1 requires password to unlock').arg(name) iconStyle: InfoTextArea.IconStyle.Warn @@ -94,9 +95,39 @@ ElDialog { Daemon.loadWallet(openwalletdialog.path, password.text) } + function maybeUnlockAnyOtherWallet() { + // try to open any other wallet with the password the user entered, hack to improve ux for + // users with non-unified wallet password. + // we should only fall back to opening a random wallet if: + // - the user did not select a specific wallet, otherwise this is confusing + // - there can be more than one password, otherwise this scan would be pointless + if (Daemon.availableWallets.rowCount() <= 1 || password.text === '') { + return false + } + if (Config.walletDidUseSinglePassword) { + // the last time the wallet was unlocked all wallets used the same password. + // trying to decrypt all of them now is most probably useless. + return false + } + if (!openwalletdialog.isStartup) { + return false // this dialog got opened because the user clicked on a specific wallet + } + let wallet_paths = Daemon.getWalletsUnlockableWithPassword(password.text) + if (wallet_paths && wallet_paths.length > 0) { + console.log('could not unlock recent wallet, falling back to: ' + wallet_paths[0]) + Daemon.loadWallet(wallet_paths[0], password.text) + return true + } + return false + } + Connections { target: Daemon function onWalletRequiresPassword() { + if (maybeUnlockAnyOtherWallet()) { + password.text = '' // reset pw so we cannot end up in a loop + return + } console.log('invalid password') _invalidPassword = true password.tf.forceActiveFocus() diff --git a/electrum/gui/qml/components/WalletDetails.qml b/electrum/gui/qml/components/WalletDetails.qml index ea1052063..c0fddd99d 100644 --- a/electrum/gui/qml/components/WalletDetails.qml +++ b/electrum/gui/qml/components/WalletDetails.qml @@ -525,15 +525,33 @@ Pane { confirmPassword: true, title: qsTr('Enter new password'), infotext: qsTr('If you forget your password, you\'ll need to restore from seed. Please make sure you have your seed stored safely') + + (Daemon.availableWallets.rowCount() > 1 && Config.walletShouldUseSinglePassword + ? "\n\n" + qsTr('The new password needs to match the password of any other existing wallet.') + : "") }) dialog.accepted.connect(function() { - var success = Daemon.currentWallet.setPassword(dialog.password) + if (Config.walletShouldUseSinglePassword // android + && Daemon.availableWallets.rowCount() > 1 // has more than one wallet + && Daemon.numWalletsWithPassword(dialog.password) < 1 // no other wallet uses this new password + ) { + var success = false + var error_msg = [ + qsTr('You need to use the password of any other existing wallet.'), + qsTr('Using different wallet passwords is not supported.'), + ].join("\n") + } else { + var success = Daemon.currentWallet.setPassword(dialog.password) + if (success && Config.walletShouldUseSinglePassword) { + Daemon.singlePassword = dialog.password + } + var error_msg = qsTr('Password change failed') + } var done_dialog = app.messageDialog.createObject(app, { title: success ? qsTr('Success') : qsTr('Error'), iconSource: success ? Qt.resolvedUrl('../../icons/info.png') : Qt.resolvedUrl('../../icons/warning.png'), - text: success ? qsTr('Password changed') : qsTr('Password change failed') + text: success ? qsTr('Password changed') : error_msg }) done_dialog.open() }) diff --git a/electrum/gui/qml/components/WalletMainView.qml b/electrum/gui/qml/components/WalletMainView.qml index a9755ac81..c39cda5ed 100644 --- a/electrum/gui/qml/components/WalletMainView.qml +++ b/electrum/gui/qml/components/WalletMainView.qml @@ -11,10 +11,9 @@ import "controls" Item { id: mainView - property string title: Daemon.currentWallet ? Daemon.currentWallet.name : qsTr('no wallet loaded') + property string title: Daemon.currentWallet.name property var _sendDialog - property string _intentUri property string _request_amount property string _request_description @@ -188,7 +187,7 @@ Item { icon.source: '../../icons/wallet.png' action: Action { text: qsTr('Wallet details') - enabled: Daemon.currentWallet && app.stack.currentItem.objectName != 'WalletDetails' + enabled: app.stack.currentItem.objectName != 'WalletDetails' onTriggered: menu.openPage(Qt.resolvedUrl('WalletDetails.qml')) } } @@ -198,7 +197,7 @@ Item { action: Action { text: qsTr('Addresses/Coins'); onTriggered: menu.openPage(Qt.resolvedUrl('Addresses.qml')); - enabled: Daemon.currentWallet && app.stack.currentItem.objectName != 'Addresses' + enabled: app.stack.currentItem.objectName != 'Addresses' } } MenuItem { @@ -206,7 +205,7 @@ Item { icon.source: '../../icons/lightning.png' action: Action { text: qsTr('Channels'); - enabled: Daemon.currentWallet && Daemon.currentWallet.isLightning && app.stack.currentItem.objectName != 'Channels' + enabled: Daemon.currentWallet.isLightning && app.stack.currentItem.objectName != 'Channels' onTriggered: menu.openPage(Qt.resolvedUrl('Channels.qml')) } } @@ -285,62 +284,16 @@ Item { History { id: history - visible: Daemon.currentWallet Layout.fillWidth: true Layout.fillHeight: true } - ColumnLayout { - Layout.alignment: Qt.AlignHCenter - Layout.fillHeight: true - spacing: 2*constants.paddingXLarge - visible: !Daemon.currentWallet - - Item { - Layout.fillHeight: true - } - Label { - Layout.alignment: Qt.AlignHCenter - text: qsTr('No wallet loaded') - font.pixelSize: constants.fontSizeXXLarge - } - - Pane { - Layout.alignment: Qt.AlignHCenter - padding: 0 - background: Rectangle { - color: Material.dialogColor - } - FlatButton { - text: qsTr('Open/Create Wallet') - icon.source: '../../icons/wallet.png' - onClicked: { - if (Daemon.availableWallets.rowCount() > 0) { - stack.push(Qt.resolvedUrl('Wallets.qml')) - } else { - var newww = app.newWalletWizard.createObject(app) - newww.walletCreated.connect(function() { - Daemon.availableWallets.reload() - // and load the new wallet - Daemon.loadWallet(newww.path, newww.wizard_data['password']) - }) - newww.open() - } - } - } - } - Item { - Layout.fillHeight: true - } - } - ButtonContainer { id: buttonContainer Layout.fillWidth: true FlatButton { id: receiveButton - visible: Daemon.currentWallet Layout.fillWidth: true Layout.preferredWidth: 1 icon.source: '../../icons/tab_receive.png' @@ -357,7 +310,6 @@ Item { } } FlatButton { - visible: Daemon.currentWallet Layout.fillWidth: true Layout.preferredWidth: 1 icon.source: '../../icons/tab_send.png' @@ -489,25 +441,22 @@ Item { } Connections { - target: AppController - function onUriReceived(uri) { - console.log('uri received: ' + uri) - if (!Daemon.currentWallet) { - console.log('No wallet open, deferring') - _intentUri = uri + target: Daemon + function onWalletLoaded() { + if (!Daemon.currentWallet) { // wallet got deleted + app.stack.replaceRoot('Wallets.qml') return } - piResolver.recipient = uri + infobanner.hide() // start hidden when switching wallets } } Connections { - target: Daemon - function onWalletLoaded() { - infobanner.hide() // start hidden when switching wallets - if (_intentUri) { - piResolver.recipient = _intentUri - _intentUri = '' + target: app + function onPendingIntentChanged() { + if (app.pendingIntent) { + piResolver.recipient = app.pendingIntent + app.pendingIntent = "" } } } @@ -819,5 +768,12 @@ Item { } } + Component.onCompleted: { + console.log("WalletMainView completed: ", Daemon.currentWallet.name) + if (app.pendingIntent) { + piResolver.recipient = app.pendingIntent + app.pendingIntent = "" + } + } } diff --git a/electrum/gui/qml/components/Wallets.qml b/electrum/gui/qml/components/Wallets.qml index 52dc31720..b38fdf78a 100644 --- a/electrum/gui/qml/components/Wallets.qml +++ b/electrum/gui/qml/components/Wallets.qml @@ -121,7 +121,21 @@ Pane { Layout.fillWidth: true text: qsTr('Create Wallet') icon.source: '../../icons/add.png' - onClicked: rootItem.createWallet() + onClicked: { + if (Daemon.availableWallets.rowCount() > 0 && Config.walletShouldUseSinglePassword + && (!Daemon.singlePassword || Daemon.numWalletsWithPassword(Daemon.singlePassword) < 1)) { + // if the user has wallets but hasn't unlocked any wallet yet force them to do so. + // this ensures they know at least one wallets password and can complete the wizard + // where they will need to enter the password of an existing wallet. + var dialog = app.messageDialog.createObject(app, { + title: qsTr('Wallet unlock required'), + text: qsTr("You have to unlock any existing wallet first before creating a new wallet."), + }) + dialog.open() + } else { + rootItem.createWallet() + } + } } } @@ -129,7 +143,11 @@ Pane { target: Daemon function onWalletLoaded() { if (app.stack.currentItem.objectName == 'Wallets') - app.stack.pop() + if (app.stack.getRoot().objectName == 'Wallets') { + app.stack.replaceRoot('WalletMainView.qml') + } else { + app.stack.pop() + } } } diff --git a/electrum/gui/qml/components/main.qml b/electrum/gui/qml/components/main.qml index cfdbf4eeb..c8d364525 100644 --- a/electrum/gui/qml/components/main.qml +++ b/electrum/gui/qml/components/main.qml @@ -38,6 +38,8 @@ ApplicationWindow property alias keyboardFreeZone: _keyboardFreeZone property alias infobanner: _infobanner + property string pendingIntent: "" + property variant activeDialogs: [] property var _exceptionDialog @@ -120,7 +122,7 @@ ApplicationWindow header: ToolBar { id: toolbar - + // Add top margin for status bar on Android when using edge-to-edge topPadding: app.statusBarHeight @@ -269,7 +271,7 @@ ApplicationWindow Layout.fillWidth: true initialItem: Component { - WalletMainView {} + Wallets {} } function getRoot() { @@ -282,6 +284,10 @@ ApplicationWindow mainStackView.push(item) } } + function replaceRoot(item_url) { + mainStackView.clear() + mainStackView.push(Qt.resolvedUrl(item_url)) + } } // Add bottom padding for navigation bar on Android when UI is edge-to-edge @@ -628,12 +634,18 @@ ApplicationWindow } property var _opendialog: undefined + property var _opendialog_startup: true function showOpenWalletDialog(name, path) { if (_opendialog == undefined) { - _opendialog = openWalletDialog.createObject(app, { name: name, path: path }) + _opendialog = openWalletDialog.createObject(app, { + name: name, + path: path, + isStartup: _opendialog_startup, + }) _opendialog.closed.connect(function() { _opendialog = undefined + _opendialog_startup = false }) _opendialog.open() } @@ -698,6 +710,10 @@ ApplicationWindow if (obj != null) app.pluginobjects[name] = obj } + function onUriReceived(uri) { + console.log('uri received (main): ' + uri) + app.pendingIntent = uri + } } function pluginsComponentsByName(comp_name) { diff --git a/electrum/gui/qml/components/wizard/WCWalletPassword.qml b/electrum/gui/qml/components/wizard/WCWalletPassword.qml index eafd6e7ee..b71bfefc9 100644 --- a/electrum/gui/qml/components/wizard/WCWalletPassword.qml +++ b/electrum/gui/qml/components/wizard/WCWalletPassword.qml @@ -5,37 +5,70 @@ import QtQuick.Controls.Material import "../controls" +// We will only end up here if Daemon.singlePasswordEnabled == False. +// If there are existing wallets, the user must reuse the password of one of them. +// This way they are guided towards password unification. +// NOTE: This also needs to be enforced when changing a wallets password. + WizardComponent { - valid: password1.text === password2.text && password1.text.length >= 6 + id: root + valid: isInputValid() + property bool enforceExistingPassword: Config.walletShouldUseSinglePassword && Daemon.availableWallets.rowCount() > 0 + property bool passwordMatchesAnyExisting: false function apply() { wizard_data['password'] = password1.text wizard_data['encrypt'] = password1.text != '' } + function isInputValid() { + if (password1.text == "") { + return false + } + if (enforceExistingPassword) { + return passwordMatchesAnyExisting + } + return password1.text === password2.text && password1.text.length >= 6 + } + + Timer { + id: passwordComparisonTimer + interval: 500 + repeat: false + onTriggered: { + root.passwordMatchesAnyExisting = Daemon.numWalletsWithPassword(password1.text) > 0 + } + } + ColumnLayout { anchors.fill: parent Label { Layout.fillWidth: true - text: Daemon.singlePasswordEnabled - ? qsTr('Enter password') - : qsTr('Enter password for %1').arg(wizard_data['wallet_name']) + text: !enforceExistingPassword ? qsTr('Enter password') : qsTr('Enter existing password') wrapMode: Text.Wrap } PasswordField { id: password1 + onTextChanged: { + if (enforceExistingPassword) { + root.passwordMatchesAnyExisting = false + passwordComparisonTimer.restart() + } + } } Label { text: qsTr('Enter password (again)') + visible: !enforceExistingPassword } PasswordField { id: password2 showReveal: false echoMode: password1.echoMode + visible: !enforceExistingPassword } RowLayout { @@ -44,7 +77,7 @@ WizardComponent { Layout.rightMargin: constants.paddingXLarge Layout.topMargin: constants.paddingXLarge - visible: password1.text != '' + visible: password1.text != '' && !enforceExistingPassword Label { Layout.rightMargin: constants.paddingLarge @@ -65,13 +98,30 @@ WizardComponent { InfoTextArea { Layout.alignment: Qt.AlignCenter text: qsTr('Passwords don\'t match') - visible: password1.text != password2.text + visible: (password1.text != password2.text) && !enforceExistingPassword iconStyle: InfoTextArea.IconStyle.Warn } InfoTextArea { Layout.alignment: Qt.AlignCenter text: qsTr('Password too short') - visible: (password1.text == password2.text) && !valid + visible: (password1.text == password2.text) && !valid && !enforceExistingPassword + iconStyle: InfoTextArea.IconStyle.Warn + } + InfoTextArea { + Layout.alignment: Qt.AlignCenter + Layout.fillWidth: true + visible: password1.text == "" && enforceExistingPassword + text: [ + qsTr("Use the password of any existing wallet."), + qsTr("Creating new wallets with different passwords is not supported.") + ].join("\n") + iconStyle: InfoTextArea.IconStyle.Info + } + InfoTextArea { + Layout.alignment: Qt.AlignCenter + Layout.fillWidth: true + visible: password1.text != "" && !valid && enforceExistingPassword + text: qsTr('Password does not match any existing wallets password.') iconStyle: InfoTextArea.IconStyle.Warn } } diff --git a/electrum/gui/qml/qeconfig.py b/electrum/gui/qml/qeconfig.py index 38cd80636..d0d4d79e1 100644 --- a/electrum/gui/qml/qeconfig.py +++ b/electrum/gui/qml/qeconfig.py @@ -331,6 +331,25 @@ class QEConfig(AuthMixin, QObject): self._lnutxoreserve = QEAmount(amount_sat=self.config.LN_UTXO_RESERVE) return self._lnutxoreserve + walletShouldUseSinglePasswordChanged = pyqtSignal() + @pyqtProperty(bool, notify=walletShouldUseSinglePasswordChanged) + def walletShouldUseSinglePassword(self): + """ + NOTE: this only indicates if we even want to use a single password, to check if we + actually use a single password the daemon needs to be checked. + """ + return self.config.WALLET_SHOULD_USE_SINGLE_PASSWORD + + walletDidUseSinglePasswordChanged = pyqtSignal() + @pyqtProperty(bool, notify=walletDidUseSinglePasswordChanged) + def walletDidUseSinglePassword(self): + """ + Allows to guess if this is a unified password instance without having + unlocked any wallet yet. Might be out of sync e.g. if wallet files get copied manually. + """ + # TODO: consider removing once encrypted wallet file headers are available + return self.config.WALLET_DID_USE_SINGLE_PASSWORD + @pyqtSlot('qint64', result=str) @pyqtSlot(QEAmount, result=str) def formatSatsForEditing(self, satoshis): diff --git a/electrum/gui/qml/qedaemon.py b/electrum/gui/qml/qedaemon.py index f9af213e3..f76a8fbe3 100644 --- a/electrum/gui/qml/qedaemon.py +++ b/electrum/gui/qml/qedaemon.py @@ -98,7 +98,7 @@ class QEWalletListModel(QAbstractListModel): i += 1 if remove >= 0: - self.beginRemoveRows(QModelIndex(), i, i) + self.beginRemoveRows(QModelIndex(), remove, remove) self._wallets = wallets self.endRemoveRows() @@ -219,7 +219,7 @@ class QEDaemon(AuthMixin, QObject): except InvalidPassword: self.walletRequiresPassword.emit(self._name, self._path) except FileNotFoundError: - self.walletOpenError.emit(_('File not found')) + self.walletOpenError.emit(_('File not found') + f":\n{self._path}") except StorageReadWriteError: self.walletOpenError.emit(_('Could not read/write file')) except WalletFileException as e: @@ -230,13 +230,14 @@ class QEDaemon(AuthMixin, QObject): if wallet is None: return - if self.daemon.config.WALLET_USE_SINGLE_PASSWORD: + if self.daemon.config.WALLET_SHOULD_USE_SINGLE_PASSWORD: self._use_single_password = self.daemon.update_password_for_directory(old_password=local_password, new_password=local_password) self._password = local_password self.singlePasswordChanged.emit() self._logger.info(f'use single password: {self._use_single_password}') else: self._logger.info('use single password disabled by config') + self.daemon.config.WALLET_DID_USE_SINGLE_PASSWORD = self._use_single_password run_hook('load_wallet', wallet) @@ -318,15 +319,59 @@ class QEDaemon(AuthMixin, QObject): def fx(self): return self.qefx + @pyqtSlot(str, result=list) + def getWalletsUnlockableWithPassword(self, password: str) -> list[str]: + """ + Returns any wallet that can be unlocked with the given password. + Can be used as fallback to unlock another wallet the user entered a + password that doesn't work for the current wallet but might work for another one. + """ + wallet_dir = os.path.dirname(self.daemon.config.get_wallet_path()) + _, _, wallet_paths_can_unlock = self.daemon.check_password_for_directory( + old_password=password, + new_password=None, + wallet_dir=wallet_dir, + ) + if not wallet_paths_can_unlock: + return [] + self._logger.debug(f"getWalletsUnlockableWithPassword: can unlock {len(wallet_paths_can_unlock)} wallets") + return [str(path) for path in wallet_paths_can_unlock] + + @pyqtSlot(str, result=int) + def numWalletsWithPassword(self, password: str) -> int: + """Returns the number of wallets that can be unlocked with the given password""" + wallet_paths_can_unlock = self.getWalletsUnlockableWithPassword(password) + return len(wallet_paths_can_unlock) + singlePasswordChanged = pyqtSignal() @pyqtProperty(bool, notify=singlePasswordChanged) def singlePasswordEnabled(self): + """ + singlePasswordEnabled is False if: + a.) the user has no wallet (and password) yet + b.) the user has wallets with different passwords (legacy) + c.) all wallets are locked, we couldn't check yet if they all use the same password + d.) we are on desktop where different passwords are allowed + """ return self._use_single_password @pyqtProperty(str, notify=singlePasswordChanged) def singlePassword(self): + """ + self._password is also set to the last loaded wallet password if we WANT a single password, + but don't actually have a single password yet. So singlePassword being set doesn't strictly + mean all wallets use the same password. + """ return self._password + @singlePassword.setter + def singlePassword(self, password: str): + assert password + assert self.daemon.config.WALLET_SHOULD_USE_SINGLE_PASSWORD + if self._password != password: + self._password = password + self.singlePasswordChanged.emit() + @pyqtSlot(result=str) def suggestWalletName(self): # FIXME why not use util.get_new_wallet_name ? diff --git a/electrum/simple_config.py b/electrum/simple_config.py index 32638bda0..a74818626 100644 --- a/electrum/simple_config.py +++ b/electrum/simple_config.py @@ -675,7 +675,9 @@ class SimpleConfig(Logger): ) WALLET_UNCONF_UTXO_FREEZE_THRESHOLD_SAT = ConfigVar('unconf_utxo_freeze_threshold', default=5_000, type_=int) WALLET_PAYREQ_EXPIRY_SECONDS = ConfigVar('request_expiry', default=invoices.PR_DEFAULT_EXPIRATION_WHEN_CREATING, type_=int) - WALLET_USE_SINGLE_PASSWORD = ConfigVar('single_password', default=False, type_=bool) + WALLET_SHOULD_USE_SINGLE_PASSWORD = ConfigVar('should_use_single_password', default=False, type_=bool) + # TODO: consider removing WALLET_DID_USE_SINGLE_PASSWORD once encrypted wallet file headers are available + WALLET_DID_USE_SINGLE_PASSWORD = ConfigVar('did_use_single_password', default=False, type_=bool) # note: 'use_change' and 'multiple_change' are per-wallet settings WALLET_SEND_CHANGE_TO_LIGHTNING = ConfigVar( 'send_change_to_lightning', default=False, type_=bool, diff --git a/run_electrum b/run_electrum index 3d75478c5..df45836e1 100755 --- a/run_electrum +++ b/run_electrum @@ -397,7 +397,7 @@ def main(): 'verbosity': '*' if util.is_android_debug_apk() else '', 'cmd': 'gui', SimpleConfig.GUI_NAME.key(): 'qml', - SimpleConfig.WALLET_USE_SINGLE_PASSWORD.key(): True, + SimpleConfig.WALLET_SHOULD_USE_SINGLE_PASSWORD.key(): True, } SimpleConfig.set_chain_config_opt_based_on_android_packagename(config_options) else: diff --git a/tests/test_daemon.py b/tests/test_daemon.py index 08f53cf47..f5bbbade2 100644 --- a/tests/test_daemon.py +++ b/tests/test_daemon.py @@ -52,7 +52,7 @@ class TestUnifiedPassword(DaemonTestCase): def setUp(self): super().setUp() - self.config.WALLET_USE_SINGLE_PASSWORD = True + self.config.WALLET_SHOULD_USE_SINGLE_PASSWORD = True def _run_post_unif_sanity_checks(self, paths: Iterable[str], *, password: str): for path in paths: @@ -64,8 +64,11 @@ class TestUnifiedPassword(DaemonTestCase): self.assertTrue(w.has_keystore_encryption()) if w.has_seed(): self.assertIsInstance(w.get_seed(password), str) - can_be_unified, is_unified = self.daemon._check_password_for_directory(old_password=password, wallet_dir=self.wallet_dir) - self.assertEqual((True, True), (can_be_unified, is_unified)) + can_be_unified, is_unified, wallet_paths_can_unlock = self.daemon.check_password_for_directory( + old_password=password, + wallet_dir=self.wallet_dir, + ) + self.assertEqual((True, True, len(paths)), (can_be_unified, is_unified, len(wallet_paths_can_unlock))) # "cannot unify pw" tests ---> @@ -77,7 +80,7 @@ class TestUnifiedPassword(DaemonTestCase): with open(path2, "rb") as f: raw2_before = f.read() - can_be_unified, is_unified = self.daemon._check_password_for_directory(old_password="123456", wallet_dir=self.wallet_dir) + can_be_unified, is_unified, _ = self.daemon.check_password_for_directory(old_password="123456", wallet_dir=self.wallet_dir) self.assertEqual((False, False), (can_be_unified, is_unified)) is_unified = self.daemon.update_password_for_directory(old_password="123456", new_password="123456") self.assertFalse(is_unified) @@ -100,7 +103,7 @@ class TestUnifiedPassword(DaemonTestCase): with open(path3, "rb") as f: raw3_before = f.read() - can_be_unified, is_unified = self.daemon._check_password_for_directory(old_password="123456", wallet_dir=self.wallet_dir) + can_be_unified, is_unified, _ = self.daemon.check_password_for_directory(old_password="123456", wallet_dir=self.wallet_dir) self.assertEqual((False, False), (can_be_unified, is_unified)) is_unified = self.daemon.update_password_for_directory(old_password="123456", new_password="123456") self.assertFalse(is_unified) @@ -120,7 +123,7 @@ class TestUnifiedPassword(DaemonTestCase): async def test_can_unify_two_std_wallets_both_have_ks_and_sto_enc(self): path1 = self._restore_wallet_from_text("9dk", password="123456", encrypt_file=True) path2 = self._restore_wallet_from_text("x8", password="123456", encrypt_file=True) - can_be_unified, is_unified = self.daemon._check_password_for_directory(old_password="123456", wallet_dir=self.wallet_dir) + can_be_unified, is_unified, _ = self.daemon.check_password_for_directory(old_password="123456", wallet_dir=self.wallet_dir) self.assertEqual((True, True), (can_be_unified, is_unified)) is_unified = self.daemon.update_password_for_directory(old_password="123456", new_password="123456") self.assertTrue(is_unified) @@ -132,7 +135,7 @@ class TestUnifiedPassword(DaemonTestCase): with open(path2, "rb") as f: raw2_before = f.read() - can_be_unified, is_unified = self.daemon._check_password_for_directory(old_password="123456", wallet_dir=self.wallet_dir) + can_be_unified, is_unified, _ = self.daemon.check_password_for_directory(old_password="123456", wallet_dir=self.wallet_dir) self.assertEqual((True, False), (can_be_unified, is_unified)) is_unified = self.daemon.update_password_for_directory(old_password="123456", new_password="123456") self.assertTrue(is_unified) @@ -145,7 +148,7 @@ class TestUnifiedPassword(DaemonTestCase): async def test_can_unify_two_std_wallets_one_without_password(self): path1 = self._restore_wallet_from_text("9dk", password=None) path2 = self._restore_wallet_from_text("x8", password="123456", encrypt_file=True) - can_be_unified, is_unified = self.daemon._check_password_for_directory(old_password="123456", wallet_dir=self.wallet_dir) + can_be_unified, is_unified, _ = self.daemon.check_password_for_directory(old_password="123456", wallet_dir=self.wallet_dir) self.assertEqual((True, False), (can_be_unified, is_unified)) is_unified = self.daemon.update_password_for_directory(old_password="123456", new_password="123456") self.assertTrue(is_unified) @@ -179,7 +182,7 @@ class TestUnifiedPassword(DaemonTestCase): paths.append(self._restore_wallet_from_text(addrs, password="123456", encrypt_file=False)) paths.append(self._restore_wallet_from_text(addrs, password=None)) # do unification - can_be_unified, is_unified = self.daemon._check_password_for_directory(old_password="123456", wallet_dir=self.wallet_dir) + can_be_unified, is_unified, _ = self.daemon.check_password_for_directory(old_password="123456", wallet_dir=self.wallet_dir) self.assertEqual((True, False), (can_be_unified, is_unified)) is_unified = self.daemon.update_password_for_directory(old_password="123456", new_password="123456") self.assertTrue(is_unified)