async-substrate-interface 1.2.2__tar.gz → 1.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {async_substrate_interface-1.2.2 → async_substrate_interface-1.3.0}/PKG-INFO +1 -1
- {async_substrate_interface-1.2.2 → async_substrate_interface-1.3.0}/async_substrate_interface/async_substrate.py +51 -33
- {async_substrate_interface-1.2.2 → async_substrate_interface-1.3.0}/async_substrate_interface/errors.py +10 -0
- {async_substrate_interface-1.2.2 → async_substrate_interface-1.3.0}/async_substrate_interface/substrate_addons.py +40 -9
- {async_substrate_interface-1.2.2 → async_substrate_interface-1.3.0}/async_substrate_interface/sync_substrate.py +13 -6
- {async_substrate_interface-1.2.2 → async_substrate_interface-1.3.0}/async_substrate_interface/utils/cache.py +56 -0
- {async_substrate_interface-1.2.2 → async_substrate_interface-1.3.0}/async_substrate_interface.egg-info/PKG-INFO +1 -1
- {async_substrate_interface-1.2.2 → async_substrate_interface-1.3.0}/pyproject.toml +1 -1
- {async_substrate_interface-1.2.2 → async_substrate_interface-1.3.0}/tests/test_substrate_addons.py +31 -2
- {async_substrate_interface-1.2.2 → async_substrate_interface-1.3.0}/LICENSE +0 -0
- {async_substrate_interface-1.2.2 → async_substrate_interface-1.3.0}/README.md +0 -0
- {async_substrate_interface-1.2.2 → async_substrate_interface-1.3.0}/async_substrate_interface/__init__.py +0 -0
- {async_substrate_interface-1.2.2 → async_substrate_interface-1.3.0}/async_substrate_interface/const.py +0 -0
- {async_substrate_interface-1.2.2 → async_substrate_interface-1.3.0}/async_substrate_interface/protocols.py +0 -0
- {async_substrate_interface-1.2.2 → async_substrate_interface-1.3.0}/async_substrate_interface/type_registry.py +0 -0
- {async_substrate_interface-1.2.2 → async_substrate_interface-1.3.0}/async_substrate_interface/types.py +0 -0
- {async_substrate_interface-1.2.2 → async_substrate_interface-1.3.0}/async_substrate_interface/utils/__init__.py +0 -0
- {async_substrate_interface-1.2.2 → async_substrate_interface-1.3.0}/async_substrate_interface/utils/decoding.py +0 -0
- {async_substrate_interface-1.2.2 → async_substrate_interface-1.3.0}/async_substrate_interface/utils/hasher.py +0 -0
- {async_substrate_interface-1.2.2 → async_substrate_interface-1.3.0}/async_substrate_interface/utils/storage.py +0 -0
- {async_substrate_interface-1.2.2 → async_substrate_interface-1.3.0}/async_substrate_interface.egg-info/SOURCES.txt +0 -0
- {async_substrate_interface-1.2.2 → async_substrate_interface-1.3.0}/async_substrate_interface.egg-info/dependency_links.txt +0 -0
- {async_substrate_interface-1.2.2 → async_substrate_interface-1.3.0}/async_substrate_interface.egg-info/requires.txt +0 -0
- {async_substrate_interface-1.2.2 → async_substrate_interface-1.3.0}/async_substrate_interface.egg-info/top_level.txt +0 -0
- {async_substrate_interface-1.2.2 → async_substrate_interface-1.3.0}/setup.cfg +0 -0
- {async_substrate_interface-1.2.2 → async_substrate_interface-1.3.0}/tests/test_old_new.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: async-substrate-interface
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Asyncio library for interacting with substrate. Mostly API-compatible with py-substrate-interface
|
|
5
5
|
Author: Opentensor Foundation
|
|
6
6
|
Author-email: BD Himes <b@latent.to>
|
|
@@ -22,7 +22,6 @@ from typing import (
|
|
|
22
22
|
TYPE_CHECKING,
|
|
23
23
|
)
|
|
24
24
|
|
|
25
|
-
import asyncstdlib as a
|
|
26
25
|
from bt_decode import MetadataV15, PortableRegistry, decode as decode_by_type_string
|
|
27
26
|
from scalecodec.base import ScaleBytes, ScaleType, RuntimeConfigurationObject
|
|
28
27
|
from scalecodec.types import (
|
|
@@ -42,6 +41,7 @@ from async_substrate_interface.errors import (
|
|
|
42
41
|
BlockNotFound,
|
|
43
42
|
MaxRetriesExceeded,
|
|
44
43
|
MetadataAtVersionNotFound,
|
|
44
|
+
StateDiscardedError,
|
|
45
45
|
)
|
|
46
46
|
from async_substrate_interface.protocols import Keypair
|
|
47
47
|
from async_substrate_interface.types import (
|
|
@@ -58,7 +58,7 @@ from async_substrate_interface.utils import (
|
|
|
58
58
|
get_next_id,
|
|
59
59
|
rng as random,
|
|
60
60
|
)
|
|
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
|
|
62
62
|
from async_substrate_interface.utils.decoding import (
|
|
63
63
|
_determine_if_old_runtime_call,
|
|
64
64
|
_bt_decode_to_dict_or_list,
|
|
@@ -539,14 +539,17 @@ class Websocket:
|
|
|
539
539
|
"You are instantiating the AsyncSubstrateInterface Websocket outside of an event loop. "
|
|
540
540
|
"Verify this is intended."
|
|
541
541
|
)
|
|
542
|
-
|
|
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
|
|
543
546
|
self.last_received = now
|
|
544
547
|
self.last_sent = now
|
|
548
|
+
self._in_use_ids = set()
|
|
545
549
|
|
|
546
550
|
async def __aenter__(self):
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
await self.connect()
|
|
551
|
+
self._in_use += 1
|
|
552
|
+
await self.connect()
|
|
550
553
|
return self
|
|
551
554
|
|
|
552
555
|
@staticmethod
|
|
@@ -559,18 +562,19 @@ class Websocket:
|
|
|
559
562
|
self.last_sent = now
|
|
560
563
|
if self._exit_task:
|
|
561
564
|
self._exit_task.cancel()
|
|
562
|
-
|
|
563
|
-
self._initialized
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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
|
|
574
578
|
|
|
575
579
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
576
580
|
async with self._lock: # TODO is this actually what I want to happen?
|
|
@@ -619,6 +623,7 @@ class Websocket:
|
|
|
619
623
|
self._open_subscriptions -= 1
|
|
620
624
|
if "id" in response:
|
|
621
625
|
self._received[response["id"]] = response
|
|
626
|
+
self._in_use_ids.remove(response["id"])
|
|
622
627
|
elif "params" in response:
|
|
623
628
|
self._received[response["params"]["subscription"]] = response
|
|
624
629
|
else:
|
|
@@ -649,6 +654,9 @@ class Websocket:
|
|
|
649
654
|
id: the internal ID of the request (incremented int)
|
|
650
655
|
"""
|
|
651
656
|
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)
|
|
652
660
|
# self._open_subscriptions += 1
|
|
653
661
|
await self.max_subscriptions.acquire()
|
|
654
662
|
try:
|
|
@@ -674,7 +682,7 @@ class Websocket:
|
|
|
674
682
|
self.max_subscriptions.release()
|
|
675
683
|
return item
|
|
676
684
|
except KeyError:
|
|
677
|
-
await asyncio.sleep(0.
|
|
685
|
+
await asyncio.sleep(0.1)
|
|
678
686
|
return None
|
|
679
687
|
|
|
680
688
|
|
|
@@ -725,6 +733,7 @@ class AsyncSubstrateInterface(SubstrateMixin):
|
|
|
725
733
|
)
|
|
726
734
|
else:
|
|
727
735
|
self.ws = AsyncMock(spec=Websocket)
|
|
736
|
+
|
|
728
737
|
self._lock = asyncio.Lock()
|
|
729
738
|
self.config = {
|
|
730
739
|
"use_remote_preset": use_remote_preset,
|
|
@@ -748,6 +757,12 @@ class AsyncSubstrateInterface(SubstrateMixin):
|
|
|
748
757
|
self.registry_type_map = {}
|
|
749
758
|
self.type_id_to_name = {}
|
|
750
759
|
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
|
+
)
|
|
751
766
|
|
|
752
767
|
async def __aenter__(self):
|
|
753
768
|
if not self._mock:
|
|
@@ -1869,9 +1884,8 @@ class AsyncSubstrateInterface(SubstrateMixin):
|
|
|
1869
1884
|
|
|
1870
1885
|
return runtime.metadata_v15
|
|
1871
1886
|
|
|
1872
|
-
@a.lru_cache(maxsize=512)
|
|
1873
1887
|
async def get_parent_block_hash(self, block_hash):
|
|
1874
|
-
return await self.
|
|
1888
|
+
return await self._parent_hash_fetcher.execute(block_hash)
|
|
1875
1889
|
|
|
1876
1890
|
async def _get_parent_block_hash(self, block_hash):
|
|
1877
1891
|
block_header = await self.rpc_request("chain_getHeader", [block_hash])
|
|
@@ -1916,9 +1930,8 @@ class AsyncSubstrateInterface(SubstrateMixin):
|
|
|
1916
1930
|
"Unknown error occurred during retrieval of events"
|
|
1917
1931
|
)
|
|
1918
1932
|
|
|
1919
|
-
@a.lru_cache(maxsize=16)
|
|
1920
1933
|
async def get_block_runtime_info(self, block_hash: str) -> dict:
|
|
1921
|
-
return await self.
|
|
1934
|
+
return await self._runtime_info_fetcher.execute(block_hash)
|
|
1922
1935
|
|
|
1923
1936
|
get_block_runtime_version = get_block_runtime_info
|
|
1924
1937
|
|
|
@@ -1929,9 +1942,8 @@ class AsyncSubstrateInterface(SubstrateMixin):
|
|
|
1929
1942
|
response = await self.rpc_request("state_getRuntimeVersion", [block_hash])
|
|
1930
1943
|
return response.get("result")
|
|
1931
1944
|
|
|
1932
|
-
@a.lru_cache(maxsize=512)
|
|
1933
1945
|
async def get_block_runtime_version_for(self, block_hash: str):
|
|
1934
|
-
return await self.
|
|
1946
|
+
return await self._runtime_version_for_fetcher.execute(block_hash)
|
|
1935
1947
|
|
|
1936
1948
|
async def _get_block_runtime_version_for(self, block_hash: str):
|
|
1937
1949
|
"""
|
|
@@ -2137,6 +2149,7 @@ class AsyncSubstrateInterface(SubstrateMixin):
|
|
|
2137
2149
|
storage_item,
|
|
2138
2150
|
result_handler,
|
|
2139
2151
|
)
|
|
2152
|
+
|
|
2140
2153
|
request_manager.add_response(
|
|
2141
2154
|
item_id, decoded_response, complete
|
|
2142
2155
|
)
|
|
@@ -2149,14 +2162,14 @@ class AsyncSubstrateInterface(SubstrateMixin):
|
|
|
2149
2162
|
and current_time - self.ws.last_sent >= self.retry_timeout
|
|
2150
2163
|
):
|
|
2151
2164
|
if attempt >= self.max_retries:
|
|
2152
|
-
logger.
|
|
2165
|
+
logger.error(
|
|
2153
2166
|
f"Timed out waiting for RPC requests {attempt} times. Exiting."
|
|
2154
2167
|
)
|
|
2155
2168
|
raise MaxRetriesExceeded("Max retries reached.")
|
|
2156
2169
|
else:
|
|
2157
2170
|
self.ws.last_received = time.time()
|
|
2158
2171
|
await self.ws.connect(force=True)
|
|
2159
|
-
logger.
|
|
2172
|
+
logger.warning(
|
|
2160
2173
|
f"Timed out waiting for RPC requests. "
|
|
2161
2174
|
f"Retrying attempt {attempt + 1} of {self.max_retries}"
|
|
2162
2175
|
)
|
|
@@ -2223,9 +2236,8 @@ class AsyncSubstrateInterface(SubstrateMixin):
|
|
|
2223
2236
|
]
|
|
2224
2237
|
result = await self._make_rpc_request(payloads, result_handler=result_handler)
|
|
2225
2238
|
if "error" in result[payload_id][0]:
|
|
2226
|
-
if (
|
|
2227
|
-
|
|
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"]
|
|
2229
2241
|
):
|
|
2230
2242
|
logger.warning(
|
|
2231
2243
|
"Failed to get runtime. Re-fetching from chain, and retrying."
|
|
@@ -2234,15 +2246,21 @@ class AsyncSubstrateInterface(SubstrateMixin):
|
|
|
2234
2246
|
return await self.rpc_request(
|
|
2235
2247
|
method, params, result_handler, block_hash, reuse_block_hash
|
|
2236
2248
|
)
|
|
2237
|
-
|
|
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)
|
|
2238
2257
|
if "result" in result[payload_id][0]:
|
|
2239
2258
|
return result[payload_id][0]
|
|
2240
2259
|
else:
|
|
2241
2260
|
raise SubstrateRequestException(result[payload_id][0])
|
|
2242
2261
|
|
|
2243
|
-
@a.lru_cache(maxsize=512)
|
|
2244
2262
|
async def get_block_hash(self, block_id: int) -> str:
|
|
2245
|
-
return await self.
|
|
2263
|
+
return await self._block_hash_fetcher.execute(block_id)
|
|
2246
2264
|
|
|
2247
2265
|
async def _get_block_hash(self, block_id: int) -> str:
|
|
2248
2266
|
return (await self.rpc_request("chain_getBlockHash", [block_id]))["result"]
|
|
@@ -22,6 +22,16 @@ class MetadataAtVersionNotFound(SubstrateRequestException):
|
|
|
22
22
|
super().__init__(message)
|
|
23
23
|
|
|
24
24
|
|
|
25
|
+
class StateDiscardedError(SubstrateRequestException):
|
|
26
|
+
def __init__(self, block_hash: str):
|
|
27
|
+
self.block_hash = block_hash
|
|
28
|
+
message = (
|
|
29
|
+
f"State discarded for {block_hash}. This indicates the block is too old, and you should instead "
|
|
30
|
+
f"make this request using an archive node."
|
|
31
|
+
)
|
|
32
|
+
super().__init__(message)
|
|
33
|
+
|
|
34
|
+
|
|
25
35
|
class StorageFunctionNotFound(ValueError):
|
|
26
36
|
pass
|
|
27
37
|
|
|
@@ -13,7 +13,7 @@ from typing import Optional
|
|
|
13
13
|
from websockets.exceptions import ConnectionClosed
|
|
14
14
|
|
|
15
15
|
from async_substrate_interface.async_substrate import AsyncSubstrateInterface, Websocket
|
|
16
|
-
from async_substrate_interface.errors import MaxRetriesExceeded
|
|
16
|
+
from async_substrate_interface.errors import MaxRetriesExceeded, StateDiscardedError
|
|
17
17
|
from async_substrate_interface.sync_substrate import SubstrateInterface
|
|
18
18
|
|
|
19
19
|
logger = logging.getLogger("async_substrate_interface")
|
|
@@ -117,6 +117,7 @@ class RetrySyncSubstrate(SubstrateInterface):
|
|
|
117
117
|
max_retries: int = 5,
|
|
118
118
|
retry_timeout: float = 60.0,
|
|
119
119
|
_mock: bool = False,
|
|
120
|
+
archive_nodes: Optional[list[str]] = None,
|
|
120
121
|
):
|
|
121
122
|
fallback_chains = fallback_chains or []
|
|
122
123
|
self.fallback_chains = (
|
|
@@ -124,6 +125,9 @@ class RetrySyncSubstrate(SubstrateInterface):
|
|
|
124
125
|
if not retry_forever
|
|
125
126
|
else cycle(fallback_chains + [url])
|
|
126
127
|
)
|
|
128
|
+
self.archive_nodes = (
|
|
129
|
+
iter(archive_nodes) if not retry_forever else cycle(archive_nodes)
|
|
130
|
+
)
|
|
127
131
|
self.use_remote_preset = use_remote_preset
|
|
128
132
|
self.chain_name = chain_name
|
|
129
133
|
self._mock = _mock
|
|
@@ -174,9 +178,12 @@ class RetrySyncSubstrate(SubstrateInterface):
|
|
|
174
178
|
EOFError,
|
|
175
179
|
ConnectionClosed,
|
|
176
180
|
TimeoutError,
|
|
181
|
+
socket.gaierror,
|
|
182
|
+
StateDiscardedError,
|
|
177
183
|
) as e:
|
|
184
|
+
use_archive = isinstance(e, StateDiscardedError)
|
|
178
185
|
try:
|
|
179
|
-
self._reinstantiate_substrate(e)
|
|
186
|
+
self._reinstantiate_substrate(e, use_archive=use_archive)
|
|
180
187
|
return method_(*args, **kwargs)
|
|
181
188
|
except StopIteration:
|
|
182
189
|
logger.error(
|
|
@@ -184,10 +191,19 @@ class RetrySyncSubstrate(SubstrateInterface):
|
|
|
184
191
|
)
|
|
185
192
|
raise MaxRetriesExceeded
|
|
186
193
|
|
|
187
|
-
def _reinstantiate_substrate(
|
|
188
|
-
|
|
194
|
+
def _reinstantiate_substrate(
|
|
195
|
+
self, e: Optional[Exception] = None, use_archive: bool = False
|
|
196
|
+
) -> None:
|
|
197
|
+
if use_archive:
|
|
198
|
+
bh = getattr(e, "block_hash", "Unknown Block Hash")
|
|
199
|
+
logger.info(
|
|
200
|
+
f"Attempt made to {bh} failed for state discarded. Attempting to switch to archive node."
|
|
201
|
+
)
|
|
202
|
+
next_network = next(self.archive_nodes)
|
|
203
|
+
else:
|
|
204
|
+
next_network = next(self.fallback_chains)
|
|
189
205
|
self.ws.close()
|
|
190
|
-
if e
|
|
206
|
+
if isinstance(e, MaxRetriesExceeded):
|
|
191
207
|
logger.error(
|
|
192
208
|
f"Max retries exceeded with {self.url}. Retrying with {next_network}."
|
|
193
209
|
)
|
|
@@ -243,6 +259,7 @@ class RetryAsyncSubstrate(AsyncSubstrateInterface):
|
|
|
243
259
|
max_retries: int = 5,
|
|
244
260
|
retry_timeout: float = 60.0,
|
|
245
261
|
_mock: bool = False,
|
|
262
|
+
archive_nodes: Optional[list[str]] = None,
|
|
246
263
|
):
|
|
247
264
|
fallback_chains = fallback_chains or []
|
|
248
265
|
self.fallback_chains = (
|
|
@@ -250,6 +267,9 @@ class RetryAsyncSubstrate(AsyncSubstrateInterface):
|
|
|
250
267
|
if not retry_forever
|
|
251
268
|
else cycle(fallback_chains + [url])
|
|
252
269
|
)
|
|
270
|
+
self.archive_nodes = (
|
|
271
|
+
iter(archive_nodes) if not retry_forever else cycle(archive_nodes)
|
|
272
|
+
)
|
|
253
273
|
self.use_remote_preset = use_remote_preset
|
|
254
274
|
self.chain_name = chain_name
|
|
255
275
|
self._mock = _mock
|
|
@@ -272,9 +292,18 @@ class RetryAsyncSubstrate(AsyncSubstrateInterface):
|
|
|
272
292
|
for method in RETRY_METHODS:
|
|
273
293
|
setattr(self, method, partial(self._retry, method))
|
|
274
294
|
|
|
275
|
-
async def _reinstantiate_substrate(
|
|
276
|
-
|
|
277
|
-
|
|
295
|
+
async def _reinstantiate_substrate(
|
|
296
|
+
self, e: Optional[Exception] = None, use_archive: bool = False
|
|
297
|
+
) -> None:
|
|
298
|
+
if use_archive:
|
|
299
|
+
bh = getattr(e, "block_hash", "Unknown Block Hash")
|
|
300
|
+
logger.info(
|
|
301
|
+
f"Attempt made to {bh} failed for state discarded. Attempting to switch to archive node."
|
|
302
|
+
)
|
|
303
|
+
next_network = next(self.archive_nodes)
|
|
304
|
+
else:
|
|
305
|
+
next_network = next(self.fallback_chains)
|
|
306
|
+
if isinstance(e, MaxRetriesExceeded):
|
|
278
307
|
logger.error(
|
|
279
308
|
f"Max retries exceeded with {self.url}. Retrying with {next_network}."
|
|
280
309
|
)
|
|
@@ -314,9 +343,11 @@ class RetryAsyncSubstrate(AsyncSubstrateInterface):
|
|
|
314
343
|
ConnectionClosed,
|
|
315
344
|
EOFError,
|
|
316
345
|
socket.gaierror,
|
|
346
|
+
StateDiscardedError,
|
|
317
347
|
) as e:
|
|
348
|
+
use_archive = isinstance(e, StateDiscardedError)
|
|
318
349
|
try:
|
|
319
|
-
await self._reinstantiate_substrate(e)
|
|
350
|
+
await self._reinstantiate_substrate(e, use_archive=use_archive)
|
|
320
351
|
return await method_(*args, **kwargs)
|
|
321
352
|
except StopAsyncIteration:
|
|
322
353
|
logger.error(
|
|
@@ -24,6 +24,7 @@ from async_substrate_interface.errors import (
|
|
|
24
24
|
BlockNotFound,
|
|
25
25
|
MaxRetriesExceeded,
|
|
26
26
|
MetadataAtVersionNotFound,
|
|
27
|
+
StateDiscardedError,
|
|
27
28
|
)
|
|
28
29
|
from async_substrate_interface.protocols import Keypair
|
|
29
30
|
from async_substrate_interface.types import (
|
|
@@ -1944,9 +1945,8 @@ class SubstrateInterface(SubstrateMixin):
|
|
|
1944
1945
|
]
|
|
1945
1946
|
result = self._make_rpc_request(payloads, result_handler=result_handler)
|
|
1946
1947
|
if "error" in result[payload_id][0]:
|
|
1947
|
-
if (
|
|
1948
|
-
|
|
1949
|
-
in result[payload_id][0]["error"]["message"]
|
|
1948
|
+
if "Failed to get runtime version" in (
|
|
1949
|
+
err_msg := result[payload_id][0]["error"]["message"]
|
|
1950
1950
|
):
|
|
1951
1951
|
logger.warning(
|
|
1952
1952
|
"Failed to get runtime. Re-fetching from chain, and retrying."
|
|
@@ -1955,7 +1955,14 @@ class SubstrateInterface(SubstrateMixin):
|
|
|
1955
1955
|
return self.rpc_request(
|
|
1956
1956
|
method, params, result_handler, block_hash, reuse_block_hash
|
|
1957
1957
|
)
|
|
1958
|
-
|
|
1958
|
+
elif (
|
|
1959
|
+
"Client error: Api called for an unknown Block: State already discarded"
|
|
1960
|
+
in err_msg
|
|
1961
|
+
):
|
|
1962
|
+
bh = err_msg.split("State already discarded for ")[1].strip()
|
|
1963
|
+
raise StateDiscardedError(bh)
|
|
1964
|
+
else:
|
|
1965
|
+
raise SubstrateRequestException(err_msg)
|
|
1959
1966
|
if "result" in result[payload_id][0]:
|
|
1960
1967
|
return result[payload_id][0]
|
|
1961
1968
|
else:
|
|
@@ -2497,13 +2504,13 @@ class SubstrateInterface(SubstrateMixin):
|
|
|
2497
2504
|
Returns:
|
|
2498
2505
|
ScaleType from the runtime call
|
|
2499
2506
|
"""
|
|
2500
|
-
self.init_runtime(block_hash=block_hash)
|
|
2507
|
+
runtime = self.init_runtime(block_hash=block_hash)
|
|
2501
2508
|
|
|
2502
2509
|
if params is None:
|
|
2503
2510
|
params = {}
|
|
2504
2511
|
|
|
2505
2512
|
try:
|
|
2506
|
-
metadata_v15_value =
|
|
2513
|
+
metadata_v15_value = runtime.metadata_v15.value()
|
|
2507
2514
|
|
|
2508
2515
|
apis = {entry["name"]: entry for entry in metadata_v15_value["apis"]}
|
|
2509
2516
|
api_entry = apis[api]
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from collections import OrderedDict
|
|
1
3
|
import functools
|
|
2
4
|
import os
|
|
3
5
|
import pickle
|
|
4
6
|
import sqlite3
|
|
5
7
|
from pathlib import Path
|
|
8
|
+
from typing import Callable, Any
|
|
9
|
+
|
|
6
10
|
import asyncstdlib as a
|
|
7
11
|
|
|
12
|
+
|
|
8
13
|
USE_CACHE = True if os.getenv("NO_CACHE") != "1" else False
|
|
9
14
|
CACHE_LOCATION = (
|
|
10
15
|
os.path.expanduser(
|
|
@@ -139,3 +144,54 @@ def async_sql_lru_cache(maxsize=None):
|
|
|
139
144
|
return inner
|
|
140
145
|
|
|
141
146
|
return decorator
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class LRUCache:
|
|
150
|
+
def __init__(self, max_size: int):
|
|
151
|
+
self.max_size = max_size
|
|
152
|
+
self.cache = OrderedDict()
|
|
153
|
+
|
|
154
|
+
def set(self, key, value):
|
|
155
|
+
if key in self.cache:
|
|
156
|
+
self.cache.move_to_end(key)
|
|
157
|
+
self.cache[key] = value
|
|
158
|
+
if len(self.cache) > self.max_size:
|
|
159
|
+
self.cache.popitem(last=False)
|
|
160
|
+
|
|
161
|
+
def get(self, key):
|
|
162
|
+
if key in self.cache:
|
|
163
|
+
# Mark as recently used
|
|
164
|
+
self.cache.move_to_end(key)
|
|
165
|
+
return self.cache[key]
|
|
166
|
+
return None
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class CachedFetcher:
|
|
170
|
+
def __init__(self, max_size: int, method: Callable):
|
|
171
|
+
self._inflight: dict[int, asyncio.Future] = {}
|
|
172
|
+
self._method = method
|
|
173
|
+
self._cache = LRUCache(max_size=max_size)
|
|
174
|
+
|
|
175
|
+
async def execute(self, single_arg: Any) -> str:
|
|
176
|
+
if item := self._cache.get(single_arg):
|
|
177
|
+
return item
|
|
178
|
+
|
|
179
|
+
if single_arg in self._inflight:
|
|
180
|
+
result = await self._inflight[single_arg]
|
|
181
|
+
return result
|
|
182
|
+
|
|
183
|
+
loop = asyncio.get_running_loop()
|
|
184
|
+
future = loop.create_future()
|
|
185
|
+
self._inflight[single_arg] = future
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
result = await self._method(single_arg)
|
|
189
|
+
self._cache.set(single_arg, result)
|
|
190
|
+
future.set_result(result)
|
|
191
|
+
return result
|
|
192
|
+
except Exception as e:
|
|
193
|
+
# Propagate errors
|
|
194
|
+
future.set_exception(e)
|
|
195
|
+
raise
|
|
196
|
+
finally:
|
|
197
|
+
self._inflight.pop(single_arg, None)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: async-substrate-interface
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Asyncio library for interacting with substrate. Mostly API-compatible with py-substrate-interface
|
|
5
5
|
Author: Opentensor Foundation
|
|
6
6
|
Author-email: BD Himes <b@latent.to>
|
{async_substrate_interface-1.2.2 → async_substrate_interface-1.3.0}/tests/test_substrate_addons.py
RENAMED
|
@@ -4,8 +4,12 @@ import subprocess
|
|
|
4
4
|
import pytest
|
|
5
5
|
import time
|
|
6
6
|
|
|
7
|
-
from async_substrate_interface
|
|
8
|
-
from async_substrate_interface.
|
|
7
|
+
from async_substrate_interface import AsyncSubstrateInterface, SubstrateInterface
|
|
8
|
+
from async_substrate_interface.substrate_addons import (
|
|
9
|
+
RetrySyncSubstrate,
|
|
10
|
+
RetryAsyncSubstrate,
|
|
11
|
+
)
|
|
12
|
+
from async_substrate_interface.errors import MaxRetriesExceeded, StateDiscardedError
|
|
9
13
|
from tests.conftest import start_docker_container
|
|
10
14
|
|
|
11
15
|
LATENT_LITE_ENTRYPOINT = "wss://lite.sub.latent.to:443"
|
|
@@ -70,3 +74,28 @@ def test_retry_sync_substrate_offline():
|
|
|
70
74
|
RetrySyncSubstrate(
|
|
71
75
|
"ws://127.0.0.1:9945", fallback_chains=["ws://127.0.0.1:9946"]
|
|
72
76
|
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@pytest.mark.asyncio
|
|
80
|
+
async def test_retry_async_subtensor_archive_node():
|
|
81
|
+
async with AsyncSubstrateInterface("wss://lite.sub.latent.to:443") as substrate:
|
|
82
|
+
current_block = await substrate.get_block_number()
|
|
83
|
+
old_block = current_block - 1000
|
|
84
|
+
with pytest.raises(StateDiscardedError):
|
|
85
|
+
await substrate.get_block(block_number=old_block)
|
|
86
|
+
async with RetryAsyncSubstrate(
|
|
87
|
+
"wss://lite.sub.latent.to:443", archive_nodes=["ws://178.156.172.75:9944"]
|
|
88
|
+
) as substrate:
|
|
89
|
+
assert isinstance((await substrate.get_block(block_number=old_block)), dict)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_retry_sync_subtensor_archive_node():
|
|
93
|
+
with SubstrateInterface("wss://lite.sub.latent.to:443") as substrate:
|
|
94
|
+
current_block = substrate.get_block_number()
|
|
95
|
+
old_block = current_block - 1000
|
|
96
|
+
with pytest.raises(StateDiscardedError):
|
|
97
|
+
substrate.get_block(block_number=old_block)
|
|
98
|
+
with RetrySyncSubstrate(
|
|
99
|
+
"wss://lite.sub.latent.to:443", archive_nodes=["ws://178.156.172.75:9944"]
|
|
100
|
+
) as substrate:
|
|
101
|
+
assert isinstance((substrate.get_block(block_number=old_block)), dict)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|