2323
2424import asyncstdlib as a
2525from bittensor_wallet .keypair import Keypair
26- from bt_decode import PortableRegistry , decode as decode_by_type_string , MetadataV15
26+ from bittensor_wallet .utils import SS58_FORMAT
27+ from bt_decode import MetadataV15 , PortableRegistry , decode as decode_by_type_string
2728from scalecodec .base import ScaleBytes , ScaleType , RuntimeConfigurationObject
28- from scalecodec .types import GenericCall , GenericRuntimeCallDefinition , GenericExtrinsic
29+ from scalecodec .types import (
30+ GenericCall ,
31+ GenericExtrinsic ,
32+ GenericRuntimeCallDefinition ,
33+ ss58_decode ,
34+ )
2935from websockets .asyncio .client import connect
3036from websockets .exceptions import ConnectionClosed
3137
@@ -789,8 +795,54 @@ async def load_registry(self):
789795 )
790796 metadata_option_hex_str = metadata_rpc_result ["result" ]
791797 metadata_option_bytes = bytes .fromhex (metadata_option_hex_str [2 :])
792- metadata_v15 = MetadataV15 .decode_from_metadata_option (metadata_option_bytes )
793- self .registry = PortableRegistry .from_metadata_v15 (metadata_v15 )
798+ self .metadata_v15 = MetadataV15 .decode_from_metadata_option (
799+ metadata_option_bytes
800+ )
801+ self .registry = PortableRegistry .from_metadata_v15 (self .metadata_v15 )
802+
803+ async def _wait_for_registry (self , _attempt : int = 1 , _retries : int = 3 ) -> None :
804+ async def _waiter ():
805+ while self .registry is None :
806+ await asyncio .sleep (0.1 )
807+ return
808+
809+ try :
810+ if not self .registry :
811+ await asyncio .wait_for (_waiter (), timeout = 10 )
812+ except TimeoutError :
813+ # indicates that registry was never loaded
814+ if not self ._initializing :
815+ raise AttributeError (
816+ "Registry was never loaded. This did not occur during initialization, which usually indicates "
817+ "you must first initialize the AsyncSubstrateInterface object, either with "
818+ "`await AsyncSubstrateInterface.initialize()` or running with `async with`"
819+ )
820+ elif _attempt < _retries :
821+ await self .load_registry ()
822+ return await self ._wait_for_registry (_attempt + 1 , _retries )
823+ else :
824+ raise AttributeError (
825+ "Registry was never loaded. This occurred during initialization, which usually indicates a "
826+ "connection or node error."
827+ )
828+
829+ async def encode_scale (
830+ self , type_string , value : Any , _attempt : int = 1 , _retries : int = 3
831+ ) -> bytes :
832+ """
833+ Helper function to encode arbitrary data into SCALE-bytes for given RUST type_string
834+
835+ Args:
836+ type_string: the type string of the SCALE object for decoding
837+ value: value to encode
838+ _attempt: the current number of attempts to load the registry needed to encode the value
839+ _retries: the maximum number of attempts to load the registry needed to encode the value
840+
841+ Returns:
842+ encoded bytes
843+ """
844+ await self ._wait_for_registry (_attempt , _retries )
845+ return self ._encode_scale (type_string , value )
794846
795847 async def decode_scale (
796848 self ,
@@ -799,7 +851,7 @@ async def decode_scale(
799851 _attempt = 1 ,
800852 _retries = 3 ,
801853 return_scale_obj = False ,
802- ) -> Any :
854+ ) -> Union [ ScaleObj , Any ] :
803855 """
804856 Helper function to decode arbitrary SCALE-bytes (e.g. 0x02000000) according to given RUST type_string
805857 (e.g. BlockNumber). The relevant versioning information of the type (if defined) will be applied if block_hash
@@ -815,62 +867,20 @@ async def decode_scale(
815867 Returns:
816868 Decoded object
817869 """
818-
819- async def _wait_for_registry ():
820- while self .registry is None :
821- await asyncio .sleep (0.1 )
822- return
823-
824870 if scale_bytes == b"\x00 " :
825871 obj = None
826872 else :
827- try :
828- if not self .registry :
829- await asyncio .wait_for (_wait_for_registry (), timeout = 10 )
873+ if type_string == "scale_info::0" : # Is an AccountId
874+ # Decode AccountId bytes to SS58 address
875+ return bytes .fromhex (ss58_decode (scale_bytes , SS58_FORMAT ))
876+ else :
877+ await self ._wait_for_registry (_attempt , _retries )
830878 obj = decode_by_type_string (type_string , self .registry , scale_bytes )
831- except TimeoutError :
832- # indicates that registry was never loaded
833- if not self ._initializing :
834- raise AttributeError (
835- "Registry was never loaded. This did not occur during initialization, which usually indicates "
836- "you must first initialize the AsyncSubstrateInterface object, either with "
837- "`await AsyncSubstrateInterface.initialize()` or running with `async with`"
838- )
839- elif _attempt < _retries :
840- await self .load_registry ()
841- return await self .decode_scale (
842- type_string , scale_bytes , _attempt + 1
843- )
844- else :
845- raise AttributeError (
846- "Registry was never loaded. This occurred during initialization, which usually indicates a "
847- "connection or node error."
848- )
849879 if return_scale_obj :
850880 return ScaleObj (obj )
851881 else :
852882 return obj
853883
854- async def encode_scale (self , type_string , value , block_hash = None ) -> ScaleBytes :
855- """
856- Helper function to encode arbitrary data into SCALE-bytes for given RUST type_string
857-
858- Args:
859- type_string: the type string of the SCALE object for decoding
860- value: value to encode
861- block_hash: the hash of the blockchain block whose metadata to use for encoding
862-
863- Returns:
864- ScaleBytes encoded value
865- """
866- if not self ._metadata or block_hash :
867- await self .init_runtime (block_hash = block_hash )
868-
869- obj = self .runtime_config .create_scale_object (
870- type_string = type_string , metadata = self ._metadata
871- )
872- return obj .encode (value )
873-
874884 async def _first_initialize_runtime (self ):
875885 """
876886 TODO docstring
@@ -2173,7 +2183,7 @@ async def query_multi(
21732183 await self .decode_scale (
21742184 storage_key .value_scale_type , change_data
21752185 ),
2176- )
2186+ ),
21772187 )
21782188
21792189 return result
@@ -2503,56 +2513,43 @@ async def runtime_call(
25032513 params = {}
25042514
25052515 try :
2506- runtime_call_def = self .runtime_config .type_registry ["runtime_api" ][api ][
2507- "methods"
2508- ][method ]
2509- runtime_api_types = self .runtime_config .type_registry ["runtime_api" ][
2510- api
2511- ].get ("types" , {})
2516+ metadata_v15 = self .metadata_v15 .value ()
2517+ apis = {entry ["name" ]: entry for entry in metadata_v15 ["apis" ]}
2518+ api_entry = apis [api ]
2519+ methods = {entry ["name" ]: entry for entry in api_entry ["methods" ]}
2520+ runtime_call_def = methods [method ]
25122521 except KeyError :
25132522 raise ValueError (f"Runtime API Call '{ api } .{ method } ' not found in registry" )
25142523
2515- if isinstance (params , list ) and len (params ) != len (runtime_call_def ["params " ]):
2524+ if isinstance (params , list ) and len (params ) != len (runtime_call_def ["inputs " ]):
25162525 raise ValueError (
25172526 f"Number of parameter provided ({ len (params )} ) does not "
2518- f"match definition { len (runtime_call_def ['params ' ])} "
2527+ f"match definition { len (runtime_call_def ['inputs ' ])} "
25192528 )
25202529
2521- # Add runtime API types to registry
2522- self .runtime_config .update_type_registry_types (runtime_api_types )
2523- runtime = Runtime (
2524- self .chain ,
2525- self .runtime_config ,
2526- self ._metadata ,
2527- self .type_registry ,
2528- )
2529-
25302530 # Encode params
2531- param_data = ScaleBytes ( bytes ())
2532- for idx , param in enumerate (runtime_call_def ["params " ]):
2533- scale_obj = runtime . runtime_config . create_scale_object ( param ["type" ])
2531+ param_data = b""
2532+ for idx , param in enumerate (runtime_call_def ["inputs " ]):
2533+ param_type_string = f"scale_info:: { param ['ty' ] } "
25342534 if isinstance (params , list ):
2535- param_data += scale_obj . encode ( params [idx ])
2535+ param_data += await self . encode_scale ( param_type_string , params [idx ])
25362536 else :
25372537 if param ["name" ] not in params :
25382538 raise ValueError (f"Runtime Call param '{ param ['name' ]} ' is missing" )
25392539
2540- param_data += scale_obj .encode (params [param ["name" ]])
2540+ param_data += await self .encode_scale (
2541+ param_type_string , params [param ["name" ]]
2542+ )
25412543
25422544 # RPC request
25432545 result_data = await self .rpc_request (
2544- "state_call" , [f"{ api } _{ method } " , str ( param_data ), block_hash ]
2546+ "state_call" , [f"{ api } _{ method } " , param_data . hex ( ), block_hash ]
25452547 )
2548+ output_type_string = f"scale_info::{ runtime_call_def ['output' ]} "
25462549
25472550 # Decode result
2548- # TODO update this to use bt-decode
2549- result_obj = runtime .runtime_config .create_scale_object (
2550- runtime_call_def ["type" ]
2551- )
2552- result_obj .decode (
2553- ScaleBytes (result_data ["result" ]),
2554- check_remaining = self .config .get ("strict_scale_decode" ),
2555- )
2551+ result_bytes = hex_to_bytes (result_data ["result" ])
2552+ result_obj = ScaleObj (await self .decode_scale (output_type_string , result_bytes ))
25562553
25572554 return result_obj
25582555
@@ -2581,7 +2578,7 @@ async def get_account_next_index(self, account_address: str) -> int:
25812578 """
25822579 This method maintains a cache of nonces for each account ss58address.
25832580 Upon subsequent calls, it will return the cached nonce + 1 instead of fetching from the chain.
2584- This allows for correct nonce management in-case of async context when gathering co-routines.
2581+ This allows for correct nonce management in-case of async context when gathering co-routines.
25852582
25862583 Args:
25872584 account_address: SS58 formatted address
@@ -2595,7 +2592,9 @@ async def get_account_next_index(self, account_address: str) -> int:
25952592
25962593 async with self ._lock :
25972594 if self ._nonces .get (account_address ) is None :
2598- nonce_obj = await self .rpc_request ("account_nextIndex" , [account_address ])
2595+ nonce_obj = await self .rpc_request (
2596+ "account_nextIndex" , [account_address ]
2597+ )
25992598 self ._nonces [account_address ] = nonce_obj ["result" ]
26002599 else :
26012600 self ._nonces [account_address ] += 1
@@ -2686,8 +2685,7 @@ async def get_payment_info(
26862685 extrinsic = await self .create_signed_extrinsic (
26872686 call = call , keypair = keypair , signature = signature
26882687 )
2689- extrinsic_len = self .runtime_config .create_scale_object ("u32" )
2690- extrinsic_len .encode (len (extrinsic .data ))
2688+ extrinsic_len = len (extrinsic .data )
26912689
26922690 result = await self .runtime_call (
26932691 "TransactionPaymentApi" , "query_info" , [extrinsic , extrinsic_len ]
0 commit comments