wallet_db.py (54087B)
1 #!/usr/bin/env python 2 # 3 # Electrum - lightweight Bitcoin client 4 # Copyright (C) 2015 Thomas Voegtlin 5 # 6 # Permission is hereby granted, free of charge, to any person 7 # obtaining a copy of this software and associated documentation files 8 # (the "Software"), to deal in the Software without restriction, 9 # including without limitation the rights to use, copy, modify, merge, 10 # publish, distribute, sublicense, and/or sell copies of the Software, 11 # and to permit persons to whom the Software is furnished to do so, 12 # subject to the following conditions: 13 # 14 # The above copyright notice and this permission notice shall be 15 # included in all copies or substantial portions of the Software. 16 # 17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 21 # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 22 # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 23 # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 # SOFTWARE. 25 import os 26 import ast 27 import json 28 import copy 29 import threading 30 from collections import defaultdict 31 from typing import Dict, Optional, List, Tuple, Set, Iterable, NamedTuple, Sequence, TYPE_CHECKING, Union 32 import binascii 33 34 from . import util, bitcoin 35 from .util import profiler, WalletFileException, multisig_type, TxMinedInfo, bfh 36 from .invoices import PR_TYPE_ONCHAIN, Invoice 37 from .keystore import bip44_derivation 38 from .transaction import Transaction, TxOutpoint, tx_from_any, PartialTransaction, PartialTxOutput 39 from .logging import Logger 40 from .lnutil import LOCAL, REMOTE, FeeUpdate, UpdateAddHtlc, LocalConfig, RemoteConfig, Keypair, OnlyPubkeyKeypair, RevocationStore, ChannelBackupStorage 41 from .lnutil import ChannelConstraints, Outpoint, ShachainElement 42 from .json_db import StoredDict, JsonDB, locked, modifier 43 from .plugin import run_hook, plugin_loaders 44 from .paymentrequest import PaymentRequest 45 from .submarine_swaps import SwapData 46 47 if TYPE_CHECKING: 48 from .storage import WalletStorage 49 50 51 # seed_version is now used for the version of the wallet file 52 53 OLD_SEED_VERSION = 4 # electrum versions < 2.0 54 NEW_SEED_VERSION = 11 # electrum versions >= 2.0 55 FINAL_SEED_VERSION = 38 # electrum >= 2.7 will set this to prevent 56 # old versions from overwriting new format 57 58 59 class TxFeesValue(NamedTuple): 60 fee: Optional[int] = None 61 is_calculated_by_us: bool = False 62 num_inputs: Optional[int] = None 63 64 65 class WalletDB(JsonDB): 66 67 def __init__(self, raw, *, manual_upgrades: bool): 68 JsonDB.__init__(self, {}) 69 self._manual_upgrades = manual_upgrades 70 self._called_after_upgrade_tasks = False 71 if raw: # loading existing db 72 self.load_data(raw) 73 self.load_plugins() 74 else: # creating new db 75 self.put('seed_version', FINAL_SEED_VERSION) 76 self._after_upgrade_tasks() 77 78 def load_data(self, s): 79 try: 80 self.data = json.loads(s) 81 except: 82 try: 83 d = ast.literal_eval(s) 84 labels = d.get('labels', {}) 85 except Exception as e: 86 raise WalletFileException("Cannot read wallet file. (parsing failed)") 87 self.data = {} 88 for key, value in d.items(): 89 try: 90 json.dumps(key) 91 json.dumps(value) 92 except: 93 self.logger.info(f'Failed to convert label to json format: {key}') 94 continue 95 self.data[key] = value 96 if not isinstance(self.data, dict): 97 raise WalletFileException("Malformed wallet file (not dict)") 98 99 if not self._manual_upgrades and self.requires_split(): 100 raise WalletFileException("This wallet has multiple accounts and must be split") 101 102 if not self.requires_upgrade(): 103 self._after_upgrade_tasks() 104 elif not self._manual_upgrades: 105 self.upgrade() 106 107 def requires_split(self): 108 d = self.get('accounts', {}) 109 return len(d) > 1 110 111 def get_split_accounts(self): 112 result = [] 113 # backward compatibility with old wallets 114 d = self.get('accounts', {}) 115 if len(d) < 2: 116 return 117 wallet_type = self.get('wallet_type') 118 if wallet_type == 'old': 119 assert len(d) == 2 120 data1 = copy.deepcopy(self.data) 121 data1['accounts'] = {'0': d['0']} 122 data1['suffix'] = 'deterministic' 123 data2 = copy.deepcopy(self.data) 124 data2['accounts'] = {'/x': d['/x']} 125 data2['seed'] = None 126 data2['seed_version'] = None 127 data2['master_public_key'] = None 128 data2['wallet_type'] = 'imported' 129 data2['suffix'] = 'imported' 130 result = [data1, data2] 131 132 elif wallet_type in ['bip44', 'trezor', 'keepkey', 'ledger', 'btchip', 'digitalbitbox', 'safe_t']: 133 mpk = self.get('master_public_keys') 134 for k in d.keys(): 135 i = int(k) 136 x = d[k] 137 if x.get("pending"): 138 continue 139 xpub = mpk["x/%d'"%i] 140 new_data = copy.deepcopy(self.data) 141 # save account, derivation and xpub at index 0 142 new_data['accounts'] = {'0': x} 143 new_data['master_public_keys'] = {"x/0'": xpub} 144 new_data['derivation'] = bip44_derivation(k) 145 new_data['suffix'] = k 146 result.append(new_data) 147 else: 148 raise WalletFileException("This wallet has multiple accounts and must be split") 149 return result 150 151 def requires_upgrade(self): 152 return self.get_seed_version() < FINAL_SEED_VERSION 153 154 @profiler 155 def upgrade(self): 156 self.logger.info('upgrading wallet format') 157 if self._called_after_upgrade_tasks: 158 # we need strict ordering between upgrade() and after_upgrade_tasks() 159 raise Exception("'after_upgrade_tasks' must NOT be called before 'upgrade'") 160 self._convert_imported() 161 self._convert_wallet_type() 162 self._convert_account() 163 self._convert_version_13_b() 164 self._convert_version_14() 165 self._convert_version_15() 166 self._convert_version_16() 167 self._convert_version_17() 168 self._convert_version_18() 169 self._convert_version_19() 170 self._convert_version_20() 171 self._convert_version_21() 172 self._convert_version_22() 173 self._convert_version_23() 174 self._convert_version_24() 175 self._convert_version_25() 176 self._convert_version_26() 177 self._convert_version_27() 178 self._convert_version_28() 179 self._convert_version_29() 180 self._convert_version_30() 181 self._convert_version_31() 182 self._convert_version_32() 183 self._convert_version_33() 184 self._convert_version_34() 185 self._convert_version_35() 186 self._convert_version_36() 187 self._convert_version_37() 188 self._convert_version_38() 189 self.put('seed_version', FINAL_SEED_VERSION) # just to be sure 190 191 self._after_upgrade_tasks() 192 193 def _after_upgrade_tasks(self): 194 self._called_after_upgrade_tasks = True 195 self._load_transactions() 196 197 def _convert_wallet_type(self): 198 if not self._is_upgrade_method_needed(0, 13): 199 return 200 201 wallet_type = self.get('wallet_type') 202 if wallet_type == 'btchip': wallet_type = 'ledger' 203 if self.get('keystore') or self.get('x1/') or wallet_type=='imported': 204 return False 205 assert not self.requires_split() 206 seed_version = self.get_seed_version() 207 seed = self.get('seed') 208 xpubs = self.get('master_public_keys') 209 xprvs = self.get('master_private_keys', {}) 210 mpk = self.get('master_public_key') 211 keypairs = self.get('keypairs') 212 key_type = self.get('key_type') 213 if seed_version == OLD_SEED_VERSION or wallet_type == 'old': 214 d = { 215 'type': 'old', 216 'seed': seed, 217 'mpk': mpk, 218 } 219 self.put('wallet_type', 'standard') 220 self.put('keystore', d) 221 222 elif key_type == 'imported': 223 d = { 224 'type': 'imported', 225 'keypairs': keypairs, 226 } 227 self.put('wallet_type', 'standard') 228 self.put('keystore', d) 229 230 elif wallet_type in ['xpub', 'standard']: 231 xpub = xpubs["x/"] 232 xprv = xprvs.get("x/") 233 d = { 234 'type': 'bip32', 235 'xpub': xpub, 236 'xprv': xprv, 237 'seed': seed, 238 } 239 self.put('wallet_type', 'standard') 240 self.put('keystore', d) 241 242 elif wallet_type in ['bip44']: 243 xpub = xpubs["x/0'"] 244 xprv = xprvs.get("x/0'") 245 d = { 246 'type': 'bip32', 247 'xpub': xpub, 248 'xprv': xprv, 249 } 250 self.put('wallet_type', 'standard') 251 self.put('keystore', d) 252 253 elif wallet_type in ['trezor', 'keepkey', 'ledger', 'digitalbitbox', 'safe_t']: 254 xpub = xpubs["x/0'"] 255 derivation = self.get('derivation', bip44_derivation(0)) 256 d = { 257 'type': 'hardware', 258 'hw_type': wallet_type, 259 'xpub': xpub, 260 'derivation': derivation, 261 } 262 self.put('wallet_type', 'standard') 263 self.put('keystore', d) 264 265 elif (wallet_type == '2fa') or multisig_type(wallet_type): 266 for key in xpubs.keys(): 267 d = { 268 'type': 'bip32', 269 'xpub': xpubs[key], 270 'xprv': xprvs.get(key), 271 } 272 if key == 'x1/' and seed: 273 d['seed'] = seed 274 self.put(key, d) 275 else: 276 raise WalletFileException('Unable to tell wallet type. Is this even a wallet file?') 277 # remove junk 278 self.put('master_public_key', None) 279 self.put('master_public_keys', None) 280 self.put('master_private_keys', None) 281 self.put('derivation', None) 282 self.put('seed', None) 283 self.put('keypairs', None) 284 self.put('key_type', None) 285 286 def _convert_version_13_b(self): 287 # version 13 is ambiguous, and has an earlier and a later structure 288 if not self._is_upgrade_method_needed(0, 13): 289 return 290 291 if self.get('wallet_type') == 'standard': 292 if self.get('keystore').get('type') == 'imported': 293 pubkeys = self.get('keystore').get('keypairs').keys() 294 d = {'change': []} 295 receiving_addresses = [] 296 for pubkey in pubkeys: 297 addr = bitcoin.pubkey_to_address('p2pkh', pubkey) 298 receiving_addresses.append(addr) 299 d['receiving'] = receiving_addresses 300 self.put('addresses', d) 301 self.put('pubkeys', None) 302 303 self.put('seed_version', 13) 304 305 def _convert_version_14(self): 306 # convert imported wallets for 3.0 307 if not self._is_upgrade_method_needed(13, 13): 308 return 309 310 if self.get('wallet_type') =='imported': 311 addresses = self.get('addresses') 312 if type(addresses) is list: 313 addresses = dict([(x, None) for x in addresses]) 314 self.put('addresses', addresses) 315 elif self.get('wallet_type') == 'standard': 316 if self.get('keystore').get('type')=='imported': 317 addresses = set(self.get('addresses').get('receiving')) 318 pubkeys = self.get('keystore').get('keypairs').keys() 319 assert len(addresses) == len(pubkeys) 320 d = {} 321 for pubkey in pubkeys: 322 addr = bitcoin.pubkey_to_address('p2pkh', pubkey) 323 assert addr in addresses 324 d[addr] = { 325 'pubkey': pubkey, 326 'redeem_script': None, 327 'type': 'p2pkh' 328 } 329 self.put('addresses', d) 330 self.put('pubkeys', None) 331 self.put('wallet_type', 'imported') 332 self.put('seed_version', 14) 333 334 def _convert_version_15(self): 335 if not self._is_upgrade_method_needed(14, 14): 336 return 337 if self.get('seed_type') == 'segwit': 338 # should not get here; get_seed_version should have caught this 339 raise Exception('unsupported derivation (development segwit, v14)') 340 self.put('seed_version', 15) 341 342 def _convert_version_16(self): 343 # fixes issue #3193 for Imported_Wallets with addresses 344 # also, previous versions allowed importing any garbage as an address 345 # which we now try to remove, see pr #3191 346 if not self._is_upgrade_method_needed(15, 15): 347 return 348 349 def remove_address(addr): 350 def remove_from_dict(dict_name): 351 d = self.get(dict_name, None) 352 if d is not None: 353 d.pop(addr, None) 354 self.put(dict_name, d) 355 356 def remove_from_list(list_name): 357 lst = self.get(list_name, None) 358 if lst is not None: 359 s = set(lst) 360 s -= {addr} 361 self.put(list_name, list(s)) 362 363 # note: we don't remove 'addr' from self.get('addresses') 364 remove_from_dict('addr_history') 365 remove_from_dict('labels') 366 remove_from_dict('payment_requests') 367 remove_from_list('frozen_addresses') 368 369 if self.get('wallet_type') == 'imported': 370 addresses = self.get('addresses') 371 assert isinstance(addresses, dict) 372 addresses_new = dict() 373 for address, details in addresses.items(): 374 if not bitcoin.is_address(address): 375 remove_address(address) 376 continue 377 if details is None: 378 addresses_new[address] = {} 379 else: 380 addresses_new[address] = details 381 self.put('addresses', addresses_new) 382 383 self.put('seed_version', 16) 384 385 def _convert_version_17(self): 386 # delete pruned_txo; construct spent_outpoints 387 if not self._is_upgrade_method_needed(16, 16): 388 return 389 390 self.put('pruned_txo', None) 391 392 transactions = self.get('transactions', {}) # txid -> raw_tx 393 spent_outpoints = defaultdict(dict) 394 for txid, raw_tx in transactions.items(): 395 tx = Transaction(raw_tx) 396 for txin in tx.inputs(): 397 if txin.is_coinbase_input(): 398 continue 399 prevout_hash = txin.prevout.txid.hex() 400 prevout_n = txin.prevout.out_idx 401 spent_outpoints[prevout_hash][str(prevout_n)] = txid 402 self.put('spent_outpoints', spent_outpoints) 403 404 self.put('seed_version', 17) 405 406 def _convert_version_18(self): 407 # delete verified_tx3 as its structure changed 408 if not self._is_upgrade_method_needed(17, 17): 409 return 410 self.put('verified_tx3', None) 411 self.put('seed_version', 18) 412 413 def _convert_version_19(self): 414 # delete tx_fees as its structure changed 415 if not self._is_upgrade_method_needed(18, 18): 416 return 417 self.put('tx_fees', None) 418 self.put('seed_version', 19) 419 420 def _convert_version_20(self): 421 # store 'derivation' (prefix) and 'root_fingerprint' in all xpub-based keystores. 422 # store explicit None values if we cannot retroactively determine them 423 if not self._is_upgrade_method_needed(19, 19): 424 return 425 426 from .bip32 import BIP32Node, convert_bip32_intpath_to_strpath 427 # note: This upgrade method reimplements bip32.root_fp_and_der_prefix_from_xkey. 428 # This is done deliberately, to avoid introducing that method as a dependency to this upgrade. 429 for ks_name in ('keystore', *['x{}/'.format(i) for i in range(1, 16)]): 430 ks = self.get(ks_name, None) 431 if ks is None: continue 432 xpub = ks.get('xpub', None) 433 if xpub is None: continue 434 bip32node = BIP32Node.from_xkey(xpub) 435 # derivation prefix 436 derivation_prefix = ks.get('derivation', None) 437 if derivation_prefix is None: 438 assert bip32node.depth >= 0, bip32node.depth 439 if bip32node.depth == 0: 440 derivation_prefix = 'm' 441 elif bip32node.depth == 1: 442 child_number_int = int.from_bytes(bip32node.child_number, 'big') 443 derivation_prefix = convert_bip32_intpath_to_strpath([child_number_int]) 444 ks['derivation'] = derivation_prefix 445 # root fingerprint 446 root_fingerprint = ks.get('ckcc_xfp', None) 447 if root_fingerprint is not None: 448 root_fingerprint = root_fingerprint.to_bytes(4, byteorder="little", signed=False).hex().lower() 449 if root_fingerprint is None: 450 if bip32node.depth == 0: 451 root_fingerprint = bip32node.calc_fingerprint_of_this_node().hex().lower() 452 elif bip32node.depth == 1: 453 root_fingerprint = bip32node.fingerprint.hex() 454 ks['root_fingerprint'] = root_fingerprint 455 ks.pop('ckcc_xfp', None) 456 self.put(ks_name, ks) 457 458 self.put('seed_version', 20) 459 460 def _convert_version_21(self): 461 if not self._is_upgrade_method_needed(20, 20): 462 return 463 channels = self.get('channels') 464 if channels: 465 for channel in channels: 466 channel['state'] = 'OPENING' 467 self.put('channels', channels) 468 self.put('seed_version', 21) 469 470 def _convert_version_22(self): 471 # construct prevouts_by_scripthash 472 if not self._is_upgrade_method_needed(21, 21): 473 return 474 475 from .bitcoin import script_to_scripthash 476 transactions = self.get('transactions', {}) # txid -> raw_tx 477 prevouts_by_scripthash = defaultdict(list) 478 for txid, raw_tx in transactions.items(): 479 tx = Transaction(raw_tx) 480 for idx, txout in enumerate(tx.outputs()): 481 outpoint = f"{txid}:{idx}" 482 scripthash = script_to_scripthash(txout.scriptpubkey.hex()) 483 prevouts_by_scripthash[scripthash].append((outpoint, txout.value)) 484 self.put('prevouts_by_scripthash', prevouts_by_scripthash) 485 486 self.put('seed_version', 22) 487 488 def _convert_version_23(self): 489 if not self._is_upgrade_method_needed(22, 22): 490 return 491 channels = self.get('channels', []) 492 LOCAL = 1 493 REMOTE = -1 494 for c in channels: 495 # move revocation store from remote_config 496 r = c['remote_config'].pop('revocation_store') 497 c['revocation_store'] = r 498 # convert fee updates 499 log = c.get('log', {}) 500 for sub in LOCAL, REMOTE: 501 l = log[str(sub)]['fee_updates'] 502 d = {} 503 for i, fu in enumerate(l): 504 d[str(i)] = { 505 'rate':fu['rate'], 506 'ctn_local':fu['ctns'][str(LOCAL)], 507 'ctn_remote':fu['ctns'][str(REMOTE)] 508 } 509 log[str(int(sub))]['fee_updates'] = d 510 self.data['channels'] = channels 511 512 self.data['seed_version'] = 23 513 514 def _convert_version_24(self): 515 if not self._is_upgrade_method_needed(23, 23): 516 return 517 channels = self.get('channels', []) 518 for c in channels: 519 # convert revocation store to dict 520 r = c['revocation_store'] 521 d = {} 522 for i in range(49): 523 v = r['buckets'][i] 524 if v is not None: 525 d[str(i)] = v 526 r['buckets'] = d 527 c['revocation_store'] = r 528 # convert channels to dict 529 self.data['channels'] = { x['channel_id']: x for x in channels } 530 # convert txi & txo 531 txi = self.get('txi', {}) 532 for tx_hash, d in list(txi.items()): 533 d2 = {} 534 for addr, l in d.items(): 535 d2[addr] = {} 536 for ser, v in l: 537 d2[addr][ser] = v 538 txi[tx_hash] = d2 539 self.data['txi'] = txi 540 txo = self.get('txo', {}) 541 for tx_hash, d in list(txo.items()): 542 d2 = {} 543 for addr, l in d.items(): 544 d2[addr] = {} 545 for n, v, cb in l: 546 d2[addr][str(n)] = (v, cb) 547 txo[tx_hash] = d2 548 self.data['txo'] = txo 549 550 self.data['seed_version'] = 24 551 552 def _convert_version_25(self): 553 if not self._is_upgrade_method_needed(24, 24): 554 return 555 # add 'type' field to onchain requests 556 requests = self.data.get('payment_requests', {}) 557 for k, r in list(requests.items()): 558 if r.get('address') == k: 559 requests[k] = { 560 'address': r['address'], 561 'amount': r.get('amount'), 562 'exp': r.get('exp'), 563 'id': r.get('id'), 564 'memo': r.get('memo'), 565 'time': r.get('time'), 566 'type': PR_TYPE_ONCHAIN, 567 } 568 # convert bip70 invoices 569 invoices = self.data.get('invoices', {}) 570 for k, r in list(invoices.items()): 571 data = r.get("hex") 572 if data: 573 pr = PaymentRequest(bytes.fromhex(data)) 574 if pr.id != k: 575 continue 576 invoices[k] = { 577 'type': PR_TYPE_ONCHAIN, 578 'amount': pr.get_amount(), 579 'bip70': data, 580 'exp': pr.get_expiration_date() - pr.get_time(), 581 'id': pr.id, 582 'message': pr.get_memo(), 583 'outputs': [x.to_legacy_tuple() for x in pr.get_outputs()], 584 'time': pr.get_time(), 585 'requestor': pr.get_requestor(), 586 } 587 self.data['seed_version'] = 25 588 589 def _convert_version_26(self): 590 if not self._is_upgrade_method_needed(25, 25): 591 return 592 channels = self.data.get('channels', {}) 593 channel_timestamps = self.data.pop('lightning_channel_timestamps', {}) 594 for channel_id, c in channels.items(): 595 item = channel_timestamps.get(channel_id) 596 if item: 597 funding_txid, funding_height, funding_timestamp, closing_txid, closing_height, closing_timestamp = item 598 if funding_txid: 599 c['funding_height'] = funding_txid, funding_height, funding_timestamp 600 if closing_txid: 601 c['closing_height'] = closing_txid, closing_height, closing_timestamp 602 self.data['seed_version'] = 26 603 604 def _convert_version_27(self): 605 if not self._is_upgrade_method_needed(26, 26): 606 return 607 channels = self.data.get('channels', {}) 608 for channel_id, c in channels.items(): 609 c['local_config']['htlc_minimum_msat'] = 1 610 self.data['seed_version'] = 27 611 612 def _convert_version_28(self): 613 if not self._is_upgrade_method_needed(27, 27): 614 return 615 channels = self.data.get('channels', {}) 616 for channel_id, c in channels.items(): 617 c['local_config']['channel_seed'] = None 618 self.data['seed_version'] = 28 619 620 def _convert_version_29(self): 621 if not self._is_upgrade_method_needed(28, 28): 622 return 623 requests = self.data.get('payment_requests', {}) 624 invoices = self.data.get('invoices', {}) 625 for d in [invoices, requests]: 626 for key, r in list(d.items()): 627 _type = r.get('type', 0) 628 item = { 629 'type': _type, 630 'message': r.get('message') or r.get('memo', ''), 631 'amount': r.get('amount'), 632 'exp': r.get('exp') or 0, 633 'time': r.get('time', 0), 634 } 635 if _type == PR_TYPE_ONCHAIN: 636 address = r.pop('address', None) 637 if address: 638 outputs = [(0, address, r.get('amount'))] 639 else: 640 outputs = r.get('outputs') 641 item.update({ 642 'outputs': outputs, 643 'id': r.get('id'), 644 'bip70': r.get('bip70'), 645 'requestor': r.get('requestor'), 646 }) 647 else: 648 item.update({ 649 'rhash': r['rhash'], 650 'invoice': r['invoice'], 651 }) 652 d[key] = item 653 self.data['seed_version'] = 29 654 655 def _convert_version_30(self): 656 if not self._is_upgrade_method_needed(29, 29): 657 return 658 659 from .invoices import PR_TYPE_ONCHAIN, PR_TYPE_LN 660 requests = self.data.get('payment_requests', {}) 661 invoices = self.data.get('invoices', {}) 662 for d in [invoices, requests]: 663 for key, item in list(d.items()): 664 _type = item['type'] 665 if _type == PR_TYPE_ONCHAIN: 666 item['amount_sat'] = item.pop('amount') 667 elif _type == PR_TYPE_LN: 668 amount_sat = item.pop('amount') 669 item['amount_msat'] = 1000 * amount_sat if amount_sat is not None else None 670 item.pop('exp') 671 item.pop('message') 672 item.pop('rhash') 673 item.pop('time') 674 else: 675 raise Exception(f"unknown invoice type: {_type}") 676 self.data['seed_version'] = 30 677 678 def _convert_version_31(self): 679 if not self._is_upgrade_method_needed(30, 30): 680 return 681 682 from .invoices import PR_TYPE_ONCHAIN 683 requests = self.data.get('payment_requests', {}) 684 invoices = self.data.get('invoices', {}) 685 for d in [invoices, requests]: 686 for key, item in list(d.items()): 687 if item['type'] == PR_TYPE_ONCHAIN: 688 item['amount_sat'] = item['amount_sat'] or 0 689 item['exp'] = item['exp'] or 0 690 item['time'] = item['time'] or 0 691 self.data['seed_version'] = 31 692 693 def _convert_version_32(self): 694 if not self._is_upgrade_method_needed(31, 31): 695 return 696 PR_TYPE_ONCHAIN = 0 697 invoices_old = self.data.get('invoices', {}) 698 invoices_new = {k: item for k, item in invoices_old.items() 699 if not (item['type'] == PR_TYPE_ONCHAIN and item['outputs'] is None)} 700 self.data['invoices'] = invoices_new 701 self.data['seed_version'] = 32 702 703 def _convert_version_33(self): 704 if not self._is_upgrade_method_needed(32, 32): 705 return 706 PR_TYPE_ONCHAIN = 0 707 requests = self.data.get('payment_requests', {}) 708 invoices = self.data.get('invoices', {}) 709 for d in [invoices, requests]: 710 for key, item in list(d.items()): 711 if item['type'] == PR_TYPE_ONCHAIN: 712 item['height'] = item.get('height') or 0 713 self.data['seed_version'] = 33 714 715 def _convert_version_34(self): 716 if not self._is_upgrade_method_needed(33, 33): 717 return 718 channels = self.data.get('channels', {}) 719 for key, item in channels.items(): 720 item['local_config']['upfront_shutdown_script'] = \ 721 item['local_config'].get('upfront_shutdown_script') or "" 722 item['remote_config']['upfront_shutdown_script'] = \ 723 item['remote_config'].get('upfront_shutdown_script') or "" 724 self.data['seed_version'] = 34 725 726 def _convert_version_35(self): 727 # same as 32, but for payment_requests 728 if not self._is_upgrade_method_needed(34, 34): 729 return 730 PR_TYPE_ONCHAIN = 0 731 requests_old = self.data.get('payment_requests', {}) 732 requests_new = {k: item for k, item in requests_old.items() 733 if not (item['type'] == PR_TYPE_ONCHAIN and item['outputs'] is None)} 734 self.data['payment_requests'] = requests_new 735 self.data['seed_version'] = 35 736 737 def _convert_version_36(self): 738 if not self._is_upgrade_method_needed(35, 35): 739 return 740 old_frozen_coins = self.data.get('frozen_coins', []) 741 new_frozen_coins = {coin: True for coin in old_frozen_coins} 742 self.data['frozen_coins'] = new_frozen_coins 743 self.data['seed_version'] = 36 744 745 def _convert_version_37(self): 746 if not self._is_upgrade_method_needed(36, 36): 747 return 748 payments = self.data.get('lightning_payments', {}) 749 for k, v in list(payments.items()): 750 amount_sat, direction, status = v 751 amount_msat = amount_sat * 1000 if amount_sat is not None else None 752 payments[k] = amount_msat, direction, status 753 self.data['lightning_payments'] = payments 754 self.data['seed_version'] = 37 755 756 def _convert_version_38(self): 757 if not self._is_upgrade_method_needed(37, 37): 758 return 759 PR_TYPE_ONCHAIN = 0 760 PR_TYPE_LN = 2 761 from .bitcoin import TOTAL_COIN_SUPPLY_LIMIT_IN_BTC, COIN 762 max_sats = TOTAL_COIN_SUPPLY_LIMIT_IN_BTC * COIN 763 requests = self.data.get('payment_requests', {}) 764 invoices = self.data.get('invoices', {}) 765 for d in [invoices, requests]: 766 for key, item in list(d.items()): 767 if item['type'] == PR_TYPE_ONCHAIN: 768 amount_sat = item['amount_sat'] 769 if amount_sat == '!': 770 continue 771 if not (isinstance(amount_sat, int) and 0 <= amount_sat <= max_sats): 772 del d[key] 773 elif item['type'] == PR_TYPE_LN: 774 amount_msat = item['amount_msat'] 775 if not amount_msat: 776 continue 777 if not (isinstance(amount_msat, int) and 0 <= amount_msat <= max_sats * 1000): 778 del d[key] 779 self.data['seed_version'] = 38 780 781 def _convert_imported(self): 782 if not self._is_upgrade_method_needed(0, 13): 783 return 784 785 # '/x' is the internal ID for imported accounts 786 d = self.get('accounts', {}).get('/x', {}).get('imported',{}) 787 if not d: 788 return False 789 addresses = [] 790 keypairs = {} 791 for addr, v in d.items(): 792 pubkey, privkey = v 793 if privkey: 794 keypairs[pubkey] = privkey 795 else: 796 addresses.append(addr) 797 if addresses and keypairs: 798 raise WalletFileException('mixed addresses and privkeys') 799 elif addresses: 800 self.put('addresses', addresses) 801 self.put('accounts', None) 802 elif keypairs: 803 self.put('wallet_type', 'standard') 804 self.put('key_type', 'imported') 805 self.put('keypairs', keypairs) 806 self.put('accounts', None) 807 else: 808 raise WalletFileException('no addresses or privkeys') 809 810 def _convert_account(self): 811 if not self._is_upgrade_method_needed(0, 13): 812 return 813 self.put('accounts', None) 814 815 def _is_upgrade_method_needed(self, min_version, max_version): 816 assert min_version <= max_version 817 cur_version = self.get_seed_version() 818 if cur_version > max_version: 819 return False 820 elif cur_version < min_version: 821 raise WalletFileException( 822 'storage upgrade: unexpected version {} (should be {}-{})' 823 .format(cur_version, min_version, max_version)) 824 else: 825 return True 826 827 @locked 828 def get_seed_version(self): 829 seed_version = self.get('seed_version') 830 if not seed_version: 831 seed_version = OLD_SEED_VERSION if len(self.get('master_public_key','')) == 128 else NEW_SEED_VERSION 832 if seed_version > FINAL_SEED_VERSION: 833 raise WalletFileException('This version of Electrum is too old to open this wallet.\n' 834 '(highest supported storage version: {}, version of this file: {})' 835 .format(FINAL_SEED_VERSION, seed_version)) 836 if seed_version==14 and self.get('seed_type') == 'segwit': 837 self._raise_unsupported_version(seed_version) 838 if seed_version >=12: 839 return seed_version 840 if seed_version not in [OLD_SEED_VERSION, NEW_SEED_VERSION]: 841 self._raise_unsupported_version(seed_version) 842 return seed_version 843 844 def _raise_unsupported_version(self, seed_version): 845 msg = f"Your wallet has an unsupported seed version: {seed_version}." 846 if seed_version in [5, 7, 8, 9, 10, 14]: 847 msg += "\n\nTo open this wallet, try 'git checkout seed_v%d'"%seed_version 848 if seed_version == 6: 849 # version 1.9.8 created v6 wallets when an incorrect seed was entered in the restore dialog 850 msg += '\n\nThis file was created because of a bug in version 1.9.8.' 851 if self.get('master_public_keys') is None and self.get('master_private_keys') is None and self.get('imported_keys') is None: 852 # pbkdf2 (at that time an additional dependency) was not included with the binaries, and wallet creation aborted. 853 msg += "\nIt does not contain any keys, and can safely be removed." 854 else: 855 # creation was complete if electrum was run from source 856 msg += "\nPlease open this file with Electrum 1.9.8, and move your coins to a new wallet." 857 raise WalletFileException(msg) 858 859 @locked 860 def get_txi_addresses(self, tx_hash: str) -> List[str]: 861 """Returns list of is_mine addresses that appear as inputs in tx.""" 862 assert isinstance(tx_hash, str) 863 return list(self.txi.get(tx_hash, {}).keys()) 864 865 @locked 866 def get_txo_addresses(self, tx_hash: str) -> List[str]: 867 """Returns list of is_mine addresses that appear as outputs in tx.""" 868 assert isinstance(tx_hash, str) 869 return list(self.txo.get(tx_hash, {}).keys()) 870 871 @locked 872 def get_txi_addr(self, tx_hash: str, address: str) -> Iterable[Tuple[str, int]]: 873 """Returns an iterable of (prev_outpoint, value).""" 874 assert isinstance(tx_hash, str) 875 assert isinstance(address, str) 876 d = self.txi.get(tx_hash, {}).get(address, {}) 877 return list(d.items()) 878 879 @locked 880 def get_txo_addr(self, tx_hash: str, address: str) -> Dict[int, Tuple[int, bool]]: 881 """Returns a dict: output_index -> (value, is_coinbase).""" 882 assert isinstance(tx_hash, str) 883 assert isinstance(address, str) 884 d = self.txo.get(tx_hash, {}).get(address, {}) 885 return {int(n): (v, cb) for (n, (v, cb)) in d.items()} 886 887 @modifier 888 def add_txi_addr(self, tx_hash: str, addr: str, ser: str, v: int) -> None: 889 assert isinstance(tx_hash, str) 890 assert isinstance(addr, str) 891 assert isinstance(ser, str) 892 assert isinstance(v, int) 893 if tx_hash not in self.txi: 894 self.txi[tx_hash] = {} 895 d = self.txi[tx_hash] 896 if addr not in d: 897 d[addr] = {} 898 d[addr][ser] = v 899 900 @modifier 901 def add_txo_addr(self, tx_hash: str, addr: str, n: Union[int, str], v: int, is_coinbase: bool) -> None: 902 n = str(n) 903 assert isinstance(tx_hash, str) 904 assert isinstance(addr, str) 905 assert isinstance(n, str) 906 assert isinstance(v, int) 907 assert isinstance(is_coinbase, bool) 908 if tx_hash not in self.txo: 909 self.txo[tx_hash] = {} 910 d = self.txo[tx_hash] 911 if addr not in d: 912 d[addr] = {} 913 d[addr][n] = (v, is_coinbase) 914 915 @locked 916 def list_txi(self) -> Sequence[str]: 917 return list(self.txi.keys()) 918 919 @locked 920 def list_txo(self) -> Sequence[str]: 921 return list(self.txo.keys()) 922 923 @modifier 924 def remove_txi(self, tx_hash: str) -> None: 925 assert isinstance(tx_hash, str) 926 self.txi.pop(tx_hash, None) 927 928 @modifier 929 def remove_txo(self, tx_hash: str) -> None: 930 assert isinstance(tx_hash, str) 931 self.txo.pop(tx_hash, None) 932 933 @locked 934 def list_spent_outpoints(self) -> Sequence[Tuple[str, str]]: 935 return [(h, n) 936 for h in self.spent_outpoints.keys() 937 for n in self.get_spent_outpoints(h) 938 ] 939 940 @locked 941 def get_spent_outpoints(self, prevout_hash: str) -> Sequence[str]: 942 assert isinstance(prevout_hash, str) 943 return list(self.spent_outpoints.get(prevout_hash, {}).keys()) 944 945 @locked 946 def get_spent_outpoint(self, prevout_hash: str, prevout_n: Union[int, str]) -> Optional[str]: 947 assert isinstance(prevout_hash, str) 948 prevout_n = str(prevout_n) 949 return self.spent_outpoints.get(prevout_hash, {}).get(prevout_n) 950 951 @modifier 952 def remove_spent_outpoint(self, prevout_hash: str, prevout_n: Union[int, str]) -> None: 953 assert isinstance(prevout_hash, str) 954 prevout_n = str(prevout_n) 955 self.spent_outpoints[prevout_hash].pop(prevout_n, None) 956 if not self.spent_outpoints[prevout_hash]: 957 self.spent_outpoints.pop(prevout_hash) 958 959 @modifier 960 def set_spent_outpoint(self, prevout_hash: str, prevout_n: Union[int, str], tx_hash: str) -> None: 961 assert isinstance(prevout_hash, str) 962 assert isinstance(tx_hash, str) 963 prevout_n = str(prevout_n) 964 if prevout_hash not in self.spent_outpoints: 965 self.spent_outpoints[prevout_hash] = {} 966 self.spent_outpoints[prevout_hash][prevout_n] = tx_hash 967 968 @modifier 969 def add_prevout_by_scripthash(self, scripthash: str, *, prevout: TxOutpoint, value: int) -> None: 970 assert isinstance(scripthash, str) 971 assert isinstance(prevout, TxOutpoint) 972 assert isinstance(value, int) 973 if scripthash not in self._prevouts_by_scripthash: 974 self._prevouts_by_scripthash[scripthash] = set() 975 self._prevouts_by_scripthash[scripthash].add((prevout.to_str(), value)) 976 977 @modifier 978 def remove_prevout_by_scripthash(self, scripthash: str, *, prevout: TxOutpoint, value: int) -> None: 979 assert isinstance(scripthash, str) 980 assert isinstance(prevout, TxOutpoint) 981 assert isinstance(value, int) 982 self._prevouts_by_scripthash[scripthash].discard((prevout.to_str(), value)) 983 if not self._prevouts_by_scripthash[scripthash]: 984 self._prevouts_by_scripthash.pop(scripthash) 985 986 @locked 987 def get_prevouts_by_scripthash(self, scripthash: str) -> Set[Tuple[TxOutpoint, int]]: 988 assert isinstance(scripthash, str) 989 prevouts_and_values = self._prevouts_by_scripthash.get(scripthash, set()) 990 return {(TxOutpoint.from_str(prevout), value) for prevout, value in prevouts_and_values} 991 992 @modifier 993 def add_transaction(self, tx_hash: str, tx: Transaction) -> None: 994 assert isinstance(tx_hash, str) 995 assert isinstance(tx, Transaction), tx 996 # note that tx might be a PartialTransaction 997 # serialize and de-serialize tx now. this might e.g. convert a complete PartialTx to a Tx 998 tx = tx_from_any(str(tx)) 999 if not tx_hash: 1000 raise Exception("trying to add tx to db without txid") 1001 if tx_hash != tx.txid(): 1002 raise Exception(f"trying to add tx to db with inconsistent txid: {tx_hash} != {tx.txid()}") 1003 # don't allow overwriting complete tx with partial tx 1004 tx_we_already_have = self.transactions.get(tx_hash, None) 1005 if tx_we_already_have is None or isinstance(tx_we_already_have, PartialTransaction): 1006 self.transactions[tx_hash] = tx 1007 1008 @modifier 1009 def remove_transaction(self, tx_hash: str) -> Optional[Transaction]: 1010 assert isinstance(tx_hash, str) 1011 return self.transactions.pop(tx_hash, None) 1012 1013 @locked 1014 def get_transaction(self, tx_hash: Optional[str]) -> Optional[Transaction]: 1015 if tx_hash is None: 1016 return None 1017 assert isinstance(tx_hash, str) 1018 return self.transactions.get(tx_hash) 1019 1020 @locked 1021 def list_transactions(self) -> Sequence[str]: 1022 return list(self.transactions.keys()) 1023 1024 @locked 1025 def get_history(self) -> Sequence[str]: 1026 return list(self.history.keys()) 1027 1028 def is_addr_in_history(self, addr: str) -> bool: 1029 # does not mean history is non-empty! 1030 assert isinstance(addr, str) 1031 return addr in self.history 1032 1033 @locked 1034 def get_addr_history(self, addr: str) -> Sequence[Tuple[str, int]]: 1035 assert isinstance(addr, str) 1036 return self.history.get(addr, []) 1037 1038 @modifier 1039 def set_addr_history(self, addr: str, hist) -> None: 1040 assert isinstance(addr, str) 1041 self.history[addr] = hist 1042 1043 @modifier 1044 def remove_addr_history(self, addr: str) -> None: 1045 assert isinstance(addr, str) 1046 self.history.pop(addr, None) 1047 1048 @locked 1049 def list_verified_tx(self) -> Sequence[str]: 1050 return list(self.verified_tx.keys()) 1051 1052 @locked 1053 def get_verified_tx(self, txid: str) -> Optional[TxMinedInfo]: 1054 assert isinstance(txid, str) 1055 if txid not in self.verified_tx: 1056 return None 1057 height, timestamp, txpos, header_hash = self.verified_tx[txid] 1058 return TxMinedInfo(height=height, 1059 conf=None, 1060 timestamp=timestamp, 1061 txpos=txpos, 1062 header_hash=header_hash) 1063 1064 @modifier 1065 def add_verified_tx(self, txid: str, info: TxMinedInfo): 1066 assert isinstance(txid, str) 1067 assert isinstance(info, TxMinedInfo) 1068 self.verified_tx[txid] = (info.height, info.timestamp, info.txpos, info.header_hash) 1069 1070 @modifier 1071 def remove_verified_tx(self, txid: str): 1072 assert isinstance(txid, str) 1073 self.verified_tx.pop(txid, None) 1074 1075 def is_in_verified_tx(self, txid: str) -> bool: 1076 assert isinstance(txid, str) 1077 return txid in self.verified_tx 1078 1079 @modifier 1080 def add_tx_fee_from_server(self, txid: str, fee_sat: Optional[int]) -> None: 1081 assert isinstance(txid, str) 1082 # note: when called with (fee_sat is None), rm currently saved value 1083 if txid not in self.tx_fees: 1084 self.tx_fees[txid] = TxFeesValue() 1085 tx_fees_value = self.tx_fees[txid] 1086 if tx_fees_value.is_calculated_by_us: 1087 return 1088 self.tx_fees[txid] = tx_fees_value._replace(fee=fee_sat, is_calculated_by_us=False) 1089 1090 @modifier 1091 def add_tx_fee_we_calculated(self, txid: str, fee_sat: Optional[int]) -> None: 1092 assert isinstance(txid, str) 1093 if fee_sat is None: 1094 return 1095 assert isinstance(fee_sat, int) 1096 if txid not in self.tx_fees: 1097 self.tx_fees[txid] = TxFeesValue() 1098 self.tx_fees[txid] = self.tx_fees[txid]._replace(fee=fee_sat, is_calculated_by_us=True) 1099 1100 @locked 1101 def get_tx_fee(self, txid: str, *, trust_server: bool = False) -> Optional[int]: 1102 assert isinstance(txid, str) 1103 """Returns tx_fee.""" 1104 tx_fees_value = self.tx_fees.get(txid) 1105 if tx_fees_value is None: 1106 return None 1107 if not trust_server and not tx_fees_value.is_calculated_by_us: 1108 return None 1109 return tx_fees_value.fee 1110 1111 @modifier 1112 def add_num_inputs_to_tx(self, txid: str, num_inputs: int) -> None: 1113 assert isinstance(txid, str) 1114 assert isinstance(num_inputs, int) 1115 if txid not in self.tx_fees: 1116 self.tx_fees[txid] = TxFeesValue() 1117 self.tx_fees[txid] = self.tx_fees[txid]._replace(num_inputs=num_inputs) 1118 1119 @locked 1120 def get_num_all_inputs_of_tx(self, txid: str) -> Optional[int]: 1121 assert isinstance(txid, str) 1122 tx_fees_value = self.tx_fees.get(txid) 1123 if tx_fees_value is None: 1124 return None 1125 return tx_fees_value.num_inputs 1126 1127 @locked 1128 def get_num_ismine_inputs_of_tx(self, txid: str) -> int: 1129 assert isinstance(txid, str) 1130 txins = self.txi.get(txid, {}) 1131 return sum([len(tupls) for addr, tupls in txins.items()]) 1132 1133 @modifier 1134 def remove_tx_fee(self, txid: str) -> None: 1135 assert isinstance(txid, str) 1136 self.tx_fees.pop(txid, None) 1137 1138 @locked 1139 def get_dict(self, name) -> dict: 1140 # Warning: interacts un-intuitively with 'put': certain parts 1141 # of 'data' will have pointers saved as separate variables. 1142 if name not in self.data: 1143 self.data[name] = {} 1144 return self.data[name] 1145 1146 @locked 1147 def num_change_addresses(self) -> int: 1148 return len(self.change_addresses) 1149 1150 @locked 1151 def num_receiving_addresses(self) -> int: 1152 return len(self.receiving_addresses) 1153 1154 @locked 1155 def get_change_addresses(self, *, slice_start=None, slice_stop=None) -> List[str]: 1156 # note: slicing makes a shallow copy 1157 return self.change_addresses[slice_start:slice_stop] 1158 1159 @locked 1160 def get_receiving_addresses(self, *, slice_start=None, slice_stop=None) -> List[str]: 1161 # note: slicing makes a shallow copy 1162 return self.receiving_addresses[slice_start:slice_stop] 1163 1164 @modifier 1165 def add_change_address(self, addr: str) -> None: 1166 assert isinstance(addr, str) 1167 self._addr_to_addr_index[addr] = (1, len(self.change_addresses)) 1168 self.change_addresses.append(addr) 1169 1170 @modifier 1171 def add_receiving_address(self, addr: str) -> None: 1172 assert isinstance(addr, str) 1173 self._addr_to_addr_index[addr] = (0, len(self.receiving_addresses)) 1174 self.receiving_addresses.append(addr) 1175 1176 @locked 1177 def get_address_index(self, address: str) -> Optional[Sequence[int]]: 1178 assert isinstance(address, str) 1179 return self._addr_to_addr_index.get(address) 1180 1181 @modifier 1182 def add_imported_address(self, addr: str, d: dict) -> None: 1183 assert isinstance(addr, str) 1184 self.imported_addresses[addr] = d 1185 1186 @modifier 1187 def remove_imported_address(self, addr: str) -> None: 1188 assert isinstance(addr, str) 1189 self.imported_addresses.pop(addr) 1190 1191 @locked 1192 def has_imported_address(self, addr: str) -> bool: 1193 assert isinstance(addr, str) 1194 return addr in self.imported_addresses 1195 1196 @locked 1197 def get_imported_addresses(self) -> Sequence[str]: 1198 return list(sorted(self.imported_addresses.keys())) 1199 1200 @locked 1201 def get_imported_address(self, addr: str) -> Optional[dict]: 1202 assert isinstance(addr, str) 1203 return self.imported_addresses.get(addr) 1204 1205 def load_addresses(self, wallet_type): 1206 """ called from Abstract_Wallet.__init__ """ 1207 if wallet_type == 'imported': 1208 self.imported_addresses = self.get_dict('addresses') # type: Dict[str, dict] 1209 else: 1210 self.get_dict('addresses') 1211 for name in ['receiving', 'change']: 1212 if name not in self.data['addresses']: 1213 self.data['addresses'][name] = [] 1214 self.change_addresses = self.data['addresses']['change'] 1215 self.receiving_addresses = self.data['addresses']['receiving'] 1216 self._addr_to_addr_index = {} # type: Dict[str, Sequence[int]] # key: address, value: (is_change, index) 1217 for i, addr in enumerate(self.receiving_addresses): 1218 self._addr_to_addr_index[addr] = (0, i) 1219 for i, addr in enumerate(self.change_addresses): 1220 self._addr_to_addr_index[addr] = (1, i) 1221 1222 @profiler 1223 def _load_transactions(self): 1224 self.data = StoredDict(self.data, self, []) 1225 # references in self.data 1226 # TODO make all these private 1227 # txid -> address -> prev_outpoint -> value 1228 self.txi = self.get_dict('txi') # type: Dict[str, Dict[str, Dict[str, int]]] 1229 # txid -> address -> output_index -> (value, is_coinbase) 1230 self.txo = self.get_dict('txo') # type: Dict[str, Dict[str, Dict[str, Tuple[int, bool]]]] 1231 self.transactions = self.get_dict('transactions') # type: Dict[str, Transaction] 1232 self.spent_outpoints = self.get_dict('spent_outpoints') # txid -> output_index -> next_txid 1233 self.history = self.get_dict('addr_history') # address -> list of (txid, height) 1234 self.verified_tx = self.get_dict('verified_tx3') # txid -> (height, timestamp, txpos, header_hash) 1235 self.tx_fees = self.get_dict('tx_fees') # type: Dict[str, TxFeesValue] 1236 # scripthash -> set of (outpoint, value) 1237 self._prevouts_by_scripthash = self.get_dict('prevouts_by_scripthash') # type: Dict[str, Set[Tuple[str, int]]] 1238 # remove unreferenced tx 1239 for tx_hash in list(self.transactions.keys()): 1240 if not self.get_txi_addresses(tx_hash) and not self.get_txo_addresses(tx_hash): 1241 self.logger.info(f"removing unreferenced tx: {tx_hash}") 1242 self.transactions.pop(tx_hash) 1243 # remove unreferenced outpoints 1244 for prevout_hash in self.spent_outpoints.keys(): 1245 d = self.spent_outpoints[prevout_hash] 1246 for prevout_n, spending_txid in list(d.items()): 1247 if spending_txid not in self.transactions: 1248 self.logger.info("removing unreferenced spent outpoint") 1249 d.pop(prevout_n) 1250 1251 @modifier 1252 def clear_history(self): 1253 self.txi.clear() 1254 self.txo.clear() 1255 self.spent_outpoints.clear() 1256 self.transactions.clear() 1257 self.history.clear() 1258 self.verified_tx.clear() 1259 self.tx_fees.clear() 1260 self._prevouts_by_scripthash.clear() 1261 1262 def _convert_dict(self, path, key, v): 1263 if key == 'transactions': 1264 # note: for performance, "deserialize=False" so that we will deserialize these on-demand 1265 v = dict((k, tx_from_any(x, deserialize=False)) for k, x in v.items()) 1266 if key == 'invoices': 1267 v = dict((k, Invoice.from_json(x)) for k, x in v.items()) 1268 if key == 'payment_requests': 1269 v = dict((k, Invoice.from_json(x)) for k, x in v.items()) 1270 elif key == 'adds': 1271 v = dict((k, UpdateAddHtlc.from_tuple(*x)) for k, x in v.items()) 1272 elif key == 'fee_updates': 1273 v = dict((k, FeeUpdate(**x)) for k, x in v.items()) 1274 elif key == 'submarine_swaps': 1275 v = dict((k, SwapData(**x)) for k, x in v.items()) 1276 elif key == 'channel_backups': 1277 v = dict((k, ChannelBackupStorage(**x)) for k, x in v.items()) 1278 elif key == 'tx_fees': 1279 v = dict((k, TxFeesValue(*x)) for k, x in v.items()) 1280 elif key == 'prevouts_by_scripthash': 1281 v = dict((k, {(prevout, value) for (prevout, value) in x}) for k, x in v.items()) 1282 elif key == 'buckets': 1283 v = dict((k, ShachainElement(bfh(x[0]), int(x[1]))) for k, x in v.items()) 1284 elif key == 'data_loss_protect_remote_pcp': 1285 v = dict((k, bfh(x)) for k, x in v.items()) 1286 return v 1287 1288 def _convert_value(self, path, key, v): 1289 if key == 'local_config': 1290 v = LocalConfig(**v) 1291 elif key == 'remote_config': 1292 v = RemoteConfig(**v) 1293 elif key == 'constraints': 1294 v = ChannelConstraints(**v) 1295 elif key == 'funding_outpoint': 1296 v = Outpoint(**v) 1297 return v 1298 1299 def _should_convert_to_stored_dict(self, key) -> bool: 1300 if key == 'keystore': 1301 return False 1302 multisig_keystore_names = [('x%d/' % i) for i in range(1, 16)] 1303 if key in multisig_keystore_names: 1304 return False 1305 return True 1306 1307 def write(self, storage: 'WalletStorage'): 1308 with self.lock: 1309 self._write(storage) 1310 1311 @profiler 1312 def _write(self, storage: 'WalletStorage'): 1313 if threading.currentThread().isDaemon(): 1314 self.logger.warning('daemon thread cannot write db') 1315 return 1316 if not self.modified(): 1317 return 1318 json_str = self.dump(human_readable=not storage.is_encrypted()) 1319 storage.write(json_str) 1320 self.set_modified(False) 1321 1322 def is_ready_to_be_used_by_wallet(self): 1323 return not self.requires_upgrade() and self._called_after_upgrade_tasks 1324 1325 def split_accounts(self, root_path): 1326 from .storage import WalletStorage 1327 out = [] 1328 result = self.get_split_accounts() 1329 for data in result: 1330 path = root_path + '.' + data['suffix'] 1331 storage = WalletStorage(path) 1332 db = WalletDB(json.dumps(data), manual_upgrades=False) 1333 db._called_after_upgrade_tasks = False 1334 db.upgrade() 1335 db.write(storage) 1336 out.append(path) 1337 return out 1338 1339 def get_action(self): 1340 action = run_hook('get_action', self) 1341 return action 1342 1343 def load_plugins(self): 1344 wallet_type = self.get('wallet_type') 1345 if wallet_type in plugin_loaders: 1346 plugin_loaders[wallet_type]() 1347 1348 def set_keystore_encryption(self, enable): 1349 self.put('use_encryption', enable)