2424import asyncstdlib as a
2525from bittensor_wallet .keypair import Keypair
2626from bittensor_wallet .utils import SS58_FORMAT
27- from bt_decode import MetadataV15 , PortableRegistry , decode as decode_by_type_string
27+ from bt_decode import (
28+ MetadataV15 ,
29+ PortableRegistry ,
30+ decode as decode_by_type_string ,
31+ AxonInfo as OldAxonInfo ,
32+ PrometheusInfo as OldPrometheusInfo ,
33+ )
2834from scalecodec .base import ScaleBytes , ScaleType , RuntimeConfigurationObject
2935from scalecodec .types import (
3036 GenericCall ,
4955 Preprocessed ,
5056)
5157from async_substrate_interface .utils import hex_to_bytes , json
58+ from async_substrate_interface .utils .decoding import _determine_if_old_runtime_call
5259from async_substrate_interface .utils .storage import StorageKey
60+ from async_substrate_interface .type_registry import _TYPE_REGISTRY
5361
5462if TYPE_CHECKING :
5563 from websockets .asyncio .client import ClientConnection
@@ -706,6 +714,8 @@ def __init__(
706714 ss58_format = self .ss58_format , implements_scale_info = True
707715 )
708716 self ._metadata_cache = {}
717+ self ._metadata_v15_cache = {}
718+ self ._old_metadata_v15 = None
709719 self ._nonces = {}
710720 self .metadata_version_hex = "0x0f000000" # v15
711721 self .reload_type_registry ()
@@ -800,6 +810,20 @@ async def load_registry(self):
800810 )
801811 self .registry = PortableRegistry .from_metadata_v15 (self .metadata_v15 )
802812
813+ async def _load_registry_at_block (self , block_hash : str ) -> MetadataV15 :
814+ # Should be called for any block that fails decoding.
815+ # Possibly the metadata was different.
816+ metadata_rpc_result = await self .rpc_request (
817+ "state_call" ,
818+ ["Metadata_metadata_at_version" , self .metadata_version_hex ],
819+ block_hash = block_hash ,
820+ )
821+ metadata_option_hex_str = metadata_rpc_result ["result" ]
822+ metadata_option_bytes = bytes .fromhex (metadata_option_hex_str [2 :])
823+ old_metadata = MetadataV15 .decode_from_metadata_option (metadata_option_bytes )
824+
825+ return old_metadata
826+
803827 async def _wait_for_registry (self , _attempt : int = 1 , _retries : int = 3 ) -> None :
804828 async def _waiter ():
805829 while self .registry is None :
@@ -890,6 +914,7 @@ async def _first_initialize_runtime(self):
890914 )
891915 self ._metadata = metadata
892916 self ._metadata_cache [self .runtime_version ] = self ._metadata
917+ self ._metadata_v15_cache [self .runtime_version ] = self .metadata_v15
893918 self .runtime_version = runtime_info .get ("specVersion" )
894919 self .runtime_config .set_active_spec_version_id (self .runtime_version )
895920 self .transaction_version = runtime_info .get ("transactionVersion" )
@@ -1015,6 +1040,30 @@ async def get_runtime(block_hash, block_id) -> Runtime:
10151040 self ._metadata_cache [self .runtime_version ] = self ._metadata
10161041 else :
10171042 metadata = self ._metadata
1043+
1044+ if self .runtime_version in self ._metadata_v15_cache :
1045+ # Get metadata v15 from cache
1046+ logging .debug (
1047+ "Retrieved metadata v15 for {} from memory" .format (
1048+ self .runtime_version
1049+ )
1050+ )
1051+ metadata_v15 = self ._old_metadata_v15 = self ._metadata_v15_cache [
1052+ self .runtime_version
1053+ ]
1054+ else :
1055+ metadata_v15 = (
1056+ self ._old_metadata_v15
1057+ ) = await self ._load_registry_at_block (block_hash = runtime_block_hash )
1058+ logging .debug (
1059+ "Retrieved metadata v15 for {} from Substrate node" .format (
1060+ self .runtime_version
1061+ )
1062+ )
1063+
1064+ # Update metadata v15 cache
1065+ self ._metadata_v15_cache [self .runtime_version ] = metadata_v15
1066+
10181067 # Update type registry
10191068 self .reload_type_registry (use_remote_preset = False , auto_discover = True )
10201069
@@ -2487,6 +2536,67 @@ async def get_chain_finalised_head(self):
24872536
24882537 return response .get ("result" )
24892538
2539+ async def _do_runtime_call_old (
2540+ self ,
2541+ api : str ,
2542+ method : str ,
2543+ params : Optional [Union [list , dict ]] = None ,
2544+ block_hash : Optional [str ] = None ,
2545+ ) -> ScaleType :
2546+ logging .debug (
2547+ f"Decoding old runtime call: { api } .{ method } with params: { params } at block hash: { block_hash } "
2548+ )
2549+ runtime_call_def = _TYPE_REGISTRY ["runtime_api" ][api ]["methods" ][method ]
2550+
2551+ # Encode params
2552+ param_data = b""
2553+
2554+ if "encoder" in runtime_call_def :
2555+ param_data = runtime_call_def ["encoder" ](params )
2556+ else :
2557+ for idx , param in enumerate (runtime_call_def ["params" ]):
2558+ param_type_string = f"{ param ['type' ]} "
2559+ if isinstance (params , list ):
2560+ param_data += await self .encode_scale (
2561+ param_type_string , params [idx ]
2562+ )
2563+ else :
2564+ if param ["name" ] not in params :
2565+ raise ValueError (
2566+ f"Runtime Call param '{ param ['name' ]} ' is missing"
2567+ )
2568+
2569+ param_data += await self .encode_scale (
2570+ param_type_string , params [param ["name" ]]
2571+ )
2572+
2573+ # RPC request
2574+ result_data = await self .rpc_request (
2575+ "state_call" , [f"{ api } _{ method } " , param_data .hex (), block_hash ]
2576+ )
2577+ result_vec_u8_bytes = hex_to_bytes (result_data ["result" ])
2578+ result_bytes = await self .decode_scale ("Vec<u8>" , result_vec_u8_bytes )
2579+
2580+ def _as_dict (obj ):
2581+ as_dict = {}
2582+ for key in dir (obj ):
2583+ if not key .startswith ("_" ):
2584+ val = getattr (obj , key )
2585+ if isinstance (val , (OldAxonInfo , OldPrometheusInfo )):
2586+ as_dict [key ] = _as_dict (val )
2587+ else :
2588+ as_dict [key ] = val
2589+ return as_dict
2590+
2591+ # Decode result
2592+ # Get correct type
2593+ result_decoded = runtime_call_def ["decoder" ](bytes (result_bytes ))
2594+ as_dict = _as_dict (result_decoded )
2595+ logging .debug ("Decoded old runtime call result: " , as_dict )
2596+ result_obj = ScaleObj (as_dict )
2597+
2598+ return result_obj
2599+
24902600 async def runtime_call (
24912601 self ,
24922602 api : str ,
@@ -2513,14 +2623,27 @@ async def runtime_call(
25132623 params = {}
25142624
25152625 try :
2516- metadata_v15 = self .metadata_v15 .value ()
2517- apis = {entry ["name" ]: entry for entry in metadata_v15 ["apis" ]}
2626+ if block_hash :
2627+ # Use old metadata v15 from init_runtime call
2628+ metadata_v15 = self ._old_metadata_v15
2629+ else :
2630+ metadata_v15 = self .metadata_v15
2631+
2632+ self .registry = PortableRegistry .from_metadata_v15 (metadata_v15 )
2633+ metadata_v15_value = metadata_v15 .value ()
2634+
2635+ apis = {entry ["name" ]: entry for entry in metadata_v15_value ["apis" ]}
25182636 api_entry = apis [api ]
25192637 methods = {entry ["name" ]: entry for entry in api_entry ["methods" ]}
25202638 runtime_call_def = methods [method ]
25212639 except KeyError :
25222640 raise ValueError (f"Runtime API Call '{ api } .{ method } ' not found in registry" )
25232641
2642+ if _determine_if_old_runtime_call (runtime_call_def , metadata_v15_value ):
2643+ result = await self ._do_runtime_call_old (api , method , params , block_hash )
2644+
2645+ return result
2646+
25242647 if isinstance (params , list ) and len (params ) != len (runtime_call_def ["inputs" ]):
25252648 raise ValueError (
25262649 f"Number of parameter provided ({ len (params )} ) does not "
0 commit comments