From 1dfe2ccec03c01f0900dc589ecb151cfbe861590 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Thu, 10 Oct 2024 10:05:38 +0200 Subject: [PATCH 1/3] qml: show option for single server in ServerConfig --- .../gui/qml/components/ServerConfigDialog.qml | 3 +++ .../qml/components/controls/ServerConfig.qml | 20 ++++++++++++++++++- .../qml/components/wizard/WCServerConfig.qml | 1 + electrum/gui/qml/qenetwork.py | 12 +++++++++++ electrum/gui/qt/wizard/server_connect.py | 1 + electrum/wizard.py | 5 ++++- 6 files changed, 40 insertions(+), 2 deletions(-) diff --git a/electrum/gui/qml/components/ServerConfigDialog.qml b/electrum/gui/qml/components/ServerConfigDialog.qml index d5304e064..2d779b97b 100644 --- a/electrum/gui/qml/components/ServerConfigDialog.qml +++ b/electrum/gui/qml/components/ServerConfigDialog.qml @@ -44,6 +44,9 @@ ElDialog { onClicked: { Config.autoConnect = serverconfig.auto_connect Network.server = serverconfig.address + Network.oneServer = serverconfig.auto_connect + ? false + : serverconfig.one_server rootItem.close() } } diff --git a/electrum/gui/qml/components/controls/ServerConfig.qml b/electrum/gui/qml/components/controls/ServerConfig.qml index 9c7160489..12227d03b 100644 --- a/electrum/gui/qml/components/controls/ServerConfig.qml +++ b/electrum/gui/qml/components/controls/ServerConfig.qml @@ -12,6 +12,7 @@ Item { property bool showAutoselectServer: true property alias auto_connect: auto_server_cb.checked property alias address: address_tf.text + property alias one_server: one_server_cb.checked implicitHeight: rootLayout.height @@ -26,7 +27,7 @@ Item { id: auto_server_cb visible: showAutoselectServer text: qsTr('Select server automatically') - checked: true + checked: !showAutoselectServer } Label { @@ -45,6 +46,22 @@ Item { } } + RowLayout { + Layout.fillWidth: true + visible: !auto_server_cb.checked && address_tf.text + + CheckBox { + id: one_server_cb + Layout.fillWidth: true + text: qsTr('One server') + } + + HelpButton { + heading: qsTr('One server') + helptext: qsTr('Connect only to a single Electrum Server. This can help with privacy, but at the cost of detecting lagging and forks') + } + } + ColumnLayout { Heading { text: qsTr('Servers') @@ -96,6 +113,7 @@ Item { Component.onCompleted: { root.auto_connect = Config.autoConnectDefined ? Config.autoConnect : false root.address = Network.server + one_server_cb.checked = Network.oneServer // TODO: initial setup should not connect already, is Network.server defined? } } diff --git a/electrum/gui/qml/components/wizard/WCServerConfig.qml b/electrum/gui/qml/components/wizard/WCServerConfig.qml index df114f84a..a48a1bd70 100644 --- a/electrum/gui/qml/components/wizard/WCServerConfig.qml +++ b/electrum/gui/qml/components/wizard/WCServerConfig.qml @@ -12,6 +12,7 @@ WizardComponent { function apply() { wizard_data['autoconnect'] = sc.address.trim() == "" wizard_data['server'] = sc.address + wizard_data['one_server'] = sc.one_server } ColumnLayout { diff --git a/electrum/gui/qml/qenetwork.py b/electrum/gui/qml/qenetwork.py index 25dc1c24f..a8074647e 100644 --- a/electrum/gui/qml/qenetwork.py +++ b/electrum/gui/qml/qenetwork.py @@ -254,6 +254,18 @@ class QENetwork(QObject, QtEventListener): def isProxyTor(self): return bool(self.network.is_proxy_tor) + @pyqtProperty(bool, notify=statusChanged) + def oneServer(self): + return self.network.oneserver + + @oneServer.setter + def oneServer(self, one_server: bool): + if one_server != self.network.oneserver: + net_params = self.network.get_parameters() + net_params = net_params._replace(oneserver=one_server) + self.network.run_from_another_thread(self.network.set_parameters(net_params)) + self.statusChanged.emit() + @pyqtProperty('QVariant', notify=feeHistogramUpdated) def feeHistogram(self): return self._fee_histogram diff --git a/electrum/gui/qt/wizard/server_connect.py b/electrum/gui/qt/wizard/server_connect.py index cebe6f146..0c6b88b36 100644 --- a/electrum/gui/qt/wizard/server_connect.py +++ b/electrum/gui/qt/wizard/server_connect.py @@ -96,3 +96,4 @@ class WCServerConfig(WizardComponent): def apply(self): self.wizard_data['autoconnect'] = self.sw.server_e.text().strip() == '' self.wizard_data['server'] = self.sw.server_e.text() + self.wizard_data['one_server'] = False diff --git a/electrum/wizard.py b/electrum/wizard.py index c5e748013..0fae46537 100644 --- a/electrum/wizard.py +++ b/electrum/wizard.py @@ -745,6 +745,7 @@ class ServerConnectWizard(AbstractWizard): self._logger.debug(f'configuring server: {wizard_data!r}') net_params = self._daemon.network.get_parameters() server = '' + oneserver = wizard_data.get('one_server', False) if not wizard_data['autoconnect']: try: server = ServerAddr.from_str_with_inference(wizard_data['server']) @@ -752,7 +753,9 @@ class ServerConnectWizard(AbstractWizard): raise Exception('failed to parse server %s' % wizard_data['server']) except Exception: return - net_params = net_params._replace(server=server, auto_connect=wizard_data['autoconnect']) + else: + oneserver = False + net_params = net_params._replace(server=server, auto_connect=wizard_data['autoconnect'], oneserver=oneserver) self._daemon.network.run_from_another_thread(self._daemon.network.set_parameters(net_params)) def do_configure_autoconnect(self, wizard_data: dict): From 82e3932aafff6493808f12a05afb6bbd9f142d9d Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Fri, 11 Oct 2024 10:35:46 +0200 Subject: [PATCH 2/3] network: add oneserver/auto_connect both enabled checks, avoid connecting to random/multiple servers if oneserver is enabled. --- electrum/network.py | 14 +++++++++++++- electrum/wizard.py | 2 -- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/electrum/network.py b/electrum/network.py index 1acc7e7f9..4a5243744 100644 --- a/electrum/network.py +++ b/electrum/network.py @@ -601,6 +601,13 @@ class Network(Logger, NetworkRetryManager[ServerAddr]): def _init_parameters_from_config(self) -> None: dns_hacks.configure_dns_resolver() self.auto_connect = self.config.NETWORK_AUTO_CONNECT + if self.auto_connect and self.config.NETWORK_ONESERVER: + # enabling both oneserver and auto_connect doesn't really make sense + # assume oneserver is enabled for privacy reasons, disable auto_connect and assume server is unpredictable + self.logger.warning(f'both "oneserver" and "auto_connect" options enabled, disabling "auto_connect" and resetting "server".') + self.config.NETWORK_SERVER = "" # let _set_default_server set harmless default (localhost) + self.auto_connect = False + self._set_default_server() self._set_proxy(ProxySettings.from_config(self.config)) self._maybe_set_oneserver() @@ -722,7 +729,12 @@ class Network(Logger, NetworkRetryManager[ServerAddr]): self.logger.warning(f'failed to parse server-string ({server!r}); falling back to localhost:1:s.') self.default_server = ServerAddr.from_str("localhost:1:s") else: - self.default_server = pick_random_server(allowed_protocols=self._allowed_protocols) + # if oneserver is enabled but no server specified then don't pick a random server + if self.config.NETWORK_ONESERVER: + self.logger.warning(f'"oneserver" option enabled, but no "server" defined; falling back to localhost:1:s.') + self.default_server = ServerAddr.from_str("localhost:1:s") + else: + self.default_server = pick_random_server(allowed_protocols=self._allowed_protocols) assert isinstance(self.default_server, ServerAddr), f"invalid type for default_server: {self.default_server!r}" def _set_proxy(self, proxy: ProxySettings): diff --git a/electrum/wizard.py b/electrum/wizard.py index 0fae46537..9f9dc7676 100644 --- a/electrum/wizard.py +++ b/electrum/wizard.py @@ -753,8 +753,6 @@ class ServerConnectWizard(AbstractWizard): raise Exception('failed to parse server %s' % wizard_data['server']) except Exception: return - else: - oneserver = False net_params = net_params._replace(server=server, auto_connect=wizard_data['autoconnect'], oneserver=oneserver) self._daemon.network.run_from_another_thread(self._daemon.network.set_parameters(net_params)) From 705f9278994da0f26afb00950f9cd6b115a5d0ad Mon Sep 17 00:00:00 2001 From: accumulator Date: Fri, 18 Apr 2025 12:34:52 +0200 Subject: [PATCH 3/3] qt: show option for single server in network dialog --- .../gui/qml/components/ServerConfigDialog.qml | 4 +-- .../qml/components/controls/ServerConfig.qml | 3 +- electrum/gui/qt/network_dialog.py | 36 +++++++++++++------ electrum/gui/qt/wizard/server_connect.py | 2 +- electrum/simple_config.py | 7 +++- tests/test_wizard.py | 2 +- 6 files changed, 38 insertions(+), 16 deletions(-) diff --git a/electrum/gui/qml/components/ServerConfigDialog.qml b/electrum/gui/qml/components/ServerConfigDialog.qml index 2d779b97b..b9679546e 100644 --- a/electrum/gui/qml/components/ServerConfigDialog.qml +++ b/electrum/gui/qml/components/ServerConfigDialog.qml @@ -42,11 +42,11 @@ ElDialog { text: qsTr('Ok') icon.source: '../../icons/confirmed.png' onClicked: { - Config.autoConnect = serverconfig.auto_connect - Network.server = serverconfig.address Network.oneServer = serverconfig.auto_connect ? false : serverconfig.one_server + Config.autoConnect = serverconfig.auto_connect + Network.server = serverconfig.address rootItem.close() } } diff --git a/electrum/gui/qml/components/controls/ServerConfig.qml b/electrum/gui/qml/components/controls/ServerConfig.qml index 12227d03b..c2704e89a 100644 --- a/electrum/gui/qml/components/controls/ServerConfig.qml +++ b/electrum/gui/qml/components/controls/ServerConfig.qml @@ -28,6 +28,7 @@ Item { visible: showAutoselectServer text: qsTr('Select server automatically') checked: !showAutoselectServer + enabled: !one_server_cb.checked } Label { @@ -58,7 +59,7 @@ Item { HelpButton { heading: qsTr('One server') - helptext: qsTr('Connect only to a single Electrum Server. This can help with privacy, but at the cost of detecting lagging and forks') + helptext: Config.longDescFor('NETWORK_ONESERVER') } } diff --git a/electrum/gui/qt/network_dialog.py b/electrum/gui/qt/network_dialog.py index e4d31f23f..fe99e3887 100644 --- a/electrum/gui/qt/network_dialog.py +++ b/electrum/gui/qt/network_dialog.py @@ -394,23 +394,29 @@ class ServerWidget(QWidget, QtEventListener): grid.addWidget(self.autoconnect_cb, 1, 0, 1, 3) grid.addWidget(HelpButton(msg), 1, 4) + self.one_server_cb = QCheckBox(_('One server')) + self.one_server_cb.setEnabled(self.config.cv.NETWORK_ONESERVER.is_modifiable()) + self.one_server_cb.stateChanged.connect(self.on_server_settings_changed) + grid.addWidget(self.one_server_cb, 2, 0, 1, 3) + grid.addWidget(HelpButton(self.config.cv.NETWORK_ONESERVER.get_long_desc()), 2, 4) + self.server_e = QLineEdit() self.server_e.editingFinished.connect(self.on_server_settings_changed) msg = _("Electrum sends your wallet addresses to a single server, in order to receive your transaction history.") - grid.addWidget(QLabel(_('Server') + ':'), 2, 0) - grid.addWidget(self.server_e, 2, 1, 1, 3) - grid.addWidget(HelpButton(msg), 2, 4) + grid.addWidget(QLabel(_('Server') + ':'), 3, 0) + grid.addWidget(self.server_e, 3, 1, 1, 3) + grid.addWidget(HelpButton(msg), 3, 4) msg = _('This is the height of your local copy of the blockchain.') self.height_label_header = QLabel(_('Blockchain') + ':') self.height_label = QLabel('') self.height_label_helpbutton = HelpButton(msg) - grid.addWidget(self.height_label_header, 3, 0) - grid.addWidget(self.height_label, 3, 1) - grid.addWidget(self.height_label_helpbutton, 3, 4) + grid.addWidget(self.height_label_header, 4, 0) + grid.addWidget(self.height_label, 4, 1) + grid.addWidget(self.height_label_helpbutton, 4, 4) self.split_label = QLabel('') - grid.addWidget(self.split_label, 4, 0, 1, 3) + grid.addWidget(self.split_label, 5, 0, 1, 3) self.layout().addLayout(grid) @@ -442,13 +448,19 @@ class ServerWidget(QWidget, QtEventListener): self.update() return auto_connect = self.autoconnect_cb.isChecked() + one_server = self.one_server_cb.isChecked() + self.autoconnect_cb.setEnabled(not one_server and self.config.cv.NETWORK_AUTO_CONNECT.is_modifiable()) + self.one_server_cb.setEnabled(not auto_connect and self.config.cv.NETWORK_ONESERVER.is_modifiable()) server = self.server_e.text().strip() net_params = self.network.get_parameters() - if server != net_params.server or auto_connect != net_params.auto_connect: + if server != net_params.server or auto_connect != net_params.auto_connect or one_server != net_params.oneserver: self.set_server() def update(self): auto_connect = self.autoconnect_cb.isChecked() + one_server = self.one_server_cb.isChecked() + self.autoconnect_cb.setEnabled(not one_server and self.config.cv.NETWORK_AUTO_CONNECT.is_modifiable()) + self.one_server_cb.setEnabled(not auto_connect and self.config.cv.NETWORK_ONESERVER.is_modifiable()) self.server_e.setEnabled(self.config.cv.NETWORK_SERVER.is_modifiable() and not auto_connect) for item in [ @@ -479,10 +491,13 @@ class ServerWidget(QWidget, QtEventListener): def update_from_config(self): auto_connect = self.config.NETWORK_AUTO_CONNECT self.autoconnect_cb.setChecked(auto_connect) + one_server = self.config.NETWORK_ONESERVER + self.one_server_cb.setChecked(one_server) server = self.config.NETWORK_SERVER self.server_e.setText(server) - self.autoconnect_cb.setEnabled(self.config.cv.NETWORK_AUTO_CONNECT.is_modifiable()) + self.autoconnect_cb.setEnabled(self.config.cv.NETWORK_AUTO_CONNECT.is_modifiable() and not one_server) + self.one_server_cb.setEnabled(self.config.cv.NETWORK_ONESERVER.is_modifiable() and not auto_connect) self.server_e.setEnabled(self.config.cv.NETWORK_SERVER.is_modifiable() and not auto_connect) self.nodes_list_widget.setEnabled(self.config.cv.NETWORK_SERVER.is_modifiable()) @@ -504,7 +519,8 @@ class ServerWidget(QWidget, QtEventListener): except Exception: return net_params = net_params._replace(server=server, - auto_connect=self.autoconnect_cb.isChecked()) + auto_connect=self.autoconnect_cb.isChecked(), + oneserver=self.one_server_cb.isChecked()) self.network.run_from_another_thread(self.network.set_parameters(net_params)) diff --git a/electrum/gui/qt/wizard/server_connect.py b/electrum/gui/qt/wizard/server_connect.py index 0c6b88b36..c7a5f3d2b 100644 --- a/electrum/gui/qt/wizard/server_connect.py +++ b/electrum/gui/qt/wizard/server_connect.py @@ -96,4 +96,4 @@ class WCServerConfig(WizardComponent): def apply(self): self.wizard_data['autoconnect'] = self.sw.server_e.text().strip() == '' self.wizard_data['server'] = self.sw.server_e.text() - self.wizard_data['one_server'] = False + self.wizard_data['one_server'] = self.wizard.config.NETWORK_ONESERVER diff --git a/electrum/simple_config.py b/electrum/simple_config.py index c02be88df..19f7f781d 100644 --- a/electrum/simple_config.py +++ b/electrum/simple_config.py @@ -614,7 +614,12 @@ class SimpleConfig(Logger): # config variables -----> NETWORK_AUTO_CONNECT = ConfigVar('auto_connect', default=True, type_=bool) - NETWORK_ONESERVER = ConfigVar('oneserver', default=False, type_=bool) + NETWORK_ONESERVER = ConfigVar( + 'oneserver', default=False, type_=bool, + short_desc=lambda: _('Connect only to a single Electrum Server'), + long_desc=lambda: _('This is only intended for connecting to your own node. ' + 'Using this option on a public server is a security risk and is discouraged.') + ) NETWORK_PROXY = ConfigVar('proxy', default=None, type_=str, convert_getter=lambda v: "none" if v is None else v) NETWORK_PROXY_USER = ConfigVar('proxy_user', default=None, type_=str) NETWORK_PROXY_PASSWORD = ConfigVar('proxy_password', default=None, type_=str) diff --git a/tests/test_wizard.py b/tests/test_wizard.py index c3f009faa..350162502 100644 --- a/tests/test_wizard.py +++ b/tests/test_wizard.py @@ -112,7 +112,7 @@ class ServerConnectWizardTestCase(WizardTestCase): serverobj = ServerAddr.from_str_with_inference('localhost:1:t') self.assertTrue(w._daemon.network.run_called) - self.assertEqual(NetworkParameters(server=serverobj, proxy=None, auto_connect=False, oneserver=None), w._daemon.network.parameters) + self.assertEqual(NetworkParameters(server=serverobj, proxy=None, auto_connect=False, oneserver=False), w._daemon.network.parameters) class WalletWizardTestCase(WizardTestCase):