core+gui: name the real cause of a failed build instead of guessing #7
85
CHANGELOG.md
85
CHANGELOG.md
@@ -2908,3 +2908,88 @@ renumber the grid rows.
|
||||
- Full test suite: 438 passed (unchanged).
|
||||
|
||||
**Outcome:** DONE.
|
||||
|
||||
---
|
||||
|
||||
## 57. Name the real cause of a failed build instead of guessing
|
||||
|
||||
**Date:** 2026-09-04
|
||||
|
||||
**Goal (owner request):** the "Building Will" report was hard to read and often
|
||||
misleading.
|
||||
|
||||
1. The long "could not build the will" block was printed entirely in amber
|
||||
(`COLOR_WARNING`), which the owner reported as barely legible.
|
||||
2. Whenever the build produced nothing, the dialog printed a FIXED list of
|
||||
three "possible reasons" (low balance / dust shares / check-alive later than
|
||||
the delivery date) regardless of what had actually happened. In a case
|
||||
reproduced from the owner's log all three were false, and the real cause
|
||||
(no delivery date left to build) was not even in the list.
|
||||
3. The "Checking your will" row had the same problem: the single sentence
|
||||
"Found CHANGES to the DATE or the HEIRS" was shown for five different
|
||||
situations, including one where it is plainly wrong - funds received, where
|
||||
neither the date nor the heirs changed.
|
||||
|
||||
**What changed:**
|
||||
|
||||
- `bal/core/heirs.py`
|
||||
- `Heirs.__init__` / `buildTransactions`: new `last_build_error` attribute
|
||||
recording WHY a build produced no transaction. Reset at the start of every
|
||||
build, and set at each path that previously returned empty with no
|
||||
explanation at all: `NO_HEIRS`, `NO_UTXO`, `NO_WILLEXECUTOR_USABLE`,
|
||||
`NO_FUTURE_DATE`, `WILLEXECUTOR_FEE`, `WILLEXECUTOR_FEE_TOO_HIGH`,
|
||||
`TX_BUILD_FAILED`, `WILLEXECUTOR_TX_ERROR`.
|
||||
- Added a `processed_willexecutors` counter so that "the loop skipped every
|
||||
will-executor" - which returned silently, with no log line whatsoever - is
|
||||
told apart from "we tried and the build failed".
|
||||
- Fixed a latent crash in the `prepare_transactions` exception handler. It
|
||||
read `e.heirname` in order to auto-deselect the offending will-executor,
|
||||
but NOTHING in the plugin sets that attribute any more (leftover from an
|
||||
older exception design), so the lookup itself raised AttributeError and the
|
||||
inner `except Exception: raise` re-raised THAT, aborting the whole build
|
||||
with a confusing secondary error instead of the real one. The handler now
|
||||
records `WILLEXECUTOR_TX_ERROR`, logs the actual exception together with
|
||||
the will-executor it happened on, and moves on to the next one - which is
|
||||
what the original code was clearly trying to do.
|
||||
|
||||
- `bal/gui/qt/dialogs.py`
|
||||
- New `msg_alert()`: an amber warning sign (U+26A0, written as a numeric HTML
|
||||
entity so the source stays ASCII) followed by text in the theme's default
|
||||
colour. Colour is what ATTRACTS attention, not what is read, so it is kept
|
||||
on the sign alone; the message body stays readable and still works under
|
||||
the dark theme, where a hard-coded black would disappear.
|
||||
- New `_build_failure_message()`: maps `last_build_error` to ONE specific
|
||||
sentence. When the code is missing or unrecognised it SAYS the cause could
|
||||
not be determined and lists what to check, instead of asserting three
|
||||
guesses as if they were the only possibilities.
|
||||
- New `_check_failure_message()`: replaces the single "Found CHANGES to the
|
||||
DATE or the HEIRS" line with seven precise messages, reusing the detail the
|
||||
exceptions already carry (heir name, will-executor URL, old and new fee
|
||||
rate). The two plain `NotCompleteWillException` cases are told apart
|
||||
STRUCTURALLY (raised with no argument vs. with one), not by matching
|
||||
message text, which would be fragile. No new exception classes were added
|
||||
(owner request).
|
||||
- Added a dedicated `except BalanceTooLowException` handler. The exception
|
||||
already carried the balance, the fees and the dust threshold, but was
|
||||
falling through to the generic handler, which printed the raw technical
|
||||
string in red and re-raised. It now shows the real figures.
|
||||
- "Checking variables" row: `No Heirs` now uses `msg_alert()`. The
|
||||
"Check Alive Threshold Passed" message deliberately STAYS red
|
||||
(`COLOR_ERROR`) because it is the more urgent situation (owner request).
|
||||
|
||||
- `bal/gui/qt/common.py`
|
||||
- Re-export `BalanceTooLowException` from `core.heirs` so the Qt layer can
|
||||
catch it.
|
||||
|
||||
**Verification:**
|
||||
- `py_compile` clean on all 44 files of the package.
|
||||
- The real `msg_alert`, `_build_failure_message` and `_check_failure_message`
|
||||
were extracted from the source via AST and executed against every reason code
|
||||
and every exception type, with the exception hierarchy rebuilt from
|
||||
`will.py`: 9 build cases and 8 check cases all produce the intended text.
|
||||
- NOT RUN: the official test suite. The machine used for this task (Windows)
|
||||
has no importable `electrum` module, so `tests/` could not be executed.
|
||||
- Manually tested by the owner in Electrum 4.8.1: `NO_FUTURE_DATE`,
|
||||
`WILLEXECUTOR_FEE` and `No Heirs` were all confirmed on screen.
|
||||
|
||||
**Outcome:** DONE.
|
||||
|
||||
@@ -363,6 +363,10 @@ class Heirs(dict, Logger):
|
||||
Logger.__init__(self)
|
||||
self.db = wallet.db
|
||||
self.wallet = wallet
|
||||
# Reason code explaining why the last buildTransactions() produced no
|
||||
# transaction (None when the last build succeeded or never ran). See
|
||||
# buildTransactions for the list of codes and why they exist.
|
||||
self.last_build_error = None
|
||||
d = self.db.get("heirs", {})
|
||||
try:
|
||||
self.update(d)
|
||||
@@ -630,6 +634,20 @@ class Heirs(dict, Logger):
|
||||
def buildTransactions(
|
||||
self, bal_plugin, wallet, tx_fees=None, utxos=None, from_locktime=0
|
||||
):
|
||||
# Reset the diagnostic reason at the start of every build attempt.
|
||||
#
|
||||
# WHY: when the build produced nothing, the GUI used to show a fixed
|
||||
# list of three "possible reasons" (low balance / dust shares /
|
||||
# check-alive after the delivery date). In practice the real cause is
|
||||
# often NONE of those three - several code paths below simply return
|
||||
# an empty result with no explanation at all, so the user was shown
|
||||
# three guesses that were all wrong. Each such path now records WHY
|
||||
# it gave up, and BalBuildWillDialog names the actual cause.
|
||||
#
|
||||
# Codes: NO_HEIRS, NO_UTXO, NO_WILLEXECUTOR_USABLE, NO_FUTURE_DATE,
|
||||
# WILLEXECUTOR_FEE, WILLEXECUTOR_FEE_TOO_HIGH, TX_BUILD_FAILED,
|
||||
# WILLEXECUTOR_TX_ERROR.
|
||||
self.last_build_error = None
|
||||
_before = list(self.keys())
|
||||
Heirs._validate(self, persist=False)
|
||||
_removed = [k for k in _before if k not in self]
|
||||
@@ -644,6 +662,7 @@ class Heirs(dict, Logger):
|
||||
", ".join(_removed),
|
||||
)
|
||||
if len(self) <= 0:
|
||||
self.last_build_error = "NO_HEIRS"
|
||||
_logger.info("while building transactions there was no heirs")
|
||||
return
|
||||
balance = 0.0
|
||||
@@ -660,12 +679,18 @@ class Heirs(dict, Logger):
|
||||
len_utxo_set += 1
|
||||
available_utxos.append(utxo)
|
||||
if len_utxo_set == 0:
|
||||
self.last_build_error = "NO_UTXO"
|
||||
_logger.info("no usable utxos")
|
||||
return
|
||||
j = -2
|
||||
willexecutorsitems = list(willexecutors.items())
|
||||
willexecutorslen = len(willexecutorsitems)
|
||||
alltxs = {}
|
||||
# Counts how many will-executors were actually PROCESSED (i.e. passed
|
||||
# the is_selected/is_valid filter below and reached the build loop).
|
||||
# If it stays 0 the loop silently skipped every single one, which is a
|
||||
# distinct failure from "we tried and the build failed".
|
||||
processed_willexecutors = 0
|
||||
while True:
|
||||
j += 1
|
||||
if j >= willexecutorslen:
|
||||
@@ -682,6 +707,7 @@ class Heirs(dict, Logger):
|
||||
url = willexecutor = None
|
||||
else:
|
||||
break
|
||||
processed_willexecutors += 1
|
||||
fees = {}
|
||||
i = 0
|
||||
txs = {}
|
||||
@@ -699,9 +725,11 @@ class Heirs(dict, Logger):
|
||||
max_fee=bal_plugin.MAX_WILLEXECUTOR_FEE.get(),
|
||||
)
|
||||
except WillExecutorFeeException:
|
||||
self.last_build_error = "WILLEXECUTOR_FEE"
|
||||
i = 10
|
||||
continue
|
||||
except WillExecutorFeeTooHighException:
|
||||
self.last_build_error = "WILLEXECUTOR_FEE_TOO_HIGH"
|
||||
i = 10
|
||||
continue
|
||||
if locktimes:
|
||||
@@ -710,19 +738,33 @@ class Heirs(dict, Logger):
|
||||
locktimes, available_utxos[:], fees, wallet
|
||||
)
|
||||
if not txs:
|
||||
self.last_build_error = "TX_BUILD_FAILED"
|
||||
return {}
|
||||
except Exception as e:
|
||||
# An unexpected failure while assembling the
|
||||
# transactions for THIS will-executor.
|
||||
#
|
||||
# WHY THIS CHANGED: the previous code read
|
||||
# ``e.heirname`` here, in order to auto-deselect the
|
||||
# will-executor blamed by the exception. NOTHING in
|
||||
# the plugin sets that attribute any more (it is a
|
||||
# leftover from an older exception design), so the
|
||||
# lookup itself raised AttributeError, and the inner
|
||||
# ``except Exception: raise`` re-raised THAT - aborting
|
||||
# the whole build with a confusing secondary error
|
||||
# instead of the real one. We now record the reason,
|
||||
# log the actual exception together with the
|
||||
# will-executor it happened on, and simply move on to
|
||||
# the next one, which is what the original code was
|
||||
# clearly trying to do.
|
||||
self.last_build_error = "WILLEXECUTOR_TX_ERROR"
|
||||
_logger.error(
|
||||
f"build transactions: error preparing transactions: {e}"
|
||||
"build transactions: error preparing transactions "
|
||||
"for will-executor %s: %r",
|
||||
(willexecutor or {}).get("url", "(none)"),
|
||||
e,
|
||||
)
|
||||
try:
|
||||
if "w!ll3x3c" in e.heirname:
|
||||
Willexecutors.is_selected(
|
||||
e.heirname[len("w!ll3x3c") :], False
|
||||
)
|
||||
break
|
||||
except Exception:
|
||||
raise
|
||||
break
|
||||
total_fees = 0
|
||||
total_fees_real = 0
|
||||
total_in = 0
|
||||
@@ -746,12 +788,26 @@ class Heirs(dict, Logger):
|
||||
if i >= 10:
|
||||
break
|
||||
else:
|
||||
self.last_build_error = "NO_FUTURE_DATE"
|
||||
_logger.info(
|
||||
f"no locktimes for willexecutor {willexecutor} skipped"
|
||||
)
|
||||
break
|
||||
alltxs.update(txs)
|
||||
|
||||
# Every will-executor was skipped by the is_selected/is_valid filter
|
||||
# (or the list was empty) and no "no will-executor" build was allowed,
|
||||
# so the loop above never even attempted a build. This path used to
|
||||
# return silently with no log line at all, which is exactly the case
|
||||
# the owner hit: the dialog then blamed balance/dust/check-alive, none
|
||||
# of which was true.
|
||||
if not alltxs and processed_willexecutors == 0:
|
||||
self.last_build_error = "NO_WILLEXECUTOR_USABLE"
|
||||
_logger.info(
|
||||
"no usable will-executor: all %d skipped (not selected or not valid)",
|
||||
willexecutorslen,
|
||||
)
|
||||
|
||||
return alltxs
|
||||
|
||||
def get_transactions(
|
||||
|
||||
@@ -108,6 +108,7 @@ from ...core.heirs import (
|
||||
HEIR_DUST_AMOUNT,
|
||||
HEIR_REAL_AMOUNT,
|
||||
OP_RETURN_PREFIX,
|
||||
BalanceTooLowException,
|
||||
HeirAmountIsDustException,
|
||||
Heirs,
|
||||
WillExecutorFeeTooHighException,
|
||||
|
||||
@@ -28,6 +28,7 @@ from .common import (
|
||||
AmountException,
|
||||
Any,
|
||||
BalTimestamp,
|
||||
BalanceTooLowException,
|
||||
BestEffortRequestFailed,
|
||||
Buttons,
|
||||
Callable,
|
||||
@@ -690,12 +691,19 @@ class BalBuildWillDialog(BalDialog):
|
||||
_logger.debug(
|
||||
"during phase1 CAE: {}, Continue to invalidate".format(cae)
|
||||
)
|
||||
self.msg_set_status("Checking variables",varrow, "Check Alive Threshold Passed: you have to Invalidate your old Will",self.COLOR_ERROR)
|
||||
self.msg_set_status(
|
||||
"Checking variables", varrow,
|
||||
"Check Alive Threshold Passed: you have to Invalidate "
|
||||
"your old Will",
|
||||
self.COLOR_ERROR,
|
||||
)
|
||||
else:
|
||||
raise cae
|
||||
return None, tx
|
||||
except NoHeirsException:
|
||||
self.msg_set_status("Checking variables", varrow,"No Heirs",self.COLOR_ERROR)
|
||||
self.msg_set_status(
|
||||
"Checking variables", varrow, self.msg_alert("No Heirs")
|
||||
)
|
||||
return "no_heirs", None
|
||||
except Exception as e:
|
||||
raise e
|
||||
@@ -811,61 +819,29 @@ class BalBuildWillDialog(BalDialog):
|
||||
_("Inheritance in mempool (waiting confirmation)"),
|
||||
self.COLOR_WARNING,
|
||||
)
|
||||
if isinstance(e, HeirChangeException):
|
||||
message = _("Heirs changed:")
|
||||
elif isinstance(e, WillExecutorNotPresent):
|
||||
message = _("Will-Executor not present")
|
||||
elif isinstance(e, WillexecutorChangeException):
|
||||
message = _("Will-Executor changed")
|
||||
elif isinstance(e, TxFeesChangedException):
|
||||
message = _("Txfees are changed")
|
||||
elif isinstance(e, HeirNotFoundException):
|
||||
# Task #01b: the old text "Heir not found" was misleading.
|
||||
# In practice this branch is reached whenever the will is no
|
||||
# longer coherent and must be rebuilt - very often simply
|
||||
# because the delivery date was anticipated, NOT because an heir
|
||||
# is genuinely missing. We therefore show a clear, accurate
|
||||
# message that covers both the DATE and the HEIRS cases.
|
||||
message = _(
|
||||
"Found CHANGES to the DATE or the HEIRS,\n"
|
||||
"a NEW WILL must be prepared."
|
||||
)
|
||||
if message:
|
||||
_logger.debug(f"message: {message}")
|
||||
self.msg_set_checking(message)
|
||||
else:
|
||||
# Task #01b: the old fallback text "New" was unclear. When the
|
||||
# will is incomplete without a more specific reason, it still
|
||||
# means the will has to be rebuilt, so we use the same clear
|
||||
# message as the HeirNotFoundException branch above.
|
||||
self.msg_set_checking(
|
||||
_(
|
||||
"Found CHANGES to the DATE or the HEIRS,\n"
|
||||
"a NEW WILL must be prepared."
|
||||
)
|
||||
)
|
||||
# All of these situations used to collapse into the SAME sentence
|
||||
# ("Found CHANGES to the DATE or the HEIRS, a NEW WILL must be
|
||||
# prepared"), shown for five genuinely different causes - and
|
||||
# plainly WRONG for the most common one, receiving funds, where
|
||||
# neither the date nor the heirs changed. _check_failure_message
|
||||
# names the real cause using the detail each exception already
|
||||
# carries (heir name, will-executor URL, old/new fee rate).
|
||||
message = self._check_failure_message(e)
|
||||
_logger.debug(f"message: {message}")
|
||||
self.msg_set_checking(message)
|
||||
|
||||
if have_to_build:
|
||||
self.msg_set_building()
|
||||
try:
|
||||
txs = self.bal_window.build_will()
|
||||
if not txs:
|
||||
# The message now names the ACTUAL reason the build gave
|
||||
# up (recorded by Heirs.buildTransactions) instead of
|
||||
# listing three fixed guesses that were frequently all
|
||||
# wrong. msg_alert keeps the warning sign coloured and the
|
||||
# text in the default colour so it stays readable.
|
||||
self.msg_set_building(
|
||||
_(
|
||||
"Could not build the will ! Possible reasons:\n"
|
||||
"1- the Balance of wallet is too low to cover the "
|
||||
"fees for miners and will executors,\n"
|
||||
"2- the Heirs' shares are below the minimum (Dust "
|
||||
"UTXO, less than 546 Satoshi),\n"
|
||||
"3- the Check Alive Date/Time is later than the "
|
||||
"delivery time (it must be earlier),\n"
|
||||
"Skipped"
|
||||
),
|
||||
# Orange (warning) instead of red (error): an empty
|
||||
# wallet after the inheritance was executed is a NORMAL
|
||||
# situation, not a failure, so the colour should not
|
||||
# alarm the user (owner request).
|
||||
color=self.COLOR_WARNING,
|
||||
self.msg_alert(self._build_failure_message())
|
||||
)
|
||||
return False, None
|
||||
|
||||
@@ -950,6 +926,28 @@ class BalBuildWillDialog(BalDialog):
|
||||
)
|
||||
return False, None
|
||||
|
||||
except BalanceTooLowException as e:
|
||||
# The core DOES detect this precisely and carries the numbers,
|
||||
# but the exception was never caught here: it fell through to
|
||||
# the generic handler below, which printed the raw technical
|
||||
# string in red and re-raised. Show the real figures instead.
|
||||
self.msg_set_building(
|
||||
self.msg_alert(
|
||||
_(
|
||||
"Wallet balance is too low: {} satoshi available, "
|
||||
"but the miner and will-executor fees need {} "
|
||||
"satoshi (the minimum usable amount is {} "
|
||||
"satoshi). Add funds, or select fewer "
|
||||
"will-executors."
|
||||
).format(
|
||||
int(e.balance), int(e.fees), int(e.dust_threshold)
|
||||
)
|
||||
+ "\n\n"
|
||||
+ _("Skipped")
|
||||
)
|
||||
)
|
||||
return False, None
|
||||
|
||||
except Exception as e:
|
||||
self.msg_set_building(self.msg_error(e))
|
||||
raise e
|
||||
@@ -2009,6 +2007,168 @@ class BalBuildWillDialog(BalDialog):
|
||||
_logger.debug(f"_executed_inheritance_status error: {_err}")
|
||||
return "MEMPOOL" if has_mempool else None
|
||||
|
||||
def _check_failure_message(self, e):
|
||||
"""Return a precise explanation of why the will is no longer coherent.
|
||||
|
||||
``Will.is_will_valid`` raises NotCompleteWillException (or one of its
|
||||
subclasses) when the stored will stops matching the wallet, the heirs
|
||||
or the will-executors. Those exceptions ALREADY carry the useful
|
||||
detail - the heir name, the will-executor URL, the old and new fee
|
||||
rate - but this dialog used to discard all of it and print the same
|
||||
sentence, "Found CHANGES to the DATE or the HEIRS", for every case.
|
||||
|
||||
That was not merely vague, it was WRONG for the most common situation:
|
||||
when the wallet simply receives new funds, neither the date nor the
|
||||
heirs changed, yet the user was sent hunting for edits never made.
|
||||
|
||||
NOTE: no new exception classes were added for this (owner request).
|
||||
Every case below is told apart using only what the core already
|
||||
raises today.
|
||||
"""
|
||||
# Subclasses first - they are all NotCompleteWillException.
|
||||
if isinstance(e, TxFeesChangedException):
|
||||
# The core raises TxFeesChangedException(f"{tx_fees}: {w.tx_fees}"),
|
||||
# i.e. "current: stored". Show both rates when they can be read,
|
||||
# and fall back to a plain sentence if that format ever changes.
|
||||
try:
|
||||
now_fee, old_fee = [p.strip() for p in str(e).split(":", 1)]
|
||||
return _(
|
||||
"Miner fee rate changed (the will was built with {} "
|
||||
"sat/byte, now it is {}): a new will must be prepared."
|
||||
).format(old_fee, now_fee)
|
||||
except Exception:
|
||||
return _(
|
||||
"The miner fee rate changed: a new will must be prepared."
|
||||
)
|
||||
if isinstance(e, WillExecutorNotPresent):
|
||||
return _(
|
||||
'Will-executor "{}" is not covered by the current will: '
|
||||
"a new will must be prepared."
|
||||
).format(str(e))
|
||||
if isinstance(e, NoWillExecutorNotPresent):
|
||||
return _(
|
||||
"Backup mode is enabled but the will has no backup "
|
||||
"transaction: a new will must be prepared."
|
||||
)
|
||||
if isinstance(e, HeirNotFoundException):
|
||||
# Raised when an heir was added, removed, or had its delivery date
|
||||
# changed. We deliberately do NOT try to tell those three apart
|
||||
# (owner request: too fine-grained); naming the heir is what makes
|
||||
# the message actionable.
|
||||
return _(
|
||||
'Heir "{}" is not covered by the current will (it was added '
|
||||
"or removed, or its delivery date changed): a new will must "
|
||||
"be prepared."
|
||||
).format(str(e))
|
||||
# Kept for completeness: nothing in the plugin raises these two today,
|
||||
# but they ARE NotCompleteWillException subclasses, so should future
|
||||
# code raise them they get a sensible message rather than the fallback.
|
||||
if isinstance(e, HeirChangeException):
|
||||
return _("The heirs changed: a new will must be prepared.")
|
||||
if isinstance(e, WillexecutorChangeException):
|
||||
return _("A will-executor changed: a new will must be prepared.")
|
||||
|
||||
# A plain NotCompleteWillException. The core raises it in exactly two
|
||||
# places, told apart STRUCTURALLY (not by matching message text, which
|
||||
# would be fragile): with no argument when the will holds no valid
|
||||
# transaction, and with one argument when a wallet utxo is not
|
||||
# included in the will.
|
||||
if type(e) is NotCompleteWillException:
|
||||
if e.args:
|
||||
return _(
|
||||
"The wallet contains funds that the current will does not "
|
||||
"cover yet: a new will must be prepared."
|
||||
)
|
||||
return _(
|
||||
"The will contains no valid transaction: a new will must be "
|
||||
"prepared."
|
||||
)
|
||||
|
||||
# An unrecognised subclass: say so honestly instead of guessing.
|
||||
return _(
|
||||
"The will is no longer coherent and must be rebuilt; the exact "
|
||||
"reason could not be determined."
|
||||
)
|
||||
|
||||
def _build_failure_message(self):
|
||||
"""Return a plain-language explanation of why the will was not built.
|
||||
|
||||
``Heirs.buildTransactions`` records a reason code in
|
||||
``last_build_error`` every time it gives up (the codes are listed in
|
||||
that method). Here we turn that code into ONE specific sentence
|
||||
telling the user what to fix.
|
||||
|
||||
WHY: this dialog used to print the same three "possible reasons"
|
||||
(low balance / dust shares / check-alive after the delivery date)
|
||||
whenever the build returned nothing. The owner hit a real case where
|
||||
all three were false - the actual cause was that no will-executor was
|
||||
usable, which the list did not even mention - so the message actively
|
||||
misled. When the reason is unknown we now SAY that it is unknown and
|
||||
list what to check, instead of asserting three guesses as if they were
|
||||
the only possibilities.
|
||||
"""
|
||||
reason = None
|
||||
try:
|
||||
reason = getattr(self.bal_window.heirs, "last_build_error", None)
|
||||
except Exception as _err:
|
||||
# A diagnostic must never break the report it is explaining.
|
||||
_logger.debug(f"_build_failure_message: {_err}")
|
||||
|
||||
messages = {
|
||||
"NO_HEIRS": _(
|
||||
"No heirs: add at least one heir before building the will."
|
||||
),
|
||||
"NO_UTXO": _(
|
||||
"The wallet has no spendable funds, so no inheritance "
|
||||
"transaction can be created."
|
||||
),
|
||||
"NO_WILLEXECUTOR_USABLE": _(
|
||||
"No usable will-executor: none of the servers in the list is "
|
||||
"both selected and valid. Open the will-executor settings, "
|
||||
"select at least one server and check that it is reachable."
|
||||
),
|
||||
"NO_FUTURE_DATE": _(
|
||||
"No delivery date left to build: every heir's date is already "
|
||||
"covered by the existing will. Choose a later delivery date, "
|
||||
"or change an heir's date."
|
||||
),
|
||||
"WILLEXECUTOR_FEE": _(
|
||||
"The amount to send must cover the miner fees plus this "
|
||||
"will-executor's fee, and the wallet balance is not enough: "
|
||||
"select cheaper will-executors, or add funds to the wallet."
|
||||
),
|
||||
"WILLEXECUTOR_FEE_TOO_HIGH": _(
|
||||
"A will-executor asks for more than the maximum fee you "
|
||||
"allowed: raise the maximum fee in the settings, or select a "
|
||||
"cheaper will-executor."
|
||||
),
|
||||
"TX_BUILD_FAILED": _(
|
||||
"The inheritance transactions could not be assembled from the "
|
||||
"available funds (the balance may not cover the miner fees)."
|
||||
),
|
||||
"WILLEXECUTOR_TX_ERROR": _(
|
||||
"An unexpected error stopped the transactions being prepared "
|
||||
"for a will-executor, so it was skipped. Try again, or select "
|
||||
"a different will-executor."
|
||||
),
|
||||
}
|
||||
|
||||
if reason in messages:
|
||||
return messages[reason] + "\n\n" + _("Skipped")
|
||||
|
||||
return (
|
||||
_(
|
||||
"Could not build the will, and the exact cause could not be "
|
||||
"determined. Please check that:\n"
|
||||
"- the wallet balance covers the miner and will-executor fees,\n"
|
||||
"- each heir's share is above the minimum (dust limit),\n"
|
||||
"- the Check Alive date is EARLIER than the delivery date,\n"
|
||||
"- at least one will-executor is selected and reachable."
|
||||
)
|
||||
+ "\n\n"
|
||||
+ _("Skipped")
|
||||
)
|
||||
|
||||
def msg_set_checking(self, status="Waiting", row=None):
|
||||
row = self.check_row if row is None else row
|
||||
self.check_row = self.msg_set_status(_("Checking your will"), row, status)
|
||||
@@ -2052,6 +2212,22 @@ class BalBuildWillDialog(BalDialog):
|
||||
# Results are shown in bold (see msg_error).
|
||||
return "<font color='{}'><b>{}</b></font>".format(self.COLOR_WARNING, e)
|
||||
|
||||
def msg_alert(self, e):
|
||||
"""Amber warning sign followed by text in the theme's default colour.
|
||||
|
||||
WHY: long warnings printed entirely in amber (COLOR_WARNING) are hard
|
||||
to read - the owner reported the multi-line "could not build the will"
|
||||
block as barely legible. Colour is only needed to ATTRACT attention,
|
||||
not to be read, so we keep it on the "warning sign" character alone and
|
||||
let the message body inherit Electrum's normal text colour. That also
|
||||
keeps it readable under the dark theme, where a hard-coded black would
|
||||
disappear. U+26A0 is written as a numeric HTML entity so the source
|
||||
file stays pure ASCII; QLabel renders it as rich text.
|
||||
"""
|
||||
return "<font color='{}'>⚠</font> <b>{}</b>".format(
|
||||
self.COLOR_WARNING, e
|
||||
)
|
||||
|
||||
def msg_set_status(self, msg, row=None, status=None, color=None):
|
||||
# The left "state" label keeps its normal weight; only the right-side
|
||||
# result (``status``) is rendered in bold so it is easy to read at a
|
||||
|
||||
Reference in New Issue
Block a user