Browse Source

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.
master
SomberNight 3 years ago
parent
commit
1530668960
No known key found for this signature in database
GPG Key ID: B33B5F232C6271E9
  1. 47
      electrum/daemon.py
  2. 1
      electrum/gui/kivy/__init__.py
  3. 2
      electrum/gui/qml/components/main.qml
  4. 7
      electrum/gui/qml/qedaemon.py
  5. 2
      electrum/gui/qml/qeserverlistmodel.py
  6. 7
      electrum/gui/qt/__init__.py
  7. 4
      electrum/gui/stdio.py
  8. 1
      electrum/gui/text.py
  9. 2
      electrum/lnwatcher.py
  10. 7
      electrum/network.py
  11. 2
      run_electrum

47
electrum/daemon.py

@ -53,7 +53,7 @@ from .simple_config import SimpleConfig
from .exchange_rate import FxThread from .exchange_rate import FxThread
from .logging import get_logger, Logger from .logging import get_logger, Logger
from . import GuiImportError from . import GuiImportError
from .plugin import run_hook from .plugin import run_hook, Plugins
if TYPE_CHECKING: if TYPE_CHECKING:
from electrum import gui from electrum import gui
@ -376,11 +376,19 @@ class WatchTowerServer(AuthenticatedServer):
class Daemon(Logger): class Daemon(Logger):
network: Optional[Network] network: Optional[Network] = None
gui_object: Optional['gui.BaseElectrumGui'] gui_object: Optional['gui.BaseElectrumGui'] = None
watchtower: Optional['WatchTowerServer'] = None
@profiler @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) Logger.__init__(self)
self.config = config self.config = config
self.listen_jsonrpc = listen_jsonrpc self.listen_jsonrpc = listen_jsonrpc
@ -392,11 +400,9 @@ class Daemon(Logger):
self.logger.warning("Ignoring parameter 'wallet_path' for daemon. " self.logger.warning("Ignoring parameter 'wallet_path' for daemon. "
"Use the load_wallet command instead.") "Use the load_wallet command instead.")
self.asyncio_loop = util.get_asyncio_loop() self.asyncio_loop = util.get_asyncio_loop()
self.network = None
if not config.get('offline'): if not config.get('offline'):
self.network = Network(config, daemon=self) self.network = Network(config, daemon=self)
self.fx = FxThread(config=config) self.fx = FxThread(config=config)
self.gui_object = None
# path -> wallet; make sure path is standardized. # path -> wallet; make sure path is standardized.
self._wallets = {} # type: Dict[str, Abstract_Wallet] self._wallets = {} # type: Dict[str, Abstract_Wallet]
self._wallet_lock = threading.RLock() self._wallet_lock = threading.RLock()
@ -406,23 +412,14 @@ class Daemon(Logger):
if listen_jsonrpc: if listen_jsonrpc:
self.commands_server = CommandsServer(self, fd) self.commands_server = CommandsServer(self, fd)
daemon_jobs.append(self.commands_server.run()) 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._stop_entered = False
self._stopping_soon_or_errored = threading.Event() self._stopping_soon_or_errored = threading.Event()
self._stopped_event = threading.Event() self._stopped_event = threading.Event()
self.taskgroup = OldTaskGroup() self.taskgroup = OldTaskGroup()
asyncio.run_coroutine_threadsafe(self._run(jobs=daemon_jobs), self.asyncio_loop) asyncio.run_coroutine_threadsafe(self._run(jobs=daemon_jobs), self.asyncio_loop)
if start_network and self.network:
self.start_network()
@log_exceptions @log_exceptions
async def _run(self, jobs: Iterable = None): 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). # not see the exception (especially if the GUI did not start yet).
self._stopping_soon_or_errored.set() 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 with_wallet_lock(func):
def func_wrapper(self: 'Daemon', *args, **kwargs): def func_wrapper(self: 'Daemon', *args, **kwargs):
with self._wallet_lock: with self._wallet_lock:
@ -566,7 +577,7 @@ class Daemon(Logger):
self.logger.info("stopped") self.logger.info("stopped")
self._stopped_event.set() self._stopped_event.set()
def run_gui(self, config, plugins): def run_gui(self, config: 'SimpleConfig', plugins: 'Plugins'):
threading.current_thread().name = 'GUI' threading.current_thread().name = 'GUI'
gui_name = config.get('gui', 'qt') gui_name = config.get('gui', 'qt')
if gui_name in ['lite', 'classic']: if gui_name in ['lite', 'classic']:

1
electrum/gui/kivy/__init__.py

@ -64,6 +64,7 @@ class ElectrumGui(BaseElectrumGui, Logger):
self.network = daemon.network self.network = daemon.network
def main(self): def main(self):
self.daemon.start_network()
from .main_window import ElectrumWindow from .main_window import ElectrumWindow
w = ElectrumWindow( w = ElectrumWindow(
config=self.config, config=self.config,

2
electrum/gui/qml/components/main.qml

@ -409,6 +409,7 @@ ApplicationWindow
Qt.callLater(Qt.quit) Qt.callLater(Qt.quit)
}) })
dialog.accepted.connect(function() { dialog.accepted.connect(function() {
Daemon.startNetwork()
var newww = app.newWalletWizard.createObject(app) var newww = app.newWalletWizard.createObject(app)
newww.walletCreated.connect(function() { newww.walletCreated.connect(function() {
Daemon.availableWallets.reload() Daemon.availableWallets.reload()
@ -419,6 +420,7 @@ ApplicationWindow
}) })
dialog.open() dialog.open()
} else { } else {
Daemon.startNetwork()
if (Daemon.availableWallets.rowCount() > 0) { if (Daemon.availableWallets.rowCount() > 0) {
Daemon.load_wallet() Daemon.load_wallet()
} else { } else {

7
electrum/gui/qml/qedaemon.py

@ -10,6 +10,7 @@ from electrum.util import WalletFileException, standardize_path
from electrum.wallet import Abstract_Wallet from electrum.wallet import Abstract_Wallet
from electrum.plugin import run_hook from electrum.plugin import run_hook
from electrum.lnchannel import ChannelState from electrum.lnchannel import ChannelState
from electrum.daemon import Daemon
from .auth import AuthMixin, auth_protect from .auth import AuthMixin, auth_protect
from .qefx import QEFX from .qefx import QEFX
@ -135,7 +136,7 @@ class QEDaemon(AuthMixin, QObject):
walletOpenError = pyqtSignal([str], arguments=["error"]) walletOpenError = pyqtSignal([str], arguments=["error"])
walletDeleteError = pyqtSignal([str,str], arguments=['code', 'message']) walletDeleteError = pyqtSignal([str,str], arguments=['code', 'message'])
def __init__(self, daemon, parent=None): def __init__(self, daemon: 'Daemon', parent=None):
super().__init__(parent) super().__init__(parent)
self.daemon = daemon self.daemon = daemon
self.qefx = QEFX(daemon.fx, daemon.config) self.qefx = QEFX(daemon.fx, daemon.config)
@ -336,3 +337,7 @@ class QEDaemon(AuthMixin, QObject):
self._server_connect_wizard = QEServerConnectWizard(self) self._server_connect_wizard = QEServerConnectWizard(self)
return self._server_connect_wizard return self._server_connect_wizard
@pyqtSlot()
def startNetwork(self):
self.daemon.start_network()

2
electrum/gui/qml/qeserverlistmodel.py

@ -108,7 +108,7 @@ class QEServerListModel(QAbstractListModel, QtEventListener):
server['address'] = i.server.to_friendly_name() server['address'] = i.server.to_friendly_name()
server['height'] = i.tip server['height'] = i.tip
self._logger.debug(f'adding server: {repr(server)}') #self._logger.debug(f'adding server: {repr(server)}')
servers.append(server) servers.append(server)
# disconnected servers # disconnected servers

7
electrum/gui/qt/__init__.py

@ -428,12 +428,15 @@ class ElectrumGui(BaseElectrumGui, Logger):
self.daemon.stop_wallet(window.wallet.storage.path) self.daemon.stop_wallet(window.wallet.storage.path)
def init_network(self): 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: if self.daemon.network:
# first-start network-setup
if self.config.get('auto_connect') is None: if self.config.get('auto_connect') is None:
wizard = InstallWizard(self.config, self.app, self.plugins, gui_object=self) wizard = InstallWizard(self.config, self.app, self.plugins, gui_object=self)
wizard.init_network(self.daemon.network) wizard.init_network(self.daemon.network)
wizard.terminate() wizard.terminate()
# start network
self.daemon.start_network()
def main(self): def main(self):
# setup Ctrl-C handling and tear-down code first, so that user can easily exit whenever # 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()) signal.signal(signal.SIGINT, lambda *args: self.app.quit())
# hook for crash reporter # hook for crash reporter
Exception_Hook.maybe_setup(config=self.config) Exception_Hook.maybe_setup(config=self.config)
# first-start network-setup # start network, and maybe show first-start network-setup
try: try:
self.init_network() self.init_network()
except UserCancelled: except UserCancelled:

4
electrum/gui/stdio.py

@ -177,7 +177,9 @@ class ElectrumGui(BaseElectrumGui, EventListener):
def main(self): 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): def do_send(self):
if not is_address(self.str_recipient): if not is_address(self.str_recipient):

1
electrum/gui/text.py

@ -517,6 +517,7 @@ class ElectrumGui(BaseElectrumGui, EventListener):
pass pass
def main(self): def main(self):
self.daemon.start_network()
tty.setraw(sys.stdin) tty.setraw(sys.stdin)
try: try:
while self.tab != -1: while self.tab != -1:

2
electrum/lnwatcher.py

@ -141,7 +141,7 @@ class LNWatcher(Logger, EventListener):
LOGGING_SHORTCUT = 'W' LOGGING_SHORTCUT = 'W'
def __init__(self, adb, network: 'Network'): def __init__(self, adb: 'AddressSynchronizer', network: 'Network'):
Logger.__init__(self) Logger.__init__(self)
self.adb = adb self.adb = adb

7
electrum/network.py

@ -331,6 +331,7 @@ class Network(Logger, NetworkRetryManager[ServerAddr]):
self._set_status('disconnected') self._set_status('disconnected')
self._has_ever_managed_to_connect_to_server = False self._has_ever_managed_to_connect_to_server = False
self._was_started = False
# lightning network # lightning network
if self.config.get('run_watchtower', False): if self.config.get('run_watchtower', False):
@ -647,6 +648,8 @@ class Network(Logger, NetworkRetryManager[ServerAddr]):
oneserver_changed = self.oneserver != net_params.oneserver oneserver_changed = self.oneserver != net_params.oneserver
default_server_changed = self.default_server != server default_server_changed = self.default_server != server
self._init_parameters_from_config() self._init_parameters_from_config()
if not self._was_started:
return
async with self.restart_lock: async with self.restart_lock:
if proxy_changed or oneserver_changed: 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 Note: the jobs will *restart* every time the network restarts, e.g. on proxy
setting changes. setting changes.
""" """
self._was_started = True
self._jobs = jobs or [] self._jobs = jobs or []
asyncio.run_coroutine_threadsafe(self._start(), self.asyncio_loop) asyncio.run_coroutine_threadsafe(self._start(), self.asyncio_loop)
@log_exceptions @log_exceptions
async def stop(self, *, full_shutdown: bool = True): 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") self.logger.info("stopping network")
# timeout: if full_shutdown, it is up to the caller to time us out, # 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 # otherwise if e.g. restarting due to proxy changes, we time out fast

2
run_electrum

@ -434,7 +434,7 @@ def handle_cmd(*, cmdname: str, config: 'SimpleConfig', config_options: dict):
fd = daemon.get_file_descriptor(config) fd = daemon.get_file_descriptor(config)
if fd is not None: if fd is not None:
plugins = init_plugins(config, config.get('gui', 'qt')) plugins = init_plugins(config, config.get('gui', 'qt'))
d = daemon.Daemon(config, fd) d = daemon.Daemon(config, fd, start_network=False)
try: try:
d.run_gui(config, plugins) d.run_gui(config, plugins)
except BaseException as e: except BaseException as e:

Loading…
Cancel
Save