URI: 
       tSeparate db from storage - storage is content-agnostic - db and storage are passed to wallet contructor - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit e1ce3aace7e3143da24115890d9bae78a9f5bcaf
   DIR parent c61e5db6a951dd26fda1913ad09622a67e03ecbb
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Wed,  5 Feb 2020 15:13:37 +0100
       
       Separate db from storage
        - storage is content-agnostic
        - db and storage are passed to wallet contructor
       
       Diffstat:
         M electrum/base_wizard.py             |      29 ++++++++++++++++-------------
         M electrum/commands.py                |       8 ++++----
         M electrum/contacts.py                |       8 ++++----
         M electrum/daemon.py                  |      13 ++++++++-----
         M electrum/gui/kivy/main_window.py    |      18 ++++++++++--------
         M electrum/gui/kivy/uix/dialogs/inst… |      10 +++++-----
         M electrum/gui/qt/__init__.py         |      10 ++++++----
         M electrum/gui/qt/installwizard.py    |      27 +++++++++++++++------------
         M electrum/gui/qt/main_window.py      |      12 ++++++------
         M electrum/gui/qt/settings_dialog.py  |       4 ++--
         M electrum/keystore.py                |       4 ++--
         M electrum/lnpeer.py                  |       2 +-
         M electrum/lnworker.py                |      26 +++++++++++++-------------
         M electrum/plugins/labels/labels.py   |       4 ++--
         M electrum/plugins/trustedcoin/qt.py  |       2 +-
         M electrum/plugins/trustedcoin/trust… |      32 ++++++++++++++++----------------
         M electrum/storage.py                 |      87 +++++--------------------------
         M electrum/tests/test_commands.py     |      14 +++++++-------
         M electrum/tests/test_lnpeer.py       |      14 ++------------
         M electrum/tests/test_storage_upgrad… |      66 +++++++++++++------------------
         M electrum/tests/test_wallet.py       |      12 +++++++-----
         M electrum/tests/test_wallet_vertica… |     208 ++++++++++++++++----------------
         M electrum/wallet.py                  |     173 ++++++++++++++++---------------
         M electrum/wallet_db.py               |      46 ++++++++++++++++++++++++++++++-
         M run_electrum                        |      15 ++++++++++++---
       
       25 files changed, 422 insertions(+), 422 deletions(-)
       ---
   DIR diff --git a/electrum/base_wizard.py b/electrum/base_wizard.py
       t@@ -39,6 +39,7 @@ from .wallet import (Imported_Wallet, Standard_Wallet, Multisig_Wallet,
                             wallet_types, Wallet, Abstract_Wallet)
        from .storage import (WalletStorage, StorageEncryptionVersion,
                              get_derivation_used_for_hw_device_encryption)
       +from .wallet_db import WalletDB
        from .i18n import _
        from .util import UserCancelled, InvalidPassword, WalletFileException
        from .simple_config import SimpleConfig
       t@@ -64,7 +65,7 @@ class WizardStackItem(NamedTuple):
            action: Any
            args: Any
            kwargs: Dict[str, Any]
       -    storage_data: dict
       +    db_data: dict
        
        
        class WizardWalletPasswordSetting(NamedTuple):
       t@@ -95,8 +96,8 @@ class BaseWizard(Logger):
            def run(self, *args, **kwargs):
                action = args[0]
                args = args[1:]
       -        storage_data = copy.deepcopy(self.data)
       -        self._stack.append(WizardStackItem(action, args, kwargs, storage_data))
       +        db_data = copy.deepcopy(self.data)
       +        self._stack.append(WizardStackItem(action, args, kwargs, db_data))
                if not action:
                    return
                if type(action) is tuple:
       t@@ -122,7 +123,7 @@ class BaseWizard(Logger):
                stack_item = self._stack.pop()
                # try to undo side effects since we last entered 'previous' frame
                # FIXME only self.storage is properly restored
       -        self.data = copy.deepcopy(stack_item.storage_data)
       +        self.data = copy.deepcopy(stack_item.db_data)
                # rerun 'previous' frame
                self.run(stack_item.action, *stack_item.args, **stack_item.kwargs)
        
       t@@ -143,17 +144,17 @@ class BaseWizard(Logger):
                choices = [pair for pair in wallet_kinds if pair[0] in wallet_types]
                self.choice_dialog(title=title, message=message, choices=choices, run_next=self.on_wallet_type)
        
       -    def upgrade_storage(self, storage):
       +    def upgrade_db(self, storage, db):
                exc = None
                def on_finished():
                    if exc is None:
       -                self.terminate(storage=storage)
       +                self.terminate(storage=storage, db=db)
                    else:
                        raise exc
                def do_upgrade():
                    nonlocal exc
                    try:
       -                storage.upgrade()
       +                db.upgrade()
                    except Exception as e:
                        exc = e
                self.waiting_dialog(do_upgrade, _('Upgrading wallet format...'), on_finished=on_finished)
       t@@ -592,6 +593,7 @@ class BaseWizard(Logger):
                                                           encrypt_keystore=encrypt_keystore)
                self.terminate()
        
       +
            def create_storage(self, path):
                if os.path.exists(path):
                    raise Exception('file already exists at path')
       t@@ -600,16 +602,17 @@ class BaseWizard(Logger):
                pw_args = self.pw_args
                self.pw_args = None  # clean-up so that it can get GC-ed
                storage = WalletStorage(path)
       -        storage.set_keystore_encryption(bool(pw_args.password) and pw_args.encrypt_keystore)
                if pw_args.encrypt_storage:
                    storage.set_password(pw_args.password, enc_version=pw_args.storage_enc_version)
       +        db = WalletDB('', manual_upgrades=False)
       +        db.set_keystore_encryption(bool(pw_args.password) and pw_args.encrypt_keystore)
                for key, value in self.data.items():
       -            storage.put(key, value)
       -        storage.write()
       -        storage.load_plugins()
       -        return storage
       +            db.put(key, value)
       +        db.load_plugins()
       +        db.write(storage)
       +        return storage, db
        
       -    def terminate(self, *, storage: Optional[WalletStorage] = None):
       +    def terminate(self, *, storage: Optional[WalletStorage], db: Optional[WalletDB] = None):
                raise NotImplementedError()  # implemented by subclasses
        
            def show_xpub_and_add_cosigners(self, xpub):
   DIR diff --git a/electrum/commands.py b/electrum/commands.py
       t@@ -262,13 +262,13 @@ class Commands:
                    raise Exception("Can't change the password of a wallet encrypted with a hw device.")
                b = wallet.storage.is_encrypted()
                wallet.update_password(password, new_password, encrypt_storage=b)
       -        wallet.storage.write()
       +        wallet.save_db()
                return {'password':wallet.has_password()}
        
            @command('w')
            async def get(self, key, wallet: Abstract_Wallet = None):
                """Return item from wallet storage"""
       -        return wallet.storage.get(key)
       +        return wallet.db.get(key)
        
            @command('')
            async def getconfig(self, key):
       t@@ -830,7 +830,7 @@ class Commands:
                tx = Transaction(tx)
                if not wallet.add_transaction(tx):
                    return False
       -        wallet.storage.write()
       +        wallet.save_db()
                return tx.txid()
        
            @command('wp')
       t@@ -906,7 +906,7 @@ class Commands:
                to_delete |= wallet.get_depending_transactions(txid)
                for tx_hash in to_delete:
                    wallet.remove_transaction(tx_hash)
       -        wallet.storage.write()
       +        wallet.save_db()
        
            @command('wn')
            async def get_tx_status(self, txid, wallet: Abstract_Wallet = None):
   DIR diff --git a/electrum/contacts.py b/electrum/contacts.py
       t@@ -33,10 +33,10 @@ from .logging import Logger
        
        class Contacts(dict, Logger):
        
       -    def __init__(self, storage):
       +    def __init__(self, db):
                Logger.__init__(self)
       -        self.storage = storage
       -        d = self.storage.get('contacts', {})
       +        self.db = db
       +        d = self.db.get('contacts', {})
                try:
                    self.update(d)
                except:
       t@@ -49,7 +49,7 @@ class Contacts(dict, Logger):
                        self[n] = ('address', k)
        
            def save(self):
       -        self.storage.put('contacts', dict(self))
       +        self.db.put('contacts', dict(self))
        
            def import_file(self, path):
                import_meta(path, self._validate, self.load_meta)
   DIR diff --git a/electrum/daemon.py b/electrum/daemon.py
       t@@ -47,6 +47,7 @@ from .util import PR_PAID, PR_EXPIRED, get_request_status
        from .util import log_exceptions, ignore_exceptions
        from .wallet import Wallet, Abstract_Wallet
        from .storage import WalletStorage
       +from .wallet_db import WalletDB
        from .commands import known_commands, Commands
        from .simple_config import SimpleConfig
        from .exchange_rate import FxThread
       t@@ -401,20 +402,22 @@ class Daemon(Logger):
                if path in self._wallets:
                    wallet = self._wallets[path]
                    return wallet
       -        storage = WalletStorage(path, manual_upgrades=manual_upgrades)
       +        storage = WalletStorage(path)
                if not storage.file_exists():
                    return
                if storage.is_encrypted():
                    if not password:
                        return
                    storage.decrypt(password)
       -        if storage.requires_split():
       +        # read data, pass it to db
       +        db = WalletDB(storage.read(), manual_upgrades=manual_upgrades)
       +        if db.requires_split():
                    return
       -        if storage.requires_upgrade():
       +        if db.requires_upgrade():
                    return
       -        if storage.get_action():
       +        if db.get_action():
                    return
       -        wallet = Wallet(storage, config=self.config)
       +        wallet = Wallet(db, storage, config=self.config)
                wallet.start_network(self.network)
                self._wallets[path] = wallet
                self.wallet = wallet
   DIR diff --git a/electrum/gui/kivy/main_window.py b/electrum/gui/kivy/main_window.py
       t@@ -10,6 +10,7 @@ import asyncio
        from typing import TYPE_CHECKING, Optional, Union, Callable
        
        from electrum.storage import WalletStorage, StorageReadWriteError
       +from electrum.wallet_db import WalletDB
        from electrum.wallet import Wallet, InternalAddressCorruption, Abstract_Wallet
        from electrum.plugin import run_hook
        from electrum.util import (profiler, InvalidPassword, send_exception_to_crash_reporter,
       t@@ -166,8 +167,8 @@ class ElectrumWindow(App):
            def on_use_change(self, instance, x):
                if self.wallet:
                    self.wallet.use_change = self.use_change
       -            self.wallet.storage.put('use_change', self.use_change)
       -            self.wallet.storage.write()
       +            self.wallet.db.put('use_change', self.use_change)
       +            self.wallet.save_db()
        
            use_unconfirmed = BooleanProperty(False)
            def on_use_unconfirmed(self, instance, x):
       t@@ -588,9 +589,9 @@ class ElectrumWindow(App):
                else:
                    return ''
        
       -    def on_wizard_complete(self, wizard, storage):
       +    def on_wizard_complete(self, wizard, storage, db):
                if storage:
       -            wallet = Wallet(storage, config=self.electrum_config)
       +            wallet = Wallet(db, storage, config=self.electrum_config)
                    wallet.start_network(self.daemon.network)
                    self.daemon.add_wallet(wallet)
                    self.load_wallet(wallet)
       t@@ -602,13 +603,14 @@ class ElectrumWindow(App):
        
            def _on_decrypted_storage(self, storage: WalletStorage):
                assert storage.is_past_initial_decryption()
       -        if storage.requires_upgrade():
       +        db = WalletDB(storage.read(), manual_upgrades=False)
       +        if db.requires_upgrade():
                    wizard = Factory.InstallWizard(self.electrum_config, self.plugins)
                    wizard.path = storage.path
                    wizard.bind(on_wizard_complete=self.on_wizard_complete)
       -            wizard.upgrade_storage(storage)
       +            wizard.upgrade_storage(storage, db)
                else:
       -            self.on_wizard_complete(wizard=None, storage=storage)
       +            self.on_wizard_complete(None, storage, db)
        
            def load_wallet_by_name(self, path, ask_if_wizard=False):
                if not path:
       t@@ -624,7 +626,7 @@ class ElectrumWindow(App):
                        self.load_wallet(wallet)
                else:
                    def launch_wizard():
       -                storage = WalletStorage(path, manual_upgrades=True)
       +                storage = WalletStorage(path)
                        if not storage.file_exists():
                            wizard = Factory.InstallWizard(self.electrum_config, self.plugins)
                            wizard.path = path
   DIR diff --git a/electrum/gui/kivy/uix/dialogs/installwizard.py b/electrum/gui/kivy/uix/dialogs/installwizard.py
       t@@ -633,7 +633,7 @@ class WizardDialog(EventsDialog):
                self._on_release = True
                self.close()
                if not button:
       -            self.parent.dispatch('on_wizard_complete', None)
       +            self.parent.dispatch('on_wizard_complete', None, None)
                    return
                if button is self.ids.back:
                    self.wizard.go_back()
       t@@ -1055,7 +1055,7 @@ class InstallWizard(BaseWizard, Widget):
        
            __events__ = ('on_wizard_complete', )
        
       -    def on_wizard_complete(self, wallet):
       +    def on_wizard_complete(self, storage, db):
                """overriden by main_window"""
                pass
        
       t@@ -1086,10 +1086,10 @@ class InstallWizard(BaseWizard, Widget):
                t = threading.Thread(target = target)
                t.start()
        
       -    def terminate(self, *, storage=None, aborted=False):
       +    def terminate(self, *, storage=None, db=None, aborted=False):
                if storage is None and not aborted:
       -            storage = self.create_storage(self.path)
       -        self.dispatch('on_wizard_complete', storage)
       +            storage, db = self.create_storage(self.path)
       +        self.dispatch('on_wizard_complete', storage, db)
        
            def choice_dialog(self, **kwargs):
                choices = kwargs['choices']
   DIR diff --git a/electrum/gui/qt/__init__.py b/electrum/gui/qt/__init__.py
       t@@ -48,6 +48,7 @@ from electrum.base_wizard import GoBack
        from electrum.util import (UserCancelled, profiler,
                                   WalletFileException, BitcoinException, get_new_wallet_name)
        from electrum.wallet import Wallet, Abstract_Wallet
       +from electrum.wallet_db import WalletDB
        from electrum.logging import Logger
        
        from .installwizard import InstallWizard, WalletAlreadyOpenInMemory
       t@@ -306,9 +307,10 @@ class ElectrumGui(Logger):
                    if storage is None:
                        wizard.path = path  # needed by trustedcoin plugin
                        wizard.run('new')
       -                storage = wizard.create_storage(path)
       +                storage, db = wizard.create_storage(path)
                    else:
       -                wizard.run_upgrades(storage)
       +                db = WalletDB(storage.read(), manual_upgrades=False)
       +                wizard.run_upgrades(storage, db)
                except (UserCancelled, GoBack):
                    return
                except WalletAlreadyOpenInMemory as e:
       t@@ -316,9 +318,9 @@ class ElectrumGui(Logger):
                finally:
                    wizard.terminate()
                # return if wallet creation is not complete
       -        if storage is None or storage.get_action():
       +        if storage is None or db.get_action():
                    return
       -        wallet = Wallet(storage, config=self.config)
       +        wallet = Wallet(db, storage, config=self.config)
                wallet.start_network(self.daemon.network)
                self.daemon.add_wallet(wallet)
                return wallet
   DIR diff --git a/electrum/gui/qt/installwizard.py b/electrum/gui/qt/installwizard.py
       t@@ -3,6 +3,7 @@
        # file LICENCE or http://www.opensource.org/licenses/mit-license.php
        
        import os
       +import json
        import sys
        import threading
        import traceback
       t@@ -225,7 +226,7 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
                        if wallet_from_memory:
                            temp_storage = wallet_from_memory.storage  # type: Optional[WalletStorage]
                        else:
       -                    temp_storage = WalletStorage(path, manual_upgrades=True)
       +                    temp_storage = WalletStorage(path)
                    except (StorageReadWriteError, WalletFileException) as e:
                        msg = _('Cannot read file') + f'\n{repr(e)}'
                    except Exception as e:
       t@@ -316,24 +317,24 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
        
                return temp_storage.path, (temp_storage if temp_storage.file_exists() else None)
        
       -    def run_upgrades(self, storage):
       +    def run_upgrades(self, storage, db):
                path = storage.path
       -        if storage.requires_split():
       +        if db.requires_split():
                    self.hide()
                    msg = _("The wallet '{}' contains multiple accounts, which are no longer supported since Electrum 2.7.\n\n"
                            "Do you want to split your wallet into multiple files?").format(path)
                    if not self.question(msg):
                        return
       -            file_list = '\n'.join(storage.split_accounts())
       -            msg = _('Your accounts have been moved to') + ':\n' + file_list + '\n\n'+ _('Do you want to delete the old file') + ':\n' + path
       +            file_list = db.split_accounts(path)
       +            msg = _('Your accounts have been moved to') + ':\n' + '\n'.join(file_list) + '\n\n'+ _('Do you want to delete the old file') + ':\n' + path
                    if self.question(msg):
                        os.remove(path)
                        self.show_warning(_('The file was removed'))
                    # raise now, to avoid having the old storage opened
                    raise UserCancelled()
        
       -        action = storage.get_action()
       -        if action and storage.requires_upgrade():
       +        action = db.get_action()
       +        if action and db.requires_upgrade():
                    raise WalletFileException('Incomplete wallet files cannot be upgraded.')
                if action:
                    self.hide()
       t@@ -345,15 +346,17 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
                            self.show_warning(_('The file was removed'))
                        return
                    self.show()
       -            self.data = storage.db.data # FIXME
       +            self.data = json.loads(storage.read())
                    self.run(action)
                    for k, v in self.data.items():
       -                storage.put(k, v)
       -            storage.write()
       +                db.put(k, v)
       +            db.write(storage)
                    return
        
       -        if storage.requires_upgrade():
       -            self.upgrade_storage(storage)
       +        if db.requires_upgrade():
       +            self.upgrade_db(storage, db)
       +
       +        return db
        
            def finished(self):
                """Called in hardware client wrapper, in order to close popups."""
   DIR diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py
       t@@ -456,7 +456,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                    send_exception_to_crash_reporter(e)
        
            def init_geometry(self):
       -        winpos = self.wallet.storage.get("winpos-qt")
       +        winpos = self.wallet.db.get("winpos-qt")
                try:
                    screen = self.app.desktop().screenGeometry()
                    assert screen.contains(QRect(*winpos))
       t@@ -469,7 +469,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                name = "Electrum Testnet" if constants.net.TESTNET else "Electrum"
                title = '%s %s  -  %s' % (name, ELECTRUM_VERSION,
                                                self.wallet.basename())
       -        extra = [self.wallet.storage.get('wallet_type', '?')]
       +        extra = [self.wallet.db.get('wallet_type', '?')]
                if self.wallet.is_watching_only():
                    extra.append(_('watching only'))
                title += '  [%s]'% ', '.join(extra)
       t@@ -1958,7 +1958,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
        
            def update_console(self):
                console = self.console
       -        console.history = self.wallet.storage.get("qt-console-history", [])
       +        console.history = self.wallet.db.get("qt-console-history", [])
                console.history_index = len(console.history)
        
                console.updateNamespace({
       t@@ -2154,7 +2154,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                dialog.setMinimumSize(500, 100)
                mpk_list = self.wallet.get_master_public_keys()
                vbox = QVBoxLayout()
       -        wallet_type = self.wallet.storage.get('wallet_type', '')
       +        wallet_type = self.wallet.db.get('wallet_type', '')
                if self.wallet.is_watching_only():
                    wallet_type += ' [{}]'.format(_('watching-only'))
                seed_available = _('True') if self.wallet.has_seed() else _('False')
       t@@ -2788,9 +2788,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                self.config.set_key("is_maximized", self.isMaximized())
                if not self.isMaximized():
                    g = self.geometry()
       -            self.wallet.storage.put("winpos-qt", [g.left(),g.top(),
       +            self.wallet.db.put("winpos-qt", [g.left(),g.top(),
                                                          g.width(),g.height()])
       -        self.wallet.storage.put("qt-console-history", self.console.history[-50:])
       +        self.wallet.db.put("qt-console-history", self.console.history[-50:])
                if self.qr_window:
                    self.qr_window.close()
                self.close_wallet()
   DIR diff --git a/electrum/gui/qt/settings_dialog.py b/electrum/gui/qt/settings_dialog.py
       t@@ -324,7 +324,7 @@ that is always connected to the internet. Configure a port if you want it to be 
                    usechange_result = x == Qt.Checked
                    if self.window.wallet.use_change != usechange_result:
                        self.window.wallet.use_change = usechange_result
       -                self.window.wallet.storage.put('use_change', self.window.wallet.use_change)
       +                self.window.wallet.db.put('use_change', self.window.wallet.use_change)
                        multiple_cb.setEnabled(self.window.wallet.use_change)
                usechange_cb.stateChanged.connect(on_usechange)
                usechange_cb.setToolTip(_('Using change addresses makes it more difficult for other people to track your transactions.'))
       t@@ -334,7 +334,7 @@ that is always connected to the internet. Configure a port if you want it to be 
                    multiple = x == Qt.Checked
                    if self.wallet.multiple_change != multiple:
                        self.wallet.multiple_change = multiple
       -                self.wallet.storage.put('multiple_change', multiple)
       +                self.wallet.db.put('multiple_change', multiple)
                multiple_change = self.wallet.multiple_change
                multiple_cb = QCheckBox(_('Use multiple change addresses'))
                multiple_cb.setEnabled(self.wallet.use_change)
   DIR diff --git a/electrum/keystore.py b/electrum/keystore.py
       t@@ -864,8 +864,8 @@ def hardware_keystore(d) -> Hardware_KeyStore:
            raise WalletFileException(f'unknown hardware type: {hw_type}. '
                                      f'hw_keystores: {list(hw_keystores)}')
        
       -def load_keystore(storage, name) -> KeyStore:
       -    d = storage.get(name, {})
       +def load_keystore(db, name) -> KeyStore:
       +    d = db.get(name, {})
            t = d.get('type')
            if not t:
                raise WalletFileException(
   DIR diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py
       t@@ -617,7 +617,7 @@ class Peer(Logger):
                    "revocation_store": {},
                }
                channel_id = chan_dict.get('channel_id')
       -        channels = self.lnworker.storage.db.get_dict('channels')
       +        channels = self.lnworker.db.get_dict('channels')
                channels[channel_id] = chan_dict
                return channels.get(channel_id)
        
   DIR diff --git a/electrum/lnworker.py b/electrum/lnworker.py
       t@@ -341,25 +341,25 @@ class LNWallet(LNWorker):
            def __init__(self, wallet: 'Abstract_Wallet', xprv):
                Logger.__init__(self)
                self.wallet = wallet
       -        self.storage = wallet.storage
       +        self.db = wallet.db
                self.config = wallet.config
                LNWorker.__init__(self, xprv)
                self.ln_keystore = keystore.from_xprv(xprv)
                self.localfeatures |= LnLocalFeatures.OPTION_DATA_LOSS_PROTECT_REQ
       -        self.payments = self.storage.db.get_dict('lightning_payments')     # RHASH -> amount, direction, is_paid
       -        self.preimages = self.storage.db.get_dict('lightning_preimages')   # RHASH -> preimage
       +        self.payments = self.db.get_dict('lightning_payments')     # RHASH -> amount, direction, is_paid
       +        self.preimages = self.db.get_dict('lightning_preimages')   # RHASH -> preimage
                self.sweep_address = wallet.get_receiving_address()
                self.lock = threading.RLock()
                self.logs = defaultdict(list)  # type: Dict[str, List[PaymentAttemptLog]]  # key is RHASH
        
                # note: accessing channels (besides simple lookup) needs self.lock!
                self.channels = {}
       -        channels = self.storage.db.get_dict("channels")
       +        channels = self.db.get_dict("channels")
                for channel_id, c in channels.items():
                    self.channels[bfh(channel_id)] = Channel(c, sweep_address=self.sweep_address, lnworker=self)
        
                # timestamps of opening and closing transactions
       -        self.channel_timestamps = self.storage.db.get_dict('lightning_channel_timestamps')
       +        self.channel_timestamps = self.db.get_dict('lightning_channel_timestamps')
                self.pending_payments = defaultdict(asyncio.Future)
        
            @ignore_exceptions
       t@@ -585,10 +585,10 @@ class LNWallet(LNWorker):
        
            def get_and_inc_counter_for_channel_keys(self):
                with self.lock:
       -            ctr = self.storage.get('lightning_channel_key_der_ctr', -1)
       +            ctr = self.db.get('lightning_channel_key_der_ctr', -1)
                    ctr += 1
       -            self.storage.put('lightning_channel_key_der_ctr', ctr)
       -            self.storage.write()
       +            self.db.put('lightning_channel_key_der_ctr', ctr)
       +            self.wallet.save_db()
                    return ctr
        
            def suggest_peer(self):
       t@@ -610,7 +610,7 @@ class LNWallet(LNWorker):
                assert type(chan) is Channel
                if chan.config[REMOTE].next_per_commitment_point == chan.config[REMOTE].current_per_commitment_point:
                    raise Exception("Tried to save channel with next_point == current_point, this should not happen")
       -        self.wallet.storage.write()
       +        self.wallet.save_db()
                self.network.trigger_callback('channel', chan)
        
            def save_short_chan_id(self, chan):
       t@@ -1127,7 +1127,7 @@ class LNWallet(LNWorker):
            def save_preimage(self, payment_hash: bytes, preimage: bytes):
                assert sha256(preimage) == payment_hash
                self.preimages[bh2u(payment_hash)] = bh2u(preimage)
       -        self.storage.write()
       +        self.wallet.save_db()
        
            def get_preimage(self, payment_hash: bytes) -> bytes:
                return bfh(self.preimages.get(bh2u(payment_hash)))
       t@@ -1145,7 +1145,7 @@ class LNWallet(LNWorker):
                assert info.status in [PR_PAID, PR_UNPAID, PR_INFLIGHT]
                with self.lock:
                    self.payments[key] = info.amount, info.direction, info.status
       -        self.storage.write()
       +        self.wallet.save_db()
        
            def get_payment_status(self, payment_hash):
                try:
       t@@ -1230,7 +1230,7 @@ class LNWallet(LNWorker):
                        del self.payments[payment_hash_hex]
                except KeyError:
                    return
       -        self.storage.write()
       +        self.wallet.save_db()
        
            def get_balance(self):
                with self.lock:
       t@@ -1276,7 +1276,7 @@ class LNWallet(LNWorker):
                with self.lock:
                    self.channels.pop(chan_id)
                    self.channel_timestamps.pop(chan_id.hex())
       -            self.storage.get('channels').pop(chan_id.hex())
       +            self.db.get('channels').pop(chan_id.hex())
        
                self.network.trigger_callback('channels_updated', self.wallet)
                self.network.trigger_callback('wallet_updated', self.wallet)
   DIR diff --git a/electrum/plugins/labels/labels.py b/electrum/plugins/labels/labels.py
       t@@ -46,7 +46,7 @@ class LabelsPlugin(BasePlugin):
        
            def get_nonce(self, wallet):
                # nonce is the nonce to be used with the next change
       -        nonce = wallet.storage.get('wallet_nonce')
       +        nonce = wallet.db.get('wallet_nonce')
                if nonce is None:
                    nonce = 1
                    self.set_nonce(wallet, nonce)
       t@@ -54,7 +54,7 @@ class LabelsPlugin(BasePlugin):
        
            def set_nonce(self, wallet, nonce):
                self.logger.info(f"set {wallet.basename()} nonce to {nonce}")
       -        wallet.storage.put("wallet_nonce", nonce)
       +        wallet.db.put("wallet_nonce", nonce)
        
            @hook
            def set_label(self, wallet, item, label):
   DIR diff --git a/electrum/plugins/trustedcoin/qt.py b/electrum/plugins/trustedcoin/qt.py
       t@@ -227,7 +227,7 @@ class Plugin(TrustedCoinPlugin):
                    wizard.confirm_dialog(title='', message=msg, run_next = lambda x: wizard.run('accept_terms_of_use'))
                except GoBack:
                    # user clicked 'Cancel' and decided to move wallet file manually
       -            wizard.create_storage(wizard.path)
       +            storage, db = wizard.create_storage(wizard.path)
                    raise
        
            def accept_terms_of_use(self, window):
   DIR diff --git a/electrum/plugins/trustedcoin/trustedcoin.py b/electrum/plugins/trustedcoin/trustedcoin.py
       t@@ -264,17 +264,17 @@ class Wallet_2fa(Multisig_Wallet):
        
            wallet_type = '2fa'
        
       -    def __init__(self, storage, *, config):
       +    def __init__(self, db, *, config):
                self.m, self.n = 2, 3
       -        Deterministic_Wallet.__init__(self, storage, config=config)
       +        Deterministic_Wallet.__init__(self, db, config=config)
                self.is_billing = False
                self.billing_info = None
                self._load_billing_addresses()
        
            def _load_billing_addresses(self):
                billing_addresses = {
       -            'legacy': self.storage.get('trustedcoin_billing_addresses', {}),
       -            'segwit': self.storage.get('trustedcoin_billing_addresses_segwit', {})
       +            'legacy': self.db.get('trustedcoin_billing_addresses', {}),
       +            'segwit': self.db.get('trustedcoin_billing_addresses_segwit', {})
                }
                self._billing_addresses = {}  # type: Dict[str, Dict[int, str]]  # addr_type -> index -> addr
                self._billing_addresses_set = set()  # set of addrs
       t@@ -289,7 +289,7 @@ class Wallet_2fa(Multisig_Wallet):
                return not self.keystores['x2/'].is_watching_only()
        
            def get_user_id(self):
       -        return get_user_id(self.storage)
       +        return get_user_id(self.db)
        
            def min_prepay(self):
                return min(self.price_per_tx.keys())
       t@@ -383,10 +383,10 @@ class Wallet_2fa(Multisig_Wallet):
                billing_addresses_of_this_type[billing_index] = address
                self._billing_addresses_set.add(address)
                self._billing_addresses[addr_type] = billing_addresses_of_this_type
       -        self.storage.put('trustedcoin_billing_addresses', self._billing_addresses['legacy'])
       -        self.storage.put('trustedcoin_billing_addresses_segwit', self._billing_addresses['segwit'])
       +        self.db.put('trustedcoin_billing_addresses', self._billing_addresses['legacy'])
       +        self.db.put('trustedcoin_billing_addresses_segwit', self._billing_addresses['segwit'])
                # FIXME this often runs in a daemon thread, where storage.write will fail
       -        self.storage.write()
       +        self.db.write(self.storage)
        
            def is_billing_address(self, addr: str) -> bool:
                return addr in self._billing_addresses_set
       t@@ -394,11 +394,11 @@ class Wallet_2fa(Multisig_Wallet):
        
        # Utility functions
        
       -def get_user_id(storage):
       +def get_user_id(db):
            def make_long_id(xpub_hot, xpub_cold):
                return sha256(''.join(sorted([xpub_hot, xpub_cold])))
       -    xpub1 = storage.get('x1/')['xpub']
       -    xpub2 = storage.get('x2/')['xpub']
       +    xpub1 = db.get('x1/')['xpub']
       +    xpub2 = db.get('x2/')['xpub']
            long_id = make_long_id(xpub1, xpub2)
            short_id = hashlib.sha256(long_id).hexdigest()
            return long_id, short_id
       t@@ -753,12 +753,12 @@ class TrustedCoinPlugin(BasePlugin):
                self.request_otp_dialog(wizard, short_id, new_secret, xpub3)
        
            @hook
       -    def get_action(self, storage):
       -        if storage.get('wallet_type') != '2fa':
       +    def get_action(self, db):
       +        if db.get('wallet_type') != '2fa':
                    return
       -        if not storage.get('x1/'):
       +        if not db.get('x1/'):
                    return self, 'show_disclaimer'
       -        if not storage.get('x2/'):
       +        if not db.get('x2/'):
                    return self, 'show_disclaimer'
       -        if not storage.get('x3/'):
       +        if not db.get('x3/'):
                    return self, 'accept_terms_of_use'
   DIR diff --git a/electrum/storage.py b/electrum/storage.py
       t@@ -32,7 +32,6 @@ from enum import IntEnum
        
        from . import ecc
        from .util import profiler, InvalidPassword, WalletFileException, bfh, standardize_path
       -from .plugin import run_hook, plugin_loaders
        
        from .wallet_db import WalletDB
        from .logging import Logger
       t@@ -53,28 +52,27 @@ class StorageEncryptionVersion(IntEnum):
        class StorageReadWriteError(Exception): pass
        
        
       +# TODO: Rename to Storage
        class WalletStorage(Logger):
        
       -    def __init__(self, path, *, manual_upgrades: bool = False):
       +    def __init__(self, path):
                Logger.__init__(self)
                self.path = standardize_path(path)
                self._file_exists = bool(self.path and os.path.exists(self.path))
       -        self._manual_upgrades = manual_upgrades
       -
                self.logger.info(f"wallet path {self.path}")
                self.pubkey = None
       +        self.decrypted = ''
                self._test_read_write_permissions(self.path)
                if self.file_exists():
                    with open(self.path, "r", encoding='utf-8') as f:
                        self.raw = f.read()
                    self._encryption_version = self._init_encryption_version()
       -            if not self.is_encrypted():
       -                self.db = WalletDB(self.raw, manual_upgrades=manual_upgrades)
       -                self.load_plugins()
                else:
       +            self.raw = ''
                    self._encryption_version = StorageEncryptionVersion.PLAINTEXT
       -            # avoid new wallets getting 'upgraded'
       -            self.db = WalletDB('', manual_upgrades=False)
       +
       +    def read(self):
       +        return self.decrypted if self.is_encrypted() else self.raw
        
            @classmethod
            def _test_read_write_permissions(cls, path):
       t@@ -98,29 +96,9 @@ class WalletStorage(Logger):
                if echo != echo2:
                    raise StorageReadWriteError('echo sanity-check failed')
        
       -    def load_plugins(self):
       -        wallet_type = self.db.get('wallet_type')
       -        if wallet_type in plugin_loaders:
       -            plugin_loaders[wallet_type]()
       -
       -    def put(self, key,value):
       -        self.db.put(key, value)
       -
       -    def get(self, key, default=None):
       -        return self.db.get(key, default)
       -
            @profiler
       -    def write(self):
       -        with self.db.lock:
       -            self._write()
       -
       -    def _write(self):
       -        if threading.currentThread().isDaemon():
       -            self.logger.warning('daemon thread cannot write db')
       -            return
       -        if not self.db.modified():
       -            return
       -        s = self.encrypt_before_writing(self.db.dump())
       +    def write(self, data):
       +        s = self.encrypt_before_writing(data)
                temp_path = "%s.tmp.%s" % (self.path, os.getpid())
                with open(temp_path, "w", encoding='utf-8') as f:
                    f.write(s)
       t@@ -135,7 +113,6 @@ class WalletStorage(Logger):
                os.chmod(self.path, mode)
                self._file_exists = True
                self.logger.info(f"saved {self.path}")
       -        self.db.set_modified(False)
        
            def file_exists(self) -> bool:
                return self._file_exists
       t@@ -148,7 +125,7 @@ class WalletStorage(Logger):
                    or if encryption is enabled but the contents have already been decrypted.
                """
                try:
       -            return bool(self.db.data)
       +            return not self.is_encrypted() or bool(self.decrypted)
                except AttributeError:
                    return False
        
       t@@ -207,12 +184,12 @@ class WalletStorage(Logger):
                if self.raw:
                    enc_magic = self._get_encryption_magic()
                    s = zlib.decompress(ec_key.decrypt_message(self.raw, enc_magic))
       +            s = s.decode('utf8')
                else:
       -            s = None
       +            s = ''
                self.pubkey = ec_key.get_public_key_hex()
       -        s = s.decode('utf8')
       -        self.db = WalletDB(s, manual_upgrades=self._manual_upgrades)
       -        self.load_plugins()
       +        self.decrypted = s
       +        return s
        
            def encrypt_before_writing(self, plaintext: str) -> str:
                s = plaintext
       t@@ -234,9 +211,6 @@ class WalletStorage(Logger):
                if self.pubkey and self.pubkey != self.get_eckey_from_password(password).get_public_key_hex():
                    raise InvalidPassword()
        
       -    def set_keystore_encryption(self, enable):
       -        self.put('use_encryption', enable)
       -
            def set_password(self, password, enc_version=None):
                """Set a password to be used for encrypting this storage."""
                if enc_version is None:
       t@@ -248,40 +222,7 @@ class WalletStorage(Logger):
                else:
                    self.pubkey = None
                    self._encryption_version = StorageEncryptionVersion.PLAINTEXT
       -        # make sure next storage.write() saves changes
       -        self.db.set_modified(True)
        
            def basename(self) -> str:
                return os.path.basename(self.path)
        
       -    def requires_upgrade(self):
       -        if not self.is_past_initial_decryption():
       -            raise Exception("storage not yet decrypted!")
       -        return self.db.requires_upgrade()
       -
       -    def is_ready_to_be_used_by_wallet(self):
       -        return not self.requires_upgrade() and self.db._called_after_upgrade_tasks
       -
       -    def upgrade(self):
       -        self.db.upgrade()
       -        self.write()
       -
       -    def requires_split(self):
       -        return self.db.requires_split()
       -
       -    def split_accounts(self):
       -        out = []
       -        result = self.db.split_accounts()
       -        for data in result:
       -            path = self.path + '.' + data['suffix']
       -            storage = WalletStorage(path)
       -            storage.db.data = data
       -            storage.db._called_after_upgrade_tasks = False
       -            storage.db.upgrade()
       -            storage.write()
       -            out.append(path)
       -        return out
       -
       -    def get_action(self):
       -        action = run_hook('get_action', self)
       -        return action
   DIR diff --git a/electrum/tests/test_commands.py b/electrum/tests/test_commands.py
       t@@ -4,7 +4,7 @@ from decimal import Decimal
        
        from electrum.util import create_and_start_event_loop
        from electrum.commands import Commands, eval_bool
       -from electrum import storage
       +from electrum import storage, wallet
        from electrum.wallet import restore_wallet_from_text
        from electrum.simple_config import SimpleConfig
        
       t@@ -77,8 +77,8 @@ class TestCommands(ElectrumTestCase):
                    for xkey2, xtype2 in xprvs:
                        self.assertEqual(xkey2, cmds._run('convert_xkey', (xkey1, xtype2)))
        
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_encrypt_decrypt(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_encrypt_decrypt(self, mock_save_db):
                wallet = restore_wallet_from_text('p2wpkh:L4rYY5QpfN6wJEF4SEKDpcGhTPnCe9zcGs6hiSnhpprZqVywFifN',
                                                  path='if_this_exists_mocking_failed_648151893',
                                                  config=self.config)['wallet']
       t@@ -88,8 +88,8 @@ class TestCommands(ElectrumTestCase):
                ciphertext = cmds._run('encrypt', (pubkey, cleartext))
                self.assertEqual(cleartext, cmds._run('decrypt', (pubkey, ciphertext), wallet=wallet))
        
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_export_private_key_imported(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_export_private_key_imported(self, mock_save_db):
                wallet = restore_wallet_from_text('p2wpkh:L4rYY5QpfN6wJEF4SEKDpcGhTPnCe9zcGs6hiSnhpprZqVywFifN p2wpkh:L4jkdiXszG26SUYvwwJhzGwg37H2nLhrbip7u6crmgNeJysv5FHL',
                                                  path='if_this_exists_mocking_failed_648151893',
                                                  config=self.config)['wallet']
       t@@ -107,8 +107,8 @@ class TestCommands(ElectrumTestCase):
                self.assertEqual(['p2wpkh:L4jkdiXszG26SUYvwwJhzGwg37H2nLhrbip7u6crmgNeJysv5FHL', 'p2wpkh:L4rYY5QpfN6wJEF4SEKDpcGhTPnCe9zcGs6hiSnhpprZqVywFifN'],
                                 cmds._run('getprivatekeys', (['bc1q2ccr34wzep58d4239tl3x3734ttle92a8srmuw', 'bc1q9pzjpjq4nqx5ycnywekcmycqz0wjp2nq604y2n'], ), wallet=wallet))
        
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_export_private_key_deterministic(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_export_private_key_deterministic(self, mock_save_db):
                wallet = restore_wallet_from_text('bitter grass shiver impose acquire brush forget axis eager alone wine silver',
                                                  gap_limit=2,
                                                  path='if_this_exists_mocking_failed_648151893',
   DIR diff --git a/electrum/tests/test_lnpeer.py b/electrum/tests/test_lnpeer.py
       t@@ -68,23 +68,13 @@ class MockNetwork:
                if self.tx_queue:
                    await self.tx_queue.put(tx)
        
       -class MockStorage:
       -    def put(self, key, value):
       -        pass
       -
       -    def get(self, key, default=None):
       -        pass
       -
       -    def write(self):
       -        pass
       -
        class MockWallet:
       -    storage = MockStorage()
            def set_label(self, x, y):
                pass
       +    def save_db(self):
       +        pass
        
        class MockLNWallet:
       -    storage = MockStorage()
            def __init__(self, remote_keypair, local_keypair, chan, tx_queue):
                self.chan = chan
                self.remote_keypair = remote_keypair
   DIR diff --git a/electrum/tests/test_storage_upgrade.py b/electrum/tests/test_storage_upgrade.py
       t@@ -1,8 +1,9 @@
        import shutil
        import tempfile
        import os
       +import json
        
       -from electrum.storage import WalletStorage
       +from electrum.wallet_db import WalletDB
        from electrum.wallet import Wallet
        from electrum import constants
        
       t@@ -293,44 +294,33 @@ class TestStorageUpgrade(WalletTestCase):
            def _upgrade_storage(self, wallet_json, accounts=1):
                if accounts == 1:
                    # test manual upgrades
       -            storage = self._load_storage_from_json_string(wallet_json=wallet_json,
       -                                                          path=self.wallet_path,
       -                                                          manual_upgrades=True)
       -            self.assertFalse(storage.requires_split())
       -            if storage.requires_upgrade():
       -                storage.upgrade()
       -                self._sanity_check_upgraded_storage(storage)
       +            db = self._load_db_from_json_string(wallet_json=wallet_json,
       +                                                manual_upgrades=True)
       +            self.assertFalse(db.requires_split())
       +            if db.requires_upgrade():
       +                db.upgrade()
       +                self._sanity_check_upgraded_db(db)
                    # test automatic upgrades
       -            path2 = os.path.join(self.user_dir, "somewallet2")
       -            storage2 = self._load_storage_from_json_string(wallet_json=wallet_json,
       -                                                           path=path2,
       -                                                           manual_upgrades=False)
       -            storage2.write()
       -            self._sanity_check_upgraded_storage(storage2)
       -            # test opening upgraded storages again
       -            s1 = WalletStorage(path2, manual_upgrades=False)
       -            self._sanity_check_upgraded_storage(s1)
       -            s2 = WalletStorage(path2, manual_upgrades=True)
       -            self._sanity_check_upgraded_storage(s2)
       +            db2 = self._load_db_from_json_string(wallet_json=wallet_json,
       +                                                 manual_upgrades=False)
       +            self._sanity_check_upgraded_db(db2)
                else:
       -            storage = self._load_storage_from_json_string(wallet_json=wallet_json,
       -                                                          path=self.wallet_path,
       -                                                          manual_upgrades=True)
       -            self.assertTrue(storage.requires_split())
       -            new_paths = storage.split_accounts()
       -            self.assertEqual(accounts, len(new_paths))
       -            for new_path in new_paths:
       -                new_storage = WalletStorage(new_path, manual_upgrades=False)
       -                self._sanity_check_upgraded_storage(new_storage)
       -
       -    def _sanity_check_upgraded_storage(self, storage):
       -        self.assertFalse(storage.requires_split())
       -        self.assertFalse(storage.requires_upgrade())
       -        w = Wallet(storage, config=self.config)
       +            db = self._load_db_from_json_string(wallet_json=wallet_json,
       +                                                manual_upgrades=True)
       +            self.assertTrue(db.requires_split())
       +            split_data = db.get_split_accounts()
       +            self.assertEqual(accounts, len(split_data))
       +            for item in split_data:
       +                data = json.dumps(item)
       +                new_db = WalletDB(data, manual_upgrades=False)
       +                self._sanity_check_upgraded_db(new_db)
       +
       +    def _sanity_check_upgraded_db(self, db):
       +        self.assertFalse(db.requires_split())
       +        self.assertFalse(db.requires_upgrade())
       +        w = Wallet(db, None, config=self.config)
        
            @staticmethod
       -    def _load_storage_from_json_string(*, wallet_json, path, manual_upgrades):
       -        with open(path, "w") as f:
       -            f.write(wallet_json)
       -        storage = WalletStorage(path, manual_upgrades=manual_upgrades)
       -        return storage
       +    def _load_db_from_json_string(*, wallet_json, manual_upgrades):
       +        db = WalletDB(wallet_json, manual_upgrades=manual_upgrades)
       +        return db
   DIR diff --git a/electrum/tests/test_wallet.py b/electrum/tests/test_wallet.py
       t@@ -58,13 +58,15 @@ class TestWalletStorage(WalletTestCase):
                with open(self.wallet_path, "w") as f:
                    contents = f.write(contents)
        
       -        storage = WalletStorage(self.wallet_path, manual_upgrades=True)
       -        self.assertEqual("b", storage.get("a"))
       -        self.assertEqual("d", storage.get("c"))
       +        storage = WalletStorage(self.wallet_path)
       +        db = WalletDB(storage.read(), manual_upgrades=True)
       +        self.assertEqual("b", db.get("a"))
       +        self.assertEqual("d", db.get("c"))
        
            def test_write_dictionary_to_file(self):
        
                storage = WalletStorage(self.wallet_path)
       +        db = WalletDB('', manual_upgrades=True)
        
                some_dict = {
                    u"a": u"b",
       t@@ -72,8 +74,8 @@ class TestWalletStorage(WalletTestCase):
                    u"seed_version": FINAL_SEED_VERSION}
        
                for key, value in some_dict.items():
       -            storage.put(key, value)
       -        storage.write()
       +            db.put(key, value)
       +        db.write(storage)
        
                with open(self.wallet_path, "r") as f:
                    contents = f.read()
   DIR diff --git a/electrum/tests/test_wallet_vertical.py b/electrum/tests/test_wallet_vertical.py
       t@@ -5,7 +5,7 @@ import tempfile
        from typing import Sequence
        import asyncio
        
       -from electrum import storage, bitcoin, keystore, bip32
       +from electrum import storage, bitcoin, keystore, bip32, wallet
        from electrum import Transaction
        from electrum import SimpleConfig
        from electrum.address_synchronizer import TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCONF_PARENT
       t@@ -46,33 +46,33 @@ class WalletIntegrityHelper:
        
            @classmethod
            def create_standard_wallet(cls, ks, *, config: SimpleConfig, gap_limit=None):
       -        store = storage.WalletStorage('if_this_exists_mocking_failed_648151893')
       -        store.put('keystore', ks.dump())
       -        store.put('gap_limit', gap_limit or cls.gap_limit)
       -        w = Standard_Wallet(store, config=config)
       +        db = storage.WalletDB('', manual_upgrades=False)
       +        db.put('keystore', ks.dump())
       +        db.put('gap_limit', gap_limit or cls.gap_limit)
       +        w = Standard_Wallet(db, None, config=config)
                w.synchronize()
                return w
        
            @classmethod
            def create_imported_wallet(cls, *, config: SimpleConfig, privkeys: bool):
       -        store = storage.WalletStorage('if_this_exists_mocking_failed_648151893')
       +        db = storage.WalletDB('', manual_upgrades=False)
                if privkeys:
                    k = keystore.Imported_KeyStore({})
       -            store.put('keystore', k.dump())
       -        w = Imported_Wallet(store, config=config)
       +            db.put('keystore', k.dump())
       +        w = Imported_Wallet(db, None, config=config)
                return w
        
            @classmethod
            def create_multisig_wallet(cls, keystores: Sequence, multisig_type: str, *,
                                       config: SimpleConfig, gap_limit=None):
                """Creates a multisig wallet."""
       -        store = storage.WalletStorage('if_this_exists_mocking_failed_648151893')
       +        db = storage.WalletDB('', manual_upgrades=True)
                for i, ks in enumerate(keystores):
                    cosigner_index = i + 1
       -            store.put('x%d/' % cosigner_index, ks.dump())
       -        store.put('wallet_type', multisig_type)
       -        store.put('gap_limit', gap_limit or cls.gap_limit)
       -        w = Multisig_Wallet(store, config=config)
       +            db.put('x%d/' % cosigner_index, ks.dump())
       +        db.put('wallet_type', multisig_type)
       +        db.put('gap_limit', gap_limit or cls.gap_limit)
       +        w = Multisig_Wallet(db, None, config=config)
                w.synchronize()
                return w
        
       t@@ -84,8 +84,8 @@ class TestWalletKeystoreAddressIntegrityForMainnet(ElectrumTestCase):
                self.config = SimpleConfig({'electrum_path': self.electrum_path})
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_electrum_seed_standard(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_electrum_seed_standard(self, mock_save_db):
                seed_words = 'cycle rocket west magnet parrot shuffle foot correct salt library feed song'
                self.assertEqual(seed_type(seed_words), 'standard')
        
       t@@ -104,8 +104,8 @@ class TestWalletKeystoreAddressIntegrityForMainnet(ElectrumTestCase):
                self.assertEqual(w.get_change_addresses()[0], '1KSezYMhAJMWqFbVFB2JshYg69UpmEXR4D')
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_electrum_seed_segwit(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_electrum_seed_segwit(self, mock_save_db):
                seed_words = 'bitter grass shiver impose acquire brush forget axis eager alone wine silver'
                self.assertEqual(seed_type(seed_words), 'segwit')
        
       t@@ -124,8 +124,8 @@ class TestWalletKeystoreAddressIntegrityForMainnet(ElectrumTestCase):
                self.assertEqual(w.get_change_addresses()[0], 'bc1qdy94n2q5qcp0kg7v9yzwe6wvfkhnvyzje7nx2p')
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_electrum_seed_segwit_passphrase(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_electrum_seed_segwit_passphrase(self, mock_save_db):
                seed_words = 'bitter grass shiver impose acquire brush forget axis eager alone wine silver'
                self.assertEqual(seed_type(seed_words), 'segwit')
        
       t@@ -144,8 +144,8 @@ class TestWalletKeystoreAddressIntegrityForMainnet(ElectrumTestCase):
                self.assertEqual(w.get_change_addresses()[0], 'bc1qcywwsy87sdp8vz5rfjh3sxdv6rt95kujdqq38g')
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_electrum_seed_old(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_electrum_seed_old(self, mock_save_db):
                seed_words = 'powerful random nobody notice nothing important anyway look away hidden message over'
                self.assertEqual(seed_type(seed_words), 'old')
        
       t@@ -163,8 +163,8 @@ class TestWalletKeystoreAddressIntegrityForMainnet(ElectrumTestCase):
                self.assertEqual(w.get_change_addresses()[0], '1KRW8pH6HFHZh889VDq6fEKvmrsmApwNfe')
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_electrum_seed_2fa_legacy(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_electrum_seed_2fa_legacy(self, mock_save_db):
                seed_words = 'kiss live scene rude gate step hip quarter bunker oxygen motor glove'
                self.assertEqual(seed_type(seed_words), '2fa')
        
       t@@ -198,8 +198,8 @@ class TestWalletKeystoreAddressIntegrityForMainnet(ElectrumTestCase):
                self.assertEqual(w.get_change_addresses()[0], '3PeZEcumRqHSPNN43hd4yskGEBdzXgY8Cy')
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_electrum_seed_2fa_segwit(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_electrum_seed_2fa_segwit(self, mock_save_db):
                seed_words = 'universe topic remind silver february ranch shine worth innocent cattle enhance wise'
                self.assertEqual(seed_type(seed_words), '2fa_segwit')
        
       t@@ -233,8 +233,8 @@ class TestWalletKeystoreAddressIntegrityForMainnet(ElectrumTestCase):
                self.assertEqual(w.get_change_addresses()[0], 'bc1qd4q50nft7kxm9yglfnpup9ed2ukj3tkxp793y0zya8dc9m39jcwq308dxz')
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_bip39_seed_bip44_standard(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_bip39_seed_bip44_standard(self, mock_save_db):
                seed_words = 'treat dwarf wealth gasp brass outside high rent blood crowd make initial'
                self.assertEqual(keystore.bip39_is_checksum_valid(seed_words), (True, True))
        
       t@@ -252,8 +252,8 @@ class TestWalletKeystoreAddressIntegrityForMainnet(ElectrumTestCase):
                self.assertEqual(w.get_change_addresses()[0], '1GG5bVeWgAp5XW7JLCphse14QaC4qiHyWn')
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_bip39_seed_bip44_standard_passphrase(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_bip39_seed_bip44_standard_passphrase(self, mock_save_db):
                seed_words = 'treat dwarf wealth gasp brass outside high rent blood crowd make initial'
                self.assertEqual(keystore.bip39_is_checksum_valid(seed_words), (True, True))
        
       t@@ -271,8 +271,8 @@ class TestWalletKeystoreAddressIntegrityForMainnet(ElectrumTestCase):
                self.assertEqual(w.get_change_addresses()[0], '1H4QD1rg2zQJ4UjuAVJr5eW1fEM8WMqyxh')
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_bip39_seed_bip49_p2sh_segwit(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_bip39_seed_bip49_p2sh_segwit(self, mock_save_db):
                seed_words = 'treat dwarf wealth gasp brass outside high rent blood crowd make initial'
                self.assertEqual(keystore.bip39_is_checksum_valid(seed_words), (True, True))
        
       t@@ -290,8 +290,8 @@ class TestWalletKeystoreAddressIntegrityForMainnet(ElectrumTestCase):
                self.assertEqual(w.get_change_addresses()[0], '3KaBTcviBLEJajTEMstsA2GWjYoPzPK7Y7')
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_bip39_seed_bip84_native_segwit(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_bip39_seed_bip84_native_segwit(self, mock_save_db):
                # test case from bip84
                seed_words = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
                self.assertEqual(keystore.bip39_is_checksum_valid(seed_words), (True, True))
       t@@ -310,8 +310,8 @@ class TestWalletKeystoreAddressIntegrityForMainnet(ElectrumTestCase):
                self.assertEqual(w.get_change_addresses()[0], 'bc1q8c6fshw2dlwun7ekn9qwf37cu2rn755upcp6el')
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_electrum_multisig_seed_standard(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_electrum_multisig_seed_standard(self, mock_save_db):
                seed_words = 'blast uniform dragon fiscal ensure vast young utility dinosaur abandon rookie sure'
                self.assertEqual(seed_type(seed_words), 'standard')
        
       t@@ -333,8 +333,8 @@ class TestWalletKeystoreAddressIntegrityForMainnet(ElectrumTestCase):
                self.assertEqual(w.get_change_addresses()[0], '36XWwEHrrVCLnhjK5MrVVGmUHghr9oWTN1')
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_electrum_multisig_seed_segwit(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_electrum_multisig_seed_segwit(self, mock_save_db):
                seed_words = 'snow nest raise royal more walk demise rotate smooth spirit canyon gun'
                self.assertEqual(seed_type(seed_words), 'segwit')
        
       t@@ -356,8 +356,8 @@ class TestWalletKeystoreAddressIntegrityForMainnet(ElectrumTestCase):
                self.assertEqual(w.get_change_addresses()[0], 'bc1qxqf840dqswcmu7a8v82fj6ej0msx08flvuy6kngr7axstjcaq6us9hrehd')
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_bip39_multisig_seed_bip45_standard(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_bip39_multisig_seed_bip45_standard(self, mock_save_db):
                seed_words = 'treat dwarf wealth gasp brass outside high rent blood crowd make initial'
                self.assertEqual(keystore.bip39_is_checksum_valid(seed_words), (True, True))
        
       t@@ -379,8 +379,8 @@ class TestWalletKeystoreAddressIntegrityForMainnet(ElectrumTestCase):
                self.assertEqual(w.get_change_addresses()[0], '3FGyDuxgUDn2pSZe5xAJH1yUwSdhzDMyEE')
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_bip39_multisig_seed_p2sh_segwit(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_bip39_multisig_seed_p2sh_segwit(self, mock_save_db):
                # bip39 seed: pulse mixture jazz invite dune enrich minor weapon mosquito flight fly vapor
                # der: m/49'/0'/0'
                # NOTE: there is currently no bip43 standard derivation path for p2wsh-p2sh
       t@@ -401,8 +401,8 @@ class TestWalletKeystoreAddressIntegrityForMainnet(ElectrumTestCase):
                self.assertEqual(w.get_change_addresses()[0], '39RhtDchc6igmx5tyoimhojFL1ZbQBrXa6')
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_bip32_extended_version_bytes(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_bip32_extended_version_bytes(self, mock_save_db):
                seed_words = 'crouch dumb relax small truck age shine pink invite spatial object tenant'
                self.assertEqual(keystore.bip39_is_checksum_valid(seed_words), (True, True))
                bip32_seed = keystore.bip39_to_seed(seed_words, '')
       t@@ -467,8 +467,8 @@ class TestWalletKeystoreAddressIntegrityForTestnet(TestCaseForTestnet):
                super().setUp()
                self.config = SimpleConfig({'electrum_path': self.electrum_path})
        
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_bip39_multisig_seed_p2sh_segwit_testnet(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_bip39_multisig_seed_p2sh_segwit_testnet(self, mock_save_db):
                # bip39 seed: finish seminar arrange erosion sunny coil insane together pretty lunch lunch rose
                # der: m/49'/1'/0'
                # NOTE: there is currently no bip43 standard derivation path for p2wsh-p2sh
       t@@ -489,8 +489,8 @@ class TestWalletKeystoreAddressIntegrityForTestnet(TestCaseForTestnet):
                self.assertEqual(w.get_change_addresses()[0], '2NFp9w8tbYYP9Ze2xQpeYBJQjx3gbXymHX7')
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_bip32_extended_version_bytes(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_bip32_extended_version_bytes(self, mock_save_db):
                seed_words = 'crouch dumb relax small truck age shine pink invite spatial object tenant'
                self.assertEqual(keystore.bip39_is_checksum_valid(seed_words), (True, True))
                bip32_seed = keystore.bip39_to_seed(seed_words, '')
       t@@ -560,8 +560,8 @@ class TestWalletSending(TestCaseForTestnet):
                return WalletIntegrityHelper.create_standard_wallet(ks, gap_limit=2, config=self.config)
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_sending_between_p2wpkh_and_compressed_p2pkh(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_sending_between_p2wpkh_and_compressed_p2pkh(self, mock_save_db):
                wallet1 = self.create_standard_wallet_from_seed('bitter grass shiver impose acquire brush forget axis eager alone wine silver')
                wallet2 = self.create_standard_wallet_from_seed('cycle rocket west magnet parrot shuffle foot correct salt library feed song')
        
       t@@ -617,8 +617,8 @@ class TestWalletSending(TestCaseForTestnet):
                self.assertEqual((0, 250000 - 5000 - 100000, 0), wallet2.get_balance())
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_sending_between_p2sh_2of3_and_uncompressed_p2pkh(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_sending_between_p2sh_2of3_and_uncompressed_p2pkh(self, mock_save_db):
                wallet1a = WalletIntegrityHelper.create_multisig_wallet(
                    [
                        keystore.from_seed('blast uniform dragon fiscal ensure vast young utility dinosaur abandon rookie sure', '', True),
       t@@ -698,8 +698,8 @@ class TestWalletSending(TestCaseForTestnet):
                self.assertEqual((0, 370000 - 5000 - 100000, 0), wallet2.get_balance())
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_sending_between_p2wsh_2of3_and_p2wsh_p2sh_2of2(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_sending_between_p2wsh_2of3_and_p2wsh_p2sh_2of2(self, mock_save_db):
                wallet1a = WalletIntegrityHelper.create_multisig_wallet(
                    [
                        keystore.from_seed('bitter grass shiver impose acquire brush forget axis eager alone wine silver', '', True),
       t@@ -808,8 +808,8 @@ class TestWalletSending(TestCaseForTestnet):
                self.assertEqual((0, 165000 - 5000 - 100000, 0), wallet2a.get_balance())
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_sending_between_p2sh_1of2_and_p2wpkh_p2sh(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_sending_between_p2sh_1of2_and_p2wpkh_p2sh(self, mock_save_db):
                wallet1a = WalletIntegrityHelper.create_multisig_wallet(
                    [
                        keystore.from_seed('phone guilt ancient scan defy gasp off rotate approve ill word exchange', '', True),
       t@@ -878,8 +878,8 @@ class TestWalletSending(TestCaseForTestnet):
                self.assertEqual((0, 1000000 - 5000 - 300000, 0), wallet2.get_balance())
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_rbf(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_rbf(self, mock_save_db):
                self.maxDiff = None
                for simulate_moving_txs in (False, True):
                    with self.subTest(msg="_bump_fee_p2pkh_when_there_is_a_change_address", simulate_moving_txs=simulate_moving_txs):
       t@@ -959,8 +959,8 @@ class TestWalletSending(TestCaseForTestnet):
                self.assertEqual((0, 7484320, 0), wallet.get_balance())
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_cpfp_p2pkh(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_cpfp_p2pkh(self, mock_save_db):
                wallet = self.create_standard_wallet_from_seed('fold object utility erase deputy output stadium feed stereo usage modify bean')
        
                # bootstrap wallet
       t@@ -1361,8 +1361,8 @@ class TestWalletSending(TestCaseForTestnet):
                self.assertEqual((0, 3_900_000, 0), wallet.get_balance())
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_cpfp_p2wpkh(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_cpfp_p2wpkh(self, mock_save_db):
                wallet = self.create_standard_wallet_from_seed('frost repair depend effort salon ring foam oak cancel receive save usage')
        
                # bootstrap wallet
       t@@ -1420,8 +1420,8 @@ class TestWalletSending(TestCaseForTestnet):
                self.assertEqual('7f827fc5256c274fd1094eb7e020c8ded0baf820356f61aa4f14a9093b0ea0ee', tx_copy.wtxid())
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_coinjoin_between_two_p2wpkh_electrum_seeds(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_coinjoin_between_two_p2wpkh_electrum_seeds(self, mock_save_db):
                wallet1 = WalletIntegrityHelper.create_standard_wallet(
                    keystore.from_seed('humor argue expand gain goat shiver remove morning security casual leopard degree', ''),
                    gap_limit=2,
       t@@ -1512,8 +1512,8 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
                self.config = SimpleConfig({'electrum_path': self.electrum_path})
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_sending_offline_old_electrum_seed_online_mpk(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_sending_offline_old_electrum_seed_online_mpk(self, mock_save_db):
                wallet_offline = WalletIntegrityHelper.create_standard_wallet(
                    keystore.from_seed('alone body father children lead goodbye phone twist exist grass kick join', '', False),
                    gap_limit=4,
       t@@ -1559,8 +1559,8 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
                self.assertEqual('06032230d0bf6a277bc4f8c39e3311a712e0e614626d0dea7cc9f592abfae5d8', tx.wtxid())
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_sending_offline_xprv_online_xpub_p2pkh(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_sending_offline_xprv_online_xpub_p2pkh(self, mock_save_db):
                wallet_offline = WalletIntegrityHelper.create_standard_wallet(
                    # bip39: "qwe", der: m/44'/1'/0'
                    keystore.from_xprv('tprv8gfKwjuAaqtHgqxMh1tosAQ28XvBMkcY5NeFRA3pZMpz6MR4H4YZ3MJM4fvNPnRKeXR1Td2vQGgjorNXfo94WvT5CYDsPAqjHxSn436G1Eu'),
       t@@ -1605,8 +1605,8 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
                self.assertEqual('d9c21696eca80321933e7444ca928aaf25eeda81aaa2f4e5c085d4d0a9cf7aa7', tx.wtxid())
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_sending_offline_xprv_online_xpub_p2wpkh_p2sh(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_sending_offline_xprv_online_xpub_p2wpkh_p2sh(self, mock_save_db):
                wallet_offline = WalletIntegrityHelper.create_standard_wallet(
                    # bip39: "qwe", der: m/49'/1'/0'
                    keystore.from_xprv('uprv8zHHrMQMQ26utWwNJ5MK2SXpB9hbmy7pbPaneii69xT8cZTyFpxQFxkknGWKP8dxBTZhzy7yP6cCnLrRCQjzJDk3G61SjZpxhFQuB2NR8a5'),
       t@@ -1652,8 +1652,8 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
                self.assertEqual('27b78ec072a403b0545258e7a1a8d494e4b6fd48bf77f4251a12160c92207cbc', tx.wtxid())
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_sending_offline_xprv_online_xpub_p2wpkh(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_sending_offline_xprv_online_xpub_p2wpkh(self, mock_save_db):
                wallet_offline = WalletIntegrityHelper.create_standard_wallet(
                    # bip39: "qwe", der: m/84'/1'/0'
                    keystore.from_xprv('vprv9K9hbuA23Bidgj1KRSHUZMa59jJLeZBpXPVn4RP7sBLArNhZxJjw4AX7aQmVTErDt4YFC11ptMLjbwxgrsH8GLQ1cx77KggWeVPeDBjr9xM'),
       t@@ -1699,8 +1699,8 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
                self.assertEqual('484e350beaa722a744bb3e2aa38de005baa8526d86536d6143e5814355acf775', tx.wtxid())
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_offline_signing_beyond_gap_limit(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_offline_signing_beyond_gap_limit(self, mock_save_db):
                wallet_offline = WalletIntegrityHelper.create_standard_wallet(
                    # bip39: "qwe", der: m/84'/1'/0'
                    keystore.from_xprv('vprv9K9hbuA23Bidgj1KRSHUZMa59jJLeZBpXPVn4RP7sBLArNhZxJjw4AX7aQmVTErDt4YFC11ptMLjbwxgrsH8GLQ1cx77KggWeVPeDBjr9xM'),
       t@@ -1746,8 +1746,8 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
                self.assertEqual('484e350beaa722a744bb3e2aa38de005baa8526d86536d6143e5814355acf775', tx.wtxid())
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_sending_offline_wif_online_addr_p2pkh(self, mock_write):  # compressed pubkey
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_sending_offline_wif_online_addr_p2pkh(self, mock_save_db):  # compressed pubkey
                wallet_offline = WalletIntegrityHelper.create_imported_wallet(privkeys=True, config=self.config)
                wallet_offline.import_private_key('p2pkh:cQDxbmQfwRV3vP1mdnVHq37nJekHLsuD3wdSQseBRA2ct4MFk5Pq', password=None)
                wallet_online = WalletIntegrityHelper.create_imported_wallet(privkeys=False, config=self.config)
       t@@ -1785,8 +1785,8 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
                self.assertEqual('e56da664631b8c666c6df38ec80c954c4ac3c4f56f040faf0070e4681e937fc4', tx.wtxid())
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_sending_offline_wif_online_addr_p2wpkh_p2sh(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_sending_offline_wif_online_addr_p2wpkh_p2sh(self, mock_save_db):
                wallet_offline = WalletIntegrityHelper.create_imported_wallet(privkeys=True, config=self.config)
                wallet_offline.import_private_key('p2wpkh-p2sh:cU9hVzhpvfn91u2zTVn8uqF2ymS7ucYH8V5TmsTDmuyMHgRk9WsJ', password=None)
                wallet_online = WalletIntegrityHelper.create_imported_wallet(privkeys=False, config=self.config)
       t@@ -1824,8 +1824,8 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
                self.assertEqual('9bb9949974954613945756c48ca5525cd5cba1b667ccb10c7a53e1ed076a1117', tx.wtxid())
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_sending_offline_wif_online_addr_p2wpkh(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_sending_offline_wif_online_addr_p2wpkh(self, mock_save_db):
                wallet_offline = WalletIntegrityHelper.create_imported_wallet(privkeys=True, config=self.config)
                wallet_offline.import_private_key('p2wpkh:cPuQzcNEgbeYZ5at9VdGkCwkPA9r34gvEVJjuoz384rTfYpahfe7', password=None)
                wallet_online = WalletIntegrityHelper.create_imported_wallet(privkeys=False, config=self.config)
       t@@ -1863,8 +1863,8 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
                self.assertEqual('3b7cc3c3352bbb43ddc086487ac696e09f2863c3d9e8636721851b8008a83ffa', tx.wtxid())
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_sending_offline_xprv_online_addr_p2pkh(self, mock_write):  # compressed pubkey
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_sending_offline_xprv_online_addr_p2pkh(self, mock_save_db):  # compressed pubkey
                wallet_offline = WalletIntegrityHelper.create_standard_wallet(
                    # bip39: "qwe", der: m/44'/1'/0'
                    keystore.from_xprv('tprv8gfKwjuAaqtHgqxMh1tosAQ28XvBMkcY5NeFRA3pZMpz6MR4H4YZ3MJM4fvNPnRKeXR1Td2vQGgjorNXfo94WvT5CYDsPAqjHxSn436G1Eu'),
       t@@ -1906,8 +1906,8 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
                self.assertEqual('e56da664631b8c666c6df38ec80c954c4ac3c4f56f040faf0070e4681e937fc4', tx.wtxid())
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_sending_offline_xprv_online_addr_p2wpkh_p2sh(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_sending_offline_xprv_online_addr_p2wpkh_p2sh(self, mock_save_db):
                wallet_offline = WalletIntegrityHelper.create_standard_wallet(
                    # bip39: "qwe", der: m/49'/1'/0'
                    keystore.from_xprv('uprv8zHHrMQMQ26utWwNJ5MK2SXpB9hbmy7pbPaneii69xT8cZTyFpxQFxkknGWKP8dxBTZhzy7yP6cCnLrRCQjzJDk3G61SjZpxhFQuB2NR8a5'),
       t@@ -1949,8 +1949,8 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
                self.assertEqual('9bb9949974954613945756c48ca5525cd5cba1b667ccb10c7a53e1ed076a1117', tx.wtxid())
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_sending_offline_xprv_online_addr_p2wpkh(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_sending_offline_xprv_online_addr_p2wpkh(self, mock_save_db):
                wallet_offline = WalletIntegrityHelper.create_standard_wallet(
                    # bip39: "qwe", der: m/84'/1'/0'
                    keystore.from_xprv('vprv9K9hbuA23Bidgj1KRSHUZMa59jJLeZBpXPVn4RP7sBLArNhZxJjw4AX7aQmVTErDt4YFC11ptMLjbwxgrsH8GLQ1cx77KggWeVPeDBjr9xM'),
       t@@ -1992,8 +1992,8 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
                self.assertEqual('3b7cc3c3352bbb43ddc086487ac696e09f2863c3d9e8636721851b8008a83ffa', tx.wtxid())
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_sending_offline_hd_multisig_online_addr_p2sh(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_sending_offline_hd_multisig_online_addr_p2sh(self, mock_save_db):
                # 2-of-3 legacy p2sh multisig
                wallet_offline1 = WalletIntegrityHelper.create_multisig_wallet(
                    [
       t@@ -2059,8 +2059,8 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
                self.assertEqual('0e8fdc8257a85ebe7eeab14a53c2c258c61a511f64176b7f8fc016bc2263d307', tx.wtxid())
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_sending_offline_hd_multisig_online_addr_p2wsh_p2sh(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_sending_offline_hd_multisig_online_addr_p2wsh_p2sh(self, mock_save_db):
                # 2-of-2 p2sh-embedded segwit multisig
                wallet_offline1 = WalletIntegrityHelper.create_multisig_wallet(
                    [
       t@@ -2130,8 +2130,8 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
                self.assertEqual('96d0bca1001778c54e4c3a07929fab5562c5b5a23fd1ca3aa3870cc5df2bf97d', tx.wtxid())
        
            @needs_test_with_all_ecc_implementations
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_sending_offline_hd_multisig_online_addr_p2wsh(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_sending_offline_hd_multisig_online_addr_p2wsh(self, mock_save_db):
                # 2-of-3 p2wsh multisig
                wallet_offline1 = WalletIntegrityHelper.create_multisig_wallet(
                    [
       t@@ -2235,24 +2235,24 @@ class TestWalletHistory_SimpleRandomOrder(TestCaseForTestnet):
                w.create_new_address(for_change=True)
                return w
        
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_restoring_old_wallet_txorder1(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_restoring_old_wallet_txorder1(self, mock_save_db):
                w = self.create_old_wallet()
                for i in [2, 12, 7, 9, 11, 10, 16, 6, 17, 1, 13, 15, 5, 8, 4, 0, 14, 18, 3]:
                    tx = Transaction(self.transactions[self.txid_list[i]])
                    w.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
                self.assertEqual(27633300, sum(w.get_balance()))
        
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_restoring_old_wallet_txorder2(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_restoring_old_wallet_txorder2(self, mock_save_db):
                w = self.create_old_wallet()
                for i in [9, 18, 2, 0, 13, 3, 1, 11, 4, 17, 7, 14, 12, 15, 10, 8, 5, 6, 16]:
                    tx = Transaction(self.transactions[self.txid_list[i]])
                    w.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
                self.assertEqual(27633300, sum(w.get_balance()))
        
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_restoring_old_wallet_txorder3(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_restoring_old_wallet_txorder3(self, mock_save_db):
                w = self.create_old_wallet()
                for i in [5, 8, 17, 0, 9, 10, 12, 3, 15, 18, 2, 11, 14, 7, 16, 1, 4, 6, 13]:
                    tx = Transaction(self.transactions[self.txid_list[i]])
       t@@ -2283,10 +2283,10 @@ class TestWalletHistory_EvilGapLimit(TestCaseForTestnet):
                w = WalletIntegrityHelper.create_standard_wallet(ks, gap_limit=20, config=self.config)
                return w
        
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_restoring_wallet_txorder1(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_restoring_wallet_txorder1(self, mock_save_db):
                w = self.create_wallet()
       -        w.storage.put('stored_height', 1316917 + 100)
       +        w.db.put('stored_height', 1316917 + 100)
                for txid in self.transactions:
                    tx = Transaction(self.transactions[txid])
                    w.add_transaction(tx)
       t@@ -2331,8 +2331,8 @@ class TestWalletHistory_DoubleSpend(TestCaseForTestnet):
                super().setUp()
                self.config = SimpleConfig({'electrum_path': self.electrum_path})
        
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_restoring_wallet_without_manual_delete(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_restoring_wallet_without_manual_delete(self, mock_save_db):
                w = restore_wallet_from_text("small rapid pattern language comic denial donate extend tide fever burden barrel",
                                             path='if_this_exists_mocking_failed_648151893',
                                             gap_limit=5,
       t@@ -2345,8 +2345,8 @@ class TestWalletHistory_DoubleSpend(TestCaseForTestnet):
                # txn C is double-spending txn B, to a wallet address
                self.assertEqual(999890, sum(w.get_balance()))
        
       -    @mock.patch.object(storage.WalletStorage, '_write')
       -    def test_restoring_wallet_with_manual_delete(self, mock_write):
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_restoring_wallet_with_manual_delete(self, mock_save_db):
                w = restore_wallet_from_text("small rapid pattern language comic denial donate extend tide fever burden barrel",
                                             path='if_this_exists_mocking_failed_648151893',
                                             gap_limit=5,
   DIR diff --git a/electrum/wallet.py b/electrum/wallet.py
       t@@ -60,6 +60,7 @@ from . import keystore
        from .keystore import load_keystore, Hardware_KeyStore, KeyStore, KeyStoreWithMPK, AddressIndexGeneric
        from .util import multisig_type
        from .storage import StorageEncryptionVersion, WalletStorage
       +from .wallet_db import WalletDB
        from . import transaction, bitcoin, coinchooser, paymentrequest, ecc, bip32
        from .transaction import (Transaction, TxInput, UnknownTxinType, TxOutput,
                                  PartialTransaction, PartialTxInput, PartialTxOutput, TxOutpoint)
       t@@ -225,44 +226,49 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
            txin_type: str
            wallet_type: str
        
       -    def __init__(self, storage: WalletStorage, *, config: SimpleConfig):
       -        if not storage.is_ready_to_be_used_by_wallet():
       +    def __init__(self, db: WalletDB, storage: Optional[WalletStorage], *, config: SimpleConfig):
       +        if not db.is_ready_to_be_used_by_wallet():
                    raise Exception("storage not ready to be used by Abstract_Wallet")
        
                self.config = config
                assert self.config is not None, "config must not be None"
       +        self.db = db
                self.storage = storage
                # load addresses needs to be called before constructor for sanity checks
       -        self.storage.db.load_addresses(self.wallet_type)
       +        db.load_addresses(self.wallet_type)
                self.keystore = None  # type: Optional[KeyStore]  # will be set by load_keystore
       -        AddressSynchronizer.__init__(self, storage.db)
       +        AddressSynchronizer.__init__(self, db)
        
                # saved fields
       -        self.use_change            = storage.get('use_change', True)
       -        self.multiple_change       = storage.get('multiple_change', False)
       -        self.labels                = storage.db.get_dict('labels')
       -        self.frozen_addresses      = set(storage.get('frozen_addresses', []))
       -        self.frozen_coins          = set(storage.get('frozen_coins', []))  # set of txid:vout strings
       -        self.fiat_value            = storage.db.get_dict('fiat_value')
       -        self.receive_requests      = storage.db.get_dict('payment_requests')
       -        self.invoices              = storage.db.get_dict('invoices')
       +        self.use_change            = db.get('use_change', True)
       +        self.multiple_change       = db.get('multiple_change', False)
       +        self.labels                = db.get_dict('labels')
       +        self.frozen_addresses      = set(db.get('frozen_addresses', []))
       +        self.frozen_coins          = set(db.get('frozen_coins', []))  # set of txid:vout strings
       +        self.fiat_value            = db.get_dict('fiat_value')
       +        self.receive_requests      = db.get_dict('payment_requests')
       +        self.invoices              = db.get_dict('invoices')
        
                self._prepare_onchain_invoice_paid_detection()
                self.calc_unused_change_addresses()
                # save wallet type the first time
       -        if self.storage.get('wallet_type') is None:
       -            self.storage.put('wallet_type', self.wallet_type)
       -        self.contacts = Contacts(self.storage)
       +        if self.db.get('wallet_type') is None:
       +            self.db.put('wallet_type', self.wallet_type)
       +        self.contacts = Contacts(self.db)
                self._coin_price_cache = {}
                # lightning
       -        ln_xprv = self.storage.get('lightning_privkey2')
       +        ln_xprv = self.db.get('lightning_privkey2')
                self.lnworker = LNWallet(self, ln_xprv) if ln_xprv else None
        
       +    def save_db(self):
       +        if self.storage:
       +            self.db.write(self.storage)
       +
            def has_lightning(self):
                return bool(self.lnworker)
        
            def init_lightning(self):
       -        if self.storage.get('lightning_privkey2'):
       +        if self.db.get('lightning_privkey2'):
                    return
                if not is_using_fast_ecc():
                    raise Exception('libsecp256k1 library not available. '
       t@@ -272,30 +278,30 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
                seed = os.urandom(32)
                node = BIP32Node.from_rootseed(seed, xtype='standard')
                ln_xprv = node.to_xprv()
       -        self.storage.put('lightning_privkey2', ln_xprv)
       -        self.storage.write()
       +        self.db.put('lightning_privkey2', ln_xprv)
       +        self.save_db()
        
            def remove_lightning(self):
       -        if not self.storage.get('lightning_privkey2'):
       +        if not self.db.get('lightning_privkey2'):
                    return
                if bool(self.lnworker.channels):
                    raise Exception('Error: This wallet has channels')
       -        self.storage.put('lightning_privkey2', None)
       -        self.storage.write()
       +        self.db.put('lightning_privkey2', None)
       +        self.save_db()
        
            def stop_threads(self):
                super().stop_threads()
                if any([ks.is_requesting_to_be_rewritten_to_wallet_file for ks in self.get_keystores()]):
                    self.save_keystore()
       -        self.storage.write()
       +        self.save_db()
        
            def set_up_to_date(self, b):
                super().set_up_to_date(b)
       -        if b: self.storage.write()
       +        if b: self.save_db()
        
            def clear_history(self):
                super().clear_history()
       -        self.storage.write()
       +        self.save_db()
        
            def start_network(self, network):
                AddressSynchronizer.start_network(self, network)
       t@@ -325,7 +331,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
                return []
        
            def basename(self) -> str:
       -        return self.storage.basename()
       +        return self.storage.basename() if self.storage else 'no name'
        
            def test_addresses_sanity(self) -> None:
                addrs = self.get_receiving_addresses()
       t@@ -615,11 +621,11 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
                else:
                    raise Exception('Unsupported invoice type')
                self.invoices[key] = invoice
       -        self.storage.write()
       +        self.save_db()
        
            def clear_invoices(self):
                self.invoices = {}
       -        self.storage.write()
       +        self.save_db()
        
            def get_invoices(self):
                out = [self.get_invoice(key) for key in self.invoices.keys()]
       t@@ -1094,7 +1100,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
                        self.frozen_addresses |= set(addrs)
                    else:
                        self.frozen_addresses -= set(addrs)
       -            self.storage.put('frozen_addresses', list(self.frozen_addresses))
       +            self.db.put('frozen_addresses', list(self.frozen_addresses))
                    return True
                return False
        
       t@@ -1106,7 +1112,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
                    self.frozen_coins |= set(utxos)
                else:
                    self.frozen_coins -= set(utxos)
       -        self.storage.put('frozen_coins', list(self.frozen_coins))
       +        self.db.put('frozen_coins', list(self.frozen_coins))
        
            def wait_until_synchronized(self, callback=None):
                def wait_for_wallet():
       t@@ -1683,12 +1689,12 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
                If True, e.g. signing a transaction will require a password.
                """
                if self.can_have_keystore_encryption():
       -            return self.storage.get('use_encryption', False)
       +            return self.db.get('use_encryption', False)
                return False
        
            def has_storage_encryption(self):
                """Returns whether encryption is enabled for the wallet file on disk."""
       -        return self.storage.is_encrypted()
       +        return self.storage and self.storage.is_encrypted()
        
            @classmethod
            def may_have_password(cls):
       t@@ -1697,18 +1703,21 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
            def check_password(self, password):
                if self.has_keystore_encryption():
                    self.keystore.check_password(password)
       -        self.storage.check_password(password)
       +        if self.has_storage_encryption():
       +            self.storage.check_password(password)
        
            def update_password(self, old_pw, new_pw, *, encrypt_storage: bool = True):
                if old_pw is None and self.has_password():
                    raise InvalidPassword()
                self.check_password(old_pw)
       -
       -        if encrypt_storage:
       -            enc_version = self.get_available_storage_encryption_version()
       -        else:
       -            enc_version = StorageEncryptionVersion.PLAINTEXT
       -        self.storage.set_password(new_pw, enc_version)
       +        if self.storage:
       +            if encrypt_storage:
       +                enc_version = self.get_available_storage_encryption_version()
       +            else:
       +                enc_version = StorageEncryptionVersion.PLAINTEXT
       +            self.storage.set_password(new_pw, enc_version)
       +        # make sure next storage.write() saves changes
       +        self.db.set_modified(True)
        
                # note: Encrypting storage with a hw device is currently only
                #       allowed for non-multisig wallets. Further,
       t@@ -1717,8 +1726,8 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
                #       extra care would need to be taken when encrypting keystores.
                self._update_password_for_keystore(old_pw, new_pw)
                encrypt_keystore = self.can_have_keystore_encryption()
       -        self.storage.set_keystore_encryption(bool(new_pw) and encrypt_keystore)
       -        self.storage.write()
       +        self.db.set_keystore_encryption(bool(new_pw) and encrypt_keystore)
       +        self.save_db()
        
            @abstractmethod
            def _update_password_for_keystore(self, old_pw: Optional[str], new_pw: Optional[str]) -> None:
       t@@ -1840,7 +1849,7 @@ class Simple_Wallet(Abstract_Wallet):
                    self.save_keystore()
        
            def save_keystore(self):
       -        self.storage.put('keystore', self.keystore.dump())
       +        self.db.put('keystore', self.keystore.dump())
        
            @abstractmethod
            def get_public_key(self, address: str) -> Optional[str]:
       t@@ -1870,8 +1879,8 @@ class Imported_Wallet(Simple_Wallet):
            wallet_type = 'imported'
            txin_type = 'address'
        
       -    def __init__(self, storage, *, config):
       -        Abstract_Wallet.__init__(self, storage, config=config)
       +    def __init__(self, db, storage, *, config):
       +        Abstract_Wallet.__init__(self, db, storage, config=config)
        
            def is_watching_only(self):
                return self.keystore is None
       t@@ -1880,10 +1889,10 @@ class Imported_Wallet(Simple_Wallet):
                return bool(self.keystore)
        
            def load_keystore(self):
       -        self.keystore = load_keystore(self.storage, 'keystore') if self.storage.get('keystore') else None
       +        self.keystore = load_keystore(self.db, 'keystore') if self.db.get('keystore') else None
        
            def save_keystore(self):
       -        self.storage.put('keystore', self.keystore.dump())
       +        self.db.put('keystore', self.keystore.dump())
        
            def can_import_address(self):
                return self.is_watching_only()
       t@@ -1931,7 +1940,7 @@ class Imported_Wallet(Simple_Wallet):
                    self.db.add_imported_address(address, {})
                    self.add_address(address)
                if write_to_disk:
       -            self.storage.write()
       +            self.save_db()
                return good_addr, bad_addr
        
            def import_address(self, address: str) -> str:
       t@@ -1977,7 +1986,7 @@ class Imported_Wallet(Simple_Wallet):
                    else:
                        self.keystore.delete_imported_key(pubkey)
                        self.save_keystore()
       -        self.storage.write()
       +        self.save_db()
        
            def is_mine(self, address) -> bool:
                return self.db.has_imported_address(address)
       t@@ -2009,7 +2018,7 @@ class Imported_Wallet(Simple_Wallet):
                    self.add_address(addr)
                self.save_keystore()
                if write_to_disk:
       -            self.storage.write()
       +            self.save_db()
                return good_addr, bad_keys
        
            def import_private_key(self, key: str, password: Optional[str]) -> str:
       t@@ -2050,10 +2059,10 @@ class Imported_Wallet(Simple_Wallet):
        
        class Deterministic_Wallet(Abstract_Wallet):
        
       -    def __init__(self, storage, *, config):
       +    def __init__(self, db, storage, *, config):
                self._ephemeral_addr_to_addr_index = {}  # type: Dict[str, Sequence[int]]
       -        Abstract_Wallet.__init__(self, storage, config=config)
       -        self.gap_limit = storage.get('gap_limit', 20)
       +        Abstract_Wallet.__init__(self, db, storage, config=config)
       +        self.gap_limit = db.get('gap_limit', 20)
                # generate addresses now. note that without libsecp this might block
                # for a few seconds!
                self.synchronize()
       t@@ -2100,8 +2109,8 @@ class Deterministic_Wallet(Abstract_Wallet):
                '''This method is not called in the code, it is kept for console use'''
                if value >= self.min_acceptable_gap():
                    self.gap_limit = value
       -            self.storage.put('gap_limit', self.gap_limit)
       -            self.storage.write()
       +            self.db.put('gap_limit', self.gap_limit)
       +            self.save_db()
                    return True
                else:
                    return False
       t@@ -2232,8 +2241,8 @@ class Simple_Deterministic_Wallet(Simple_Wallet, Deterministic_Wallet):
        
            """ Deterministic Wallet with a single pubkey per address """
        
       -    def __init__(self, storage, *, config):
       -        Deterministic_Wallet.__init__(self, storage, config=config)
       +    def __init__(self, db, storage, *, config):
       +        Deterministic_Wallet.__init__(self, db, storage, config=config)
        
            def get_public_key(self, address):
                sequence = self.get_address_index(address)
       t@@ -2241,7 +2250,7 @@ class Simple_Deterministic_Wallet(Simple_Wallet, Deterministic_Wallet):
                return pubkeys[0]
        
            def load_keystore(self):
       -        self.keystore = load_keystore(self.storage, 'keystore')
       +        self.keystore = load_keystore(self.db, 'keystore')
                try:
                    xtype = bip32.xpub_type(self.keystore.xpub)
                except:
       t@@ -2270,10 +2279,10 @@ class Standard_Wallet(Simple_Deterministic_Wallet):
        class Multisig_Wallet(Deterministic_Wallet):
            # generic m of n
        
       -    def __init__(self, storage, *, config):
       -        self.wallet_type = storage.get('wallet_type')
       +    def __init__(self, db, storage, *, config):
       +        self.wallet_type = db.get('wallet_type')
                self.m, self.n = multisig_type(self.wallet_type)
       -        Deterministic_Wallet.__init__(self, storage, config=config)
       +        Deterministic_Wallet.__init__(self, db, storage, config=config)
        
            def get_public_keys(self, address):
                return [pk.hex() for pk in self.get_public_keys_with_deriv_info(address)]
       t@@ -2314,14 +2323,14 @@ class Multisig_Wallet(Deterministic_Wallet):
                self.keystores = {}
                for i in range(self.n):
                    name = 'x%d/'%(i+1)
       -            self.keystores[name] = load_keystore(self.storage, name)
       +            self.keystores[name] = load_keystore(self.db, name)
                self.keystore = self.keystores['x1/']
                xtype = bip32.xpub_type(self.keystore.xpub)
                self.txin_type = 'p2sh' if xtype == 'standard' else xtype
        
            def save_keystore(self):
                for name, k in self.keystores.items():
       -            self.storage.put(name, k.dump())
       +            self.db.put(name, k.dump())
        
            def get_keystore(self):
                return self.keystores.get('x1/')
       t@@ -2336,13 +2345,14 @@ class Multisig_Wallet(Deterministic_Wallet):
                for name, keystore in self.keystores.items():
                    if keystore.may_have_password():
                        keystore.update_password(old_pw, new_pw)
       -                self.storage.put(name, keystore.dump())
       +                self.db.put(name, keystore.dump())
        
            def check_password(self, password):
                for name, keystore in self.keystores.items():
                    if keystore.may_have_password():
                        keystore.check_password(password)
       -        self.storage.check_password(password)
       +        if self.has_storage_encryption():
       +            self.storage.check_password(password)
        
            def get_available_storage_encryption_version(self):
                # multisig wallets are not offered hw device encryption
       t@@ -2385,10 +2395,10 @@ class Wallet(object):
            This class is actually a factory that will return a wallet of the correct
            type when passed a WalletStorage instance."""
        
       -    def __new__(self, storage: WalletStorage, *, config: SimpleConfig):
       -        wallet_type = storage.get('wallet_type')
       +    def __new__(self, db, storage: WalletStorage, *, config: SimpleConfig):
       +        wallet_type = db.get('wallet_type')
                WalletClass = Wallet.wallet_class(wallet_type)
       -        wallet = WalletClass(storage, config=config)
       +        wallet = WalletClass(db, storage, config=config)
                return wallet
        
            @staticmethod
       t@@ -2406,19 +2416,20 @@ def create_new_wallet(*, path, config: SimpleConfig, passphrase=None, password=N
            storage = WalletStorage(path)
            if storage.file_exists():
                raise Exception("Remove the existing wallet first!")
       +    db = WalletDB('', manual_upgrades=False)
        
            seed = Mnemonic('en').make_seed(seed_type)
            k = keystore.from_seed(seed, passphrase)
       -    storage.put('keystore', k.dump())
       -    storage.put('wallet_type', 'standard')
       +    db.put('keystore', k.dump())
       +    db.put('wallet_type', 'standard')
            if gap_limit is not None:
       -        storage.put('gap_limit', gap_limit)
       -    wallet = Wallet(storage, config=config)
       +        db.put('gap_limit', gap_limit)
       +    wallet = Wallet(db, storage, config=config)
            wallet.update_password(old_pw=None, new_pw=password, encrypt_storage=encrypt_file)
            wallet.synchronize()
            msg = "Please keep your seed in a safe place; if you lose it, you will not be able to restore your wallet."
        
       -    wallet.storage.write()
       +    wallet.save_db()
            return {'seed': seed, 'wallet': wallet, 'msg': msg}
        
        
       t@@ -2431,10 +2442,10 @@ def restore_wallet_from_text(text, *, path, config: SimpleConfig,
            storage = WalletStorage(path)
            if storage.file_exists():
                raise Exception("Remove the existing wallet first!")
       -
       +    db = WalletDB('', manual_upgrades=False)
            text = text.strip()
            if keystore.is_address_list(text):
       -        wallet = Imported_Wallet(storage, config=config)
       +        wallet = Imported_Wallet(db, storage, config=config)
                addresses = text.split()
                good_inputs, bad_inputs = wallet.import_addresses(addresses, write_to_disk=False)
                # FIXME tell user about bad_inputs
       t@@ -2442,8 +2453,8 @@ def restore_wallet_from_text(text, *, path, config: SimpleConfig,
                    raise Exception("None of the given addresses can be imported")
            elif keystore.is_private_key_list(text, allow_spaces_inside_key=False):
                k = keystore.Imported_KeyStore({})
       -        storage.put('keystore', k.dump())
       -        wallet = Imported_Wallet(storage, config=config)
       +        db.put('keystore', k.dump())
       +        wallet = Imported_Wallet(db, storage, config=config)
                keys = keystore.get_private_keys(text, allow_spaces_inside_key=False)
                good_inputs, bad_inputs = wallet.import_private_keys(keys, None, write_to_disk=False)
                # FIXME tell user about bad_inputs
       t@@ -2456,11 +2467,11 @@ def restore_wallet_from_text(text, *, path, config: SimpleConfig,
                    k = keystore.from_seed(text, passphrase)
                else:
                    raise Exception("Seed or key not recognized")
       -        storage.put('keystore', k.dump())
       -        storage.put('wallet_type', 'standard')
       +        db.put('keystore', k.dump())
       +        db.put('wallet_type', 'standard')
                if gap_limit is not None:
       -            storage.put('gap_limit', gap_limit)
       -        wallet = Wallet(storage, config=config)
       +            db.put('gap_limit', gap_limit)
       +        wallet = Wallet(db, storage, config=config)
        
            assert not storage.file_exists(), "file was created too soon! plaintext keys might have been written to disk"
            wallet.update_password(old_pw=None, new_pw=password, encrypt_storage=encrypt_file)
       t@@ -2468,5 +2479,5 @@ def restore_wallet_from_text(text, *, path, config: SimpleConfig,
            msg = ("This wallet was restored offline. It may contain more addresses than displayed. "
                   "Start a daemon and use load_wallet to sync its history.")
        
       -    wallet.storage.write()
       +    wallet.save_db()
            return {'wallet': wallet, 'msg': msg}
   DIR diff --git a/electrum/wallet_db.py b/electrum/wallet_db.py
       t@@ -39,6 +39,7 @@ from .logging import Logger
        from .lnutil import LOCAL, REMOTE, FeeUpdate, UpdateAddHtlc, LocalConfig, RemoteConfig, Keypair, OnlyPubkeyKeypair, RevocationStore
        from .lnutil import ChannelConstraints, Outpoint, ShachainElement
        from .json_db import StoredDict, JsonDB, locked, modifier
       +from .plugin import run_hook, plugin_loaders
        
        # seed_version is now used for the version of the wallet file
        
       t@@ -62,6 +63,7 @@ class WalletDB(JsonDB):
                self._called_after_upgrade_tasks = False
                if raw:  # loading existing db
                    self.load_data(raw)
       +            self.load_plugins()
                else:  # creating new db
                    self.put('seed_version', FINAL_SEED_VERSION)
                    self._after_upgrade_tasks()
       t@@ -99,7 +101,7 @@ class WalletDB(JsonDB):
                d = self.get('accounts', {})
                return len(d) > 1
        
       -    def split_accounts(self):
       +    def get_split_accounts(self):
                result = []
                # backward compatibility with old wallets
                d = self.get('accounts', {})
       t@@ -993,3 +995,45 @@ class WalletDB(JsonDB):
                elif len(path) > 2 and path[-2] in ['local_config', 'remote_config'] and key in ["pubkey", "privkey"]:
                    v = binascii.unhexlify(v) if v is not None else None
                return v
       +
       +    def write(self, storage):
       +        with self.lock:
       +            self._write(storage)
       +
       +    def _write(self, storage):
       +        if threading.currentThread().isDaemon():
       +            self.logger.warning('daemon thread cannot write db')
       +            return
       +        if not self.modified():
       +            return
       +        storage.write(self.dump())
       +        self.set_modified(False)
       +
       +    def is_ready_to_be_used_by_wallet(self):
       +        return not self.requires_upgrade() and self._called_after_upgrade_tasks
       +
       +    def split_accounts(self, root_path):
       +        from .storage import WalletStorage
       +        out = []
       +        result = self.get_split_accounts()
       +        for data in result:
       +            path = root_path + '.' + data['suffix']
       +            storage = WalletStorage(path)
       +            db = WalletDB(json.dumps(data), manual_upgrades=False)
       +            db._called_after_upgrade_tasks = False
       +            db.upgrade()
       +            db.write(storage)
       +            out.append(path)
       +        return out
       +
       +    def get_action(self):
       +        action = run_hook('get_action', self)
       +        return action
       +
       +    def load_plugins(self):
       +        wallet_type = self.get('wallet_type')
       +        if wallet_type in plugin_loaders:
       +            plugin_loaders[wallet_type]()
       +
       +    def set_keystore_encryption(self, enable):
       +        self.put('use_encryption', enable)
   DIR diff --git a/run_electrum b/run_electrum
       t@@ -88,6 +88,7 @@ from electrum.logging import get_logger, configure_logging
        from electrum import util
        from electrum import constants
        from electrum import SimpleConfig
       +from electrum.wallet_db import WalletDB
        from electrum.wallet import Wallet
        from electrum.storage import WalletStorage, get_derivation_used_for_hw_device_encryption
        from electrum.util import print_msg, print_stderr, json_encode, json_decode, UserCancelled
       t@@ -141,10 +142,17 @@ def init_cmdline(config_options, wallet_path, server):
                print_stderr("Exposing a single private key can compromise your entire wallet!")
                print_stderr("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
        
       +    # will we need a password
       +    if not storage.is_encrypted():
       +        db = WalletDB(storage.read(), manual_upgrades=False)
       +        use_encryption = db.get('use_encryption')
       +    else:
       +        use_encryption = True
       +
            # commands needing password
            if  ( (cmd.requires_wallet and storage.is_encrypted() and server is False)\
               or (cmdname == 'load_wallet' and storage.is_encrypted())\
       -       or (cmd.requires_password and (storage.is_encrypted() or storage.get('use_encryption')))):
       +       or (cmd.requires_password and use_encryption)):
                if storage.is_encrypted_with_hw_device():
                    # this case is handled later in the control flow
                    password = None
       t@@ -218,7 +226,8 @@ async def run_offline_command(config, config_options, plugins):
                        password = get_password_for_hw_device_encrypted_storage(plugins)
                        config_options['password'] = password
                    storage.decrypt(password)
       -        wallet = Wallet(storage, config=config)
       +        db = WalletDB(storage.read(), manual_upgrades=False)
       +        wallet = Wallet(db, storage, config=config)
                config_options['wallet'] = wallet
            else:
                wallet = None
       t@@ -245,7 +254,7 @@ async def run_offline_command(config, config_options, plugins):
            result = await func(*args, **kwargs)
            # save wallet
            if wallet:
       -        wallet.storage.write()
       +        wallet.save_db()
            return result