Skip to content

Commit 652ec0c

Browse files
committed
Reusable
1 parent 2cfa23d commit 652ec0c

File tree

1 file changed

+95
-59
lines changed

1 file changed

+95
-59
lines changed

bittensor/core/async_subtensor.py

Lines changed: 95 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,95 @@ async def encode_params(
500500

501501
return param_data.to_hex()
502502

503+
async def _runtime_method_exists(
504+
self, api: str, method: str, block_hash: str
505+
) -> bool:
506+
"""
507+
Check if a runtime call method exists at the given block.
508+
509+
The complicated logic here comes from the fact that there are two ways in which runtime calls
510+
are stored: the new and primary method is through the Metadata V15, but the V14 is a good backup (implemented
511+
around mid 2024)
512+
513+
Returns:
514+
True if the runtime call method exists, False otherwise.
515+
"""
516+
runtime = await self.substrate.init_runtime(block_hash=block_hash)
517+
if runtime.metadata_v15 is not None:
518+
metadata_v15_value = runtime.metadata_v15.value()
519+
apis = {entry["name"]: entry for entry in metadata_v15_value["apis"]}
520+
try:
521+
api_entry = apis[api]
522+
methods = {entry["name"]: entry for entry in api_entry["methods"]}
523+
_ = methods[method]
524+
return True
525+
except KeyError:
526+
return False
527+
else:
528+
try:
529+
await self.substrate.get_metadata_runtime_call_function(
530+
api=api,
531+
method=method,
532+
block_hash=block_hash,
533+
)
534+
return True
535+
except ValueError:
536+
return False
537+
538+
async def _query_with_fallback(
539+
self,
540+
*args: tuple[str, str, Optional[list[Any]]],
541+
block_hash: Optional[str] = None,
542+
default_value: Any = ValueError,
543+
):
544+
"""
545+
Queries the subtensor node with a given set of args, falling back to the next group if the method
546+
does not exist at the given block. This method exists to support backwards compatibility for blocks.
547+
548+
Parameters:
549+
*args: Tuples containing (module, storage_function, params) in the order they should be attempted.
550+
block_hash: The hash of the block being queried. If not provided, the chain tip will be used.
551+
default_value: The default value to return if none of the methods exist at the given block.
552+
553+
Returns:
554+
The value returned by the subtensor node, or the default value if none of the methods exist at the given
555+
block.
556+
557+
Raises:
558+
ValueError: If no default value is provided, and none of the methods exist at the given block, a
559+
ValueError will be raised.
560+
561+
Example:
562+
value = await self._query_with_fallback(
563+
# the first attempt will be made to SubtensorModule.MechanismEmissionSplit with params `[1]`
564+
("SubtensorModule", "MechanismEmissionSplit", [1]),
565+
# if it does not exist at the given block, the next attempt will be made to
566+
# SubtensorModule.MechanismEmission with params `None`
567+
("SubtensorModule", "MechanismEmission", None),
568+
block_hash="0x1234",
569+
# if none of the methods exist at the given block, the default value of `None` will be returned
570+
default_value=None,
571+
)
572+
"""
573+
if block_hash is None:
574+
block_hash = await self.substrate.get_chain_head()
575+
for module, storage_function, params in args:
576+
if await self.substrate.get_metadata_storage_function(
577+
module_name=module,
578+
storage_name=storage_function,
579+
block_hash=block_hash,
580+
):
581+
return await self.substrate.query(
582+
module=module,
583+
storage_function=storage_function,
584+
block_hash=block_hash,
585+
params=params,
586+
)
587+
if not isinstance(default_value, ValueError):
588+
return default_value
589+
else:
590+
raise default_value
591+
503592
async def get_hyperparameter(
504593
self,
505594
param_name: str,
@@ -2673,19 +2762,10 @@ async def get_mechanism_emission_split(
26732762
whole numbers). Returns None if emission is evenly split or if the data is unavailable.
26742763
"""
26752764
block_hash = await self.determine_block_hash(block, block_hash, reuse_block)
2676-
module = "SubtensorModule"
2677-
storage_function = "MechanismEmissionSplit"
2678-
if not await self.substrate.get_metadata_storage_function(
2679-
module,
2680-
storage_function,
2681-
block_hash=block_hash,
2682-
):
2683-
return None
2684-
result = await self.substrate.query(
2685-
module=module,
2686-
storage_function=storage_function,
2687-
params=[netuid],
2765+
result = await self._query_with_fallback(
2766+
("SubtensorModule", "MechanismEmissionSplit", [netuid]),
26882767
block_hash=block_hash,
2768+
default_value=None
26892769
)
26902770
if result is None or not hasattr(result, "value"):
26912771
return None
@@ -2711,57 +2791,13 @@ async def get_mechanism_count(
27112791
The number of mechanisms for the given subnet.
27122792
"""
27132793
block_hash = await self.determine_block_hash(block, block_hash, reuse_block)
2714-
module = "SubtensorModule"
2715-
storage_function = "MechanismCountCurrent"
2716-
if not await self.substrate.get_metadata_storage_function(
2717-
module,
2718-
storage_function,
2719-
block_hash=block_hash,
2720-
):
2721-
return 1
2722-
query = await self.substrate.query(
2723-
module=module,
2724-
storage_function=storage_function,
2725-
params=[netuid],
2794+
query = await self._query_with_fallback(
2795+
("SubtensorModule", "MechanismCountCurrent", [netuid]),
27262796
block_hash=block_hash,
2797+
default_value=None,
27272798
)
27282799
return getattr(query, "value", 1)
27292800

2730-
async def _runtime_method_exists(
2731-
self, api: str, method: str, block_hash: str
2732-
) -> bool:
2733-
"""
2734-
Check if a runtime call method exists at the given block.
2735-
2736-
The complicated logic here comes from the fact that there are two ways in which runtime calls
2737-
are stored: the new and primary method is through the Metadata V15, but the V14 is a good backup (implemented
2738-
around mid 2024)
2739-
2740-
Returns:
2741-
True if the runtime call method exists, False otherwise.
2742-
"""
2743-
runtime = await self.substrate.init_runtime(block_hash=block_hash)
2744-
if runtime.metadata_v15 is not None:
2745-
metadata_v15_value = runtime.metadata_v15.value()
2746-
apis = {entry["name"]: entry for entry in metadata_v15_value["apis"]}
2747-
try:
2748-
api_entry = apis[api]
2749-
methods = {entry["name"]: entry for entry in api_entry["methods"]}
2750-
_ = methods[method]
2751-
return True
2752-
except KeyError:
2753-
return False
2754-
else:
2755-
try:
2756-
await self.substrate.get_metadata_runtime_call_function(
2757-
api=api,
2758-
method=method,
2759-
block_hash=block_hash,
2760-
)
2761-
return True
2762-
except ValueError:
2763-
return False
2764-
27652801
async def get_metagraph_info(
27662802
self,
27672803
netuid: int,

0 commit comments

Comments
 (0)