Skip to content

Commit 6be8718

Browse files
committed
implement some retry
1 parent 25cdd5a commit 6be8718

1 file changed

Lines changed: 110 additions & 97 deletions

File tree

subvortex/validator/neuron/src/challenge.py

Lines changed: 110 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ async def challenge_miner(self, miner: Miner):
219219
return (verified, reason, details)
220220

221221

222-
async def challenge_subtensor(miner: Miner, challenge):
222+
async def challenge_subtensor(miner: Miner, challenge, max_retries=3):
223223
"""
224224
Challenge the subtensor by requesting the value of a property of a specific neuron in a specific subnet at a certain block
225225
"""
@@ -228,116 +228,129 @@ async def challenge_subtensor(miner: Miner, challenge):
228228
details = None
229229
process_time = None
230230

231-
try:
232-
# Get the details of the challenge
233-
block_hash, params, value = challenge
234-
235-
ws = None
231+
# Retry logic for geographic connectivity issues
232+
for attempt in range(max_retries):
236233
try:
237-
ws = await websockets.connect(f"ws://{miner.ip}:9944")
238-
239-
except asyncio.TimeoutError as ex:
240-
return (verified, "WebSocket connection timed out", str(ex), process_time)
241-
242-
except (InvalidURI, OSError) as ex:
243-
return (
244-
verified,
245-
f"Invalid WebSocket URI ws://{miner.ip}:9944",
246-
str(ex),
247-
process_time,
234+
# Get the details of the challenge
235+
block_hash, params, value = challenge
236+
237+
# Add small delay between retries
238+
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}")
241+
242+
ws = None
243+
try:
244+
# 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+
},
248254
)
249-
except InvalidHandshake as ex:
250-
return (verified, "WebSocket handshake failed", str(ex), process_time)
251-
252-
except WebSocketException as ex:
253-
return (verified, "Websocket exception", str(ex), process_time)
254-
255-
# Set start time
256-
start_time = time.time()
257-
258-
# Prepare data payload
259-
data = json.dumps(
260-
{
261-
"id": "state_call0",
262-
"jsonrpc": "2.0",
263-
"method": "state_call",
264-
"params": [
265-
"NeuronInfoRuntimeApi_get_neuron_lite",
266-
params,
267-
block_hash,
268-
],
269-
}
270-
)
271255

272-
try:
273-
# Send request
274-
await ws.send(data)
275-
276-
except ConnectionClosed as ex:
277-
return (
278-
verified,
279-
"WebSocket closed before sending data",
280-
str(ex),
281-
process_time,
256+
except InvalidURI as ex:
257+
# Invalid URI - don't retry, fail immediately
258+
reason = f"Invalid WebSocket URI ws://{miner.ip}:9944"
259+
details = str(ex)
260+
break
261+
262+
except (asyncio.TimeoutError, OSError, InvalidHandshake, WebSocketException) as ex:
263+
# These are connection errors - let retry logic handle them
264+
raise ex
265+
266+
267+
# Set start time
268+
start_time = time.time()
269+
270+
# Prepare data payload
271+
data = json.dumps(
272+
{
273+
"id": "state_call0",
274+
"jsonrpc": "2.0",
275+
"method": "state_call",
276+
"params": [
277+
"NeuronInfoRuntimeApi_get_neuron_lite",
278+
params,
279+
block_hash,
280+
],
281+
}
282282
)
283283

284-
try:
285-
# Receive response
286-
response = await ws.recv()
287-
288-
except asyncio.TimeoutError as ex:
289-
return (verified, "WebSocket receive timed out", str(ex), process_time)
290-
291-
except ConnectionClosed as ex:
292-
return (
293-
verified,
294-
"WebSocket closed before receiving response",
295-
str(ex),
296-
process_time,
297-
)
284+
try:
285+
# Send request
286+
await ws.send(data)
298287

299-
# Calculate process time
300-
process_time = time.time() - start_time
288+
except ConnectionClosed as ex:
289+
# Connection error - let retry logic handle
290+
raise ex
301291

302-
# Load the response
303-
try:
304-
response = json.loads(response)
292+
try:
293+
# Receive response with timeout for international connections
294+
response = await asyncio.wait_for(ws.recv(), timeout=20.0)
305295

306-
except json.JSONDecodeError as ex:
307-
return (verified, "Received malformed JSON response", str(ex), process_time)
296+
except (asyncio.TimeoutError, ConnectionClosed) as ex:
297+
# Connection error - let retry logic handle
298+
raise ex
308299

309-
if "error" in response:
310-
return (
311-
verified,
312-
f"Error in response: {response['error'].get('message', 'Unknown error')}",
313-
"",
314-
process_time,
315-
)
300+
# Calculate process time
301+
process_time = time.time() - start_time
316302

317-
if "result" not in response:
318-
return (
319-
verified,
320-
f"Response does not contain a 'result' field",
321-
"",
322-
process_time,
323-
)
303+
# Load the response
304+
try:
305+
response = json.loads(response)
324306

325-
# Verify the challenge
326-
verified = response["result"] == value
307+
except json.JSONDecodeError as ex:
308+
reason = "Received malformed JSON response"
309+
details = str(ex)
310+
break
327311

328-
# Log total process time breakdown
329-
total_time = time.time() - start_time
330-
btul.logging.trace(
331-
f"[{CHALLENGE_NAME}][{miner.uid}] Total challenge time: {total_time:.2f}s"
332-
)
312+
if "error" in response:
313+
reason = f"Error in response: {response['error'].get('message', 'Unknown error')}"
314+
details = ""
315+
break
333316

334-
except Exception as ex:
335-
reason = "Unexpected exception"
336-
details = str(ex)
317+
if "result" not in response:
318+
reason = "Response does not contain a 'result' field"
319+
details = ""
320+
break
337321

338-
finally:
339-
if ws:
340-
await ws.close()
322+
# Verify the challenge
323+
verified = response["result"] == value
324+
325+
# Log total process time breakdown
326+
total_time = time.time() - start_time
327+
btul.logging.trace(
328+
f"[{CHALLENGE_NAME}][{miner.uid}] Total challenge time: {total_time:.2f}s"
329+
)
330+
331+
# Success - break retry loop
332+
break
333+
334+
except (ConnectionClosed, WebSocketException, asyncio.TimeoutError) as ex:
335+
# These are retryable errors - continue to next attempt
336+
reason = f"Connection error on attempt {attempt + 1}/{max_retries}"
337+
details = str(ex)
338+
btul.logging.debug(f"[{CHALLENGE_NAME}][{miner.uid}] {reason}: {details}")
339+
340+
if attempt == max_retries - 1: # Last attempt
341+
reason = "WebSocket connection failed after retries"
342+
break
343+
344+
except Exception as ex:
345+
# Non-retryable error - break immediately
346+
reason = "Unexpected exception"
347+
details = str(ex)
348+
break
349+
350+
finally:
351+
if ws:
352+
await ws.close()
353+
ws = None
341354

342355
return (verified, reason, details, process_time)
343356

0 commit comments

Comments
 (0)