From e61fffab55f8cb59638de7d77ab1791611bd466e Mon Sep 17 00:00:00 2001 From: Neil Booth Date: Sun, 31 Jan 2016 19:36:21 +0900 Subject: [PATCH] Trezor/KeepKey: force watching only improvements Only warn about watching only once given a chance to pair. Failure to pair makes watching-only and warns. In error message to user, distinguish between failure to connect and failure to pair. --- gui/qt/main_window.py | 1 + lib/plugins.py | 52 +++++++++++++++++++++++----------- plugins/hw_wallet/hw_wallet.py | 13 +++++---- plugins/trezor/plugin.py | 15 ++++------ 4 files changed, 50 insertions(+), 31 deletions(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index c00c46027..d218ff1ba 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -311,6 +311,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): title = 'Electrum %s - %s' % (self.wallet.electrum_version, self.wallet.basename()) if self.wallet.is_watching_only(): + self.warn_if_watching_only() title += ' [%s]' % (_('watching only')) self.setWindowTitle(title) self.password_menu.setEnabled(self.wallet.can_change_password()) diff --git a/lib/plugins.py b/lib/plugins.py index a362c8f35..7cdb4bc45 100644 --- a/lib/plugins.py +++ b/lib/plugins.py @@ -234,6 +234,13 @@ class BasePlugin(PrintError): def settings_dialog(self): pass + +class DeviceNotFoundError(Exception): + pass + +class DeviceUnpairableError(Exception): + pass + Device = namedtuple("Device", "path interface_number id_ product_key") DeviceInfo = namedtuple("DeviceInfo", "device description initialized") @@ -368,25 +375,38 @@ class DeviceMgr(PrintError): return self.create_client(device, wallet.handler, plugin) if force_pair: - first_address, derivation = wallet.first_address() - assert first_address - - # The wallet has not been previously paired, so let the user - # choose an unpaired device and compare its first address. - info = self.select_device(wallet, plugin, devices) - if info: - client = self.client_lookup(info.device.id_) - if client and client.is_pairable(): - # See comment above for same code - client.handler = wallet.handler - # This will trigger a PIN/passphrase entry request - client_first_address = client.first_address(derivation) - if client_first_address == first_address: - self.pair_wallet(wallet, info.device.id_) - return client + return self.force_pair_wallet(plugin, wallet, devices) return None + def force_pair_wallet(self, plugin, wallet, devices): + first_address, derivation = wallet.first_address() + assert first_address + + # The wallet has not been previously paired, so let the user + # choose an unpaired device and compare its first address. + info = self.select_device(wallet, plugin, devices) + if info: + client = self.client_lookup(info.device.id_) + if client and client.is_pairable(): + # See comment above for same code + client.handler = wallet.handler + # This will trigger a PIN/passphrase entry request + client_first_address = client.first_address(derivation) + if client_first_address == first_address: + self.pair_wallet(wallet, info.device.id_) + return client + + if info and client: + # The user input has wrong PIN or passphrase + raise DeviceUnpairableError( + _('Unable to pair with your %s.') % plugin.device) + + raise DeviceNotFoundError( + _('Could not connect to your %s. Verify the cable is ' + 'connected and that no other application is using it.') + % plugin.device) + def unpaired_device_infos(self, handler, plugin, devices=None): '''Returns a list of DeviceInfo objects: one for each connected, unpaired device accepted by the plugin.''' diff --git a/plugins/hw_wallet/hw_wallet.py b/plugins/hw_wallet/hw_wallet.py index 22dfa2af0..0faf83140 100644 --- a/plugins/hw_wallet/hw_wallet.py +++ b/plugins/hw_wallet/hw_wallet.py @@ -39,26 +39,29 @@ class BIP44_HW_Wallet(BIP44_Wallet): # handler. The handler is per-window and preserved across # device reconnects self.handler = None - self.force_watching_only = True + self.force_watching_only = False def set_session_timeout(self, seconds): self.print_error("setting session timeout to %d seconds" % seconds) self.session_timeout = seconds self.storage.put('session_timeout', seconds) + def set_force_watching_only(self, value): + if value != self.force_watching_only: + self.force_watching_only = value + self.handler.watching_only_changed() + def unpaired(self): '''A device paired with the wallet was diconnected. This can be called in any thread context.''' self.print_error("unpaired") - self.force_watching_only = True - self.handler.watching_only_changed() + self.set_force_watching_only(True) def paired(self): '''A device paired with the wallet was (re-)connected. This can be called in any thread context.''' self.print_error("paired") - self.force_watching_only = False - self.handler.watching_only_changed() + self.set_force_watching_only(False) def timeout(self): '''Called when the wallet session times out. Note this is called from diff --git a/plugins/trezor/plugin.py b/plugins/trezor/plugin.py index c18f52837..af046fb03 100644 --- a/plugins/trezor/plugin.py +++ b/plugins/trezor/plugin.py @@ -20,9 +20,6 @@ from ..hw_wallet import BIP44_HW_Wallet, HW_PluginBase # TREZOR initialization methods TIM_NEW, TIM_RECOVER, TIM_MNEMONIC, TIM_PRIVKEY = range(0, 4) -class DeviceDisconnectedError(Exception): - pass - class TrezorCompatibleWallet(BIP44_HW_Wallet): def get_public_key(self, bip32_path): @@ -137,17 +134,15 @@ class TrezorCompatiblePlugin(HW_PluginBase): assert self.main_thread != threading.current_thread() devmgr = self.device_manager() - client = devmgr.client_for_wallet(self, wallet, force_pair) + try: + client = devmgr.client_for_wallet(self, wallet, force_pair) + except: + wallet.set_force_watching_only(True) + raise if client: self.print_error("set last_operation") wallet.last_operation = time.time() - elif force_pair: - msg = (_('Could not connect to your %s. Verify the ' - 'cable is connected and that no other app is ' - 'using it.\nContinuing in watching-only mode ' - 'until the device is re-connected.') % self.device) - raise DeviceDisconnectedError(msg) return client