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 .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']:

1
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,

2
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 {

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.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()

2
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

7
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:

4
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):

1
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:

2
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

7
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

2
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:

Loading…
Cancel
Save