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:
@@ -36,7 +36,7 @@ import time
|
||||
import attr
|
||||
|
||||
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 .keystore import bip44_derivation
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
|
||||
@@ -100,46 +105,21 @@ for key in ['locked_in', 'fails', 'settles']:
|
||||
json_db.register_parent_key(key, lambda x: HTLCOwner(int(x)))
|
||||
|
||||
|
||||
class WalletDB(JsonDB):
|
||||
|
||||
def __init__(self, data, *, storage=None, manual_upgrades: bool):
|
||||
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()
|
||||
class WalletDBUpgrader(Logger):
|
||||
|
||||
def load_data(self, s):
|
||||
try:
|
||||
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)")
|
||||
self.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
|
||||
self.data[key] = value
|
||||
if not isinstance(self.data, dict):
|
||||
raise WalletFileException("Malformed wallet file (not dict)")
|
||||
def __init__(self, data):
|
||||
Logger.__init__(self)
|
||||
self.data = data
|
||||
|
||||
def get(self, key, default=None):
|
||||
return self.data.get(key, default)
|
||||
|
||||
def put(self, key, value):
|
||||
if value is not None:
|
||||
self.data[key] = value
|
||||
else:
|
||||
self.data.pop(key, None)
|
||||
|
||||
def requires_split(self):
|
||||
d = self.get('accounts', {})
|
||||
@@ -192,9 +172,6 @@ class WalletDB(JsonDB):
|
||||
@profiler
|
||||
def upgrade(self):
|
||||
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_wallet_type()
|
||||
self._convert_account()
|
||||
@@ -242,12 +219,6 @@ class WalletDB(JsonDB):
|
||||
self._convert_version_54()
|
||||
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):
|
||||
if not self._is_upgrade_method_needed(0, 13):
|
||||
return
|
||||
@@ -1140,7 +1111,6 @@ class WalletDB(JsonDB):
|
||||
else:
|
||||
return True
|
||||
|
||||
@locked
|
||||
def get_seed_version(self):
|
||||
seed_version = self.get('seed_version')
|
||||
if not seed_version:
|
||||
@@ -1191,14 +1161,62 @@ class WalletDB(JsonDB):
|
||||
# generic exception
|
||||
raise WalletFileException(msg)
|
||||
|
||||
def _add_db_creation_metadata(self):
|
||||
# store this for debugging purposes
|
||||
v = DBMetadata(
|
||||
creation_timestamp=int(time.time()),
|
||||
first_electrum_version_used=ELECTRUM_VERSION,
|
||||
)
|
||||
assert self.get("db_metadata", None) is None
|
||||
self.put("db_metadata", v)
|
||||
|
||||
class WalletDB(JsonDB):
|
||||
|
||||
def __init__(self, s, *, storage=None, manual_upgrades=True):
|
||||
self._upgrade = not manual_upgrades
|
||||
JsonDB.__init__(self, s, storage, encoder=MyEncoder)
|
||||
# create pointers
|
||||
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]:
|
||||
# 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)
|
||||
|
||||
@profiler
|
||||
def _load_transactions(self):
|
||||
self.data = StoredDict(self.data, self, [])
|
||||
def load_transactions(self):
|
||||
# references in self.data
|
||||
# TODO make all these private
|
||||
# txid -> address -> prev_outpoint -> value
|
||||
@@ -1608,21 +1625,19 @@ class WalletDB(JsonDB):
|
||||
return True
|
||||
|
||||
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
|
||||
out = []
|
||||
result = self.get_split_accounts()
|
||||
for data in result:
|
||||
file_list = []
|
||||
for data in split_data:
|
||||
path = root_path + '.' + data['suffix']
|
||||
storage = WalletStorage(path)
|
||||
db = WalletDB(json.dumps(data), storage=storage, manual_upgrades=False)
|
||||
db._called_after_upgrade_tasks = False
|
||||
db.upgrade()
|
||||
item_storage = WalletStorage(path)
|
||||
db = WalletDB(json.dumps(data), storage=item_storage, manual_upgrades=False)
|
||||
db.write()
|
||||
out.append(path)
|
||||
return out
|
||||
file_list.append(path)
|
||||
return file_list
|
||||
|
||||
def get_action(self):
|
||||
action = run_hook('get_action', self)
|
||||
|
||||
Reference in New Issue
Block a user