URI: 
       thw: allow bypassing "too old firmware" error when using hw wallets - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 371e1a6ebff4cf7660edf7d586a47dfc940e4263
   DIR parent 7cba46c317caa95b77337592ffa10e3de0fd2107
  HTML Author: SomberNight <somber.night@protonmail.com>
       Date:   Fri, 31 May 2019 04:09:03 +0200
       
       hw: allow bypassing "too old firmware" error when using hw wallets
       
       The framework here is generic enough that it can be used for any hw plugin,
       however atm only Trezor is implemented.
       
       closes #5391
       
       Diffstat:
         M electrum/base_wizard.py             |      11 ++++++++++-
         M electrum/gui/qt/util.py             |      12 ++++++++----
         M electrum/plugin.py                  |       2 +-
         M electrum/plugins/hw_wallet/plugin.… |      20 ++++++++++++++++++++
         M electrum/plugins/hw_wallet/qt.py    |      20 +++++++++++++++++++-
         M electrum/plugins/trezor/clientbase… |      19 +++++++++++--------
         M electrum/plugins/trezor/trezor.py   |       4 ++--
       
       7 files changed, 71 insertions(+), 17 deletions(-)
       ---
   DIR diff --git a/electrum/base_wizard.py b/electrum/base_wizard.py
       t@@ -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
       t@@ -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:
       t@@ -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
   DIR diff --git a/electrum/gui/qt/util.py b/electrum/gui/qt/util.py
       t@@ -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,
   DIR diff --git a/electrum/plugin.py b/electrum/plugin.py
       t@@ -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)
   DIR diff --git a/electrum/plugins/hw_wallet/plugin.py b/electrum/plugins/hw_wallet/plugin.py
       t@@ -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
       t@@ -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:
       t@@ -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
   DIR diff --git a/electrum/plugins/hw_wallet/qt.py b/electrum/plugins/hw_wallet/qt.py
       t@@ -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.
       t@@ -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.'''
   DIR diff --git a/electrum/plugins/trezor/clientbase.py b/electrum/plugins/trezor/clientbase.py
       t@@ -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
       t@@ -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
       t@@ -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
   DIR diff --git a/electrum/plugins/trezor/trezor.py b/electrum/plugins/trezor/trezor.py
       t@@ -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__)
        
       t@@ -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)