From cd3173a289810e4b21fc41760016920fff9bfb5d Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 23 Oct 2025 17:02:41 +0000 Subject: [PATCH] interface: extend client to be able to support a range of protocols --- electrum/gui/qml/qeapp.py | 2 +- electrum/interface.py | 26 ++++++++++++++++++++++---- electrum/network.py | 4 ++-- electrum/version.py | 3 ++- 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/electrum/gui/qml/qeapp.py b/electrum/gui/qml/qeapp.py index c14c0d47f..a4df18b13 100644 --- a/electrum/gui/qml/qeapp.py +++ b/electrum/gui/qml/qeapp.py @@ -546,7 +546,7 @@ class ElectrumQmlApplication(QGuiApplication): self.context.setContextProperty('QRIP', self.qr_ip_h) self.context.setContextProperty('BUILD', { 'electrum_version': version.ELECTRUM_VERSION, - 'protocol_version': version.PROTOCOL_VERSION, + 'protocol_version': f"[{version.PROTOCOL_VERSION_MIN}, {version.PROTOCOL_VERSION_MAX}]", 'qt_version': QT_VERSION_STR, 'pyqt_version': PYQT_VERSION_STR }) diff --git a/electrum/interface.py b/electrum/interface.py index ecd6f99ad..a092e3500 100644 --- a/electrum/interface.py +++ b/electrum/interface.py @@ -50,7 +50,7 @@ import certifi from .util import (ignore_exceptions, log_exceptions, bfh, ESocksProxy, is_integer, is_non_negative_integer, is_hash256_str, is_hex_str, is_int_or_float, is_non_negative_int_or_float, OldTaskGroup, - send_exception_to_crash_reporter, error_text_str_to_safe_str) + send_exception_to_crash_reporter, error_text_str_to_safe_str, versiontuple) from . import util from . import x509 from . import pem @@ -139,6 +139,18 @@ def assert_list_or_tuple(val: Any) -> None: raise RequestCorrupted(f'{val!r} should be a list or tuple') +def protocol_tuple(s: Any) -> tuple[int, ...]: + """Converts a protocol version number, such as "1.0" to a tuple (1, 0). + + If the version number is bad, (0, ) indicating version 0 is returned. + """ + try: + assert isinstance(s, str) + return versiontuple(s) + except Exception: + return (0, ) + + class ChainResolutionMode(enum.Enum): CATCHUP = enum.auto() BACKWARD = enum.auto() @@ -574,6 +586,8 @@ class Interface(Logger): self.fee_estimates_eta = {} # type: Dict[int, int] + self.active_protocol_tuple = (0,) # type: Optional[tuple[int, ...]] + # Dump network messages (only for this interface). Set at runtime from the console. self.debug = False @@ -964,15 +978,19 @@ class Interface(Logger): start = time.perf_counter() self.session = session # type: NotificationSession self.session.set_default_timeout(self.network.get_network_timeout_seconds(NetworkTimeout.Generic)) + client_prange = [version.PROTOCOL_VERSION_MIN, version.PROTOCOL_VERSION_MAX] try: - ver = await session.send_request('server.version', [self.client_name(), version.PROTOCOL_VERSION]) + ver = await session.send_request('server.version', [self.client_name(), client_prange]) except aiorpcx.jsonrpc.RPCError as e: raise GracefulDisconnect(e) # probably 'unsupported protocol version' if exit_early: return - if ver[1] != version.PROTOCOL_VERSION: + self.active_protocol_tuple = protocol_tuple(ver[1]) + client_pmin = protocol_tuple(client_prange[0]) + client_pmax = protocol_tuple(client_prange[1]) + if not (client_pmin <= self.active_protocol_tuple <= client_pmax): raise GracefulDisconnect(f'server violated protocol-version-negotiation. ' - f'we asked for {version.PROTOCOL_VERSION!r}, they sent {ver[1]!r}') + f'we asked for {client_prange!r}, they sent {ver[1]!r}') if not self.network.check_interface_against_healthy_spread_of_connected_servers(self): raise GracefulDisconnect(f'too many connected servers already ' f'in bucket {self.bucket_based_on_ipaddress()}') diff --git a/electrum/network.py b/electrum/network.py index c49b48f9c..966a31e6f 100644 --- a/electrum/network.py +++ b/electrum/network.py @@ -55,7 +55,7 @@ from .interface import ( Interface, PREFERRED_NETWORK_PROTOCOL, RequestTimedOut, NetworkTimeout, BUCKET_NAME_OF_ONION_SERVERS, NetworkException, RequestCorrupted, ServerAddr, TxBroadcastError, ) -from .version import PROTOCOL_VERSION +from .version import PROTOCOL_VERSION_MIN from .i18n import _ from .logging import get_logger, Logger from .fee_policy import FeeHistogram, FeeTimeEstimates, FEE_ETA_TARGETS @@ -118,7 +118,7 @@ def parse_servers(result: Sequence[Tuple[str, str, List[str]]]) -> Dict[str, dic def filter_version(servers): def is_recent(version): try: - return util.versiontuple(version) >= util.versiontuple(PROTOCOL_VERSION) + return util.versiontuple(version) >= util.versiontuple(PROTOCOL_VERSION_MIN) except Exception as e: return False return {k: v for k, v in servers.items() if is_recent(v.get('version'))} diff --git a/electrum/version.py b/electrum/version.py index 40ec2115f..49ad233e6 100644 --- a/electrum/version.py +++ b/electrum/version.py @@ -1,6 +1,7 @@ ELECTRUM_VERSION = '4.6.2' # version of the client package -PROTOCOL_VERSION = '1.4' # protocol version requested +PROTOCOL_VERSION_MIN = '1.4' # electrum protocol +PROTOCOL_VERSION_MAX = '1.4' # The hash of the mnemonic seed must begin with this SEED_PREFIX = '01' # Standard wallet