Skip to content

Commit f441e7a

Browse files
committed
Add handler to persist closing warnings
1 parent 6d2179f commit f441e7a

File tree

2 files changed

+69
-29
lines changed

2 files changed

+69
-29
lines changed

electrum/gui/qt/main_window.py

+9-28
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
filename_field, address_field, char_width_in_lineedit, webopen,
8989
TRANSACTION_FILE_EXTENSION_FILTER_ANY, MONOSPACE_FONT,
9090
getOpenFileName, getSaveFileName, ShowQRLineEdit, QtEventListener, qt_event_listener,
91-
event_listener, scan_qr_from_screenshot)
91+
event_listener, scan_qr_from_screenshot, QtClosingWarningHandler)
9292
from .wizard.wallet import WIF_HELP_TEXT
9393
from .history_list import HistoryList, HistoryModel
9494
from .update_checker import UpdateCheck, UpdateCheckThread
@@ -271,8 +271,7 @@ def add_optional_tab(tabs, tab, icon, description):
271271

272272
# network callbacks
273273
self.register_callbacks()
274-
# wallet closing warning callbacks
275-
self.closing_warning_callbacks = [] # type: List[Callable[[], Optional[str]]]
274+
self.closing_warning_handler = QtClosingWarningHandler(wallet)
276275
# banner may already be there
277276
if self.network and self.network.banner:
278277
self.console.showMessage(self.network.banner)
@@ -2690,7 +2689,7 @@ def _show_closing_warnings(self) -> bool:
26902689
"""Show any closing warnings and return True if the user chose to quit anyway."""
26912690

26922691
warnings: Set[str] = set()
2693-
for cb in self.closing_warning_callbacks:
2692+
for cb in self.closing_warning_handler.get_closing_warning_callbacks():
26942693
if warning := cb():
26952694
warnings.add(warning)
26962695

@@ -2710,23 +2709,6 @@ def _show_closing_warnings(self) -> bool:
27102709
return True
27112710
return False
27122711

2713-
def register_closing_warning_callback(self, callback: Callable[[], Optional[str]]) -> None:
2714-
"""
2715-
Registers a callback that will be called when the wallet is closed. If the callback
2716-
returns a string it will be shown to the user as a warning to prevent them closing the wallet.
2717-
"""
2718-
assert not asyncio.iscoroutinefunction(callback)
2719-
if not self.config.get("cmd") == "gui":
2720-
return
2721-
def warning_callback() -> Optional[str]:
2722-
try:
2723-
return callback()
2724-
except Exception:
2725-
self.logger.exception("Error in closing warning callback")
2726-
return None
2727-
self.logger.debug(f"registering wallet closing warning callback")
2728-
self.closing_warning_callbacks.append(warning_callback)
2729-
27302712
def closeEvent(self, event):
27312713
# note that closeEvent is NOT called if the user quits with Ctrl-C
27322714
if not self._show_closing_warnings():
@@ -2918,13 +2900,12 @@ def on_swap_result(self, txid: Optional[str], *, is_reverse: bool):
29182900
else:
29192901
msg += messages.MSG_FORWARD_SWAP_FUNDING_MEMPOOL + messages.MSG_FORWARD_SWAP_HTLC_EXPIRY
29202902

2921-
def wallet_closing_warning_callback() -> Optional[str]:
2922-
# gets called when the wallet GUI is closed
2923-
warning = messages.MSG_REVERSE_SWAP_FUNDING_MEMPOOL if is_reverse \
2924-
else messages.MSG_FORWARD_SWAP_FUNDING_MEMPOOL
2925-
if self.wallet.adb.get_tx_height(txid).height < 1:
2926-
return _("Ongoing submarine swap") + ":\n" + warning
2927-
self.register_closing_warning_callback(wallet_closing_warning_callback)
2903+
warning_msg = messages.MSG_REVERSE_SWAP_FUNDING_MEMPOOL if is_reverse \
2904+
else messages.MSG_FORWARD_SWAP_FUNDING_MEMPOOL
2905+
self.closing_warning_handler.register_unconfirmed_tx_warning(
2906+
txid=txid,
2907+
warning_msg=_("Ongoing submarine swap") + ":\n" + warning_msg,
2908+
)
29282909

29292910
self.show_message_signal.emit(msg)
29302911
else:

electrum/gui/qt/util.py

+60-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import os
88
import webbrowser
99
from functools import partial, lru_cache, wraps
10+
from asyncio import iscoroutinefunction
1011
from typing import (NamedTuple, Callable, Optional, TYPE_CHECKING, List, Any, Sequence, Tuple, Union)
1112

1213
from PyQt6 import QtCore
@@ -20,6 +21,7 @@
2021
QFrame, QAbstractButton)
2122

2223
from electrum.i18n import _
24+
from electrum.logging import Logger
2325
from electrum.util import (FileImportFailed, FileExportFailed, resource_path, EventListener, event_listener,
2426
get_logger, UserCancelled, UserFacingException)
2527
from electrum.invoices import (PR_UNPAID, PR_PAID, PR_EXPIRED, PR_INFLIGHT, PR_UNKNOWN, PR_FAILED, PR_ROUTING,
@@ -34,7 +36,7 @@
3436

3537
from electrum.simple_config import SimpleConfig
3638
from electrum.simple_config import ConfigVarWithConfig
37-
39+
from electrum.wallet import Abstract_Wallet
3840

3941
if platform.system() == 'Windows':
4042
MONOSPACE_FONT = 'Lucida Console'
@@ -748,6 +750,63 @@ def scan_qr_from_screenshot() -> QrCodeResult:
748750
assert len(scanned_qr) == 1, f"{len(scanned_qr)=}, expected 1"
749751
return scanned_qr[0]
750752

753+
class QtClosingWarningHandler(Logger):
754+
"""
755+
Provides handlers to register specific types of wallet closing warning callbacks,
756+
and persists them.
757+
"""
758+
def __init__(self, wallet: 'Abstract_Wallet'):
759+
Logger.__init__(self)
760+
self.wallet = wallet
761+
persisted_callbacks = wallet.db.get_dict('wallet_closing_warning_callbacks')
762+
763+
# unconfirmed tx warnings: txid -> warning message, minimum confirmations
764+
self.unconfirmed_tx_warnings: dict[str, Tuple[str, int]] \
765+
= persisted_callbacks.setdefault('unconfirmed_tx_warnings', {})
766+
767+
self._closing_warning_callbacks = [] # type: List[Callable[[], Optional[str]]]
768+
self._load_persisted_callbacks()
769+
770+
def _load_persisted_callbacks(self) -> None:
771+
"""Re-registers the callbacks loaded from the database."""
772+
for txid, (warning_msg, minconf) in self.unconfirmed_tx_warnings.items():
773+
self.register_unconfirmed_tx_warning(txid, warning_msg, minconf)
774+
775+
def _register_generic_warning_callback(
776+
self,
777+
callback: Callable[[], Optional[str]],
778+
cleanup_cb: Optional[Callable[[], None]] = None
779+
) -> None:
780+
"""Wraps the callback in try/except and stores it in the callback list."""
781+
assert not iscoroutinefunction(callback)
782+
assert not iscoroutinefunction(cleanup_cb) if cleanup_cb else True
783+
784+
def warning_callback() -> Optional[str]:
785+
warning: Optional[str] = None
786+
try:
787+
warning = callback()
788+
except Exception:
789+
self.logger.exception("Error in closing warning callback")
790+
if warning is None and cleanup_cb:
791+
self.logger.debug(f"removing closing warning callback {callback.__name__}")
792+
cleanup_cb()
793+
return warning
794+
795+
self.logger.debug(f"registering wallet closing warning callback: {callback.__name__}")
796+
self._closing_warning_callbacks.append(warning_callback)
797+
798+
def register_unconfirmed_tx_warning(self, txid: str, warning_msg: str, minconf: int = 1) -> None:
799+
"""Registers a closing warning for a transaction that has to get confirmed."""
800+
self.unconfirmed_tx_warnings[txid] = (warning_msg, minconf)
801+
def unconfirmed_tx_warning_cb() -> Optional[str]:
802+
if self.wallet.adb.get_tx_height(txid).height < minconf:
803+
return warning_msg
804+
return None
805+
cleanup_cb = lambda: self.unconfirmed_tx_warnings.pop(txid, None)
806+
self._register_generic_warning_callback(unconfirmed_tx_warning_cb, cleanup_cb)
807+
808+
def get_closing_warning_callbacks(self):
809+
return self._closing_warning_callbacks
751810

752811
class GenericInputHandler:
753812
def input_qr_from_camera(

0 commit comments

Comments
 (0)