66
77from bittensor_wallet .keypair import Keypair
88from bittensor_wallet .utils import SS58_FORMAT
9- from bt_decode import MetadataV15 , PortableRegistry , decode as decode_by_type_string
9+ from bt_decode import (
10+ MetadataV15 ,
11+ PortableRegistry ,
12+ decode as decode_by_type_string ,
13+ AxonInfo as OldAxonInfo ,
14+ PrometheusInfo as OldPrometheusInfo ,
15+ )
1016from scalecodec import (
1117 GenericCall ,
1218 GenericExtrinsic ,
3137)
3238from async_substrate_interface .utils import hex_to_bytes , json
3339from async_substrate_interface .utils .storage import StorageKey
40+ from async_substrate_interface .type_registry import _TYPE_REGISTRY
3441
3542
3643ResultHandler = Callable [[dict , Any ], tuple [dict , bool ]]
@@ -505,6 +512,8 @@ def __init__(
505512 ss58_format = self .ss58_format , implements_scale_info = True
506513 )
507514 self ._metadata_cache = {}
515+ self ._metadata_v15_cache = {}
516+ self ._old_metadata_v15 = None
508517 self .metadata_version_hex = "0x0f000000" # v15
509518 self .reload_type_registry ()
510519 if not _mock :
@@ -593,6 +602,20 @@ def load_registry(self):
593602 )
594603 self .registry = PortableRegistry .from_metadata_v15 (self .metadata_v15 )
595604
605+ def _load_registry_at_block (self , block_hash : str ) -> MetadataV15 :
606+ # Should be called for any block that fails decoding.
607+ # Possibly the metadata was different.
608+ metadata_rpc_result = self .rpc_request (
609+ "state_call" ,
610+ ["Metadata_metadata_at_version" , self .metadata_version_hex ],
611+ block_hash = block_hash ,
612+ )
613+ metadata_option_hex_str = metadata_rpc_result ["result" ]
614+ metadata_option_bytes = bytes .fromhex (metadata_option_hex_str [2 :])
615+ old_metadata = MetadataV15 .decode_from_metadata_option (metadata_option_bytes )
616+
617+ return old_metadata
618+
596619 def decode_scale (
597620 self ,
598621 type_string : str ,
@@ -634,6 +657,7 @@ def _first_initialize_runtime(self):
634657 metadata = self .get_block_metadata ()
635658 self ._metadata = metadata
636659 self ._metadata_cache [self .runtime_version ] = self ._metadata
660+ self ._metadata_v15_cache [self .runtime_version ] = self .metadata_v15
637661 self .runtime_version = runtime_info .get ("specVersion" )
638662 self .runtime_config .set_active_spec_version_id (self .runtime_version )
639663 self .transaction_version = runtime_info .get ("transactionVersion" )
@@ -755,6 +779,30 @@ def get_runtime(block_hash, block_id) -> Runtime:
755779 self ._metadata_cache [self .runtime_version ] = self ._metadata
756780 else :
757781 metadata = self ._metadata
782+
783+ if self .runtime_version in self ._metadata_v15_cache :
784+ # Get metadata v15 from cache
785+ logging .debug (
786+ "Retrieved metadata v15 for {} from memory" .format (
787+ self .runtime_version
788+ )
789+ )
790+ metadata_v15 = self ._old_metadata_v15 = self ._metadata_v15_cache [
791+ self .runtime_version
792+ ]
793+ else :
794+ metadata_v15 = self ._old_metadata_v15 = self ._load_registry_at_block (
795+ block_hash = runtime_block_hash
796+ )
797+ logging .debug (
798+ "Retrieved metadata v15 for {} from Substrate node" .format (
799+ self .runtime_version
800+ )
801+ )
802+
803+ # Update metadata v15 cache
804+ self ._metadata_v15_cache [self .runtime_version ] = metadata_v15
805+
758806 # Update type registry
759807 self .reload_type_registry (use_remote_preset = False , auto_discover = True )
760808
@@ -2202,6 +2250,61 @@ def get_chain_finalised_head(self):
22022250
22032251 return response .get ("result" )
22042252
2253+ def _do_runtime_call_old (
2254+ self ,
2255+ api : str ,
2256+ method : str ,
2257+ params : Optional [Union [list , dict ]] = None ,
2258+ block_hash : Optional [str ] = None ,
2259+ ) -> ScaleType :
2260+ runtime_call_def = _TYPE_REGISTRY ["runtime_api" ][api ]["methods" ][method ]
2261+
2262+ # Encode params
2263+ param_data = b""
2264+
2265+ if "encoder" in runtime_call_def :
2266+ param_data = runtime_call_def ["encoder" ](params )
2267+ else :
2268+ for idx , param in enumerate (runtime_call_def ["params" ]):
2269+ param_type_string = f"{ param ['type' ]} "
2270+ if isinstance (params , list ):
2271+ param_data += self .encode_scale (param_type_string , params [idx ])
2272+ else :
2273+ if param ["name" ] not in params :
2274+ raise ValueError (
2275+ f"Runtime Call param '{ param ['name' ]} ' is missing"
2276+ )
2277+
2278+ param_data += self .encode_scale (
2279+ param_type_string , params [param ["name" ]]
2280+ )
2281+
2282+ # RPC request
2283+ result_data = self .rpc_request (
2284+ "state_call" , [f"{ api } _{ method } " , param_data .hex (), block_hash ]
2285+ )
2286+ result_vec_u8_bytes = hex_to_bytes (result_data ["result" ])
2287+ result_bytes = self .decode_scale ("Vec<u8>" , result_vec_u8_bytes )
2288+
2289+ def _as_dict (obj ):
2290+ as_dict = {}
2291+ for key in dir (obj ):
2292+ if not key .startswith ("_" ):
2293+ val = getattr (obj , key )
2294+ if isinstance (val , (OldAxonInfo , OldPrometheusInfo )):
2295+ as_dict [key ] = _as_dict (val )
2296+ else :
2297+ as_dict [key ] = val
2298+ return as_dict
2299+
2300+ # Decode result
2301+ # Get correct type
2302+ result_decoded = runtime_call_def ["decoder" ](bytes (result_bytes ))
2303+ as_dict = _as_dict (result_decoded )
2304+ result_obj = ScaleObj (as_dict )
2305+
2306+ return result_obj
2307+
22052308 def runtime_call (
22062309 self ,
22072310 api : str ,
@@ -2228,14 +2331,54 @@ def runtime_call(
22282331 params = {}
22292332
22302333 try :
2231- metadata_v15 = self .metadata_v15 .value ()
2232- apis = {entry ["name" ]: entry for entry in metadata_v15 ["apis" ]}
2334+ if block_hash :
2335+ # Use old metadata v15 from init_runtime call
2336+ metadata_v15 = self ._old_metadata_v15
2337+ else :
2338+ metadata_v15 = self .metadata_v15
2339+
2340+ self .registry = PortableRegistry .from_metadata_v15 (metadata_v15 )
2341+ metadata_v15_value = metadata_v15 .value ()
2342+
2343+ apis = {entry ["name" ]: entry for entry in metadata_v15_value ["apis" ]}
22332344 api_entry = apis [api ]
22342345 methods = {entry ["name" ]: entry for entry in api_entry ["methods" ]}
22352346 runtime_call_def = methods [method ]
22362347 except KeyError :
22372348 raise ValueError (f"Runtime API Call '{ api } .{ method } ' not found in registry" )
22382349
2350+ # Check if the output type is a Vec<u8>
2351+ # If so, call the API using the old method
2352+ output_type_def = [
2353+ x
2354+ for x in metadata_v15_value ["types" ]["types" ]
2355+ if x ["id" ] == runtime_call_def ["output" ]
2356+ ]
2357+ if output_type_def :
2358+ output_type_def = output_type_def [0 ]
2359+
2360+ if "sequence" in output_type_def ["type" ]["def" ]:
2361+ output_type_seq_def_id = output_type_def ["type" ]["def" ]["sequence" ][
2362+ "type"
2363+ ]
2364+ output_type_seq_def = [
2365+ x
2366+ for x in metadata_v15_value ["types" ]["types" ]
2367+ if x ["id" ] == output_type_seq_def_id
2368+ ]
2369+ if output_type_seq_def :
2370+ output_type_seq_def = output_type_seq_def [0 ]
2371+ if (
2372+ "primitive" in output_type_seq_def ["type" ]["def" ]
2373+ and output_type_seq_def ["type" ]["def" ]["primitive" ] == "u8"
2374+ ):
2375+ # This is Vec<u8>
2376+ result = self ._do_runtime_call_old (
2377+ api , method , params , block_hash
2378+ )
2379+
2380+ return result
2381+
22392382 if isinstance (params , list ) and len (params ) != len (runtime_call_def ["inputs" ]):
22402383 raise ValueError (
22412384 f"Number of parameter provided ({ len (params )} ) does not "
0 commit comments