1111
1212from pqnstack .app .api .deps import ClientDep
1313from pqnstack .app .api .deps import StateDep
14- from pqnstack .app .core .config import settings , NodeState
14+ from pqnstack .app .core .config import NodeState
15+ from pqnstack .app .core .config import qkd_result_received_event
16+ from pqnstack .app .core .config import settings
1517from pqnstack .constants import BasisBool
1618from pqnstack .constants import QKDEncodingBasis
1719from pqnstack .network .client import Client
@@ -195,10 +197,9 @@ def request_qkd_basis_list(leader_basis_list: list[str], state: StateDep) -> lis
195197
196198
197199@router .post ("/set_qkd_emoji" )
198- def set_qkd_emoji (emoji : str , state : StateDep ):
200+ def set_qkd_emoji (emoji : str , state : StateDep ) -> None :
199201 """Set the emoji pick for QKD."""
200202 state .qkd_emoji_pick = emoji
201- return {"message" : "Emoji set successfully" }
202203
203204
204205@router .get ("/question_order" )
@@ -213,7 +214,6 @@ async def request_qkd_question_order(
213214 If this node is a follower, it requests the question order from the leader node.
214215 Returns the question order as a list of integers.
215216 """
216-
217217 if state .leading and state .following :
218218 logger .error ("Node cannot be both leader and follower" )
219219 raise HTTPException (
@@ -245,8 +245,8 @@ async def request_qkd_question_order(
245245 detail = "Failed to get question order from leader" ,
246246 )
247247 state .qkd_question_order = r .json ()
248- except Exception as e :
249- logger .error ("Error requesting question order from leader: %s" , e )
248+ except ( httpx . HTTPError , httpx . RequestError , httpx . TimeoutException ) as e :
249+ logger .exception ("Error requesting question order from leader: %s" )
250250 raise HTTPException (
251251 status_code = status .HTTP_500_INTERNAL_SERVER_ERROR ,
252252 detail = f"Error requesting question order from leader: { e } " ,
@@ -257,12 +257,15 @@ async def request_qkd_question_order(
257257 status_code = status .HTTP_500_INTERNAL_SERVER_ERROR ,
258258 detail = "Follower node has no leader address set" ,
259259 )
260+
260261 return state .qkd_question_order
261262
262263
263264@router .get ("/is_follower_ready" )
264265async def is_follower_ready (state : StateDep ) -> bool :
265- """Check if the follower node is ready for QKD.
266+ """
267+ Check if the follower node is ready for QKD.
268+
266269 Follower is ready when the state has the basis list with as many choices as the bitstring length.
267270 """
268271 if not state .following :
@@ -277,42 +280,39 @@ async def is_follower_ready(state: StateDep) -> bool:
277280
278281@router .post ("/submit_qkd_result" )
279282async def submit_qkd_result (result : QKDResult , state : StateDep ) -> None :
280- """
281- QKD leader calls this endpoint of the follower to submit the QKD result as well as the emoji chosen.
282- """
283+ """QKD leader calls this endpoint of the follower to submit the QKD result as well as the emoji chosen."""
283284 state .qkd_emoji_pick = result .emoji
284285 state .qkd_n_matching_bits = result .n_matching_bits
286+ qkd_result_received_event .set () # Signal that the result has been received
285287 logger .info ("Received QKD result from follower: %s" , result )
286288
287289
288- async def _wait_for_follower_ready (state : NodeState , http_client : httpx .AsyncClient ):
290+ async def _wait_for_follower_ready (state : NodeState , http_client : httpx .AsyncClient ) -> None :
289291 """Poll the follower until it's ready, checking every 0.5 seconds."""
290292 while True :
291293 try :
292294 r = await http_client .get (f"http://{ state .followers_address } /qkd/is_follower_ready" )
293295 if r .status_code == status .HTTP_200_OK :
294296 is_ready = r .json ()
295297 if is_ready :
296- logger .info ("YAY FOLLOWER READY " )
298+ logger .info ("Follower has all basis choices. Ready to start QKD " )
297299 break
298- else :
299- logger .info ("Tried checking if follower is ready, but it wasn't ready" )
300+ logger .info ("Tried checking if follower is ready, but it wasn't ready" )
300301 else :
301302 logger .info ("Tried checking if follower is ready, but received non-200 status code" )
302- except Exception as e :
303+ except ( httpx . HTTPError , httpx . RequestError , httpx . TimeoutException ) as e :
303304 logger .info ("Tried checking if follower is ready, but encountered error: %s" , e )
304305
305306 await asyncio .sleep (0.5 )
306307
307308
308309async def _submit_qkd_result_to_follower (
309310 state : NodeState , http_client : httpx .AsyncClient , qkd_result : QKDResult
310- ):
311+ ) -> None :
311312 """Submit the QKD result to the follower node."""
312313 try :
313314 r = await http_client .post (
314- f"http://{ state .followers_address } /qkd/submit_qkd_result" ,
315- json = qkd_result .model_dump ()
315+ f"http://{ state .followers_address } /qkd/submit_qkd_result" , json = qkd_result .model_dump ()
316316 )
317317 if r .status_code != status .HTTP_200_OK :
318318 logger .error ("Failed to submit QKD result to follower: %s" , r .text )
@@ -321,8 +321,8 @@ async def _submit_qkd_result_to_follower(
321321 detail = "Failed to submit QKD result to follower" ,
322322 )
323323 logger .info ("Successfully submitted QKD result to follower" )
324- except Exception as e :
325- logger .error ("Error submitting QKD result to follower: %s" , e )
324+ except ( httpx . HTTPError , httpx . RequestError , httpx . TimeoutException ) as e :
325+ logger .exception ("Error submitting QKD result to follower" )
326326 raise HTTPException (
327327 status_code = status .HTTP_500_INTERNAL_SERVER_ERROR ,
328328 detail = f"Error submitting QKD result to follower: { e } " ,
@@ -343,42 +343,46 @@ async def _submit_qkd_basis_list_leader(
343343 n_matching_bits = len (ret ),
344344 n_total_bits = settings .qkd_settings .bitstring_length ,
345345 emoji = state .qkd_emoji_pick ,
346- role = "leader"
346+ role = "leader" ,
347347 )
348348
349349 # Submit result to follower
350350 await _submit_qkd_result_to_follower (state , http_client , qkd_result )
351351 return qkd_result
352352
353353
354- async def _submit_qkd_basis_list_follower (
355- state : NodeState , http_client : httpx .Client , basis_list : list [QKDEncodingBasis ]
356- ) -> QKDResult :
354+ async def _submit_qkd_basis_list_follower (state : NodeState , basis_list : list [QKDEncodingBasis ]) -> QKDResult :
357355 state .qkd_follower_basis_list = basis_list
358356
359- # Wait until the leader submits the QKD result (state.qkd_n_matching_bits != -1)
360- while state .qkd_n_matching_bits == - 1 :
361- await asyncio .sleep (0.5 )
357+ # don't wait for the event if the result is already set. This avoids deadlocks in case the result was set before this function is called.
358+ if state .qkd_n_matching_bits == - 1 :
359+ # Wait until the leader submits the QKD result
360+ await qkd_result_received_event .wait ()
362361
363362 # Reassemble the QKDResult object from the state
364363 qkd_result = QKDResult (
365364 n_matching_bits = state .qkd_n_matching_bits ,
366365 n_total_bits = settings .qkd_settings .bitstring_length ,
367366 emoji = state .qkd_emoji_pick ,
368- role = "follower"
367+ role = "follower" ,
369368 )
370369
370+ # Clear the event for the next QKD run
371+ qkd_result_received_event .clear ()
372+
371373 logger .info ("Follower received QKD result: %s" , state .qkd_n_matching_bits )
372374 return qkd_result
373375
374376
375377@router .post ("/submit_selection_and_start_qkd" )
376378async def submit_qkd_selection_and_start_qkd (
377379 state : StateDep , http_client : ClientDep , basis_list : list [str ], timetagger_address : str = ""
378- ):
379- """GUI calls this function to submit the QKD basis selection and start the QKD protocol.
380- This call is called by both leader and follower, depending on the node role, different actions are taken."""
380+ ) -> QKDResult :
381+ """
382+ GUI calls this function to submit the QKD basis selection and start the QKD protocol.
381383
384+ This call is called by both leader and follower, depending on the node role, different actions are taken.
385+ """
382386 if state .leading and state .following :
383387 logger .error ("Node cannot be both leader and follower" )
384388 raise HTTPException (
@@ -401,7 +405,7 @@ async def submit_qkd_selection_and_start_qkd(
401405 elif basis_str .lower () == "b" :
402406 qkd_basis_list .append (QKDEncodingBasis .DA )
403407 else :
404- logger .error ( f "Invalid basis string: { basis_str } . Expected 'a' or 'b'" )
408+ logger .exception ( "Invalid basis string: %s . Expected 'a' or 'b'" , basis_str )
405409 raise HTTPException (
406410 status_code = status .HTTP_400_BAD_REQUEST ,
407411 detail = f"Invalid basis string: { basis_str } . Expected 'a' or 'b'" ,
@@ -414,9 +418,7 @@ async def submit_qkd_selection_and_start_qkd(
414418 status_code = status .HTTP_400_BAD_REQUEST ,
415419 detail = "Leader must provide timetagger address to start QKD" ,
416420 )
417- ret = await _submit_qkd_basis_list_leader (state , http_client , qkd_basis_list , timetagger_address )
418- return ret
421+ return await _submit_qkd_basis_list_leader (state , http_client , qkd_basis_list , timetagger_address )
419422
420- if state .following :
421- ret = await _submit_qkd_basis_list_follower (state , http_client , qkd_basis_list )
422- return ret
423+ # If the node is not leading, it is assumed it is a follower due to previous check
424+ return await _submit_qkd_basis_list_follower (state , qkd_basis_list )
0 commit comments