electrum

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

wallet.py (136312B)


      1 # Electrum - lightweight Bitcoin client
      2 # Copyright (C) 2015 Thomas Voegtlin
      3 #
      4 # Permission is hereby granted, free of charge, to any person
      5 # obtaining a copy of this software and associated documentation files
      6 # (the "Software"), to deal in the Software without restriction,
      7 # including without limitation the rights to use, copy, modify, merge,
      8 # publish, distribute, sublicense, and/or sell copies of the Software,
      9 # and to permit persons to whom the Software is furnished to do so,
     10 # subject to the following conditions:
     11 #
     12 # The above copyright notice and this permission notice shall be
     13 # included in all copies or substantial portions of the Software.
     14 #
     15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
     16 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
     17 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
     18 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
     19 # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
     20 # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
     21 # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
     22 # SOFTWARE.
     23 
     24 # Wallet classes:
     25 #   - Imported_Wallet: imported addresses or single keys, 0 or 1 keystore
     26 #   - Standard_Wallet: one HD keystore, P2PKH-like scripts
     27 #   - Multisig_Wallet: several HD keystores, M-of-N OP_CHECKMULTISIG scripts
     28 
     29 import os
     30 import sys
     31 import random
     32 import time
     33 import json
     34 import copy
     35 import errno
     36 import traceback
     37 import operator
     38 import math
     39 from functools import partial
     40 from collections import defaultdict
     41 from numbers import Number
     42 from decimal import Decimal
     43 from typing import TYPE_CHECKING, List, Optional, Tuple, Union, NamedTuple, Sequence, Dict, Any, Set
     44 from abc import ABC, abstractmethod
     45 import itertools
     46 import threading
     47 import enum
     48 
     49 from aiorpcx import TaskGroup, timeout_after, TaskTimeout, ignore_after
     50 
     51 from .i18n import _
     52 from .bip32 import BIP32Node, convert_bip32_intpath_to_strpath, convert_bip32_path_to_list_of_uint32
     53 from .crypto import sha256
     54 from . import util
     55 from .util import (NotEnoughFunds, UserCancelled, profiler,
     56                    format_satoshis, format_fee_satoshis, NoDynamicFeeEstimates,
     57                    WalletFileException, BitcoinException, MultipleSpendMaxTxOutputs,
     58                    InvalidPassword, format_time, timestamp_to_datetime, Satoshis,
     59                    Fiat, bfh, bh2u, TxMinedInfo, quantize_feerate, create_bip21_uri, OrderedDictWithIndex)
     60 from .util import get_backup_dir
     61 from .simple_config import SimpleConfig, FEE_RATIO_HIGH_WARNING, FEERATE_WARNING_HIGH_FEE
     62 from .bitcoin import COIN, TYPE_ADDRESS
     63 from .bitcoin import is_address, address_to_script, is_minikey, relayfee, dust_threshold
     64 from .crypto import sha256d
     65 from . import keystore
     66 from .keystore import (load_keystore, Hardware_KeyStore, KeyStore, KeyStoreWithMPK,
     67                        AddressIndexGeneric, CannotDerivePubkey)
     68 from .util import multisig_type
     69 from .storage import StorageEncryptionVersion, WalletStorage
     70 from .wallet_db import WalletDB
     71 from . import transaction, bitcoin, coinchooser, paymentrequest, ecc, bip32
     72 from .transaction import (Transaction, TxInput, UnknownTxinType, TxOutput,
     73                           PartialTransaction, PartialTxInput, PartialTxOutput, TxOutpoint)
     74 from .plugin import run_hook
     75 from .address_synchronizer import (AddressSynchronizer, TX_HEIGHT_LOCAL,
     76                                    TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_FUTURE)
     77 from .invoices import Invoice, OnchainInvoice, LNInvoice
     78 from .invoices import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED, PR_UNCONFIRMED, PR_TYPE_ONCHAIN, PR_TYPE_LN
     79 from .contacts import Contacts
     80 from .interface import NetworkException
     81 from .mnemonic import Mnemonic
     82 from .logging import get_logger
     83 from .lnworker import LNWallet
     84 from .paymentrequest import PaymentRequest
     85 from .util import read_json_file, write_json_file, UserFacingException
     86 
     87 if TYPE_CHECKING:
     88     from .network import Network
     89     from .exchange_rate import FxThread
     90 
     91 
     92 _logger = get_logger(__name__)
     93 
     94 TX_STATUS = [
     95     _('Unconfirmed'),
     96     _('Unconfirmed parent'),
     97     _('Not Verified'),
     98     _('Local'),
     99 ]
    100 
    101 
    102 class BumpFeeStrategy(enum.Enum):
    103     COINCHOOSER = enum.auto()
    104     DECREASE_CHANGE = enum.auto()
    105     DECREASE_PAYMENT = enum.auto()
    106 
    107 
    108 async def _append_utxos_to_inputs(*, inputs: List[PartialTxInput], network: 'Network',
    109                                   pubkey: str, txin_type: str, imax: int) -> None:
    110     if txin_type in ('p2pkh', 'p2wpkh', 'p2wpkh-p2sh'):
    111         address = bitcoin.pubkey_to_address(txin_type, pubkey)
    112         scripthash = bitcoin.address_to_scripthash(address)
    113     elif txin_type == 'p2pk':
    114         script = bitcoin.public_key_to_p2pk_script(pubkey)
    115         scripthash = bitcoin.script_to_scripthash(script)
    116     else:
    117         raise Exception(f'unexpected txin_type to sweep: {txin_type}')
    118 
    119     async def append_single_utxo(item):
    120         prev_tx_raw = await network.get_transaction(item['tx_hash'])
    121         prev_tx = Transaction(prev_tx_raw)
    122         prev_txout = prev_tx.outputs()[item['tx_pos']]
    123         if scripthash != bitcoin.script_to_scripthash(prev_txout.scriptpubkey.hex()):
    124             raise Exception('scripthash mismatch when sweeping')
    125         prevout_str = item['tx_hash'] + ':%d' % item['tx_pos']
    126         prevout = TxOutpoint.from_str(prevout_str)
    127         txin = PartialTxInput(prevout=prevout)
    128         txin.utxo = prev_tx
    129         txin.block_height = int(item['height'])
    130         txin.script_type = txin_type
    131         txin.pubkeys = [bfh(pubkey)]
    132         txin.num_sig = 1
    133         if txin_type == 'p2wpkh-p2sh':
    134             txin.redeem_script = bfh(bitcoin.p2wpkh_nested_script(pubkey))
    135         inputs.append(txin)
    136 
    137     u = await network.listunspent_for_scripthash(scripthash)
    138     async with TaskGroup() as group:
    139         for item in u:
    140             if len(inputs) >= imax:
    141                 break
    142             await group.spawn(append_single_utxo(item))
    143 
    144 
    145 async def sweep_preparations(privkeys, network: 'Network', imax=100):
    146 
    147     async def find_utxos_for_privkey(txin_type, privkey, compressed):
    148         pubkey = ecc.ECPrivkey(privkey).get_public_key_hex(compressed=compressed)
    149         await _append_utxos_to_inputs(
    150             inputs=inputs,
    151             network=network,
    152             pubkey=pubkey,
    153             txin_type=txin_type,
    154             imax=imax)
    155         keypairs[pubkey] = privkey, compressed
    156 
    157     inputs = []  # type: List[PartialTxInput]
    158     keypairs = {}
    159     async with TaskGroup() as group:
    160         for sec in privkeys:
    161             txin_type, privkey, compressed = bitcoin.deserialize_privkey(sec)
    162             await group.spawn(find_utxos_for_privkey(txin_type, privkey, compressed))
    163             # do other lookups to increase support coverage
    164             if is_minikey(sec):
    165                 # minikeys don't have a compressed byte
    166                 # we lookup both compressed and uncompressed pubkeys
    167                 await group.spawn(find_utxos_for_privkey(txin_type, privkey, not compressed))
    168             elif txin_type == 'p2pkh':
    169                 # WIF serialization does not distinguish p2pkh and p2pk
    170                 # we also search for pay-to-pubkey outputs
    171                 await group.spawn(find_utxos_for_privkey('p2pk', privkey, compressed))
    172     if not inputs:
    173         raise UserFacingException(_('No inputs found.'))
    174     return inputs, keypairs
    175 
    176 
    177 async def sweep(
    178         privkeys,
    179         *,
    180         network: 'Network',
    181         config: 'SimpleConfig',
    182         to_address: str,
    183         fee: int = None,
    184         imax=100,
    185         locktime=None,
    186         tx_version=None
    187 ) -> PartialTransaction:
    188     inputs, keypairs = await sweep_preparations(privkeys, network, imax)
    189     total = sum(txin.value_sats() for txin in inputs)
    190     if fee is None:
    191         outputs = [PartialTxOutput(scriptpubkey=bfh(bitcoin.address_to_script(to_address)),
    192                                    value=total)]
    193         tx = PartialTransaction.from_io(inputs, outputs)
    194         fee = config.estimate_fee(tx.estimated_size())
    195     if total - fee < 0:
    196         raise Exception(_('Not enough funds on address.') + '\nTotal: %d satoshis\nFee: %d'%(total, fee))
    197     if total - fee < dust_threshold(network):
    198         raise Exception(_('Not enough funds on address.') + '\nTotal: %d satoshis\nFee: %d\nDust Threshold: %d'%(total, fee, dust_threshold(network)))
    199 
    200     outputs = [PartialTxOutput(scriptpubkey=bfh(bitcoin.address_to_script(to_address)),
    201                                value=total - fee)]
    202     if locktime is None:
    203         locktime = get_locktime_for_new_transaction(network)
    204 
    205     tx = PartialTransaction.from_io(inputs, outputs, locktime=locktime, version=tx_version)
    206     rbf = bool(config.get('use_rbf', True))
    207     tx.set_rbf(rbf)
    208     tx.sign(keypairs)
    209     return tx
    210 
    211 
    212 def get_locktime_for_new_transaction(network: 'Network') -> int:
    213     # if no network or not up to date, just set locktime to zero
    214     if not network:
    215         return 0
    216     chain = network.blockchain()
    217     if chain.is_tip_stale():
    218         return 0
    219     # discourage "fee sniping"
    220     locktime = chain.height()
    221     # sometimes pick locktime a bit further back, to help privacy
    222     # of setups that need more time (offline/multisig/coinjoin/...)
    223     if random.randint(0, 9) == 0:
    224         locktime = max(0, locktime - random.randint(0, 99))
    225     return locktime
    226 
    227 
    228 
    229 class CannotBumpFee(Exception):
    230     def __str__(self):
    231         return _('Cannot bump fee') + ':\n\n' + Exception.__str__(self)
    232 
    233 class CannotDoubleSpendTx(Exception):
    234     def __str__(self):
    235         return _('Cannot cancel transaction') + ':\n\n' + Exception.__str__(self)
    236 
    237 class CannotCPFP(Exception):
    238     def __str__(self):
    239         return _('Cannot create child transaction') + ':\n\n' + Exception.__str__(self)
    240 
    241 class InternalAddressCorruption(Exception):
    242     def __str__(self):
    243         return _("Wallet file corruption detected. "
    244                  "Please restore your wallet from seed, and compare the addresses in both files")
    245 
    246 
    247 class TxWalletDetails(NamedTuple):
    248     txid: Optional[str]
    249     status: str
    250     label: str
    251     can_broadcast: bool
    252     can_bump: bool
    253     can_cpfp: bool
    254     can_dscancel: bool  # whether user can double-spend to self
    255     can_save_as_local: bool
    256     amount: Optional[int]
    257     fee: Optional[int]
    258     tx_mined_status: TxMinedInfo
    259     mempool_depth_bytes: Optional[int]
    260     can_remove: bool  # whether user should be allowed to delete tx
    261     is_lightning_funding_tx: bool
    262 
    263 
    264 class Abstract_Wallet(AddressSynchronizer, ABC):
    265     """
    266     Wallet classes are created to handle various address generation methods.
    267     Completion states (watching-only, single account, no seed, etc) are handled inside classes.
    268     """
    269 
    270     LOGGING_SHORTCUT = 'w'
    271     max_change_outputs = 3
    272     gap_limit_for_change = 10
    273 
    274     txin_type: str
    275     wallet_type: str
    276     lnworker: Optional['LNWallet']
    277 
    278     def __init__(self, db: WalletDB, storage: Optional[WalletStorage], *, config: SimpleConfig):
    279         if not db.is_ready_to_be_used_by_wallet():
    280             raise Exception("storage not ready to be used by Abstract_Wallet")
    281 
    282         self.config = config
    283         assert self.config is not None, "config must not be None"
    284         self.db = db
    285         self.storage = storage
    286         # load addresses needs to be called before constructor for sanity checks
    287         db.load_addresses(self.wallet_type)
    288         self.keystore = None  # type: Optional[KeyStore]  # will be set by load_keystore
    289         AddressSynchronizer.__init__(self, db)
    290 
    291         # saved fields
    292         self.use_change            = db.get('use_change', True)
    293         self.multiple_change       = db.get('multiple_change', False)
    294         self._labels                = db.get_dict('labels')
    295         self._frozen_addresses      = set(db.get('frozen_addresses', []))
    296         self._frozen_coins          = db.get_dict('frozen_coins')  # type: Dict[str, bool]
    297         self.fiat_value            = db.get_dict('fiat_value')
    298         self.receive_requests      = db.get_dict('payment_requests')  # type: Dict[str, Invoice]
    299         self.invoices              = db.get_dict('invoices')  # type: Dict[str, Invoice]
    300         self._reserved_addresses   = set(db.get('reserved_addresses', []))
    301 
    302         self._freeze_lock = threading.Lock()  # for mutating/iterating frozen_{addresses,coins}
    303 
    304         self._prepare_onchain_invoice_paid_detection()
    305         self.calc_unused_change_addresses()
    306         # save wallet type the first time
    307         if self.db.get('wallet_type') is None:
    308             self.db.put('wallet_type', self.wallet_type)
    309         self.contacts = Contacts(self.db)
    310         self._coin_price_cache = {}
    311 
    312         self.lnworker = None
    313 
    314     def save_db(self):
    315         if self.storage:
    316             self.db.write(self.storage)
    317 
    318     def save_backup(self):
    319         backup_dir = get_backup_dir(self.config)
    320         if backup_dir is None:
    321             return
    322         new_db = WalletDB(self.db.dump(), manual_upgrades=False)
    323 
    324         if self.lnworker:
    325             channel_backups = new_db.get_dict('channel_backups')
    326             for chan_id, chan in self.lnworker.channels.items():
    327                 channel_backups[chan_id.hex()] = self.lnworker.create_channel_backup(chan_id)
    328             new_db.put('channels', None)
    329             new_db.put('lightning_privkey2', None)
    330 
    331         new_path = os.path.join(backup_dir, self.basename() + '.backup')
    332         new_storage = WalletStorage(new_path)
    333         new_storage._encryption_version = self.storage._encryption_version
    334         new_storage.pubkey = self.storage.pubkey
    335         new_db.set_modified(True)
    336         new_db.write(new_storage)
    337         return new_path
    338 
    339     def has_lightning(self):
    340         return bool(self.lnworker)
    341 
    342     def can_have_lightning(self):
    343         # we want static_remotekey to be a wallet address
    344         return self.txin_type == 'p2wpkh'
    345 
    346     def init_lightning(self):
    347         assert self.can_have_lightning()
    348         if self.db.get('lightning_privkey2'):
    349             return
    350         # TODO derive this deterministically from wallet.keystore at keystore generation time
    351         # probably along a hardened path ( lnd-equivalent would be m/1017'/coinType'/ )
    352         seed = os.urandom(32)
    353         node = BIP32Node.from_rootseed(seed, xtype='standard')
    354         ln_xprv = node.to_xprv()
    355         self.db.put('lightning_privkey2', ln_xprv)
    356 
    357     async def stop(self):
    358         """Stop all networking and save DB to disk."""
    359         try:
    360             async with ignore_after(5):
    361                 await super().stop()
    362                 if self.network:
    363                     if self.lnworker:
    364                         await self.lnworker.stop()
    365                         self.lnworker = None
    366         finally:  # even if we get cancelled
    367             if any([ks.is_requesting_to_be_rewritten_to_wallet_file for ks in self.get_keystores()]):
    368                 self.save_keystore()
    369             self.save_db()
    370 
    371     def set_up_to_date(self, b):
    372         super().set_up_to_date(b)
    373         if b: self.save_db()
    374 
    375     def clear_history(self):
    376         super().clear_history()
    377         self.save_db()
    378 
    379     def start_network(self, network):
    380         AddressSynchronizer.start_network(self, network)
    381         if network:
    382             if self.lnworker:
    383                 self.lnworker.start_network(network)
    384                 # only start gossiping when we already have channels
    385                 if self.db.get('channels'):
    386                     self.network.start_gossip()
    387 
    388     def load_and_cleanup(self):
    389         self.load_keystore()
    390         self.test_addresses_sanity()
    391         super().load_and_cleanup()
    392 
    393     @abstractmethod
    394     def load_keystore(self) -> None:
    395         pass
    396 
    397     def diagnostic_name(self):
    398         return self.basename()
    399 
    400     def __str__(self):
    401         return self.basename()
    402 
    403     def get_master_public_key(self):
    404         return None
    405 
    406     def get_master_public_keys(self):
    407         return []
    408 
    409     def basename(self) -> str:
    410         return self.storage.basename() if self.storage else 'no name'
    411 
    412     def test_addresses_sanity(self) -> None:
    413         addrs = self.get_receiving_addresses()
    414         if len(addrs) > 0:
    415             addr = str(addrs[0])
    416             if not bitcoin.is_address(addr):
    417                 neutered_addr = addr[:5] + '..' + addr[-2:]
    418                 raise WalletFileException(f'The addresses in this wallet are not bitcoin addresses.\n'
    419                                           f'e.g. {neutered_addr} (length: {len(addr)})')
    420 
    421     def check_returned_address_for_corruption(func):
    422         def wrapper(self, *args, **kwargs):
    423             addr = func(self, *args, **kwargs)
    424             self.check_address_for_corruption(addr)
    425             return addr
    426         return wrapper
    427 
    428     def calc_unused_change_addresses(self) -> Sequence[str]:
    429         """Returns a list of change addresses to choose from, for usage in e.g. new transactions.
    430         The caller should give priority to earlier ones in the list.
    431         """
    432         with self.lock:
    433             # We want a list of unused change addresses.
    434             # As a performance optimisation, to avoid checking all addresses every time,
    435             # we maintain a list of "not old" addresses ("old" addresses have deeply confirmed history),
    436             # and only check those.
    437             if not hasattr(self, '_not_old_change_addresses'):
    438                 self._not_old_change_addresses = self.get_change_addresses()
    439             self._not_old_change_addresses = [addr for addr in self._not_old_change_addresses
    440                                               if not self.address_is_old(addr)]
    441             unused_addrs = [addr for addr in self._not_old_change_addresses
    442                             if not self.is_used(addr) and not self.is_address_reserved(addr)]
    443             return unused_addrs
    444 
    445     def is_deterministic(self) -> bool:
    446         return self.keystore.is_deterministic()
    447 
    448     def _set_label(self, key: str, value: Optional[str]) -> None:
    449         with self.lock:
    450             if value is None:
    451                 self._labels.pop(key, None)
    452             else:
    453                 self._labels[key] = value
    454 
    455     def set_label(self, name: str, text: str = None) -> bool:
    456         if not name:
    457             return False
    458         changed = False
    459         with self.lock:
    460             old_text = self._labels.get(name)
    461             if text:
    462                 text = text.replace("\n", " ")
    463                 if old_text != text:
    464                     self._labels[name] = text
    465                     changed = True
    466             else:
    467                 if old_text is not None:
    468                     self._labels.pop(name)
    469                     changed = True
    470         if changed:
    471             run_hook('set_label', self, name, text)
    472         return changed
    473 
    474     def import_labels(self, path):
    475         data = read_json_file(path)
    476         for key, value in data.items():
    477             self.set_label(key, value)
    478 
    479     def export_labels(self, path):
    480         write_json_file(path, self.get_all_labels())
    481 
    482     def set_fiat_value(self, txid, ccy, text, fx, value_sat):
    483         if not self.db.get_transaction(txid):
    484             return
    485         # since fx is inserting the thousands separator,
    486         # and not util, also have fx remove it
    487         text = fx.remove_thousands_separator(text)
    488         def_fiat = self.default_fiat_value(txid, fx, value_sat)
    489         formatted = fx.ccy_amount_str(def_fiat, commas=False)
    490         def_fiat_rounded = Decimal(formatted)
    491         reset = not text
    492         if not reset:
    493             try:
    494                 text_dec = Decimal(text)
    495                 text_dec_rounded = Decimal(fx.ccy_amount_str(text_dec, commas=False))
    496                 reset = text_dec_rounded == def_fiat_rounded
    497             except:
    498                 # garbage. not resetting, but not saving either
    499                 return False
    500         if reset:
    501             d = self.fiat_value.get(ccy, {})
    502             if d and txid in d:
    503                 d.pop(txid)
    504             else:
    505                 # avoid saving empty dict
    506                 return True
    507         else:
    508             if ccy not in self.fiat_value:
    509                 self.fiat_value[ccy] = {}
    510             self.fiat_value[ccy][txid] = text
    511         return reset
    512 
    513     def get_fiat_value(self, txid, ccy):
    514         fiat_value = self.fiat_value.get(ccy, {}).get(txid)
    515         try:
    516             return Decimal(fiat_value)
    517         except:
    518             return
    519 
    520     def is_mine(self, address) -> bool:
    521         if not address: return False
    522         return bool(self.get_address_index(address))
    523 
    524     def is_change(self, address) -> bool:
    525         if not self.is_mine(address):
    526             return False
    527         return self.get_address_index(address)[0] == 1
    528 
    529     @abstractmethod
    530     def get_address_index(self, address: str) -> Optional[AddressIndexGeneric]:
    531         pass
    532 
    533     @abstractmethod
    534     def get_address_path_str(self, address: str) -> Optional[str]:
    535         """Returns derivation path str such as "m/0/5" to address,
    536         or None if not applicable.
    537         """
    538         pass
    539 
    540     @abstractmethod
    541     def get_redeem_script(self, address: str) -> Optional[str]:
    542         pass
    543 
    544     @abstractmethod
    545     def get_witness_script(self, address: str) -> Optional[str]:
    546         pass
    547 
    548     @abstractmethod
    549     def get_txin_type(self, address: str) -> str:
    550         """Return script type of wallet address."""
    551         pass
    552 
    553     def export_private_key(self, address: str, password: Optional[str]) -> str:
    554         if self.is_watching_only():
    555             raise Exception(_("This is a watching-only wallet"))
    556         if not is_address(address):
    557             raise Exception(f"Invalid bitcoin address: {address}")
    558         if not self.is_mine(address):
    559             raise Exception(_('Address not in wallet.') + f' {address}')
    560         index = self.get_address_index(address)
    561         pk, compressed = self.keystore.get_private_key(index, password)
    562         txin_type = self.get_txin_type(address)
    563         serialized_privkey = bitcoin.serialize_privkey(pk, compressed, txin_type)
    564         return serialized_privkey
    565 
    566     def export_private_key_for_path(self, path: Union[Sequence[int], str], password: Optional[str]) -> str:
    567         raise Exception("this wallet is not deterministic")
    568 
    569     @abstractmethod
    570     def get_public_keys(self, address: str) -> Sequence[str]:
    571         pass
    572 
    573     def get_public_keys_with_deriv_info(self, address: str) -> Dict[bytes, Tuple[KeyStoreWithMPK, Sequence[int]]]:
    574         """Returns a map: pubkey -> (keystore, derivation_suffix)"""
    575         return {}
    576 
    577     def get_tx_info(self, tx: Transaction) -> TxWalletDetails:
    578         tx_wallet_delta = self.get_wallet_delta(tx)
    579         is_relevant = tx_wallet_delta.is_relevant
    580         is_any_input_ismine = tx_wallet_delta.is_any_input_ismine
    581         fee = tx_wallet_delta.fee
    582         exp_n = None
    583         can_broadcast = False
    584         can_bump = False
    585         can_cpfp = False
    586         tx_hash = tx.txid()  # note: txid can be None! e.g. when called from GUI tx dialog
    587         is_lightning_funding_tx = False
    588         if self.has_lightning() and tx_hash is not None:
    589             is_lightning_funding_tx = any([chan.funding_outpoint.txid == tx_hash
    590                                            for chan in self.lnworker.channels.values()])
    591         tx_we_already_have_in_db = self.db.get_transaction(tx_hash)
    592         can_save_as_local = (is_relevant and tx.txid() is not None
    593                              and (tx_we_already_have_in_db is None or not tx_we_already_have_in_db.is_complete()))
    594         label = ''
    595         tx_mined_status = self.get_tx_height(tx_hash)
    596         can_remove = ((tx_mined_status.height in [TX_HEIGHT_FUTURE, TX_HEIGHT_LOCAL])
    597                       # otherwise 'height' is unreliable (typically LOCAL):
    598                       and is_relevant
    599                       # don't offer during common signing flow, e.g. when watch-only wallet starts creating a tx:
    600                       and bool(tx_we_already_have_in_db))
    601         can_dscancel = False
    602         if tx.is_complete():
    603             if tx_we_already_have_in_db:
    604                 label = self.get_label_for_txid(tx_hash)
    605                 if tx_mined_status.height > 0:
    606                     if tx_mined_status.conf:
    607                         status = _("{} confirmations").format(tx_mined_status.conf)
    608                     else:
    609                         status = _('Not verified')
    610                 elif tx_mined_status.height in (TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED):
    611                     status = _('Unconfirmed')
    612                     if fee is None:
    613                         fee = self.get_tx_fee(tx_hash)
    614                     if fee and self.network and self.config.has_fee_mempool():
    615                         size = tx.estimated_size()
    616                         fee_per_byte = fee / size
    617                         exp_n = self.config.fee_to_depth(fee_per_byte)
    618                     can_bump = is_any_input_ismine and not tx.is_final()
    619                     can_dscancel = (is_any_input_ismine and not tx.is_final()
    620                                     and not all([self.is_mine(txout.address) for txout in tx.outputs()]))
    621                     try:
    622                         self.cpfp(tx, 0)
    623                         can_cpfp = True
    624                     except:
    625                         can_cpfp = False
    626                 else:
    627                     status = _('Local')
    628                     can_broadcast = self.network is not None
    629                     can_bump = is_any_input_ismine and not tx.is_final()
    630             else:
    631                 status = _("Signed")
    632                 can_broadcast = self.network is not None
    633         else:
    634             assert isinstance(tx, PartialTransaction)
    635             s, r = tx.signature_count()
    636             status = _("Unsigned") if s == 0 else _('Partially signed') + ' (%d/%d)'%(s,r)
    637 
    638         if is_relevant:
    639             if tx_wallet_delta.is_all_input_ismine:
    640                 assert fee is not None
    641                 amount = tx_wallet_delta.delta + fee
    642             else:
    643                 amount = tx_wallet_delta.delta
    644         else:
    645             amount = None
    646 
    647         if is_lightning_funding_tx:
    648             can_bump = False  # would change txid
    649 
    650         return TxWalletDetails(
    651             txid=tx_hash,
    652             status=status,
    653             label=label,
    654             can_broadcast=can_broadcast,
    655             can_bump=can_bump,
    656             can_cpfp=can_cpfp,
    657             can_dscancel=can_dscancel,
    658             can_save_as_local=can_save_as_local,
    659             amount=amount,
    660             fee=fee,
    661             tx_mined_status=tx_mined_status,
    662             mempool_depth_bytes=exp_n,
    663             can_remove=can_remove,
    664             is_lightning_funding_tx=is_lightning_funding_tx,
    665         )
    666 
    667     def get_spendable_coins(self, domain, *, nonlocal_only=False) -> Sequence[PartialTxInput]:
    668         confirmed_only = self.config.get('confirmed_only', False)
    669         with self._freeze_lock:
    670             frozen_addresses = self._frozen_addresses.copy()
    671         utxos = self.get_utxos(domain,
    672                                excluded_addresses=frozen_addresses,
    673                                mature_only=True,
    674                                confirmed_only=confirmed_only,
    675                                nonlocal_only=nonlocal_only)
    676         utxos = [utxo for utxo in utxos if not self.is_frozen_coin(utxo)]
    677         return utxos
    678 
    679     @abstractmethod
    680     def get_receiving_addresses(self, *, slice_start=None, slice_stop=None) -> Sequence[str]:
    681         pass
    682 
    683     @abstractmethod
    684     def get_change_addresses(self, *, slice_start=None, slice_stop=None) -> Sequence[str]:
    685         pass
    686 
    687     def dummy_address(self):
    688         # first receiving address
    689         return self.get_receiving_addresses(slice_start=0, slice_stop=1)[0]
    690 
    691     def get_frozen_balance(self):
    692         with self._freeze_lock:
    693             frozen_addresses = self._frozen_addresses.copy()
    694         # note: for coins, use is_frozen_coin instead of _frozen_coins,
    695         #       as latter only contains *manually* frozen ones
    696         frozen_coins = {utxo.prevout.to_str() for utxo in self.get_utxos()
    697                         if self.is_frozen_coin(utxo)}
    698         if not frozen_coins:  # shortcut
    699             return self.get_balance(frozen_addresses)
    700         c1, u1, x1 = self.get_balance()
    701         c2, u2, x2 = self.get_balance(
    702             excluded_addresses=frozen_addresses,
    703             excluded_coins=frozen_coins,
    704         )
    705         return c1-c2, u1-u2, x1-x2
    706 
    707     def balance_at_timestamp(self, domain, target_timestamp):
    708         # we assume that get_history returns items ordered by block height
    709         # we also assume that block timestamps are monotonic (which is false...!)
    710         h = self.get_history(domain=domain)
    711         balance = 0
    712         for hist_item in h:
    713             balance = hist_item.balance
    714             if hist_item.tx_mined_status.timestamp is None or hist_item.tx_mined_status.timestamp > target_timestamp:
    715                 return balance - hist_item.delta
    716         # return last balance
    717         return balance
    718 
    719     def get_onchain_history(self, *, domain=None):
    720         monotonic_timestamp = 0
    721         for hist_item in self.get_history(domain=domain):
    722             monotonic_timestamp = max(monotonic_timestamp, (hist_item.tx_mined_status.timestamp or 999_999_999_999))
    723             yield {
    724                 'txid': hist_item.txid,
    725                 'fee_sat': hist_item.fee,
    726                 'height': hist_item.tx_mined_status.height,
    727                 'confirmations': hist_item.tx_mined_status.conf,
    728                 'timestamp': hist_item.tx_mined_status.timestamp,
    729                 'monotonic_timestamp': monotonic_timestamp,
    730                 'incoming': True if hist_item.delta>0 else False,
    731                 'bc_value': Satoshis(hist_item.delta),
    732                 'bc_balance': Satoshis(hist_item.balance),
    733                 'date': timestamp_to_datetime(hist_item.tx_mined_status.timestamp),
    734                 'label': self.get_label_for_txid(hist_item.txid),
    735                 'txpos_in_block': hist_item.tx_mined_status.txpos,
    736             }
    737 
    738     def create_invoice(self, *, outputs: List[PartialTxOutput], message, pr, URI) -> Invoice:
    739         height=self.get_local_height()
    740         if pr:
    741             return OnchainInvoice.from_bip70_payreq(pr, height)
    742         if '!' in (x.value for x in outputs):
    743             amount = '!'
    744         else:
    745             amount = sum(x.value for x in outputs)
    746         timestamp = None
    747         exp = None
    748         if URI:
    749             timestamp = URI.get('time')
    750             exp = URI.get('exp')
    751         timestamp = timestamp or int(time.time())
    752         exp = exp or 0
    753         _id = bh2u(sha256d(repr(outputs) + "%d"%timestamp))[0:10]
    754         invoice = OnchainInvoice(
    755             type=PR_TYPE_ONCHAIN,
    756             amount_sat=amount,
    757             outputs=outputs,
    758             message=message,
    759             id=_id,
    760             time=timestamp,
    761             exp=exp,
    762             bip70=None,
    763             requestor=None,
    764             height=height,
    765         )
    766         return invoice
    767 
    768     def save_invoice(self, invoice: Invoice) -> None:
    769         key = self.get_key_for_outgoing_invoice(invoice)
    770         if not invoice.is_lightning():
    771             assert isinstance(invoice, OnchainInvoice)
    772             if self.is_onchain_invoice_paid(invoice, 0):
    773                 self.logger.info("saving invoice... but it is already paid!")
    774             with self.transaction_lock:
    775                 for txout in invoice.outputs:
    776                     self._invoices_from_scriptpubkey_map[txout.scriptpubkey].add(key)
    777         self.invoices[key] = invoice
    778         self.save_db()
    779 
    780     def clear_invoices(self):
    781         self.invoices = {}
    782         self.save_db()
    783 
    784     def clear_requests(self):
    785         self.receive_requests = {}
    786         self.save_db()
    787 
    788     def get_invoices(self):
    789         out = list(self.invoices.values())
    790         out.sort(key=lambda x:x.time)
    791         return out
    792 
    793     def get_unpaid_invoices(self):
    794         invoices = self.get_invoices()
    795         return [x for x in invoices if self.get_invoice_status(x) != PR_PAID]
    796 
    797     def get_invoice(self, key):
    798         return self.invoices.get(key)
    799 
    800     def import_requests(self, path):
    801         data = read_json_file(path)
    802         for x in data:
    803             req = Invoice.from_json(x)
    804             self.add_payment_request(req)
    805 
    806     def export_requests(self, path):
    807         write_json_file(path, list(self.receive_requests.values()))
    808 
    809     def import_invoices(self, path):
    810         data = read_json_file(path)
    811         for x in data:
    812             invoice = Invoice.from_json(x)
    813             self.save_invoice(invoice)
    814 
    815     def export_invoices(self, path):
    816         write_json_file(path, list(self.invoices.values()))
    817 
    818     def _get_relevant_invoice_keys_for_tx(self, tx: Transaction) -> Set[str]:
    819         relevant_invoice_keys = set()
    820         with self.transaction_lock:
    821             for txout in tx.outputs():
    822                 for invoice_key in self._invoices_from_scriptpubkey_map.get(txout.scriptpubkey, set()):
    823                     # note: the invoice might have been deleted since, so check now:
    824                     if invoice_key in self.invoices:
    825                         relevant_invoice_keys.add(invoice_key)
    826         return relevant_invoice_keys
    827 
    828     def get_relevant_invoices_for_tx(self, tx: Transaction) -> Sequence[OnchainInvoice]:
    829         invoice_keys = self._get_relevant_invoice_keys_for_tx(tx)
    830         invoices = [self.get_invoice(key) for key in invoice_keys]
    831         invoices = [inv for inv in invoices if inv]  # filter out None
    832         for inv in invoices:
    833             assert isinstance(inv, OnchainInvoice), f"unexpected type {type(inv)}"
    834         return invoices
    835 
    836     def _prepare_onchain_invoice_paid_detection(self):
    837         # scriptpubkey -> list(invoice_keys)
    838         self._invoices_from_scriptpubkey_map = defaultdict(set)  # type: Dict[bytes, Set[str]]
    839         for invoice_key, invoice in self.invoices.items():
    840             if invoice.type == PR_TYPE_ONCHAIN:
    841                 assert isinstance(invoice, OnchainInvoice)
    842                 for txout in invoice.outputs:
    843                     self._invoices_from_scriptpubkey_map[txout.scriptpubkey].add(invoice_key)
    844 
    845     def _is_onchain_invoice_paid(self, invoice: Invoice, conf: int) -> Tuple[bool, Sequence[str]]:
    846         """Returns whether on-chain invoice is satisfied, and list of relevant TXIDs."""
    847         assert invoice.type == PR_TYPE_ONCHAIN
    848         assert isinstance(invoice, OnchainInvoice)
    849         invoice_amounts = defaultdict(int)  # type: Dict[bytes, int]  # scriptpubkey -> value_sats
    850         for txo in invoice.outputs:  # type: PartialTxOutput
    851             invoice_amounts[txo.scriptpubkey] += 1 if txo.value == '!' else txo.value
    852         relevant_txs = []
    853         with self.transaction_lock:
    854             for invoice_scriptpubkey, invoice_amt in invoice_amounts.items():
    855                 scripthash = bitcoin.script_to_scripthash(invoice_scriptpubkey.hex())
    856                 prevouts_and_values = self.db.get_prevouts_by_scripthash(scripthash)
    857                 total_received = 0
    858                 for prevout, v in prevouts_and_values:
    859                     tx_height = self.get_tx_height(prevout.txid.hex())
    860                     if tx_height.height > 0 and tx_height.height <= invoice.height:
    861                         continue
    862                     if tx_height.conf < conf:
    863                         continue
    864                     total_received += v
    865                     relevant_txs.append(prevout.txid.hex())
    866                 # check that there is at least one TXO, and that they pay enough.
    867                 # note: "at least one TXO" check is needed for zero amount invoice (e.g. OP_RETURN)
    868                 if len(prevouts_and_values) == 0:
    869                     return False, []
    870                 if total_received < invoice_amt:
    871                     return False, []
    872         return True, relevant_txs
    873 
    874     def is_onchain_invoice_paid(self, invoice: Invoice, conf: int) -> bool:
    875         return self._is_onchain_invoice_paid(invoice, conf)[0]
    876 
    877     def _maybe_set_tx_label_based_on_invoices(self, tx: Transaction) -> bool:
    878         # note: this is not done in 'get_default_label' as that would require deserializing each tx
    879         tx_hash = tx.txid()
    880         labels = []
    881         for invoice in self.get_relevant_invoices_for_tx(tx):
    882             if invoice.message:
    883                 labels.append(invoice.message)
    884         if labels and not self._labels.get(tx_hash, ''):
    885             self.set_label(tx_hash, "; ".join(labels))
    886         return bool(labels)
    887 
    888     def add_transaction(self, tx, *, allow_unrelated=False):
    889         tx_was_added = super().add_transaction(tx, allow_unrelated=allow_unrelated)
    890 
    891         if tx_was_added:
    892             self._maybe_set_tx_label_based_on_invoices(tx)
    893         return tx_was_added
    894 
    895     @profiler
    896     def get_full_history(self, fx=None, *, onchain_domain=None, include_lightning=True):
    897         transactions_tmp = OrderedDictWithIndex()
    898         # add on-chain txns
    899         onchain_history = self.get_onchain_history(domain=onchain_domain)
    900         for tx_item in onchain_history:
    901             txid = tx_item['txid']
    902             transactions_tmp[txid] = tx_item
    903         # add lnworker onchain transactions
    904         lnworker_history = self.lnworker.get_onchain_history() if self.lnworker and include_lightning else {}
    905         for txid, item in lnworker_history.items():
    906             if txid in transactions_tmp:
    907                 tx_item = transactions_tmp[txid]
    908                 tx_item['group_id'] = item.get('group_id')  # for swaps
    909                 tx_item['label'] = item['label']
    910                 tx_item['type'] = item['type']
    911                 ln_value = Decimal(item['amount_msat']) / 1000   # for channel open/close tx
    912                 tx_item['ln_value'] = Satoshis(ln_value)
    913             else:
    914                 transactions_tmp[txid] = item
    915                 ln_value = Decimal(item['amount_msat']) / 1000   # for channel open/close tx
    916                 item['ln_value'] = Satoshis(ln_value)
    917         # add lightning_transactions
    918         lightning_history = self.lnworker.get_lightning_history() if self.lnworker and include_lightning else {}
    919         for tx_item in lightning_history.values():
    920             txid = tx_item.get('txid')
    921             ln_value = Decimal(tx_item['amount_msat']) / 1000
    922             tx_item['lightning'] = True
    923             tx_item['ln_value'] = Satoshis(ln_value)
    924             key = tx_item.get('txid') or tx_item['payment_hash']
    925             transactions_tmp[key] = tx_item
    926         # sort on-chain and LN stuff into new dict, by timestamp
    927         # (we rely on this being a *stable* sort)
    928         transactions = OrderedDictWithIndex()
    929         for k, v in sorted(list(transactions_tmp.items()),
    930                            key=lambda x: x[1].get('monotonic_timestamp') or x[1].get('timestamp') or float('inf')):
    931             transactions[k] = v
    932         now = time.time()
    933         balance = 0
    934         for item in transactions.values():
    935             # add on-chain and lightning values
    936             value = Decimal(0)
    937             if item.get('bc_value'):
    938                 value += item['bc_value'].value
    939             if item.get('ln_value'):
    940                 value += item.get('ln_value').value
    941             # note: 'value' and 'balance' has msat precision (as LN has msat precision)
    942             item['value'] = Satoshis(value)
    943             balance += value
    944             item['balance'] = Satoshis(balance)
    945             if fx and fx.is_enabled() and fx.get_history_config():
    946                 txid = item.get('txid')
    947                 if not item.get('lightning') and txid:
    948                     fiat_fields = self.get_tx_item_fiat(tx_hash=txid, amount_sat=value, fx=fx, tx_fee=item['fee_sat'])
    949                     item.update(fiat_fields)
    950                 else:
    951                     timestamp = item['timestamp'] or now
    952                     fiat_value = value / Decimal(bitcoin.COIN) * fx.timestamp_rate(timestamp)
    953                     item['fiat_value'] = Fiat(fiat_value, fx.ccy)
    954                     item['fiat_default'] = True
    955         return transactions
    956 
    957     @profiler
    958     def get_detailed_history(self, from_timestamp=None, to_timestamp=None,
    959                              fx=None, show_addresses=False, from_height=None, to_height=None):
    960         # History with capital gains, using utxo pricing
    961         # FIXME: Lightning capital gains would requires FIFO
    962         if (from_timestamp is not None or to_timestamp is not None) \
    963                 and (from_height is not None or to_height is not None):
    964             raise Exception('timestamp and block height based filtering cannot be used together')
    965         out = []
    966         income = 0
    967         expenditures = 0
    968         capital_gains = Decimal(0)
    969         fiat_income = Decimal(0)
    970         fiat_expenditures = Decimal(0)
    971         now = time.time()
    972         for item in self.get_onchain_history():
    973             timestamp = item['timestamp']
    974             if from_timestamp and (timestamp or now) < from_timestamp:
    975                 continue
    976             if to_timestamp and (timestamp or now) >= to_timestamp:
    977                 continue
    978             height = item['height']
    979             if from_height is not None and from_height > height > 0:
    980                 continue
    981             if to_height is not None and (height >= to_height or height <= 0):
    982                 continue
    983             tx_hash = item['txid']
    984             tx = self.db.get_transaction(tx_hash)
    985             tx_fee = item['fee_sat']
    986             item['fee'] = Satoshis(tx_fee) if tx_fee is not None else None
    987             if show_addresses:
    988                 item['inputs'] = list(map(lambda x: x.to_json(), tx.inputs()))
    989                 item['outputs'] = list(map(lambda x: {'address': x.get_ui_address_str(), 'value': Satoshis(x.value)},
    990                                            tx.outputs()))
    991             # fixme: use in and out values
    992             value = item['bc_value'].value
    993             if value < 0:
    994                 expenditures += -value
    995             else:
    996                 income += value
    997             # fiat computations
    998             if fx and fx.is_enabled() and fx.get_history_config():
    999                 fiat_fields = self.get_tx_item_fiat(tx_hash=tx_hash, amount_sat=value, fx=fx, tx_fee=tx_fee)
   1000                 fiat_value = fiat_fields['fiat_value'].value
   1001                 item.update(fiat_fields)
   1002                 if value < 0:
   1003                     capital_gains += fiat_fields['capital_gain'].value
   1004                     fiat_expenditures += -fiat_value
   1005                 else:
   1006                     fiat_income += fiat_value
   1007             out.append(item)
   1008         # add summary
   1009         if out:
   1010             b, v = out[0]['bc_balance'].value, out[0]['bc_value'].value
   1011             start_balance = None if b is None or v is None else b - v
   1012             end_balance = out[-1]['bc_balance'].value
   1013             if from_timestamp is not None and to_timestamp is not None:
   1014                 start_date = timestamp_to_datetime(from_timestamp)
   1015                 end_date = timestamp_to_datetime(to_timestamp)
   1016             else:
   1017                 start_date = None
   1018                 end_date = None
   1019             summary = {
   1020                 'start_date': start_date,
   1021                 'end_date': end_date,
   1022                 'from_height': from_height,
   1023                 'to_height': to_height,
   1024                 'start_balance': Satoshis(start_balance),
   1025                 'end_balance': Satoshis(end_balance),
   1026                 'incoming': Satoshis(income),
   1027                 'outgoing': Satoshis(expenditures)
   1028             }
   1029             if fx and fx.is_enabled() and fx.get_history_config():
   1030                 unrealized = self.unrealized_gains(None, fx.timestamp_rate, fx.ccy)
   1031                 summary['fiat_currency'] = fx.ccy
   1032                 summary['fiat_capital_gains'] = Fiat(capital_gains, fx.ccy)
   1033                 summary['fiat_incoming'] = Fiat(fiat_income, fx.ccy)
   1034                 summary['fiat_outgoing'] = Fiat(fiat_expenditures, fx.ccy)
   1035                 summary['fiat_unrealized_gains'] = Fiat(unrealized, fx.ccy)
   1036                 summary['fiat_start_balance'] = Fiat(fx.historical_value(start_balance, start_date), fx.ccy)
   1037                 summary['fiat_end_balance'] = Fiat(fx.historical_value(end_balance, end_date), fx.ccy)
   1038                 summary['fiat_start_value'] = Fiat(fx.historical_value(COIN, start_date), fx.ccy)
   1039                 summary['fiat_end_value'] = Fiat(fx.historical_value(COIN, end_date), fx.ccy)
   1040         else:
   1041             summary = {}
   1042         return {
   1043             'transactions': out,
   1044             'summary': summary
   1045         }
   1046 
   1047     def default_fiat_value(self, tx_hash, fx, value_sat):
   1048         return value_sat / Decimal(COIN) * self.price_at_timestamp(tx_hash, fx.timestamp_rate)
   1049 
   1050     def get_tx_item_fiat(
   1051             self,
   1052             *,
   1053             tx_hash: str,
   1054             amount_sat: int,
   1055             fx: 'FxThread',
   1056             tx_fee: Optional[int],
   1057     ) -> Dict[str, Any]:
   1058         item = {}
   1059         fiat_value = self.get_fiat_value(tx_hash, fx.ccy)
   1060         fiat_default = fiat_value is None
   1061         fiat_rate = self.price_at_timestamp(tx_hash, fx.timestamp_rate)
   1062         fiat_value = fiat_value if fiat_value is not None else self.default_fiat_value(tx_hash, fx, amount_sat)
   1063         fiat_fee = tx_fee / Decimal(COIN) * fiat_rate if tx_fee is not None else None
   1064         item['fiat_currency'] = fx.ccy
   1065         item['fiat_rate'] = Fiat(fiat_rate, fx.ccy)
   1066         item['fiat_value'] = Fiat(fiat_value, fx.ccy)
   1067         item['fiat_fee'] = Fiat(fiat_fee, fx.ccy) if fiat_fee else None
   1068         item['fiat_default'] = fiat_default
   1069         if amount_sat < 0:
   1070             acquisition_price = - amount_sat / Decimal(COIN) * self.average_price(tx_hash, fx.timestamp_rate, fx.ccy)
   1071             liquidation_price = - fiat_value
   1072             item['acquisition_price'] = Fiat(acquisition_price, fx.ccy)
   1073             cg = liquidation_price - acquisition_price
   1074             item['capital_gain'] = Fiat(cg, fx.ccy)
   1075         return item
   1076 
   1077     def get_label(self, key: str) -> str:
   1078         # key is typically: address / txid / LN-payment-hash-hex
   1079         return self._labels.get(key) or ''
   1080 
   1081     def get_label_for_txid(self, tx_hash: str) -> str:
   1082         return self._labels.get(tx_hash) or self._get_default_label_for_txid(tx_hash)
   1083 
   1084     def _get_default_label_for_txid(self, tx_hash: str) -> str:
   1085         # if no inputs are ismine, concat labels of output addresses
   1086         if not self.db.get_txi_addresses(tx_hash):
   1087             labels = []
   1088             for addr in self.db.get_txo_addresses(tx_hash):
   1089                 label = self._labels.get(addr)
   1090                 if label:
   1091                     labels.append(label)
   1092             return ', '.join(labels)
   1093         return ''
   1094 
   1095     def get_all_labels(self) -> Dict[str, str]:
   1096         with self.lock:
   1097             return copy.copy(self._labels)
   1098 
   1099     def get_tx_status(self, tx_hash, tx_mined_info: TxMinedInfo):
   1100         extra = []
   1101         height = tx_mined_info.height
   1102         conf = tx_mined_info.conf
   1103         timestamp = tx_mined_info.timestamp
   1104         if height == TX_HEIGHT_FUTURE:
   1105             assert conf < 0, conf
   1106             num_blocks_remainining = -conf
   1107             return 2, f'in {num_blocks_remainining} blocks'
   1108         if conf == 0:
   1109             tx = self.db.get_transaction(tx_hash)
   1110             if not tx:
   1111                 return 2, 'unknown'
   1112             is_final = tx and tx.is_final()
   1113             if not is_final:
   1114                 extra.append('rbf')
   1115             fee = self.get_tx_fee(tx_hash)
   1116             if fee is not None:
   1117                 size = tx.estimated_size()
   1118                 fee_per_byte = fee / size
   1119                 extra.append(format_fee_satoshis(fee_per_byte) + ' sat/b')
   1120             if fee is not None and height in (TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED) \
   1121                and self.config.has_fee_mempool():
   1122                 exp_n = self.config.fee_to_depth(fee_per_byte)
   1123                 if exp_n is not None:
   1124                     extra.append('%.2f MB'%(exp_n/1000000))
   1125             if height == TX_HEIGHT_LOCAL:
   1126                 status = 3
   1127             elif height == TX_HEIGHT_UNCONF_PARENT:
   1128                 status = 1
   1129             elif height == TX_HEIGHT_UNCONFIRMED:
   1130                 status = 0
   1131             else:
   1132                 status = 2  # not SPV verified
   1133         else:
   1134             status = 3 + min(conf, 6)
   1135         time_str = format_time(timestamp) if timestamp else _("unknown")
   1136         status_str = TX_STATUS[status] if status < 4 else time_str
   1137         if extra:
   1138             status_str += ' [%s]'%(', '.join(extra))
   1139         return status, status_str
   1140 
   1141     def relayfee(self):
   1142         return relayfee(self.network)
   1143 
   1144     def dust_threshold(self):
   1145         return dust_threshold(self.network)
   1146 
   1147     def get_unconfirmed_base_tx_for_batching(self) -> Optional[Transaction]:
   1148         candidate = None
   1149         for hist_item in self.get_history():
   1150             # tx should not be mined yet
   1151             if hist_item.tx_mined_status.conf > 0: continue
   1152             # conservative future proofing of code: only allow known unconfirmed types
   1153             if hist_item.tx_mined_status.height not in (TX_HEIGHT_UNCONFIRMED,
   1154                                                         TX_HEIGHT_UNCONF_PARENT,
   1155                                                         TX_HEIGHT_LOCAL):
   1156                 continue
   1157             # tx should be "outgoing" from wallet
   1158             if hist_item.delta >= 0:
   1159                 continue
   1160             tx = self.db.get_transaction(hist_item.txid)
   1161             if not tx:
   1162                 continue
   1163             # is_mine outputs should not be spent yet
   1164             # to avoid cancelling our own dependent transactions
   1165             txid = tx.txid()
   1166             if any([self.is_mine(o.address) and self.db.get_spent_outpoint(txid, output_idx)
   1167                     for output_idx, o in enumerate(tx.outputs())]):
   1168                 continue
   1169             # all inputs should be is_mine
   1170             if not all([self.is_mine(self.get_txin_address(txin)) for txin in tx.inputs()]):
   1171                 continue
   1172             # prefer txns already in mempool (vs local)
   1173             if hist_item.tx_mined_status.height == TX_HEIGHT_LOCAL:
   1174                 candidate = tx
   1175                 continue
   1176             # tx must have opted-in for RBF
   1177             if tx.is_final(): continue
   1178             return tx
   1179         return candidate
   1180 
   1181     def get_change_addresses_for_new_transaction(
   1182             self, preferred_change_addr=None, *, allow_reuse: bool = True,
   1183     ) -> List[str]:
   1184         change_addrs = []
   1185         if preferred_change_addr:
   1186             if isinstance(preferred_change_addr, (list, tuple)):
   1187                 change_addrs = list(preferred_change_addr)
   1188             else:
   1189                 change_addrs = [preferred_change_addr]
   1190         elif self.use_change:
   1191             # Recalc and get unused change addresses
   1192             addrs = self.calc_unused_change_addresses()
   1193             # New change addresses are created only after a few
   1194             # confirmations.
   1195             if addrs:
   1196                 # if there are any unused, select all
   1197                 change_addrs = addrs
   1198             else:
   1199                 # if there are none, take one randomly from the last few
   1200                 if not allow_reuse:
   1201                     return []
   1202                 addrs = self.get_change_addresses(slice_start=-self.gap_limit_for_change)
   1203                 change_addrs = [random.choice(addrs)] if addrs else []
   1204         for addr in change_addrs:
   1205             assert is_address(addr), f"not valid bitcoin address: {addr}"
   1206             # note that change addresses are not necessarily ismine
   1207             # in which case this is a no-op
   1208             self.check_address_for_corruption(addr)
   1209         max_change = self.max_change_outputs if self.multiple_change else 1
   1210         return change_addrs[:max_change]
   1211 
   1212     def get_single_change_address_for_new_transaction(
   1213             self, preferred_change_addr=None, *, allow_reuse: bool = True,
   1214     ) -> Optional[str]:
   1215         addrs = self.get_change_addresses_for_new_transaction(
   1216             preferred_change_addr=preferred_change_addr,
   1217             allow_reuse=allow_reuse,
   1218         )
   1219         if addrs:
   1220             return addrs[0]
   1221         return None
   1222 
   1223     @check_returned_address_for_corruption
   1224     def get_new_sweep_address_for_channel(self) -> str:
   1225         # Recalc and get unused change addresses
   1226         addrs = self.calc_unused_change_addresses()
   1227         if addrs:
   1228             selected_addr = addrs[0]
   1229         else:
   1230             # if there are none, take one randomly from the last few
   1231             addrs = self.get_change_addresses(slice_start=-self.gap_limit_for_change)
   1232             if addrs:
   1233                 selected_addr = random.choice(addrs)
   1234             else:  # fallback for e.g. imported wallets
   1235                 selected_addr = self.get_receiving_address()
   1236         assert is_address(selected_addr), f"not valid bitcoin address: {selected_addr}"
   1237         return selected_addr
   1238 
   1239     def make_unsigned_transaction(self, *, coins: Sequence[PartialTxInput],
   1240                                   outputs: List[PartialTxOutput], fee=None,
   1241                                   change_addr: str = None, is_sweep=False) -> PartialTransaction:
   1242 
   1243         if any([c.already_has_some_signatures() for c in coins]):
   1244             raise Exception("Some inputs already contain signatures!")
   1245 
   1246         # prevent side-effect with '!'
   1247         outputs = copy.deepcopy(outputs)
   1248 
   1249         # check outputs
   1250         i_max = None
   1251         for i, o in enumerate(outputs):
   1252             if o.value == '!':
   1253                 if i_max is not None:
   1254                     raise MultipleSpendMaxTxOutputs()
   1255                 i_max = i
   1256 
   1257         if fee is None and self.config.fee_per_kb() is None:
   1258             raise NoDynamicFeeEstimates()
   1259 
   1260         for item in coins:
   1261             self.add_input_info(item)
   1262 
   1263         # Fee estimator
   1264         if fee is None:
   1265             fee_estimator = self.config.estimate_fee
   1266         elif isinstance(fee, Number):
   1267             fee_estimator = lambda size: fee
   1268         elif callable(fee):
   1269             fee_estimator = fee
   1270         else:
   1271             raise Exception(f'Invalid argument fee: {fee}')
   1272 
   1273         if i_max is None:
   1274             # Let the coin chooser select the coins to spend
   1275             coin_chooser = coinchooser.get_coin_chooser(self.config)
   1276             # If there is an unconfirmed RBF tx, merge with it
   1277             base_tx = self.get_unconfirmed_base_tx_for_batching()
   1278             if self.config.get('batch_rbf', False) and base_tx:
   1279                 # make sure we don't try to spend change from the tx-to-be-replaced:
   1280                 coins = [c for c in coins if c.prevout.txid.hex() != base_tx.txid()]
   1281                 is_local = self.get_tx_height(base_tx.txid()).height == TX_HEIGHT_LOCAL
   1282                 base_tx = PartialTransaction.from_tx(base_tx)
   1283                 base_tx.add_info_from_wallet(self)
   1284                 base_tx_fee = base_tx.get_fee()
   1285                 relayfeerate = Decimal(self.relayfee()) / 1000
   1286                 original_fee_estimator = fee_estimator
   1287                 def fee_estimator(size: Union[int, float, Decimal]) -> int:
   1288                     size = Decimal(size)
   1289                     lower_bound = base_tx_fee + round(size * relayfeerate)
   1290                     lower_bound = lower_bound if not is_local else 0
   1291                     return int(max(lower_bound, original_fee_estimator(size)))
   1292                 txi = base_tx.inputs()
   1293                 txo = list(filter(lambda o: not self.is_change(o.address), base_tx.outputs()))
   1294                 old_change_addrs = [o.address for o in base_tx.outputs() if self.is_change(o.address)]
   1295             else:
   1296                 txi = []
   1297                 txo = []
   1298                 old_change_addrs = []
   1299             # change address. if empty, coin_chooser will set it
   1300             change_addrs = self.get_change_addresses_for_new_transaction(change_addr or old_change_addrs)
   1301             tx = coin_chooser.make_tx(coins=coins,
   1302                                       inputs=txi,
   1303                                       outputs=list(outputs) + txo,
   1304                                       change_addrs=change_addrs,
   1305                                       fee_estimator_vb=fee_estimator,
   1306                                       dust_threshold=self.dust_threshold())
   1307         else:
   1308             # "spend max" branch
   1309             # note: This *will* spend inputs with negative effective value (if there are any).
   1310             #       Given as the user is spending "max", and so might be abandoning the wallet,
   1311             #       try to include all UTXOs, otherwise leftover might remain in the UTXO set
   1312             #       forever. see #5433
   1313             # note: Actually it might be the case that not all UTXOs from the wallet are
   1314             #       being spent if the user manually selected UTXOs.
   1315             sendable = sum(map(lambda c: c.value_sats(), coins))
   1316             outputs[i_max].value = 0
   1317             tx = PartialTransaction.from_io(list(coins), list(outputs))
   1318             fee = fee_estimator(tx.estimated_size())
   1319             amount = sendable - tx.output_value() - fee
   1320             if amount < 0:
   1321                 raise NotEnoughFunds()
   1322             outputs[i_max].value = amount
   1323             tx = PartialTransaction.from_io(list(coins), list(outputs))
   1324 
   1325         # Timelock tx to current height.
   1326         tx.locktime = get_locktime_for_new_transaction(self.network)
   1327 
   1328         tx.set_rbf(False)  # caller can set RBF manually later
   1329         tx.add_info_from_wallet(self)
   1330         run_hook('make_unsigned_transaction', self, tx)
   1331         return tx
   1332 
   1333     def mktx(self, *, outputs: List[PartialTxOutput], password=None, fee=None, change_addr=None,
   1334              domain=None, rbf=False, nonlocal_only=False, tx_version=None, sign=True) -> PartialTransaction:
   1335         coins = self.get_spendable_coins(domain, nonlocal_only=nonlocal_only)
   1336         tx = self.make_unsigned_transaction(coins=coins,
   1337                                             outputs=outputs,
   1338                                             fee=fee,
   1339                                             change_addr=change_addr)
   1340         tx.set_rbf(rbf)
   1341         if tx_version is not None:
   1342             tx.version = tx_version
   1343         if sign:
   1344             self.sign_transaction(tx, password)
   1345         return tx
   1346 
   1347     def is_frozen_address(self, addr: str) -> bool:
   1348         return addr in self._frozen_addresses
   1349 
   1350     def is_frozen_coin(self, utxo: PartialTxInput) -> bool:
   1351         prevout_str = utxo.prevout.to_str()
   1352         frozen = self._frozen_coins.get(prevout_str, None)
   1353         # note: there are three possible states for 'frozen':
   1354         #       True/False if the user explicitly set it,
   1355         #       None otherwise
   1356         if frozen is None:
   1357             return self._is_coin_small_and_unconfirmed(utxo)
   1358         return bool(frozen)
   1359 
   1360     def _is_coin_small_and_unconfirmed(self, utxo: PartialTxInput) -> bool:
   1361         """If true, the coin should not be spent.
   1362         The idea here is that an attacker might send us a UTXO in a
   1363         large low-fee unconfirmed tx that will ~never confirm. If we
   1364         spend it as part of a tx ourselves, that too will not confirm
   1365         (unless we use a high fee but that might not be worth it for
   1366         a small value UTXO).
   1367         In particular, this test triggers for large "dusting transactions"
   1368         that are used for advertising purposes by some entities.
   1369         see #6960
   1370         """
   1371         # confirmed UTXOs are fine; check this first for performance:
   1372         block_height = utxo.block_height
   1373         assert block_height is not None
   1374         if block_height > 0:
   1375             return False
   1376         # exempt large value UTXOs
   1377         value_sats = utxo.value_sats()
   1378         assert value_sats is not None
   1379         threshold = self.config.get('unconf_utxo_freeze_threshold', 5_000)
   1380         if value_sats >= threshold:
   1381             return False
   1382         # if funding tx has any is_mine input, then UTXO is fine
   1383         funding_tx = self.db.get_transaction(utxo.prevout.txid.hex())
   1384         if funding_tx is None:
   1385             # we should typically have the funding tx available;
   1386             # might not have it e.g. while not up_to_date
   1387             return True
   1388         if any(self.is_mine(self.get_txin_address(txin))
   1389                for txin in funding_tx.inputs()):
   1390             return False
   1391         return True
   1392 
   1393     def set_frozen_state_of_addresses(self, addrs: Sequence[str], freeze: bool) -> bool:
   1394         """Set frozen state of the addresses to FREEZE, True or False"""
   1395         if all(self.is_mine(addr) for addr in addrs):
   1396             with self._freeze_lock:
   1397                 if freeze:
   1398                     self._frozen_addresses |= set(addrs)
   1399                 else:
   1400                     self._frozen_addresses -= set(addrs)
   1401                 self.db.put('frozen_addresses', list(self._frozen_addresses))
   1402                 return True
   1403         return False
   1404 
   1405     def set_frozen_state_of_coins(self, utxos: Sequence[str], freeze: bool) -> None:
   1406         """Set frozen state of the utxos to FREEZE, True or False"""
   1407         # basic sanity check that input is not garbage: (see if raises)
   1408         [TxOutpoint.from_str(utxo) for utxo in utxos]
   1409         with self._freeze_lock:
   1410             for utxo in utxos:
   1411                 self._frozen_coins[utxo] = bool(freeze)
   1412 
   1413     def is_address_reserved(self, addr: str) -> bool:
   1414         # note: atm 'reserved' status is only taken into consideration for 'change addresses'
   1415         return addr in self._reserved_addresses
   1416 
   1417     def set_reserved_state_of_address(self, addr: str, *, reserved: bool) -> None:
   1418         if not self.is_mine(addr):
   1419             return
   1420         with self.lock:
   1421             if reserved:
   1422                 self._reserved_addresses.add(addr)
   1423             else:
   1424                 self._reserved_addresses.discard(addr)
   1425             self.db.put('reserved_addresses', list(self._reserved_addresses))
   1426 
   1427     def can_export(self):
   1428         return not self.is_watching_only() and hasattr(self.keystore, 'get_private_key')
   1429 
   1430     def address_is_old(self, address: str, *, req_conf: int = 3) -> bool:
   1431         """Returns whether address has any history that is deeply confirmed.
   1432         Used for reorg-safe(ish) gap limit roll-forward.
   1433         """
   1434         max_conf = -1
   1435         h = self.db.get_addr_history(address)
   1436         needs_spv_check = not self.config.get("skipmerklecheck", False)
   1437         for tx_hash, tx_height in h:
   1438             if needs_spv_check:
   1439                 tx_age = self.get_tx_height(tx_hash).conf
   1440             else:
   1441                 if tx_height <= 0:
   1442                     tx_age = 0
   1443                 else:
   1444                     tx_age = self.get_local_height() - tx_height + 1
   1445             max_conf = max(max_conf, tx_age)
   1446         return max_conf >= req_conf
   1447 
   1448     def bump_fee(
   1449             self,
   1450             *,
   1451             tx: Transaction,
   1452             txid: str = None,
   1453             new_fee_rate: Union[int, float, Decimal],
   1454             coins: Sequence[PartialTxInput] = None,
   1455             strategies: Sequence[BumpFeeStrategy] = None,
   1456     ) -> PartialTransaction:
   1457         """Increase the miner fee of 'tx'.
   1458         'new_fee_rate' is the target min rate in sat/vbyte
   1459         'coins' is a list of UTXOs we can choose from as potential new inputs to be added
   1460         """
   1461         txid = txid or tx.txid()
   1462         assert txid
   1463         assert tx.txid() in (None, txid)
   1464         if not isinstance(tx, PartialTransaction):
   1465             tx = PartialTransaction.from_tx(tx)
   1466         assert isinstance(tx, PartialTransaction)
   1467         tx.remove_signatures()
   1468         if tx.is_final():
   1469             raise CannotBumpFee(_('Transaction is final'))
   1470         new_fee_rate = quantize_feerate(new_fee_rate)  # strip excess precision
   1471         try:
   1472             # note: this might download input utxos over network
   1473             tx.add_info_from_wallet(self, ignore_network_issues=False)
   1474         except NetworkException as e:
   1475             raise CannotBumpFee(repr(e))
   1476         old_tx_size = tx.estimated_size()
   1477         old_fee = tx.get_fee()
   1478         assert old_fee is not None
   1479         old_fee_rate = old_fee / old_tx_size  # sat/vbyte
   1480         if new_fee_rate <= old_fee_rate:
   1481             raise CannotBumpFee(_("The new fee rate needs to be higher than the old fee rate."))
   1482 
   1483         if not strategies:
   1484             strategies = [BumpFeeStrategy.COINCHOOSER, BumpFeeStrategy.DECREASE_CHANGE]
   1485         tx_new = None
   1486         exc = None
   1487         for strat in strategies:
   1488             try:
   1489                 if strat == BumpFeeStrategy.COINCHOOSER:
   1490                     tx_new = self._bump_fee_through_coinchooser(
   1491                         tx=tx,
   1492                         txid=txid,
   1493                         new_fee_rate=new_fee_rate,
   1494                         coins=coins,
   1495                     )
   1496                 elif strat == BumpFeeStrategy.DECREASE_CHANGE:
   1497                     tx_new = self._bump_fee_through_decreasing_change(
   1498                         tx=tx, new_fee_rate=new_fee_rate)
   1499                 elif strat == BumpFeeStrategy.DECREASE_PAYMENT:
   1500                     tx_new = self._bump_fee_through_decreasing_payment(
   1501                         tx=tx, new_fee_rate=new_fee_rate)
   1502                 else:
   1503                     raise NotImplementedError(f"unexpected strategy: {strat}")
   1504             except CannotBumpFee as e:
   1505                 exc = e
   1506             else:
   1507                 strat_used = strat
   1508                 break
   1509         if tx_new is None:
   1510             assert exc
   1511             raise exc  # all strategies failed, re-raise last exception
   1512 
   1513         target_min_fee = new_fee_rate * tx_new.estimated_size()
   1514         actual_fee = tx_new.get_fee()
   1515         if actual_fee + 1 < target_min_fee:
   1516             raise CannotBumpFee(
   1517                 f"bump_fee fee target was not met (strategy: {strat_used}). "
   1518                 f"got {actual_fee}, expected >={target_min_fee}. "
   1519                 f"target rate was {new_fee_rate}")
   1520         tx_new.locktime = get_locktime_for_new_transaction(self.network)
   1521         tx_new.set_rbf(True)
   1522         tx_new.add_info_from_wallet(self)
   1523         return tx_new
   1524 
   1525     def _bump_fee_through_coinchooser(
   1526             self,
   1527             *,
   1528             tx: PartialTransaction,
   1529             txid: str,
   1530             new_fee_rate: Union[int, Decimal],
   1531             coins: Sequence[PartialTxInput] = None,
   1532     ) -> PartialTransaction:
   1533         """Increase the miner fee of 'tx'.
   1534 
   1535         - keeps all inputs
   1536         - keeps all not is_mine outputs,
   1537         - allows adding new inputs
   1538         """
   1539         assert txid
   1540         tx = copy.deepcopy(tx)
   1541         tx.add_info_from_wallet(self)
   1542         assert tx.get_fee() is not None
   1543         old_inputs = list(tx.inputs())
   1544         old_outputs = list(tx.outputs())
   1545         # change address
   1546         old_change_addrs = [o.address for o in old_outputs if self.is_change(o.address)]
   1547         change_addrs = self.get_change_addresses_for_new_transaction(old_change_addrs)
   1548         # which outputs to keep?
   1549         if old_change_addrs:
   1550             fixed_outputs = list(filter(lambda o: not self.is_change(o.address), old_outputs))
   1551         else:
   1552             if all(self.is_mine(o.address) for o in old_outputs):
   1553                 # all outputs are is_mine and none of them are change.
   1554                 # we bail out as it's unclear what the user would want!
   1555                 # the coinchooser bump fee method is probably not a good idea in this case
   1556                 raise CannotBumpFee(_('All outputs are non-change is_mine'))
   1557             old_not_is_mine = list(filter(lambda o: not self.is_mine(o.address), old_outputs))
   1558             if old_not_is_mine:
   1559                 fixed_outputs = old_not_is_mine
   1560             else:
   1561                 fixed_outputs = old_outputs
   1562         if not fixed_outputs:
   1563             raise CannotBumpFee(_('Could not figure out which outputs to keep'))
   1564 
   1565         if coins is None:
   1566             coins = self.get_spendable_coins(None)
   1567         # make sure we don't try to spend output from the tx-to-be-replaced:
   1568         coins = [c for c in coins if c.prevout.txid.hex() != txid]
   1569         for item in coins:
   1570             self.add_input_info(item)
   1571         def fee_estimator(size):
   1572             return self.config.estimate_fee_for_feerate(fee_per_kb=new_fee_rate*1000, size=size)
   1573         coin_chooser = coinchooser.get_coin_chooser(self.config)
   1574         try:
   1575             return coin_chooser.make_tx(
   1576                 coins=coins,
   1577                 inputs=old_inputs,
   1578                 outputs=fixed_outputs,
   1579                 change_addrs=change_addrs,
   1580                 fee_estimator_vb=fee_estimator,
   1581                 dust_threshold=self.dust_threshold())
   1582         except NotEnoughFunds as e:
   1583             raise CannotBumpFee(e)
   1584 
   1585     def _bump_fee_through_decreasing_change(
   1586             self,
   1587             *,
   1588             tx: PartialTransaction,
   1589             new_fee_rate: Union[int, Decimal],
   1590     ) -> PartialTransaction:
   1591         """Increase the miner fee of 'tx'.
   1592 
   1593         - keeps all inputs
   1594         - no new inputs are added
   1595         - allows decreasing and removing outputs (change is decreased first)
   1596         This is less "safe" than "coinchooser" method as it might end up decreasing
   1597         e.g. a payment to a merchant; but e.g. if the user has sent "Max" previously,
   1598         this is the only way to RBF.
   1599         """
   1600         tx = copy.deepcopy(tx)
   1601         tx.add_info_from_wallet(self)
   1602         assert tx.get_fee() is not None
   1603         inputs = tx.inputs()
   1604         outputs = tx._outputs  # note: we will mutate this directly
   1605 
   1606         # use own outputs
   1607         s = list(filter(lambda o: self.is_mine(o.address), outputs))
   1608         # ... unless there is none
   1609         if not s:
   1610             s = outputs
   1611             x_fee = run_hook('get_tx_extra_fee', self, tx)
   1612             if x_fee:
   1613                 x_fee_address, x_fee_amount = x_fee
   1614                 s = list(filter(lambda o: o.address != x_fee_address, s))
   1615         if not s:
   1616             raise CannotBumpFee('No outputs at all??')
   1617 
   1618         # prioritize low value outputs, to get rid of dust
   1619         s = sorted(s, key=lambda o: o.value)
   1620         for o in s:
   1621             target_fee = int(math.ceil(tx.estimated_size() * new_fee_rate))
   1622             delta = target_fee - tx.get_fee()
   1623             i = outputs.index(o)
   1624             if o.value - delta >= self.dust_threshold():
   1625                 new_output_value = o.value - delta
   1626                 assert isinstance(new_output_value, int)
   1627                 outputs[i].value = new_output_value
   1628                 delta = 0
   1629                 break
   1630             else:
   1631                 del outputs[i]
   1632                 # note: we mutated the outputs of tx, which will affect
   1633                 #       tx.estimated_size() in the next iteration
   1634         if delta > 0:
   1635             raise CannotBumpFee(_('Could not find suitable outputs'))
   1636 
   1637         return PartialTransaction.from_io(inputs, outputs)
   1638 
   1639     def _bump_fee_through_decreasing_payment(
   1640             self,
   1641             *,
   1642             tx: PartialTransaction,
   1643             new_fee_rate: Union[int, Decimal],
   1644     ) -> PartialTransaction:
   1645         """Increase the miner fee of 'tx'.
   1646 
   1647         - keeps all inputs
   1648         - no new inputs are added
   1649         - decreases payment outputs (not change!). Each non-ismine output is decreased
   1650           proportionally to their byte-size.
   1651         """
   1652         tx = copy.deepcopy(tx)
   1653         tx.add_info_from_wallet(self)
   1654         assert tx.get_fee() is not None
   1655         inputs = tx.inputs()
   1656         outputs = tx.outputs()
   1657 
   1658         # select non-ismine outputs
   1659         s = [(idx, out) for (idx, out) in enumerate(outputs)
   1660              if not self.is_mine(out.address)]
   1661         # exempt 2fa fee output if present
   1662         x_fee = run_hook('get_tx_extra_fee', self, tx)
   1663         if x_fee:
   1664             x_fee_address, x_fee_amount = x_fee
   1665             s = [(idx, out) for (idx, out) in s if out.address != x_fee_address]
   1666         if not s:
   1667             raise CannotBumpFee("Cannot find payment output")
   1668 
   1669         del_out_idxs = set()
   1670         tx_size = tx.estimated_size()
   1671         cur_fee = tx.get_fee()
   1672         # Main loop. Each iteration decreases value of all selected outputs.
   1673         # The number of iterations is bounded by len(s) as only the final iteration
   1674         # can *not remove* any output.
   1675         for __ in range(len(s) + 1):
   1676             target_fee = int(math.ceil(tx_size * new_fee_rate))
   1677             delta_total = target_fee - cur_fee
   1678             if delta_total <= 0:
   1679                 break
   1680             out_size_total = sum(Transaction.estimated_output_size_for_script(out.scriptpubkey.hex())
   1681                                  for (idx, out) in s if idx not in del_out_idxs)
   1682             for idx, out in s:
   1683                 out_size = Transaction.estimated_output_size_for_script(out.scriptpubkey.hex())
   1684                 delta = int(math.ceil(delta_total * out_size / out_size_total))
   1685                 if out.value - delta >= self.dust_threshold():
   1686                     new_output_value = out.value - delta
   1687                     assert isinstance(new_output_value, int)
   1688                     outputs[idx].value = new_output_value
   1689                     cur_fee += delta
   1690                 else:  # remove output
   1691                     tx_size -= out_size
   1692                     cur_fee += out.value
   1693                     del_out_idxs.add(idx)
   1694         if delta_total > 0:
   1695             raise CannotBumpFee(_('Could not find suitable outputs'))
   1696 
   1697         outputs = [out for (idx, out) in enumerate(outputs) if idx not in del_out_idxs]
   1698         return PartialTransaction.from_io(inputs, outputs)
   1699 
   1700     def cpfp(self, tx: Transaction, fee: int) -> Optional[PartialTransaction]:
   1701         txid = tx.txid()
   1702         for i, o in enumerate(tx.outputs()):
   1703             address, value = o.address, o.value
   1704             if self.is_mine(address):
   1705                 break
   1706         else:
   1707             raise CannotCPFP(_("Could not find suitable output"))
   1708         coins = self.get_addr_utxo(address)
   1709         item = coins.get(TxOutpoint.from_str(txid+':%d'%i))
   1710         if not item:
   1711             raise CannotCPFP(_("Could not find coins for output"))
   1712         inputs = [item]
   1713         out_address = (self.get_single_change_address_for_new_transaction(allow_reuse=False)
   1714                        or self.get_unused_address()
   1715                        or address)
   1716         output_value = value - fee
   1717         if output_value < self.dust_threshold():
   1718             raise CannotCPFP(_("The output value remaining after fee is too low."))
   1719         outputs = [PartialTxOutput.from_address_and_value(out_address, output_value)]
   1720         locktime = get_locktime_for_new_transaction(self.network)
   1721         tx_new = PartialTransaction.from_io(inputs, outputs, locktime=locktime)
   1722         tx_new.set_rbf(True)
   1723         tx_new.add_info_from_wallet(self)
   1724         return tx_new
   1725 
   1726     def dscancel(
   1727             self, *, tx: Transaction, new_fee_rate: Union[int, float, Decimal]
   1728     ) -> PartialTransaction:
   1729         """Double-Spend-Cancel: cancel an unconfirmed tx by double-spending
   1730         its inputs, paying ourselves.
   1731         'new_fee_rate' is the target min rate in sat/vbyte
   1732         """
   1733         if not isinstance(tx, PartialTransaction):
   1734             tx = PartialTransaction.from_tx(tx)
   1735         assert isinstance(tx, PartialTransaction)
   1736         tx.remove_signatures()
   1737 
   1738         if tx.is_final():
   1739             raise CannotDoubleSpendTx(_('Transaction is final'))
   1740         new_fee_rate = quantize_feerate(new_fee_rate)  # strip excess precision
   1741         try:
   1742             # note: this might download input utxos over network
   1743             tx.add_info_from_wallet(self, ignore_network_issues=False)
   1744         except NetworkException as e:
   1745             raise CannotDoubleSpendTx(repr(e))
   1746         old_tx_size = tx.estimated_size()
   1747         old_fee = tx.get_fee()
   1748         assert old_fee is not None
   1749         old_fee_rate = old_fee / old_tx_size  # sat/vbyte
   1750         if new_fee_rate <= old_fee_rate:
   1751             raise CannotDoubleSpendTx(_("The new fee rate needs to be higher than the old fee rate."))
   1752         # grab all ismine inputs
   1753         inputs = [txin for txin in tx.inputs()
   1754                   if self.is_mine(self.get_txin_address(txin))]
   1755         value = sum([txin.value_sats() for txin in inputs])
   1756         # figure out output address
   1757         old_change_addrs = [o.address for o in tx.outputs() if self.is_mine(o.address)]
   1758         out_address = (self.get_single_change_address_for_new_transaction(old_change_addrs)
   1759                        or self.get_receiving_address())
   1760         locktime = get_locktime_for_new_transaction(self.network)
   1761         outputs = [PartialTxOutput.from_address_and_value(out_address, value)]
   1762         tx_new = PartialTransaction.from_io(inputs, outputs, locktime=locktime)
   1763         new_tx_size = tx_new.estimated_size()
   1764         new_fee = max(
   1765             new_fee_rate * new_tx_size,
   1766             old_fee + self.relayfee() * new_tx_size / Decimal(1000),  # BIP-125 rules 3 and 4
   1767         )
   1768         new_fee = int(math.ceil(new_fee))
   1769         output_value = value - new_fee
   1770         if output_value < self.dust_threshold():
   1771             raise CannotDoubleSpendTx(_("The output value remaining after fee is too low."))
   1772         outputs = [PartialTxOutput.from_address_and_value(out_address, value - new_fee)]
   1773         tx_new = PartialTransaction.from_io(inputs, outputs, locktime=locktime)
   1774         tx_new.set_rbf(True)
   1775         tx_new.add_info_from_wallet(self)
   1776         return tx_new
   1777 
   1778     @abstractmethod
   1779     def _add_input_sig_info(self, txin: PartialTxInput, address: str, *, only_der_suffix: bool) -> None:
   1780         pass
   1781 
   1782     def _add_txinout_derivation_info(self, txinout: Union[PartialTxInput, PartialTxOutput],
   1783                                      address: str, *, only_der_suffix: bool) -> None:
   1784         pass  # implemented by subclasses
   1785 
   1786     def _add_input_utxo_info(
   1787             self,
   1788             txin: PartialTxInput,
   1789             *,
   1790             address: str = None,
   1791             ignore_network_issues: bool = True,
   1792     ) -> None:
   1793         # We prefer to include UTXO (full tx) for every input.
   1794         # We cannot include UTXO if the prev tx is not signed yet though (chain of unsigned txs),
   1795         # in which case we might include a WITNESS_UTXO.
   1796         address = address or txin.address
   1797         if txin.witness_utxo is None and txin.is_segwit() and address:
   1798             received, spent = self.get_addr_io(address)
   1799             item = received.get(txin.prevout.to_str())
   1800             if item:
   1801                 txin_value = item[1]
   1802                 txin.witness_utxo = TxOutput.from_address_and_value(address, txin_value)
   1803         if txin.utxo is None:
   1804             txin.utxo = self.get_input_tx(txin.prevout.txid.hex(), ignore_network_issues=ignore_network_issues)
   1805         txin.ensure_there_is_only_one_utxo()
   1806 
   1807     def _learn_derivation_path_for_address_from_txinout(self, txinout: Union[PartialTxInput, PartialTxOutput],
   1808                                                         address: str) -> bool:
   1809         """Tries to learn the derivation path for an address (potentially beyond gap limit)
   1810         using data available in given txin/txout.
   1811         Returns whether the address was found to be is_mine.
   1812         """
   1813         return False  # implemented by subclasses
   1814 
   1815     def add_input_info(
   1816             self,
   1817             txin: PartialTxInput,
   1818             *,
   1819             only_der_suffix: bool = False,
   1820             ignore_network_issues: bool = True,
   1821     ) -> None:
   1822         address = self.get_txin_address(txin)
   1823         # note: we add input utxos regardless of is_mine
   1824         self._add_input_utxo_info(txin, ignore_network_issues=ignore_network_issues, address=address)
   1825         if not self.is_mine(address):
   1826             is_mine = self._learn_derivation_path_for_address_from_txinout(txin, address)
   1827             if not is_mine:
   1828                 return
   1829         # set script_type first, as later checks might rely on it:
   1830         txin.script_type = self.get_txin_type(address)
   1831         txin.num_sig = self.m if isinstance(self, Multisig_Wallet) else 1
   1832         if txin.redeem_script is None:
   1833             try:
   1834                 redeem_script_hex = self.get_redeem_script(address)
   1835                 txin.redeem_script = bfh(redeem_script_hex) if redeem_script_hex else None
   1836             except UnknownTxinType:
   1837                 pass
   1838         if txin.witness_script is None:
   1839             try:
   1840                 witness_script_hex = self.get_witness_script(address)
   1841                 txin.witness_script = bfh(witness_script_hex) if witness_script_hex else None
   1842             except UnknownTxinType:
   1843                 pass
   1844         self._add_input_sig_info(txin, address, only_der_suffix=only_der_suffix)
   1845 
   1846     def can_sign(self, tx: Transaction) -> bool:
   1847         if not isinstance(tx, PartialTransaction):
   1848             return False
   1849         if tx.is_complete():
   1850             return False
   1851         # add info to inputs if we can; otherwise we might return a false negative:
   1852         tx.add_info_from_wallet(self)
   1853         for txin in tx.inputs():
   1854             # note: is_mine check needed to avoid false positives.
   1855             #       just because keystore could sign, txin does not necessarily belong to wallet.
   1856             #       Example: we have p2pkh-like addresses and txin is a multisig that involves our pubkey.
   1857             if not self.is_mine(txin.address):
   1858                 continue
   1859             for k in self.get_keystores():
   1860                 if k.can_sign_txin(txin):
   1861                     return True
   1862         return False
   1863 
   1864     def get_input_tx(self, tx_hash: str, *, ignore_network_issues=False) -> Optional[Transaction]:
   1865         # First look up an input transaction in the wallet where it
   1866         # will likely be.  If co-signing a transaction it may not have
   1867         # all the input txs, in which case we ask the network.
   1868         tx = self.db.get_transaction(tx_hash)
   1869         if not tx and self.network and self.network.has_internet_connection():
   1870             try:
   1871                 raw_tx = self.network.run_from_another_thread(
   1872                     self.network.get_transaction(tx_hash, timeout=10))
   1873             except NetworkException as e:
   1874                 self.logger.info(f'got network error getting input txn. err: {repr(e)}. txid: {tx_hash}. '
   1875                                  f'if you are intentionally offline, consider using the --offline flag')
   1876                 if not ignore_network_issues:
   1877                     raise e
   1878             else:
   1879                 tx = Transaction(raw_tx)
   1880         if not tx and not ignore_network_issues:
   1881             raise NetworkException('failed to get prev tx from network')
   1882         return tx
   1883 
   1884     def add_output_info(self, txout: PartialTxOutput, *, only_der_suffix: bool = False) -> None:
   1885         address = txout.address
   1886         if not self.is_mine(address):
   1887             is_mine = self._learn_derivation_path_for_address_from_txinout(txout, address)
   1888             if not is_mine:
   1889                 return
   1890         txout.script_type = self.get_txin_type(address)
   1891         txout.is_mine = True
   1892         txout.is_change = self.is_change(address)
   1893         if isinstance(self, Multisig_Wallet):
   1894             txout.num_sig = self.m
   1895         self._add_txinout_derivation_info(txout, address, only_der_suffix=only_der_suffix)
   1896         if txout.redeem_script is None:
   1897             try:
   1898                 redeem_script_hex = self.get_redeem_script(address)
   1899                 txout.redeem_script = bfh(redeem_script_hex) if redeem_script_hex else None
   1900             except UnknownTxinType:
   1901                 pass
   1902         if txout.witness_script is None:
   1903             try:
   1904                 witness_script_hex = self.get_witness_script(address)
   1905                 txout.witness_script = bfh(witness_script_hex) if witness_script_hex else None
   1906             except UnknownTxinType:
   1907                 pass
   1908 
   1909     def sign_transaction(self, tx: Transaction, password) -> Optional[PartialTransaction]:
   1910         if self.is_watching_only():
   1911             return
   1912         if not isinstance(tx, PartialTransaction):
   1913             return
   1914         # add info to a temporary tx copy; including xpubs
   1915         # and full derivation paths as hw keystores might want them
   1916         tmp_tx = copy.deepcopy(tx)
   1917         tmp_tx.add_info_from_wallet(self, include_xpubs=True)
   1918         # sign. start with ready keystores.
   1919         for k in sorted(self.get_keystores(), key=lambda ks: ks.ready_to_sign(), reverse=True):
   1920             try:
   1921                 if k.can_sign(tmp_tx):
   1922                     k.sign_transaction(tmp_tx, password)
   1923             except UserCancelled:
   1924                 continue
   1925         # remove sensitive info; then copy back details from temporary tx
   1926         tmp_tx.remove_xpubs_and_bip32_paths()
   1927         tx.combine_with_other_psbt(tmp_tx)
   1928         tx.add_info_from_wallet(self, include_xpubs=False)
   1929         return tx
   1930 
   1931     def try_detecting_internal_addresses_corruption(self) -> None:
   1932         pass
   1933 
   1934     def check_address_for_corruption(self, addr: str) -> None:
   1935         pass
   1936 
   1937     def get_unused_addresses(self) -> Sequence[str]:
   1938         domain = self.get_receiving_addresses()
   1939         # TODO we should index receive_requests by id
   1940         in_use_by_request = [k for k in self.receive_requests.keys()
   1941                              if self.get_request_status(k) != PR_EXPIRED]
   1942         in_use_by_request = set(in_use_by_request)
   1943         return [addr for addr in domain if not self.is_used(addr)
   1944                 and addr not in in_use_by_request]
   1945 
   1946     @check_returned_address_for_corruption
   1947     def get_unused_address(self) -> Optional[str]:
   1948         """Get an unused receiving address, if there is one.
   1949         Note: there might NOT be one available!
   1950         """
   1951         addrs = self.get_unused_addresses()
   1952         if addrs:
   1953             return addrs[0]
   1954 
   1955     @check_returned_address_for_corruption
   1956     def get_receiving_address(self) -> str:
   1957         """Get a receiving address. Guaranteed to always return an address."""
   1958         unused_addr = self.get_unused_address()
   1959         if unused_addr:
   1960             return unused_addr
   1961         domain = self.get_receiving_addresses()
   1962         if not domain:
   1963             raise Exception("no receiving addresses in wallet?!")
   1964         choice = domain[0]
   1965         for addr in domain:
   1966             if not self.is_used(addr):
   1967                 if addr not in self.receive_requests.keys():
   1968                     return addr
   1969                 else:
   1970                     choice = addr
   1971         return choice
   1972 
   1973     def create_new_address(self, for_change: bool = False):
   1974         raise Exception("this wallet cannot generate new addresses")
   1975 
   1976     def import_address(self, address: str) -> str:
   1977         raise Exception("this wallet cannot import addresses")
   1978 
   1979     def import_addresses(self, addresses: List[str], *,
   1980                          write_to_disk=True) -> Tuple[List[str], List[Tuple[str, str]]]:
   1981         raise Exception("this wallet cannot import addresses")
   1982 
   1983     def delete_address(self, address: str) -> None:
   1984         raise Exception("this wallet cannot delete addresses")
   1985 
   1986     def get_onchain_request_status(self, r):
   1987         address = r.get_address()
   1988         amount = r.get_amount_sat()
   1989         received, sent = self.get_addr_io(address)
   1990         l = []
   1991         for txo, x in received.items():
   1992             h, v, is_cb = x
   1993             txid, n = txo.split(':')
   1994             tx_height = self.get_tx_height(txid)
   1995             height = tx_height.height
   1996             if height > 0 and height <= r.height:
   1997                 continue
   1998             conf = tx_height.conf
   1999             l.append((conf, v))
   2000         vsum = 0
   2001         for conf, v in reversed(sorted(l)):
   2002             vsum += v
   2003             if vsum >= amount:
   2004                 return True, conf
   2005         return False, None
   2006 
   2007     def get_request_URI(self, req: OnchainInvoice) -> str:
   2008         addr = req.get_address()
   2009         message = self.get_label(addr)
   2010         amount = req.amount_sat
   2011         extra_query_params = {}
   2012         if req.time:
   2013             extra_query_params['time'] = str(int(req.time))
   2014         if req.exp:
   2015             extra_query_params['exp'] = str(int(req.exp))
   2016         #if req.get('name') and req.get('sig'):
   2017         #    sig = bfh(req.get('sig'))
   2018         #    sig = bitcoin.base_encode(sig, base=58)
   2019         #    extra_query_params['name'] = req['name']
   2020         #    extra_query_params['sig'] = sig
   2021         uri = create_bip21_uri(addr, amount, message, extra_query_params=extra_query_params)
   2022         return str(uri)
   2023 
   2024     def check_expired_status(self, r: Invoice, status):
   2025         if r.is_lightning() and r.exp == 0:
   2026             status = PR_EXPIRED  # for BOLT-11 invoices, exp==0 means 0 seconds
   2027         if status == PR_UNPAID and r.exp > 0 and r.time + r.exp < time.time():
   2028             status = PR_EXPIRED
   2029         return status
   2030 
   2031     def get_invoice_status(self, invoice: Invoice):
   2032         if invoice.is_lightning():
   2033             status = self.lnworker.get_invoice_status(invoice) if self.lnworker else PR_UNKNOWN
   2034         else:
   2035             if self.is_onchain_invoice_paid(invoice, 1):
   2036                 status =PR_PAID
   2037             elif self.is_onchain_invoice_paid(invoice, 0):
   2038                 status = PR_UNCONFIRMED
   2039             else:
   2040                 status = PR_UNPAID
   2041         return self.check_expired_status(invoice, status)
   2042 
   2043     def get_request_status(self, key):
   2044         r = self.get_request(key)
   2045         if r is None:
   2046             return PR_UNKNOWN
   2047         if r.is_lightning():
   2048             assert isinstance(r, LNInvoice)
   2049             status = self.lnworker.get_payment_status(bfh(r.rhash)) if self.lnworker else PR_UNKNOWN
   2050         else:
   2051             assert isinstance(r, OnchainInvoice)
   2052             paid, conf = self.get_onchain_request_status(r)
   2053             if not paid:
   2054                 status = PR_UNPAID
   2055             elif conf == 0:
   2056                 status = PR_UNCONFIRMED
   2057             else:
   2058                 status = PR_PAID
   2059         return self.check_expired_status(r, status)
   2060 
   2061     def get_request(self, key):
   2062         return self.receive_requests.get(key)
   2063 
   2064     def get_formatted_request(self, key):
   2065         x = self.receive_requests.get(key)
   2066         if x:
   2067             return self.export_request(x)
   2068 
   2069     def export_request(self, x: Invoice) -> Dict[str, Any]:
   2070         key = self.get_key_for_receive_request(x)
   2071         status = self.get_request_status(key)
   2072         status_str = x.get_status_str(status)
   2073         is_lightning = x.is_lightning()
   2074         d = {
   2075             'is_lightning': is_lightning,
   2076             'amount_BTC': format_satoshis(x.get_amount_sat()),
   2077             'message': x.message,
   2078             'timestamp': x.time,
   2079             'expiration': x.exp,
   2080             'status': status,
   2081             'status_str': status_str,
   2082         }
   2083         if is_lightning:
   2084             assert isinstance(x, LNInvoice)
   2085             d['rhash'] = x.rhash
   2086             d['invoice'] = x.invoice
   2087             d['amount_msat'] = x.get_amount_msat()
   2088             if self.lnworker and status == PR_UNPAID:
   2089                 d['can_receive'] = self.lnworker.can_receive_invoice(x)
   2090         else:
   2091             assert isinstance(x, OnchainInvoice)
   2092             paid, conf = self.get_onchain_request_status(x)
   2093             d['amount_sat'] = x.get_amount_sat()
   2094             d['address'] = x.get_address()
   2095             d['URI'] = self.get_request_URI(x)
   2096             if conf is not None:
   2097                 d['confirmations'] = conf
   2098         # add URL if we are running a payserver
   2099         payserver = self.config.get_netaddress('payserver_address')
   2100         if payserver:
   2101             root = self.config.get('payserver_root', '/r')
   2102             use_ssl = bool(self.config.get('ssl_keyfile'))
   2103             protocol = 'https' if use_ssl else 'http'
   2104             base = '%s://%s:%d'%(protocol, payserver.host, payserver.port)
   2105             d['view_url'] = base + root + '/pay?id=' + key
   2106             if use_ssl and 'URI' in d:
   2107                 request_url = base + '/bip70/' + key + '.bip70'
   2108                 d['bip70_url'] = request_url
   2109         return d
   2110 
   2111     def export_invoice(self, x: Invoice) -> Dict[str, Any]:
   2112         status = self.get_invoice_status(x)
   2113         status_str = x.get_status_str(status)
   2114         is_lightning = x.is_lightning()
   2115         d = {
   2116             'is_lightning': is_lightning,
   2117             'amount_BTC': format_satoshis(x.get_amount_sat()),
   2118             'message': x.message,
   2119             'timestamp': x.time,
   2120             'expiration': x.exp,
   2121             'status': status,
   2122             'status_str': status_str,
   2123         }
   2124         if is_lightning:
   2125             assert isinstance(x, LNInvoice)
   2126             d['invoice'] = x.invoice
   2127             d['amount_msat'] = x.get_amount_msat()
   2128             if self.lnworker and status == PR_UNPAID:
   2129                 d['can_pay'] = self.lnworker.can_pay_invoice(x)
   2130         else:
   2131             assert isinstance(x, OnchainInvoice)
   2132             amount_sat = x.get_amount_sat()
   2133             assert isinstance(amount_sat, (int, str, type(None)))
   2134             d['amount_sat'] = amount_sat
   2135             d['outputs'] = [y.to_legacy_tuple() for y in x.outputs]
   2136             if x.bip70:
   2137                 d['bip70'] = x.bip70
   2138                 d['requestor'] = x.requestor
   2139         return d
   2140 
   2141     def receive_tx_callback(self, tx_hash, tx, tx_height):
   2142         super().receive_tx_callback(tx_hash, tx, tx_height)
   2143         for txo in tx.outputs():
   2144             addr = self.get_txout_address(txo)
   2145             if addr in self.receive_requests:
   2146                 status = self.get_request_status(addr)
   2147                 util.trigger_callback('request_status', self, addr, status)
   2148 
   2149     def make_payment_request(self, address, amount_sat, message, expiration):
   2150         # TODO maybe merge with wallet.create_invoice()...
   2151         #      note that they use incompatible "id"
   2152         amount_sat = amount_sat or 0
   2153         timestamp = int(time.time())
   2154         _id = bh2u(sha256d(address + "%d"%timestamp))[0:10]
   2155         expiration = expiration or 0
   2156         return OnchainInvoice(
   2157             type=PR_TYPE_ONCHAIN,
   2158             outputs=[(TYPE_ADDRESS, address, amount_sat)],
   2159             message=message,
   2160             time=timestamp,
   2161             amount_sat=amount_sat,
   2162             exp=expiration,
   2163             id=_id,
   2164             bip70=None,
   2165             requestor=None,
   2166             height=self.get_local_height(),
   2167         )
   2168 
   2169     def sign_payment_request(self, key, alias, alias_addr, password):  # FIXME this is broken
   2170         req = self.receive_requests.get(key)
   2171         assert isinstance(req, OnchainInvoice)
   2172         alias_privkey = self.export_private_key(alias_addr, password)
   2173         pr = paymentrequest.make_unsigned_request(req)
   2174         paymentrequest.sign_request_with_alias(pr, alias, alias_privkey)
   2175         req.bip70 = pr.raw.hex()
   2176         req['name'] = pr.pki_data
   2177         req['sig'] = bh2u(pr.signature)
   2178         self.receive_requests[key] = req
   2179 
   2180     @classmethod
   2181     def get_key_for_outgoing_invoice(cls, invoice: Invoice) -> str:
   2182         """Return the key to use for this invoice in self.invoices."""
   2183         if invoice.is_lightning():
   2184             assert isinstance(invoice, LNInvoice)
   2185             key = invoice.rhash
   2186         else:
   2187             assert isinstance(invoice, OnchainInvoice)
   2188             key = invoice.id
   2189         return key
   2190 
   2191     def get_key_for_receive_request(self, req: Invoice, *, sanity_checks: bool = False) -> str:
   2192         """Return the key to use for this invoice in self.receive_requests."""
   2193         if not req.is_lightning():
   2194             assert isinstance(req, OnchainInvoice)
   2195             addr = req.get_address()
   2196             if sanity_checks:
   2197                 if not bitcoin.is_address(addr):
   2198                     raise Exception(_('Invalid Bitcoin address.'))
   2199                 if not self.is_mine(addr):
   2200                     raise Exception(_('Address not in wallet.'))
   2201             key = addr
   2202         else:
   2203             assert isinstance(req, LNInvoice)
   2204             key = req.rhash
   2205         return key
   2206 
   2207     def add_payment_request(self, req: Invoice):
   2208         key = self.get_key_for_receive_request(req, sanity_checks=True)
   2209         message = req.message
   2210         self.receive_requests[key] = req
   2211         self.set_label(key, message)  # should be a default label
   2212         return req
   2213 
   2214     def delete_request(self, key):
   2215         """ lightning or on-chain """
   2216         if key in self.receive_requests:
   2217             self.remove_payment_request(key)
   2218         elif self.lnworker:
   2219             self.lnworker.delete_payment(key)
   2220 
   2221     def delete_invoice(self, key):
   2222         """ lightning or on-chain """
   2223         if key in self.invoices:
   2224             self.invoices.pop(key)
   2225         elif self.lnworker:
   2226             self.lnworker.delete_payment(key)
   2227 
   2228     def remove_payment_request(self, addr):
   2229         if addr not in self.receive_requests:
   2230             return False
   2231         self.receive_requests.pop(addr)
   2232         return True
   2233 
   2234     def get_sorted_requests(self) -> List[Invoice]:
   2235         """ sorted by timestamp """
   2236         out = [self.get_request(x) for x in self.receive_requests.keys()]
   2237         out = [x for x in out if x is not None]
   2238         out.sort(key=lambda x: x.time)
   2239         return out
   2240 
   2241     def get_unpaid_requests(self):
   2242         out = [self.get_request(x) for x in self.receive_requests.keys() if self.get_request_status(x) != PR_PAID]
   2243         out = [x for x in out if x is not None]
   2244         out.sort(key=lambda x: x.time)
   2245         return out
   2246 
   2247     @abstractmethod
   2248     def get_fingerprint(self) -> str:
   2249         """Returns a string that can be used to identify this wallet.
   2250         Used e.g. by Labels plugin, and LN channel backups.
   2251         Returns empty string "" for wallets that don't have an ID.
   2252         """
   2253         pass
   2254 
   2255     def can_import_privkey(self):
   2256         return False
   2257 
   2258     def can_import_address(self):
   2259         return False
   2260 
   2261     def can_delete_address(self):
   2262         return False
   2263 
   2264     def has_password(self):
   2265         return self.has_keystore_encryption() or self.has_storage_encryption()
   2266 
   2267     def can_have_keystore_encryption(self):
   2268         return self.keystore and self.keystore.may_have_password()
   2269 
   2270     def get_available_storage_encryption_version(self) -> StorageEncryptionVersion:
   2271         """Returns the type of storage encryption offered to the user.
   2272 
   2273         A wallet file (storage) is either encrypted with this version
   2274         or is stored in plaintext.
   2275         """
   2276         if isinstance(self.keystore, Hardware_KeyStore):
   2277             return StorageEncryptionVersion.XPUB_PASSWORD
   2278         else:
   2279             return StorageEncryptionVersion.USER_PASSWORD
   2280 
   2281     def has_keystore_encryption(self):
   2282         """Returns whether encryption is enabled for the keystore.
   2283 
   2284         If True, e.g. signing a transaction will require a password.
   2285         """
   2286         if self.can_have_keystore_encryption():
   2287             return self.db.get('use_encryption', False)
   2288         return False
   2289 
   2290     def has_storage_encryption(self):
   2291         """Returns whether encryption is enabled for the wallet file on disk."""
   2292         return self.storage and self.storage.is_encrypted()
   2293 
   2294     @classmethod
   2295     def may_have_password(cls):
   2296         return True
   2297 
   2298     def check_password(self, password):
   2299         if self.has_keystore_encryption():
   2300             self.keystore.check_password(password)
   2301         if self.has_storage_encryption():
   2302             self.storage.check_password(password)
   2303 
   2304     def update_password(self, old_pw, new_pw, *, encrypt_storage: bool = True):
   2305         if old_pw is None and self.has_password():
   2306             raise InvalidPassword()
   2307         self.check_password(old_pw)
   2308         if self.storage:
   2309             if encrypt_storage:
   2310                 enc_version = self.get_available_storage_encryption_version()
   2311             else:
   2312                 enc_version = StorageEncryptionVersion.PLAINTEXT
   2313             self.storage.set_password(new_pw, enc_version)
   2314         # make sure next storage.write() saves changes
   2315         self.db.set_modified(True)
   2316 
   2317         # note: Encrypting storage with a hw device is currently only
   2318         #       allowed for non-multisig wallets. Further,
   2319         #       Hardware_KeyStore.may_have_password() == False.
   2320         #       If these were not the case,
   2321         #       extra care would need to be taken when encrypting keystores.
   2322         self._update_password_for_keystore(old_pw, new_pw)
   2323         encrypt_keystore = self.can_have_keystore_encryption()
   2324         self.db.set_keystore_encryption(bool(new_pw) and encrypt_keystore)
   2325         self.save_db()
   2326 
   2327     @abstractmethod
   2328     def _update_password_for_keystore(self, old_pw: Optional[str], new_pw: Optional[str]) -> None:
   2329         pass
   2330 
   2331     def sign_message(self, address, message, password):
   2332         index = self.get_address_index(address)
   2333         return self.keystore.sign_message(index, message, password)
   2334 
   2335     def decrypt_message(self, pubkey: str, message, password) -> bytes:
   2336         addr = self.pubkeys_to_address([pubkey])
   2337         index = self.get_address_index(addr)
   2338         return self.keystore.decrypt_message(index, message, password)
   2339 
   2340     @abstractmethod
   2341     def pubkeys_to_address(self, pubkeys: Sequence[str]) -> Optional[str]:
   2342         pass
   2343 
   2344     def price_at_timestamp(self, txid, price_func):
   2345         """Returns fiat price of bitcoin at the time tx got confirmed."""
   2346         timestamp = self.get_tx_height(txid).timestamp
   2347         return price_func(timestamp if timestamp else time.time())
   2348 
   2349     def unrealized_gains(self, domain, price_func, ccy):
   2350         coins = self.get_utxos(domain)
   2351         now = time.time()
   2352         p = price_func(now)
   2353         ap = sum(self.coin_price(coin.prevout.txid.hex(), price_func, ccy, self.get_txin_value(coin)) for coin in coins)
   2354         lp = sum([coin.value_sats() for coin in coins]) * p / Decimal(COIN)
   2355         return lp - ap
   2356 
   2357     def average_price(self, txid, price_func, ccy) -> Decimal:
   2358         """ Average acquisition price of the inputs of a transaction """
   2359         input_value = 0
   2360         total_price = 0
   2361         txi_addresses = self.db.get_txi_addresses(txid)
   2362         if not txi_addresses:
   2363             return Decimal('NaN')
   2364         for addr in txi_addresses:
   2365             d = self.db.get_txi_addr(txid, addr)
   2366             for ser, v in d:
   2367                 input_value += v
   2368                 total_price += self.coin_price(ser.split(':')[0], price_func, ccy, v)
   2369         return total_price / (input_value/Decimal(COIN))
   2370 
   2371     def clear_coin_price_cache(self):
   2372         self._coin_price_cache = {}
   2373 
   2374     def coin_price(self, txid, price_func, ccy, txin_value) -> Decimal:
   2375         """
   2376         Acquisition price of a coin.
   2377         This assumes that either all inputs are mine, or no input is mine.
   2378         """
   2379         if txin_value is None:
   2380             return Decimal('NaN')
   2381         cache_key = "{}:{}:{}".format(str(txid), str(ccy), str(txin_value))
   2382         result = self._coin_price_cache.get(cache_key, None)
   2383         if result is not None:
   2384             return result
   2385         if self.db.get_txi_addresses(txid):
   2386             result = self.average_price(txid, price_func, ccy) * txin_value/Decimal(COIN)
   2387             self._coin_price_cache[cache_key] = result
   2388             return result
   2389         else:
   2390             fiat_value = self.get_fiat_value(txid, ccy)
   2391             if fiat_value is not None:
   2392                 return fiat_value
   2393             else:
   2394                 p = self.price_at_timestamp(txid, price_func)
   2395                 return p * txin_value/Decimal(COIN)
   2396 
   2397     def is_billing_address(self, addr):
   2398         # overridden for TrustedCoin wallets
   2399         return False
   2400 
   2401     @abstractmethod
   2402     def is_watching_only(self) -> bool:
   2403         pass
   2404 
   2405     def get_keystore(self) -> Optional[KeyStore]:
   2406         return self.keystore
   2407 
   2408     def get_keystores(self) -> Sequence[KeyStore]:
   2409         return [self.keystore] if self.keystore else []
   2410 
   2411     @abstractmethod
   2412     def save_keystore(self):
   2413         pass
   2414 
   2415     @abstractmethod
   2416     def has_seed(self) -> bool:
   2417         pass
   2418 
   2419     @abstractmethod
   2420     def get_all_known_addresses_beyond_gap_limit(self) -> Set[str]:
   2421         pass
   2422 
   2423     def create_transaction(self, outputs, *, fee=None, feerate=None, change_addr=None, domain_addr=None, domain_coins=None,
   2424               unsigned=False, rbf=None, password=None, locktime=None):
   2425         if fee is not None and feerate is not None:
   2426             raise Exception("Cannot specify both 'fee' and 'feerate' at the same time!")
   2427         coins = self.get_spendable_coins(domain_addr)
   2428         if domain_coins is not None:
   2429             coins = [coin for coin in coins if (coin.prevout.to_str() in domain_coins)]
   2430         if feerate is not None:
   2431             fee_per_kb = 1000 * Decimal(feerate)
   2432             fee_estimator = partial(SimpleConfig.estimate_fee_for_feerate, fee_per_kb)
   2433         else:
   2434             fee_estimator = fee
   2435         tx = self.make_unsigned_transaction(
   2436             coins=coins,
   2437             outputs=outputs,
   2438             fee=fee_estimator,
   2439             change_addr=change_addr)
   2440         if locktime is not None:
   2441             tx.locktime = locktime
   2442         if rbf is None:
   2443             rbf = bool(self.config.get('use_rbf', True))
   2444         tx.set_rbf(rbf)
   2445         if not unsigned:
   2446             self.sign_transaction(tx, password)
   2447         return tx
   2448 
   2449     def get_warning_for_risk_of_burning_coins_as_fees(self, tx: 'PartialTransaction') -> Optional[str]:
   2450         """Returns a warning message if there is risk of burning coins as fees if we sign.
   2451         Note that if not all inputs are ismine, e.g. coinjoin, the risk is not just about fees.
   2452 
   2453         Note:
   2454             - legacy sighash does not commit to any input amounts
   2455             - BIP-0143 sighash only commits to the *corresponding* input amount
   2456             - BIP-taproot sighash commits to *all* input amounts
   2457         """
   2458         assert isinstance(tx, PartialTransaction)
   2459         # if we have all full previous txs, we *know* all the input amounts -> fine
   2460         if all([txin.utxo for txin in tx.inputs()]):
   2461             return None
   2462         # a single segwit input -> fine
   2463         if len(tx.inputs()) == 1 and tx.inputs()[0].is_segwit() and tx.inputs()[0].witness_utxo:
   2464             return None
   2465         # coinjoin or similar
   2466         if any([not self.is_mine(txin.address) for txin in tx.inputs()]):
   2467             return (_("Warning") + ": "
   2468                     + _("The input amounts could not be verified as the previous transactions are missing.\n"
   2469                         "The amount of money being spent CANNOT be verified."))
   2470         # some inputs are legacy
   2471         if any([not txin.is_segwit() for txin in tx.inputs()]):
   2472             return (_("Warning") + ": "
   2473                     + _("The fee could not be verified. Signing non-segwit inputs is risky:\n"
   2474                         "if this transaction was maliciously modified before you sign,\n"
   2475                         "you might end up paying a higher mining fee than displayed."))
   2476         # all inputs are segwit
   2477         # https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2017-August/014843.html
   2478         return (_("Warning") + ": "
   2479                 + _("If you received this transaction from an untrusted device, "
   2480                     "do not accept to sign it more than once,\n"
   2481                     "otherwise you could end up paying a different fee."))
   2482 
   2483     def get_tx_fee_warning(
   2484             self,
   2485             *,
   2486             invoice_amt: int,
   2487             tx_size: int,
   2488             fee: int,
   2489     ) -> Optional[Tuple[bool, str, str]]:
   2490         feerate = Decimal(fee) / tx_size  # sat/byte
   2491         fee_ratio = Decimal(fee) / invoice_amt if invoice_amt else 1
   2492         long_warning = None
   2493         short_warning = None
   2494         allow_send = True
   2495         if feerate < self.relayfee() / 1000:
   2496             long_warning = (
   2497                     _("This transaction requires a higher fee, or it will not be propagated by your current server") + "\n"
   2498                     + _("Try to raise your transaction fee, or use a server with a lower relay fee.")
   2499             )
   2500             short_warning = _("below relay fee") + "!"
   2501             allow_send = False
   2502         elif fee_ratio >= FEE_RATIO_HIGH_WARNING:
   2503             long_warning = (
   2504                     _('Warning') + ': ' + _("The fee for this transaction seems unusually high.")
   2505                     + f'\n({fee_ratio*100:.2f}% of amount)')
   2506             short_warning = _("high fee ratio") + "!"
   2507         elif feerate > FEERATE_WARNING_HIGH_FEE / 1000:
   2508             long_warning = (
   2509                     _('Warning') + ': ' + _("The fee for this transaction seems unusually high.")
   2510                     + f'\n(feerate: {feerate:.2f} sat/byte)')
   2511             short_warning = _("high fee rate") + "!"
   2512         if long_warning is None:
   2513             return None
   2514         else:
   2515             return allow_send, long_warning, short_warning
   2516 
   2517 
   2518 class Simple_Wallet(Abstract_Wallet):
   2519     # wallet with a single keystore
   2520 
   2521     def is_watching_only(self):
   2522         return self.keystore.is_watching_only()
   2523 
   2524     def _update_password_for_keystore(self, old_pw, new_pw):
   2525         if self.keystore and self.keystore.may_have_password():
   2526             self.keystore.update_password(old_pw, new_pw)
   2527             self.save_keystore()
   2528 
   2529     def save_keystore(self):
   2530         self.db.put('keystore', self.keystore.dump())
   2531 
   2532     @abstractmethod
   2533     def get_public_key(self, address: str) -> Optional[str]:
   2534         pass
   2535 
   2536     def get_public_keys(self, address: str) -> Sequence[str]:
   2537         return [self.get_public_key(address)]
   2538 
   2539     def get_redeem_script(self, address: str) -> Optional[str]:
   2540         txin_type = self.get_txin_type(address)
   2541         if txin_type in ('p2pkh', 'p2wpkh', 'p2pk'):
   2542             return None
   2543         if txin_type == 'p2wpkh-p2sh':
   2544             pubkey = self.get_public_key(address)
   2545             return bitcoin.p2wpkh_nested_script(pubkey)
   2546         if txin_type == 'address':
   2547             return None
   2548         raise UnknownTxinType(f'unexpected txin_type {txin_type}')
   2549 
   2550     def get_witness_script(self, address: str) -> Optional[str]:
   2551         return None
   2552 
   2553 
   2554 class Imported_Wallet(Simple_Wallet):
   2555     # wallet made of imported addresses
   2556 
   2557     wallet_type = 'imported'
   2558     txin_type = 'address'
   2559 
   2560     def __init__(self, db, storage, *, config):
   2561         Abstract_Wallet.__init__(self, db, storage, config=config)
   2562 
   2563     def is_watching_only(self):
   2564         return self.keystore is None
   2565 
   2566     def can_import_privkey(self):
   2567         return bool(self.keystore)
   2568 
   2569     def load_keystore(self):
   2570         self.keystore = load_keystore(self.db, 'keystore') if self.db.get('keystore') else None
   2571 
   2572     def save_keystore(self):
   2573         self.db.put('keystore', self.keystore.dump())
   2574 
   2575     def can_import_address(self):
   2576         return self.is_watching_only()
   2577 
   2578     def can_delete_address(self):
   2579         return True
   2580 
   2581     def has_seed(self):
   2582         return False
   2583 
   2584     def is_deterministic(self):
   2585         return False
   2586 
   2587     def is_change(self, address):
   2588         return False
   2589 
   2590     def get_all_known_addresses_beyond_gap_limit(self) -> Set[str]:
   2591         return set()
   2592 
   2593     def get_fingerprint(self):
   2594         return ''
   2595 
   2596     def get_addresses(self):
   2597         # note: overridden so that the history can be cleared
   2598         return self.db.get_imported_addresses()
   2599 
   2600     def get_receiving_addresses(self, **kwargs):
   2601         return self.get_addresses()
   2602 
   2603     def get_change_addresses(self, **kwargs):
   2604         return []
   2605 
   2606     def import_addresses(self, addresses: List[str], *,
   2607                          write_to_disk=True) -> Tuple[List[str], List[Tuple[str, str]]]:
   2608         good_addr = []  # type: List[str]
   2609         bad_addr = []  # type: List[Tuple[str, str]]
   2610         for address in addresses:
   2611             if not bitcoin.is_address(address):
   2612                 bad_addr.append((address, _('invalid address')))
   2613                 continue
   2614             if self.db.has_imported_address(address):
   2615                 bad_addr.append((address, _('address already in wallet')))
   2616                 continue
   2617             good_addr.append(address)
   2618             self.db.add_imported_address(address, {})
   2619             self.add_address(address)
   2620         if write_to_disk:
   2621             self.save_db()
   2622         return good_addr, bad_addr
   2623 
   2624     def import_address(self, address: str) -> str:
   2625         good_addr, bad_addr = self.import_addresses([address])
   2626         if good_addr and good_addr[0] == address:
   2627             return address
   2628         else:
   2629             raise BitcoinException(str(bad_addr[0][1]))
   2630 
   2631     def delete_address(self, address: str) -> None:
   2632         if not self.db.has_imported_address(address):
   2633             return
   2634         if len(self.get_addresses()) <= 1:
   2635             raise UserFacingException("cannot delete last remaining address from wallet")
   2636         transactions_to_remove = set()  # only referred to by this address
   2637         transactions_new = set()  # txs that are not only referred to by address
   2638         with self.lock:
   2639             for addr in self.db.get_history():
   2640                 details = self.get_address_history(addr)
   2641                 if addr == address:
   2642                     for tx_hash, height in details:
   2643                         transactions_to_remove.add(tx_hash)
   2644                 else:
   2645                     for tx_hash, height in details:
   2646                         transactions_new.add(tx_hash)
   2647             transactions_to_remove -= transactions_new
   2648             self.db.remove_addr_history(address)
   2649             for tx_hash in transactions_to_remove:
   2650                 self.remove_transaction(tx_hash)
   2651         self.set_label(address, None)
   2652         self.remove_payment_request(address)
   2653         self.set_frozen_state_of_addresses([address], False)
   2654         pubkey = self.get_public_key(address)
   2655         self.db.remove_imported_address(address)
   2656         if pubkey:
   2657             # delete key iff no other address uses it (e.g. p2pkh and p2wpkh for same key)
   2658             for txin_type in bitcoin.WIF_SCRIPT_TYPES.keys():
   2659                 try:
   2660                     addr2 = bitcoin.pubkey_to_address(txin_type, pubkey)
   2661                 except NotImplementedError:
   2662                     pass
   2663                 else:
   2664                     if self.db.has_imported_address(addr2):
   2665                         break
   2666             else:
   2667                 self.keystore.delete_imported_key(pubkey)
   2668                 self.save_keystore()
   2669         self.save_db()
   2670 
   2671     def is_mine(self, address) -> bool:
   2672         if not address: return False
   2673         return self.db.has_imported_address(address)
   2674 
   2675     def get_address_index(self, address) -> Optional[str]:
   2676         # returns None if address is not mine
   2677         return self.get_public_key(address)
   2678 
   2679     def get_address_path_str(self, address):
   2680         return None
   2681 
   2682     def get_public_key(self, address) -> Optional[str]:
   2683         x = self.db.get_imported_address(address)
   2684         return x.get('pubkey') if x else None
   2685 
   2686     def import_private_keys(self, keys: List[str], password: Optional[str], *,
   2687                             write_to_disk=True) -> Tuple[List[str], List[Tuple[str, str]]]:
   2688         good_addr = []  # type: List[str]
   2689         bad_keys = []  # type: List[Tuple[str, str]]
   2690         for key in keys:
   2691             try:
   2692                 txin_type, pubkey = self.keystore.import_privkey(key, password)
   2693             except Exception as e:
   2694                 bad_keys.append((key, _('invalid private key') + f': {e}'))
   2695                 continue
   2696             if txin_type not in ('p2pkh', 'p2wpkh', 'p2wpkh-p2sh'):
   2697                 bad_keys.append((key, _('not implemented type') + f': {txin_type}'))
   2698                 continue
   2699             addr = bitcoin.pubkey_to_address(txin_type, pubkey)
   2700             good_addr.append(addr)
   2701             self.db.add_imported_address(addr, {'type':txin_type, 'pubkey':pubkey})
   2702             self.add_address(addr)
   2703         self.save_keystore()
   2704         if write_to_disk:
   2705             self.save_db()
   2706         return good_addr, bad_keys
   2707 
   2708     def import_private_key(self, key: str, password: Optional[str]) -> str:
   2709         good_addr, bad_keys = self.import_private_keys([key], password=password)
   2710         if good_addr:
   2711             return good_addr[0]
   2712         else:
   2713             raise BitcoinException(str(bad_keys[0][1]))
   2714 
   2715     def get_txin_type(self, address):
   2716         return self.db.get_imported_address(address).get('type', 'address')
   2717 
   2718     def _add_input_sig_info(self, txin, address, *, only_der_suffix):
   2719         if not self.is_mine(address):
   2720             return
   2721         if txin.script_type in ('unknown', 'address'):
   2722             return
   2723         elif txin.script_type in ('p2pkh', 'p2wpkh', 'p2wpkh-p2sh'):
   2724             pubkey = self.get_public_key(address)
   2725             if not pubkey:
   2726                 return
   2727             txin.pubkeys = [bfh(pubkey)]
   2728         else:
   2729             raise Exception(f'Unexpected script type: {txin.script_type}. '
   2730                             f'Imported wallets are not implemented to handle this.')
   2731 
   2732     def pubkeys_to_address(self, pubkeys):
   2733         pubkey = pubkeys[0]
   2734         for addr in self.db.get_imported_addresses():  # FIXME slow...
   2735             if self.db.get_imported_address(addr)['pubkey'] == pubkey:
   2736                 return addr
   2737         return None
   2738 
   2739     def decrypt_message(self, pubkey: str, message, password) -> bytes:
   2740         # this is significantly faster than the implementation in the superclass
   2741         return self.keystore.decrypt_message(pubkey, message, password)
   2742 
   2743 
   2744 class Deterministic_Wallet(Abstract_Wallet):
   2745 
   2746     def __init__(self, db, storage, *, config):
   2747         self._ephemeral_addr_to_addr_index = {}  # type: Dict[str, Sequence[int]]
   2748         Abstract_Wallet.__init__(self, db, storage, config=config)
   2749         self.gap_limit = db.get('gap_limit', 20)
   2750         # generate addresses now. note that without libsecp this might block
   2751         # for a few seconds!
   2752         self.synchronize()
   2753 
   2754         # create lightning keys
   2755         if self.can_have_lightning():
   2756             self.init_lightning()
   2757         ln_xprv = self.db.get('lightning_privkey2')
   2758         # lnworker can only be initialized once receiving addresses are available
   2759         # therefore we instantiate lnworker in DeterministicWallet
   2760         self.lnworker = LNWallet(self, ln_xprv) if ln_xprv else None
   2761 
   2762     def has_seed(self):
   2763         return self.keystore.has_seed()
   2764 
   2765     def get_addresses(self):
   2766         # note: overridden so that the history can be cleared.
   2767         # addresses are ordered based on derivation
   2768         out = self.get_receiving_addresses()
   2769         out += self.get_change_addresses()
   2770         return out
   2771 
   2772     def get_receiving_addresses(self, *, slice_start=None, slice_stop=None):
   2773         return self.db.get_receiving_addresses(slice_start=slice_start, slice_stop=slice_stop)
   2774 
   2775     def get_change_addresses(self, *, slice_start=None, slice_stop=None):
   2776         return self.db.get_change_addresses(slice_start=slice_start, slice_stop=slice_stop)
   2777 
   2778     @profiler
   2779     def try_detecting_internal_addresses_corruption(self):
   2780         addresses_all = self.get_addresses()
   2781         # sample 1: first few
   2782         addresses_sample1 = addresses_all[:10]
   2783         # sample2: a few more randomly selected
   2784         addresses_rand = addresses_all[10:]
   2785         addresses_sample2 = random.sample(addresses_rand, min(len(addresses_rand), 10))
   2786         for addr_found in itertools.chain(addresses_sample1, addresses_sample2):
   2787             self.check_address_for_corruption(addr_found)
   2788 
   2789     def check_address_for_corruption(self, addr):
   2790         if addr and self.is_mine(addr):
   2791             if addr != self.derive_address(*self.get_address_index(addr)):
   2792                 raise InternalAddressCorruption()
   2793 
   2794     def get_seed(self, password):
   2795         return self.keystore.get_seed(password)
   2796 
   2797     def change_gap_limit(self, value):
   2798         '''This method is not called in the code, it is kept for console use'''
   2799         value = int(value)
   2800         if value >= self.min_acceptable_gap():
   2801             self.gap_limit = value
   2802             self.db.put('gap_limit', self.gap_limit)
   2803             self.save_db()
   2804             return True
   2805         else:
   2806             return False
   2807 
   2808     def num_unused_trailing_addresses(self, addresses):
   2809         k = 0
   2810         for addr in addresses[::-1]:
   2811             if self.db.get_addr_history(addr):
   2812                 break
   2813             k += 1
   2814         return k
   2815 
   2816     def min_acceptable_gap(self) -> int:
   2817         # fixme: this assumes wallet is synchronized
   2818         n = 0
   2819         nmax = 0
   2820         addresses = self.get_receiving_addresses()
   2821         k = self.num_unused_trailing_addresses(addresses)
   2822         for addr in addresses[0:-k]:
   2823             if self.address_is_old(addr):
   2824                 n = 0
   2825             else:
   2826                 n += 1
   2827                 nmax = max(nmax, n)
   2828         return nmax + 1
   2829 
   2830     @abstractmethod
   2831     def derive_pubkeys(self, c: int, i: int) -> Sequence[str]:
   2832         pass
   2833 
   2834     def derive_address(self, for_change: int, n: int) -> str:
   2835         for_change = int(for_change)
   2836         pubkeys = self.derive_pubkeys(for_change, n)
   2837         return self.pubkeys_to_address(pubkeys)
   2838 
   2839     def export_private_key_for_path(self, path: Union[Sequence[int], str], password: Optional[str]) -> str:
   2840         if isinstance(path, str):
   2841             path = convert_bip32_path_to_list_of_uint32(path)
   2842         pk, compressed = self.keystore.get_private_key(path, password)
   2843         txin_type = self.get_txin_type()  # assumes no mixed-scripts in wallet
   2844         return bitcoin.serialize_privkey(pk, compressed, txin_type)
   2845 
   2846     def get_public_keys_with_deriv_info(self, address: str):
   2847         der_suffix = self.get_address_index(address)
   2848         der_suffix = [int(x) for x in der_suffix]
   2849         return {k.derive_pubkey(*der_suffix): (k, der_suffix)
   2850                 for k in self.get_keystores()}
   2851 
   2852     def _add_input_sig_info(self, txin, address, *, only_der_suffix):
   2853         self._add_txinout_derivation_info(txin, address, only_der_suffix=only_der_suffix)
   2854 
   2855     def _add_txinout_derivation_info(self, txinout, address, *, only_der_suffix):
   2856         if not self.is_mine(address):
   2857             return
   2858         pubkey_deriv_info = self.get_public_keys_with_deriv_info(address)
   2859         txinout.pubkeys = sorted([pk for pk in list(pubkey_deriv_info)])
   2860         for pubkey in pubkey_deriv_info:
   2861             ks, der_suffix = pubkey_deriv_info[pubkey]
   2862             fp_bytes, der_full = ks.get_fp_and_derivation_to_be_used_in_partial_tx(der_suffix,
   2863                                                                                    only_der_suffix=only_der_suffix)
   2864             txinout.bip32_paths[pubkey] = (fp_bytes, der_full)
   2865 
   2866     def create_new_address(self, for_change: bool = False):
   2867         assert type(for_change) is bool
   2868         with self.lock:
   2869             n = self.db.num_change_addresses() if for_change else self.db.num_receiving_addresses()
   2870             address = self.derive_address(int(for_change), n)
   2871             self.db.add_change_address(address) if for_change else self.db.add_receiving_address(address)
   2872             self.add_address(address)
   2873             if for_change:
   2874                 # note: if it's actually "old", it will get filtered later
   2875                 self._not_old_change_addresses.append(address)
   2876             return address
   2877 
   2878     def synchronize_sequence(self, for_change):
   2879         limit = self.gap_limit_for_change if for_change else self.gap_limit
   2880         while True:
   2881             num_addr = self.db.num_change_addresses() if for_change else self.db.num_receiving_addresses()
   2882             if num_addr < limit:
   2883                 self.create_new_address(for_change)
   2884                 continue
   2885             if for_change:
   2886                 last_few_addresses = self.get_change_addresses(slice_start=-limit)
   2887             else:
   2888                 last_few_addresses = self.get_receiving_addresses(slice_start=-limit)
   2889             if any(map(self.address_is_old, last_few_addresses)):
   2890                 self.create_new_address(for_change)
   2891             else:
   2892                 break
   2893 
   2894     @AddressSynchronizer.with_local_height_cached
   2895     def synchronize(self):
   2896         with self.lock:
   2897             self.synchronize_sequence(False)
   2898             self.synchronize_sequence(True)
   2899 
   2900     def get_all_known_addresses_beyond_gap_limit(self):
   2901         # note that we don't stop at first large gap
   2902         found = set()
   2903 
   2904         def process_addresses(addrs, gap_limit):
   2905             rolling_num_unused = 0
   2906             for addr in addrs:
   2907                 if self.db.get_addr_history(addr):
   2908                     rolling_num_unused = 0
   2909                 else:
   2910                     if rolling_num_unused >= gap_limit:
   2911                         found.add(addr)
   2912                     rolling_num_unused += 1
   2913 
   2914         process_addresses(self.get_receiving_addresses(), self.gap_limit)
   2915         process_addresses(self.get_change_addresses(), self.gap_limit_for_change)
   2916         return found
   2917 
   2918     def get_address_index(self, address) -> Optional[Sequence[int]]:
   2919         return self.db.get_address_index(address) or self._ephemeral_addr_to_addr_index.get(address)
   2920 
   2921     def get_address_path_str(self, address):
   2922         intpath = self.get_address_index(address)
   2923         if intpath is None:
   2924             return None
   2925         return convert_bip32_intpath_to_strpath(intpath)
   2926 
   2927     def _learn_derivation_path_for_address_from_txinout(self, txinout, address):
   2928         for ks in self.get_keystores():
   2929             pubkey, der_suffix = ks.find_my_pubkey_in_txinout(txinout, only_der_suffix=True)
   2930             if der_suffix is not None:
   2931                 # note: we already know the pubkey belongs to the keystore,
   2932                 #       but the script template might be different
   2933                 if len(der_suffix) != 2: continue
   2934                 try:
   2935                     my_address = self.derive_address(*der_suffix)
   2936                 except CannotDerivePubkey:
   2937                     my_address = None
   2938                 if my_address == address:
   2939                     self._ephemeral_addr_to_addr_index[address] = list(der_suffix)
   2940                     return True
   2941         return False
   2942 
   2943     def get_master_public_keys(self):
   2944         return [self.get_master_public_key()]
   2945 
   2946     def get_fingerprint(self):
   2947         return self.get_master_public_key()
   2948 
   2949     def get_txin_type(self, address=None):
   2950         return self.txin_type
   2951 
   2952 
   2953 class Simple_Deterministic_Wallet(Simple_Wallet, Deterministic_Wallet):
   2954 
   2955     """ Deterministic Wallet with a single pubkey per address """
   2956 
   2957     def __init__(self, db, storage, *, config):
   2958         Deterministic_Wallet.__init__(self, db, storage, config=config)
   2959 
   2960     def get_public_key(self, address):
   2961         sequence = self.get_address_index(address)
   2962         pubkeys = self.derive_pubkeys(*sequence)
   2963         return pubkeys[0]
   2964 
   2965     def load_keystore(self):
   2966         self.keystore = load_keystore(self.db, 'keystore')
   2967         try:
   2968             xtype = bip32.xpub_type(self.keystore.xpub)
   2969         except:
   2970             xtype = 'standard'
   2971         self.txin_type = 'p2pkh' if xtype == 'standard' else xtype
   2972 
   2973     def get_master_public_key(self):
   2974         return self.keystore.get_master_public_key()
   2975 
   2976     def derive_pubkeys(self, c, i):
   2977         return [self.keystore.derive_pubkey(c, i).hex()]
   2978 
   2979 
   2980 
   2981 
   2982 
   2983 
   2984 class Standard_Wallet(Simple_Deterministic_Wallet):
   2985     wallet_type = 'standard'
   2986 
   2987     def pubkeys_to_address(self, pubkeys):
   2988         pubkey = pubkeys[0]
   2989         return bitcoin.pubkey_to_address(self.txin_type, pubkey)
   2990 
   2991 
   2992 class Multisig_Wallet(Deterministic_Wallet):
   2993     # generic m of n
   2994 
   2995     def __init__(self, db, storage, *, config):
   2996         self.wallet_type = db.get('wallet_type')
   2997         self.m, self.n = multisig_type(self.wallet_type)
   2998         Deterministic_Wallet.__init__(self, db, storage, config=config)
   2999 
   3000     def get_public_keys(self, address):
   3001         return [pk.hex() for pk in self.get_public_keys_with_deriv_info(address)]
   3002 
   3003     def pubkeys_to_address(self, pubkeys):
   3004         redeem_script = self.pubkeys_to_scriptcode(pubkeys)
   3005         return bitcoin.redeem_script_to_address(self.txin_type, redeem_script)
   3006 
   3007     def pubkeys_to_scriptcode(self, pubkeys: Sequence[str]) -> str:
   3008         return transaction.multisig_script(sorted(pubkeys), self.m)
   3009 
   3010     def get_redeem_script(self, address):
   3011         txin_type = self.get_txin_type(address)
   3012         pubkeys = self.get_public_keys(address)
   3013         scriptcode = self.pubkeys_to_scriptcode(pubkeys)
   3014         if txin_type == 'p2sh':
   3015             return scriptcode
   3016         elif txin_type == 'p2wsh-p2sh':
   3017             return bitcoin.p2wsh_nested_script(scriptcode)
   3018         elif txin_type == 'p2wsh':
   3019             return None
   3020         raise UnknownTxinType(f'unexpected txin_type {txin_type}')
   3021 
   3022     def get_witness_script(self, address):
   3023         txin_type = self.get_txin_type(address)
   3024         pubkeys = self.get_public_keys(address)
   3025         scriptcode = self.pubkeys_to_scriptcode(pubkeys)
   3026         if txin_type == 'p2sh':
   3027             return None
   3028         elif txin_type in ('p2wsh-p2sh', 'p2wsh'):
   3029             return scriptcode
   3030         raise UnknownTxinType(f'unexpected txin_type {txin_type}')
   3031 
   3032     def derive_pubkeys(self, c, i):
   3033         return [k.derive_pubkey(c, i).hex() for k in self.get_keystores()]
   3034 
   3035     def load_keystore(self):
   3036         self.keystores = {}
   3037         for i in range(self.n):
   3038             name = 'x%d/'%(i+1)
   3039             self.keystores[name] = load_keystore(self.db, name)
   3040         self.keystore = self.keystores['x1/']
   3041         xtype = bip32.xpub_type(self.keystore.xpub)
   3042         self.txin_type = 'p2sh' if xtype == 'standard' else xtype
   3043 
   3044     def save_keystore(self):
   3045         for name, k in self.keystores.items():
   3046             self.db.put(name, k.dump())
   3047 
   3048     def get_keystore(self):
   3049         return self.keystores.get('x1/')
   3050 
   3051     def get_keystores(self):
   3052         return [self.keystores[i] for i in sorted(self.keystores.keys())]
   3053 
   3054     def can_have_keystore_encryption(self):
   3055         return any([k.may_have_password() for k in self.get_keystores()])
   3056 
   3057     def _update_password_for_keystore(self, old_pw, new_pw):
   3058         for name, keystore in self.keystores.items():
   3059             if keystore.may_have_password():
   3060                 keystore.update_password(old_pw, new_pw)
   3061                 self.db.put(name, keystore.dump())
   3062 
   3063     def check_password(self, password):
   3064         for name, keystore in self.keystores.items():
   3065             if keystore.may_have_password():
   3066                 keystore.check_password(password)
   3067         if self.has_storage_encryption():
   3068             self.storage.check_password(password)
   3069 
   3070     def get_available_storage_encryption_version(self):
   3071         # multisig wallets are not offered hw device encryption
   3072         return StorageEncryptionVersion.USER_PASSWORD
   3073 
   3074     def has_seed(self):
   3075         return self.keystore.has_seed()
   3076 
   3077     def is_watching_only(self):
   3078         return all([k.is_watching_only() for k in self.get_keystores()])
   3079 
   3080     def get_master_public_key(self):
   3081         return self.keystore.get_master_public_key()
   3082 
   3083     def get_master_public_keys(self):
   3084         return [k.get_master_public_key() for k in self.get_keystores()]
   3085 
   3086     def get_fingerprint(self):
   3087         return ''.join(sorted(self.get_master_public_keys()))
   3088 
   3089 
   3090 wallet_types = ['standard', 'multisig', 'imported']
   3091 
   3092 def register_wallet_type(category):
   3093     wallet_types.append(category)
   3094 
   3095 wallet_constructors = {
   3096     'standard': Standard_Wallet,
   3097     'old': Standard_Wallet,
   3098     'xpub': Standard_Wallet,
   3099     'imported': Imported_Wallet
   3100 }
   3101 
   3102 def register_constructor(wallet_type, constructor):
   3103     wallet_constructors[wallet_type] = constructor
   3104 
   3105 # former WalletFactory
   3106 class Wallet(object):
   3107     """The main wallet "entry point".
   3108     This class is actually a factory that will return a wallet of the correct
   3109     type when passed a WalletStorage instance."""
   3110 
   3111     def __new__(self, db: 'WalletDB', storage: Optional[WalletStorage], *, config: SimpleConfig):
   3112         wallet_type = db.get('wallet_type')
   3113         WalletClass = Wallet.wallet_class(wallet_type)
   3114         wallet = WalletClass(db, storage, config=config)
   3115         return wallet
   3116 
   3117     @staticmethod
   3118     def wallet_class(wallet_type):
   3119         if multisig_type(wallet_type):
   3120             return Multisig_Wallet
   3121         if wallet_type in wallet_constructors:
   3122             return wallet_constructors[wallet_type]
   3123         raise WalletFileException("Unknown wallet type: " + str(wallet_type))
   3124 
   3125 
   3126 def create_new_wallet(*, path, config: SimpleConfig, passphrase=None, password=None,
   3127                       encrypt_file=True, seed_type=None, gap_limit=None) -> dict:
   3128     """Create a new wallet"""
   3129     storage = WalletStorage(path)
   3130     if storage.file_exists():
   3131         raise Exception("Remove the existing wallet first!")
   3132     db = WalletDB('', manual_upgrades=False)
   3133 
   3134     seed = Mnemonic('en').make_seed(seed_type=seed_type)
   3135     k = keystore.from_seed(seed, passphrase)
   3136     db.put('keystore', k.dump())
   3137     db.put('wallet_type', 'standard')
   3138     if gap_limit is not None:
   3139         db.put('gap_limit', gap_limit)
   3140     wallet = Wallet(db, storage, config=config)
   3141     wallet.update_password(old_pw=None, new_pw=password, encrypt_storage=encrypt_file)
   3142     wallet.synchronize()
   3143     msg = "Please keep your seed in a safe place; if you lose it, you will not be able to restore your wallet."
   3144     wallet.save_db()
   3145     return {'seed': seed, 'wallet': wallet, 'msg': msg}
   3146 
   3147 
   3148 def restore_wallet_from_text(text, *, path, config: SimpleConfig,
   3149                              passphrase=None, password=None, encrypt_file=True,
   3150                              gap_limit=None) -> dict:
   3151     """Restore a wallet from text. Text can be a seed phrase, a master
   3152     public key, a master private key, a list of bitcoin addresses
   3153     or bitcoin private keys."""
   3154     storage = WalletStorage(path)
   3155     if storage.file_exists():
   3156         raise Exception("Remove the existing wallet first!")
   3157     db = WalletDB('', manual_upgrades=False)
   3158     text = text.strip()
   3159     if keystore.is_address_list(text):
   3160         wallet = Imported_Wallet(db, storage, config=config)
   3161         addresses = text.split()
   3162         good_inputs, bad_inputs = wallet.import_addresses(addresses, write_to_disk=False)
   3163         # FIXME tell user about bad_inputs
   3164         if not good_inputs:
   3165             raise Exception("None of the given addresses can be imported")
   3166     elif keystore.is_private_key_list(text, allow_spaces_inside_key=False):
   3167         k = keystore.Imported_KeyStore({})
   3168         db.put('keystore', k.dump())
   3169         wallet = Imported_Wallet(db, storage, config=config)
   3170         keys = keystore.get_private_keys(text, allow_spaces_inside_key=False)
   3171         good_inputs, bad_inputs = wallet.import_private_keys(keys, None, write_to_disk=False)
   3172         # FIXME tell user about bad_inputs
   3173         if not good_inputs:
   3174             raise Exception("None of the given privkeys can be imported")
   3175     else:
   3176         if keystore.is_master_key(text):
   3177             k = keystore.from_master_key(text)
   3178         elif keystore.is_seed(text):
   3179             k = keystore.from_seed(text, passphrase)
   3180         else:
   3181             raise Exception("Seed or key not recognized")
   3182         db.put('keystore', k.dump())
   3183         db.put('wallet_type', 'standard')
   3184         if gap_limit is not None:
   3185             db.put('gap_limit', gap_limit)
   3186         wallet = Wallet(db, storage, config=config)
   3187     assert not storage.file_exists(), "file was created too soon! plaintext keys might have been written to disk"
   3188     wallet.update_password(old_pw=None, new_pw=password, encrypt_storage=encrypt_file)
   3189     wallet.synchronize()
   3190     msg = ("This wallet was restored offline. It may contain more addresses than displayed. "
   3191            "Start a daemon and use load_wallet to sync its history.")
   3192     wallet.save_db()
   3193     return {'wallet': wallet, 'msg': msg}
   3194 
   3195 
   3196 def check_password_for_directory(config: SimpleConfig, old_password, new_password=None) -> bool:
   3197     """Checks password against all wallets and returns True if they can all be updated.
   3198     If new_password is not None, update all wallet passwords to new_password.
   3199     """
   3200     dirname = os.path.dirname(config.get_wallet_path())
   3201     failed = []
   3202     for filename in os.listdir(dirname):
   3203         path = os.path.join(dirname, filename)
   3204         if not os.path.isfile(path):
   3205             continue
   3206         basename = os.path.basename(path)
   3207         storage = WalletStorage(path)
   3208         if not storage.is_encrypted():
   3209             # it is a bit wasteful load the wallet here, but that is fine
   3210             # because we are progressively enforcing storage encryption.
   3211             db = WalletDB(storage.read(), manual_upgrades=False)
   3212             wallet = Wallet(db, storage, config=config)
   3213             if wallet.has_keystore_encryption():
   3214                 try:
   3215                     wallet.check_password(old_password)
   3216                 except:
   3217                     failed.append(basename)
   3218                     continue
   3219                 if new_password:
   3220                     wallet.update_password(old_password, new_password)
   3221             else:
   3222                 if new_password:
   3223                     wallet.update_password(None, new_password)
   3224             continue
   3225         if not storage.is_encrypted_with_user_pw():
   3226             failed.append(basename)
   3227             continue
   3228         try:
   3229             storage.check_password(old_password)
   3230         except:
   3231             failed.append(basename)
   3232             continue
   3233         db = WalletDB(storage.read(), manual_upgrades=False)
   3234         wallet = Wallet(db, storage, config=config)
   3235         try:
   3236             wallet.check_password(old_password)
   3237         except:
   3238             failed.append(basename)
   3239             continue
   3240         if new_password:
   3241             wallet.update_password(old_password, new_password)
   3242     return failed == []
   3243 
   3244 
   3245 def update_password_for_directory(config: SimpleConfig, old_password, new_password) -> bool:
   3246     assert new_password is not None
   3247     assert check_password_for_directory(config, old_password, None)
   3248     return check_password_for_directory(config, old_password, new_password)