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