@@ -964,6 +964,8 @@ def __init__(
964964 self .env = session_mgr .env
965965 self .coin = self .env .coin
966966 self .client = 'unknown'
967+ self .sv_seen = False # has seen 'server.version' message?
968+ self .sv_negotiated = asyncio .Event () # done negotiating protocol version
967969 self .anon_logs = self .env .anon_logs
968970 self .txs_sent = 0
969971 self .log_me = SessionBase .log_new
@@ -1027,12 +1029,15 @@ async def handle_request(self, request):
10271029 handler = None
10281030 method = 'invalid method' if handler is None else request .method
10291031
1030- # If DROP_CLIENT_UNKNOWN is enabled, check if the client identified
1031- # by calling server.version previously. If not, disconnect the session
1032- if self .env . drop_client_unknown and method != ' server.version' and self . client == 'unknown' :
1033- self .logger . info ( f'disconnecting because client is unknown' )
1032+ # Version negotiation must happen before any other messages.
1033+ if not self . sv_seen and method != 'server.version' :
1034+ self .logger . info ( f'closing session: server.version must be first msg. got: { method } ' )
1035+ await self ._do_crash_old_electrum_client ( )
10341036 raise ReplyAndDisconnect (RPCError (
10351037 BAD_REQUEST , f'use server.version to identify client' ))
1038+ # Wait for version negotiation to finish before processing other messages.
1039+ if method != 'server.version' and not self .sv_negotiated .is_set ():
1040+ await self .sv_negotiated .wait ()
10361041
10371042 self .session_mgr ._method_counts [method ] += 1
10381043 coro = handler_invocation (handler , request )()
@@ -1041,6 +1046,21 @@ async def handle_request(self, request):
10411046 def protocol_version_string (self ) -> str :
10421047 raise NotImplementedError ()
10431048
1049+ async def maybe_crash_old_client (self , ptuple , crash_client_ver ):
1050+ if crash_client_ver :
1051+ client_ver = util .protocol_tuple (self .client )
1052+ is_old_protocol = ptuple is None or ptuple <= (1 , 2 )
1053+ is_old_client = client_ver != (0 ,) and client_ver <= crash_client_ver
1054+ if is_old_protocol and is_old_client :
1055+ await self ._do_crash_old_electrum_client ()
1056+
1057+ async def _do_crash_old_electrum_client (self ):
1058+ self .logger .info (f'attempting to crash old client with version { self .client } ' )
1059+ # this can crash electrum client 2.6 <= v < 3.1.2
1060+ await self .send_notification ('blockchain.relayfee' , ())
1061+ # this can crash electrum client (v < 2.8.2) UNION (3.0.0 <= v < 3.3.0)
1062+ await self .send_notification ('blockchain.estimatefee' , ())
1063+
10441064
10451065class ElectrumX (SessionBase ):
10461066 '''A TCP server that handles incoming Electrum connections.'''
@@ -1474,11 +1494,20 @@ async def ping(self):
14741494 self .bump_cost (0.1 )
14751495 return None
14761496
1477- async def server_version (self , client_name = '' , protocol_version = None ):
1497+ async def server_version (
1498+ self ,
1499+ client_name = '' ,
1500+ protocol_version = None ,
1501+ * extra_args ,
1502+ ** extra_kwargs ,
1503+ ):
14781504 '''Returns the server version as a string.
14791505
14801506 client_name: a string identifying the client
14811507 protocol_version: the protocol version spoken by the client
1508+
1509+ note: extraneous unknown args for 'server.version' MUST be tolerated
1510+ and ignored by the server, to allow for future extensions.
14821511 '''
14831512 self .bump_cost (0.5 )
14841513 if self .sv_seen :
@@ -1498,7 +1527,7 @@ async def server_version(self, client_name='', protocol_version=None):
14981527 ptuple , client_min = util .protocol_version (
14991528 protocol_version , self .PROTOCOL_MIN , self .PROTOCOL_MAX )
15001529
1501- await self .crash_old_client (ptuple , self .env .coin .CRASH_CLIENT_VER )
1530+ await self .maybe_crash_old_client (ptuple , self .env .coin .CRASH_CLIENT_VER )
15021531
15031532 if ptuple is None :
15041533 if client_min > self .PROTOCOL_MIN :
@@ -1509,20 +1538,9 @@ async def server_version(self, client_name='', protocol_version=None):
15091538 BAD_REQUEST , f'unsupported protocol version: { protocol_version } ' ))
15101539 self .set_request_handlers (ptuple )
15111540
1541+ self .sv_negotiated .set ()
15121542 return electrumx .version , self .protocol_version_string ()
15131543
1514- async def crash_old_client (self , ptuple , crash_client_ver ):
1515- if crash_client_ver :
1516- client_ver = util .protocol_tuple (self .client )
1517- is_old_protocol = ptuple is None or ptuple <= (1 , 2 )
1518- is_old_client = client_ver != (0 ,) and client_ver <= crash_client_ver
1519- if is_old_protocol and is_old_client :
1520- self .logger .info (f'attempting to crash old client with version { self .client } ' )
1521- # this can crash electrum client 2.6 <= v < 3.1.2
1522- await self .send_notification ('blockchain.relayfee' , ())
1523- # this can crash electrum client (v < 2.8.2) UNION (3.0.0 <= v < 3.3.0)
1524- await self .send_notification ('blockchain.estimatefee' , ())
1525-
15261544 async def transaction_broadcast (self , raw_tx ):
15271545 '''Broadcast a raw transaction to the network.
15281546
@@ -1695,6 +1713,8 @@ class LocalRPC(SessionBase):
16951713
16961714 def __init__ (self , * args , ** kwargs ):
16971715 super ().__init__ (* args , ** kwargs )
1716+ self .sv_seen = True
1717+ self .sv_negotiated .set ()
16981718 self .client = 'RPC'
16991719 self .connection .max_response_size = 0
17001720
0 commit comments