When postponing the delivery time of an already signed/sent will, the user
was asked to sign the on-chain invalidation transaction twice before the new
(postponed) will could be built.
Root cause: after the invalidation tx was broadcast, on_success_invalidate
restarted task_phase1 to rebuild the will, but the old will items were still
marked COMPLETE/PUSHED with their original tx.locktime (the on-chain
invalidation did not update the in-memory status). The postpone check therefore
fired WillPostponedException a second time, requesting another invalidation.
Fix:
- Add Will.mark_invalidated_by_tx(will, tx): marks INVALIDATED every valid will
item that spends a prevout consumed by the just-broadcast invalidation tx.
Setting INVALIDATED clears the VALID flag, removing those items from
only_valid_list so the postpone/expire check no longer fires.
- Call it from loop_broadcast_invalidating after a successful broadcast (txid
obtained) and persist via save_willitems. On the phase-1 restart the old will
is no longer VALID, so the will is rebuilt directly: a single invalidation
signature followed by the new will.
Tests: add test_will_mark_invalidated_by_tx and
test_will_mark_invalidated_by_tx_no_match plus the WillPostponedException
hierarchy assertion. 184 tests pass; smoke and external-zip OK; ruff clean.
Bump version to 0.3.1.
Postpone safety (Strategy B):
- A signed/sent will carries an immutable locktime; postponing the delivery
time previously did nothing, so a will-executor could still broadcast the
old (earlier-locktime) transaction and execute the inheritance too early.
- core/will.py: add WillPostponedException and detect postpone by comparing
the requested locktime against w.tx.locktime (the locktime frozen in the
signed transaction) instead of the in-memory heir entry, which is updated
together with the new value and would always compare equal.
- gui/qt/dialogs.py (BalBuildWillDialog.task_phase1, the real path used by
Tools -> Prepare): handle WillPostponedException before NotCompleteWill;
return (None, tx) to trigger sign + broadcast of the invalidation, then the
user presses Prepare again to rebuild/re-sign/re-send (two explicit steps).
- gui/qt/window.py: mirror the branch in build_inheritance_transaction with an
explanatory message; wording aligned to the 'Prepare' button.
- gui/qt/common.py: export WillPostponedException.
- A postpone on a will that was never signed/sent just rebuilds (no on-chain
fee).
Server status column:
- gui/qt/lists.py: add a dedicated 'Server' column to PreviewList with an
always-readable label and a tooltip (will-executor URL + state).
- gui/qt/theme.py: add server_status_text() and server_status_tooltip(),
reusing the existing status flags.
- gui/qt/common.py: export the new theme helpers.
Docs: update README.md, bal/README.md and CHANGELOG_REFACTOR.md.
Tests: 182 passed; smoke + external-zip OK; ruff has no new real findings.