From 3264159f621d641b307f07ab9047b404a56bd289 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Thu, 16 Jan 2025 16:08:38 +0200 Subject: [PATCH 1/2] Adds a warning for uninitialised Substrate --- .../substrate_interface.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/async_substrate_interface/substrate_interface.py b/async_substrate_interface/substrate_interface.py index 4661a87..402717c 100644 --- a/async_substrate_interface/substrate_interface.py +++ b/async_substrate_interface/substrate_interface.py @@ -2496,15 +2496,22 @@ async def get_block_metadata( raise SubstrateRequestException(response["error"]["message"]) if response.get("result") and decode: - metadata_decoder = self.runtime_config.create_scale_object( - "MetadataVersioned", data=ScaleBytes(response.get("result")) - ) - metadata_decoder.decode() + try: + metadata_decoder = self.runtime_config.create_scale_object( + "MetadataVersioned", data=ScaleBytes(response.get("result")) + ) + metadata_decoder.decode() + except NotImplementedError: + if not self.initialized: + raise SubstrateRequestException( + "You are attempting to decode a SCALE object before Runtime initialization. You need to first " + "either `await AsyncSubstrateInterface.initialize()` or use `async with` the object." + ) + else: + raise return metadata_decoder - return response - async def _preprocess( self, query_for: Optional[list], From 95a0d424803cdc16d98b79bc68cc5487350a6eb8 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Thu, 16 Jan 2025 16:54:14 +0200 Subject: [PATCH 2/2] Raise the error in the correct place. Move `reload_type_registry()` to `__init__`, as it works now that chain is always a string. --- .../substrate_interface.py | 48 +++++++++++-------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/async_substrate_interface/substrate_interface.py b/async_substrate_interface/substrate_interface.py index 402717c..8def9c4 100644 --- a/async_substrate_interface/substrate_interface.py +++ b/async_substrate_interface/substrate_interface.py @@ -1089,6 +1089,8 @@ def __init__( else: self.query_map_result_cls = QueryMapResult self.extrinsic_receipt_cls = AsyncExtrinsicReceipt + self.reload_type_registry() + self._initializing = False async def __aenter__(self): await self.initialize() @@ -1099,13 +1101,14 @@ async def initialize(self): Initialize the connection to the chain. """ async with self._lock: + self._initializing = True if not self.initialized: if not self.__chain: chain = await self.rpc_request("system_chain", []) self.__chain = chain.get("result") - self.reload_type_registry() await asyncio.gather(self.load_registry(), self._init_init_runtime()) self.initialized = True + self._initializing = False async def __aexit__(self, exc_type, exc_val, exc_tb): pass @@ -1248,19 +1251,28 @@ async def _wait_for_registry(): if scale_bytes == b"\x00": obj = None else: - if not self.registry: - await asyncio.wait_for(_wait_for_registry(), timeout=10) try: + if not self.registry: + await asyncio.wait_for(_wait_for_registry(), timeout=10) obj = decode_by_type_string(type_string, self.registry, scale_bytes) except TimeoutError: # indicates that registry was never loaded - if _attempt < _retries: + if not self._initializing: + raise AttributeError( + "Registry was never loaded. This did not occur during initialization, which usually indicates " + "you must first initialize the AsyncSubstrateInterface object, either with " + "`await AsyncSubstrateInterface.initialize()` or running with `async with`" + ) + elif _attempt < _retries: await self.load_registry() return await self.decode_scale( type_string, scale_bytes, _attempt + 1 ) else: - raise ValueError("Registry was never loaded.") + raise AttributeError( + "Registry was never loaded. This occurred during initialization, which usually indicates a " + "connection or node error." + ) if return_scale_obj: return ScaleObj(obj) else: @@ -2471,7 +2483,7 @@ async def get_block_runtime_version(self, block_hash: str) -> dict: async def get_block_metadata( self, block_hash: Optional[str] = None, decode: bool = True - ) -> Union[dict, ScaleType]: + ) -> Optional[Union[dict, ScaleType]]: """ A pass-though to existing JSONRPC method `state_getMetadata`. @@ -2480,7 +2492,8 @@ async def get_block_metadata( decode: Whether to decode the metadata or present it raw Returns: - metadata, either as a dict (not decoded) or ScaleType (decoded) + metadata, either as a dict (not decoded) or ScaleType (decoded); None if there was no response + from the server """ params = None if decode and not self.runtime_config: @@ -2495,22 +2508,15 @@ async def get_block_metadata( if "error" in response: raise SubstrateRequestException(response["error"]["message"]) - if response.get("result") and decode: - try: - metadata_decoder = self.runtime_config.create_scale_object( - "MetadataVersioned", data=ScaleBytes(response.get("result")) - ) - metadata_decoder.decode() - except NotImplementedError: - if not self.initialized: - raise SubstrateRequestException( - "You are attempting to decode a SCALE object before Runtime initialization. You need to first " - "either `await AsyncSubstrateInterface.initialize()` or use `async with` the object." - ) - else: - raise + if (result := response.get("result")) and decode: + metadata_decoder = self.runtime_config.create_scale_object( + "MetadataVersioned", data=ScaleBytes(result) + ) + metadata_decoder.decode() return metadata_decoder + else: + return result async def _preprocess( self,