Files
bal-electrum-plugin/CHANGELOG_REFACTOR.md
genspark-ai-developer[bot] 3c44a29f84 fix: crash GUI su Windows (OverflowError anno 2038) — schede/menu BAL rotti (#3)
* fix(gui): voci di menu BAL duplicate/condensate dopo riavvio o cambio wallet

Sintomo (Windows 11): dopo aver riavviato Electrum o cambiato wallet, le
schede Will/Heirs sparivano dalla tab bar e dal menu, e compariva una voce
di menu condensata/illeggibile (icona + testo sovrapposti) sotto il logo di
Electrum, accanto a 'Portafogli'.

Causa: init_menubar_tools veniva eseguito DUE volte sulla stessa finestra.
Con il plugin gia abilitato, al riavvio Electrum invoca sia l'hook
init_menubar sia il percorso di init a caldo (init_qt -> _setup_window),
entrambi chiamano init_menubar_tools -> addTab/addAction duplicati.
Nell'originale init_qt faceva return (chiedendo il riavvio) e quindi i menu
venivano creati una sola volta; rimuovendo quel return (fix B3) e' emersa la
doppia inizializzazione.

Fix:
- BalWindow._menubar_initialized: guardia di idempotenza.
- init_menubar_tools: se gia inizializzato, esce subito (niente duplicati).
- on_close: resetta il flag dopo aver rimosso tab/azioni, cosi la stessa
  finestra puo essere riusata per un altro wallet.
- tests/gui_fixes_test.py: regressione che verifica la guardia in __init__,
  init_menubar_tools e on_close.

Logica di business invariata (nessuna modifica a bal/core/*).

* fix(gui): ripristina create_status_bar come no-op (come originale)

L'elemento di menu condensato/illeggibile sotto il logo di Electrum era
causato dal StatusBarButton aggiunto da create_status_bar.

Nell'originale Gitea questo hook aveva un 'return' subito dopo il log, PRIMA
di costruire il bottone: era quindi disabilitato di proposito. Durante la
pulizia del 'dead code' nel refactoring quel return era stato rimosso,
riattivando la creazione del bottone -> elemento icona+testo renderizzato
nel punto sbagliato dopo riavvio/cambio wallet.

Fix: create_status_bar torna a essere un no-op (return), fedele all'originale.
Le impostazioni restano raggiungibili da Strumenti -> Plugin.

Regressione: gui_fixes_test verifica che create_status_bar non chiami
addPermanentWidget.

* fix(core): OverflowError su Windows (anno 2038) che rompeva tab/menu BAL

CAUSA VERA (dal log Electrum dell'utente, Windows 11):

  OverflowError: Python int too large to convert to C int
    window.py __init__ -> create_heirs_tab -> WillSettingsWidget
    -> on_locktime_change -> BalTimestamp.to_date
    -> datetime.fromtimestamp(NLOCKTIME_MAX)

Su Windows time_t e' a 32 bit, quindi datetime.fromtimestamp() solleva
OverflowError per qualsiasi timestamp oltre il 2038 (es. NLOCKTIME_MAX =
2**32-1 = 4294967295, usato come locktime di default/sentinella). Su Linux
64-bit la stessa chiamata funziona: per questo il bug si vedeva solo su
Windows e i test su Linux non lo intercettavano.

L'eccezione interrompeva BalWindow.__init__ durante init_menubar/load_wallet,
lasciando le schede Will/Heirs e la voce di menu a meta' costruzione ->
l'elemento grafico condensato/illeggibile sotto il logo di Electrum.

FIX (comportamento invariato per tutti i valori normali):
- BalTimestamp._safe_fromtimestamp(): datetime.fromtimestamp con clamp a
  INT32_MAX in caso di OverflowError/OSError/ValueError, esattamente come la
  funzione get_max_allowed_timestamp() dell'originale (Electrum issue #6170).
- Usato in to_date / to_timestamp / __str__ / __repr__ di BalTimestamp.
- widgets.py set_value: usa il converter sicuro.
- util.py timestamp_minus: stessa protezione inline con clamp a INT32_MAX.

I valori entro il 2038 (date assolute normali, durate relative come 90d/5y)
producono lo stesso identico risultato di prima.

TEST: tests/windows_overflow_test.py riproduce il limite 32-bit di Windows
(monkeypatch di datetime.fromtimestamp) e dimostra che senza il fix si ottiene
lo stesso OverflowError del log, mentre col fix passa. Verificato anche che il
test FALLISCE senza il fix.

* docs(it): documenta il fix OverflowError Windows (anno 2038) nel changelog

Aggiunge la sezione §13 al CHANGELOG_REFACTOR.md che descrive:
- sintomo (schede/menu rotti su Windows dopo riavvio/cambio wallet)
- causa vera dal log (datetime.fromtimestamp(NLOCKTIME_MAX) -> OverflowError
  su time_t 32-bit di Windows)
- fix con _safe_fromtimestamp (clamp a INT32_MAX, come Electrum #6170)
- test di regressione windows_overflow_test.py
Aggiornata anche la cronologia (§12) con PR #3.

---------

Co-authored-by: GenSpark AI Developer <ai@genspark.dev>
2026-06-13 16:29:30 +00:00

23 KiB

BAL — Resoconto del refactoring (per l'autore originale)

Questo documento elenca tutte le modifiche apportate al plugin BAL (Bitcoin After Life) rispetto alla versione originale 0.2.8.

Principio guida: refactoring conservativo e a comportamento invariato (Approccio A). La logica di business è stata mantenuta byte-identica dove possibile; sono cambiati soprattutto la disposizione dei file e gli import. Nessuna riscrittura algoritmica.

Ambiente di verifica: Electrum 4.7.2 + PyQt6 (l'ultima release stabile che espone json_db.register_dict).


1. Riorganizzazione della struttura (separazione logica / GUI)

Il problema principale segnalato era che la logica e la grafica erano mescolate, in particolare in un unico file qt.py da 4131 righe.

Struttura PRIMA (flat, 7 file)

BAL/
├── __init__.py        (vuoto, 0 righe)
├── bal.py             (243)  logica + plugin base
├── util.py            (533)  helper
├── heirs.py           (791)  modello eredi + costruzione tx
├── will.py            (927)  modello will/WillItem
├── willexecutors.py   (374)  networking will-executor
├── qt.py              (4131) TUTTA la GUI + il Plugin in un solo file
└── bal_resources.py   (14)

Struttura DOPO (core/ vs gui/)

bal/
├── manifest.json            metadati conformi allo standard
├── qt.py                    shim di caricamento (re-export di Plugin)
├── __init__.py              docstring di architettura + __version__
├── core/                    LOGICA senza dipendenze Qt
│   ├── util.py              (ex util.py)
│   ├── plugin_base.py       (ex bal.py)
│   ├── heirs.py             (ex heirs.py)
│   ├── will.py              (ex will.py)
│   └── willexecutors.py     (ex willexecutors.py)
└── gui/qt/                  PRESENTAZIONE PyQt6
    ├── theme.py      (59)   mappatura stato → colore
    ├── common.py     (155)  import condivisi + helper GUI
    ├── widgets.py    (782)  widget "foglia"
    ├── calendar.py   (80)   BalCalendar
    ├── dialogs.py    (1127) finestre di dialogo
    ├── lists.py      (957)  viste ad albero (eredi/preview/executor)
    ├── window.py     (952)  controller GUI per-wallet (BalWindow)
    └── plugin.py     (273)  classe Plugin (@hook Electrum → GUI)

Il file qt.py da 4131 righe è stato suddiviso per responsabilità. I corpi delle classi sono stati copiati verbatim (riga per riga) per non toccare la logica delicata delle transazioni di eredità.

Mappa: dove sono finite le 40 classi/funzioni di qt.py

Classe/funzione (riga orig.) Nuovo modulo
Plugin (67) gui/qt/plugin.py
shown_cv (317) gui/qt/common.py
BalWindow (330) gui/qt/window.py
add_widget (1257) gui/qt/common.py
ClickableLabel (1263) gui/qt/widgets.py
BalTxFeesWidget (1271) gui/qt/widgets.py
_LockTimeEditor (1340) gui/qt/widgets.py
BalTimeEditWidget (1374) gui/qt/widgets.py
TimeRawEditWidget (1508) gui/qt/widgets.py
LockTimeRawEdit (1527) gui/qt/widgets.py
LockTimeDateEdit (1605) gui/qt/widgets.py
ThresholdTimeWidget (1644) gui/qt/widgets.py
LockTimeWidget (1664) gui/qt/widgets.py
WillSettingsWidget (1683) gui/qt/widgets.py
PercAmountEdit (1818) gui/qt/widgets.py
BalDialog (1883) gui/qt/dialogs.py
BalWizardDialog (1913) gui/qt/dialogs.py
BalWizardWidget (2002) gui/qt/dialogs.py
BalWizardHeirsWidget (2068) gui/qt/dialogs.py
BalWizardWEDownloadWidget (2103) gui/qt/dialogs.py
BalWizardWEWidget (2190) gui/qt/dialogs.py
BalWizardLocktimeAndFeeWidget(2207) gui/qt/dialogs.py
BalWaitingDialog (2224) gui/qt/dialogs.py
BalBlockingWaitingDialog (2285) gui/qt/dialogs.py
BalLineEdit (2304) gui/qt/widgets.py
BalTextEdit (2312) gui/qt/widgets.py
BalCheckBox (2320) gui/qt/widgets.py
BalBuildWillDialog (2335) gui/qt/dialogs.py
HeirListWidget (2858) gui/qt/lists.py
PreviewList (3059) gui/qt/lists.py
WillDetailDialog (3445) gui/qt/dialogs.py
WillWidget (3545) gui/qt/widgets.py
WillExecutorListWidget (3637) gui/qt/lists.py
WillExecutorWidget (3873) gui/qt/lists.py
WillExecutorDialog (3982) gui/qt/dialogs.py
CheckAliveError (4018) gui/qt/common.py
log_error (4028) gui/qt/common.py
export_meta_gui (4043) gui/qt/common.py
BalCalendar (4066) gui/qt/calendar.py

2. Rimozioni (codice morto / debug) — comportamento invariato

Tutte le rimozioni seguenti sono state verificate come non utilizzate o puramente di debug, quindi non alterano il comportamento del plugin.

  1. util.pycore/util.py: rimossi tre helper di debug usati solo per stampe a console:

    • print_var() (orig. riga 439)
    • print_utxo() (orig. riga 474)
    • print_prevout() (orig. riga 486)
  2. bal.pycore/plugin_base.py: rimossa la funzione stub vuota get_will_settings(x) (orig. righe 12-14):

    def get_will_settings(x):
        # print(x)
        pass
    

    ⚠️ Verificato: non era riferita da nessun register_dict — i tre register_dict usano tuple, dict, lambda x: x. Quindi era codice morto. La funzione usata get_will(x) è stata mantenuta identica.

  3. will.py (WillItem) → spostato in gui/qt/theme.py: il metodo WillItem.get_color() (orig. riga 852) restituiva colori esadecimali — è logica di presentazione, non di dominio. È stato spostato fuori dal modello e trasformato nella funzione status_color(will_item) in gui/qt/theme.py. Verificato byte-identico su tutte le combinazioni di stato (stessa catena di get_status(...), stessi codici colore).


3. Cambi di import (necessari per la nuova struttura)

Gli import sono stati aggiornati da "flat" a "a package". Esempi:

Prima Dopo
from .bal import BalPlugin from .plugin_base import BalPlugin (in willexecutors)
from .util import Util from .util import Util (invariato, ora dentro core/)
(in qt.py) from .bal import ... i moduli GUI importano da ...core.X
  • Aggiunti from .common import _, _logger nei moduli GUI, perché import * non esporta i nomi che iniziano con underscore.
  • Aggiunti 3 import "lazy" (dentro le funzioni) in dialogs.py per spezzare il ciclo dialogs ↔ lists (lists importa BalBuildWillDialog da dialogs).

La logica interna dei metodi non è stata toccata: prepare_transactions(), buildTransactions() ecc. sono verbatim.


4. Packaging conforme allo standard Electrum

manifest.json reso conforme a https://plugins.electrum.org/developers.html :

Campo Prima Dopo
name "BAL" "bal" (minuscolo = nome dir)
version (assente, era solo nella description) "0.2.8"
description con <br> HTML testo pulito
licence (assente) "MIT"
fullname/author/available_for/icon presenti invariati
  • __init__.py (era vuoto): ora contiene la docstring di architettura e __version__ = "0.2.8".
  • Portati nel package: LICENSE, VERSION, README.md, bal_resources.py, e la cartella wallet_util/ (invariata).

5. CORREZIONE BUG: caricamento come plugin esterno (.zip)

Durante i test su Electrum 4.7.2 portable per Windows sono emersi due problemi reali nel caricare il plugin come plugin esterno da .zip:

Bug 5a — ModuleNotFoundError: No module named 'electrum_external_plugins'

  • Causa: Electrum carica i plugin esterni da zip sotto il package sintetico electrum_external_plugins.bal, ed esegue solo l'__init__ del package e il modulo qt. Non registra il package radice sintetico né i sotto-package annidati (gui, gui.qt). Un semplice from .gui.qt.plugin import Plugin fallisce risalendo ai parent mancanti.
  • Fix: qt.py ora è uno shim resiliente che (1) rileva a runtime il proprio nome di package (__package__), (2) ricostruisce in sys.modules gli eventuali package padre mancanti, (3) importa Plugin con importlib.import_module. Funziona sia come plugin interno (electrum.plugins.bal) sia esterno (electrum_external_plugins.bal).

Bug 5b — zlib.error: Error -5 ... incomplete or truncated stream

  • Causa: alcune build portable di Electrum su Windows non riescono a decomprimere con zipimport archivi che contengono voci di directory o compressione non standard.
  • Fix: aggiunto build_zip.py, che genera un archivio "zipimport-friendly": solo file (nessuna voce di directory), DEFLATE standard, ordine deterministico (SHA-256 riproducibile), escludendo __pycache__/*.pyc. Stampa anche l'hash SHA-256 per verificare l'integrità del download.

6. Test aggiunti

  • tests/smoke_test.py — verifica import + comportamento di base (BalTimestamp, helper di Util, costanti HEIR_*, stati di WillItem, hook del Plugin).
  • tests/external_zip_test.py — riproduce fedelmente la sequenza di caricamento di un plugin esterno da zip di Electrum (regressione per il Bug 5a/5b).

Tutti i test passano sotto Electrum 4.7.2 + PyQt6.


7. Riepilogo: cosa NON è cambiato

  • La logica di costruzione delle transazioni (heirs.py, will.py).
  • I valori e i tipi di json_db.register_dict(...).
  • I codici colore degli stati (solo spostati in theme.py).
  • L'algoritmo di tutte le classi GUI (copiate verbatim).
  • Il formato dei dati salvati nel wallet.

8. Note / raccomandazioni

  • Il plugin richiede Electrum 4.7.2: json_db.register_dict è stato rimosso nelle versioni successive (master), dove andrebbe sostituito con stored_dict.register_name. Valutare un adeguamento se si vuole supportare Electrum più recente.
  • Prima del rilascio è consigliata una prova end-to-end in una sessione Electrum reale (preferibilmente su testnet), oltre agli smoke test.

9. CORREZIONI GUI — finestre e ciclo di vita (B1-B10)

Dopo il refactoring di struttura sono stati corretti dieci difetti grafici e di ciclo di vita delle finestre, già presenti nel codice originale. La logica di business è rimasta byte-identica (nessuna modifica a bal/core/*): sono cambiati solo presentazione, parent, modalità, z-order, ciclo di vita e cleanup delle finestre Qt.

Sintomi segnalati dall'utente, ora risolti:

  • (S1) le finestre del plugin sparivano dietro la finestra di Electrum;
  • (S2) alcuni meccanismi funzionavano solo dopo aver chiuso e riavviato Electrum.
ID Problema (presente nell'originale) Correzione applicata
B1 self.parent = parent sovrascriveva il metodo parent() di Qt, rompendo la gerarchia delle finestre rinominato in self._bal_parent (in dialogs.py, lists.py, widgets.py); il parent reale passa da top_level_of(parent)
B2 dialoghi aperti con .show() non modale → finivano sotto la finestra principale sostituiti con show_on_top() / show_modal() e parent corretto
B3 messaggio "Please restart Electrum to activate the BAL plugin": il plugin si attivava solo dopo riavvio inizializzazione a caldo con _setup_window() che replica load_wallet — niente più riavvio
B4 chiave del dizionario finestre usava il metodo winId invece del valore chiave stabile _window_key() basata su id(window)
B5 on_close ingoiava tutti gli errori con except: pass riscritto: niente except:pass, log per ogni passo, reset pulito dello stato
B6 BalBlockingWaitingDialog bloccava il thread della GUI (processEvents commentato) ripristinato processEvents() → GUI reattiva durante l'attesa
B7 closeEvent/hideEvent con cleanup del thread commentato gestione esplicita di closeEvent/hideEvent + chiamata a super()
B8 closeEvent incompleto in alcuni dialog gestione uniforme dello stato di chiusura
B9 show()+raise_() senza activateWindow() né modalità → finestra non in primo piano bring_to_front() = raise_() + activateWindow()
B10 gestione multi-wallet / multi-finestra fragile; menu cercato per titolo &Tools uso dell'API ufficiale window.tools_menu

Nuovo modulo: gui/qt/window_utils.py (119 righe)

Gli helper per la gestione delle finestre sono stati centralizzati in un unico modulo, così la stessa logica non viene duplicata nei vari dialog:

  • top_level_of(widget) — risale alla finestra di primo livello corretta da usare come parent;
  • bring_to_front(window)raise_() + activateWindow() per portare in primo piano;
  • stop_thread(thread) — stop+wait sicuro di un TaskThread;
  • show_modal(dialog) — apertura modale corretta (exec());
  • show_on_top(window) — apertura non modale ma sopra le altre finestre.

gui/qt/common.py importa questi helper e li rende disponibili al resto della GUI.


10. CORREZIONE BUG: download lista will-executor

Dopo l'installazione del pacchetto con le correzioni GUI, l'utente ha segnalato che il comando "download list" dei will-executor non scaricava più la lista.

Indagine

Il codice di rete (core/willexecutors.py: send_request, handle_response, download_list, initialize_willexecutor) è stato confrontato riga per riga con l'originale Gitea ed è risultato byte-identico (l'unica differenza è il parametro aggiuntivo welist_server in download_list, retro-compatibile).

Durante l'indagine sono comunque emersi e stati corretti due difetti reali introdotti dalle correzioni GUI, che potevano "perdere" il risultato del download:

  1. BalDialog.closeEvent/hideEvent fermavano il TaskThread. In Electrum TaskThread.on_done esegue cb_done (cioè self.accept, che chiude il dialog) prima di cb_result (cioè on_success, che aggiorna la lista). Fermare il thread alla chiusura del dialog scartava quindi il risultato appena scaricato. → I due metodi sono stati riportati a non fermare il thread (con commento esplicativo nel codice).
  2. BalWaitingDialog.exe() usava una modalità sbagliata (show_modal / WindowModal). → Ripristinato l'originale self.exec(), aggiungendo prima bring_to_front(self) per garantire il primo piano.

Inoltre i percorsi del pulsante e del wizard (che prima scaricavano in modi diversi e con messaggi diversi) sono stati unificati in un unico helper fetch_will_executors_list, eseguito dentro il worker del TaskThread.

Causa vera del mancato download: ambientale, NON del plugin

Una probe di controllo con urllib che bypassava completamente Electrum falliva ugualmente con WinError 10054 ("connection forcibly closed by remote host"): segno che la rete/ISP dell'utente resettava la connessione HTTPS verso welist.bitcoin-after.life. La conferma definitiva: attivando una VPN il download è andato a buon fine.

L'originale "sembrava" funzionare perché spedisce comunque un will-executor di default già incorporato (https://we.bitcoin-after.life), quindi la lista non risultava mai del tutto vuota anche senza un download riuscito.

Pulizia finale (scelta dall'utente — "Opzione 1")

  • Finestra di attesa non bloccante mantenuta (BalWaitingDialog), così la GUI non si congela durante il download.

  • Fallback dell'URL: prima l'URL configurato (WELIST_SERVER), poi quello hardcoded https://welist.bitcoin-after.life/.

  • Diagnostica dettagliata spostata nei soli log (rimossa la probe urllib dall'interfaccia).

  • Messaggio d'errore semplice per l'utente, in inglese (DOWNLOAD_FAILED_MESSAGE):

    "Could not download the will-executors list. This is usually caused by your internet connection or a firewall, not by the plugin. Please check your connection (a VPN often helps) and try again."

File toccati (solo presentazione/GUI, logica invariata)

  • gui/qt/window.py — helper condiviso fetch_will_executors_list, download_list con TaskThread + BalWaitingDialog, costante DOWNLOAD_FAILED_MESSAGE.
  • gui/qt/lists.pyWillExecutorWidget.download_list instradato sul percorso condiviso con on_success che aggiorna/salva la lista.
  • gui/qt/dialogs.pyBalDialog.closeEvent/hideEvent non fermano più il thread; BalWaitingDialog.exe() torna a self.exec() + bring_to_front.
  • tests/gui_fixes_test.py — asserzione di regressione: verifica che closeEvent/hideEvent non contengano stop_thread (per non reintrodurre il bug che scartava il download).

11. Confronto strutturale finale (originale Gitea → refactor)

Conteggio file .py (escluse cartelle generate):

Originale (Gitea) righe Refactor (bal/) righe
__init__.py 1 __init__.py 37
bal.py 161 core/plugin_base.py 351
util.py 1051 core/util.py 614
heirs.py 792 core/heirs.py 806
will.py 903 core/will.py 938
willexecutors.py 547 core/willexecutors.py 390
qt.py (monolite GUI) 3777 suddiviso in gui/qt/* (vedi sotto)
bal_resources.py 14 bal_resources.py 14
wallet_util/*.py 275 wallet_util/*.py (invariati) 280

Suddivisione del vecchio qt.py (3777 righe) nei moduli GUI:

Modulo refactor righe Contenuto
gui/qt/plugin.py 303 classe Plugin (@hook Electrum → GUI)
gui/qt/window.py 1048 BalWindow (controller per-wallet)
gui/qt/dialogs.py 1155 finestre di dialogo + wizard
gui/qt/lists.py 964 viste ad albero (eredi/preview/executor)
gui/qt/widgets.py 782 widget "foglia"
gui/qt/common.py 157 import condivisi + helper
gui/qt/window_utils.py 119 helper finestre (NUOVO — vedi §9)
gui/qt/calendar.py 80 BalCalendar
gui/qt/theme.py 59 mappatura stato → colore
gui/qt/__init__.py 17 init package GUI

Le differenze nei conteggi di righe rispetto all'originale derivano da: riformattazione/commenti, separazione degli import per modulo, e spostamento di funzioni tra util.py/bal.py e i nuovi moduli. Gli algoritmi non sono stati modificati.


12. Cronologia delle modifiche su GitHub

  • 4198a51 — import iniziale del refactor strutturale (v0.2.8): separazione core/ (logica) vs gui/qt/ (presentazione), packaging conforme, fix caricamento zip esterno, smoke test (sezioni §1-§8).
  • d56fa36 — questo changelog del refactoring (in italiano).
  • 4806997DIAGNOSI_GUI.md: diagnosi dei bug GUI di z-order e ciclo di vita (Fase A).
  • dd6f677 (PR #2, squash) — correzioni GUI B1-B10 + fix download lista will-executor + window_utils.py + test di regressione (sezioni §9-§10).
  • PR #3 — fix OverflowError su Windows (anno 2038) che rompeva le schede Will/Heirs e la voce di menu (sezione §13).

13. CORREZIONE BUG: OverflowError su Windows (limite anno 2038)

Sintomo (Windows 11)

Dopo aver riavviato Electrum o cambiato wallet, le schede Will e Heirs sparivano e compariva una voce di menu condensata/illeggibile (icona + testo sovrapposti) sotto il logo di Electrum, accanto a Portafogli. Su Linux il problema non si manifestava.

Causa vera (dal log di Electrum dell'utente)

OverflowError: Python int too large to convert to C int
  window.py __init__ -> create_heirs_tab -> WillSettingsWidget
  -> on_locktime_change -> BalTimestamp.to_date
  -> datetime.fromtimestamp(NLOCKTIME_MAX)
  • NLOCKTIME_MAX = 2**32 - 1 = 4294967295 viene usato come locktime di default/sentinella.
  • Su Windows time_t è a 32 bit, quindi datetime.fromtimestamp(ts) solleva OverflowError per qualsiasi timestamp oltre il 2038.
  • Su Linux 64-bit la stessa chiamata funziona: ecco perché il bug si vedeva solo su Windows e i test su Linux non lo intercettavano.
  • L'eccezione interrompeva BalWindow.__init__ durante init_menubar / load_wallet, lasciando le schede Will/Heirs e la voce di menu a metà costruzione → l'elemento grafico condensato/illeggibile sotto il logo.

Nota: i due primi tentativi di correzione (status-bar no-op e idempotenza di init_menubar_tools) non centravano la causa; sono stati comunque mantenuti perché innocui e leggermente migliorativi, ma il vero colpevole era questo crash a monte.

Fix (comportamento invariato per tutti i valori normali)

  • BalTimestamp._safe_fromtimestamp(): datetime.fromtimestamp con clamp a INT32_MAX (anno 2038) in caso di OverflowError/OSError/ ValueError, esattamente come la funzione get_max_allowed_timestamp() dell'originale (workaround per Electrum issue #6170).
  • Usato in to_date / to_timestamp / __str__ / __repr__ di BalTimestamp.
  • gui/qt/widgets.py (set_value): usa il converter sicuro.
  • core/util.py (timestamp_minus): stessa protezione inline con clamp a INT32_MAX.

I valori entro il 2038 (date assolute normali, durate relative come 90d/5y) producono lo stesso identico risultato di prima.

Test

  • tests/windows_overflow_test.py riproduce il limite 32-bit di Windows (monkeypatch di datetime.fromtimestamp) e dimostra che senza il fix si ottiene lo stesso OverflowError del log, mentre con il fix passa. Verificato anche che il test fallisce senza il fix.

Confermato dall'utente: "si ora funziona".