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