23
23
24
24
import asyncstdlib as a
25
25
from 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
27
28
from 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
+ )
29
35
from websockets .asyncio .client import connect
30
36
from websockets .exceptions import ConnectionClosed
31
37
@@ -789,8 +795,54 @@ async def load_registry(self):
789
795
)
790
796
metadata_option_hex_str = metadata_rpc_result ["result" ]
791
797
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 )
794
846
795
847
async def decode_scale (
796
848
self ,
@@ -799,7 +851,7 @@ async def decode_scale(
799
851
_attempt = 1 ,
800
852
_retries = 3 ,
801
853
return_scale_obj = False ,
802
- ) -> Any :
854
+ ) -> Union [ ScaleObj , Any ] :
803
855
"""
804
856
Helper function to decode arbitrary SCALE-bytes (e.g. 0x02000000) according to given RUST type_string
805
857
(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(
815
867
Returns:
816
868
Decoded object
817
869
"""
818
-
819
- async def _wait_for_registry ():
820
- while self .registry is None :
821
- await asyncio .sleep (0.1 )
822
- return
823
-
824
870
if scale_bytes == b"\x00 " :
825
871
obj = None
826
872
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 )
830
878
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
- )
849
879
if return_scale_obj :
850
880
return ScaleObj (obj )
851
881
else :
852
882
return obj
853
883
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
-
874
884
async def _first_initialize_runtime (self ):
875
885
"""
876
886
TODO docstring
@@ -2173,7 +2183,7 @@ async def query_multi(
2173
2183
await self .decode_scale (
2174
2184
storage_key .value_scale_type , change_data
2175
2185
),
2176
- )
2186
+ ),
2177
2187
)
2178
2188
2179
2189
return result
@@ -2503,56 +2513,43 @@ async def runtime_call(
2503
2513
params = {}
2504
2514
2505
2515
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 ]
2512
2521
except KeyError :
2513
2522
raise ValueError (f"Runtime API Call '{ api } .{ method } ' not found in registry" )
2514
2523
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 " ]):
2516
2525
raise ValueError (
2517
2526
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 ' ])} "
2519
2528
)
2520
2529
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
-
2530
2530
# 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' ] } "
2534
2534
if isinstance (params , list ):
2535
- param_data += scale_obj . encode ( params [idx ])
2535
+ param_data += await self . encode_scale ( param_type_string , params [idx ])
2536
2536
else :
2537
2537
if param ["name" ] not in params :
2538
2538
raise ValueError (f"Runtime Call param '{ param ['name' ]} ' is missing" )
2539
2539
2540
- param_data += scale_obj .encode (params [param ["name" ]])
2540
+ param_data += await self .encode_scale (
2541
+ param_type_string , params [param ["name" ]]
2542
+ )
2541
2543
2542
2544
# RPC request
2543
2545
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 ]
2545
2547
)
2548
+ output_type_string = f"scale_info::{ runtime_call_def ['output' ]} "
2546
2549
2547
2550
# 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 ))
2556
2553
2557
2554
return result_obj
2558
2555
@@ -2581,7 +2578,7 @@ async def get_account_next_index(self, account_address: str) -> int:
2581
2578
"""
2582
2579
This method maintains a cache of nonces for each account ss58address.
2583
2580
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.
2585
2582
2586
2583
Args:
2587
2584
account_address: SS58 formatted address
@@ -2595,7 +2592,9 @@ async def get_account_next_index(self, account_address: str) -> int:
2595
2592
2596
2593
async with self ._lock :
2597
2594
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
+ )
2599
2598
self ._nonces [account_address ] = nonce_obj ["result" ]
2600
2599
else :
2601
2600
self ._nonces [account_address ] += 1
@@ -2686,8 +2685,7 @@ async def get_payment_info(
2686
2685
extrinsic = await self .create_signed_extrinsic (
2687
2686
call = call , keypair = keypair , signature = signature
2688
2687
)
2689
- extrinsic_len = self .runtime_config .create_scale_object ("u32" )
2690
- extrinsic_len .encode (len (extrinsic .data ))
2688
+ extrinsic_len = len (extrinsic .data )
2691
2689
2692
2690
result = await self .runtime_call (
2693
2691
"TransactionPaymentApi" , "query_info" , [extrinsic , extrinsic_len ]
0 commit comments