From 15306689607a6aca298d9b4343377469e226a9f3 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Wed, 29 Mar 2023 22:09:46 +0000 Subject: [PATCH] qt/qml: delay starting network until after first-start-network-setup The qt, qml, and kivy GUIs have a first-start network-setup screen that allows the user customising the network settings before creating a wallet. Previously the daemon used to create the network and start it, before this screen, before the GUI even starts. If the user changed network settings, those would be set on the already running network, potentially including restarting the network. Now it becomes the responsibility of the GUI to start the network, allowing this first-start customisation to take place before starting the network at all. The qt and the qml GUIs are adapted to make use of this. Kivy, and the other prototype GUIs are not adapted and just start the network right away, as before. --- electrum/daemon.py | 47 +++++++++++++++++---------- electrum/gui/kivy/__init__.py | 1 + electrum/gui/qml/components/main.qml | 2 ++ electrum/gui/qml/qedaemon.py | 7 +++- electrum/gui/qml/qeserverlistmodel.py | 2 +- electrum/gui/qt/__init__.py | 7 ++-- electrum/gui/stdio.py | 4 ++- electrum/gui/text.py | 1 + electrum/lnwatcher.py | 2 +- electrum/network.py | 7 ++++ run_electrum | 2 +- 11 files changed, 57 insertions(+), 25 deletions(-) diff --git a/electrum/daemon.py b/electrum/daemon.py index 6c87fee07..2a10b106a 100644 --- a/electrum/daemon.py +++ b/electrum/daemon.py @@ -53,7 +53,7 @@ from .simple_config import SimpleConfig from .exchange_rate import FxThread from .logging import get_logger, Logger from . import GuiImportError -from .plugin import run_hook +from .plugin import run_hook, Plugins if TYPE_CHECKING: from electrum import gui @@ -376,11 +376,19 @@ class WatchTowerServer(AuthenticatedServer): class Daemon(Logger): - network: Optional[Network] - gui_object: Optional['gui.BaseElectrumGui'] + network: Optional[Network] = None + gui_object: Optional['gui.BaseElectrumGui'] = None + watchtower: Optional['WatchTowerServer'] = None @profiler - def __init__(self, config: SimpleConfig, fd=None, *, listen_jsonrpc=True): + def __init__( + self, + config: SimpleConfig, + fd=None, + *, + listen_jsonrpc: bool = True, + start_network: bool = True, # setting to False allows customising network settings before starting it + ): Logger.__init__(self) self.config = config self.listen_jsonrpc = listen_jsonrpc @@ -392,11 +400,9 @@ class Daemon(Logger): self.logger.warning("Ignoring parameter 'wallet_path' for daemon. " "Use the load_wallet command instead.") self.asyncio_loop = util.get_asyncio_loop() - self.network = None if not config.get('offline'): self.network = Network(config, daemon=self) self.fx = FxThread(config=config) - self.gui_object = None # path -> wallet; make sure path is standardized. self._wallets = {} # type: Dict[str, Abstract_Wallet] self._wallet_lock = threading.RLock() @@ -406,23 +412,14 @@ class Daemon(Logger): if listen_jsonrpc: self.commands_server = CommandsServer(self, fd) daemon_jobs.append(self.commands_server.run()) - # server-side watchtower - self.watchtower = None - watchtower_address = self.config.get_netaddress('watchtower_address') - if not config.get('offline') and watchtower_address: - self.watchtower = WatchTowerServer(self.network, watchtower_address) - daemon_jobs.append(self.watchtower.run) - if self.network: - self.network.start(jobs=[self.fx.run]) - # prepare lightning functionality, also load channel db early - if self.config.get('use_gossip', False): - self.network.start_gossip() self._stop_entered = False self._stopping_soon_or_errored = threading.Event() self._stopped_event = threading.Event() self.taskgroup = OldTaskGroup() asyncio.run_coroutine_threadsafe(self._run(jobs=daemon_jobs), self.asyncio_loop) + if start_network and self.network: + self.start_network() @log_exceptions async def _run(self, jobs: Iterable = None): @@ -442,6 +439,20 @@ class Daemon(Logger): # not see the exception (especially if the GUI did not start yet). self._stopping_soon_or_errored.set() + def start_network(self): + self.logger.info(f"starting network.") + assert not self.config.get('offline') + assert self.network + # server-side watchtower + if watchtower_address := self.config.get_netaddress('watchtower_address'): + self.watchtower = WatchTowerServer(self.network, watchtower_address) + asyncio.run_coroutine_threadsafe(self.taskgroup.spawn(self.watchtower.run), self.asyncio_loop) + + self.network.start(jobs=[self.fx.run]) + # prepare lightning functionality, also load channel db early + if self.config.get('use_gossip', False): + self.network.start_gossip() + def with_wallet_lock(func): def func_wrapper(self: 'Daemon', *args, **kwargs): with self._wallet_lock: @@ -566,7 +577,7 @@ class Daemon(Logger): self.logger.info("stopped") self._stopped_event.set() - def run_gui(self, config, plugins): + def run_gui(self, config: 'SimpleConfig', plugins: 'Plugins'): threading.current_thread().name = 'GUI' gui_name = config.get('gui', 'qt') if gui_name in ['lite', 'classic']: diff --git a/electrum/gui/kivy/__init__.py b/electrum/gui/kivy/__init__.py index b7c9249a6..da2fd2b76 100644 --- a/electrum/gui/kivy/__init__.py +++ b/electrum/gui/kivy/__init__.py @@ -64,6 +64,7 @@ class ElectrumGui(BaseElectrumGui, Logger): self.network = daemon.network def main(self): + self.daemon.start_network() from .main_window import ElectrumWindow w = ElectrumWindow( config=self.config, diff --git a/electrum/gui/qml/components/main.qml b/electrum/gui/qml/components/main.qml index 461491b35..7baaf45ee 100644 --- a/electrum/gui/qml/components/main.qml +++ b/electrum/gui/qml/components/main.qml @@ -409,6 +409,7 @@ ApplicationWindow Qt.callLater(Qt.quit) }) dialog.accepted.connect(function() { + Daemon.startNetwork() var newww = app.newWalletWizard.createObject(app) newww.walletCreated.connect(function() { Daemon.availableWallets.reload() @@ -419,6 +420,7 @@ ApplicationWindow }) dialog.open() } else { + Daemon.startNetwork() if (Daemon.availableWallets.rowCount() > 0) { Daemon.load_wallet() } else { diff --git a/electrum/gui/qml/qedaemon.py b/electrum/gui/qml/qedaemon.py index 569b24ad8..de959e931 100644 --- a/electrum/gui/qml/qedaemon.py +++ b/electrum/gui/qml/qedaemon.py @@ -10,6 +10,7 @@ from electrum.util import WalletFileException, standardize_path from electrum.wallet import Abstract_Wallet from electrum.plugin import run_hook from electrum.lnchannel import ChannelState +from electrum.daemon import Daemon from .auth import AuthMixin, auth_protect from .qefx import QEFX @@ -135,7 +136,7 @@ class QEDaemon(AuthMixin, QObject): walletOpenError = pyqtSignal([str], arguments=["error"]) walletDeleteError = pyqtSignal([str,str], arguments=['code', 'message']) - def __init__(self, daemon, parent=None): + def __init__(self, daemon: 'Daemon', parent=None): super().__init__(parent) self.daemon = daemon self.qefx = QEFX(daemon.fx, daemon.config) @@ -336,3 +337,7 @@ class QEDaemon(AuthMixin, QObject): self._server_connect_wizard = QEServerConnectWizard(self) return self._server_connect_wizard + + @pyqtSlot() + def startNetwork(self): + self.daemon.start_network() diff --git a/electrum/gui/qml/qeserverlistmodel.py b/electrum/gui/qml/qeserverlistmodel.py index d839b1b79..97a635b85 100644 --- a/electrum/gui/qml/qeserverlistmodel.py +++ b/electrum/gui/qml/qeserverlistmodel.py @@ -108,7 +108,7 @@ class QEServerListModel(QAbstractListModel, QtEventListener): server['address'] = i.server.to_friendly_name() server['height'] = i.tip - self._logger.debug(f'adding server: {repr(server)}') + #self._logger.debug(f'adding server: {repr(server)}') servers.append(server) # disconnected servers diff --git a/electrum/gui/qt/__init__.py b/electrum/gui/qt/__init__.py index 73ba81e53..890475fe0 100644 --- a/electrum/gui/qt/__init__.py +++ b/electrum/gui/qt/__init__.py @@ -428,12 +428,15 @@ class ElectrumGui(BaseElectrumGui, Logger): self.daemon.stop_wallet(window.wallet.storage.path) def init_network(self): - # Show network dialog if config does not exist + """Start the network, including showing a first-start network dialog if config does not exist.""" if self.daemon.network: + # first-start network-setup if self.config.get('auto_connect') is None: wizard = InstallWizard(self.config, self.app, self.plugins, gui_object=self) wizard.init_network(self.daemon.network) wizard.terminate() + # start network + self.daemon.start_network() def main(self): # setup Ctrl-C handling and tear-down code first, so that user can easily exit whenever @@ -443,7 +446,7 @@ class ElectrumGui(BaseElectrumGui, Logger): signal.signal(signal.SIGINT, lambda *args: self.app.quit()) # hook for crash reporter Exception_Hook.maybe_setup(config=self.config) - # first-start network-setup + # start network, and maybe show first-start network-setup try: self.init_network() except UserCancelled: diff --git a/electrum/gui/stdio.py b/electrum/gui/stdio.py index ef77a8bf5..f8d05af45 100644 --- a/electrum/gui/stdio.py +++ b/electrum/gui/stdio.py @@ -177,7 +177,9 @@ class ElectrumGui(BaseElectrumGui, EventListener): def main(self): - while self.done == 0: self.main_command() + self.daemon.start_network() + while self.done == 0: + self.main_command() def do_send(self): if not is_address(self.str_recipient): diff --git a/electrum/gui/text.py b/electrum/gui/text.py index 4f08d18fc..22f6361e1 100644 --- a/electrum/gui/text.py +++ b/electrum/gui/text.py @@ -517,6 +517,7 @@ class ElectrumGui(BaseElectrumGui, EventListener): pass def main(self): + self.daemon.start_network() tty.setraw(sys.stdin) try: while self.tab != -1: diff --git a/electrum/lnwatcher.py b/electrum/lnwatcher.py index 109490df2..51b741b42 100644 --- a/electrum/lnwatcher.py +++ b/electrum/lnwatcher.py @@ -141,7 +141,7 @@ class LNWatcher(Logger, EventListener): LOGGING_SHORTCUT = 'W' - def __init__(self, adb, network: 'Network'): + def __init__(self, adb: 'AddressSynchronizer', network: 'Network'): Logger.__init__(self) self.adb = adb diff --git a/electrum/network.py b/electrum/network.py index 9a6fb84d5..71467d048 100644 --- a/electrum/network.py +++ b/electrum/network.py @@ -331,6 +331,7 @@ class Network(Logger, NetworkRetryManager[ServerAddr]): self._set_status('disconnected') self._has_ever_managed_to_connect_to_server = False + self._was_started = False # lightning network if self.config.get('run_watchtower', False): @@ -647,6 +648,8 @@ class Network(Logger, NetworkRetryManager[ServerAddr]): oneserver_changed = self.oneserver != net_params.oneserver default_server_changed = self.default_server != server self._init_parameters_from_config() + if not self._was_started: + return async with self.restart_lock: if proxy_changed or oneserver_changed: @@ -1268,11 +1271,15 @@ class Network(Logger, NetworkRetryManager[ServerAddr]): Note: the jobs will *restart* every time the network restarts, e.g. on proxy setting changes. """ + self._was_started = True self._jobs = jobs or [] asyncio.run_coroutine_threadsafe(self._start(), self.asyncio_loop) @log_exceptions async def stop(self, *, full_shutdown: bool = True): + if not self._was_started: + self.logger.info("not stopping network as it was never started") + return self.logger.info("stopping network") # timeout: if full_shutdown, it is up to the caller to time us out, # otherwise if e.g. restarting due to proxy changes, we time out fast diff --git a/run_electrum b/run_electrum index fb27bf45a..c587d7bed 100755 --- a/run_electrum +++ b/run_electrum @@ -434,7 +434,7 @@ def handle_cmd(*, cmdname: str, config: 'SimpleConfig', config_options: dict): fd = daemon.get_file_descriptor(config) if fd is not None: plugins = init_plugins(config, config.get('gui', 'qt')) - d = daemon.Daemon(config, fd) + d = daemon.Daemon(config, fd, start_network=False) try: d.run_gui(config, plugins) except BaseException as e: