@@ -460,6 +460,148 @@ async def determine_block_hash(
460460 return await self .get_block_hash (block )
461461 return None
462462
463+ async def _runtime_method_exists (
464+ self , api : str , method : str , block_hash : str
465+ ) -> bool :
466+ """
467+ Check if a runtime call method exists at the given block.
468+
469+ The complicated logic here comes from the fact that there are two ways in which runtime calls
470+ are stored: the new and primary method is through the Metadata V15, but the V14 is a good backup (implemented
471+ around mid 2024)
472+
473+ Returns:
474+ True if the runtime call method exists, False otherwise.
475+ """
476+ runtime = await self .substrate .init_runtime (block_hash = block_hash )
477+ if runtime .metadata_v15 is not None :
478+ metadata_v15_value = runtime .metadata_v15 .value ()
479+ apis = {entry ["name" ]: entry for entry in metadata_v15_value ["apis" ]}
480+ try :
481+ api_entry = apis [api ]
482+ methods = {entry ["name" ]: entry for entry in api_entry ["methods" ]}
483+ _ = methods [method ]
484+ return True
485+ except KeyError :
486+ return False
487+ else :
488+ try :
489+ await self .substrate .get_metadata_runtime_call_function (
490+ api = api ,
491+ method = method ,
492+ block_hash = block_hash ,
493+ )
494+ return True
495+ except ValueError :
496+ return False
497+
498+ async def _query_with_fallback (
499+ self ,
500+ * args : tuple [str , str , Optional [list [Any ]]],
501+ block_hash : Optional [str ] = None ,
502+ default_value : Any = ValueError ,
503+ ):
504+ """
505+ Queries the subtensor node with a given set of args, falling back to the next group if the method
506+ does not exist at the given block. This method exists to support backwards compatibility for blocks.
507+
508+ Parameters:
509+ *args: Tuples containing (module, storage_function, params) in the order they should be attempted.
510+ block_hash: The hash of the block being queried. If not provided, the chain tip will be used.
511+ default_value: The default value to return if none of the methods exist at the given block.
512+
513+ Returns:
514+ The value returned by the subtensor node, or the default value if none of the methods exist at the given
515+ block.
516+
517+ Raises:
518+ ValueError: If no default value is provided, and none of the methods exist at the given block, a
519+ ValueError will be raised.
520+
521+ Example:
522+ value = await self._query_with_fallback(
523+ # the first attempt will be made to SubtensorModule.MechanismEmissionSplit with params `[1]`
524+ ("SubtensorModule", "MechanismEmissionSplit", [1]),
525+ # if it does not exist at the given block, the next attempt will be made to
526+ # SubtensorModule.MechanismEmission with params `None`
527+ ("SubtensorModule", "MechanismEmission", None),
528+ block_hash="0x1234",
529+ # if none of the methods exist at the given block, the default value of `None` will be returned
530+ default_value=None,
531+ )
532+ """
533+ if block_hash is None :
534+ block_hash = await self .substrate .get_chain_head ()
535+ for module , storage_function , params in args :
536+ if await self .substrate .get_metadata_storage_function (
537+ module_name = module ,
538+ storage_name = storage_function ,
539+ block_hash = block_hash ,
540+ ):
541+ return await self .substrate .query (
542+ module = module ,
543+ storage_function = storage_function ,
544+ block_hash = block_hash ,
545+ params = params ,
546+ )
547+ if not isinstance (default_value , ValueError ):
548+ return default_value
549+ else :
550+ raise default_value
551+
552+ async def _runtime_call_with_fallback (
553+ self ,
554+ * args : tuple [str , str , Optional [list [Any ]] | dict [str , Any ]],
555+ block_hash : Optional [str ] = None ,
556+ default_value : Any = ValueError ,
557+ ):
558+ """
559+ Makes a runtime call to the subtensor node with a given set of args, falling back to the next group if the
560+ api.method does not exist at the given block. This method exists to support backwards compatibility for blocks.
561+
562+ Parameters:
563+ *args: Tuples containing (api, method, params) in the order they should be attempted.
564+ block_hash: The hash of the block being queried. If not provided, the chain tip will be used.
565+ default_value: The default value to return if none of the methods exist at the given block.
566+
567+ Raises:
568+ ValueError: If no default value is provided, and none of the methods exist at the given block, a
569+ ValueError will be raised.
570+
571+ Example:
572+ query = await self._runtime_call_with_fallback(
573+ # the first attempt will be made to SubnetInfoRuntimeApi.get_selective_mechagraph with the
574+ # given params
575+ (
576+ "SubnetInfoRuntimeApi",
577+ "get_selective_mechagraph",
578+ [netuid, mechid, [f for f in range(len(SelectiveMetagraphIndex))]],
579+ ),
580+ # if it does not exist at the given block, the next attempt will be made as such:
581+ ("SubnetInfoRuntimeApi", "get_metagraph", [[netuid]]),
582+ block_hash=block_hash,
583+ # if none of the methods exist at the given block, the default value will be returned
584+ default_value=None,
585+ )
586+
587+ """
588+ if block_hash is None :
589+ block_hash = await self .substrate .get_chain_head ()
590+ for api , method , params in args :
591+ if await self ._runtime_method_exists (
592+ api = api , method = method , block_hash = block_hash
593+ ):
594+ return await self .substrate .runtime_call (
595+ api = api ,
596+ method = method ,
597+ block_hash = block_hash ,
598+ params = params ,
599+ )
600+ if not isinstance (default_value , ValueError ):
601+ return default_value
602+ else :
603+ raise default_value
604+
463605 async def get_hyperparameter (
464606 self ,
465607 param_name : str ,
@@ -2667,11 +2809,10 @@ async def get_mechanism_emission_split(
26672809 whole numbers). Returns None if emission is evenly split or if the data is unavailable.
26682810 """
26692811 block_hash = await self .determine_block_hash (block , block_hash , reuse_block )
2670- result = await self .substrate .query (
2671- module = "SubtensorModule" ,
2672- storage_function = "MechanismEmissionSplit" ,
2673- params = [netuid ],
2812+ result = await self ._query_with_fallback (
2813+ ("SubtensorModule" , "MechanismEmissionSplit" , [netuid ]),
26742814 block_hash = block_hash ,
2815+ default_value = None ,
26752816 )
26762817 if result is None or not hasattr (result , "value" ):
26772818 return None
@@ -2697,11 +2838,10 @@ async def get_mechanism_count(
26972838 The number of mechanisms for the given subnet.
26982839 """
26992840 block_hash = await self .determine_block_hash (block , block_hash , reuse_block )
2700- query = await self .substrate .query (
2701- module = "SubtensorModule" ,
2702- storage_function = "MechanismCountCurrent" ,
2703- params = [netuid ],
2841+ query = await self ._query_with_fallback (
2842+ ("SubtensorModule" , "MechanismCountCurrent" , [netuid ]),
27042843 block_hash = block_hash ,
2844+ default_value = None ,
27052845 )
27062846 return getattr (query , "value" , 1 )
27072847
@@ -2763,22 +2903,43 @@ async def get_metagraph_info(
27632903 block_hash = await self .determine_block_hash (block , block_hash , reuse_block )
27642904 if not block_hash and reuse_block :
27652905 block_hash = self .substrate .last_block_hash
2906+ if not block_hash :
2907+ block_hash = await self .substrate .get_chain_head ()
27662908
2767- indexes = (
2768- [
2909+ # Normalize selected_indices to a list of integers
2910+ if selected_indices is not None :
2911+ indexes = [
27692912 f .value if isinstance (f , SelectiveMetagraphIndex ) else f
27702913 for f in selected_indices
27712914 ]
2772- if selected_indices is not None
2773- else [f for f in range (len (SelectiveMetagraphIndex ))]
2774- )
2915+ if 0 not in indexes :
2916+ indexes = [0 ] + indexes
2917+ query = await self ._runtime_call_with_fallback (
2918+ (
2919+ "SubnetInfoRuntimeApi" ,
2920+ "get_selective_mechagraph" ,
2921+ [netuid , mechid , indexes ],
2922+ ),
2923+ ("SubnetInfoRuntimeApi" , "get_selective_metagraph" , [netuid , indexes ]),
2924+ block_hash = block_hash ,
2925+ default_value = ValueError (
2926+ "You have specified `selected_indices` to retrieve metagraph info selectively, but the "
2927+ "selective runtime calls are not available at this block (probably too old). Do not specify "
2928+ "`selected_indices` to retrieve metagraph info selectively."
2929+ ),
2930+ )
2931+ else :
2932+ query = await self ._runtime_call_with_fallback (
2933+ (
2934+ "SubnetInfoRuntimeApi" ,
2935+ "get_selective_mechagraph" ,
2936+ [netuid , mechid , [f for f in range (len (SelectiveMetagraphIndex ))]],
2937+ ),
2938+ ("SubnetInfoRuntimeApi" , "get_metagraph" , [[netuid ]]),
2939+ block_hash = block_hash ,
2940+ default_value = None ,
2941+ )
27752942
2776- query = await self .substrate .runtime_call (
2777- api = "SubnetInfoRuntimeApi" ,
2778- method = "get_selective_mechagraph" ,
2779- params = [netuid , mechid , indexes if 0 in indexes else [0 ] + indexes ],
2780- block_hash = block_hash ,
2781- )
27822943 if getattr (query , "value" , None ) is None :
27832944 logging .error (
27842945 f"Subnet mechanism { netuid } .{ mechid if mechid else 0 } does not exist."
0 commit comments