qt wizard bip39 recovery: better handle --offline mode
```
32.40 | E | gui.qt.exception_window.Exception_Hook | exception caught by crash reporter
Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/gui/qt/wizard/wallet.py", line 709, in <lambda>
button.clicked.connect(lambda: Bip39RecoveryDialog(self, get_account_xpub, on_account_select))
File "/home/user/wspace/electrum/electrum/gui/qt/bip39_recovery_dialog.py", line 40, in __init__
fut = asyncio.run_coroutine_threadsafe(coro, network.asyncio_loop)
AttributeError: 'NoneType' object has no attribute 'asyncio_loop'
```
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENCE or http://www.opensource.org/licenses/mit-license.php
|
# file LICENCE or http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING, Optional
|
||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
from . import bitcoin
|
from . import bitcoin
|
||||||
@@ -10,13 +10,15 @@ from .constants import BIP39_WALLET_FORMATS
|
|||||||
from .bip32 import BIP32_PRIME, BIP32Node
|
from .bip32 import BIP32_PRIME, BIP32Node
|
||||||
from .bip32 import convert_bip32_strpath_to_intpath as bip32_str_to_ints
|
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 .bip32 import convert_bip32_intpath_to_strpath as bip32_ints_to_str
|
||||||
from .util import OldTaskGroup
|
from .util import OldTaskGroup, NetworkOfflineException
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .network import Network
|
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:
|
async with OldTaskGroup() as group:
|
||||||
account_scan_tasks = []
|
account_scan_tasks = []
|
||||||
for wallet_format in BIP39_WALLET_FORMATS:
|
for wallet_format in BIP39_WALLET_FORMATS:
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from electrum import Network, keystore
|
|||||||
from electrum.bip32 import BIP32Node
|
from electrum.bip32 import BIP32Node
|
||||||
from electrum.bip39_recovery import account_discovery
|
from electrum.bip39_recovery import account_discovery
|
||||||
from electrum.logging import get_logger
|
from electrum.logging import get_logger
|
||||||
|
from electrum.util import get_asyncio_loop
|
||||||
|
|
||||||
from .util import TaskThread
|
from .util import TaskThread
|
||||||
|
|
||||||
@@ -84,7 +85,7 @@ class QEBip39RecoveryListModel(QAbstractListModel):
|
|||||||
network = Network.get_instance()
|
network = Network.get_instance()
|
||||||
coro = account_discovery(network, self.get_account_xpub)
|
coro = account_discovery(network, self.get_account_xpub)
|
||||||
self.state = QEBip39RecoveryListModel.State.Scanning
|
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(
|
self._thread.add(
|
||||||
fut.result,
|
fut.result,
|
||||||
on_success=self.on_recovery_success,
|
on_success=self.on_recovery_success,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from electrum.i18n import _
|
|||||||
from electrum.network import Network
|
from electrum.network import Network
|
||||||
from electrum.bip39_recovery import account_discovery
|
from electrum.bip39_recovery import account_discovery
|
||||||
from electrum.logging import get_logger
|
from electrum.logging import get_logger
|
||||||
|
from electrum.util import get_asyncio_loop, UserFacingException
|
||||||
|
|
||||||
from .util import WindowModalDialog, MessageBoxMixin, TaskThread, Buttons, CancelButton, OkButton
|
from .util import WindowModalDialog, MessageBoxMixin, TaskThread, Buttons, CancelButton, OkButton
|
||||||
|
|
||||||
@@ -37,7 +38,7 @@ class Bip39RecoveryDialog(WindowModalDialog):
|
|||||||
self.thread.finished.connect(self.deleteLater) # see #3956
|
self.thread.finished.connect(self.deleteLater) # see #3956
|
||||||
network = Network.get_instance()
|
network = Network.get_instance()
|
||||||
coro = account_discovery(network, self.get_account_xpub)
|
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(
|
self.thread.add(
|
||||||
fut.result,
|
fut.result,
|
||||||
on_success=self.on_recovery_success,
|
on_success=self.on_recovery_success,
|
||||||
@@ -81,8 +82,12 @@ class Bip39RecoveryDialog(WindowModalDialog):
|
|||||||
if isinstance(e, concurrent.futures.CancelledError):
|
if isinstance(e, concurrent.futures.CancelledError):
|
||||||
return
|
return
|
||||||
self.clear_content()
|
self.clear_content()
|
||||||
self.content.addWidget(QLabel(_('Error: Account discovery failed.')))
|
msg = _('Error: Account discovery failed.')
|
||||||
_logger.error(f"recovery error", exc_info=exc_info)
|
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):
|
def clear_content(self):
|
||||||
for i in reversed(range(self.content.count())):
|
for i in reversed(range(self.content.count())):
|
||||||
|
|||||||
@@ -407,6 +407,9 @@ class Network(Logger, NetworkRetryManager[ServerAddr]):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_instance() -> Optional["Network"]:
|
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
|
return _INSTANCE
|
||||||
|
|
||||||
def with_recent_servers_lock(func):
|
def with_recent_servers_lock(func):
|
||||||
|
|||||||
@@ -204,6 +204,14 @@ class UserFacingException(Exception):
|
|||||||
class InvoiceError(UserFacingException): pass
|
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.
|
# Throw this exception to unwind the stack like when an error occurs.
|
||||||
# However unlike other exceptions the user won't be informed.
|
# However unlike other exceptions the user won't be informed.
|
||||||
class UserCancelled(Exception):
|
class UserCancelled(Exception):
|
||||||
|
|||||||
Reference in New Issue
Block a user