URI: 
       tlet DB handle addresses - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 56ced1dfd5e3f402aba9270b721ebf6db04b05b7
   DIR parent 2abc4f63347c0c508558a74e425d7409cc92b05c
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Mon,  4 Mar 2019 12:53:04 +0100
       
       let DB handle addresses
       
       Diffstat:
         M electrum/json_db.py                 |      67 +++++++++++++++++++++++++++++++
         M electrum/wallet.py                  |     107 ++++++++++---------------------
       
       2 files changed, 102 insertions(+), 72 deletions(-)
       ---
   DIR diff --git a/electrum/json_db.py b/electrum/json_db.py
       t@@ -667,6 +667,73 @@ class JsonDB(PrintError):
                    self.data[name] = {}
                return self.data[name]
        
       +    @locked
       +    def num_change_addresses(self):
       +        return len(self.change_addresses)
       +
       +    @locked
       +    def num_receiving_addresses(self):
       +        return len(self.receiving_addresses)
       +
       +    @locked
       +    def get_change_addresses(self):
       +        return list(self.change_addresses)
       +
       +    @locked
       +    def get_receiving_addresses(self):
       +        return list(self.receiving_addresses)
       +
       +    @modifier
       +    def add_change_address(self, addr):
       +        self._addr_to_addr_index[addr] = (True, len(self.change_addresses))
       +        self.change_addresses.append(addr)
       +
       +    @modifier
       +    def add_receiving_address(self, addr):
       +        self._addr_to_addr_index[addr] = (False, len(self.receiving_addresses))
       +        self.receiving_addresses.append(addr)
       +
       +    @locked
       +    def get_address_index(self, address):
       +        return self._addr_to_addr_index.get(address)
       +
       +    @modifier
       +    def add_imported_address(self, addr, d):
       +        self.imported_addresses[addr] = d
       +
       +    @modifier
       +    def remove_imported_address(self, addr):
       +        self.imported_addresses.pop(addr)
       +
       +    @locked
       +    def has_imported_address(self, addr):
       +        return addr in self.imported_addresses
       +
       +    @locked
       +    def get_imported_addresses(self):
       +        return list(sorted(self.imported_addresses.keys()))
       +
       +    @locked
       +    def get_imported_address(self, addr):
       +        return self.imported_addresses.get(addr)
       +
       +    def load_addresses(self, wallet_type):
       +        """ called from Abstract_Wallet.__init__ """
       +        if wallet_type == 'imported':
       +            self.imported_addresses = self.get_data_ref('addresses')
       +        else:
       +            self.get_data_ref('addresses')
       +            for name in ['receiving', 'change']:
       +                if name not in self.data['addresses']:
       +                    self.data['addresses'][name] = []
       +            self.change_addresses = self.data['addresses']['change']
       +            self.receiving_addresses = self.data['addresses']['receiving']
       +            self._addr_to_addr_index = {}  # key: address, value: (is_change, index)
       +            for i, addr in enumerate(self.receiving_addresses):
       +                self._addr_to_addr_index[addr] = (False, i)
       +            for i, addr in enumerate(self.change_addresses):
       +                self._addr_to_addr_index[addr] = (True, i)
       +
            @profiler
            def load_transactions(self):
                # references in self.data
   DIR diff --git a/electrum/wallet.py b/electrum/wallet.py
       t@@ -195,6 +195,9 @@ class Abstract_Wallet(AddressSynchronizer):
                if storage.requires_upgrade():
                    raise Exception("storage must be upgraded before constructing wallet")
        
       +        # load addresses needs to be called before constructor for sanity checks
       +        storage.db.load_addresses(self.wallet_type)
       +
                AddressSynchronizer.__init__(self, storage)
        
                # saved fields
       t@@ -219,7 +222,6 @@ class Abstract_Wallet(AddressSynchronizer):
        
            def load_and_cleanup(self):
                self.load_keystore()
       -        self.load_addresses()
                self.test_addresses_sanity()
                super().load_and_cleanup()
        
       t@@ -235,15 +237,6 @@ class Abstract_Wallet(AddressSynchronizer):
            def basename(self):
                return os.path.basename(self.storage.path)
        
       -    def save_addresses(self):
       -        self.storage.put('addresses', {'receiving':self.receiving_addresses, 'change':self.change_addresses})
       -
       -    def load_addresses(self):
       -        d = self.storage.get('addresses', {})
       -        if type(d) != dict: d={}
       -        self.receiving_addresses = d.get('receiving', [])
       -        self.change_addresses = d.get('change', [])
       -
            def test_addresses_sanity(self):
                addrs = self.get_receiving_addresses()
                if len(addrs) > 0:
       t@@ -320,11 +313,7 @@ class Abstract_Wallet(AddressSynchronizer):
                    return
        
            def is_mine(self, address):
       -        try:
       -            self.get_address_index(address)
       -        except KeyError:
       -            return False
       -        return True
       +        return bool(self.get_address_index(address))
        
            def is_change(self, address):
                if not self.is_mine(address):
       t@@ -1127,12 +1116,7 @@ class Abstract_Wallet(AddressSynchronizer):
                return True
        
            def get_sorted_requests(self, config):
       -        def f(addr):
       -            try:
       -                return self.get_address_index(addr)
       -            except:
       -                return
       -        keys = map(lambda x: (f(x), x), self.receive_requests.keys())
       +        keys = map(lambda x: (self.get_address_index(x), x), self.receive_requests.keys())
                sorted_keys = sorted(filter(lambda x: x[0] is not None, keys))
                return [self.get_payment_request(x[1], config) for x in sorted_keys]
        
       t@@ -1327,18 +1311,12 @@ class Imported_Wallet(Simple_Wallet):
        
            def load_keystore(self):
                self.keystore = load_keystore(self.storage, 'keystore') if self.storage.get('keystore') else None
       -
       -    def save_keystore(self):
       -        self.storage.put('keystore', self.keystore.dump())
       -
       -    def load_addresses(self):
       -        self.addresses = self.storage.get('addresses', {})
                # fixme: a reference to addresses is needed
                if self.keystore:
       -            self.keystore.addresses = self.addresses
       +            self.keystore.addresses = self.db.imported_addresses
        
       -    def save_addresses(self):
       -        self.storage.put('addresses', self.addresses)
       +    def save_keystore(self):
       +        self.storage.put('keystore', self.keystore.dump())
        
            def can_import_address(self):
                return self.is_watching_only()
       t@@ -1366,7 +1344,7 @@ class Imported_Wallet(Simple_Wallet):
        
            def get_addresses(self):
                # note: overridden so that the history can be cleared
       -        return sorted(self.addresses.keys())
       +        return self.db.get_imported_addresses()
        
            def get_receiving_addresses(self):
                return self.get_addresses()
       t@@ -1382,13 +1360,14 @@ class Imported_Wallet(Simple_Wallet):
                    if not bitcoin.is_address(address):
                        bad_addr.append((address, _('invalid address')))
                        continue
       -            if address in self.addresses:
       +            if self.db.has_imported_address(address):
                        bad_addr.append((address, _('address already in wallet')))
                        continue
                    good_addr.append(address)
       -            self.addresses[address] = {}
       +            self.db.add_imported_address(address, {})
                    self.add_address(address)
       -        self.save_addresses()
       +        if write_to_disk:
       +            self.storage.write()
                return good_addr, bad_addr
        
            def import_address(self, address: str) -> str:
       t@@ -1399,7 +1378,7 @@ class Imported_Wallet(Simple_Wallet):
                    raise BitcoinException(str(bad_addr[0][1]))
        
            def delete_address(self, address):
       -        if address not in self.addresses:
       +        if not self.db.has_imported_address(address):
                    return
                transactions_to_remove = set()  # only referred to by this address
                transactions_new = set()  # txs that are not only referred to by address
       t@@ -1424,7 +1403,7 @@ class Imported_Wallet(Simple_Wallet):
                self.remove_payment_request(address, {})
                self.set_frozen_state([address], False)
                pubkey = self.get_public_key(address)
       -        self.addresses.pop(address)
       +        self.db.remove_imported_address(address)
                if pubkey:
                    # delete key iff no other address uses it (e.g. p2pkh and p2wpkh for same key)
                    for txin_type in bitcoin.WIF_SCRIPT_TYPES.keys():
       t@@ -1433,19 +1412,23 @@ class Imported_Wallet(Simple_Wallet):
                        except NotImplementedError:
                            pass
                        else:
       -                    if addr2 in self.addresses:
       +                    if self.db.has_imported_address(addr2):
                                break
                    else:
                        self.keystore.delete_imported_key(pubkey)
                        self.save_keystore()
       -        self.save_addresses()
                self.storage.write()
        
       +    def is_mine(self, address):
       +        return self.db.has_imported_address(address)
       +
            def get_address_index(self, address):
       +        # returns None is address is not mine
                return self.get_public_key(address)
        
            def get_public_key(self, address):
       -        return self.addresses[address].get('pubkey')
       +        x = self.db.get_imported_address(address)
       +        return x.get('pubkey') if x else None
        
            def import_private_keys(self, keys: List[str], password: Optional[str], *,
                                    write_to_disk=True) -> Tuple[List[str], List[Tuple[str, str]]]:
       t@@ -1462,10 +1445,9 @@ class Imported_Wallet(Simple_Wallet):
                        continue
                    addr = bitcoin.pubkey_to_address(txin_type, pubkey)
                    good_addr.append(addr)
       -            self.addresses[addr] = {'type':txin_type, 'pubkey':pubkey, 'redeem_script':None}
       +            self.db.add_imported_address(addr, {'type':txin_type, 'pubkey':pubkey, 'redeem_script':None})
                    self.add_address(addr)
                self.save_keystore()
       -        self.save_addresses()
                if write_to_disk:
                    self.storage.write()
                return good_addr, bad_keys
       t@@ -1478,12 +1460,12 @@ class Imported_Wallet(Simple_Wallet):
                    raise BitcoinException(str(bad_keys[0][1]))
        
            def get_redeem_script(self, address):
       -        d = self.addresses[address]
       +        d = self.db.get_imported_address(address)
                redeem_script = d['redeem_script']
                return redeem_script
        
            def get_txin_type(self, address):
       -        return self.addresses[address].get('type', 'address')
       +        return self.db.get_imported_address(address).get('type', 'address')
        
            def add_input_sig_info(self, txin, address):
                if self.is_watching_only():
       t@@ -1492,7 +1474,7 @@ class Imported_Wallet(Simple_Wallet):
                    txin['signatures'] = [None]
                    return
                if txin['type'] in ['p2pkh', 'p2wpkh', 'p2wpkh-p2sh']:
       -            pubkey = self.addresses[address]['pubkey']
       +            pubkey = self.db.get_imported_address(address)['pubkey']
                    txin['num_sig'] = 1
                    txin['x_pubkeys'] = [pubkey]
                    txin['signatures'] = [None]
       t@@ -1500,8 +1482,8 @@ class Imported_Wallet(Simple_Wallet):
                    raise NotImplementedError('imported wallets for p2sh are not implemented')
        
            def pubkeys_to_address(self, pubkey):
       -        for addr, v in self.addresses.items():
       -            if v.get('pubkey') == pubkey:
       +        for addr in self.db.get_imported_addresses():
       +            if self.db.get_imported_address(addr)['pubkey'] == pubkey:
                        return addr
        
        class Deterministic_Wallet(Abstract_Wallet):
       t@@ -1525,10 +1507,10 @@ class Deterministic_Wallet(Abstract_Wallet):
                return out
        
            def get_receiving_addresses(self):
       -        return self.receiving_addresses
       +        return self.db.get_receiving_addresses()
        
            def get_change_addresses(self):
       -        return self.change_addresses
       +        return self.db.get_change_addresses()
        
            @profiler
            def try_detecting_internal_addresses_corruption(self):
       t@@ -1557,18 +1539,10 @@ class Deterministic_Wallet(Abstract_Wallet):
        
            def change_gap_limit(self, value):
                '''This method is not called in the code, it is kept for console use'''
       -        if value >= self.gap_limit:
       +        if value >= self.min_acceptable_gap():
                    self.gap_limit = value
                    self.storage.put('gap_limit', self.gap_limit)
       -            return True
       -        elif value >= self.min_acceptable_gap():
       -            addresses = self.get_receiving_addresses()
       -            k = self.num_unused_trailing_addresses(addresses)
       -            n = len(addresses) - k + value
       -            self.receiving_addresses = self.receiving_addresses[0:n]
       -            self.gap_limit = value
       -            self.storage.put('gap_limit', self.gap_limit)
       -            self.save_addresses()
       +            self.storage.write()
                    return True
                else:
                    return False
       t@@ -1595,14 +1569,6 @@ class Deterministic_Wallet(Abstract_Wallet):
                        nmax = max(nmax, n)
                return nmax + 1
        
       -    def load_addresses(self):
       -        super().load_addresses()
       -        self._addr_to_addr_index = {}  # key: address, value: (is_change, index)
       -        for i, addr in enumerate(self.receiving_addresses):
       -            self._addr_to_addr_index[addr] = (False, i)
       -        for i, addr in enumerate(self.change_addresses):
       -            self._addr_to_addr_index[addr] = (True, i)
       -
            def derive_address(self, for_change, n):
                x = self.derive_pubkeys(for_change, n)
                return self.pubkeys_to_address(x)
       t@@ -1610,12 +1576,9 @@ class Deterministic_Wallet(Abstract_Wallet):
            def create_new_address(self, for_change=False):
                assert type(for_change) is bool
                with self.lock:
       -            addr_list = self.change_addresses if for_change else self.receiving_addresses
       -            n = len(addr_list)
       +            n = self.db.num_change_addresses() if for_change else self.db.num_receiving_addresses()
                    address = self.derive_address(for_change, n)
       -            addr_list.append(address)
       -            self._addr_to_addr_index[address] = (for_change, n)
       -            self.save_addresses()
       +            self.db.add_change_address(address) if for_change else self.db.add_receiving_address(address)
                    self.add_address(address)
                    if for_change:
                        # note: if it's actually used, it will get filtered later
       t@@ -1652,7 +1615,7 @@ class Deterministic_Wallet(Abstract_Wallet):
                return True
        
            def get_address_index(self, address):
       -        return self._addr_to_addr_index[address]
       +        return self.db.get_address_index(address)
        
            def get_master_public_keys(self):
                return [self.get_master_public_key()]