URI: 
       tdevice manager: index devices by xpub - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 664077397ead66ed63abfca6bb4614fc19020485
   DIR parent a972a476bce083c55e9e64f48cb56f71d644df69
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Sat, 20 Aug 2016 14:55:57 +0200
       
       device manager: index devices by xpub
       
       Diffstat:
         M lib/base_wizard.py                  |       5 +++--
         M lib/plugins.py                      |      84 +++++++++++++++----------------
         M plugins/hw_wallet/plugin.py         |       5 +++--
         M plugins/keepkey/__init__.py         |       1 -
         M plugins/ledger/__init__.py          |       1 -
         M plugins/trezor/__init__.py          |       1 -
         M plugins/trezor/plugin.py            |      33 +++++++++++++++++++------------
         M plugins/trezor/qt_generic.py        |      16 ++++++++++------
       
       8 files changed, 76 insertions(+), 70 deletions(-)
       ---
   DIR diff --git a/lib/base_wizard.py b/lib/base_wizard.py
       t@@ -178,8 +178,9 @@ class BaseWizard(object):
            def on_hardware_account_id(self, account_id):
                from keystore import load_keystore
                self.storage.put('account_id', int(account_id))
       -        keystore = load_keystore(self.storage, None)
       -        keystore.plugin.on_create_wallet(keystore, self)
       +        name = self.storage.get('hardware_type')
       +        plugin = self.plugins.get_plugin(name)
       +        plugin.on_create_wallet(self.storage, self)
        
            def on_hardware_seed(self):
                self.storage.put('key_type', 'hw_seed')
   DIR diff --git a/lib/plugins.py b/lib/plugins.py
       t@@ -144,7 +144,7 @@ class Plugins(DaemonThread):
                for name, (gui_good, details) in self.hw_wallets.items():
                    if gui_good:
                        try:
       -                    p = self.wallet_plugin_loader(name)
       +                    p = self.get_plugin(name)
                            if action == 'restore' or p.is_enabled():
                                wallet_types.append(details[1])
                                descs.append(details[2])
       t@@ -157,7 +157,7 @@ class Plugins(DaemonThread):
                from wallet import register_wallet_type, register_constructor
                self.print_error("registering wallet type", (wallet_type, name))
                def loader():
       -            plugin = self.wallet_plugin_loader(name)
       +            plugin = self.get_plugin(name)
                    register_constructor(wallet_type, plugin.wallet_class)
                register_wallet_type(wallet_type)
                plugin_loaders[wallet_type] = loader
       t@@ -165,13 +165,13 @@ class Plugins(DaemonThread):
            def register_keystore(self, name, gui_good, details):
                from keystore import register_keystore
                def dynamic_constructor():
       -            return self.wallet_plugin_loader(name).keystore_class()
       +            return self.get_plugin(name).keystore_class()
                if details[0] == 'hardware':
                    self.hw_wallets[name] = (gui_good, details)
                    self.print_error("registering keystore %s: %s" %(name, details))
                register_keystore(details[0], details[1], dynamic_constructor)
        
       -    def wallet_plugin_loader(self, name):
       +    def get_plugin(self, name):
                if not name in self.plugins:
                    self.load_plugin(name)
                return self.plugins[name]
       t@@ -297,9 +297,9 @@ class DeviceMgr(ThreadJob, PrintError):
        
            def __init__(self, config):
                super(DeviceMgr, self).__init__()
       -        # Keyed by wallet.  The value is the device id if the wallet
       +        # Keyed by xpub.  The value is the device id 
                # has been paired, and None otherwise.
       -        self.wallets = {}
       +        self.xpub_ids = {}
                # A list of clients.  The key is the client, the value is
                # a (path, id_) pair.
                self.clients = {}
       t@@ -339,38 +339,38 @@ class DeviceMgr(ThreadJob, PrintError):
                        self.clients[client] = (device.path, device.id_)
                return client
        
       -    def wallet_id(self, wallet):
       +    def xpub_id(self, xpub):
                with self.lock:
       -            return self.wallets.get(wallet)
       +            return self.xpub_ids.get(xpub)
        
       -    def wallet_by_id(self, id_):
       +    def xpub_by_id(self, id_):
                with self.lock:
       -            for wallet, wallet_id in self.wallets.items():
       -                if wallet_id == id_:
       -                    return wallet
       +            for xpub, xpub_id in self.xpub_ids.items():
       +                if xpub_id == id_:
       +                    return xpub
                    return None
        
       -    def unpair_wallet(self, wallet):
       +    def unpair_xpub(self, xpub):
                with self.lock:
       -            if not wallet in self.wallets:
       +            if not xpub in self.xpub_ids:
                        return
       -            wallet_id = self.wallets.pop(wallet)
       -            client = self.client_lookup(wallet_id)
       +            _id = self.xpub_ids.pop(xpub)
       +            client = self.client_lookup(_id)
                    self.clients.pop(client, None)
       -        wallet.unpaired()
       +        #wallet.unpaired()
                if client:
                    client.close()
        
            def unpair_id(self, id_):
                with self.lock:
       -            wallet = self.wallet_by_id(id_)
       -        if wallet:
       -            self.unpair_wallet(wallet)
       +            xpub = self.xpub_by_id(id_)
       +        if xpub:
       +            self.unpair_xpub(xpub)
        
       -    def pair_wallet(self, wallet, id_):
       +    def pair_xpub(self, xpub, id_):
                with self.lock:
       -            self.wallets[wallet] = id_
       -        wallet.paired()
       +            self.xpub_ids[xpub] = id_
       +        #wallet.paired()
        
            def client_lookup(self, id_):
                with self.lock:
       t@@ -386,37 +386,33 @@ class DeviceMgr(ThreadJob, PrintError):
                self.scan_devices(handler)
                return self.client_lookup(id_)
        
       -    def client_for_keystore(self, plugin, keystore, force_pair):
       -        assert keystore.handler
       -        devices = self.scan_devices(keystore.handler)
       -        wallet_id = self.wallet_id(keystore)
       -        client = self.client_lookup(wallet_id)
       +    def client_for_xpub(self, plugin, xpub, derivation, handler, force_pair):
       +        devices = self.scan_devices(handler)
       +        _id = self.xpub_id(xpub)
       +        client = self.client_lookup(_id)
                if client:
                    # An unpaired client might have another wallet's handler
                    # from a prior scan.  Replace to fix dialog parenting.
       -            client.handler = keystore.handler
       +            client.handler = handler
                    return client
        
                for device in devices:
       -            if device.id_ == wallet_id:
       -                return self.create_client(device, keystore.handler, plugin)
       +            if device.id_ == _id:
       +                return self.create_client(device, handler, plugin)
        
                if force_pair:
       -            return self.force_pair_wallet(plugin, keystore, devices)
       +            return self.force_pair_xpub(plugin, handler, xpub, derivation, devices)
        
                return None
        
       -    def force_pair_wallet(self, plugin, keystore, devices):
       -        xpub = keystore.get_master_public_key()
       -        derivation = keystore.get_derivation()
       -
       +    def force_pair_xpub(self, plugin, handler, xpub, derivation, devices):
                # The wallet has not been previously paired, so let the user
                # choose an unpaired device and compare its first address.
       -        info = self.select_device(keystore, plugin, devices)
       +        info = self.select_device(handler, plugin, devices)
                client = self.client_lookup(info.device.id_)
                if client and client.is_pairable():
                    # See comment above for same code
       -            client.handler = keystore.handler
       +            client.handler = handler
                    # This will trigger a PIN/passphrase entry request
                    try:
                        client_xpub = client.get_xpub(derivation)
       t@@ -424,7 +420,7 @@ class DeviceMgr(ThreadJob, PrintError):
                         # Bad / cancelled PIN / passphrase
                        client_xpub = None
                    if client_xpub == xpub:
       -                self.pair_wallet(keystore, info.device.id_)
       +                self.pair_xpub(xpub, info.device.id_)
                        return client
        
                # The user input has wrong PIN or passphrase, or cancelled input,
       t@@ -441,7 +437,7 @@ class DeviceMgr(ThreadJob, PrintError):
                unpaired device accepted by the plugin.'''
                if devices is None:
                    devices = self.scan_devices(handler)
       -        devices = [dev for dev in devices if not self.wallet_by_id(dev.id_)]
       +        devices = [dev for dev in devices if not self.xpub_by_id(dev.id_)]
        
                states = [_("wiped"), _("initialized")]
                infos = []
       t@@ -458,17 +454,17 @@ class DeviceMgr(ThreadJob, PrintError):
        
                return infos
        
       -    def select_device(self, wallet, plugin, devices=None):
       +    def select_device(self, handler, plugin, devices=None):
                '''Ask the user to select a device to use if there is more than one,
                and return the DeviceInfo for the device.'''
                while True:
       -            infos = self.unpaired_device_infos(wallet.handler, plugin, devices)
       +            infos = self.unpaired_device_infos(handler, plugin, devices)
                    if infos:
                        break
                    msg = _('Could not connect to your %s.  Verify the cable is '
                            'connected and that no other application is using it.\n\n'
                            'Try to connect again?') % plugin.device
       -            if not wallet.handler.yes_no_question(msg):
       +            if not handler.yes_no_question(msg):
                        raise UserCancelled()
                    devices = None
        
       t@@ -476,7 +472,7 @@ class DeviceMgr(ThreadJob, PrintError):
                    return infos[0]
                msg = _("Please select which %s device to use:") % plugin.device
                descriptions = [info.description for info in infos]
       -        return infos[wallet.handler.query_choice(msg, descriptions)]
       +        return infos[handler.query_choice(msg, descriptions)]
        
            def scan_devices(self, handler):
                # All currently supported hardware libraries use hid, so we
   DIR diff --git a/plugins/hw_wallet/plugin.py b/plugins/hw_wallet/plugin.py
       t@@ -48,6 +48,7 @@ class HW_PluginBase(BasePlugin):
        
            @hook
            def close_wallet(self, wallet):
       -        if isinstance(wallet.get_keystore(), self.keystore_class):
       -            self.device_manager().unpair_wallet(wallet)
       +        keystore = wallet.get_keystore()
       +        if isinstance(keystore, self.keystore_class):
       +            self.device_manager().unpair_xpub(keystore.xpub)
        
   DIR diff --git a/plugins/keepkey/__init__.py b/plugins/keepkey/__init__.py
       t@@ -3,6 +3,5 @@ from electrum.i18n import _
        fullname = 'KeepKey'
        description = _('Provides support for KeepKey hardware wallet')
        requires = [('keepkeylib','github.com/keepkey/python-keepkey')]
       -#requires_wallet_type = ['keepkey']
        registers_keystore = ('hardware', 'keepkey', _("KeepKey wallet"))
        available_for = ['qt', 'cmdline']
   DIR diff --git a/plugins/ledger/__init__.py b/plugins/ledger/__init__.py
       t@@ -3,6 +3,5 @@ from electrum.i18n import _
        fullname = 'Ledger Wallet'
        description = 'Provides support for Ledger hardware wallet'
        requires = [('btchip', 'github.com/ledgerhq/btchip-python')]
       -#requires_wallet_type = ['btchip']
        registers_keystore = ('hardware', 'btchip', _("Ledger wallet"))
        available_for = ['qt', 'cmdline']
   DIR diff --git a/plugins/trezor/__init__.py b/plugins/trezor/__init__.py
       t@@ -3,7 +3,6 @@ from electrum.i18n import _
        fullname = 'TREZOR Wallet'
        description = _('Provides support for TREZOR hardware wallet')
        requires = [('trezorlib','github.com/trezor/python-trezor')]
       -#requires_wallet_type = ['trezor']
        registers_keystore = ('hardware', 'trezor', _("TREZOR wallet"))
        available_for = ['qt', 'cmdline']
        
   DIR diff --git a/plugins/trezor/plugin.py b/plugins/trezor/plugin.py
       t@@ -31,10 +31,6 @@ class TrezorCompatibleKeyStore(Hardware_KeyStore):
            def get_client(self, force_pair=True):
                return self.plugin.get_client(self, force_pair)
        
       -    def init_xpub(self):
       -        client = self.get_client()
       -        self.xpub = client.get_xpub(self.get_derivation())
       -
            def decrypt_message(self, pubkey, message, password):
                raise RuntimeError(_('Electrum and %s encryption and decryption are currently incompatible') % self.device)
                address = public_key_to_bc_address(pubkey.decode('hex'))
       t@@ -142,7 +138,11 @@ class TrezorCompatiblePlugin(HW_PluginBase):
                # All client interaction should not be in the main GUI thread
                assert self.main_thread != threading.current_thread()
                devmgr = self.device_manager()
       -        client = devmgr.client_for_keystore(self, keystore, force_pair)
       +        derivation = keystore.get_derivation()
       +        xpub = keystore.xpub
       +        handler = keystore.handler
       +        client = devmgr.client_for_xpub(self, xpub, derivation, handler, force_pair)
       +        # returns the client for a given keystore. can use xpub
                if client:
                    client.used()
                return client
       t@@ -202,24 +202,31 @@ class TrezorCompatiblePlugin(HW_PluginBase):
                        pin = pin_protection  # It's the pin, not a boolean
                        client.load_device_by_xprv(item, pin, passphrase_protection,
                                                   label, language)
       -            # After successful initialization create accounts
       -            keystore.init_xpub()
       -            #wallet.create_main_account()
       +            # After successful initialization get xpub
       +            self.xpub = client.get_xpub(derivation)
        
                return initialize_method
        
       -    def setup_device(self, keystore, on_done, on_error):
       +    def init_xpub(self, derivation, device_id, handler):
       +        devmgr = self.device_manager()
       +        client = devmgr.client_by_id(device_id, handler)
       +        if client:
       +            client.used()
       +        self.xpub = client.get_xpub(derivation)
       +
       +    def setup_device(self, derivation, thread, handler, on_done, on_error):
                '''Called when creating a new wallet.  Select the device to use.  If
                the device is uninitialized, go through the intialization
                process.  Then create the wallet accounts.'''
                devmgr = self.device_manager()
       -        device_info = devmgr.select_device(keystore, self)
       -        devmgr.pair_wallet(keystore, device_info.device.id_)
       +        device_info = devmgr.select_device(handler, self)
       +        device_id = device_info.device.id_
       +        #devmgr.pair_wallet(keystore, device_info.device.id_)
                if device_info.initialized:
       -            task = keystore.init_xpub
       +            task = lambda: self.init_xpub(derivation, device_id, handler)
                else:
                    task = self.initialize_device(keystore)
       -        keystore.thread.add(task, on_done=on_done, on_error=on_error)
       +        thread.add(task, on_done=on_done, on_error=on_error)
        
            def sign_transaction(self, keystore, tx, prev_tx, xpub_path):
                self.prev_tx = prev_tx
   DIR diff --git a/plugins/trezor/qt_generic.py b/plugins/trezor/qt_generic.py
       t@@ -284,19 +284,23 @@ def qt_plugin_class(base_plugin_class):
                # Trigger a pairing
                keystore.thread.add(partial(self.get_client, keystore))
        
       -    def on_create_wallet(self, keystore, wizard):
       -        keystore.handler = self.create_handler(wizard)
       -        keystore.thread = TaskThread(wizard, wizard.on_error)
       +    def on_create_wallet(self, storage, wizard):
       +        from electrum.keystore import load_keystore
       +        handler = self.create_handler(wizard)
       +        thread = TaskThread(wizard, wizard.on_error)
                # Setup device and create accounts in separate thread; wait until done
                loop = QEventLoop()
                exc_info = []
       -        self.setup_device(keystore, on_done=loop.quit,
       +        derivation = "m/44'/0'/%d'"%storage.get('account_id')
       +        self.setup_device(derivation, thread, handler, on_done=loop.quit,
                                  on_error=lambda info: exc_info.extend(info))
                loop.exec_()
                # If an exception was thrown, show to user and exit install wizard
                if exc_info:
                    wizard.on_error(exc_info)
                    raise UserCancelled
       +        storage.put('master_public_keys', {'/x':self.xpub})
       +        keystore = load_keystore(storage, '/x') # this calls the dynamic constructor
                wizard.create_wallet(keystore, None)
        
            @hook
       t@@ -316,9 +320,9 @@ def qt_plugin_class(base_plugin_class):
                '''This dialog box should be usable even if the user has
                forgotten their PIN or it is in bootloader mode.'''
                keystore = window.wallet.get_keystore()
       -        device_id = self.device_manager().wallet_id(keystore)
       +        device_id = self.device_manager().xpub_id(keystore.xpub)
                if not device_id:
       -            info = self.device_manager().select_device(keystore, self)
       +            info = self.device_manager().select_device(keystore.handler, self)
                    device_id = info.device.id_
                return device_id