electrum

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

commit 7a080352f8864077875206658bb610113eded7e6
parent cfbd83c43230ad6b31e98120daff8336c3ac0b7a
Author: ThomasV <thomasv@electrum.org>
Date:   Fri, 29 Nov 2019 18:24:08 +0100

Merge pull request #5809 from SomberNight/201911_invoice_paid_detection

wallet: better (outgoing) invoice "paid" detection
Diffstat:
Melectrum/address_synchronizer.py | 19++++++++++++++-----
Melectrum/gui/kivy/main_window.py | 8+-------
Melectrum/gui/kivy/uix/screens.py | 6+++---
Melectrum/gui/qt/invoice_list.py | 3++-
Melectrum/gui/qt/main_window.py | 37+++++++++++++++----------------------
Melectrum/gui/qt/transaction_dialog.py | 17++++++++---------
Melectrum/json_db.py | 51+++++++++++++++++++++++++++++++++++++++++++++++----
Melectrum/wallet.py | 77++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
8 files changed, 152 insertions(+), 66 deletions(-)

diff --git a/electrum/address_synchronizer.py b/electrum/address_synchronizer.py @@ -26,7 +26,7 @@ import threading import asyncio import itertools from collections import defaultdict -from typing import TYPE_CHECKING, Dict, Optional, Set, Tuple, NamedTuple, Sequence +from typing import TYPE_CHECKING, Dict, Optional, Set, Tuple, NamedTuple, Sequence, List from . import bitcoin from .bitcoin import COINBASE_MATURITY @@ -207,7 +207,7 @@ class AddressSynchronizer(Logger): conflicting_txns -= {tx_hash} return conflicting_txns - def add_transaction(self, tx: Transaction, allow_unrelated=False) -> bool: + def add_transaction(self, tx: Transaction, *, allow_unrelated=False) -> bool: """Returns whether the tx was successfully added to the wallet history.""" assert tx, tx assert tx.is_complete() @@ -283,6 +283,8 @@ class AddressSynchronizer(Logger): for n, txo in enumerate(tx.outputs()): v = txo.value ser = tx_hash + ':%d'%n + scripthash = bitcoin.script_to_scripthash(txo.scriptpubkey.hex()) + self.db.add_prevout_by_scripthash(scripthash, prevout=TxOutpoint.from_str(ser), value=v) addr = self.get_txout_address(txo) if addr and self.is_mine(addr): self.db.add_txo_addr(tx_hash, addr, n, v, is_coinbase) @@ -299,7 +301,7 @@ class AddressSynchronizer(Logger): self.db.add_num_inputs_to_tx(tx_hash, len(tx.inputs())) return True - def remove_transaction(self, tx_hash): + def remove_transaction(self, tx_hash: str) -> None: def remove_from_spent_outpoints(): # undo spends in spent_outpoints if tx is not None: @@ -317,7 +319,7 @@ class AddressSynchronizer(Logger): if spending_txid == tx_hash: self.db.remove_spent_outpoint(prevout_hash, prevout_n) - with self.transaction_lock: + with self.lock, self.transaction_lock: self.logger.info(f"removing tx from history {tx_hash}") tx = self.db.remove_transaction(tx_hash) remove_from_spent_outpoints() @@ -327,6 +329,13 @@ class AddressSynchronizer(Logger): self.db.remove_txi(tx_hash) self.db.remove_txo(tx_hash) self.db.remove_tx_fee(tx_hash) + self.db.remove_verified_tx(tx_hash) + self.unverified_tx.pop(tx_hash, None) + if tx: + for idx, txo in enumerate(tx.outputs()): + scripthash = bitcoin.script_to_scripthash(txo.scriptpubkey.hex()) + prevout = TxOutpoint(bfh(tx_hash), idx) + self.db.remove_prevout_by_scripthash(scripthash, prevout=prevout, value=txo.value) def get_depending_transactions(self, tx_hash): """Returns all (grand-)children of tx_hash in this wallet.""" @@ -338,7 +347,7 @@ class AddressSynchronizer(Logger): children |= self.get_depending_transactions(other_hash) return children - def receive_tx_callback(self, tx_hash, tx, tx_height): + def receive_tx_callback(self, tx_hash: str, tx: Transaction, tx_height: int) -> None: self.add_unverified_tx(tx_hash, tx_height) self.add_transaction(tx, allow_unrelated=True) diff --git a/electrum/gui/kivy/main_window.py b/electrum/gui/kivy/main_window.py @@ -1028,18 +1028,12 @@ class ElectrumWindow(App): status, msg = True, tx.txid() Clock.schedule_once(lambda dt: on_complete(status, msg)) - def broadcast(self, tx, invoice=None): + def broadcast(self, tx): def on_complete(ok, msg): if ok: self.show_info(_('Payment sent.')) if self.send_screen: self.send_screen.do_clear() - if invoice: - key = invoice['id'] - txid = tx.txid() - self.wallet.set_label(txid, invoice['message']) - self.wallet.set_paid(key, txid) - self.update_tab('invoices') else: msg = msg or '' self.show_error(msg) diff --git a/electrum/gui/kivy/uix/screens.py b/electrum/gui/kivy/uix/screens.py @@ -380,14 +380,14 @@ class SendScreen(CScreen): if fee > feerate_warning * tx.estimated_size() / 1000: msg.append(_('Warning') + ': ' + _("The fee for this transaction seems unusually high.")) msg.append(_("Enter your PIN code to proceed")) - self.app.protected('\n'.join(msg), self.send_tx, (tx, invoice)) + self.app.protected('\n'.join(msg), self.send_tx, (tx,)) - def send_tx(self, tx, invoice, password): + def send_tx(self, tx, password): if self.app.wallet.has_password() and password is None: return def on_success(tx): if tx.is_complete(): - self.app.broadcast(tx, invoice) + self.app.broadcast(tx) else: self.app.tx_dialog(tx) def on_failure(error): diff --git a/electrum/gui/qt/invoice_list.py b/electrum/gui/qt/invoice_list.py @@ -96,7 +96,8 @@ class InvoiceList(MyTreeView): _list = self.parent.wallet.get_invoices() # filter out paid invoices unless we have the log lnworker_logs = self.parent.wallet.lnworker.logs if self.parent.wallet.lnworker else {} - _list = [x for x in _list if x and x.get('status') != PR_PAID or x.get('rhash') in lnworker_logs] + _list = [x for x in _list + if x and (x.get('status') != PR_PAID or x.get('rhash') in lnworker_logs)] self.model().clear() self.update_headers(self.__class__.headers) for idx, item in enumerate(_list): diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py @@ -928,9 +928,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): d = address_dialog.AddressDialog(self, addr) d.exec_() - def show_transaction(self, tx, *, invoice=None, tx_desc=None): + def show_transaction(self, tx, *, tx_desc=None): '''tx_desc is set only for txs created in the Send tab''' - show_transaction(tx, parent=self, invoice=invoice, desc=tx_desc) + show_transaction(tx, parent=self, desc=tx_desc) def create_receive_tab(self): # A 4-column grid layout. All the stretch is in the last column. @@ -1472,7 +1472,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): self.pay_lightning_invoice(invoice['invoice'], amount_sat=invoice['amount']) elif invoice['type'] == PR_TYPE_ONCHAIN: outputs = invoice['outputs'] - self.pay_onchain_dialog(self.get_coins(), outputs, invoice=invoice) + self.pay_onchain_dialog(self.get_coins(), outputs) else: raise Exception('unknown invoice type') @@ -1492,7 +1492,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): def pay_onchain_dialog(self, inputs: Sequence[PartialTxInput], outputs: List[PartialTxOutput], *, - invoice=None, external_keypairs=None) -> None: + external_keypairs=None) -> None: # trustedcoin requires this if run_hook('abort_send', self): return @@ -1508,8 +1508,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): return if self.config.get('advanced_preview'): self.preview_tx_dialog(make_tx=make_tx, - external_keypairs=external_keypairs, - invoice=invoice) + external_keypairs=external_keypairs) return output_value = '!' if '!' in output_values else sum(output_values) @@ -1524,27 +1523,26 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): if is_send: def sign_done(success): if success: - self.broadcast_or_show(tx, invoice=invoice) + self.broadcast_or_show(tx) self.sign_tx_with_password(tx, callback=sign_done, password=password, external_keypairs=external_keypairs) else: self.preview_tx_dialog(make_tx=make_tx, - external_keypairs=external_keypairs, - invoice=invoice) + external_keypairs=external_keypairs) - def preview_tx_dialog(self, *, make_tx, external_keypairs=None, invoice=None): + def preview_tx_dialog(self, *, make_tx, external_keypairs=None): d = PreviewTxDialog(make_tx=make_tx, external_keypairs=external_keypairs, - window=self, invoice=invoice) + window=self) d.show() - def broadcast_or_show(self, tx, *, invoice=None): + def broadcast_or_show(self, tx: Transaction): if not self.network: self.show_error(_("You can't broadcast a transaction without a live network connection.")) - self.show_transaction(tx, invoice=invoice) + self.show_transaction(tx) elif not tx.is_complete(): - self.show_transaction(tx, invoice=invoice) + self.show_transaction(tx) else: - self.broadcast_transaction(tx, invoice=invoice) + self.broadcast_transaction(tx) @protected def sign_tx(self, tx, *, callback, external_keypairs, password): @@ -1568,7 +1566,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): msg = _('Signing transaction...') WaitingDialog(self, msg, task, on_success, on_failure) - def broadcast_transaction(self, tx: Transaction, *, invoice=None, tx_desc=None): + def broadcast_transaction(self, tx: Transaction): def broadcast_thread(): # non-GUI thread @@ -1584,11 +1582,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): return False, repr(e) # success txid = tx.txid() - if tx_desc: - self.wallet.set_label(txid, tx_desc) - if invoice: - self.wallet.set_paid(invoice['id'], txid) - self.wallet.set_label(txid, invoice['message']) if pr: self.payment_request = None refund_address = self.wallet.get_receiving_address() @@ -2709,7 +2702,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): scriptpubkey = bfh(bitcoin.address_to_script(addr)) outputs = [PartialTxOutput(scriptpubkey=scriptpubkey, value='!')] self.warn_if_watching_only() - self.pay_onchain_dialog(coins, outputs, invoice=None, external_keypairs=keypairs) + self.pay_onchain_dialog(coins, outputs, external_keypairs=keypairs) def _do_import(self, title, header_layout, func): text = text_dialog(self, title, header_layout, _('Import'), allow_multi=True) diff --git a/electrum/gui/qt/transaction_dialog.py b/electrum/gui/qt/transaction_dialog.py @@ -75,9 +75,9 @@ _logger = get_logger(__name__) dialogs = [] # Otherwise python randomly garbage collects the dialogs... -def show_transaction(tx: Transaction, *, parent: 'ElectrumWindow', invoice=None, desc=None, prompt_if_unsaved=False): +def show_transaction(tx: Transaction, *, parent: 'ElectrumWindow', desc=None, prompt_if_unsaved=False): try: - d = TxDialog(tx, parent=parent, invoice=invoice, desc=desc, prompt_if_unsaved=prompt_if_unsaved) + d = TxDialog(tx, parent=parent, desc=desc, prompt_if_unsaved=prompt_if_unsaved) except SerializationError as e: _logger.exception('unable to deserialize the transaction') parent.show_critical(_("Electrum was unable to deserialize the transaction:") + "\n" + str(e)) @@ -88,7 +88,7 @@ def show_transaction(tx: Transaction, *, parent: 'ElectrumWindow', invoice=None, class BaseTxDialog(QDialog, MessageBoxMixin): - def __init__(self, *, parent: 'ElectrumWindow', invoice, desc, prompt_if_unsaved, finalized: bool, external_keypairs=None): + def __init__(self, *, parent: 'ElectrumWindow', desc, prompt_if_unsaved, finalized: bool, external_keypairs=None): '''Transactions in the wallet will show their description. Pass desc to give a description for txs not yet in the wallet. ''' @@ -103,7 +103,6 @@ class BaseTxDialog(QDialog, MessageBoxMixin): self.prompt_if_unsaved = prompt_if_unsaved self.saved = False self.desc = desc - self.invoice = invoice self.setMinimumWidth(950) self.set_title() @@ -213,7 +212,7 @@ class BaseTxDialog(QDialog, MessageBoxMixin): def do_broadcast(self): self.main_window.push_top_level_window(self) try: - self.main_window.broadcast_transaction(self.tx, invoice=self.invoice, tx_desc=self.desc) + self.main_window.broadcast_transaction(self.tx) finally: self.main_window.pop_top_level_window(self) self.saved = True @@ -592,8 +591,8 @@ class TxDetailLabel(QLabel): class TxDialog(BaseTxDialog): - def __init__(self, tx: Transaction, *, parent: 'ElectrumWindow', invoice, desc, prompt_if_unsaved): - BaseTxDialog.__init__(self, parent=parent, invoice=invoice, desc=desc, prompt_if_unsaved=prompt_if_unsaved, finalized=True) + def __init__(self, tx: Transaction, *, parent: 'ElectrumWindow', desc, prompt_if_unsaved): + BaseTxDialog.__init__(self, parent=parent, desc=desc, prompt_if_unsaved=prompt_if_unsaved, finalized=True) self.set_tx(tx) self.update() @@ -601,9 +600,9 @@ class TxDialog(BaseTxDialog): class PreviewTxDialog(BaseTxDialog, TxEditor): - def __init__(self, *, make_tx, external_keypairs, window: 'ElectrumWindow', invoice): + def __init__(self, *, make_tx, external_keypairs, window: 'ElectrumWindow'): TxEditor.__init__(self, window=window, make_tx=make_tx, is_sweep=bool(external_keypairs)) - BaseTxDialog.__init__(self, parent=window, invoice=invoice, desc='', prompt_if_unsaved=False, + BaseTxDialog.__init__(self, parent=window, desc='', prompt_if_unsaved=False, finalized=False, external_keypairs=external_keypairs) self.update_tx() self.update() diff --git a/electrum/json_db.py b/electrum/json_db.py @@ -31,16 +31,16 @@ from collections import defaultdict from typing import Dict, Optional, List, Tuple, Set, Iterable, NamedTuple, Sequence from . import util, bitcoin -from .util import profiler, WalletFileException, multisig_type, TxMinedInfo +from .util import profiler, WalletFileException, multisig_type, TxMinedInfo, bfh from .keystore import bip44_derivation -from .transaction import Transaction +from .transaction import Transaction, TxOutpoint from .logging import Logger # seed_version is now used for the version of the wallet file OLD_SEED_VERSION = 4 # electrum versions < 2.0 NEW_SEED_VERSION = 11 # electrum versions >= 2.0 -FINAL_SEED_VERSION = 21 # electrum >= 2.7 will set this to prevent +FINAL_SEED_VERSION = 22 # electrum >= 2.7 will set this to prevent # old versions from overwriting new format @@ -215,6 +215,7 @@ class JsonDB(Logger): self._convert_version_19() self._convert_version_20() self._convert_version_21() + self._convert_version_22() self.put('seed_version', FINAL_SEED_VERSION) # just to be sure self._after_upgrade_tasks() @@ -496,6 +497,24 @@ class JsonDB(Logger): self.put('channels', channels) self.put('seed_version', 21) + def _convert_version_22(self): + # construct prevouts_by_scripthash + if not self._is_upgrade_method_needed(21, 21): + return + + from .bitcoin import script_to_scripthash + transactions = self.get('transactions', {}) # txid -> raw_tx + prevouts_by_scripthash = defaultdict(list) + for txid, raw_tx in transactions.items(): + tx = Transaction(raw_tx) + for idx, txout in enumerate(tx.outputs()): + outpoint = f"{txid}:{idx}" + scripthash = script_to_scripthash(txout.scriptpubkey.hex()) + prevouts_by_scripthash[scripthash].append((outpoint, txout.value)) + self.put('prevouts_by_scripthash', prevouts_by_scripthash) + + self.put('seed_version', 22) + def _convert_imported(self): if not self._is_upgrade_method_needed(0, 13): return @@ -661,6 +680,25 @@ class JsonDB(Logger): self.spent_outpoints[prevout_hash][prevout_n] = tx_hash @modifier + def add_prevout_by_scripthash(self, scripthash: str, *, prevout: TxOutpoint, value: int) -> None: + assert isinstance(prevout, TxOutpoint) + if scripthash not in self._prevouts_by_scripthash: + self._prevouts_by_scripthash[scripthash] = set() + self._prevouts_by_scripthash[scripthash].add((prevout.to_str(), value)) + + @modifier + def remove_prevout_by_scripthash(self, scripthash: str, *, prevout: TxOutpoint, value: int) -> None: + assert isinstance(prevout, TxOutpoint) + self._prevouts_by_scripthash[scripthash].discard((prevout.to_str(), value)) + if not self._prevouts_by_scripthash[scripthash]: + self._prevouts_by_scripthash.pop(scripthash) + + @locked + def get_prevouts_by_scripthash(self, scripthash: str) -> Set[Tuple[TxOutpoint, int]]: + prevouts_and_values = self._prevouts_by_scripthash.get(scripthash, set()) + return {(TxOutpoint.from_str(prevout), value) for prevout, value in prevouts_and_values} + + @modifier def add_transaction(self, tx_hash: str, tx: Transaction) -> None: assert isinstance(tx, Transaction) self.transactions[tx_hash] = tx @@ -863,14 +901,19 @@ class JsonDB(Logger): self.history = self.get_data_ref('addr_history') # address -> list of (txid, height) self.verified_tx = self.get_data_ref('verified_tx3') # txid -> (height, timestamp, txpos, header_hash) self.tx_fees = self.get_data_ref('tx_fees') # type: Dict[str, TxFeesValue] + # scripthash -> set of (outpoint, value) + self._prevouts_by_scripthash = self.get_data_ref('prevouts_by_scripthash') # type: Dict[str, Set[Tuple[str, int]]] # convert raw hex transactions to Transaction objects for tx_hash, raw_tx in self.transactions.items(): self.transactions[tx_hash] = Transaction(raw_tx) - # convert list to set + # convert txi, txo: list to set for t in self.txi, self.txo: for d in t.values(): for addr, lst in d.items(): d[addr] = set([tuple(x) for x in lst]) + # convert prevouts_by_scripthash: list to set, list to tuple + for scripthash, lst in self._prevouts_by_scripthash.items(): + self._prevouts_by_scripthash[scripthash] = {(prevout, value) for prevout, value in lst} # remove unreferenced tx for tx_hash in list(self.transactions.keys()): if not self.get_txi_addresses(tx_hash) and not self.get_txo_addresses(tx_hash): diff --git a/electrum/wallet.py b/electrum/wallet.py @@ -36,9 +36,10 @@ import errno import traceback import operator from functools import partial +from collections import defaultdict from numbers import Number from decimal import Decimal -from typing import TYPE_CHECKING, List, Optional, Tuple, Union, NamedTuple, Sequence, Dict, Any +from typing import TYPE_CHECKING, List, Optional, Tuple, Union, NamedTuple, Sequence, Dict, Any, Set from .i18n import _ from .bip32 import BIP32Node @@ -252,6 +253,7 @@ class Abstract_Wallet(AddressSynchronizer): if invoice.get('type') == PR_TYPE_ONCHAIN: outputs = [PartialTxOutput.from_legacy_tuple(*output) for output in invoice.get('outputs')] invoice['outputs'] = outputs + self._prepare_onchain_invoice_paid_detection() self.calc_unused_change_addresses() # save wallet type the first time if self.storage.get('wallet_type') is None: @@ -614,7 +616,10 @@ class Abstract_Wallet(AddressSynchronizer): elif invoice_type == PR_TYPE_ONCHAIN: key = bh2u(sha256(repr(invoice))[0:16]) invoice['id'] = key - invoice['txid'] = None + outputs = invoice['outputs'] # type: List[PartialTxOutput] + with self.transaction_lock: + for txout in outputs: + self._invoices_from_scriptpubkey_map[txout.scriptpubkey].add(key) else: raise Exception('Unsupported invoice type') self.invoices[key] = invoice @@ -632,27 +637,73 @@ class Abstract_Wallet(AddressSynchronizer): out.sort(key=operator.itemgetter('time')) return out - def set_paid(self, key, txid): - if key not in self.invoices: - return - invoice = self.invoices[key] - assert invoice.get('type') == PR_TYPE_ONCHAIN - invoice['txid'] = txid - self.storage.put('invoices', self.invoices) - def get_invoice(self, key): if key not in self.invoices: return item = copy.copy(self.invoices[key]) request_type = item.get('type') if request_type == PR_TYPE_ONCHAIN: - item['status'] = PR_PAID if item.get('txid') is not None else PR_UNPAID + item['status'] = PR_PAID if self._is_onchain_invoice_paid(item)[0] else PR_UNPAID elif self.lnworker and request_type == PR_TYPE_LN: item['status'] = self.lnworker.get_payment_status(bfh(item['rhash'])) else: return return item + def _get_relevant_invoice_keys_for_tx(self, tx: Transaction) -> Set[str]: + relevant_invoice_keys = set() + for txout in tx.outputs(): + for invoice_key in self._invoices_from_scriptpubkey_map.get(txout.scriptpubkey, set()): + relevant_invoice_keys.add(invoice_key) + return relevant_invoice_keys + + def _prepare_onchain_invoice_paid_detection(self): + # scriptpubkey -> list(invoice_keys) + self._invoices_from_scriptpubkey_map = defaultdict(set) # type: Dict[bytes, Set[str]] + for invoice_key, invoice in self.invoices.items(): + if invoice.get('type') == PR_TYPE_ONCHAIN: + outputs = invoice['outputs'] # type: List[PartialTxOutput] + for txout in outputs: + self._invoices_from_scriptpubkey_map[txout.scriptpubkey].add(invoice_key) + + def _is_onchain_invoice_paid(self, invoice) -> Tuple[bool, Sequence[str]]: + """Returns whether on-chain invoice is satisfied, and list of relevant TXIDs.""" + assert invoice.get('type') == PR_TYPE_ONCHAIN + invoice_amounts = defaultdict(int) # type: Dict[bytes, int] # scriptpubkey -> value_sats + for txo in invoice['outputs']: # type: PartialTxOutput + invoice_amounts[txo.scriptpubkey] += 1 if txo.value == '!' else txo.value + relevant_txs = [] + with self.transaction_lock: + for invoice_scriptpubkey, invoice_amt in invoice_amounts.items(): + scripthash = bitcoin.script_to_scripthash(invoice_scriptpubkey.hex()) + prevouts_and_values = self.db.get_prevouts_by_scripthash(scripthash) + relevant_txs += [prevout.txid.hex() for prevout, v in prevouts_and_values] + total_received = sum([v for prevout, v in prevouts_and_values]) + if total_received < invoice_amt: + return False, [] + return True, relevant_txs + + def _maybe_set_tx_label_based_on_invoices(self, tx: Transaction) -> bool: + tx_hash = tx.txid() + with self.transaction_lock: + labels = [] + for invoice_key in self._get_relevant_invoice_keys_for_tx(tx): + invoice = self.invoices.get(invoice_key) + if invoice is None: continue + assert invoice.get('type') == PR_TYPE_ONCHAIN + if invoice['message']: + labels.append(invoice['message']) + if labels: + self.set_label(tx_hash, "; ".join(labels)) + return bool(labels) + + def add_transaction(self, tx, *, allow_unrelated=False): + tx_was_added = super().add_transaction(tx, allow_unrelated=allow_unrelated) + + if tx_was_added: + self._maybe_set_tx_label_based_on_invoices(tx) + return tx_was_added + @profiler def get_full_history(self, fx=None, *, onchain_domain=None, include_lightning=True): transactions = OrderedDictWithIndex() @@ -1877,10 +1928,6 @@ class Imported_Wallet(Simple_Wallet): self.db.remove_addr_history(address) for tx_hash in transactions_to_remove: self.remove_transaction(tx_hash) - self.db.remove_tx_fee(tx_hash) - self.db.remove_verified_tx(tx_hash) - self.unverified_tx.pop(tx_hash, None) - self.db.remove_transaction(tx_hash) self.set_label(address, None) self.remove_payment_request(address) self.set_frozen_state_of_addresses([address], False)