tWrite the wallet less often - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit e1c0298fc29b171b1300076c7ec57fe5bf01707a DIR parent 0a3d74de8f2fa8eece37f4b728fb62a5b0658418 HTML Author: Neil Booth <kyuupichan@gmail.com> Date: Sun, 20 Dec 2015 15:39:57 +0900 Write the wallet less often This should speed up synchronization / restoration of large wallets. Wallets are written only when they switch to up_to_date state, or when stop_threads() is called when closing the daemon, or when a command line command finishes. Diffstat: M electrum | 10 +++++++--- M gui/gtk.py | 2 +- M gui/qt/installwizard.py | 4 ++-- M lib/synchronizer.py | 2 +- M lib/tests/test_wallet.py | 2 +- M lib/util.py | 6 +++--- M lib/wallet.py | 70 ++++++++++++++++---------------- M plugins/labels/labels.py | 10 +++++----- M plugins/trustedcoin/trustedcoin.py | 4 ++-- 9 files changed, 57 insertions(+), 53 deletions(-) --- DIR diff --git a/electrum b/electrum t@@ -167,6 +167,7 @@ def init_cmdline(config): print_msg("Your wallet generation seed is:\n\"%s\"" % seed) print_msg("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.") + wallet.storage.write() print_msg("Wallet saved in '%s'" % wallet.storage.path) sys.exit(0) t@@ -215,20 +216,22 @@ def init_cmdline(config): if raw_input("Are you sure you want to continue? (y/n) ") in ['y', 'Y', 'yes']: wallet.storage.path = ns wallet.seed = '' - wallet.storage.put('seed', '', True) + wallet.storage.put('seed', '') wallet.use_encryption = False - wallet.storage.put('use_encryption', wallet.use_encryption, True) + wallet.storage.put('use_encryption', wallet.use_encryption) for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = '' - wallet.storage.put('imported_keys', wallet.imported_keys, True) + wallet.storage.put('imported_keys', wallet.imported_keys) print_msg("Done.") else: print_msg("Action canceled.") + wallet.storage.write() sys.exit(0) elif cmd.name == 'password': new_password = prompt_password('New password:') wallet.update_password(password, new_password) + wallet.storage.write() sys.exit(0) return cmd, password, wallet t@@ -320,6 +323,7 @@ if __name__ == '__main__': if not (cmd.requires_network or cmd.requires_wallet) or config.get('offline'): result = run_offline_command(config, cmd, wallet, password) print_msg(json_encode(result)) + wallet.storage.write() sys.exit(0) else: config_options['password'] = password DIR diff --git a/gui/gtk.py b/gui/gtk.py t@@ -1300,7 +1300,7 @@ class ElectrumGui(): gap = self.config.get('gap_limit', 5) if gap != 5: wallet.gap_limit = gap - wallet.storage.put('gap_limit', gap, True) + wallet.storage.put('gap_limit', gap) if action == 'create': seed = wallet.make_seed() DIR diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py t@@ -463,7 +463,7 @@ class InstallWizard(QDialog): elif wallet_type == 'twofactor': wallet_type = '2fa' if action == 'create': - self.storage.put('wallet_type', wallet_type, False) + self.storage.put('wallet_type', wallet_type) if action is None: return t@@ -560,7 +560,7 @@ class InstallWizard(QDialog): password = self.password_dialog() if any(map(lambda x: Wallet.is_seed(x) or Wallet.is_xprv(x), key_list)) else None wallet = Wallet.from_multisig(key_list, password, self.storage, t) else: - self.storage.put('wallet_type', t, False) + self.storage.put('wallet_type', t) # call the constructor to load the plugin (side effect) Wallet(self.storage) wallet = always_hook('installwizard_restore', self, self.storage) DIR diff --git a/lib/synchronizer.py b/lib/synchronizer.py t@@ -179,5 +179,5 @@ class Synchronizer(ThreadJob): if up_to_date != self.wallet.is_up_to_date(): self.wallet.set_up_to_date(up_to_date) if up_to_date: - self.wallet.save_transactions() + self.wallet.save_transactions(write=True) self.network.trigger_callback('updated') DIR diff --git a/lib/tests/test_wallet.py b/lib/tests/test_wallet.py t@@ -57,7 +57,7 @@ class TestWalletStorage(WalletTestCase): some_dict = {"a":"b", "c":"d"} for key, value in some_dict.items(): - storage.put(key, value, False) + storage.put(key, value) storage.write() contents = "" DIR diff --git a/lib/util.py b/lib/util.py t@@ -155,14 +155,14 @@ def json_decode(x): # decorator that prints execution time def profiler(func): - def do_profile(func, args): + def do_profile(func, args, kw_args): n = func.func_name t0 = time.time() - o = apply(func, args) + o = func(*args, **kw_args) t = time.time() - t0 print_error("[profiler]", n, "%.4f"%t) return o - return lambda *args: do_profile(func, args) + return lambda *args, **kw_args: do_profile(func, args, kw_args) DIR diff --git a/lib/wallet.py b/lib/wallet.py t@@ -98,7 +98,7 @@ class WalletStorage(PrintError): v = copy.deepcopy(v) return v - def put(self, key, value, save = True): + def put(self, key, value): try: json.dumps(key) json.dumps(value) t@@ -113,8 +113,6 @@ class WalletStorage(PrintError): elif key in self.data: self.modified = True self.data.pop(key) - if save: - self.write() def write(self): if threading.currentThread().isDaemon(): t@@ -142,7 +140,7 @@ class WalletStorage(PrintError): import stat os.chmod(self.path, mode) self.print_error("saved", self.path) - + self.modified = False class Abstract_Wallet(PrintError): t@@ -199,7 +197,7 @@ class Abstract_Wallet(PrintError): # save wallet type the first time if self.storage.get('wallet_type') is None: - self.storage.put('wallet_type', self.wallet_type, True) + self.storage.put('wallet_type', self.wallet_type) def diagnostic_name(self): return self.basename() t@@ -220,17 +218,18 @@ class Abstract_Wallet(PrintError): self.transactions.pop(tx_hash) @profiler - def save_transactions(self): + def save_transactions(self, write=False): with self.transaction_lock: tx = {} for k,v in self.transactions.items(): tx[k] = str(v) - # Flush storage only with the last put - self.storage.put('transactions', tx, False) - self.storage.put('txi', self.txi, False) - self.storage.put('txo', self.txo, False) - self.storage.put('pruned_txo', self.pruned_txo, False) - self.storage.put('addr_history', self.history, True) + self.storage.put('transactions', tx) + self.storage.put('txi', self.txi) + self.storage.put('txo', self.txo) + self.storage.put('pruned_txo', self.pruned_txo) + self.storage.put('addr_history', self.history) + if write: + self.storage.write() def clear_history(self): with self.transaction_lock: t@@ -376,7 +375,7 @@ class Abstract_Wallet(PrintError): if changed: run_hook('set_label', self, name, text) - self.storage.put('labels', self.labels, True) + self.storage.put('labels', self.labels) return changed t@@ -436,7 +435,7 @@ class Abstract_Wallet(PrintError): self.unverified_tx.pop(tx_hash, None) with self.lock: self.verified_tx[tx_hash] = info # (tx_height, timestamp, pos) - self.storage.put('verified_tx3', self.verified_tx, True) + self.storage.put('verified_tx3', self.verified_tx) conf, timestamp = self.get_confirmations(tx_hash) self.network.trigger_callback('verified', tx_hash, conf, timestamp) t@@ -1033,7 +1032,7 @@ class Abstract_Wallet(PrintError): if self.has_seed(): decoded = self.get_seed(old_password) self.seed = pw_encode( decoded, new_password) - self.storage.put('seed', self.seed, True) + self.storage.put('seed', self.seed) imported_account = self.accounts.get(IMPORTED_ACCOUNT) if imported_account: t@@ -1045,10 +1044,10 @@ class Abstract_Wallet(PrintError): b = pw_decode(v, old_password) c = pw_encode(b, new_password) self.master_private_keys[k] = c - self.storage.put('master_private_keys', self.master_private_keys, True) + self.storage.put('master_private_keys', self.master_private_keys) self.use_encryption = (new_password != None) - self.storage.put('use_encryption', self.use_encryption,True) + self.storage.put('use_encryption', self.use_encryption) def is_frozen(self, addr): return addr in self.frozen_addresses t@@ -1060,7 +1059,7 @@ class Abstract_Wallet(PrintError): self.frozen_addresses |= set(addrs) else: self.frozen_addresses -= set(addrs) - self.storage.put('frozen_addresses', list(self.frozen_addresses), True) + self.storage.put('frozen_addresses', list(self.frozen_addresses)) return True return False t@@ -1098,7 +1097,8 @@ class Abstract_Wallet(PrintError): self.verifier = None # Now no references to the syncronizer or verifier # remain so they will be GC-ed - self.storage.put('stored_height', self.get_local_height(), True) + self.storage.put('stored_height', self.get_local_height()) + self.storage.write() def wait_until_synchronized(self, callback=None): from i18n import _ t@@ -1136,7 +1136,7 @@ class Abstract_Wallet(PrintError): d = {} for k, v in self.accounts.items(): d[k] = v.dump() - self.storage.put('accounts', d, True) + self.storage.put('accounts', d) def can_import(self): return not self.is_watching_only() t@@ -1420,9 +1420,9 @@ class Deterministic_Wallet(Abstract_Wallet): else: self.use_encryption = False - self.storage.put('seed', self.seed, False) - self.storage.put('seed_version', self.seed_version, False) - self.storage.put('use_encryption', self.use_encryption,True) + self.storage.put('seed', self.seed) + self.storage.put('seed_version', self.seed_version) + self.storage.put('use_encryption', self.use_encryption) def get_seed(self, password): return pw_decode(self.seed, password) t@@ -1434,7 +1434,7 @@ class Deterministic_Wallet(Abstract_Wallet): assert isinstance(value, int), 'gap limit must be of type int, not of %s'%type(value) if value >= self.gap_limit: self.gap_limit = value - self.storage.put('gap_limit', self.gap_limit, True) + self.storage.put('gap_limit', self.gap_limit) return True elif value >= self.min_acceptable_gap(): t@@ -1445,7 +1445,7 @@ class Deterministic_Wallet(Abstract_Wallet): account.receiving_pubkeys = account.receiving_pubkeys[0:n] account.receiving_addresses = account.receiving_addresses[0:n] self.gap_limit = value - self.storage.put('gap_limit', self.gap_limit, True) + self.storage.put('gap_limit', self.gap_limit) self.save_accounts() return True else: t@@ -1568,11 +1568,11 @@ class BIP32_Wallet(Deterministic_Wallet): if xpub in self.master_public_keys.values(): raise BaseException('Duplicate master public key') self.master_public_keys[name] = xpub - self.storage.put('master_public_keys', self.master_public_keys, True) + self.storage.put('master_public_keys', self.master_public_keys) def add_master_private_key(self, name, xpriv, password): self.master_private_keys[name] = pw_encode(xpriv, password) - self.storage.put('master_private_keys', self.master_private_keys, True) + self.storage.put('master_private_keys', self.master_private_keys) def derive_xkeys(self, root, derivation, password): x = self.master_private_keys[root] t@@ -1615,16 +1615,16 @@ class BIP32_Simple_Wallet(BIP32_Wallet): def create_xprv_wallet(self, xprv, password): xpub = bitcoin.xpub_from_xprv(xprv) account = BIP32_Account({'xpub':xpub}) - self.storage.put('seed_version', self.seed_version, True) + self.storage.put('seed_version', self.seed_version) self.add_master_private_key(self.root_name, xprv, password) self.add_master_public_key(self.root_name, xpub) self.add_account('0', account) self.use_encryption = (password != None) - self.storage.put('use_encryption', self.use_encryption,True) + self.storage.put('use_encryption', self.use_encryption) def create_xpub_wallet(self, xpub): account = BIP32_Account({'xpub':xpub}) - self.storage.put('seed_version', self.seed_version, True) + self.storage.put('seed_version', self.seed_version) self.add_master_public_key(self.root_name, xpub) self.add_account('0', account) t@@ -1823,7 +1823,7 @@ class OldWallet(Deterministic_Wallet): def create_master_keys(self, password): seed = self.get_seed(password) mpk = OldAccount.mpk_from_seed(seed) - self.storage.put('master_public_key', mpk, True) + self.storage.put('master_public_key', mpk) def get_master_public_key(self): return self.storage.get("master_public_key") t@@ -1841,8 +1841,8 @@ class OldWallet(Deterministic_Wallet): def create_watching_only_wallet(self, mpk): self.seed_version = OLD_SEED_VERSION - self.storage.put('seed_version', self.seed_version, False) - self.storage.put('master_public_key', mpk, True) + self.storage.put('seed_version', self.seed_version) + self.storage.put('master_public_key', mpk) self.create_account(mpk) def get_seed(self, password): t@@ -2026,7 +2026,7 @@ class Wallet(object): @classmethod def from_multisig(klass, key_list, password, storage, wallet_type): - storage.put('wallet_type', wallet_type, True) + storage.put('wallet_type', wallet_type) self = Multisig_Wallet(storage) key_list = sorted(key_list, key = lambda x: klass.is_xpub(x)) for i, text in enumerate(key_list): t@@ -2045,7 +2045,7 @@ class Wallet(object): else: self.add_cosigner_seed(text, name, password) self.use_encryption = (password != None) - self.storage.put('use_encryption', self.use_encryption, True) + self.storage.put('use_encryption', self.use_encryption) self.create_main_account(password) return self DIR diff --git a/plugins/labels/labels.py b/plugins/labels/labels.py t@@ -42,9 +42,9 @@ class LabelsPlugin(BasePlugin): self.set_nonce(wallet, nonce) return nonce - def set_nonce(self, wallet, nonce, force_write=True): + def set_nonce(self, wallet, nonce): self.print_error("set", wallet.basename(), "nonce to", nonce) - wallet.storage.put("wallet_nonce", nonce, force_write) + wallet.storage.put("wallet_nonce", nonce) @hook def set_label(self, wallet, item, label): t@@ -61,7 +61,7 @@ class LabelsPlugin(BasePlugin): t.setDaemon(True) t.start() # Caller will write the wallet - self.set_nonce(wallet, nonce + 1, force_write=False) + self.set_nonce(wallet, nonce + 1) def do_request(self, method, url = "/labels", is_batch=False, data=None): url = 'https://' + self.target_host + url t@@ -125,8 +125,8 @@ class LabelsPlugin(BasePlugin): self.print_error("received %d labels" % len(response)) # do not write to disk because we're in a daemon thread - wallet.storage.put('labels', wallet.labels, False) - self.set_nonce(wallet, response["nonce"] + 1, False) + wallet.storage.put('labels', wallet.labels) + self.set_nonce(wallet, response["nonce"] + 1) self.on_pulled(wallet) except Exception as e: DIR diff --git a/plugins/trustedcoin/trustedcoin.py b/plugins/trustedcoin/trustedcoin.py t@@ -305,8 +305,8 @@ class TrustedCoinPlugin(BasePlugin): return password = window.password_dialog() - wallet.storage.put('seed_version', wallet.seed_version, True) - wallet.storage.put('use_encryption', password is not None, True) + wallet.storage.put('seed_version', wallet.seed_version) + wallet.storage.put('use_encryption', password is not None) words = seed.split() n = len(words)/2