URI: 
       twallet.remove_transaction: also rm dependent/child txs - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit c4e9afa019e74a46585ca9af3eef433f19f338b9
   DIR parent 4f3a28b87e244a158d6a038fdf791c480f583418
  HTML Author: SomberNight <somber.night@protonmail.com>
       Date:   Thu, 21 Jan 2021 18:05:48 +0100
       
       wallet.remove_transaction: also rm dependent/child txs
       
       Main motivation is that I often use wallet.remove_transaction
       from the Qt console, and would find this behaviour more intuitive.
       Note that previously if one were to call this on a tx with children,
       tthe crash reporter would appear with "wallet.get_history() failed balance sanity-check".
       
       related: https://github.com/spesmilo/electrum/issues/6960#issuecomment-764716533
       
       Diffstat:
         M electrum/address_synchronizer.py    |      19 ++++++++++++++-----
         M electrum/commands.py                |       5 +----
         M electrum/gui/kivy/uix/dialogs/tx_d… |      10 ++++------
         M electrum/gui/qt/history_list.py     |      10 ++++------
       
       4 files changed, 23 insertions(+), 21 deletions(-)
       ---
   DIR diff --git a/electrum/address_synchronizer.py b/electrum/address_synchronizer.py
       t@@ -292,11 +292,7 @@ class AddressSynchronizer(Logger):
                            # this is a local tx that conflicts with non-local txns; drop.
                            return False
                        # keep this txn and remove all conflicting
       -                to_remove = set()
       -                to_remove |= conflicting_txns
       -                for conflicting_tx_hash in conflicting_txns:
       -                    to_remove |= self.get_depending_transactions(conflicting_tx_hash)
       -                for tx_hash2 in to_remove:
       +                for tx_hash2 in conflicting_txns:
                            self.remove_transaction(tx_hash2)
                    # add inputs
                    def add_value_from_prev_output():
       t@@ -342,6 +338,19 @@ class AddressSynchronizer(Logger):
                    return True
        
            def remove_transaction(self, tx_hash: str) -> None:
       +        """Removes a transaction AND all its dependents/children
       +        from the wallet history.
       +        """
       +        with self.lock, self.transaction_lock:
       +            to_remove = {tx_hash}
       +            to_remove |= self.get_depending_transactions(tx_hash)
       +            for txid in to_remove:
       +                self._remove_transaction(txid)
       +
       +    def _remove_transaction(self, tx_hash: str) -> None:
       +        """Removes a single transaction from the wallet history, and attempts
       +         to undo all effects of the tx (spending inputs, creating outputs, etc).
       +        """
                def remove_from_spent_outpoints():
                    # undo spends in spent_outpoints
                    if tx is not None:
   DIR diff --git a/electrum/commands.py b/electrum/commands.py
       t@@ -933,13 +933,10 @@ class Commands:
                if not is_hash256_str(txid):
                    raise Exception(f"{repr(txid)} is not a txid")
                height = wallet.get_tx_height(txid).height
       -        to_delete = {txid}
                if height != TX_HEIGHT_LOCAL:
                    raise Exception(f'Only local transactions can be removed. '
                                    f'This tx has height: {height} != {TX_HEIGHT_LOCAL}')
       -        to_delete |= wallet.get_depending_transactions(txid)
       -        for tx_hash in to_delete:
       -            wallet.remove_transaction(tx_hash)
       +        wallet.remove_transaction(txid)
                wallet.save_db()
        
            @command('wn')
   DIR diff --git a/electrum/gui/kivy/uix/dialogs/tx_dialog.py b/electrum/gui/kivy/uix/dialogs/tx_dialog.py
       t@@ -348,17 +348,15 @@ class TxDialog(Factory.Popup):
        
            def remove_local_tx(self):
                txid = self.tx.txid()
       -        to_delete = {txid}
       -        to_delete |= self.wallet.get_depending_transactions(txid)
       +        num_child_txs = len(self.wallet.get_depending_transactions(txid))
                question = _("Are you sure you want to remove this transaction?")
       -        if len(to_delete) > 1:
       +        if num_child_txs > 0:
                    question = (_("Are you sure you want to remove this transaction and {} child transactions?")
       -                        .format(len(to_delete) - 1))
       +                        .format(num_child_txs))
        
                def on_prompt(b):
                    if b:
       -                for tx in to_delete:
       -                    self.wallet.remove_transaction(tx)
       +                self.wallet.remove_transaction(txid)
                        self.wallet.save_db()
                        self.app._trigger_update_wallet()  # FIXME private...
                        self.dismiss()
   DIR diff --git a/electrum/gui/qt/history_list.py b/electrum/gui/qt/history_list.py
       t@@ -710,17 +710,15 @@ class HistoryList(MyTreeView, AcceptFileDragDrop):
                menu.exec_(self.viewport().mapToGlobal(position))
        
            def remove_local_tx(self, tx_hash: str):
       -        to_delete = {tx_hash}
       -        to_delete |= self.wallet.get_depending_transactions(tx_hash)
       +        num_child_txs = len(self.wallet.get_depending_transactions(tx_hash))
                question = _("Are you sure you want to remove this transaction?")
       -        if len(to_delete) > 1:
       +        if num_child_txs > 0:
                    question = (_("Are you sure you want to remove this transaction and {} child transactions?")
       -                        .format(len(to_delete) - 1))
       +                        .format(num_child_txs))
                if not self.parent.question(msg=question,
                                            title=_("Please confirm")):
                    return
       -        for tx in to_delete:
       -            self.wallet.remove_transaction(tx)
       +        self.wallet.remove_transaction(tx_hash)
                self.wallet.save_db()
                # need to update at least: history_list, utxo_list, address_list
                self.parent.need_update.set()