fix(will): prevent double invalidation when postponing a signed will

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.
This commit is contained in:
GenSpark AI Developer
2026-06-15 23:05:59 +00:00
committed by steal
parent 081e46515f
commit e477c5aa5b
8 changed files with 160 additions and 4 deletions

View File

@@ -1 +1 @@
0.3.0
0.3.1

View File

@@ -34,4 +34,4 @@ The plugin targets Electrum 4.7.2 (the last stable release exposing
``json_db.register_dict``) and PyQt6.
"""
__version__ = "0.3.0"
__version__ = "0.3.1"

View File

@@ -91,7 +91,7 @@ class BalPlugin(BasePlugin):
"""
_version = None
__version__ = "0.3.0" # AUTOMATICALLY GENERATED DO NOT EDIT
__version__ = "0.3.1" # AUTOMATICALLY GENERATED DO NOT EDIT
# Command used to open an .ics calendar file, per operating system.
default_app = {

View File

@@ -420,6 +420,31 @@ 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():

View File

@@ -708,6 +708,19 @@ 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}")

View File

@@ -1,7 +1,7 @@
{
"name": "bal",
"fullname": "Bitcoin After Life",
"version": "0.3.0",
"version": "0.3.1",
"description": "Provides free and decentralized Bitcoin inheritance support. Build time-locked 'will' transactions that transfer funds to your heirs if you stop refreshing them (dead-man's switch), optionally relayed by will-executor servers.",
"author": "Svatantrya",
"licence": "MIT",