diff --git a/electrum/bip39_recovery.py b/electrum/bip39_recovery.py index bf9fa5375..3b857b3eb 100644 --- a/electrum/bip39_recovery.py +++ b/electrum/bip39_recovery.py @@ -2,7 +2,7 @@ # Distributed under the MIT software license, see the accompanying # file LICENCE or http://www.opensource.org/licenses/mit-license.php -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional import itertools from . import bitcoin @@ -10,13 +10,15 @@ from .constants import BIP39_WALLET_FORMATS from .bip32 import BIP32_PRIME, BIP32Node from .bip32 import convert_bip32_strpath_to_intpath as bip32_str_to_ints from .bip32 import convert_bip32_intpath_to_strpath as bip32_ints_to_str -from .util import OldTaskGroup +from .util import OldTaskGroup, NetworkOfflineException if TYPE_CHECKING: from .network import Network -async def account_discovery(network: 'Network', get_account_xpub): +async def account_discovery(network: Optional['Network'], get_account_xpub): + if network is None: + raise NetworkOfflineException() async with OldTaskGroup() as group: account_scan_tasks = [] for wallet_format in BIP39_WALLET_FORMATS: diff --git a/electrum/gui/qml/qebip39recovery.py b/electrum/gui/qml/qebip39recovery.py index 90d1831f8..cb7d70ab5 100644 --- a/electrum/gui/qml/qebip39recovery.py +++ b/electrum/gui/qml/qebip39recovery.py @@ -9,6 +9,7 @@ from electrum import Network, keystore from electrum.bip32 import BIP32Node from electrum.bip39_recovery import account_discovery from electrum.logging import get_logger +from electrum.util import get_asyncio_loop from .util import TaskThread @@ -84,7 +85,7 @@ class QEBip39RecoveryListModel(QAbstractListModel): network = Network.get_instance() coro = account_discovery(network, self.get_account_xpub) self.state = QEBip39RecoveryListModel.State.Scanning - fut = asyncio.run_coroutine_threadsafe(coro, network.asyncio_loop) + fut = asyncio.run_coroutine_threadsafe(coro, get_asyncio_loop()) self._thread.add( fut.result, on_success=self.on_recovery_success, diff --git a/electrum/gui/qt/bip39_recovery_dialog.py b/electrum/gui/qt/bip39_recovery_dialog.py index 875eaaeb5..b833588b7 100644 --- a/electrum/gui/qt/bip39_recovery_dialog.py +++ b/electrum/gui/qt/bip39_recovery_dialog.py @@ -12,6 +12,7 @@ from electrum.i18n import _ from electrum.network import Network from electrum.bip39_recovery import account_discovery from electrum.logging import get_logger +from electrum.util import get_asyncio_loop, UserFacingException from .util import WindowModalDialog, MessageBoxMixin, TaskThread, Buttons, CancelButton, OkButton @@ -37,7 +38,7 @@ class Bip39RecoveryDialog(WindowModalDialog): self.thread.finished.connect(self.deleteLater) # see #3956 network = Network.get_instance() coro = account_discovery(network, self.get_account_xpub) - fut = asyncio.run_coroutine_threadsafe(coro, network.asyncio_loop) + fut = asyncio.run_coroutine_threadsafe(coro, get_asyncio_loop()) self.thread.add( fut.result, on_success=self.on_recovery_success, @@ -81,8 +82,12 @@ class Bip39RecoveryDialog(WindowModalDialog): if isinstance(e, concurrent.futures.CancelledError): return self.clear_content() - self.content.addWidget(QLabel(_('Error: Account discovery failed.'))) - _logger.error(f"recovery error", exc_info=exc_info) + msg = _('Error: Account discovery failed.') + if isinstance(e, UserFacingException): + msg += f"\n{e}" + else: + _logger.error(f"recovery error", exc_info=exc_info) + self.content.addWidget(QLabel(msg)) def clear_content(self): for i in reversed(range(self.content.count())): diff --git a/electrum/network.py b/electrum/network.py index e8b952a91..f91e6e1e4 100644 --- a/electrum/network.py +++ b/electrum/network.py @@ -407,6 +407,9 @@ class Network(Logger, NetworkRetryManager[ServerAddr]): @staticmethod def get_instance() -> Optional["Network"]: + """Return the global singleton network instance. + Note that this can return None! If we are run with the --offline flag, there is no network. + """ return _INSTANCE def with_recent_servers_lock(func): diff --git a/electrum/util.py b/electrum/util.py index dc6b28074..eb0a83ce4 100644 --- a/electrum/util.py +++ b/electrum/util.py @@ -204,6 +204,14 @@ class UserFacingException(Exception): class InvoiceError(UserFacingException): pass +class NetworkOfflineException(UserFacingException): + """Can be raised if we are running in offline mode (--offline flag) + and the user requests an operation that requires the network. + """ + def __str__(self): + return _("You are offline.") + + # Throw this exception to unwind the stack like when an error occurs. # However unlike other exceptions the user won't be informed. class UserCancelled(Exception):