1818import time
1919import random
2020import asyncio
21+ import string
2122import traceback
2223import websockets
2324import bittensor .core .subtensor as btcs
2425import bittensor .utils .btlogging as btul
26+ from itertools import cycle
27+ from websockets .sync .client import connect , ClientConnection
2528from typing import Dict
2629from collections import Counter
2730from websockets .exceptions import (
7679 "last_update" ,
7780]
7881
82+ id_cycle = cycle (range (1 , 999 ))
83+
84+ rng = random .Random ()
85+
86+
87+ def get_next_id () -> str :
88+ """
89+ Generates a pseudo-random ID by returning the next int of a range from 1-998 prepended with
90+ two random ascii characters.
91+ """
92+ random_letters = "" .join (rng .choices (string .ascii_letters , k = 2 ))
93+ return f"{ random_letters } { next (id_cycle )} "
94+
7995
8096def get_runtime_call_definition (
8197 substrate : btcs .SubstrateInterface , api : str , method : str
@@ -219,7 +235,7 @@ async def challenge_miner(self, miner: Miner):
219235 return (verified , reason , details )
220236
221237
222- async def challenge_subtensor (miner : Miner , challenge , max_retries = 3 ):
238+ def challenge_subtensor (miner : Miner , challenge , max_retries = 3 ):
223239 """
224240 Challenge the subtensor by requesting the value of a property of a specific neuron in a specific subnet at a certain block
225241 """
@@ -229,48 +245,52 @@ async def challenge_subtensor(miner: Miner, challenge, max_retries=3):
229245 process_time = None
230246
231247 # Retry logic for geographic connectivity issues
248+ ws : ClientConnection = None
232249 for attempt in range (max_retries ):
233250 try :
234251 # Get the details of the challenge
235252 block_hash , params , value = challenge
236-
253+
237254 # Add small delay between retries
238255 if attempt > 0 :
239- await asyncio .sleep (attempt * 1.0 ) # 1s, 2s, 3s delays
240- btul .logging .debug (f"[{ CHALLENGE_NAME } ][{ miner .uid } ] Retry attempt { attempt + 1 } /{ max_retries } " )
256+ btul .logging .trace (
257+ f"[{ CHALLENGE_NAME } ][{ miner .uid } ] Retry attempt { attempt + 1 } /{ max_retries } "
258+ )
241259
242- ws = None
243260 try :
244261 # Increase connection timeout for international connections
245- ws = await websockets .connect (
246- f"ws://{ miner .ip } :9944" ,
247- ping_timeout = None , # Disable ping timeout
248- ping_interval = None , # Disable ping
249- close_timeout = 10 , # 10s close timeout
250- open_timeout = 15 , # 15s connection timeout
251- additional_headers = {
252- "Connection" : "keep-alive" ,
253- },
254- )
262+ ws = (
263+ connect (
264+ f"ws://{ miner .ip } :9944" ,
265+ max_size = 2 ** 32 ,
266+ )
267+ if not ws or ws .close_code
268+ else ws
269+ )
255270
256271 except InvalidURI as ex :
257272 # Invalid URI - don't retry, fail immediately
258273 reason = f"Invalid WebSocket URI ws://{ miner .ip } :9944"
259274 details = str (ex )
260275 break
261-
262- except (asyncio .TimeoutError , OSError , InvalidHandshake , WebSocketException ) as ex :
276+
277+ except (
278+ asyncio .TimeoutError ,
279+ OSError ,
280+ InvalidHandshake ,
281+ WebSocketException ,
282+ ) as ex :
263283 # These are connection errors - let retry logic handle them
264284 raise ex
265285
266-
267286 # Set start time
268287 start_time = time .time ()
269288
270289 # Prepare data payload
290+ item_id = get_next_id ()
271291 data = json .dumps (
272292 {
273- "id" : "state_call0" ,
293+ "id" : item_id ,
274294 "jsonrpc" : "2.0" ,
275295 "method" : "state_call" ,
276296 "params" : [
@@ -283,18 +303,30 @@ async def challenge_subtensor(miner: Miner, challenge, max_retries=3):
283303
284304 try :
285305 # Send request
286- await ws .send (data )
306+ ws .send (data )
287307
288308 except ConnectionClosed as ex :
289309 # Connection error - let retry logic handle
310+ btul .logging .trace (
311+ f"[{ CHALLENGE_NAME } ][{ miner .uid } ] WebSocket closed during send: { ex } "
312+ )
290313 raise ex
291314
292315 try :
293316 # Receive response with timeout for international connections
294- response = await asyncio .wait_for (ws .recv (), timeout = 20.0 )
317+ response = ws .recv (decode = False , timeout = 60.0 )
318+
319+ except asyncio .TimeoutError as ex :
320+ btul .logging .trace (
321+ f"[{ CHALLENGE_NAME } ][{ miner .uid } ] WebSocket receive timeout: { ex } "
322+ )
323+ raise ex
295324
296- except ( asyncio . TimeoutError , ConnectionClosed ) as ex :
325+ except ConnectionClosed as ex :
297326 # Connection error - let retry logic handle
327+ btul .logging .trace (
328+ f"[{ CHALLENGE_NAME } ][{ miner .uid } ] WebSocket closed during receive: { ex } "
329+ )
298330 raise ex
299331
300332 # Calculate process time
@@ -327,18 +359,18 @@ async def challenge_subtensor(miner: Miner, challenge, max_retries=3):
327359 btul .logging .trace (
328360 f"[{ CHALLENGE_NAME } ][{ miner .uid } ] Total challenge time: { total_time :.2f} s"
329361 )
330-
362+
331363 # Success - break retry loop
332364 break
333365
334366 except (ConnectionClosed , WebSocketException , asyncio .TimeoutError ) as ex :
335367 # These are retryable errors - continue to next attempt
336368 reason = f"Connection error on attempt { attempt + 1 } /{ max_retries } "
337369 details = str (ex )
338- btul .logging .debug (f"[{ CHALLENGE_NAME } ][{ miner .uid } ] { reason } : { details } " )
339-
370+ btul .logging .trace (f"[{ CHALLENGE_NAME } ][{ miner .uid } ] { reason } : { details } " )
371+
340372 if attempt == max_retries - 1 : # Last attempt
341- reason = "WebSocket connection failed after retries"
373+ reason = f "WebSocket connection failed after { max_retries } retries: { str ( ex ) } "
342374 break
343375
344376 except Exception as ex :
@@ -349,7 +381,7 @@ async def challenge_subtensor(miner: Miner, challenge, max_retries=3):
349381
350382 finally :
351383 if ws :
352- await ws .close ()
384+ ws .close ()
353385 ws = None
354386
355387 return (verified , reason , details , process_time )
@@ -383,7 +415,7 @@ async def handle_challenge(self, uid: int, challenge):
383415 # Challenge Subtensor - Process time + check the challenge
384416 btul .logging .debug (f"[{ CHALLENGE_NAME } ][{ miner .uid } ] Challenging subtensor" )
385417 subtensor_verified , subtensor_reason , subtensor_details , subtensor_time = (
386- await challenge_subtensor (miner , challenge )
418+ challenge_subtensor (miner , challenge )
387419 )
388420 if subtensor_verified :
389421 btul .logging .success (f"[{ CHALLENGE_NAME } ][{ miner .uid } ] Subtensor verified" )
0 commit comments