2222 TYPE_CHECKING ,
2323)
2424
25- import asyncstdlib as a
2625from bt_decode import MetadataV15 , PortableRegistry , decode as decode_by_type_string
2726from scalecodec .base import ScaleBytes , ScaleType , RuntimeConfigurationObject
2827from scalecodec .types import (
4241 BlockNotFound ,
4342 MaxRetriesExceeded ,
4443 MetadataAtVersionNotFound ,
44+ StateDiscardedError ,
4545)
4646from async_substrate_interface .protocols import Keypair
4747from async_substrate_interface .types import (
5858 get_next_id ,
5959 rng as random ,
6060)
61- from async_substrate_interface .utils .cache import async_sql_lru_cache
61+ from async_substrate_interface .utils .cache import async_sql_lru_cache , CachedFetcher
6262from async_substrate_interface .utils .decoding import (
6363 _determine_if_old_runtime_call ,
6464 _bt_decode_to_dict_or_list ,
@@ -539,14 +539,17 @@ def __init__(
539539 "You are instantiating the AsyncSubstrateInterface Websocket outside of an event loop. "
540540 "Verify this is intended."
541541 )
542- now = asyncio .new_event_loop ().time ()
542+ # default value for in case there's no running asyncio loop
543+ # this really doesn't matter in most cases, as it's only used for comparison on the first call to
544+ # see how long it's been since the last call
545+ now = 0.0
543546 self .last_received = now
544547 self .last_sent = now
548+ self ._in_use_ids = set ()
545549
546550 async def __aenter__ (self ):
547- async with self ._lock :
548- self ._in_use += 1
549- await self .connect ()
551+ self ._in_use += 1
552+ await self .connect ()
550553 return self
551554
552555 @staticmethod
@@ -559,18 +562,19 @@ async def connect(self, force=False):
559562 self .last_sent = now
560563 if self ._exit_task :
561564 self ._exit_task .cancel ()
562- if not self ._initialized or force :
563- self ._initialized = True
564- try :
565- self ._receiving_task .cancel ()
566- await self ._receiving_task
567- await self .ws .close ()
568- except (AttributeError , asyncio .CancelledError ):
569- pass
570- self .ws = await asyncio .wait_for (
571- connect (self .ws_url , ** self ._options ), timeout = 10
572- )
573- self ._receiving_task = asyncio .create_task (self ._start_receiving ())
565+ async with self ._lock :
566+ if not self ._initialized or force :
567+ try :
568+ self ._receiving_task .cancel ()
569+ await self ._receiving_task
570+ await self .ws .close ()
571+ except (AttributeError , asyncio .CancelledError ):
572+ pass
573+ self .ws = await asyncio .wait_for (
574+ connect (self .ws_url , ** self ._options ), timeout = 10
575+ )
576+ self ._receiving_task = asyncio .create_task (self ._start_receiving ())
577+ self ._initialized = True
574578
575579 async def __aexit__ (self , exc_type , exc_val , exc_tb ):
576580 async with self ._lock : # TODO is this actually what I want to happen?
@@ -619,6 +623,7 @@ async def _recv(self) -> None:
619623 self ._open_subscriptions -= 1
620624 if "id" in response :
621625 self ._received [response ["id" ]] = response
626+ self ._in_use_ids .remove (response ["id" ])
622627 elif "params" in response :
623628 self ._received [response ["params" ]["subscription" ]] = response
624629 else :
@@ -649,6 +654,9 @@ async def send(self, payload: dict) -> int:
649654 id: the internal ID of the request (incremented int)
650655 """
651656 original_id = get_next_id ()
657+ while original_id in self ._in_use_ids :
658+ original_id = get_next_id ()
659+ self ._in_use_ids .add (original_id )
652660 # self._open_subscriptions += 1
653661 await self .max_subscriptions .acquire ()
654662 try :
@@ -674,7 +682,7 @@ async def retrieve(self, item_id: int) -> Optional[dict]:
674682 self .max_subscriptions .release ()
675683 return item
676684 except KeyError :
677- await asyncio .sleep (0.001 )
685+ await asyncio .sleep (0.1 )
678686 return None
679687
680688
@@ -725,6 +733,7 @@ def __init__(
725733 )
726734 else :
727735 self .ws = AsyncMock (spec = Websocket )
736+
728737 self ._lock = asyncio .Lock ()
729738 self .config = {
730739 "use_remote_preset" : use_remote_preset ,
@@ -748,6 +757,12 @@ def __init__(
748757 self .registry_type_map = {}
749758 self .type_id_to_name = {}
750759 self ._mock = _mock
760+ self ._block_hash_fetcher = CachedFetcher (512 , self ._get_block_hash )
761+ self ._parent_hash_fetcher = CachedFetcher (512 , self ._get_parent_block_hash )
762+ self ._runtime_info_fetcher = CachedFetcher (16 , self ._get_block_runtime_info )
763+ self ._runtime_version_for_fetcher = CachedFetcher (
764+ 512 , self ._get_block_runtime_version_for
765+ )
751766
752767 async def __aenter__ (self ):
753768 if not self ._mock :
@@ -1869,9 +1884,8 @@ async def get_metadata(self, block_hash=None) -> MetadataV15:
18691884
18701885 return runtime .metadata_v15
18711886
1872- @a .lru_cache (maxsize = 512 )
18731887 async def get_parent_block_hash (self , block_hash ):
1874- return await self ._get_parent_block_hash (block_hash )
1888+ return await self ._parent_hash_fetcher . execute (block_hash )
18751889
18761890 async def _get_parent_block_hash (self , block_hash ):
18771891 block_header = await self .rpc_request ("chain_getHeader" , [block_hash ])
@@ -1916,9 +1930,8 @@ async def get_storage_by_key(self, block_hash: str, storage_key: str) -> Any:
19161930 "Unknown error occurred during retrieval of events"
19171931 )
19181932
1919- @a .lru_cache (maxsize = 16 )
19201933 async def get_block_runtime_info (self , block_hash : str ) -> dict :
1921- return await self ._get_block_runtime_info (block_hash )
1934+ return await self ._runtime_info_fetcher . execute (block_hash )
19221935
19231936 get_block_runtime_version = get_block_runtime_info
19241937
@@ -1929,9 +1942,8 @@ async def _get_block_runtime_info(self, block_hash: str) -> dict:
19291942 response = await self .rpc_request ("state_getRuntimeVersion" , [block_hash ])
19301943 return response .get ("result" )
19311944
1932- @a .lru_cache (maxsize = 512 )
19331945 async def get_block_runtime_version_for (self , block_hash : str ):
1934- return await self ._get_block_runtime_version_for (block_hash )
1946+ return await self ._runtime_version_for_fetcher . execute (block_hash )
19351947
19361948 async def _get_block_runtime_version_for (self , block_hash : str ):
19371949 """
@@ -2137,6 +2149,7 @@ async def _make_rpc_request(
21372149 storage_item ,
21382150 result_handler ,
21392151 )
2152+
21402153 request_manager .add_response (
21412154 item_id , decoded_response , complete
21422155 )
@@ -2149,14 +2162,14 @@ async def _make_rpc_request(
21492162 and current_time - self .ws .last_sent >= self .retry_timeout
21502163 ):
21512164 if attempt >= self .max_retries :
2152- logger .warning (
2165+ logger .error (
21532166 f"Timed out waiting for RPC requests { attempt } times. Exiting."
21542167 )
21552168 raise MaxRetriesExceeded ("Max retries reached." )
21562169 else :
21572170 self .ws .last_received = time .time ()
21582171 await self .ws .connect (force = True )
2159- logger .error (
2172+ logger .warning (
21602173 f"Timed out waiting for RPC requests. "
21612174 f"Retrying attempt { attempt + 1 } of { self .max_retries } "
21622175 )
@@ -2223,9 +2236,8 @@ async def rpc_request(
22232236 ]
22242237 result = await self ._make_rpc_request (payloads , result_handler = result_handler )
22252238 if "error" in result [payload_id ][0 ]:
2226- if (
2227- "Failed to get runtime version"
2228- in result [payload_id ][0 ]["error" ]["message" ]
2239+ if "Failed to get runtime version" in (
2240+ err_msg := result [payload_id ][0 ]["error" ]["message" ]
22292241 ):
22302242 logger .warning (
22312243 "Failed to get runtime. Re-fetching from chain, and retrying."
@@ -2234,15 +2246,21 @@ async def rpc_request(
22342246 return await self .rpc_request (
22352247 method , params , result_handler , block_hash , reuse_block_hash
22362248 )
2237- raise SubstrateRequestException (result [payload_id ][0 ]["error" ]["message" ])
2249+ elif (
2250+ "Client error: Api called for an unknown Block: State already discarded"
2251+ in err_msg
2252+ ):
2253+ bh = err_msg .split ("State already discarded for " )[1 ].strip ()
2254+ raise StateDiscardedError (bh )
2255+ else :
2256+ raise SubstrateRequestException (err_msg )
22382257 if "result" in result [payload_id ][0 ]:
22392258 return result [payload_id ][0 ]
22402259 else :
22412260 raise SubstrateRequestException (result [payload_id ][0 ])
22422261
2243- @a .lru_cache (maxsize = 512 )
22442262 async def get_block_hash (self , block_id : int ) -> str :
2245- return await self ._get_block_hash (block_id )
2263+ return await self ._block_hash_fetcher . execute (block_id )
22462264
22472265 async def _get_block_hash (self , block_id : int ) -> str :
22482266 return (await self .rpc_request ("chain_getBlockHash" , [block_id ]))["result" ]
0 commit comments