URI: 
       tMerge pull request #5830 from SomberNight/20191209_wallet_perf - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 0828454ef1bda75f7436b39225ed6cd31edc4fa7
   DIR parent 9d83dea0dcace1eded930308c050d143cf9df4ce
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Mon,  9 Dec 2019 17:30:10 +0100
       
       Merge pull request #5830 from SomberNight/20191209_wallet_perf
       
       wallet perf: significant speedup for make_unsigned_transaction and rel.
       Diffstat:
         M electrum/base_wizard.py             |       2 +-
         M electrum/bip32.py                   |       1 +
         M electrum/keystore.py                |      38 +++++++++++++++++++++----------
         M run_electrum                        |       4 ++--
       
       4 files changed, 30 insertions(+), 15 deletions(-)
       ---
   DIR diff --git a/electrum/base_wizard.py b/electrum/base_wizard.py
       t@@ -369,7 +369,7 @@ class BaseWizard(Logger):
                elif purpose == HWD_SETUP_DECRYPT_WALLET:
                    derivation = get_derivation_used_for_hw_device_encryption()
                    xpub = self.plugin.get_xpub(device_info.device.id_, derivation, 'standard', self)
       -            password = keystore.Xpub.get_pubkey_from_xpub(xpub, ())
       +            password = keystore.Xpub.get_pubkey_from_xpub(xpub, ()).hex()
                    try:
                        storage.decrypt(password)
                    except InvalidPassword:
   DIR diff --git a/electrum/bip32.py b/electrum/bip32.py
       t@@ -273,6 +273,7 @@ class BIP32Node(NamedTuple):
                """Returns the fingerprint of this node.
                Note that self.fingerprint is of the *parent*.
                """
       +        # TODO cache this
                return hash_160(self.eckey.get_public_key_bytes(compressed=True))[0:4]
        
        
   DIR diff --git a/electrum/keystore.py b/electrum/keystore.py
       t@@ -28,6 +28,7 @@ from unicodedata import normalize
        import hashlib
        import re
        from typing import Tuple, TYPE_CHECKING, Union, Sequence, Optional, Dict, List, NamedTuple
       +from functools import lru_cache
        
        from . import bitcoin, ecc, constants, bip32
        from .bitcoin import deserialize_privkey, serialize_privkey
       t@@ -142,8 +143,8 @@ class KeyStore(Logger):
                            if not test_der_suffix_against_pubkey(der_suffix, pubkey):
                                der_suffix = None
                    # try fp against our intermediate fingerprint
       -            if (der_suffix is None and hasattr(self, 'xpub') and
       -                    fp_found == BIP32Node.from_xkey(self.xpub).calc_fingerprint_of_this_node()):
       +            if (der_suffix is None and hasattr(self, 'get_bip32_node_for_xpub') and
       +                    fp_found == self.get_bip32_node_for_xpub().calc_fingerprint_of_this_node()):
                        der_suffix = path_found
                        if not test_der_suffix_against_pubkey(der_suffix, pubkey):
                            der_suffix = None
       t@@ -330,6 +331,7 @@ class Xpub:
                self.xpub = None
                self.xpub_receive = None
                self.xpub_change = None
       +        self._xpub_bip32_node = None  # type: Optional[BIP32Node]
        
                # "key origin" info (subclass should persist these):
                self._derivation_prefix = derivation_prefix  # type: Optional[str]
       t@@ -338,6 +340,13 @@ class Xpub:
            def get_master_public_key(self):
                return self.xpub
        
       +    def get_bip32_node_for_xpub(self) -> Optional[BIP32Node]:
       +        if self._xpub_bip32_node is None:
       +            if self.xpub is None:
       +                return None
       +            self._xpub_bip32_node = BIP32Node.from_xkey(self.xpub)
       +        return self._xpub_bip32_node
       +
            def get_derivation_prefix(self) -> Optional[str]:
                """Returns to bip32 path from some root node to self.xpub
                Note that the return value might be None; if it is unknown.
       t@@ -366,7 +375,7 @@ class Xpub:
                    der_prefix_ints = convert_bip32_path_to_list_of_uint32(der_prefix_str)
                else:
                    # use intermediate fp, and claim der suffix is the full path
       -            fingerprint_bytes = BIP32Node.from_xkey(self.xpub).calc_fingerprint_of_this_node()
       +            fingerprint_bytes = self.get_bip32_node_for_xpub().calc_fingerprint_of_this_node()
                    der_prefix_ints = convert_bip32_path_to_list_of_uint32('m')
                der_full = der_prefix_ints + list(der_suffix)
                return fingerprint_bytes, der_full
       t@@ -375,7 +384,7 @@ class Xpub:
                assert self.xpub
                fp_bytes, der_full = self.get_fp_and_derivation_to_be_used_in_partial_tx(der_suffix=[],
                                                                                         only_der_suffix=only_der_suffix)
       -        bip32node = BIP32Node.from_xkey(self.xpub)
       +        bip32node = self.get_bip32_node_for_xpub()
                depth = len(der_full)
                child_number_int = der_full[-1] if len(der_full) >= 1 else 0
                child_number_bytes = child_number_int.to_bytes(length=4, byteorder="big")
       t@@ -390,7 +399,7 @@ class Xpub:
                # try to derive ourselves from what we were given
                child_node1 = root_node.subkey_at_private_derivation(derivation_prefix)
                child_pubkey_bytes1 = child_node1.eckey.get_public_key_bytes(compressed=True)
       -        child_node2 = BIP32Node.from_xkey(self.xpub)
       +        child_node2 = self.get_bip32_node_for_xpub()
                child_pubkey_bytes2 = child_node2.eckey.get_public_key_bytes(compressed=True)
                if child_pubkey_bytes1 != child_pubkey_bytes2:
                    raise Exception("(xpub, derivation_prefix, root_node) inconsistency")
       t@@ -402,12 +411,12 @@ class Xpub:
                self._root_fingerprint = root_fingerprint
                self._derivation_prefix = normalize_bip32_derivation(derivation_prefix)
        
       -    def derive_pubkey(self, for_change, n) -> str:
       -        for_change = int(for_change)
       +    @lru_cache(maxsize=None)
       +    def _derive_pubkey_bytes(self, for_change: int, n: int) -> bytes:
                assert for_change in (0, 1)
                xpub = self.xpub_change if for_change else self.xpub_receive
                if xpub is None:
       -            rootnode = BIP32Node.from_xkey(self.xpub)
       +            rootnode = self.get_bip32_node_for_xpub()
                    xpub = rootnode.subkey_at_public_derivation((for_change,)).to_xpub()
                    if for_change:
                        self.xpub_change = xpub
       t@@ -415,10 +424,15 @@ class Xpub:
                        self.xpub_receive = xpub
                return self.get_pubkey_from_xpub(xpub, (n,))
        
       +    def derive_pubkey(self, for_change: int, n: int) -> str:
       +        for_change = int(for_change)
       +        assert for_change in (0, 1)
       +        return self._derive_pubkey_bytes(for_change, n).hex()
       +
            @classmethod
       -    def get_pubkey_from_xpub(self, xpub, sequence):
       +    def get_pubkey_from_xpub(self, xpub: str, sequence) -> bytes:
                node = BIP32Node.from_xkey(xpub).subkey_at_public_derivation(sequence)
       -        return node.eckey.get_public_key_hex(compressed=True)
       +        return node.eckey.get_public_key_bytes(compressed=True)
        
        
        class BIP32_KeyStore(Deterministic_KeyStore, Xpub):
       t@@ -447,7 +461,7 @@ class BIP32_KeyStore(Deterministic_KeyStore, Xpub):
        
            def check_password(self, password):
                xprv = pw_decode(self.xprv, password, version=self.pw_hash_version)
       -        if BIP32Node.from_xkey(xprv).chaincode != BIP32Node.from_xkey(self.xpub).chaincode:
       +        if BIP32Node.from_xkey(xprv).chaincode != self.get_bip32_node_for_xpub().chaincode:
                    raise InvalidPassword()
        
            def update_password(self, old_password, new_password):
       t@@ -692,7 +706,7 @@ class Hardware_KeyStore(KeyStore, Xpub):
                client = self.plugin.get_client(self)
                derivation = get_derivation_used_for_hw_device_encryption()
                xpub = client.get_xpub(derivation, "standard")
       -        password = self.get_pubkey_from_xpub(xpub, ())
       +        password = self.get_pubkey_from_xpub(xpub, ()).hex()
                return password
        
            def has_usable_connection_with_device(self) -> bool:
   DIR diff --git a/run_electrum b/run_electrum
       t@@ -178,7 +178,7 @@ def get_connected_hw_devices(plugins):
            return devices
        
        
       -def get_password_for_hw_device_encrypted_storage(plugins):
       +def get_password_for_hw_device_encrypted_storage(plugins) -> str:
            devices = get_connected_hw_devices(plugins)
            if len(devices) == 0:
                print_msg("Error: No connected hw device found. Cannot decrypt this wallet.")
       t@@ -194,7 +194,7 @@ def get_password_for_hw_device_encrypted_storage(plugins):
                xpub = plugin.get_xpub(device_info.device.id_, derivation, 'standard', plugin.handler)
            except UserCancelled:
                sys.exit(0)
       -    password = keystore.Xpub.get_pubkey_from_xpub(xpub, ())
       +    password = keystore.Xpub.get_pubkey_from_xpub(xpub, ()).hex()
            return password