|
|
|
|
@ -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) |
|
|
|
|
|