Skip to content

Commit 4f5fca3

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

File tree

2 files changed

+71
-29
lines changed

2 files changed

+71
-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

+62-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'
@@ -749,6 +751,65 @@ def scan_qr_from_screenshot() -> QrCodeResult:
749751
return scanned_qr[0]
750752

751753

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

0 commit comments

Comments
 (0)