diff --git a/electrum/gui/messages.py b/electrum/gui/messages.py
index 8c3837f91..af5a13fa5 100644
--- a/electrum/gui/messages.py
+++ b/electrum/gui/messages.py
@@ -104,3 +104,34 @@ MSG_TERMS_OF_USE = (
)
TERMS_OF_USE_LATEST_VERSION : int = 1 # bump this if we want users re-prompted due to changes
+
+MSG_CONNECTMODE_AUTOCONNECT = _('Auto-connect')
+MSG_CONNECTMODE_MANUAL = _('Manual server selection')
+MSG_CONNECTMODE_ONESERVER = _('Connect only to a single server')
+
+MSG_CONNECTMODE_SERVER_HELP = _(
+ "Electrum connects to a unique server in order to receive your transaction history. "
+ "This server will learn your wallet adddresses."
+)
+MSG_CONNECTMODE_NODES_HELP = _(
+ "In addition to your history server, Electrum will try to maintain connections with ~10 extra servers, in order to download block headers and find out the longest blockchain. "
+ "These servers are only used for block header notifications and fee estimates; they do not learn your wallet addresses. "
+ "Getting block headers from multiple sources is useful to detect lagging servers and forks. "
+ "Fork detection is security-critical for determining number of confirmations."
+)
+
+MSG_CONNECTMODE_AUTOCONNECT_HELP = _(
+ "Electrum will always use a history server that is on the longest blockchain. "
+ "If your current server is unresponsive or lagging, Electrum will switch to another server."
+)
+
+MSG_CONNECTMODE_MANUAL_HELP = _(
+ "Electrum will stay with the server you selected. It will warn you if your server is lagging."
+)
+
+MSG_CONNECTMODE_ONESERVER_HELP = _(
+ "Electrum will stay with the server you selected, and it will not connect to additional nodes. "
+ "This will disable fork detection. "
+ "This mode is only intended for connecting to your own fully trusted server. "
+ "Using this option on a public server is a security risk and is discouraged."
+)
diff --git a/electrum/gui/qt/network_dialog.py b/electrum/gui/qt/network_dialog.py
index 68cee6c0c..79b138dc6 100644
--- a/electrum/gui/qt/network_dialog.py
+++ b/electrum/gui/qt/network_dialog.py
@@ -39,6 +39,7 @@ from electrum.interface import ServerAddr, PREFERRED_NETWORK_PROTOCOL
from electrum.network import Network, ProxySettings, is_valid_host, is_valid_port
from electrum.logging import get_logger
from electrum.util import is_valid_websocket_url
+from electrum.gui import messages
from .util import (
Buttons, CloseButton, HelpButton, read_QIcon, char_width_in_lineedit, PasswordLineEdit, QtEventListener,
@@ -61,7 +62,7 @@ class NetworkDialog(QDialog, QtEventListener):
self._blockchain_tab = ServerWidget(network)
self._proxy_tab = ProxyWidget(network)
self._nostr_tab = NostrWidget(network)
- tabs.addTab(self._blockchain_tab, _('Electrum servers'))
+ tabs.addTab(self._blockchain_tab, _('Server'))
tabs.addTab(self._nostr_tab, _('Nostr'))
tabs.addTab(self._proxy_tab, _('Proxy'))
vbox = QVBoxLayout(self)
@@ -362,7 +363,17 @@ class ProxyWidget(QWidget):
self.update()
+class ConnectMode(IntEnum):
+ AUTOCONNECT = 0
+ MANUAL = 1
+ ONESERVER = 2
+
class ServerWidget(QWidget, QtEventListener):
+ CONNECT_MODES = {
+ ConnectMode.AUTOCONNECT: messages.MSG_CONNECTMODE_AUTOCONNECT,
+ ConnectMode.MANUAL: messages.MSG_CONNECTMODE_MANUAL,
+ ConnectMode.ONESERVER: messages.MSG_CONNECTMODE_ONESERVER,
+ }
def __init__(self, network: Network, parent=None):
super().__init__(parent)
@@ -373,46 +384,47 @@ class ServerWidget(QWidget, QtEventListener):
grid = QGridLayout()
- msg = ' '.join([
- _("Electrum connects to several nodes in order to download block headers and find out the longest blockchain."),
- _("This blockchain is used to verify the transactions sent by your transaction server.")
- ])
+ self.connect_combo = QComboBox()
+ for i, v in sorted(self.CONNECT_MODES.items()):
+ self.connect_combo.addItem(v, i)
+ self.connect_combo.currentIndexChanged.connect(self.on_server_settings_changed)
+ grid.addWidget(QLabel(_('Connection mode') + ':'), 0, 0)
+ msg = (
+ f"""
+ {messages.MSG_CONNECTMODE_SERVER_HELP}
+ {messages.MSG_CONNECTMODE_NODES_HELP}
+
+ - {messages.MSG_CONNECTMODE_AUTOCONNECT}: {messages.MSG_CONNECTMODE_AUTOCONNECT_HELP}
+ - {messages.MSG_CONNECTMODE_MANUAL}: {messages.MSG_CONNECTMODE_MANUAL_HELP}
+ - {messages.MSG_CONNECTMODE_ONESERVER}: {messages.MSG_CONNECTMODE_ONESERVER_HELP}
+
+ """
+ )
+ grid.addWidget(HelpButton(msg), 0, 4)
+ grid.addWidget(self.connect_combo, 0, 1, 1, 3)
self.status_label_header = QLabel(_('Status') + ':')
self.status_label = QLabel('')
- self.status_label_helpbutton = HelpButton(msg)
- grid.addWidget(self.status_label_header, 0, 0)
- grid.addWidget(self.status_label, 0, 1, 1, 3)
- grid.addWidget(self.status_label_helpbutton, 0, 4)
-
- self.autoconnect_cb = QCheckBox(self.config.cv.NETWORK_AUTO_CONNECT.get_short_desc())
- self.autoconnect_cb.stateChanged.connect(self.on_server_settings_changed)
-
- grid.addWidget(self.autoconnect_cb, 1, 0, 1, 3)
- grid.addWidget(HelpButton(self.config.cv.NETWORK_AUTO_CONNECT.get_long_desc()), 1, 4)
-
- self.one_server_cb = QCheckBox(self.config.cv.NETWORK_ONESERVER.get_short_desc())
- 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.status_label_helpbutton = HelpButton(messages.MSG_CONNECTMODE_NODES_HELP)
+ grid.addWidget(self.status_label_header, 1, 0)
+ grid.addWidget(self.status_label, 1, 1, 1, 3)
+ grid.addWidget(self.status_label_helpbutton, 1, 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') + ':'), 3, 0)
- grid.addWidget(self.server_e, 3, 1, 1, 3)
- grid.addWidget(HelpButton(msg), 3, 4)
+ grid.addWidget(QLabel(_('Server') + ':'), 4, 0)
+ grid.addWidget(self.server_e, 4, 1, 1, 3)
+ grid.addWidget(HelpButton(messages.MSG_CONNECTMODE_SERVER_HELP), 4, 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, 4, 0)
- grid.addWidget(self.height_label, 4, 1)
- grid.addWidget(self.height_label_helpbutton, 4, 4)
+ grid.addWidget(self.height_label_header, 2, 0)
+ grid.addWidget(self.height_label, 2, 1)
+ grid.addWidget(self.height_label_helpbutton, 2, 4)
self.split_label = QLabel('')
- grid.addWidget(self.split_label, 5, 0, 1, 3)
+ grid.addWidget(self.split_label, 3, 1, 1, 3)
self.layout().addLayout(grid)
@@ -439,32 +451,28 @@ class ServerWidget(QWidget, QtEventListener):
self.nodes_list_widget.update() # NOTE: move event handling to widget itself?
self.update()
+ def is_auto_connect(self):
+ return self.connect_combo.currentIndex() == ConnectMode.AUTOCONNECT
+
+ def is_one_server(self):
+ return self.connect_combo.currentIndex() == ConnectMode.ONESERVER
+
def on_server_settings_changed(self):
if not self.network._was_started:
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 or one_server != net_params.oneserver:
+ if server != net_params.server or self.is_auto_connect() != net_params.auto_connect or self.is_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)
-
+ self.server_e.setEnabled(self.config.cv.NETWORK_SERVER.is_modifiable() and not self.is_auto_connect())
for item in [
self.status_label_header, self.status_label, self.status_label_helpbutton,
self.height_label_header, self.height_label, self.height_label_helpbutton]:
item.setVisible(self.network._was_started)
-
- msg = ''
+ msg = _('Fork detection disabled') if self.is_one_server() else ''
if self.network._was_started:
# Network was started, so we don't run in initial setup wizard.
# behavior in this case is to apply changes immediately.
@@ -477,8 +485,8 @@ class ServerWidget(QWidget, QtEventListener):
chain = self.network.blockchain()
forkpoint = chain.get_max_forkpoint()
name = chain.get_name()
- msg = _('Chain split detected at block {0}').format(forkpoint) + '\n'
- if auto_connect:
+ msg = _('Fork detected at block {0}').format(forkpoint) + '\n'
+ if self.is_auto_connect():
msg += _('You are following branch {}').format(name)
else:
msg += _('Your server is on branch {0} ({1} blocks)').format(name, chain.get_branch_size())
@@ -486,14 +494,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)
+ v = ConnectMode.AUTOCONNECT if auto_connect else ConnectMode.ONESERVER if one_server else ConnectMode.MANUAL
+ self.connect_combo.setCurrentIndex(v)
+
server = self.config.NETWORK_SERVER
self.server_e.setText(server)
- 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())
@@ -514,9 +521,11 @@ class ServerWidget(QWidget, QtEventListener):
raise Exception("failed to parse server")
except Exception:
return
- net_params = net_params._replace(server=server,
- auto_connect=self.autoconnect_cb.isChecked(),
- oneserver=self.one_server_cb.isChecked())
+ net_params = net_params._replace(
+ server=server,
+ auto_connect=self.is_auto_connect(),
+ oneserver=self.is_one_server(),
+ )
self.network.run_from_another_thread(self.network.set_parameters(net_params))
@@ -528,11 +537,16 @@ class NostrWidget(QWidget, QtEventListener):
self.config = network.config
vbox = QVBoxLayout()
self.setLayout(vbox)
- nostr_relays_label = HelpLabel.from_configvar(self.config.cv.NOSTR_RELAYS)
+ grid = QGridLayout()
+ nostr_relays_label = QLabel(self.config.cv.NOSTR_RELAYS.get_short_desc())
+ nostr_helpbutton = HelpButton(self.config.cv.NOSTR_RELAYS.get_long_desc())
+ grid.addWidget(nostr_relays_label, 0, 0)
+ grid.addWidget(nostr_helpbutton, 0, 1)
+ vbox.addLayout(grid)
+
self.relays_list = QListWidget()
self.relay_edit = QLineEdit()
self.relay_edit.textChanged.connect(self.on_relay_edited)
- vbox.addWidget(nostr_relays_label)
vbox.addWidget(self.relays_list)
vbox.addStretch()
self.add_button = QPushButton(_('Add'))