From e75076429d2f0e4b746fd82267285c539fab0624 Mon Sep 17 00:00:00 2001 From: f321x Date: Tue, 12 Aug 2025 10:28:47 +0200 Subject: [PATCH 1/3] wizard: raise more specific exc in create_storage() Modifies `NewWalletWizard.create_storage()` to raise more specific exception types instead of plain `Exception`. This should allow the calling GUI to separate between non-bug exceptions (like invalid user input), and bugs which should not happen and be passed to the bug reporter. --- electrum/wizard.py | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/electrum/wizard.py b/electrum/wizard.py index 48a3cee9f93f..cd782ef91ccb 100644 --- a/electrum/wizard.py +++ b/electrum/wizard.py @@ -12,7 +12,8 @@ from electrum.network import ProxySettings from electrum.plugin import run_hook from electrum.slip39 import EncryptedSeed -from electrum.storage import WalletStorage, StorageEncryptionVersion +from electrum.storage import WalletStorage, StorageEncryptionVersion, StorageReadWriteError +from electrum.util import UserFacingException from electrum.wallet_db import WalletDB from electrum.bip32 import normalize_bip32_derivation, xpub_type from electrum import keystore, mnemonic, bitcoin @@ -661,8 +662,11 @@ def create_storage(self, path: str, data: dict): assert data['wallet_type'] in ['standard', '2fa', 'imported', 'multisig'] if os.path.exists(path): - raise Exception('file already exists at path') - storage = WalletStorage(path) + raise UserFacingException(_('File already exists at path: {}').format(path)) + try: + storage = WalletStorage(path) + except StorageReadWriteError as e: + raise UserFacingException(e) # TODO: refactor using self.keystore_from_data k = None @@ -701,35 +705,35 @@ def create_storage(self, path: str, data: dict): self._logger.debug('creating keystore from 2fa seed') k = keystore.from_xprv(data['x1']['xprv']) else: - raise Exception('unsupported/unknown seed_type %s' % data['seed_type']) + raise NotImplementedError('unsupported/unknown seed_type %s' % data['seed_type']) elif data['keystore_type'] == 'masterkey': k = keystore.from_master_key(data['master_key']) if isinstance(k, keystore.Xpub): # has xpub t1 = xpub_type(k.xpub) if data['wallet_type'] == 'multisig': if t1 not in ['standard', 'p2wsh', 'p2wsh-p2sh']: - raise Exception('wrong key type %s' % t1) + raise UserFacingException(_('Wrong key type {}').format(t1)) else: if t1 not in ['standard', 'p2wpkh', 'p2wpkh-p2sh']: - raise Exception('wrong key type %s' % t1) + raise UserFacingException(_('Wrong key type {}').format(t1)) elif isinstance(k, keystore.Old_KeyStore): pass else: - raise Exception(f'unexpected keystore type: {type(k)}') + raise NotImplementedError(f'unexpected keystore type: {type(k)}') elif data['keystore_type'] == 'hardware': k = self.hw_keystore(data) if isinstance(k, keystore.Xpub): # has xpub t1 = xpub_type(k.xpub) if data['wallet_type'] == 'multisig': if t1 not in ['standard', 'p2wsh', 'p2wsh-p2sh']: - raise Exception('wrong key type %s' % t1) + raise UserFacingException(_('Wrong key type {}').format(t1)) else: if t1 not in ['standard', 'p2wpkh', 'p2wpkh-p2sh']: - raise Exception('wrong key type %s' % t1) + raise UserFacingException(_('Wrong key type {}').format(t1)) else: - raise Exception(f'unexpected keystore type: {type(k)}') + raise NotImplementedError(f'unexpected keystore type: {type(k)}') else: - raise Exception('unsupported/unknown keystore_type %s' % data['keystore_type']) + raise NotImplementedError('unsupported/unknown keystore_type %s' % data['keystore_type']) if data['password']: if k and k.may_have_password(): @@ -764,16 +768,16 @@ def create_storage(self, path: str, data: dict): db.put('use_trustedcoin', True) elif data['wallet_type'] == 'multisig': if not isinstance(k, keystore.Xpub): - raise Exception(f'unexpected keystore(main) type={type(k)} in multisig. not bip32.') + raise TypeError(f'unexpected keystore(main) type={type(k)} in multisig. not bip32.') k_xpub_type = xpub_type(k.xpub) db.put('wallet_type', '%dof%d' % (data['multisig_signatures'], data['multisig_participants'])) db.put('x1', k.dump()) for cosigner in data['multisig_cosigner_data']: cosigner_keystore = self.keystore_from_data('multisig', data['multisig_cosigner_data'][cosigner]) if not isinstance(cosigner_keystore, keystore.Xpub): - raise Exception(f'unexpected keystore(cosigner) type={type(cosigner_keystore)} in multisig. not bip32.') + raise TypeError(f'unexpected keystore(cosigner) type={type(cosigner_keystore)} in multisig. not bip32.') if k_xpub_type != xpub_type(cosigner_keystore.xpub): - raise Exception('multisig wallet needs to have homogeneous xpub types') + raise UserFacingException(_('Multisig wallet needs to have homogeneous xpub types.')) if data['encrypt'] and cosigner_keystore.may_have_password(): cosigner_keystore.update_password(None, data['password']) db.put(f'x{cosigner}', cosigner_keystore.dump()) From 3280b649228d0ba7524aeedc0c6c187a1c3a5975 Mon Sep 17 00:00:00 2001 From: f321x Date: Tue, 12 Aug 2025 10:33:46 +0200 Subject: [PATCH 2/3] qt: wizard: differentiate between create_storage exc types Differentiate between the `UserFacingException` and other exceptions when creating the storage. Forward other exceptions to the reporter so they can get fixed. --- electrum/gui/qt/__init__.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/electrum/gui/qt/__init__.py b/electrum/gui/qt/__init__.py index dcd5092e9766..91ef850b27f2 100644 --- a/electrum/gui/qt/__init__.py +++ b/electrum/gui/qt/__init__.py @@ -70,7 +70,7 @@ from electrum.plugin import run_hook from electrum.util import (UserCancelled, profiler, send_exception_to_crash_reporter, WalletFileException, get_new_wallet_name, InvalidPassword, - standardize_path) + standardize_path, UserFacingException) from electrum.wallet import Wallet, Abstract_Wallet from electrum.wallet_db import WalletRequiresSplit, WalletRequiresUpgrade, WalletUnfinished from electrum.gui import BaseElectrumGui @@ -411,12 +411,15 @@ def start_new_window( return except Exception as e: self.logger.exception('') - err_text = str(e) if isinstance(e, WalletFileException) else repr(e) - custom_message_box(icon=QMessageBox.Icon.Warning, - parent=None, - title=_('Error'), - text=_('Cannot load wallet') + '(2) :\n' + err_text) - if isinstance(e, WalletFileException) and e.should_report_crash: + if isinstance(e, UserFacingException) \ + or isinstance(e, WalletFileException) and not e.should_report_crash: + err_text = str(e) if isinstance(e, WalletFileException) else repr(e) + custom_message_box(icon=QMessageBox.Icon.Warning, + parent=None, + title=_('Error'), + text=_('Cannot load wallet') + '(2) :\n' + err_text) + elif isinstance(e, WalletFileException) and e.should_report_crash \ + or not isinstance(e, WalletFileException): send_exception_to_crash_reporter(e) if app_is_starting: # If we raise in this context, there are no more fallbacks, we will shut down. From f92e9e5d911788e10810dbe72073ee8f4a23b1d6 Mon Sep 17 00:00:00 2001 From: f321x Date: Tue, 12 Aug 2025 10:36:46 +0200 Subject: [PATCH 3/3] qml: wizard: differentiate between create_storage exc types Differentiate between the `UserFacingException` and other exceptions when creating the storage. Forward other exceptions to the reporter so they can get fixed. --- electrum/gui/qml/qewizard.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/electrum/gui/qml/qewizard.py b/electrum/gui/qml/qewizard.py index cd45e9e57d6f..ca1961f8cf9d 100644 --- a/electrum/gui/qml/qewizard.py +++ b/electrum/gui/qml/qewizard.py @@ -3,11 +3,12 @@ from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject +from electrum.base_crash_reporter import send_exception_to_crash_reporter from electrum.logging import get_logger from electrum import mnemonic from electrum.wizard import NewWalletWizard, ServerConnectWizard, TermsOfUseWizard from electrum.storage import WalletStorage, StorageReadWriteError -from electrum.util import WalletFileException +from electrum.util import WalletFileException, UserFacingException from electrum.gui import messages if TYPE_CHECKING: @@ -172,9 +173,12 @@ def createStorage(self, js_data, single_password_enabled, single_password): self.path = path self.createSuccess.emit() + except UserFacingException as e: + self._logger.debug(f"createStorage errored: {e!r}", exc_info=True) + self.createError.emit(str(e)) except Exception as e: self._logger.exception(f"createStorage errored: {e!r}") - self.createError.emit(str(e)) + send_exception_to_crash_reporter(e) class QEServerConnectWizard(ServerConnectWizard, QEAbstractWizard):