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, the crash reporter would appear with "wallet.get_history() failed balance sanity-check". related: https://github.com/spesmilo/electrum/issues/6960#issuecomment-764716533
This commit is contained in:
@@ -292,11 +292,7 @@ class AddressSynchronizer(Logger):
|
|||||||
# this is a local tx that conflicts with non-local txns; drop.
|
# this is a local tx that conflicts with non-local txns; drop.
|
||||||
return False
|
return False
|
||||||
# keep this txn and remove all conflicting
|
# keep this txn and remove all conflicting
|
||||||
to_remove = set()
|
for tx_hash2 in conflicting_txns:
|
||||||
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:
|
|
||||||
self.remove_transaction(tx_hash2)
|
self.remove_transaction(tx_hash2)
|
||||||
# add inputs
|
# add inputs
|
||||||
def add_value_from_prev_output():
|
def add_value_from_prev_output():
|
||||||
@@ -342,6 +338,19 @@ class AddressSynchronizer(Logger):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def remove_transaction(self, tx_hash: str) -> None:
|
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():
|
def remove_from_spent_outpoints():
|
||||||
# undo spends in spent_outpoints
|
# undo spends in spent_outpoints
|
||||||
if tx is not None:
|
if tx is not None:
|
||||||
|
|||||||
@@ -933,13 +933,10 @@ class Commands:
|
|||||||
if not is_hash256_str(txid):
|
if not is_hash256_str(txid):
|
||||||
raise Exception(f"{repr(txid)} is not a txid")
|
raise Exception(f"{repr(txid)} is not a txid")
|
||||||
height = wallet.get_tx_height(txid).height
|
height = wallet.get_tx_height(txid).height
|
||||||
to_delete = {txid}
|
|
||||||
if height != TX_HEIGHT_LOCAL:
|
if height != TX_HEIGHT_LOCAL:
|
||||||
raise Exception(f'Only local transactions can be removed. '
|
raise Exception(f'Only local transactions can be removed. '
|
||||||
f'This tx has height: {height} != {TX_HEIGHT_LOCAL}')
|
f'This tx has height: {height} != {TX_HEIGHT_LOCAL}')
|
||||||
to_delete |= wallet.get_depending_transactions(txid)
|
wallet.remove_transaction(txid)
|
||||||
for tx_hash in to_delete:
|
|
||||||
wallet.remove_transaction(tx_hash)
|
|
||||||
wallet.save_db()
|
wallet.save_db()
|
||||||
|
|
||||||
@command('wn')
|
@command('wn')
|
||||||
|
|||||||
@@ -348,17 +348,15 @@ class TxDialog(Factory.Popup):
|
|||||||
|
|
||||||
def remove_local_tx(self):
|
def remove_local_tx(self):
|
||||||
txid = self.tx.txid()
|
txid = self.tx.txid()
|
||||||
to_delete = {txid}
|
num_child_txs = len(self.wallet.get_depending_transactions(txid))
|
||||||
to_delete |= self.wallet.get_depending_transactions(txid)
|
|
||||||
question = _("Are you sure you want to remove this transaction?")
|
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?")
|
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):
|
def on_prompt(b):
|
||||||
if b:
|
if b:
|
||||||
for tx in to_delete:
|
self.wallet.remove_transaction(txid)
|
||||||
self.wallet.remove_transaction(tx)
|
|
||||||
self.wallet.save_db()
|
self.wallet.save_db()
|
||||||
self.app._trigger_update_wallet() # FIXME private...
|
self.app._trigger_update_wallet() # FIXME private...
|
||||||
self.dismiss()
|
self.dismiss()
|
||||||
|
|||||||
@@ -710,17 +710,15 @@ class HistoryList(MyTreeView, AcceptFileDragDrop):
|
|||||||
menu.exec_(self.viewport().mapToGlobal(position))
|
menu.exec_(self.viewport().mapToGlobal(position))
|
||||||
|
|
||||||
def remove_local_tx(self, tx_hash: str):
|
def remove_local_tx(self, tx_hash: str):
|
||||||
to_delete = {tx_hash}
|
num_child_txs = len(self.wallet.get_depending_transactions(tx_hash))
|
||||||
to_delete |= self.wallet.get_depending_transactions(tx_hash)
|
|
||||||
question = _("Are you sure you want to remove this transaction?")
|
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?")
|
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,
|
if not self.parent.question(msg=question,
|
||||||
title=_("Please confirm")):
|
title=_("Please confirm")):
|
||||||
return
|
return
|
||||||
for tx in to_delete:
|
self.wallet.remove_transaction(tx_hash)
|
||||||
self.wallet.remove_transaction(tx)
|
|
||||||
self.wallet.save_db()
|
self.wallet.save_db()
|
||||||
# need to update at least: history_list, utxo_list, address_list
|
# need to update at least: history_list, utxo_list, address_list
|
||||||
self.parent.need_update.set()
|
self.parent.need_update.set()
|
||||||
|
|||||||
Reference in New Issue
Block a user