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: