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

@@ -83,6 +83,25 @@ class Will:
if v.get_status("VALID"):
yield k
@staticmethod
def needs_server_check(w):
"""Return True if ``w`` should be queried on its will-executor server
when the user presses Check (or on Electrum close).
A will needs a server check when it is VALID, has a will-executor
assigned, and is not yet CHECKED. This intentionally includes wills
that are not (yet) marked PUSHED: a will that was actually sent in the
past but whose saved status still reads "New" would otherwise be
skipped, leaving the Server column stuck on "Not sent". The server
response (see WillItem.set_check_willexecutor) then corrects the status
to PUSHED/CHECKED if the transaction is present, or CHECK_FAIL if not.
"""
return bool(
w.get_status("VALID")
and w.we
and not w.get_status("CHECKED")
)
@staticmethod
def search_equal_tx(will, tx, wid):
for w in will:
@@ -420,31 +439,6 @@ class Will:
_logger.debug("len utxo_to_spend <=0")
pass
@staticmethod
def mark_invalidated_by_tx(will, tx):
"""Mark as INVALIDATED every valid will item that spends at least one
of the prevouts consumed by ``tx`` (the on-chain invalidation tx that
was just broadcast).
Once the invalidation tx is broadcast, the previously signed/sent will
transactions that relied on those same UTXOs can no longer be mined, so
their will items must stop being VALID. Setting INVALIDATED clears the
VALID flag (see WillItem.set_status), which removes them from
only_valid_list and therefore prevents the postpone/expire check from
firing a *second* invalidation on the next pass.
Returns the list of will ids that were marked.
"""
spent_prevouts = {i.prevout.to_str() for i in tx.inputs()}
invalidated = []
for wid in Will.only_valid_list(will):
w = will[wid]
wi_prevouts = {i.prevout.to_str() for i in w.tx.inputs()}
if spent_prevouts & wi_prevouts:
Will.set_invalidate(wid, will)
invalidated.append(wid)
return invalidated
@staticmethod
def is_new(will):
for wid, w in will.items():
@@ -732,9 +726,18 @@ class Will:
# signed/sent falls through here -> a plain rebuild via
# HeirNotFoundException (no on-chain fee needed).
else:
# The will still carries this heir, but the heir is no
# longer present in the current heirs set: the user
# removed it. This must trigger a rebuild exactly like
# "heir added" does, otherwise the removed heir would
# silently stay in the inheritance transaction. Raising
# HeirNotFoundException reuses the same rebuild path used
# by the Check button and by on_close (Electrum quit).
_logger.debug(
f"heir not present transaction is not valid:{wheir} {wid}, {w}"
f"heir removed, transaction is not valid:"
f"{wheir} {wid}, {w}"
)
raise HeirNotFoundException(wheir)
if willexecutor := w.we:
count = willexecutors_found.get(willexecutor["url"], 0)