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.
This commit is contained in:
Neil Booth 2016-01-31 19:36:21 +09:00
parent abaf1bc6dc
commit e61fffab55
4 changed files with 50 additions and 31 deletions

View File

@ -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())

View File

@ -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.'''

View File

@ -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

View File

@ -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