49
49
Preprocessed ,
50
50
)
51
51
from 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
+ )
52
56
from async_substrate_interface .utils .storage import StorageKey
57
+ from async_substrate_interface .type_registry import _TYPE_REGISTRY
53
58
54
59
if TYPE_CHECKING :
55
60
from websockets .asyncio .client import ClientConnection
@@ -706,6 +711,8 @@ def __init__(
706
711
ss58_format = self .ss58_format , implements_scale_info = True
707
712
)
708
713
self ._metadata_cache = {}
714
+ self ._metadata_v15_cache = {}
715
+ self ._old_metadata_v15 = None
709
716
self ._nonces = {}
710
717
self .metadata_version_hex = "0x0f000000" # v15
711
718
self .reload_type_registry ()
@@ -800,6 +807,20 @@ async def load_registry(self):
800
807
)
801
808
self .registry = PortableRegistry .from_metadata_v15 (self .metadata_v15 )
802
809
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
+
803
824
async def _wait_for_registry (self , _attempt : int = 1 , _retries : int = 3 ) -> None :
804
825
async def _waiter ():
805
826
while self .registry is None :
@@ -930,7 +951,10 @@ async def get_runtime(block_hash, block_id) -> Runtime:
930
951
if (
931
952
(block_hash and block_hash == self .last_block_hash )
932
953
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
+ ):
934
958
return Runtime (
935
959
self .chain ,
936
960
self .runtime_config ,
@@ -976,9 +1000,9 @@ async def get_runtime(block_hash, block_id) -> Runtime:
976
1000
f"No runtime information for block '{ block_hash } '"
977
1001
)
978
1002
# 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 ]
982
1006
):
983
1007
return Runtime (
984
1008
self .chain ,
@@ -1002,6 +1026,8 @@ async def get_runtime(block_hash, block_id) -> Runtime:
1002
1026
self .runtime_version
1003
1027
]
1004
1028
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.
1005
1031
metadata = self ._metadata = await self .get_block_metadata (
1006
1032
block_hash = runtime_block_hash , decode = True
1007
1033
)
@@ -1015,6 +1041,30 @@ async def get_runtime(block_hash, block_id) -> Runtime:
1015
1041
self ._metadata_cache [self .runtime_version ] = self ._metadata
1016
1042
else :
1017
1043
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
+
1018
1068
# Update type registry
1019
1069
self .reload_type_registry (use_remote_preset = False , auto_discover = True )
1020
1070
@@ -2487,6 +2537,56 @@ async def get_chain_finalised_head(self):
2487
2537
2488
2538
return response .get ("result" )
2489
2539
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
+
2490
2590
async def runtime_call (
2491
2591
self ,
2492
2592
api : str ,
@@ -2513,14 +2613,27 @@ async def runtime_call(
2513
2613
params = {}
2514
2614
2515
2615
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" ]}
2518
2626
api_entry = apis [api ]
2519
2627
methods = {entry ["name" ]: entry for entry in api_entry ["methods" ]}
2520
2628
runtime_call_def = methods [method ]
2521
2629
except KeyError :
2522
2630
raise ValueError (f"Runtime API Call '{ api } .{ method } ' not found in registry" )
2523
2631
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
+
2524
2637
if isinstance (params , list ) and len (params ) != len (runtime_call_def ["inputs" ]):
2525
2638
raise ValueError (
2526
2639
f"Number of parameter provided ({ len (params )} ) does not "
0 commit comments