commit 3d9f321cae28f5806e5d75d9032d3c89a7459dd2
parent 5b8e096d5709e051024c91a1b637ff2bc9b2cb74
Author: Neil Booth <kyuupichan@gmail.com>
Date: Tue, 5 Jan 2016 06:47:14 +0900
Use a shared device manager
Use a shared device manager across USB devices (not yet taken
advantage of by ledger). This reduces USB scans and abstracts
device management cleanly.
We no longer scan at regular intervals in a background thread.
Diffstat:
6 files changed, 334 insertions(+), 189 deletions(-)
diff --git a/lib/plugins.py b/lib/plugins.py
@@ -44,6 +44,8 @@ class Plugins(DaemonThread):
self.plugins = {}
self.gui_name = gui_name
self.descriptions = []
+ self.device_manager = DeviceMgr()
+
for loader, name, ispkg in pkgutil.iter_modules([self.pkgpath]):
m = loader.find_module(name).load_module(name)
d = m.__dict__
@@ -212,3 +214,199 @@ class BasePlugin(PrintError):
def settings_dialog(self):
pass
+
+
+class DeviceMgr(PrintError):
+ '''Manages hardware clients. A client communicates over a hardware
+ channel with the device. A client is a pair: a device ID (serial
+ number) and hardware port. If either change then a different
+ client is instantiated.
+
+ In addition to tracking device IDs, the device manager tracks
+ hardware wallets and manages wallet pairing. A device ID may be
+ paired with a wallet when it is confirmed that the hardware device
+ matches the wallet, i.e. they have the same master public key. A
+ device ID can be unpaired if e.g. it is wiped.
+
+ Because of hotplugging, a wallet must request its client
+ dynamically each time it is required, rather than caching it
+ itself.
+
+ The device manager is shared across plugins, so just one place
+ does hardware scans when needed. By tracking device serial
+ numbers the number of necessary hardware scans is reduced, e.g. if
+ a device is plugged into a different port the wallet is
+ automatically re-paired.
+
+ Wallets are informed on connect / disconnect / unpairing events.
+ It must implement connected(), disconnected() and unpaired()
+ callbacks. Being connected implies a pairing. Being disconnected
+ doesn't. Callbacks can happen in any thread context, and we do
+ them without holding the lock.
+
+ This plugin is thread-safe. Currently only USB is implemented.
+ '''
+
+ # Client lookup types. CACHED will look up in our client cache
+ # only. PRESENT will do a scan if there is no client in the cache.
+ # PAIRED will try and pair the wallet, which will involve requesting
+ # a PIN and passphrase if they are enabled
+ (CACHED, PRESENT, PAIRED) = range(3)
+
+ def __init__(self):
+ super(DeviceMgr, self).__init__()
+ # Keyed by wallet. The value is the device_id if the wallet
+ # has been paired, and None otherwise.
+ self.wallets = {}
+ # A list of clients. We create a client for every device present
+ # that is of a registered hardware type
+ self.clients = []
+ # What we recognise. Keyed by (vendor_id, product_id) pairs,
+ # the value is a handler for those devices. The handler must
+ # implement
+ self.recognised_hardware = {}
+ # For synchronization
+ self.lock = threading.RLock()
+
+ def register_devices(self, handler, device_pairs):
+ for pair in device_pairs:
+ self.recognised_hardware[pair] = handler
+
+ def close_client(self, client):
+ with self.lock:
+ if client in self.clients:
+ self.clients.remove(client)
+ client.close()
+
+ def close_wallet(self, wallet):
+ # Remove the wallet from our list; close any client
+ with self.lock:
+ device_id = self.wallets.pop(wallet, None)
+ self.close_client(self.client_by_device_id(device_id))
+
+ def clients_of_type(self, classinfo):
+ with self.lock:
+ return [client for client in self.clients
+ if isinstance(client, classinfo)]
+
+ def client_by_device_id(self, device_id):
+ with self.lock:
+ for client in self.clients:
+ if client.device_id() == device_id:
+ return client
+ return None
+
+ def wallet_by_device_id(self, device_id):
+ with self.lock:
+ for wallet, wallet_device_id in self.wallets.items():
+ if wallet_device_id == device_id:
+ return wallet
+ return None
+
+ def paired_wallets(self):
+ with self.lock:
+ return [wallet for (wallet, device_id) in self.wallets.items()
+ if device_id is not None]
+
+ def pair_wallet(self, wallet, client):
+ assert client in self.clients
+ self.print_error("paired:", wallet, client)
+ self.wallets[wallet] = client.device_id()
+ client.pair_wallet(wallet)
+ wallet.connected()
+
+ def scan_devices(self):
+ # All currently supported hardware libraries use hid, so we
+ # assume it here. This can be easily abstracted if necessary.
+ # Note this import must be local so those without hardware
+ # wallet libraries are not affected.
+ import hid
+
+ self.print_error("scanning devices...")
+
+ # First see what's connected that we know about
+ devices = {}
+ for d in hid.enumerate(0, 0):
+ product_key = (d['vendor_id'], d['product_id'])
+ device_id = d['serial_number']
+ path = d['path']
+
+ handler = self.recognised_hardware.get(product_key)
+ if handler:
+ devices[device_id] = (handler, path, product_key)
+
+ # Now find out what was disconnected
+ with self.lock:
+ disconnected = [client for client in self.clients
+ if not client.device_id() in devices]
+
+ # Close disconnected clients after informing their wallets
+ for client in disconnected:
+ wallet = self.wallet_by_device_id(client.device_id())
+ if wallet:
+ wallet.disconnected()
+ self.close_client(client)
+
+ # Now see if any new devices are present.
+ for device_id, (handler, path, product_key) in devices.items():
+ try:
+ client = handler.create_client(path, product_key)
+ except BaseException as e:
+ self.print_error("could not create client", str(e))
+ client = None
+ if client:
+ self.print_error("client created for", path)
+ with self.lock:
+ self.clients.append(client)
+ # Inform re-paired wallet
+ wallet = self.wallet_by_device_id(device_id)
+ if wallet:
+ self.pair_wallet(wallet, client)
+
+ def get_client(self, wallet, lookup=PAIRED):
+ '''Returns a client for the wallet, or None if one could not be
+ found.'''
+ with self.lock:
+ device_id = self.wallets.get(wallet)
+ client = self.client_by_device_id(device_id)
+ if client:
+ return client
+
+ if lookup == DeviceMgr.CACHED:
+ return None
+
+ first_address, derivation = wallet.first_address()
+ # Wallets don't have a first address in the install wizard
+ # until account creation
+ if not first_address:
+ self.print_error("no first address for ", wallet)
+ return None
+
+ # We didn't find it, so scan for new devices. We scan as
+ # little as possible: some people report a USB scan is slow on
+ # Linux when a Trezor is plugged in
+ self.scan_devices()
+
+ with self.lock:
+ # Maybe the scan found it? If the wallet has a device_id
+ # from a prior pairing, we can determine success now.
+ if device_id:
+ return self.client_by_device_id(device_id)
+
+ # Stop here if no wake and we couldn't find it.
+ if lookup == DeviceMgr.PRESENT:
+ return None
+
+ # The wallet has not been previously paired, so get the
+ # first address of all unpaired clients and compare.
+ for client in self.clients:
+ # If already paired skip it
+ if self.wallet_by_device_id(client.device_id()):
+ continue
+ # This will trigger a PIN/passphrase entry request
+ if client.first_address(wallet, derivation) == first_address:
+ self.pair_wallet(wallet, client)
+ return client
+
+ # Not found
+ return None
diff --git a/plugins/keepkey/keepkey.py b/plugins/keepkey/keepkey.py
@@ -17,7 +17,7 @@ class KeepKeyPlugin(TrezorCompatiblePlugin):
client_class = trezor_client_class(ProtocolMixin, BaseClient, proto)
import keepkeylib.ckd_public as ckd_public
from keepkeylib.client import types
- from keepkeylib.transport_hid import HidTransport
+ from keepkeylib.transport_hid import HidTransport, DEVICE_IDS
libraries_available = True
- except:
+ except ImportError:
libraries_available = False
diff --git a/plugins/trezor/client.py b/plugins/trezor/client.py
@@ -77,7 +77,7 @@ def trezor_client_class(protocol_mixin, base_client, proto):
self.msg_code_override = None
def __str__(self):
- return "%s/%s/%s" % (self.label(), self.device_id(), self.path[0])
+ return "%s/%s/%s" % (self.label(), self.device_id(), self.path)
def label(self):
'''The name given by the user to the device.'''
@@ -91,6 +91,9 @@ def trezor_client_class(protocol_mixin, base_client, proto):
'''True if initialized, False if wiped.'''
return self.features.initialized
+ def pair_wallet(self, wallet):
+ self.wallet = wallet
+
def handler(self):
assert self.wallet and self.wallet.handler
return self.wallet.handler
@@ -111,6 +114,15 @@ def trezor_client_class(protocol_mixin, base_client, proto):
path.append(abs(int(x)) | prime)
return path
+ def first_address(self, wallet, derivation):
+ assert not self.wallet
+ # Assign the wallet so we have a handler
+ self.wallet = wallet
+ try:
+ return self.address_from_derivation(derivation)
+ finally:
+ self.wallet = None
+
def address_from_derivation(self, derivation):
return self.get_address('Bitcoin', self.expand_path(derivation))
@@ -128,6 +140,24 @@ def trezor_client_class(protocol_mixin, base_client, proto):
finally:
self.msg_code_override = None
+ def clear_session(self):
+ '''Clear the session to force pin (and passphrase if enabled)
+ re-entry. Does not leak exceptions.'''
+ self.print_error("clear session:", self)
+ try:
+ super(TrezorClient, self).clear_session()
+ except BaseException as e:
+ # If the device was removed it has the same effect...
+ self.print_error("clear_session: ignoring error", str(e))
+ pass
+
+ def close(self):
+ '''Called when Our wallet was closed or the device removed.'''
+ self.print_error("disconnected")
+ self.clear_session()
+ # Release the device
+ self.transport.close()
+
def firmware_version(self):
f = self.features
return (f.major_version, f.minor_version, f.patch_version)
diff --git a/plugins/trezor/plugin.py b/plugins/trezor/plugin.py
@@ -13,14 +13,19 @@ from electrum.transaction import (deserialize, is_extended_pubkey,
Transaction, x_to_xpub)
from electrum.wallet import BIP32_HD_Wallet, BIP44_Wallet
from electrum.util import ThreadJob
+from electrum.plugins import DeviceMgr
class DeviceDisconnectedError(Exception):
pass
+class OutdatedFirmwareError(Exception):
+ pass
+
class TrezorCompatibleWallet(BIP44_Wallet):
# Extend BIP44 Wallet as required by hardware implementation.
# Derived classes must set:
# - device
+ # - DEVICE_IDS
# - wallet_type
restore_wallet_class = BIP44_Wallet
@@ -76,14 +81,20 @@ class TrezorCompatibleWallet(BIP44_Wallet):
'''The wallet is watching-only if its trezor device is not connected,
or if it is connected but uninitialized.'''
assert not self.has_seed()
- client = self.plugin.lookup_client(self)
+ client = self.get_client(DeviceMgr.CACHED)
return not (client and client.is_initialized())
def can_change_password(self):
return False
- def client(self):
- return self.plugin.client(self)
+ def get_client(self, lookup=DeviceMgr.PAIRED):
+ return self.plugin.get_client(self, lookup)
+
+ def first_address(self):
+ '''Used to check a hardware wallet matches a software wallet'''
+ account = self.accounts.get('0')
+ derivation = self.address_derivation('0', 0, 0)
+ return (account.first_address()[0] if account else None, derivation)
def derive_xkeys(self, root, derivation, password):
if self.master_public_keys.get(root):
@@ -96,7 +107,7 @@ class TrezorCompatibleWallet(BIP44_Wallet):
return xpub, None
def get_public_key(self, bip32_path):
- client = self.client()
+ client = self.get_client()
address_n = client.expand_path(bip32_path)
node = client.get_public_node(address_n).node
xpub = ("0488B21E".decode('hex') + chr(node.depth)
@@ -111,7 +122,7 @@ class TrezorCompatibleWallet(BIP44_Wallet):
raise RuntimeError(_('Decrypt method is not implemented'))
def sign_message(self, address, message, password):
- client = self.client()
+ client = self.get_client()
address_path = self.address_id(address)
address_n = client.expand_path(address_path)
msg_sig = client.sign_message('Bitcoin', address_n, message)
@@ -152,96 +163,89 @@ class TrezorCompatiblePlugin(BasePlugin, ThreadJob):
# libraries_available, libraries_URL, minimum_firmware,
# wallet_class, ckd_public, types, HidTransport
- # This plugin automatically keeps track of attached devices, and
- # connects to anything attached creating a new Client instance.
- # When disconnected, the client is informed via a callback.
- # As a device can be disconnected and/or reconnected in a different
- # USB port (giving it a new path), the wallet must be dynamic in
- # asking for its client.
- # If a wallet is successfully paired with a given device, the plugin
- # stores its serial number in the wallet so it can be automatically
- # re-paired if the same device is connected elsewhere.
- # Approaching things this way permits several devices to be connected
- # simultaneously and handled smoothly.
-
def __init__(self, parent, config, name):
BasePlugin.__init__(self, parent, config, name)
self.device = self.wallet_class.device
self.wallet_class.plugin = self
self.prevent_timeout = time.time() + 3600 * 24 * 365
- # A set of client instances to USB paths
- self.clients = set()
- # The device wallets we have seen to inform on reconnection
- self.paired_wallets = set()
- self.last_scan = 0
+ self.device_manager().register_devices(self, self.DEVICE_IDS)
+
+ def is_enabled(self):
+ return self.libraries_available
+
+ def device_manager(self):
+ return self.parent.device_manager
def thread_jobs(self):
- # Scan connected devices every second. The test for libraries
- # available is necessary to recover wallets on machines without
- # libraries
+ # Thread job to handle device timeouts
return [self] if self.libraries_available else []
def run(self):
- '''Runs in the context of the Plugins thread.'''
+ '''Handle device timeouts. Runs in the context of the Plugins
+ thread.'''
now = time.time()
- if now > self.last_scan + 1:
- self.last_scan = now
- self.scan_devices()
-
- for wallet in self.paired_wallets:
- if now > wallet.last_operation + wallet.session_timeout:
- client = self.lookup_client(wallet)
- if client:
- wallet.last_operation = self.prevent_timeout
- self.clear_session(client)
- wallet.timeout()
-
- def scan_devices(self):
- '''Scan devices. Runs in the context of the Plugins thread.'''
- paths = self.HidTransport.enumerate()
- connected = set([c for c in self.clients if c.path in paths])
- disconnected = self.clients - connected
-
- self.clients = connected
-
- # Inform clients and wallets they were disconnected
- for client in disconnected:
- self.print_error("device disconnected:", client)
- if client.wallet:
- client.wallet.disconnected()
-
- for path in paths:
- # Look for new paths
- if any(c.path == path for c in connected):
- continue
-
- try:
- transport = self.HidTransport(path)
- except BaseException as e:
- # We were probably just disconnected; never mind
- self.print_error("cannot connect at", path, str(e))
- continue
+ for wallet in self.device_manager().paired_wallets():
+ if (isinstance(wallet, self.wallet_class)
+ and hasattr(wallet, 'last_operation')
+ and now > wallet.last_operation + wallet.session_timeout):
+ client = self.get_client(wallet, DeviceMgr.CACHED)
+ if client:
+ wallet.last_operation = self.prevent_timeout
+ client.clear_session()
+ wallet.timeout()
+
+ def create_client(self, path, product_key):
+ pair = ((None, path) if self.HidTransport._detect_debuglink(path)
+ else (path, None))
+ try:
+ transport = self.HidTransport(pair)
+ except BaseException as e:
+ # We were probably just disconnected; never mind
+ self.print_error("cannot connect at", path, str(e))
+ return None
+ self.print_error("connected to device at", path)
+ return self.client_class(transport, path, self)
- self.print_error("connected to device at", path[0])
+ def get_client(self, wallet, lookup=DeviceMgr.PAIRED, check_firmware=True):
+ '''check_firmware is ignored unless doing a PAIRED lookup.'''
+ client = self.device_manager().get_client(wallet, lookup)
+ # Try a ping if doing at least a PRESENT lookup
+ if client and lookup != DeviceMgr.CACHED:
+ self.print_error("set last_operation")
+ wallet.last_operation = time.time()
try:
- client = self.client_class(transport, path, self)
+ client.ping('t')
except BaseException as e:
- self.print_error("cannot create client for", path, str(e))
- else:
- self.clients.add(client)
- self.print_error("new device:", client)
+ self.print_error("ping failed", str(e))
+ # Remove it from the manager's cache
+ self.device_manager().close_client(client)
+ client = None
+
+ if lookup == DeviceMgr.PAIRED:
+ assert wallet.handler
+ if not client:
+ 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)
+ wallet.handler.show_error(msg)
+ raise DeviceDisconnectedError(msg)
+
+ if (check_firmware and not
+ client.atleast_version(*self.minimum_firmware)):
+ msg = (_('Outdated %s firmware for device labelled %s. Please '
+ 'download the updated firmware from %s') %
+ (self.device, client.label(), self.firmware_URL))
+ wallet.handler.show_error(msg)
+ raise OutdatedFirmwareError(msg)
- # Inform reconnected wallets
- for wallet in self.paired_wallets:
- if wallet.device_id == client.features.device_id:
- client.wallet = wallet
- wallet.connected()
+ return client
- def clear_session(self, client):
- # Clearing the session forces pin re-entry
- self.print_error("clear session:", client)
- client.clear_session()
+ @hook
+ def close_wallet(self, wallet):
+ if isinstance(wallet, self.wallet_class):
+ self.device_manager().close_wallet(wallet)
def initialize_device(self, wallet, wizard):
# Prevent timeouts during initialization
@@ -254,105 +258,25 @@ class TrezorCompatiblePlugin(BasePlugin, ThreadJob):
strength = 64 * (strength + 2) # 128, 192 or 256
language = ''
- client = self.client(wallet)
+ client = self.get_client(wallet)
client.reset_device(True, strength, passphrase_protection,
pin_protection, label, language)
-
def select_device(self, wallet, wizard):
'''Called when creating a new wallet. Select the device to use. If
the device is uninitialized, go through the intialization
process.'''
- clients = list(self.clients)
+ self.device_manager().scan_devices()
+ clients = self.device_manager().clients_of_type(self.client_class)
suffixes = [_("An unnamed device (wiped)"), _(" (initialized)")]
labels = [client.label() + suffixes[client.is_initialized()]
for client in clients]
msg = _("Please select which %s device to use:") % self.device
client = clients[wizard.query_choice(msg, labels)]
- self.pair_wallet(wallet, client)
+ self.device_manager().pair_wallet(wallet, client)
if not client.is_initialized():
self.initialize_device(wallet, wizard)
- def operated_on(self, wallet):
- self.print_error("set last_operation")
- wallet.last_operation = time.time()
-
- def pair_wallet(self, wallet, client):
- self.print_error("pairing wallet %s to device %s" % (wallet, client))
- self.operated_on(wallet)
- self.paired_wallets.add(wallet)
- wallet.device_id = client.features.device_id
- wallet.last_operation = time.time()
- client.wallet = wallet
- wallet.connected()
-
- def try_to_pair_wallet(self, wallet):
- '''Call this when loading an existing wallet to find if the
- associated device is connected.'''
- account = '0'
- if not account in wallet.accounts:
- self.print_error("try pair_wallet: wallet has no accounts")
- return None
-
- first_address = wallet.accounts[account].first_address()[0]
- derivation = wallet.address_derivation(account, 0, 0)
- for client in self.clients:
- if client.wallet:
- continue
-
- if not client.atleast_version(*self.minimum_firmware):
- wallet.handler.show_error(
- _('Outdated %s firmware for device labelled %s. Please '
- 'download the updated firmware from %s') %
- (self.device, client.label(), self.firmware_URL))
- continue
-
- # This gives us a handler
- client.wallet = wallet
- device_address = None
- try:
- device_address = client.address_from_derivation(derivation)
- finally:
- client.wallet = None
-
- if first_address == device_address:
- self.pair_wallet(wallet, client)
- return client
-
- return None
-
- def lookup_client(self, wallet):
- for client in self.clients:
- if client.features.device_id == wallet.device_id:
- return client
- return None
-
- def client(self, wallet):
- '''Returns a wrapped client which handles cleanup in case of
- thrown exceptions, etc.'''
- assert isinstance(wallet, self.wallet_class)
- assert wallet.handler != None
-
- self.operated_on(wallet)
- if wallet.device_id is None:
- client = self.try_to_pair_wallet(wallet)
- else:
- client = self.lookup_client(wallet)
-
- if not client:
- 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)
- if not self.clients:
- wallet.handler.show_error(msg)
- raise DeviceDisconnectedError(msg)
-
- return client
-
- def is_enabled(self):
- return self.libraries_available
-
def on_restore_wallet(self, wallet, wizard):
assert isinstance(wallet, self.wallet_class)
@@ -371,22 +295,10 @@ class TrezorCompatiblePlugin(BasePlugin, ThreadJob):
wallet.create_main_account(password)
return wallet
- @hook
- def close_wallet(self, wallet):
- if isinstance(wallet, self.wallet_class):
- # Don't retain references to a closed wallet
- self.paired_wallets.discard(wallet)
- client = self.lookup_client(wallet)
- if client:
- self.clear_session(client)
- # Release the device
- self.clients.discard(client)
- client.transport.close()
-
def sign_transaction(self, wallet, tx, prev_tx, xpub_path):
self.prev_tx = prev_tx
self.xpub_path = xpub_path
- client = self.client(wallet)
+ client = self.get_client(wallet)
inputs = self.tx_inputs(tx, True)
outputs = self.tx_outputs(wallet, tx)
signed_tx = client.sign_tx('Bitcoin', inputs, outputs)[1]
@@ -394,7 +306,7 @@ class TrezorCompatiblePlugin(BasePlugin, ThreadJob):
tx.update_signatures(raw)
def show_address(self, wallet, address):
- client = self.client(wallet)
+ client = self.get_client(wallet)
if not client.atleast_version(1, 3):
wallet.handler.show_error(_("Your device firmware is too old"))
return
diff --git a/plugins/trezor/qt_generic.py b/plugins/trezor/qt_generic.py
@@ -10,7 +10,7 @@ from electrum_gui.qt.util import *
from plugin import TrezorCompatiblePlugin
from electrum.i18n import _
-from electrum.plugins import hook
+from electrum.plugins import hook, DeviceMgr
from electrum.util import PrintError
from electrum.wallet import BIP44_Wallet
@@ -132,7 +132,7 @@ def qt_plugin_class(base_plugin_class):
window.statusBar().addPermanentWidget(window.tzb)
wallet.handler = self.create_handler(window)
# Trigger a pairing
- self.client(wallet)
+ self.get_client(wallet)
def on_create_wallet(self, wallet, wizard):
assert type(wallet) == self.wallet_class
@@ -148,8 +148,8 @@ def qt_plugin_class(base_plugin_class):
def settings_dialog(self, window):
- def client():
- return self.client(wallet)
+ def get_client(lookup=DeviceMgr.PAIRED):
+ return self.get_client(wallet, lookup)
def add_rows_to_layout(layout, rows):
for row_num, items in enumerate(rows):
@@ -158,7 +158,7 @@ def qt_plugin_class(base_plugin_class):
layout.addWidget(widget, row_num, col_num)
def refresh():
- features = client().features
+ features = get_client(DeviceMgr.PAIRED).features
bl_hash = features.bootloader_hash.encode('hex').upper()
bl_hash = "%s...%s" % (bl_hash[:10], bl_hash[-10:])
version = "%d.%d.%d" % (features.major_version,
@@ -184,11 +184,11 @@ def qt_plugin_class(base_plugin_class):
response = QInputDialog().getText(dialog, title, msg)
if not response[1]:
return
- client().change_label(str(response[0]))
+ get_client().change_label(str(response[0]))
refresh()
def set_pin():
- client().set_pin(remove=False)
+ get_client().set_pin(remove=False)
refresh()
def clear_pin():
@@ -198,10 +198,11 @@ def qt_plugin_class(base_plugin_class):
"Are you certain you want to remove your PIN?") % device
if not dialog.question(msg, title=title):
return
- client().set_pin(remove=True)
+ get_client().set_pin(remove=True)
refresh()
def wipe_device():
+ # FIXME: cannot yet wipe a device that is only plugged in
title = _("Confirm Device Wipe")
msg = _("Are you sure you want to wipe the device? "
"You should make sure you have a copy of your recovery "
@@ -215,7 +216,11 @@ def qt_plugin_class(base_plugin_class):
if not dialog.question(msg, title=title,
icon=QMessageBox.Critical):
return
- client().wipe_device()
+ # Note: we use PRESENT so that a user who has forgotten
+ # their PIN is not prevented from wiping their device
+ get_client(DeviceMgr.PRESENT).wipe_device()
+ wallet.wiped()
+ self.device_manager().close_wallet(wallet)
refresh()
def slider_moved():
diff --git a/plugins/trezor/trezor.py b/plugins/trezor/trezor.py
@@ -17,7 +17,7 @@ class TrezorPlugin(TrezorCompatiblePlugin):
client_class = trezor_client_class(ProtocolMixin, BaseClient, proto)
import trezorlib.ckd_public as ckd_public
from trezorlib.client import types
- from trezorlib.transport_hid import HidTransport
+ from trezorlib.transport_hid import HidTransport, DEVICE_IDS
libraries_available = True
except ImportError:
libraries_available = False