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.
172 lines
6.6 KiB
Python
172 lines
6.6 KiB
Python
"""
|
|
Real-world simulation of the inheritance update flows.
|
|
|
|
This script does NOT touch the GUI. It drives the core decision function
|
|
``Will.check_willexecutors_and_heirs`` (the one that decides whether a will is
|
|
still coherent or must be rebuilt) through the scenarios the user reported:
|
|
|
|
1. delivery date moved forward (postpone) -> must NOT stay "coherent"
|
|
2. an heir is added -> must trigger rebuild
|
|
3. an heir is removed -> must trigger rebuild
|
|
4. a single heir percentage / amount is changed -> must trigger rebuild
|
|
5. nothing changed -> stays coherent
|
|
|
|
For each scenario we report which exception (if any) is raised, because that is
|
|
exactly what the GUI relies on to decide whether to rebuild the inheritance
|
|
transactions. If the function returns True (coherent) when something DID
|
|
change, the GUI will (correctly) show no update -- which is the symptom the
|
|
user described.
|
|
|
|
Run:
|
|
QT_QPA_PLATFORM=offscreen PYTHONPATH=electrum-src python3 tests/sim_update_flows.py
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
import copy
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir))
|
|
|
|
from bal.core.will import (
|
|
WillItem, Will,
|
|
NotCompleteWillException, HeirNotFoundException, NoHeirsException,
|
|
TxFeesChangedException, WillExpiredException,
|
|
)
|
|
from bal.core.util import Util
|
|
|
|
# A valid serialized tx (1 input + 1 output, version 2). Its nLockTime is 0.
|
|
_VALID_TX_HEX = (
|
|
"01000000012a5c9a94fcde98f5581cd00162c60a13936ceb75389ea65b"
|
|
"f38633b424eb4031000000006c493046022100a82bbc57a0136751e543"
|
|
"3f41cf000b3f1a99c6744775e76ec764fb78c54ee100022100f9e80b7d"
|
|
"e89de861dc6fb0c1429d5da72c2b6b2ee2406bc9bfb1beedd729d98501"
|
|
"2102e61d176da16edd1d258a200ad9759ef63adf8e14cd97f53227bae3"
|
|
"5cdb84d2f6ffffffff0140420f00000000001976a914230ac37834073a"
|
|
"42146f11ef8414ae929feaafc388ac00000000"
|
|
)
|
|
|
|
# A locktime far in the past (so the frozen tx.locktime is a fixed integer we
|
|
# control via monkey-patching below). We will override w.tx.locktime per test.
|
|
TX_FEES = 100
|
|
|
|
|
|
def _make_will_item(heirs, tx_locktime, status_complete=False):
|
|
"""Build a WillItem whose stored heirs == ``heirs`` and whose tx.locktime
|
|
is forced to ``tx_locktime`` (the value frozen in the signed Bitcoin tx)."""
|
|
d = {
|
|
"tx": _VALID_TX_HEX,
|
|
"heirs": copy.deepcopy(heirs),
|
|
"willexecutor": None,
|
|
"status": "",
|
|
"description": "",
|
|
"time": 0,
|
|
"change": "",
|
|
"baltx_fees": TX_FEES,
|
|
}
|
|
item = WillItem(d, _id="willid_1")
|
|
item.STATUS = copy.deepcopy(WillItem.STATUS_DEFAULT)
|
|
# Force the locktime frozen "inside" the signed tx.
|
|
item.tx.locktime = tx_locktime
|
|
if status_complete:
|
|
item.set_status("COMPLETE", True)
|
|
return item
|
|
|
|
|
|
def _run(label, will_heirs, current_heirs, tx_locktime,
|
|
status_complete=False, check_date=0):
|
|
"""Run check_willexecutors_and_heirs and report the outcome."""
|
|
item = _make_will_item(will_heirs, tx_locktime, status_complete)
|
|
will = {"willid_1": item}
|
|
outcome = None
|
|
try:
|
|
result = Will.check_willexecutors_and_heirs(
|
|
will,
|
|
current_heirs, # the (possibly edited) heirs dict
|
|
{}, # willexecutors
|
|
False, # self_willexecutor
|
|
check_date, # check_date (timestamp)
|
|
TX_FEES, # tx_fees
|
|
)
|
|
outcome = f"coherent (returned {result})"
|
|
except HeirNotFoundException as e:
|
|
outcome = f"HeirNotFoundException: {e}"
|
|
except NoHeirsException as e:
|
|
outcome = f"NoHeirsException: {e}"
|
|
except TxFeesChangedException as e:
|
|
outcome = f"TxFeesChangedException: {e}"
|
|
except WillExpiredException as e:
|
|
outcome = f"WillExpiredException: {e}"
|
|
except NotCompleteWillException as e:
|
|
outcome = f"{type(e).__name__}: {e}"
|
|
except Exception as e:
|
|
outcome = f"!! UNEXPECTED {type(e).__name__}: {e}"
|
|
print(f"[{label}]")
|
|
print(f" -> {outcome}")
|
|
return outcome
|
|
|
|
|
|
def main():
|
|
# locktime string "0d" -> Util.parse_locktime_string returns a timestamp
|
|
# ~ now. We use explicit integer timestamps to keep things deterministic.
|
|
base_lt = 1900000000 # frozen tx.locktime (year ~2030)
|
|
later_lt = "2000000000" # a later locktime string (postpone)
|
|
same_lt = str(base_lt)
|
|
|
|
# Scenario 0: nothing changed -> should be coherent.
|
|
heirs = {"alice": ["addr_alice", 5000, same_lt]}
|
|
_run("0. nothing changed",
|
|
will_heirs=heirs, current_heirs=copy.deepcopy(heirs),
|
|
tx_locktime=base_lt, check_date=0)
|
|
|
|
# Scenario 1: delivery date moved forward (postpone), will NOT yet signed.
|
|
heirs_will = {"alice": ["addr_alice", 5000, same_lt]}
|
|
heirs_now = {"alice": ["addr_alice", 5000, later_lt]}
|
|
_run("1. date postponed (unsigned will)",
|
|
will_heirs=heirs_will, current_heirs=heirs_now,
|
|
tx_locktime=base_lt, check_date=0)
|
|
|
|
# Scenario 1b: postpone on a SIGNED will (status COMPLETE).
|
|
_run("1b. date postponed (SIGNED will)",
|
|
will_heirs=heirs_will, current_heirs=heirs_now,
|
|
tx_locktime=base_lt, status_complete=True, check_date=0)
|
|
|
|
# Scenario 2: an heir is ADDED.
|
|
heirs_will = {"alice": ["addr_alice", 5000, same_lt]}
|
|
heirs_now = {
|
|
"alice": ["addr_alice", 5000, same_lt],
|
|
"bob": ["addr_bob", 3000, same_lt],
|
|
}
|
|
_run("2. heir added (bob)",
|
|
will_heirs=heirs_will, current_heirs=heirs_now,
|
|
tx_locktime=base_lt, check_date=0)
|
|
|
|
# Scenario 3: an heir is REMOVED.
|
|
heirs_will = {
|
|
"alice": ["addr_alice", 5000, same_lt],
|
|
"bob": ["addr_bob", 3000, same_lt],
|
|
}
|
|
heirs_now = {"alice": ["addr_alice", 5000, same_lt]}
|
|
_run("3. heir removed (bob)",
|
|
will_heirs=heirs_will, current_heirs=heirs_now,
|
|
tx_locktime=base_lt, check_date=0)
|
|
|
|
# Scenario 4: a single heir AMOUNT/percentage changed.
|
|
heirs_will = {"alice": ["addr_alice", 5000, same_lt]}
|
|
heirs_now = {"alice": ["addr_alice", 9999, same_lt]}
|
|
_run("4. heir amount changed (5000 -> 9999)",
|
|
will_heirs=heirs_will, current_heirs=heirs_now,
|
|
tx_locktime=base_lt, check_date=0)
|
|
|
|
# Scenario 5: heir ADDRESS changed.
|
|
heirs_will = {"alice": ["addr_alice", 5000, same_lt]}
|
|
heirs_now = {"alice": ["addr_NEW", 5000, same_lt]}
|
|
_run("5. heir address changed",
|
|
will_heirs=heirs_will, current_heirs=heirs_now,
|
|
tx_locktime=base_lt, check_date=0)
|
|
|
|
print("\n[done] simulation finished")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|