dust bug fix

This commit is contained in:
2026-02-09 11:58:07 -04:00
parent 2a4eab81fd
commit 0df6786f50
4 changed files with 108 additions and 44 deletions

View File

@@ -29,7 +29,8 @@ from electrum.util import (
from .util import Util from .util import Util
from .willexecutors import Willexecutors from .willexecutors import Willexecutors
from electrum.util import BitcoinException
from electrum import constants
if TYPE_CHECKING: if TYPE_CHECKING:
from .simple_config import SimpleConfig from .simple_config import SimpleConfig
from .wallet_db import WalletDB from .wallet_db import WalletDB
@@ -41,6 +42,7 @@ HEIR_ADDRESS = 0
HEIR_AMOUNT = 1 HEIR_AMOUNT = 1
HEIR_LOCKTIME = 2 HEIR_LOCKTIME = 2
HEIR_REAL_AMOUNT = 3 HEIR_REAL_AMOUNT = 3
HEIR_DUST_AMOUNT = 4
TRANSACTION_LABEL = "inheritance transaction" TRANSACTION_LABEL = "inheritance transaction"
@@ -88,6 +90,7 @@ def prepare_transactions(locktimes, available_utxos, fees, wallet):
txsout = {} txsout = {}
locktime, _ = Util.get_lowest_locktimes(locktimes) locktime, _ = Util.get_lowest_locktimes(locktimes)
if not locktime: if not locktime:
_logger.info("prepare transactions, no locktime")
return return
locktime = locktime[0] locktime = locktime[0]
@@ -101,22 +104,25 @@ def prepare_transactions(locktimes, available_utxos, fees, wallet):
outputs = [] outputs = []
paid_heirs = {} paid_heirs = {}
for name, heir in heirs.items(): for name, heir in heirs.items():
if len(heir) > HEIR_REAL_AMOUNT and not "DUST" in str(heir[HEIR_REAL_AMOUNT]):
try: try:
if len(heir) > HEIR_REAL_AMOUNT:
real_amount = heir[HEIR_REAL_AMOUNT] real_amount = heir[HEIR_REAL_AMOUNT]
out_amount += real_amount
description += f"{name}\n"
paid_heirs[name] = heir
outputs.append( outputs.append(
PartialTxOutput.from_address_and_value( PartialTxOutput.from_address_and_value(
heir[HEIR_ADDRESS], real_amount heir[HEIR_ADDRESS], real_amount
) )
) )
else: out_amount += real_amount
description += f"{name}\n"
except BitcoinException as e:
_logger.info("exception decoding output{} - {}".format(type(e),e))
heir[HEIR_REAL_AMOUNT]=e
except Exception as e:
heir[HEIR_REAL_AMOUNT]=e
_logger.info(f"error preparing transactions {e}")
pass pass
except Exception as e: paid_heirs[name] = heir
pass
in_amount = 0.0 in_amount = 0.0
used_utxos = [] used_utxos = []
@@ -125,12 +131,14 @@ def prepare_transactions(locktimes, available_utxos, fees, wallet):
value = utxo.value_sats() value = utxo.value_sats()
in_amount += value in_amount += value
used_utxos.append(utxo) used_utxos.append(utxo)
if in_amount > out_amount: if in_amount >= out_amount:
break break
except IndexError as e: except IndexError as e:
_logger.info(f"error preparing transactions index error {e} {in_amount}, {out_amount}")
pass pass
if int(in_amount) < int(out_amount): if int(in_amount) < int(out_amount):
_logger.info("error preparing transactions in_amount < out_amount ({} < {}) ")
break break
heirsvalue = out_amount heirsvalue = out_amount
change = get_change_output(wallet, in_amount, out_amount, fee) change = get_change_output(wallet, in_amount, out_amount, fee)
@@ -263,9 +271,10 @@ def get_change_output(wallet, in_amount, out_amount, fee):
class Heirs(dict, Logger): class Heirs(dict, Logger):
def __init__(self, db: "WalletDB"): def __init__(self, wallet):
Logger.__init__(self) Logger.__init__(self)
self.db = db self.db = wallet.db
self.wallet=wallet
d = self.db.get("heirs", {}) d = self.db.get("heirs", {})
try: try:
self.update(d) self.update(d)
@@ -327,6 +336,10 @@ class Heirs(dict, Logger):
if value > wallet.dust_threshold(): if value > wallet.dust_threshold():
heir_list[key].insert(HEIR_REAL_AMOUNT, value) heir_list[key].insert(HEIR_REAL_AMOUNT, value)
amount += value amount += value
else:
heir_list[key].insert(HEIR_REAL_AMOUNT,f"DUST: {value}")
heir_list[key].insert(HEIR_DUST_AMOUNT,value)
_logger.info(f"{key}, {value} is dust will be ignored")
except Exception as e: except Exception as e:
raise e raise e
@@ -449,6 +462,7 @@ class Heirs(dict, Logger):
): ):
Heirs._validate(self) Heirs._validate(self)
if len(self) <= 0: if len(self) <= 0:
_logger.info("while building transactions there was no heirs")
return return
balance = 0.0 balance = 0.0
len_utxo_set = 0 len_utxo_set = 0
@@ -464,6 +478,7 @@ class Heirs(dict, Logger):
len_utxo_set += 1 len_utxo_set += 1
available_utxos.append(utxo) available_utxos.append(utxo)
if len_utxo_set == 0: if len_utxo_set == 0:
_logger.info("no usable utxos")
return return
j = -2 j = -2
willexecutorsitems = list(willexecutors.items()) willexecutorsitems = list(willexecutors.items())
@@ -505,6 +520,7 @@ class Heirs(dict, Logger):
if not txs: if not txs:
return {} return {}
except Exception as e: except Exception as e:
_logger.info(f"error preparing transactions{e}")
try: try:
if "w!ll3x3c" in e.heirname: if "w!ll3x3c" in e.heirname:
Willexecutors.is_selected(willexecutors[w], False) Willexecutors.is_selected(willexecutors[w], False)
@@ -632,7 +648,7 @@ class Heirs(dict, Logger):
return None return None
def validate_address(address): def validate_address(address):
if not bitcoin.is_address(address): if not bitcoin.is_address(address,net=constants.net):
raise NotAnAddress(f"not an address,{address}") raise NotAnAddress(f"not an address,{address}")
return address return address
@@ -661,12 +677,14 @@ class Heirs(dict, Logger):
return (address, amount, locktime) return (address, amount, locktime)
def _validate(data, timestamp_to_check=False): def _validate(data, timestamp_to_check=False):
for k, v in list(data.items()): for k, v in list(data.items()):
if k == "heirs": if k == "heirs":
return Heirs._validate(v) return Heirs._validate(v,timestamp_to_check)
try: try:
Heirs.validate_heir(k, v) Heirs.validate_heir(k, v, timestamp_to_check)
except Exception as e: except Exception as e:
_logger.info(f"exception heir removed {e}")
data.pop(k) data.pop(k)
return data return data
@@ -685,3 +703,6 @@ class LocktimeNotValid(ValueError):
class HeirExpiredException(LocktimeNotValid): class HeirExpiredException(LocktimeNotValid):
pass pass
class HeirAmountIsDustException(Exception):
pass

32
qt.py
View File

@@ -145,7 +145,7 @@ from electrum.util import (
from .bal import BalPlugin from .bal import BalPlugin
from .bal_resources import DEFAULT_ICON, icon_path from .bal_resources import DEFAULT_ICON, icon_path
from .heirs import Heirs from .heirs import (Heirs,HEIR_REAL_AMOUNT,HEIR_DUST_AMOUNT)
from .util import Util from .util import Util
from .will import ( from .will import (
AmountException, AmountException,
@@ -473,7 +473,7 @@ class BalWindow(Logger):
self.bal_plugin, update=False, bal_window=self self.bal_plugin, update=False, bal_window=self
) )
if not self.heirs: if not self.heirs:
self.heirs = Heirs._validate(Heirs(self.wallet.db)) self.heirs = Heirs._validate(Heirs(self.wallet))
if not self.will: if not self.will:
self.will = self.wallet.db.get_dict("will") self.will = self.wallet.db.get_dict("will")
Util.fix_will_tx_fees(self.will) Util.fix_will_tx_fees(self.will)
@@ -655,6 +655,7 @@ class BalWindow(Logger):
Will.normalize_will(self.willitems, self.wallet) Will.normalize_will(self.willitems, self.wallet)
def build_will(self, ignore_duplicate=True, keep_original=True): def build_will(self, ignore_duplicate=True, keep_original=True):
_logger.debug("building will...")
will = {} will = {}
willtodelete = [] willtodelete = []
willtoappend = {} willtoappend = {}
@@ -670,6 +671,9 @@ class BalWindow(Logger):
if Willexecutors.is_selected(w): if Willexecutors.is_selected(w):
f = True f = True
if not f: if not f:
_logger.error(
"No Will-Executor or backup transaction selected"
)
raise NoWillExecutorNotPresent( raise NoWillExecutorNotPresent(
"No Will-Executor or backup transaction selected" "No Will-Executor or backup transaction selected"
) )
@@ -680,7 +684,8 @@ class BalWindow(Logger):
None, None,
self.date_to_check, self.date_to_check,
) )
self.logger.info(txs)
self.logger.info(f"txs built: {txs}")
creation_time = time.time() creation_time = time.time()
if txs: if txs:
for txid in txs: for txid in txs:
@@ -699,7 +704,13 @@ class BalWindow(Logger):
tx["txchildren"] = [] tx["txchildren"] = []
will[txid] = WillItem(tx, _id=txid, wallet=self.wallet) will[txid] = WillItem(tx, _id=txid, wallet=self.wallet)
self.update_will(will) self.update_will(will)
else:
self.logger.info("No transactions was built")
self.logger.info(f"will-settings: {self.will_settings}")
self.logger.info(f"date_to_check:{self.date_to_check}")
self.logger.info(f"heirs: {self.heirs}")
except Exception as e: except Exception as e:
self.logger.info(f"Exception build_will: {e}")
raise e raise e
pass pass
return self.willitems return self.willitems
@@ -2110,7 +2121,7 @@ class BalBuildWillDialog(BalDialog):
_logger.debug("no heirs") _logger.debug("no heirs")
self.msg_set_checking("No Heirs") self.msg_set_checking("No Heirs")
except NotCompleteWillException as e: except NotCompleteWillException as e:
_logger.debug("not complete", e) _logger.debug(f"not complete {e} true")
message = False message = False
have_to_build = True have_to_build = True
if isinstance(e, HeirChangeException): if isinstance(e, HeirChangeException):
@@ -2124,7 +2135,7 @@ class BalBuildWillDialog(BalDialog):
elif isinstance(e, HeirNotFoundException): elif isinstance(e, HeirNotFoundException):
message = "Heir not found" message = "Heir not found"
if message: if message:
_logger.debug("message") _logger.debug(f"message: {message}")
self.msg_set_checking(message) self.msg_set_checking(message)
else: else:
self.msg_set_checking("New") self.msg_set_checking("New")
@@ -2140,6 +2151,15 @@ class BalBuildWillDialog(BalDialog):
except Exception as e: except Exception as e:
self.msg_set_building(self.msg_error(e)) self.msg_set_building(self.msg_error(e))
return False, None return False, None
excluded_heirs=[]
for wid in Will.only_valid(self.bal_window.willitems):
heirs=self.bal_window.willitems[wid].heirs
for hid,heir in heirs.items():
if "DUST" in str(heir[HEIR_REAL_AMOUNT]):
self.msg_set_status(f"<font color=\"red\">{hid},{heir[HEIR_DUST_AMOUNT]} is DUST",None,f"Excluded from will {wid}</font>")
have_to_sign = False have_to_sign = False
for wid in Will.only_valid(self.bal_window.willitems): for wid in Will.only_valid(self.bal_window.willitems):
if not self.bal_window.willitems[wid].get_status("COMPLETE"): if not self.bal_window.willitems[wid].get_status("COMPLETE"):
@@ -2399,8 +2419,6 @@ class BalBuildWillDialog(BalDialog):
self.password = self.bal_window.get_wallet_password(msg, parent=self) self.password = self.bal_window.get_wallet_password(msg, parent=self)
def msg_edit_row(self, line, row=None): def msg_edit_row(self, line, row=None):
_logger.debug(f"{row},{line}")
try: try:
self.labels[row] = line self.labels[row] = line
except Exception: except Exception:

63
will.py
View File

@@ -28,7 +28,6 @@ _logger = get_logger(__name__)
class Will: class Will:
# return an array with the list of children
def get_children(will, willid): def get_children(will, willid):
out = [] out = []
for _id in will: for _id in will:
@@ -153,6 +152,15 @@ class Will:
inp._TxInput__value_sats = change.value inp._TxInput__value_sats = change.value
return inp return inp
"""
in questa situazione sono presenti due transazioni con id differente(quindi transazioni differenti)
per prima cosa controllo il locktime
se il locktime della nuova transazione e' maggiore del locktime della vecchia transazione, allora
confronto gli eredi, per locktime se corrispondono controllo i willexecutor
se hanno la stessa url ma le fee vecchie sono superiori alle fee nuove, allora anticipare.
"""
def check_anticipate(ow: "WillItem", nw: "WillItem"): def check_anticipate(ow: "WillItem", nw: "WillItem"):
anticipate = Util.anticipate_locktime(ow.tx.locktime, days=1) anticipate = Util.anticipate_locktime(ow.tx.locktime, days=1)
if int(nw.tx.locktime) >= int(anticipate): if int(nw.tx.locktime) >= int(anticipate):
@@ -256,7 +264,7 @@ class Will:
to_append = {} to_append = {}
new_inputs = Will.get_all_inputs(will, only_valid=True) new_inputs = Will.get_all_inputs(will, only_valid=True)
for nid, nwi in will.items(): for nid, nwi in will.items():
if nwi.search_anticipate(new_inputs) or nwi.search_anticipate(old_inputs): if nwi.search_anticipate(new_inputs):
if nid != nwi.tx.txid(): if nid != nwi.tx.txid():
redo = True redo = True
to_delete.append(nid) to_delete.append(nid)
@@ -266,6 +274,17 @@ class Will:
Will.change_input( Will.change_input(
will, nid, i, outputs[i], new_inputs, to_delete, to_append will, nid, i, outputs[i], new_inputs, to_delete, to_append
) )
if nwi.search_anticipate(old_inputs):
if nid != nwi.tx.txid():
redo = True
to_delete.append(nid)
to_append[nwi.tx.txid()] = nwi
outputs = nwi.tx.outputs()
for i in range(0, len(outputs)):
Will.change_input(
will, nid, i, outputs[i], new_inputs, to_delete, to_append
)
for w in to_delete: for w in to_delete:
try: try:
@@ -275,8 +294,10 @@ class Will:
for k, w in to_append.items(): for k, w in to_append.items():
will[k] = w will[k] = w
if redo: if redo:
Will.search_anticipate_rec(will, old_inputs) Will.search_anticipate_rec(will, old_inputs)
def update_will(old_will, new_will): def update_will(old_will, new_will):
all_old_inputs = Will.get_all_inputs(old_will, only_valid=True) all_old_inputs = Will.get_all_inputs(old_will, only_valid=True)
all_inputs_min_locktime = Will.get_all_inputs_min_locktime(all_old_inputs) all_inputs_min_locktime = Will.get_all_inputs_min_locktime(all_old_inputs)
@@ -284,7 +305,6 @@ class Will:
# check if the new input is already spent by other transaction # check if the new input is already spent by other transaction
# if it is use the same locktime, or anticipate. # if it is use the same locktime, or anticipate.
Will.search_anticipate_rec(new_will, all_old_inputs) Will.search_anticipate_rec(new_will, all_old_inputs)
other_inputs = Will.get_all_inputs(old_will, {}) other_inputs = Will.get_all_inputs(old_will, {})
try: try:
Will.normalize_will(new_will, others_inputs=other_inputs) Will.normalize_will(new_will, others_inputs=other_inputs)
@@ -419,20 +439,18 @@ class Will:
# check if transactions are stil valid tecnically valid # check if transactions are stil valid tecnically valid
def check_invalidated(willtree, utxos_list, wallet): def check_invalidated(willtree, utxos_list, wallet):
for wid, w in willtree.items(): for wid, w in willtree.items():
#if not w.father: if not w.father or willtree[w.father].get_status("CONFIRMED") or willtree[w.father].get_status("PENDING"):
for inp in w.tx.inputs(): for inp in w.tx.inputs():
inp_str = Util.utxo_to_str(inp) inp_str = Util.utxo_to_str(inp)
if not inp_str in utxos_list: if not inp_str in utxos_list:
if wallet: if wallet:
height = Will.check_tx_height(w.tx, wallet) height = Will.check_tx_height(w.tx, wallet)
if height < 0: if height < 0:
Will.set_invalidate(wid, willtree) Will.set_invalidate(wid, willtree)
elif height == 0: elif height == 0:
w.set_status("PENDING", True) w.set_status("PENDING", True)
else: else:
w.set_status("CONFIRMED", True) w.set_status("CONFIRMED", True)
#else:
# print("father",w.father)
def reflect_to_children(treeitem): def reflect_to_children(treeitem):
if not treeitem.get_status("VALID"): if not treeitem.get_status("VALID"):
@@ -632,7 +650,6 @@ class Will:
class WillItem(Logger): class WillItem(Logger):
STATUS_DEFAULT = { STATUS_DEFAULT = {
"ANTICIPATED": ["Anticipated", False], "ANTICIPATED": ["Anticipated", False],
"BROADCASTED": ["Broadcasted", False], "BROADCASTED": ["Broadcasted", False],
@@ -662,7 +679,7 @@ class WillItem(Logger):
if self.STATUS[status][1] == bool(value): if self.STATUS[status][1] == bool(value):
return None return None
self.status += "." + ("NOT " if not value else "" + _(self.STATUS[status][0])) self.status += "." + (("NOT " if not value else "") + _(self.STATUS[status][0]))
self.STATUS[status][1] = bool(value) self.STATUS[status][1] = bool(value)
if value: if value:
if status in ["INVALIDATED", "REPLACED", "CONFIRMED", "PENDING"]: if status in ["INVALIDATED", "REPLACED", "CONFIRMED", "PENDING"]:
@@ -867,3 +884,11 @@ class PercAmountException(AmountException):
class FixedAmountException(AmountException): class FixedAmountException(AmountException):
pass pass
def test_check_invalidated():
Will.check_invalidated(will, utxos_list, wallet)

View File

@@ -119,7 +119,7 @@ class Willexecutors:
def send_request(method, url, data=None, *, timeout=10): def send_request(method, url, data=None, *, timeout=10):
network = Network.get_instance() network = Network.get_instance()
if not network: if not network:
raise ErrorConnectingServer("You are offline.") raise Exception("You are offline.")
_logger.debug(f"<-- {method} {url} {data}") _logger.debug(f"<-- {method} {url} {data}")
headers = {} headers = {}
headers["user-agent"] = f"BalPlugin v:{BalPlugin.version()}" headers["user-agent"] = f"BalPlugin v:{BalPlugin.version()}"
@@ -171,7 +171,7 @@ class Willexecutors:
out = True out = True
try: try:
_logger.debug(f"willexecutor['txs']") _logger.debug(f"{willexecutor[url]}: {willexecutor['txs']}")
if w := Willexecutors.send_request( if w := Willexecutors.send_request(
"post", "post",
willexecutor["url"] + "/" + constants.net.NET_NAME + "/pushtxs", willexecutor["url"] + "/" + constants.net.NET_NAME + "/pushtxs",