forked from bitcoinafterlife/bal-electrum-plugin
feat(will): invalidate signed will on postpone + add Server status column
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.
This commit is contained in:
@@ -671,14 +671,41 @@ class Will:
|
||||
their = will[wid].heirs[wheir]
|
||||
if heir := heirs.get(wheir, None):
|
||||
|
||||
if (
|
||||
heir[0] == their[0]
|
||||
and heir[1] == their[1]
|
||||
and Util.parse_locktime_string(heir[2])
|
||||
>= Util.parse_locktime_string(their[2])
|
||||
):
|
||||
count = heirs_found.get(wheir, 0)
|
||||
heirs_found[wheir] = count + 1
|
||||
if heir[0] == their[0] and heir[1] == their[1]:
|
||||
# The requested (possibly new) locktime for this heir.
|
||||
new_locktime = Util.parse_locktime_string(heir[2])
|
||||
# IMPORTANT: compare against the locktime that is
|
||||
# actually frozen inside the already-signed Bitcoin
|
||||
# transaction (w.tx.locktime), NOT against their[2].
|
||||
# their[2] is the heir entry stored in the will item,
|
||||
# which is updated in memory together with the new
|
||||
# heirs dict when the user postpones, so it would
|
||||
# always equal new_locktime and the postpone would go
|
||||
# undetected. w.tx.locktime is immutable once signed
|
||||
# and is exactly what the will-executors hold.
|
||||
tx_locktime = int(w.tx.locktime)
|
||||
if new_locktime == tx_locktime:
|
||||
# Unchanged: this heir is still coherent.
|
||||
count = heirs_found.get(wheir, 0)
|
||||
heirs_found[wheir] = count + 1
|
||||
elif new_locktime > tx_locktime and (
|
||||
w.get_status("COMPLETE") or w.get_status("PUSHED")
|
||||
):
|
||||
# POSTPONE of an already signed/sent will: the
|
||||
# old pre-signed tx must be invalidated on-chain
|
||||
# first, otherwise a will-executor could
|
||||
# broadcast the earlier-locktime tx and execute
|
||||
# the inheritance too early.
|
||||
raise WillPostponedException(
|
||||
f"{wheir}: locktime postponed "
|
||||
f"{tx_locktime}->{new_locktime} "
|
||||
f"on a signed/sent will"
|
||||
)
|
||||
# new_locktime < tx_locktime (anticipate) is left to
|
||||
# check_will_expired -> WillExpiredException.
|
||||
# new_locktime > tx_locktime on a will that was never
|
||||
# signed/sent falls through here -> a plain rebuild via
|
||||
# HeirNotFoundException (no on-chain fee needed).
|
||||
else:
|
||||
_logger.debug(
|
||||
f"heir not present transaction is not valid:{wheir} {wid}, {w}"
|
||||
@@ -912,6 +939,21 @@ class HeirNotFoundException(NotCompleteWillException):
|
||||
pass
|
||||
|
||||
|
||||
class WillPostponedException(NotCompleteWillException):
|
||||
"""An already signed/sent will is being postponed.
|
||||
|
||||
When a will that has already been signed (``COMPLETE``) and/or pushed to
|
||||
will-executors (``PUSHED``) gets its locktime moved to a LATER date, the
|
||||
previously committed coins must be invalidated on-chain BEFORE rebuilding
|
||||
the new inheritance. Otherwise a will-executor could broadcast the old
|
||||
(earlier-locktime) transaction and execute the inheritance too early to
|
||||
collect the fees. Invalidating spends the same UTXOs now, permanently
|
||||
voiding the old pre-signed transaction.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class WillexecutorChangeException(NotCompleteWillException):
|
||||
pass
|
||||
|
||||
|
||||
Reference in New Issue
Block a user