core+gui+cli: anticipation/rebuild dates, preserve relative settings

Fixes around the delivery/build/check date handling (karen7 regtest):

- build_will (GUI + CLI) re-anchors date_to_check to the CURRENT heirs'
  earliest future delivery before building, so an anticipated rebuild is no
  longer blocked by the stale old-will anchor (NO_FUTURE_DATE). The checks of
  the existing will keep their anchored date_to_check.
- _sync_locktime_to_built_txs now PRESERVES RELATIVE locktime/threshold
  recipes ("2y"/"150d") in WILL_SETTINGS instead of freezing them to absolute
  timestamps: the anchored comparisons (resolve_locktime_against_tx and
  resolve_date_to_check with built_locktime) already prevent the daily
  invalidate prompt. Absolute values still sync on a genuine automatic
  anticipation.
- Will.remove_stale_wallet_history drops stale wallet-LOCAL will placeholders
  before every (re)build in GUI and CLI so their coins are available again.
- check_willexecutors_and_heirs raises HeirNotFoundException outside the
  count_heirs gate: a shortened relative recipe on a signed will now triggers
  a plain rebuild ("no heirs" only when there really are none).
- is_locktime_below_threshold compares the settings on one reference frame
  (resolve_guard_threshold), no longer against the built-will anchor.

Tests: purge unit + call-site, no-heirs, guard, anticipated-rebuild
end-to-end (GUI) + CLI mirror + fixed_percent_lists_amount unit,
relative-preserve sync; conftest restores electrum.constants.net after each
test (cross-file pollution guard).
This commit is contained in:
2026-09-10 17:07:22 -04:00
parent d5f30e89b9
commit 7c9bbfce58
16 changed files with 724 additions and 522 deletions

View File

@@ -28,6 +28,7 @@ The status flags themselves (the source of truth) stay here; only the mapping
from datetime import datetime, timezone
from electrum.address_synchronizer import TX_HEIGHT_FUTURE, TX_HEIGHT_LOCAL
from electrum.i18n import _
from electrum.logging import Logger, get_logger
from electrum.transaction import (
@@ -847,6 +848,48 @@ class Will:
except Exception as e:
_logger.error(f"save_valid_transactions_to_history failed: {e}")
@staticmethod
def remove_stale_wallet_history(wallet, history_label):
"""Delete wallet-LOCAL will transactions saved under ``history_label``.
``save_valid_transactions_to_history`` stores the not-yet-signed
inheritance txs into the wallet's local history; those local
placeholders nominally spend the coins they reference. When the will is
REBUILT (prepare/build, auto-rebuild, on-close rebuild, CLI build) the
stale placeholders must be removed so the coins become available again
to the new build (see ``Util.get_available_utxos``). Only
wallet-local/future (non-broadcast) txs whose label matches the history
label template are removed; confirmed/broadcast history is never
touched. Returns the txids that were removed.
"""
if not wallet or not getattr(wallet, "adb", None):
return []
removed = []
for txid, label in Will._wallet_labels(wallet):
if not label or not Util._label_matches_history(label, history_label):
continue
try:
height = int(wallet.adb.get_tx_height(txid).height())
except Exception:
continue
if height not in (TX_HEIGHT_LOCAL, TX_HEIGHT_FUTURE):
continue
try:
wallet.adb.remove_transaction(txid)
removed.append(txid)
except Exception as e:
_logger.error(f"remove from history failed for {txid}: {e}")
continue
try:
wallet.set_label(txid, None)
except Exception as e:
_logger.error(f"set_label failed for {txid}: {e}")
try:
wallet.save_db()
except Exception as e:
_logger.error(f"save_db failed after history purge: {e}")
return removed
@staticmethod
def _add_transaction_to_history(wallet, tx, txid):
"""Store *tx* into the wallet's local history via ``adb``.
@@ -1213,9 +1256,9 @@ class Will:
if Util.parse_locktime_string(heirs[h][2]) >= check_date:
count_heirs += 1
if h not in heirs_found:
_logger.debug(f"heir: {h} not found")
raise HeirNotFoundException(h)
if h not in heirs_found:
_logger.debug(f"heir: {h} not found")
raise HeirNotFoundException(h)
if not count_heirs:
raise NoHeirsException("there are not valid heirs")
if self_willexecutor and no_willexecutor == 0: