Compare commits
10 Commits
184eb7b619
...
415d23f5a5
| Author | SHA1 | Date | |
|---|---|---|---|
| 415d23f5a5 | |||
| 449e815f89 | |||
| 59c3271fc7 | |||
| 7722272c39 | |||
| 375aa73624 | |||
| 1d3227d963 | |||
| 5cac176dfb | |||
| e4bcc4f911 | |||
| 2d94c00136 | |||
| 62502263fd |
180
CHANGELOG.md
180
CHANGELOG.md
@@ -2149,3 +2149,183 @@ earlier)" as one of the three possible reasons (v0.5.9).
|
||||
- `ruff`: no new errors.
|
||||
|
||||
**Outcome:** DONE (delivered as test ZIP v0.5.13; commit only after confirmation).
|
||||
|
||||
---
|
||||
|
||||
## 38. v0.5.14 - Shorten long Tor (.onion) will-executor URLs in the list
|
||||
|
||||
**Date:** 2026-07-15
|
||||
|
||||
**Goal (owner request):** In the will-executor list, a Tor (.onion) URL is very
|
||||
long and overflows the URL column. Show it shortened (like the welist site at
|
||||
welist.bitcoin-after.life), keeping the full address available.
|
||||
|
||||
**What changed (`bal/gui/qt/lists.py`, display only):**
|
||||
|
||||
- `WillExecutorListWidget.update()`: the URL column now displays a shortened
|
||||
form for long addresses - the first 37 characters + an ellipsis (matching the
|
||||
welist site) - while the FULL url is still stored as the row's key data and is
|
||||
set as the cell tooltip (so hovering shows the complete address). URLs of 40
|
||||
characters or fewer are shown unchanged.
|
||||
- New `_FullUrlEditDelegate` installed on the URL column: when the user
|
||||
double-clicks the URL cell to edit it, the editor is preloaded with the FULL
|
||||
url (from the key role) instead of the shortened display text. Without this,
|
||||
editing a shortened URL would have saved the truncated text and corrupted the
|
||||
will-executor key. Verified against Electrum's edit-commit path
|
||||
(my_treeview on_commitData -> on_edited): the committed text is the editor's
|
||||
text (now the full URL) and the edit key is read from the role, so an
|
||||
unmodified URL round-trips intact.
|
||||
- Only the URL column's presentation/editor changed; download, ping and
|
||||
connection logic are untouched.
|
||||
|
||||
**Verification:**
|
||||
- Full test suite against **Electrum 4.7.2**: `266 passed`; against **Electrum
|
||||
4.8.0**: `266 passed` (same 2 pre-existing, unrelated failures in both).
|
||||
- `ruff`: no new errors.
|
||||
- Shortening check: the 69-char sample .onion becomes a 38-char display
|
||||
("http://gxvdgcqdy2s7x5cujmkaqua72r2aqo..."), while short https URLs are shown
|
||||
unchanged.
|
||||
|
||||
**Note:** the separate ~5s delay observed when contacting the .onion server is
|
||||
inherent to Tor building a circuit to the hidden service, not a plugin bug; it
|
||||
was analysed but intentionally NOT changed here.
|
||||
|
||||
**Outcome:** DONE (delivered as test ZIP v0.5.14; commit only after confirmation).
|
||||
|
||||
---
|
||||
|
||||
## 39. v0.5.15 - Fix KeyError on delete/select/ping of shortened .onion will-executors
|
||||
|
||||
**Date:** 2026-07-15
|
||||
|
||||
**Problem (owner-reported):** After v0.5.14 shortened long .onion URLs in the
|
||||
will-executor list, deleting (or selecting/deselecting/pinging) a server raised:
|
||||
|
||||
KeyError: 'http://gxvdgcqdy2s7x5cujmkaqua72r2aqo...'
|
||||
|
||||
**Cause:** `WillExecutorListWidget.create_menu` collected the selected key with
|
||||
`item.data(0)`, which returns the DISPLAY text - now the SHORTENED URL (with the
|
||||
ellipsis). That truncated string was then used as a dict key in
|
||||
delete/select/deselect/ping, none of which exist in `willexecutors_list` (keyed
|
||||
by the full URL) -> KeyError. This was a regression introduced by v0.5.14.
|
||||
|
||||
**Fix (`bal/gui/qt/lists.py`, `create_menu`):** the selected key is now read from
|
||||
the real key role (`ROLE_HEIR_KEY + Columns.URL`), which always holds the full
|
||||
URL - the same mechanism already used by `get_edit_key_from_coordinate` and
|
||||
`on_double_click` - with a fallback to `item.data(0)` only if the role is
|
||||
missing. So the context-menu actions operate on the full URL again.
|
||||
|
||||
**Verification:**
|
||||
- Full test suite against **Electrum 4.7.2**: `266 passed`; against **Electrum
|
||||
4.8.0**: `266 passed` (same 2 pre-existing, unrelated failures in both).
|
||||
- `ruff`: no new errors.
|
||||
- Checked there are no other `.data(0)` key usages left in the will-executor
|
||||
list (the remaining ones are in the heir/will lists, which do not shorten
|
||||
URLs).
|
||||
|
||||
**Outcome:** DONE (delivered as test ZIP v0.5.15; commit only after confirmation).
|
||||
|
||||
---
|
||||
|
||||
## 40. v0.5.16 - Skip Tor (.onion) will-executors from download when Electrum is not on Tor
|
||||
|
||||
**Date:** 2026-07-16
|
||||
|
||||
**Goal (owner request):** A .onion will-executor is only reachable through Tor.
|
||||
Users connected via Tor should get the .onion servers in the welist download;
|
||||
users NOT on Tor should NOT download them at all - contacting them would only
|
||||
waste time (they are unreachable). No user-facing option: the behaviour is
|
||||
driven solely by whether Electrum is connected through Tor.
|
||||
|
||||
**What changed:**
|
||||
- `bal/core/willexecutors.py`: new helpers `is_onion_url(url)` (detects .onion
|
||||
hosts) and `is_tor_active()` (True only when `Network.is_proxy_tor is True`;
|
||||
False/None => not on Tor; read defensively for offline mode). Present in both
|
||||
Electrum 4.7.2 and 4.8.0.
|
||||
- `bal/gui/qt/window.py` (`fetch_will_executors_list`): when Tor is NOT active,
|
||||
.onion servers are dropped from the downloaded list (not initialised, not
|
||||
shown). When Tor is active, all servers are kept as before.
|
||||
- `bal/gui/qt/common.py`: re-export the two helpers for the Qt layer.
|
||||
|
||||
**Note:** this replaces an earlier, more complex experiment (a "Force onion"
|
||||
setting plus a non-selectable "Tor off" row) that was never committed to Gitea;
|
||||
the final behaviour is the simple Tor-driven filter described above.
|
||||
|
||||
**Verification:**
|
||||
- Full test suite against **Electrum 4.7.2**: `266 passed`; against **Electrum
|
||||
4.8.0**: `266 passed` (same 2 pre-existing, unrelated failures in both).
|
||||
- `ruff`: no new errors.
|
||||
- Filter check: with Tor off the .onion server is removed from the list and only
|
||||
the https servers remain; with Tor on the .onion server is kept.
|
||||
|
||||
**Caveat:** the test suite does not simulate a real Tor connection, so the
|
||||
decisive confirmation (with Electrum on Tor = celeste dot vs off = green dot)
|
||||
must be done by the owner on Windows.
|
||||
|
||||
**Outcome:** DONE (delivered as test ZIP v0.5.16; commit only after confirmation).
|
||||
|
||||
---
|
||||
|
||||
## 41. v0.5.17 - Fix crash when the welist server returns a non-dict response
|
||||
|
||||
**Date:** 2026-07-16
|
||||
|
||||
**Problem (owner-reported, Electrum 4.8.0, Tor active):** pressing Download List
|
||||
could crash with:
|
||||
|
||||
ValueError: dictionary update sequence element #0 has length 1; 2 is required
|
||||
at window.py on_success -> self.willexecutors.update(result)
|
||||
|
||||
**Root cause:** the will-executor list download assumed the server always
|
||||
returns a dict of {url: info}. `handle_response` does `json.loads(...)` and, when
|
||||
the body is NOT valid JSON (e.g. an error/HTML/plain-text page - more likely over
|
||||
Tor when a server misbehaves), it returns the RAW STRING instead. That string
|
||||
then reached `self.willexecutors.update(result)`, and `dict.update("...")`
|
||||
iterates the string characterwise, raising the ValueError. The bug was latent
|
||||
(not introduced by the onion filter) and surfaced with Tor active.
|
||||
|
||||
**Fix (`bal/gui/qt/window.py`):**
|
||||
- `fetch_will_executors_list`: only accept the response if it is a `dict`;
|
||||
otherwise log it, record "invalid response format" and try the next candidate
|
||||
(so the normal "download failed" message is shown instead of crashing). Also,
|
||||
each entry is now checked to be a dict before use (malformed entries skipped).
|
||||
- `on_success`: second line of defense - only `self.willexecutors.update(result)`
|
||||
when `result` is a non-empty dict; otherwise show the clean download-failed
|
||||
warning. A non-dict can no longer crash the callback.
|
||||
|
||||
**Verification:**
|
||||
- Anti-crash check: on_success with a string, a list or an empty dict shows the
|
||||
warning without crashing; a valid dict updates normally.
|
||||
- Full test suite against **Electrum 4.7.2**: `266 passed`; against **Electrum
|
||||
4.8.0**: `266 passed` (same 2 pre-existing, unrelated failures in both).
|
||||
- `ruff`: no new errors.
|
||||
|
||||
**Outcome:** DONE (delivered as test ZIP v0.5.17; commit only after confirmation).
|
||||
|
||||
---
|
||||
|
||||
## 42. v0.5.18 - Clearer message when the list download fails/times out over Tor
|
||||
|
||||
**Date:** 2026-07-16
|
||||
|
||||
**Context (owner-reported):** With Electrum connected through Tor, "Download
|
||||
List" could sit until the timeout and download nothing, while the exact same
|
||||
build worked fine without Tor (or when the user's exit IP changed via VPN). This
|
||||
is not a plugin bug: it is a slow/obstructed Tor path on the user's connection.
|
||||
The request was to make the failure message clearer (no timeout change).
|
||||
|
||||
**What changed (`bal/gui/qt/window.py`, text/UX only):**
|
||||
- New `DOWNLOAD_FAILED_TOR_MESSAGE`: "Could not download the will-executors list
|
||||
over Tor. ... Your Tor connection may be slow. Please try again, or use a VPN
|
||||
(or temporarily disable Tor) for a faster connection."
|
||||
- Both failure paths (`on_success` with an empty/invalid result, and the generic
|
||||
branch of `on_failure`) now show the Tor-specific message when
|
||||
`is_tor_active()` is true, and the existing generic message otherwise.
|
||||
- The download timeout itself is unchanged.
|
||||
|
||||
**Verification:**
|
||||
- Full test suite against **Electrum 4.7.2**: `266 passed`; against **Electrum
|
||||
4.8.0**: `266 passed` (same 2 pre-existing, unrelated failures in both).
|
||||
- `ruff`: no new errors.
|
||||
|
||||
**Outcome:** DONE (delivered as test ZIP v0.5.18; commit only after confirmation).
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.5.15
|
||||
0.5.18
|
||||
@@ -36,4 +36,4 @@ The plugin supports Electrum 4.7.2 and 4.8.0 with PyQt6. Electrum 4.8.0 removed
|
||||
available and adapts, so both releases keep working.
|
||||
"""
|
||||
|
||||
__version__ = "0.5.15"
|
||||
__version__ = "0.5.18"
|
||||
|
||||
@@ -121,7 +121,7 @@ class BalPlugin(BasePlugin):
|
||||
"""
|
||||
|
||||
_version = None
|
||||
__version__ = "0.5.13" # AUTOMATICALLY GENERATED DO NOT EDIT
|
||||
__version__ = "0.5.18" # AUTOMATICALLY GENERATED DO NOT EDIT
|
||||
|
||||
# Command used to open an .ics calendar file, per operating system.
|
||||
default_app = {
|
||||
|
||||
@@ -68,6 +68,47 @@ CHECK_GLOBAL_DEADLINE = NETWORK_DEADLINE
|
||||
_logger = get_logger(__name__)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Tor / .onion helpers
|
||||
# --------------------------------------------------------------------------- #
|
||||
# A .onion will-executor is only reachable when Electrum routes traffic through
|
||||
# Tor. There is no point downloading .onion servers from welist when Electrum is
|
||||
# not on Tor: they would only waste time (unreachable). We therefore drop them
|
||||
# from the downloaded list unless Tor is confirmed active. Electrum itself gates
|
||||
# .onion the same way (`if host.endswith('.onion') and not self.is_proxy_tor`).
|
||||
|
||||
|
||||
def is_onion_url(url):
|
||||
"""True if the URL points to a Tor hidden service (.onion host)."""
|
||||
if not url:
|
||||
return False
|
||||
try:
|
||||
u = url.lower()
|
||||
if "://" in u:
|
||||
u = u.split("://", 1)[1]
|
||||
host = u.split("/", 1)[0].split(":", 1)[0]
|
||||
return host.endswith(".onion")
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def is_tor_active():
|
||||
"""True only if Electrum is CONFIRMED connected through Tor.
|
||||
|
||||
Reads Network.is_proxy_tor defensively (network may be None in offline
|
||||
mode). It is TRI-STATE (True / False / None); we require True, so False and
|
||||
None (undetermined) both mean "Tor not active". Available in Electrum 4.7.2
|
||||
and 4.8.0.
|
||||
"""
|
||||
try:
|
||||
network = Network.get_instance()
|
||||
if network is None:
|
||||
return False
|
||||
return network.is_proxy_tor is True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
chainname = BalPlugin.chainname
|
||||
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ from ...core.will import (AmountException, HeirChangeException,
|
||||
WillexecutorChangeException, WillExecutorNotPresent,
|
||||
WillExpiredException, WillItem, WillPostponedException)
|
||||
from ...core.willexecutors import Willexecutors
|
||||
from ...core.willexecutors import is_onion_url, is_tor_active # noqa: F401
|
||||
|
||||
# --- Presentation helpers ---
|
||||
from .theme import server_status_text, server_status_tooltip, status_color
|
||||
|
||||
@@ -1156,9 +1156,35 @@ class BalWindow:
|
||||
f"len={len(resp) if hasattr(resp, '__len__') else 'n/a'}"
|
||||
)
|
||||
if resp:
|
||||
# Robustness: the server is expected to return a dict of
|
||||
# {url: info}. If it returns anything else (a raw string
|
||||
# error page, a list, etc. - more likely over Tor when a
|
||||
# server misbehaves), do NOT use it: that would later crash
|
||||
# on `self.willexecutors.update(result)`. Treat it as an
|
||||
# empty/failed response and try the next candidate.
|
||||
if not isinstance(resp, dict):
|
||||
_logger.warning(
|
||||
f"fetch_will_executors_list: {url} -> unexpected "
|
||||
f"response type {type(resp).__name__}, ignoring"
|
||||
)
|
||||
last_error = "invalid response format"
|
||||
continue
|
||||
result = resp
|
||||
for w in result:
|
||||
if w not in ("status", "url"):
|
||||
# Tor gating: if Electrum is NOT connected through Tor, drop
|
||||
# the .onion servers entirely - they are unreachable without
|
||||
# Tor and would only waste time. When on Tor, keep them all.
|
||||
tor_on = is_tor_active()
|
||||
for w in list(result.keys()):
|
||||
if w in ("status", "url"):
|
||||
continue
|
||||
# Defensive: each entry must be a dict too; skip anything
|
||||
# malformed rather than crashing downstream.
|
||||
if not isinstance(result.get(w), dict):
|
||||
del result[w]
|
||||
continue
|
||||
if not tor_on and is_onion_url(w):
|
||||
del result[w]
|
||||
continue
|
||||
Willexecutors.initialize_willexecutor(
|
||||
result[w], w, None,
|
||||
old_willexecutors.get(w, None),
|
||||
@@ -1191,6 +1217,16 @@ class BalWindow:
|
||||
"and try again."
|
||||
)
|
||||
|
||||
# Shown when the download fails while Electrum is connected through Tor:
|
||||
# the most common cause is a slow Tor connection, so guide the user
|
||||
# accordingly instead of the generic message above.
|
||||
DOWNLOAD_FAILED_TOR_MESSAGE = (
|
||||
"Could not download the will-executors list over Tor.\n\n"
|
||||
"Electrum is connected through Tor and the connection is taking too "
|
||||
"long. Your Tor connection may be slow. Please try again, or use a VPN "
|
||||
"(or temporarily disable Tor) for a faster connection."
|
||||
)
|
||||
|
||||
def download_list(self, willexecutors, fn_on_success, fn_on_failure=None):
|
||||
if fn_on_failure is None:
|
||||
fn_on_failure = log_error
|
||||
@@ -1235,9 +1271,18 @@ class BalWindow:
|
||||
stop_heartbeat.set()
|
||||
|
||||
def on_success(result):
|
||||
if result:
|
||||
# Defensive: only merge a proper dict (see fetch_will_executors_list).
|
||||
# A non-dict here would raise "dictionary update sequence element..."
|
||||
if isinstance(result, dict) and result:
|
||||
self.willexecutors.update(result)
|
||||
fn_on_success(result)
|
||||
else:
|
||||
# Tor-aware failure message: a download failure/timeout while
|
||||
# Electrum is connected through Tor is most often a slow Tor
|
||||
# connection, so point the user to that (try again / VPN /
|
||||
# disable Tor). Otherwise keep the generic connection message.
|
||||
if is_tor_active():
|
||||
self.show_warning(_(self.DOWNLOAD_FAILED_TOR_MESSAGE))
|
||||
else:
|
||||
self.show_warning(_(self.DOWNLOAD_FAILED_MESSAGE))
|
||||
|
||||
@@ -1269,6 +1314,11 @@ class BalWindow:
|
||||
f"No active will-executor found for the "
|
||||
f"{err.chain} network."
|
||||
))
|
||||
else:
|
||||
# Tor-aware: a generic failure/timeout while on Tor is most
|
||||
# likely a slow Tor connection.
|
||||
if is_tor_active():
|
||||
self.show_warning(_(self.DOWNLOAD_FAILED_TOR_MESSAGE))
|
||||
else:
|
||||
self.show_warning(_(self.DOWNLOAD_FAILED_MESSAGE))
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "bal",
|
||||
"fullname": "Bitcoin After Life",
|
||||
"version": "0.5.15",
|
||||
"version": "0.5.18",
|
||||
"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",
|
||||
|
||||
Reference in New Issue
Block a user