mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-08-23 09:37:31 +00:00
Separate db from storage
- storage is content-agnostic - db and storage are passed to wallet contructor
This commit is contained in:
parent
c61e5db6a9
commit
e1ce3aace7
25 changed files with 421 additions and 421 deletions
|
@ -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
|
||||
|
@ -64,7 +65,7 @@ class WizardStackItem(NamedTuple):
|
|||
action: Any
|
||||
args: Any
|
||||
kwargs: Dict[str, Any]
|
||||
storage_data: dict
|
||||
db_data: dict
|
||||
|
||||
|
||||
class WizardWalletPasswordSetting(NamedTuple):
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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')
|
||||
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
@ -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')
|
||||
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
@ -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
|
||||
|
||||
|
@ -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']
|
||||
|
|
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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()
|
||||
|
@ -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."""
|
||||
|
|
|
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -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({
|
||||
|
@ -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')
|
||||
|
@ -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()
|
||||
|
|
|
@ -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.'))
|
||||
|
@ -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)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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)))
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
@ -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']
|
||||
|
@ -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']
|
||||
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
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_storage(self, storage):
|
||||
self.assertFalse(storage.requires_split())
|
||||
self.assertFalse(storage.requires_upgrade())
|
||||
w = Wallet(storage, config=self.config)
|
||||
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
|
||||
|
|
|
@ -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",
|
||||
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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
|
||||
|
@ -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, '')
|
||||
|
@ -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
|
||||
|
@ -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, '')
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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),
|
||||
|
@ -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),
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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'),
|
||||
|
@ -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'),
|
||||
|
@ -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'),
|
||||
|
@ -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'),
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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'),
|
||||
|
@ -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'),
|
||||
|
@ -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'),
|
||||
|
@ -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(
|
||||
[
|
||||
|
@ -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(
|
||||
[
|
||||
|
@ -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(
|
||||
[
|
||||
|
@ -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]])
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
@ -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. '
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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()]
|
||||
|
@ -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
|
||||
|
||||
|
@ -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():
|
||||
|
@ -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):
|
||||
|
@ -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,
|
||||
|
@ -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:
|
||||
|
@ -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]:
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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)]
|
||||
|
@ -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/')
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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', {})
|
||||
|
@ -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)
|
||||
|
|
15
run_electrum
15
run_electrum
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue