fix(plugin): missed-update fixes, server re-check, and bold Building Will results (v0.3.2)

Revert the v0.3.1 double-invalidation change (it caused an inheritance-list
regression: stale/invalidated wills lingered and heir/date updates became
incoherent) and add several targeted missed-update fixes plus a UI refinement.

Revert (v0.3.1 -> v0.3.2):
- Remove Will.mark_invalidated_by_tx() and its call in
  loop_broadcast_invalidating. core/will.py and gui/qt/dialogs.py are restored
  to the working v0.3.0 behaviour. The postpone double-invalidation issue is
  intentionally left open, to be addressed without touching the shared
  broadcast path.

FIX 1 - detect heir removal on Check / Electrum close:
- core/will.py (check_willexecutors_and_heirs): the else-branch now raises
  HeirNotFoundException when a will still carries an heir that is no longer in
  the current heirs set (heir removed), mirroring the existing 'heir added'
  path. Rebuild therefore triggers on Check and on_close (same build_will_task
  path), as decided by the user (manual update only, no auto-rebuild).

FIX 2 - Check queries servers for already-sent wills:
- core/will.py: new Will.needs_server_check(w) returns True for any VALID will
  with a will-executor that is not yet CHECKED (no longer limited to PUSHED).
- gui/qt/lists.py (PreviewList.check): use needs_server_check so wills stuck on
  'New / Not sent' are re-checked instead of reporting 'nothing to do'.

FIX 3 - Settings-dialog hide toggles refresh the list:
- core/plugin_base.py: new sync_hide_filters() re-reads the cached
  _hide_invalidated / _hide_replaced flags from the persisted config.
- gui/qt/window.py (update_all): call sync_hide_filters() before refreshing, so
  toggling 'Hide Invalidated' / 'Hide Replaced' in the Settings dialog (which
  writes the config directly) updates the transaction list immediately instead
  of requiring an Electrum restart.

UI - bold results in the Building Will dialog:
- gui/qt/dialogs.py (BalBuildWillDialog): render the right-side results in bold
  (Ok, Ko, Nothing to do, Skipped, Wait, Timeout, ...) keeping the left-side
  state labels in normal weight. Centralised in msg_ok/msg_error/msg_warning/
  msg_set_status, plus the will-executor push/check rows now show Ok/Ko and
  True/False in bold + colour (green/red).

Tests/tooling:
- tests/test_core_will.py: add test_check_heirs_unchanged_is_coherent,
  test_check_heir_removed_triggers_rebuild, test_check_heir_added_triggers_rebuild,
  test_needs_server_check.
- tests/sim_update_flows.py: real-world update-scenario simulation.
- tests/preview_build_will_dialog.py, tests/preview_we_rows.py: GUI-only
  before/after previews of the bold formatting.
- Bump version to 0.3.2 (VERSION, manifest.json, __init__.py, plugin_base.py).

186 tests pass; smoke test, external-zip test and update-flow simulation OK;
ruff reports only pre-existing star-import false positives.
This commit is contained in:
GenSpark AI Developer
2026-06-16 07:56:11 +00:00
committed by steal
parent e477c5aa5b
commit 365824767b
14 changed files with 669 additions and 163 deletions

View File

@@ -708,19 +708,6 @@ class BalBuildWillDialog(BalDialog):
self.msg_set_invalidating(self.msg_ok())
if not txid:
_logger.debug(f"should not be none txid: {txid}")
else:
# The invalidation tx is now broadcast, so the old signed/sent
# will transactions spending those same UTXOs can no longer be
# mined. Mark them INVALIDATED (which clears their VALID flag)
# so the postpone/expire check does NOT fire a second
# invalidation when phase 1 is restarted to rebuild the new
# (postponed) will.
invalidated = Will.mark_invalidated_by_tx(
self.bal_window.willitems, tx
)
if invalidated:
_logger.debug(f"invalidated will items: {invalidated}")
self.bal_window.save_willitems()
except TxBroadcastError as e:
_logger.error(f"fail to broadcast transaction:{e}")
@@ -784,7 +771,10 @@ class BalBuildWillDialog(BalDialog):
self.bal_window.willitems[wid].set_status("PUSH_FAIL", True)
retry_flag["value"] = True
done["count"] += 1
self.msg_edit_row("{} : {}".format(url, "Ok" if ok else "Ko"))
# Show the per-server result (Ok/Ko) in bold + color so the
# outcome stands out, keeping the server URL in normal weight.
result = self.msg_ok("Ok") if ok else self.msg_error("Ko")
self.msg_edit_row("{} : {}".format(url, result))
self.msg_set_pushing(_status_line())
def on_timeout(url, willexecutor):
@@ -836,7 +826,7 @@ class BalBuildWillDialog(BalDialog):
if self._stopping:
return
row = self.msg_edit_row(
"checking {} - {} : {}".format(
"checking {} - {} : <b>{}</b>".format(
self.bal_window.willitems[wid].we["url"], wid, "Waiting"
)
)
@@ -844,11 +834,16 @@ class BalBuildWillDialog(BalDialog):
w.set_check_willexecutor(
Willexecutors.check_transaction(wid, w.we["url"])
)
# Show the CHECKED result in bold + color (green True /
# red False) so the outcome stands out, keeping the server
# URL and tx id in normal weight.
checked = self.bal_window.willitems[wid].get_status("CHECKED")
result = self.msg_ok(checked) if checked else self.msg_error(checked)
row = self.msg_edit_row(
"checked {} - {} : {}".format(
self.bal_window.willitems[wid].we["url"],
wid,
self.bal_window.willitems[wid].get_status("CHECKED"),
result,
),
row,
)
@@ -1034,20 +1029,31 @@ class BalBuildWillDialog(BalDialog):
self.wait_row = self.msg_edit_row(f"Please wait {status}secs", self.wait_row)
def msg_error(self, e):
return "<font color='{}'>{}</font>".format(self.COLOR_ERROR, e)
# Results are shown in bold so the outcome stands out from the
# left-side state label (which stays in normal weight).
return "<font color='{}'><b>{}</b></font>".format(self.COLOR_ERROR, e)
def msg_ok(self, e="Ok"):
return "<font color='{}'>{}</font>".format(self.COLOR_OK, e)
# Results are shown in bold (see msg_error).
return "<font color='{}'><b>{}</b></font>".format(self.COLOR_OK, e)
def msg_warning(self, e):
return "<font color='{}'>{}</font".format(self.COLOR_WARNING, e)
# Results are shown in bold (see msg_error).
return "<font color='{}'><b>{}</b></font>".format(self.COLOR_WARNING, e)
def msg_set_status(self, msg, row=None, status=None, color=None):
# The left "state" label keeps its normal weight; only the right-side
# result (``status``) is rendered in bold so it is easy to read at a
# glance. ``status`` may already contain rich-text emitted by
# msg_ok/msg_error/msg_warning (which add their own <b>...</b>); wrapping
# it again in <b> is harmless for those cases.
status = "Wait" if status is None else status
if color is None:
line = f"{_(msg)}:\t{status}"
line = "{}:\t<b>{}</b>".format(_(msg), status)
else:
line = "<font color={}>{}:\t{}</font>".format(color, _(msg), status)
line = "{}:\t<font color={}><b>{}</b></font>".format(
_(msg), color, status
)
return self.msg_edit_row(line, row)
def ask_password(self, msg=None):

View File

@@ -532,11 +532,15 @@ class PreviewList(MyTreeView, MessageBoxMixin):
will = {}
for wid, w in self.bal_window.willitems.items():
if (
w.get_status("VALID")
and w.get_status("PUSHED")
and not w.get_status("CHECKED")
):
# Query the will-executor server for every valid will that HAS a
# will-executor assigned and is not yet CHECKED. Previously only
# transactions already marked PUSHED were checked, so a will that
# had actually been sent in the past but whose saved status still
# read "New" (not PUSHED) was skipped and the Check button reported
# "nothing to do". Will.needs_server_check now also includes such
# non-PUSHED wills, so the server can confirm the transaction is
# present and correct the status (see set_check_willexecutor).
if Will.needs_server_check(w):
will[wid] = w
if will:
self.bal_window.check_transactions(will)

View File

@@ -1227,6 +1227,13 @@ class BalWindow:
def update_all(self):
try:
# Re-sync the cached "hide invalidated/replaced" flags from the
# persisted config before refreshing the list. The Settings dialog
# checkboxes write the config directly (without touching the cached
# flags), so without this the list would keep filtering with the old
# value and the invalidated/replaced rows would not appear/disappear
# until Electrum was restarted.
self.bal_plugin.sync_hide_filters()
Will.add_willtree(self.willitems)
all_utxos = self.wallet.get_utxos()
utxos_list = Will.utxos_strs(all_utxos)