electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

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)