URI: 
       twallet_db.py - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
       twallet_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)