4949 Preprocessed ,
5050)
5151from async_substrate_interface .utils import hex_to_bytes , json
52+ from async_substrate_interface .utils .decoding import (
53+ _determine_if_old_runtime_call ,
54+ _bt_decode_to_dict_or_list ,
55+ )
5256from async_substrate_interface .utils .storage import StorageKey
57+ from async_substrate_interface .type_registry import _TYPE_REGISTRY
5358
5459if TYPE_CHECKING :
5560 from websockets .asyncio .client import ClientConnection
@@ -685,7 +690,7 @@ def __init__(
685690 self .ws = Websocket (
686691 url ,
687692 options = {
688- "max_size" : 2 ** 32 ,
693+ "max_size" : self . ws_max_size ,
689694 "write_limit" : 2 ** 16 ,
690695 },
691696 )
@@ -706,6 +711,8 @@ def __init__(
706711 ss58_format = self .ss58_format , implements_scale_info = True
707712 )
708713 self ._metadata_cache = {}
714+ self ._metadata_v15_cache = {}
715+ self ._old_metadata_v15 = None
709716 self ._nonces = {}
710717 self .metadata_version_hex = "0x0f000000" # v15
711718 self .reload_type_registry ()
@@ -800,6 +807,20 @@ async def load_registry(self):
800807 )
801808 self .registry = PortableRegistry .from_metadata_v15 (self .metadata_v15 )
802809
810+ async def _load_registry_at_block (self , block_hash : str ) -> MetadataV15 :
811+ # Should be called for any block that fails decoding.
812+ # Possibly the metadata was different.
813+ metadata_rpc_result = await self .rpc_request (
814+ "state_call" ,
815+ ["Metadata_metadata_at_version" , self .metadata_version_hex ],
816+ block_hash = block_hash ,
817+ )
818+ metadata_option_hex_str = metadata_rpc_result ["result" ]
819+ metadata_option_bytes = bytes .fromhex (metadata_option_hex_str [2 :])
820+ old_metadata = MetadataV15 .decode_from_metadata_option (metadata_option_bytes )
821+
822+ return old_metadata
823+
803824 async def _wait_for_registry (self , _attempt : int = 1 , _retries : int = 3 ) -> None :
804825 async def _waiter ():
805826 while self .registry is None :
@@ -930,7 +951,10 @@ async def get_runtime(block_hash, block_id) -> Runtime:
930951 if (
931952 (block_hash and block_hash == self .last_block_hash )
932953 or (block_id and block_id == self .block_id )
933- ) and self ._metadata is not None :
954+ ) and all (
955+ x is not None
956+ for x in [self ._metadata , self ._old_metadata_v15 , self .metadata_v15 ]
957+ ):
934958 return Runtime (
935959 self .chain ,
936960 self .runtime_config ,
@@ -976,9 +1000,9 @@ async def get_runtime(block_hash, block_id) -> Runtime:
9761000 f"No runtime information for block '{ block_hash } '"
9771001 )
9781002 # Check if runtime state already set to current block
979- if (
980- runtime_info . get ( "specVersion" ) == self . runtime_version
981- and self ._metadata is not None
1003+ if runtime_info . get ( "specVersion" ) == self . runtime_version and all (
1004+ x is not None
1005+ for x in [ self ._metadata , self . _old_metadata_v15 , self . metadata_v15 ]
9821006 ):
9831007 return Runtime (
9841008 self .chain ,
@@ -1002,6 +1026,8 @@ async def get_runtime(block_hash, block_id) -> Runtime:
10021026 self .runtime_version
10031027 ]
10041028 else :
1029+ # TODO when I get time, I'd like to add this and the metadata v15 as tasks with callbacks
1030+ # TODO to update the caches, but I don't have time now.
10051031 metadata = self ._metadata = await self .get_block_metadata (
10061032 block_hash = runtime_block_hash , decode = True
10071033 )
@@ -1015,6 +1041,30 @@ async def get_runtime(block_hash, block_id) -> Runtime:
10151041 self ._metadata_cache [self .runtime_version ] = self ._metadata
10161042 else :
10171043 metadata = self ._metadata
1044+
1045+ if self .runtime_version in self ._metadata_v15_cache :
1046+ # Get metadata v15 from cache
1047+ logging .debug (
1048+ "Retrieved metadata v15 for {} from memory" .format (
1049+ self .runtime_version
1050+ )
1051+ )
1052+ metadata_v15 = self ._old_metadata_v15 = self ._metadata_v15_cache [
1053+ self .runtime_version
1054+ ]
1055+ else :
1056+ metadata_v15 = (
1057+ self ._old_metadata_v15
1058+ ) = await self ._load_registry_at_block (block_hash = runtime_block_hash )
1059+ logging .debug (
1060+ "Retrieved metadata v15 for {} from Substrate node" .format (
1061+ self .runtime_version
1062+ )
1063+ )
1064+
1065+ # Update metadata v15 cache
1066+ self ._metadata_v15_cache [self .runtime_version ] = metadata_v15
1067+
10181068 # Update type registry
10191069 self .reload_type_registry (use_remote_preset = False , auto_discover = True )
10201070
@@ -2487,6 +2537,56 @@ async def get_chain_finalised_head(self):
24872537
24882538 return response .get ("result" )
24892539
2540+ async def _do_runtime_call_old (
2541+ self ,
2542+ api : str ,
2543+ method : str ,
2544+ params : Optional [Union [list , dict ]] = None ,
2545+ block_hash : Optional [str ] = None ,
2546+ ) -> ScaleType :
2547+ logging .debug (
2548+ f"Decoding old runtime call: { api } .{ method } with params: { params } at block hash: { block_hash } "
2549+ )
2550+ runtime_call_def = _TYPE_REGISTRY ["runtime_api" ][api ]["methods" ][method ]
2551+
2552+ # Encode params
2553+ param_data = b""
2554+
2555+ if "encoder" in runtime_call_def :
2556+ param_data = runtime_call_def ["encoder" ](params )
2557+ else :
2558+ for idx , param in enumerate (runtime_call_def ["params" ]):
2559+ param_type_string = f"{ param ['type' ]} "
2560+ if isinstance (params , list ):
2561+ param_data += await self .encode_scale (
2562+ param_type_string , params [idx ]
2563+ )
2564+ else :
2565+ if param ["name" ] not in params :
2566+ raise ValueError (
2567+ f"Runtime Call param '{ param ['name' ]} ' is missing"
2568+ )
2569+
2570+ param_data += await self .encode_scale (
2571+ param_type_string , params [param ["name" ]]
2572+ )
2573+
2574+ # RPC request
2575+ result_data = await self .rpc_request (
2576+ "state_call" , [f"{ api } _{ method } " , param_data .hex (), block_hash ]
2577+ )
2578+ result_vec_u8_bytes = hex_to_bytes (result_data ["result" ])
2579+ result_bytes = await self .decode_scale ("Vec<u8>" , result_vec_u8_bytes )
2580+
2581+ # Decode result
2582+ # Get correct type
2583+ result_decoded = runtime_call_def ["decoder" ](bytes (result_bytes ))
2584+ as_dict = _bt_decode_to_dict_or_list (result_decoded )
2585+ logging .debug ("Decoded old runtime call result: " , as_dict )
2586+ result_obj = ScaleObj (as_dict )
2587+
2588+ return result_obj
2589+
24902590 async def runtime_call (
24912591 self ,
24922592 api : str ,
@@ -2513,14 +2613,27 @@ async def runtime_call(
25132613 params = {}
25142614
25152615 try :
2516- metadata_v15 = self .metadata_v15 .value ()
2517- apis = {entry ["name" ]: entry for entry in metadata_v15 ["apis" ]}
2616+ if block_hash :
2617+ # Use old metadata v15 from init_runtime call
2618+ metadata_v15 = self ._old_metadata_v15
2619+ else :
2620+ metadata_v15 = self .metadata_v15
2621+
2622+ self .registry = PortableRegistry .from_metadata_v15 (metadata_v15 )
2623+ metadata_v15_value = metadata_v15 .value ()
2624+
2625+ apis = {entry ["name" ]: entry for entry in metadata_v15_value ["apis" ]}
25182626 api_entry = apis [api ]
25192627 methods = {entry ["name" ]: entry for entry in api_entry ["methods" ]}
25202628 runtime_call_def = methods [method ]
25212629 except KeyError :
25222630 raise ValueError (f"Runtime API Call '{ api } .{ method } ' not found in registry" )
25232631
2632+ if _determine_if_old_runtime_call (runtime_call_def , metadata_v15_value ):
2633+ result = await self ._do_runtime_call_old (api , method , params , block_hash )
2634+
2635+ return result
2636+
25242637 if isinstance (params , list ) and len (params ) != len (runtime_call_def ["inputs" ]):
25252638 raise ValueError (
25262639 f"Number of parameter provided ({ len (params )} ) does not "
0 commit comments