Separate WalletDB from storage upgrades.
Make sure that WalletDB.data is always a StoredDict. Perform db upgrades in a separate class, since they operate on a dict object.
This commit is contained in:
@@ -47,7 +47,7 @@ from .util import log_exceptions, ignore_exceptions, randrange, OldTaskGroup
|
|||||||
from .util import EventListener, event_listener
|
from .util import EventListener, event_listener
|
||||||
from .wallet import Wallet, Abstract_Wallet
|
from .wallet import Wallet, Abstract_Wallet
|
||||||
from .storage import WalletStorage
|
from .storage import WalletStorage
|
||||||
from .wallet_db import WalletDB
|
from .wallet_db import WalletDB, WalletRequiresSplit, WalletRequiresUpgrade
|
||||||
from .commands import known_commands, Commands
|
from .commands import known_commands, Commands
|
||||||
from .simple_config import SimpleConfig
|
from .simple_config import SimpleConfig
|
||||||
from .exchange_rate import FxThread
|
from .exchange_rate import FxThread
|
||||||
@@ -512,10 +512,11 @@ class Daemon(Logger):
|
|||||||
return
|
return
|
||||||
storage.decrypt(password)
|
storage.decrypt(password)
|
||||||
# read data, pass it to db
|
# read data, pass it to db
|
||||||
db = WalletDB(storage.read(), storage=storage, manual_upgrades=manual_upgrades)
|
try:
|
||||||
if db.requires_split():
|
db = WalletDB(storage.read(), storage=storage, manual_upgrades=manual_upgrades)
|
||||||
|
except WalletRequiresSplit:
|
||||||
return
|
return
|
||||||
if db.requires_upgrade():
|
except WalletRequiresUpgrade:
|
||||||
return
|
return
|
||||||
if db.get_action():
|
if db.get_action():
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
|
|||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
from electrum.logging import get_logger
|
from electrum.logging import get_logger
|
||||||
from electrum.storage import WalletStorage
|
from electrum.storage import WalletStorage
|
||||||
from electrum.wallet_db import WalletDB
|
from electrum.wallet_db import WalletDB, WalletRequiresSplit
|
||||||
from electrum.wallet import Wallet
|
from electrum.wallet import Wallet
|
||||||
from electrum.util import InvalidPassword, WalletFileException, send_exception_to_crash_reporter
|
from electrum.util import InvalidPassword, WalletFileException, send_exception_to_crash_reporter
|
||||||
|
|
||||||
@@ -143,7 +143,10 @@ class QEWalletDB(QObject):
|
|||||||
else: # storage not encrypted; but it might still have a keystore pw
|
else: # storage not encrypted; but it might still have a keystore pw
|
||||||
# FIXME hack... load both db and full wallet, just to tell if it has keystore pw.
|
# FIXME hack... load both db and full wallet, just to tell if it has keystore pw.
|
||||||
# this also completely ignores db.requires_split(), db.get_action(), etc
|
# this also completely ignores db.requires_split(), db.get_action(), etc
|
||||||
db = WalletDB(self._storage.read(), storage=self._storage, manual_upgrades=False)
|
try:
|
||||||
|
db = WalletDB(self._storage.read(), storage=self._storage, manual_upgrades=False)
|
||||||
|
except WalletRequiresSplit as e:
|
||||||
|
raise WalletFileException(_('This wallet requires to be split. This is currently not supported on mobile'))
|
||||||
wallet = Wallet(db, config=self._config)
|
wallet = Wallet(db, config=self._config)
|
||||||
self.needsPassword = wallet.has_password()
|
self.needsPassword = wallet.has_password()
|
||||||
if self.needsPassword:
|
if self.needsPassword:
|
||||||
@@ -162,18 +165,14 @@ class QEWalletDB(QObject):
|
|||||||
def _load_db(self):
|
def _load_db(self):
|
||||||
"""can raise WalletFileException"""
|
"""can raise WalletFileException"""
|
||||||
# needs storage accessible
|
# needs storage accessible
|
||||||
self._db = WalletDB(self._storage.read(), storage=self._storage, manual_upgrades=True)
|
try:
|
||||||
if self._db.requires_split():
|
self._db = WalletDB(self._storage.read(), storage=self._storage, manual_upgrades=False)
|
||||||
|
except WalletRequiresSplit as e:
|
||||||
self._logger.warning('wallet requires split')
|
self._logger.warning('wallet requires split')
|
||||||
raise WalletFileException(_('This wallet needs splitting. This is not supported on mobile'))
|
raise WalletFileException(_('This wallet needs splitting. This is not supported on mobile'))
|
||||||
if self._db.get_action():
|
if self._db.get_action():
|
||||||
self._logger.warning('action pending. QML version doesn\'t support continuation of wizard')
|
self._logger.warning('action pending. QML version doesn\'t support continuation of wizard')
|
||||||
raise WalletFileException(_('This wallet has an action pending. This is currently not supported on mobile'))
|
raise WalletFileException(_('This wallet has an action pending. This is currently not supported on mobile'))
|
||||||
|
|
||||||
if self._db.requires_upgrade():
|
|
||||||
self._logger.warning('wallet requires upgrade, upgrading')
|
|
||||||
self._db.upgrade()
|
|
||||||
self._db.write()
|
|
||||||
|
|
||||||
self._ready = True
|
self._ready = True
|
||||||
self.readyChanged.emit()
|
self.readyChanged.emit()
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ from electrum.plugin import run_hook
|
|||||||
from electrum.util import (UserCancelled, profiler, send_exception_to_crash_reporter,
|
from electrum.util import (UserCancelled, profiler, send_exception_to_crash_reporter,
|
||||||
WalletFileException, BitcoinException, get_new_wallet_name)
|
WalletFileException, BitcoinException, get_new_wallet_name)
|
||||||
from electrum.wallet import Wallet, Abstract_Wallet
|
from electrum.wallet import Wallet, Abstract_Wallet
|
||||||
from electrum.wallet_db import WalletDB
|
from electrum.wallet_db import WalletDB, WalletRequiresSplit, WalletRequiresUpgrade
|
||||||
from electrum.logging import Logger
|
from electrum.logging import Logger
|
||||||
from electrum.gui import BaseElectrumGui
|
from electrum.gui import BaseElectrumGui
|
||||||
from electrum.simple_config import SimpleConfig
|
from electrum.simple_config import SimpleConfig
|
||||||
@@ -430,10 +430,11 @@ class ElectrumGui(BaseElectrumGui, Logger):
|
|||||||
if storage.is_encrypted_with_user_pw() or storage.is_encrypted_with_hw_device():
|
if storage.is_encrypted_with_user_pw() or storage.is_encrypted_with_hw_device():
|
||||||
storage.decrypt(d['password'])
|
storage.decrypt(d['password'])
|
||||||
|
|
||||||
db = WalletDB(storage.read(), storage=storage, manual_upgrades=True)
|
try:
|
||||||
if db.requires_split() or db.requires_upgrade():
|
db = WalletDB(storage.read(), storage=storage, manual_upgrades=True)
|
||||||
|
except WalletRequiresSplit as e:
|
||||||
try:
|
try:
|
||||||
wizard.run_upgrades(db)
|
wizard.run_split(storage, e._split_data)
|
||||||
except UserCancelled:
|
except UserCancelled:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
import json
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
@@ -21,6 +22,7 @@ from electrum.wallet import wallet_types
|
|||||||
from .wizard import QEAbstractWizard, WizardComponent
|
from .wizard import QEAbstractWizard, WizardComponent
|
||||||
from electrum.logging import get_logger, Logger
|
from electrum.logging import get_logger, Logger
|
||||||
from electrum import WalletStorage, mnemonic, keystore
|
from electrum import WalletStorage, mnemonic, keystore
|
||||||
|
from electrum.wallet_db import WalletDB
|
||||||
from electrum.wizard import NewWalletWizard
|
from electrum.wizard import NewWalletWizard
|
||||||
|
|
||||||
from electrum.gui.qt.bip39_recovery_dialog import Bip39RecoveryDialog
|
from electrum.gui.qt.bip39_recovery_dialog import Bip39RecoveryDialog
|
||||||
@@ -34,7 +36,6 @@ if TYPE_CHECKING:
|
|||||||
from electrum.simple_config import SimpleConfig
|
from electrum.simple_config import SimpleConfig
|
||||||
from electrum.plugin import Plugins
|
from electrum.plugin import Plugins
|
||||||
from electrum.daemon import Daemon
|
from electrum.daemon import Daemon
|
||||||
from electrum.wallet_db import WalletDB
|
|
||||||
from electrum.gui.qt import QElectrumApplication
|
from electrum.gui.qt import QElectrumApplication
|
||||||
|
|
||||||
WIF_HELP_TEXT = (_('WIF keys are typed in Electrum, based on script type.') + '\n\n' +
|
WIF_HELP_TEXT = (_('WIF keys are typed in Electrum, based on script type.') + '\n\n' +
|
||||||
@@ -161,25 +162,20 @@ class QENewWalletWizard(NewWalletWizard, QEAbstractWizard, MessageBoxMixin):
|
|||||||
self._password = data['password']
|
self._password = data['password']
|
||||||
self.path = path
|
self.path = path
|
||||||
|
|
||||||
def run_upgrades(self, db: 'WalletDB') -> None:
|
def run_split(self, storage, split_data) -> None:
|
||||||
storage = db.storage
|
root_path = storage.path
|
||||||
path = storage.path
|
msg = _(
|
||||||
if db.requires_split():
|
"The wallet '{}' contains multiple accounts, which are no longer supported since Electrum 2.7.\n\n"
|
||||||
msg = _(
|
"Do you want to split your wallet into multiple files?").format(root_path)
|
||||||
"The wallet '{}' contains multiple accounts, which are no longer supported since Electrum 2.7.\n\n"
|
if self.question(msg):
|
||||||
"Do you want to split your wallet into multiple files?").format(path)
|
file_list = WalletDB.split_accounts(root_path, split_data)
|
||||||
if not self.question(msg):
|
|
||||||
return
|
|
||||||
file_list = db.split_accounts(path)
|
|
||||||
msg = _('Your accounts have been moved to') + ':\n' + '\n'.join(file_list) + '\n\n' + _(
|
msg = _('Your accounts have been moved to') + ':\n' + '\n'.join(file_list) + '\n\n' + _(
|
||||||
'Do you want to delete the old file') + ':\n' + path
|
'Do you want to delete the old file') + ':\n' + root_path
|
||||||
if self.question(msg):
|
if self.question(msg):
|
||||||
os.remove(path)
|
os.remove(root_path)
|
||||||
self.show_warning(_('The file was removed'))
|
self.show_warning(_('The file was removed'))
|
||||||
# raise now, to avoid having the old storage opened
|
# raise now, to avoid having the old storage opened
|
||||||
raise UserCancelled()
|
raise UserCancelled()
|
||||||
if db.requires_upgrade():
|
|
||||||
self.waiting_dialog(db.upgrade, _('Upgrading wallet format...'))
|
|
||||||
|
|
||||||
def is_finalized(self, wizard_data: dict) -> bool:
|
def is_finalized(self, wizard_data: dict) -> bool:
|
||||||
# check decryption of existing wallet and keep wizard open if incorrect.
|
# check decryption of existing wallet and keep wizard open if incorrect.
|
||||||
|
|||||||
@@ -34,8 +34,6 @@ from .logging import Logger
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .storage import WalletStorage
|
from .storage import WalletStorage
|
||||||
|
|
||||||
JsonDBJsonEncoder = util.MyEncoder
|
|
||||||
|
|
||||||
def modifier(func):
|
def modifier(func):
|
||||||
def wrapper(self, *args, **kwargs):
|
def wrapper(self, *args, **kwargs):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
@@ -168,24 +166,28 @@ class StoredDict(dict):
|
|||||||
|
|
||||||
class JsonDB(Logger):
|
class JsonDB(Logger):
|
||||||
|
|
||||||
def __init__(self, data, storage=None):
|
def __init__(self, s: str, storage=None, encoder=None):
|
||||||
Logger.__init__(self)
|
Logger.__init__(self)
|
||||||
self.lock = threading.RLock()
|
self.lock = threading.RLock()
|
||||||
self.storage = storage
|
self.storage = storage
|
||||||
|
self.encoder = encoder
|
||||||
self._modified = False
|
self._modified = False
|
||||||
# load data
|
# load data
|
||||||
if data:
|
data = self.load_data(s)
|
||||||
self.load_data(data)
|
# convert to StoredDict
|
||||||
else:
|
self.data = StoredDict(data, self, [])
|
||||||
self.data = {}
|
|
||||||
|
|
||||||
def load_data(self, s):
|
def load_data(self, s:str) -> dict:
|
||||||
|
""" overloaded in wallet_db """
|
||||||
|
if s == '':
|
||||||
|
return {}
|
||||||
try:
|
try:
|
||||||
self.data = json.loads(s)
|
data = json.loads(s)
|
||||||
except Exception:
|
except Exception:
|
||||||
raise WalletFileException("Cannot read wallet file. (parsing failed)")
|
raise WalletFileException("Cannot read wallet file. (parsing failed)")
|
||||||
if not isinstance(self.data, dict):
|
if not isinstance(data, dict):
|
||||||
raise WalletFileException("Malformed wallet file (not dict)")
|
raise WalletFileException("Malformed wallet file (not dict)")
|
||||||
|
return data
|
||||||
|
|
||||||
def set_modified(self, b):
|
def set_modified(self, b):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
@@ -204,8 +206,8 @@ class JsonDB(Logger):
|
|||||||
@modifier
|
@modifier
|
||||||
def put(self, key, value):
|
def put(self, key, value):
|
||||||
try:
|
try:
|
||||||
json.dumps(key, cls=JsonDBJsonEncoder)
|
json.dumps(key, cls=self.encoder)
|
||||||
json.dumps(value, cls=JsonDBJsonEncoder)
|
json.dumps(value, cls=self.encoder)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.logger.info(f"json error: cannot save {repr(key)} ({repr(value)})")
|
self.logger.info(f"json error: cannot save {repr(key)} ({repr(value)})")
|
||||||
return False
|
return False
|
||||||
@@ -235,7 +237,7 @@ class JsonDB(Logger):
|
|||||||
self.data,
|
self.data,
|
||||||
indent=4 if human_readable else None,
|
indent=4 if human_readable else None,
|
||||||
sort_keys=bool(human_readable),
|
sort_keys=bool(human_readable),
|
||||||
cls=JsonDBJsonEncoder,
|
cls=self.encoder,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _should_convert_to_stored_dict(self, key) -> bool:
|
def _should_convert_to_stored_dict(self, key) -> bool:
|
||||||
|
|||||||
@@ -325,7 +325,7 @@ class WatchTower(LNWatcher):
|
|||||||
LOGGING_SHORTCUT = 'W'
|
LOGGING_SHORTCUT = 'W'
|
||||||
|
|
||||||
def __init__(self, network: 'Network'):
|
def __init__(self, network: 'Network'):
|
||||||
adb = AddressSynchronizer(WalletDB({}, storage=None, manual_upgrades=False), network.config, name=self.diagnostic_name())
|
adb = AddressSynchronizer(WalletDB('', storage=None, manual_upgrades=False), network.config, name=self.diagnostic_name())
|
||||||
adb.start_network(network)
|
adb.start_network(network)
|
||||||
LNWatcher.__init__(self, adb, network)
|
LNWatcher.__init__(self, adb, network)
|
||||||
self.network = network
|
self.network = network
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import asyncio
|
|||||||
import inspect
|
import inspect
|
||||||
|
|
||||||
import electrum
|
import electrum
|
||||||
from electrum.wallet_db import WalletDB
|
from electrum.wallet_db import WalletDBUpgrader, WalletDB, WalletRequiresUpgrade, WalletRequiresSplit
|
||||||
from electrum.wallet import Wallet
|
from electrum.wallet import Wallet
|
||||||
from electrum import constants
|
from electrum import constants
|
||||||
from electrum import util
|
from electrum import util
|
||||||
@@ -331,31 +331,30 @@ class TestStorageUpgrade(WalletTestCase):
|
|||||||
async def _upgrade_storage(self, wallet_json, accounts=1) -> Optional[WalletDB]:
|
async def _upgrade_storage(self, wallet_json, accounts=1) -> Optional[WalletDB]:
|
||||||
if accounts == 1:
|
if accounts == 1:
|
||||||
# test manual upgrades
|
# test manual upgrades
|
||||||
db = self._load_db_from_json_string(wallet_json=wallet_json,
|
try:
|
||||||
manual_upgrades=True)
|
db = self._load_db_from_json_string(
|
||||||
self.assertFalse(db.requires_split())
|
wallet_json=wallet_json,
|
||||||
if db.requires_upgrade():
|
manual_upgrades=True)
|
||||||
db.upgrade()
|
except WalletRequiresUpgrade:
|
||||||
|
db = self._load_db_from_json_string(
|
||||||
|
wallet_json=wallet_json,
|
||||||
|
manual_upgrades=False)
|
||||||
await self._sanity_check_upgraded_db(db)
|
await self._sanity_check_upgraded_db(db)
|
||||||
# test automatic upgrades
|
return db
|
||||||
db2 = self._load_db_from_json_string(wallet_json=wallet_json,
|
|
||||||
manual_upgrades=False)
|
|
||||||
await self._sanity_check_upgraded_db(db2)
|
|
||||||
return db2
|
|
||||||
else:
|
else:
|
||||||
db = self._load_db_from_json_string(wallet_json=wallet_json,
|
try:
|
||||||
manual_upgrades=True)
|
db = self._load_db_from_json_string(
|
||||||
self.assertTrue(db.requires_split())
|
wallet_json=wallet_json,
|
||||||
split_data = db.get_split_accounts()
|
manual_upgrades=True)
|
||||||
self.assertEqual(accounts, len(split_data))
|
except WalletRequiresSplit as e:
|
||||||
for item in split_data:
|
split_data = e._split_data
|
||||||
data = json.dumps(item)
|
self.assertEqual(accounts, len(split_data))
|
||||||
new_db = WalletDB(data, storage=None, manual_upgrades=False)
|
for item in split_data:
|
||||||
await self._sanity_check_upgraded_db(new_db)
|
data = json.dumps(item)
|
||||||
|
new_db = WalletDB(data, storage=None, manual_upgrades=False)
|
||||||
|
await self._sanity_check_upgraded_db(new_db)
|
||||||
|
|
||||||
async def _sanity_check_upgraded_db(self, db):
|
async def _sanity_check_upgraded_db(self, db):
|
||||||
self.assertFalse(db.requires_split())
|
|
||||||
self.assertFalse(db.requires_upgrade())
|
|
||||||
wallet = Wallet(db, config=self.config)
|
wallet = Wallet(db, config=self.config)
|
||||||
await wallet.stop()
|
await wallet.stop()
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from electrum.wallet import (Abstract_Wallet, Standard_Wallet, create_new_wallet
|
|||||||
from electrum.exchange_rate import ExchangeBase, FxThread
|
from electrum.exchange_rate import ExchangeBase, FxThread
|
||||||
from electrum.util import TxMinedInfo, InvalidPassword
|
from electrum.util import TxMinedInfo, InvalidPassword
|
||||||
from electrum.bitcoin import COIN
|
from electrum.bitcoin import COIN
|
||||||
from electrum.wallet_db import WalletDB
|
from electrum.wallet_db import WalletDB, JsonDB
|
||||||
from electrum.simple_config import SimpleConfig
|
from electrum.simple_config import SimpleConfig
|
||||||
from electrum import util
|
from electrum import util
|
||||||
|
|
||||||
@@ -60,14 +60,14 @@ class TestWalletStorage(WalletTestCase):
|
|||||||
contents = f.write(contents)
|
contents = f.write(contents)
|
||||||
|
|
||||||
storage = WalletStorage(self.wallet_path)
|
storage = WalletStorage(self.wallet_path)
|
||||||
db = WalletDB(storage.read(), storage=storage, manual_upgrades=True)
|
db = JsonDB(storage.read(), storage=storage)
|
||||||
self.assertEqual("b", db.get("a"))
|
self.assertEqual("b", db.get("a"))
|
||||||
self.assertEqual("d", db.get("c"))
|
self.assertEqual("d", db.get("c"))
|
||||||
|
|
||||||
def test_write_dictionary_to_file(self):
|
def test_write_dictionary_to_file(self):
|
||||||
|
|
||||||
storage = WalletStorage(self.wallet_path)
|
storage = WalletStorage(self.wallet_path)
|
||||||
db = WalletDB('', storage=storage, manual_upgrades=True)
|
db = JsonDB('', storage=storage)
|
||||||
|
|
||||||
some_dict = {
|
some_dict = {
|
||||||
u"a": u"b",
|
u"a": u"b",
|
||||||
@@ -110,7 +110,7 @@ class FakeWallet:
|
|||||||
def __init__(self, fiat_value):
|
def __init__(self, fiat_value):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.fiat_value = fiat_value
|
self.fiat_value = fiat_value
|
||||||
self.db = WalletDB("{}", storage=None, manual_upgrades=True)
|
self.db = WalletDB('', storage=None, manual_upgrades=True)
|
||||||
self.adb = FakeADB()
|
self.adb = FakeADB()
|
||||||
self.db.transactions = self.db.verified_tx = {'abc':'Tx'}
|
self.db.transactions = self.db.verified_tx = {'abc':'Tx'}
|
||||||
|
|
||||||
|
|||||||
@@ -309,8 +309,8 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
|||||||
|
|
||||||
def __init__(self, db: WalletDB, *, config: SimpleConfig):
|
def __init__(self, db: WalletDB, *, config: SimpleConfig):
|
||||||
|
|
||||||
if not db.is_ready_to_be_used_by_wallet():
|
#if not db.is_ready_to_be_used_by_wallet():
|
||||||
raise Exception("storage not ready to be used by Abstract_Wallet")
|
# raise Exception("storage not ready to be used by Abstract_Wallet")
|
||||||
|
|
||||||
self.config = config
|
self.config = config
|
||||||
assert self.config is not None, "config must not be None"
|
assert self.config is not None, "config must not be None"
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import time
|
|||||||
import attr
|
import attr
|
||||||
|
|
||||||
from . import util, bitcoin
|
from . import util, bitcoin
|
||||||
from .util import profiler, WalletFileException, multisig_type, TxMinedInfo, bfh
|
from .util import profiler, WalletFileException, multisig_type, TxMinedInfo, bfh, MyEncoder
|
||||||
from .invoices import Invoice, Request
|
from .invoices import Invoice, Request
|
||||||
from .keystore import bip44_derivation
|
from .keystore import bip44_derivation
|
||||||
from .transaction import Transaction, TxOutpoint, tx_from_any, PartialTransaction, PartialTxOutput
|
from .transaction import Transaction, TxOutpoint, tx_from_any, PartialTransaction, PartialTxOutput
|
||||||
@@ -49,6 +49,11 @@ from .plugin import run_hook, plugin_loaders
|
|||||||
from .version import ELECTRUM_VERSION
|
from .version import ELECTRUM_VERSION
|
||||||
|
|
||||||
|
|
||||||
|
class WalletRequiresUpgrade(WalletFileException):
|
||||||
|
pass
|
||||||
|
class WalletRequiresSplit(WalletFileException):
|
||||||
|
def __init__(self, split_data):
|
||||||
|
self._split_data = split_data
|
||||||
|
|
||||||
# seed_version is now used for the version of the wallet file
|
# seed_version is now used for the version of the wallet file
|
||||||
|
|
||||||
@@ -100,46 +105,21 @@ for key in ['locked_in', 'fails', 'settles']:
|
|||||||
json_db.register_parent_key(key, lambda x: HTLCOwner(int(x)))
|
json_db.register_parent_key(key, lambda x: HTLCOwner(int(x)))
|
||||||
|
|
||||||
|
|
||||||
class WalletDB(JsonDB):
|
|
||||||
|
|
||||||
def __init__(self, data, *, storage=None, manual_upgrades: bool):
|
class WalletDBUpgrader(Logger):
|
||||||
JsonDB.__init__(self, data, storage)
|
|
||||||
if not data:
|
|
||||||
# create new DB
|
|
||||||
self.put('seed_version', FINAL_SEED_VERSION)
|
|
||||||
self._add_db_creation_metadata()
|
|
||||||
self._after_upgrade_tasks()
|
|
||||||
self._manual_upgrades = manual_upgrades
|
|
||||||
self._called_after_upgrade_tasks = False
|
|
||||||
if not self._manual_upgrades and self.requires_split():
|
|
||||||
raise WalletFileException("This wallet has multiple accounts and must be split")
|
|
||||||
if not self.requires_upgrade():
|
|
||||||
self._after_upgrade_tasks()
|
|
||||||
elif not self._manual_upgrades:
|
|
||||||
self.upgrade()
|
|
||||||
# load plugins that are conditional on wallet type
|
|
||||||
self.load_plugins()
|
|
||||||
|
|
||||||
def load_data(self, s):
|
def __init__(self, data):
|
||||||
try:
|
Logger.__init__(self)
|
||||||
JsonDB.load_data(self, s)
|
self.data = data
|
||||||
except Exception:
|
|
||||||
try:
|
def get(self, key, default=None):
|
||||||
d = ast.literal_eval(s)
|
return self.data.get(key, default)
|
||||||
labels = d.get('labels', {})
|
|
||||||
except Exception as e:
|
def put(self, key, value):
|
||||||
raise WalletFileException("Cannot read wallet file. (parsing failed)")
|
if value is not None:
|
||||||
self.data = {}
|
self.data[key] = value
|
||||||
for key, value in d.items():
|
else:
|
||||||
try:
|
self.data.pop(key, None)
|
||||||
json.dumps(key)
|
|
||||||
json.dumps(value)
|
|
||||||
except Exception:
|
|
||||||
self.logger.info(f'Failed to convert label to json format: {key}')
|
|
||||||
continue
|
|
||||||
self.data[key] = value
|
|
||||||
if not isinstance(self.data, dict):
|
|
||||||
raise WalletFileException("Malformed wallet file (not dict)")
|
|
||||||
|
|
||||||
def requires_split(self):
|
def requires_split(self):
|
||||||
d = self.get('accounts', {})
|
d = self.get('accounts', {})
|
||||||
@@ -192,9 +172,6 @@ class WalletDB(JsonDB):
|
|||||||
@profiler
|
@profiler
|
||||||
def upgrade(self):
|
def upgrade(self):
|
||||||
self.logger.info('upgrading wallet format')
|
self.logger.info('upgrading wallet format')
|
||||||
if self._called_after_upgrade_tasks:
|
|
||||||
# we need strict ordering between upgrade() and after_upgrade_tasks()
|
|
||||||
raise Exception("'after_upgrade_tasks' must NOT be called before 'upgrade'")
|
|
||||||
self._convert_imported()
|
self._convert_imported()
|
||||||
self._convert_wallet_type()
|
self._convert_wallet_type()
|
||||||
self._convert_account()
|
self._convert_account()
|
||||||
@@ -242,12 +219,6 @@ class WalletDB(JsonDB):
|
|||||||
self._convert_version_54()
|
self._convert_version_54()
|
||||||
self.put('seed_version', FINAL_SEED_VERSION) # just to be sure
|
self.put('seed_version', FINAL_SEED_VERSION) # just to be sure
|
||||||
|
|
||||||
self._after_upgrade_tasks()
|
|
||||||
|
|
||||||
def _after_upgrade_tasks(self):
|
|
||||||
self._called_after_upgrade_tasks = True
|
|
||||||
self._load_transactions()
|
|
||||||
|
|
||||||
def _convert_wallet_type(self):
|
def _convert_wallet_type(self):
|
||||||
if not self._is_upgrade_method_needed(0, 13):
|
if not self._is_upgrade_method_needed(0, 13):
|
||||||
return
|
return
|
||||||
@@ -1140,7 +1111,6 @@ class WalletDB(JsonDB):
|
|||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@locked
|
|
||||||
def get_seed_version(self):
|
def get_seed_version(self):
|
||||||
seed_version = self.get('seed_version')
|
seed_version = self.get('seed_version')
|
||||||
if not seed_version:
|
if not seed_version:
|
||||||
@@ -1191,14 +1161,62 @@ class WalletDB(JsonDB):
|
|||||||
# generic exception
|
# generic exception
|
||||||
raise WalletFileException(msg)
|
raise WalletFileException(msg)
|
||||||
|
|
||||||
def _add_db_creation_metadata(self):
|
|
||||||
# store this for debugging purposes
|
class WalletDB(JsonDB):
|
||||||
v = DBMetadata(
|
|
||||||
creation_timestamp=int(time.time()),
|
def __init__(self, s, *, storage=None, manual_upgrades=True):
|
||||||
first_electrum_version_used=ELECTRUM_VERSION,
|
self._upgrade = not manual_upgrades
|
||||||
)
|
JsonDB.__init__(self, s, storage, encoder=MyEncoder)
|
||||||
assert self.get("db_metadata", None) is None
|
# create pointers
|
||||||
self.put("db_metadata", v)
|
self.load_transactions()
|
||||||
|
# load plugins that are conditional on wallet type
|
||||||
|
self.load_plugins()
|
||||||
|
|
||||||
|
def load_data(self, s):
|
||||||
|
try:
|
||||||
|
data = JsonDB.load_data(self, s)
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
d = ast.literal_eval(s)
|
||||||
|
labels = d.get('labels', {})
|
||||||
|
except Exception as e:
|
||||||
|
raise WalletFileException("Cannot read wallet file. (parsing failed)")
|
||||||
|
data = {}
|
||||||
|
for key, value in d.items():
|
||||||
|
try:
|
||||||
|
json.dumps(key)
|
||||||
|
json.dumps(value)
|
||||||
|
except Exception:
|
||||||
|
self.logger.info(f'Failed to convert label to json format: {key}')
|
||||||
|
continue
|
||||||
|
data[key] = value
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
raise WalletFileException("Malformed wallet file (not dict)")
|
||||||
|
|
||||||
|
if len(data) == 0:
|
||||||
|
# create new DB
|
||||||
|
data['seed_version'] = FINAL_SEED_VERSION
|
||||||
|
# store this for debugging purposes
|
||||||
|
v = DBMetadata(
|
||||||
|
creation_timestamp=int(time.time()),
|
||||||
|
first_electrum_version_used=ELECTRUM_VERSION,
|
||||||
|
)
|
||||||
|
assert data.get("db_metadata", None) is None
|
||||||
|
data["db_metadata"] = v
|
||||||
|
|
||||||
|
dbu = WalletDBUpgrader(data)
|
||||||
|
if dbu.requires_split():
|
||||||
|
raise WalletRequiresSplit(dbu.get_split_accounts())
|
||||||
|
if self._upgrade:
|
||||||
|
dbu.upgrade()
|
||||||
|
if dbu.requires_upgrade():
|
||||||
|
raise WalletRequiresUpgrade()
|
||||||
|
return dbu.data
|
||||||
|
|
||||||
|
|
||||||
|
@locked
|
||||||
|
def get_seed_version(self):
|
||||||
|
return self.get('seed_version')
|
||||||
|
|
||||||
def get_db_metadata(self) -> Optional[DBMetadata]:
|
def get_db_metadata(self) -> Optional[DBMetadata]:
|
||||||
# field only present for wallet files created with ver 4.4.0 or later
|
# field only present for wallet files created with ver 4.4.0 or later
|
||||||
@@ -1560,8 +1578,7 @@ class WalletDB(JsonDB):
|
|||||||
self._addr_to_addr_index[addr] = (1, i)
|
self._addr_to_addr_index[addr] = (1, i)
|
||||||
|
|
||||||
@profiler
|
@profiler
|
||||||
def _load_transactions(self):
|
def load_transactions(self):
|
||||||
self.data = StoredDict(self.data, self, [])
|
|
||||||
# references in self.data
|
# references in self.data
|
||||||
# TODO make all these private
|
# TODO make all these private
|
||||||
# txid -> address -> prev_outpoint -> value
|
# txid -> address -> prev_outpoint -> value
|
||||||
@@ -1608,21 +1625,19 @@ class WalletDB(JsonDB):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def is_ready_to_be_used_by_wallet(self):
|
def is_ready_to_be_used_by_wallet(self):
|
||||||
return not self.requires_upgrade() and self._called_after_upgrade_tasks
|
return not self._requires_upgrade
|
||||||
|
|
||||||
def split_accounts(self, root_path):
|
@classmethod
|
||||||
|
def split_accounts(klass, root_path, split_data):
|
||||||
from .storage import WalletStorage
|
from .storage import WalletStorage
|
||||||
out = []
|
file_list = []
|
||||||
result = self.get_split_accounts()
|
for data in split_data:
|
||||||
for data in result:
|
|
||||||
path = root_path + '.' + data['suffix']
|
path = root_path + '.' + data['suffix']
|
||||||
storage = WalletStorage(path)
|
item_storage = WalletStorage(path)
|
||||||
db = WalletDB(json.dumps(data), storage=storage, manual_upgrades=False)
|
db = WalletDB(json.dumps(data), storage=item_storage, manual_upgrades=False)
|
||||||
db._called_after_upgrade_tasks = False
|
|
||||||
db.upgrade()
|
|
||||||
db.write()
|
db.write()
|
||||||
out.append(path)
|
file_list.append(path)
|
||||||
return out
|
return file_list
|
||||||
|
|
||||||
def get_action(self):
|
def get_action(self):
|
||||||
action = run_hook('get_action', self)
|
action = run_hook('get_action', self)
|
||||||
|
|||||||
Reference in New Issue
Block a user