diff --git a/electrum/base_wizard.py b/electrum/base_wizard.py index ac8d7874b..72bc4c95e 100644 --- a/electrum/base_wizard.py +++ b/electrum/base_wizard.py @@ -44,6 +44,7 @@ from .util import UserCancelled, InvalidPassword, WalletFileException from .simple_config import SimpleConfig from .plugin import Plugins, HardwarePluginLibraryUnavailable from .logging import Logger +from .plugins.hw_wallet.plugin import OutdatedHwFirmwareException, HW_PluginBase if TYPE_CHECKING: from .plugin import DeviceInfo @@ -323,7 +324,7 @@ class BaseWizard(Logger): run_next=lambda *args: self.on_device(*args, purpose=purpose, storage=storage)) def on_device(self, name, device_info, *, purpose, storage=None): - self.plugin = self.plugins.get_plugin(name) + self.plugin = self.plugins.get_plugin(name) # type: HW_PluginBase try: self.plugin.setup_device(device_info, self, purpose) except OSError as e: @@ -335,6 +336,14 @@ class BaseWizard(Logger): devmgr.unpair_id(device_info.device.id_) self.choose_hw_device(purpose, storage=storage) return + except OutdatedHwFirmwareException as e: + if self.question(e.text_ignore_old_fw_and_continue(), title=_("Outdated device firmware")): + self.plugin.set_ignore_outdated_fw() + # will need to re-pair + devmgr = self.plugins.device_manager + devmgr.unpair_id(device_info.device.id_) + self.choose_hw_device(purpose, storage=storage) + return except (UserCancelled, GoBack): self.choose_hw_device(purpose, storage=storage) return diff --git a/electrum/gui/qt/util.py b/electrum/gui/qt/util.py index 9c48f7ff4..9ac6649ca 100644 --- a/electrum/gui/qt/util.py +++ b/electrum/gui/qt/util.py @@ -206,11 +206,15 @@ class MessageBoxMixin(object): def top_level_window(self, test_func=None): return self.top_level_window_recurse(test_func) - def question(self, msg, parent=None, title=None, icon=None): + def question(self, msg, parent=None, title=None, icon=None, **kwargs) -> bool: Yes, No = QMessageBox.Yes, QMessageBox.No - return self.msg_box(icon or QMessageBox.Question, - parent, title or '', - msg, buttons=Yes|No, defaultButton=No) == Yes + return Yes == self.msg_box(icon=icon or QMessageBox.Question, + parent=parent, + title=title or '', + text=msg, + buttons=Yes|No, + defaultButton=No, + **kwargs) def show_warning(self, msg, parent=None, title=None, **kwargs): return self.msg_box(QMessageBox.Warning, parent, diff --git a/electrum/plugin.py b/electrum/plugin.py index cf72ef221..173ecdc57 100644 --- a/electrum/plugin.py +++ b/electrum/plugin.py @@ -403,7 +403,7 @@ class DeviceMgr(ThreadJob): def unpair_xpub(self, xpub): with self.lock: - if not xpub in self.xpub_ids: + if xpub not in self.xpub_ids: return _id = self.xpub_ids.pop(xpub) self._close_client(_id) diff --git a/electrum/plugins/hw_wallet/plugin.py b/electrum/plugins/hw_wallet/plugin.py index 9b2270284..fd3ed6979 100644 --- a/electrum/plugins/hw_wallet/plugin.py +++ b/electrum/plugins/hw_wallet/plugin.py @@ -44,6 +44,7 @@ class HW_PluginBase(BasePlugin): BasePlugin.__init__(self, parent, config, name) self.device = self.keystore_class.device self.keystore_class.plugin = self + self._ignore_outdated_fw = False def is_enabled(self): return True @@ -124,6 +125,12 @@ class HW_PluginBase(BasePlugin): message += '\n' + _("Make sure you install it with python3") return message + def set_ignore_outdated_fw(self): + self._ignore_outdated_fw = True + + def is_outdated_fw_ignored(self) -> bool: + return self._ignore_outdated_fw + def is_any_tx_output_on_change_branch(tx: Transaction): if not tx.output_info: @@ -160,3 +167,16 @@ def only_hook_if_libraries_available(func): class LibraryFoundButUnusable(Exception): def __init__(self, library_version='unknown'): self.library_version = library_version + + +class OutdatedHwFirmwareException(UserFacingException): + + def text_ignore_old_fw_and_continue(self) -> str: + suffix = (_("The firmware of your hardware device is too old. " + "If possible, you should upgrade it. " + "You can ignore this error and try to continue, however things are likely to break.") + "\n\n" + + _("Ignore and continue?")) + if str(self): + return str(self) + "\n\n" + suffix + else: + return suffix diff --git a/electrum/plugins/hw_wallet/qt.py b/electrum/plugins/hw_wallet/qt.py index 86f9a4099..a93a9d30c 100644 --- a/electrum/plugins/hw_wallet/qt.py +++ b/electrum/plugins/hw_wallet/qt.py @@ -37,6 +37,8 @@ from electrum.gui.qt.util import (read_QIcon, WWLabel, OkButton, WindowModalDial from electrum.i18n import _ from electrum.logging import Logger +from .plugin import OutdatedHwFirmwareException + # The trickiest thing about this handler was getting windows properly # parented on macOS. @@ -212,11 +214,27 @@ class QtPluginBase(object): handler = self.create_handler(window) handler.button = button keystore.handler = handler - keystore.thread = TaskThread(window, window.on_error) + keystore.thread = TaskThread(window, on_error=partial(self.on_task_thread_error, window, keystore)) self.add_show_address_on_hw_device_button_for_receive_addr(wallet, keystore, window) # Trigger a pairing keystore.thread.add(partial(self.get_client, keystore)) + def on_task_thread_error(self, window, keystore, exc_info): + e = exc_info[1] + if isinstance(e, OutdatedHwFirmwareException): + if window.question(e.text_ignore_old_fw_and_continue(), title=_("Outdated device firmware")): + self.set_ignore_outdated_fw() + # will need to re-pair + devmgr = self.device_manager() + def re_pair_device(): + device_id = self.choose_device(window, keystore) + devmgr.unpair_id(device_id) + self.get_client(keystore) + keystore.thread.add(re_pair_device) + return + else: + window.on_error(exc_info) + def choose_device(self, window, keystore): '''This dialog box should be usable even if the user has forgotten their PIN or it is in bootloader mode.''' diff --git a/electrum/plugins/trezor/clientbase.py b/electrum/plugins/trezor/clientbase.py index 9ce2b3699..7188c3798 100644 --- a/electrum/plugins/trezor/clientbase.py +++ b/electrum/plugins/trezor/clientbase.py @@ -7,6 +7,7 @@ from electrum.util import UserCancelled, UserFacingException from electrum.keystore import bip39_normalize_passphrase from electrum.bip32 import BIP32Node, convert_bip32_path_to_list_of_uint32 as parse_path from electrum.logging import Logger +from electrum.plugins.hw_wallet.plugin import OutdatedHwFirmwareException from trezorlib.client import TrezorClient from trezorlib.exceptions import TrezorFailure, Cancelled, OutdatedFirmwareError @@ -29,6 +30,8 @@ MESSAGES = { class TrezorClientBase(Logger): def __init__(self, transport, handler, plugin): + if plugin.is_outdated_fw_ignored(): + TrezorClient.is_outdated = lambda *args, **kwargs: False self.client = TrezorClient(transport, ui=self) self.plugin = plugin self.device = plugin.device @@ -62,15 +65,15 @@ class TrezorClientBase(Logger): def __enter__(self): return self - def __exit__(self, exc_type, exc_value, traceback): + def __exit__(self, exc_type, e, traceback): self.end_flow() - if exc_value is not None: - if issubclass(exc_type, Cancelled): - raise UserCancelled from exc_value - elif issubclass(exc_type, TrezorFailure): - raise RuntimeError(str(exc_value)) from exc_value - elif issubclass(exc_type, OutdatedFirmwareError): - raise UserFacingException(exc_value) from exc_value + if e is not None: + if isinstance(e, Cancelled): + raise UserCancelled from e + elif isinstance(e, TrezorFailure): + raise RuntimeError(str(e)) from e + elif isinstance(e, OutdatedFirmwareError): + raise OutdatedHwFirmwareException(e) from e else: return False return True diff --git a/electrum/plugins/trezor/trezor.py b/electrum/plugins/trezor/trezor.py index 83a05668e..26c240f9c 100644 --- a/electrum/plugins/trezor/trezor.py +++ b/electrum/plugins/trezor/trezor.py @@ -15,7 +15,7 @@ from electrum.logging import get_logger from ..hw_wallet import HW_PluginBase from ..hw_wallet.plugin import (is_any_tx_output_on_change_branch, trezor_validate_op_return_output_and_get_data, - LibraryFoundButUnusable) + LibraryFoundButUnusable, OutdatedHwFirmwareException) _logger = get_logger(__name__) @@ -275,7 +275,7 @@ class TrezorPlugin(HW_PluginBase): msg = (_('Outdated {} firmware for device labelled {}. Please ' 'download the updated firmware from {}') .format(self.device, client.label(), self.firmware_URL)) - raise UserFacingException(msg) + raise OutdatedHwFirmwareException(msg) # fixme: we should use: client.handler = wizard client.handler = self.create_handler(wizard)