Skip to content

Commit 8b7eb0c

Browse files
committed
Add archive node for RetrySubstrate
1 parent 5b97c88 commit 8b7eb0c

File tree

4 files changed

+52
-12
lines changed

4 files changed

+52
-12
lines changed

async_substrate_interface/async_substrate.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
BlockNotFound,
4242
MaxRetriesExceeded,
4343
MetadataAtVersionNotFound,
44+
StateDiscardedError,
4445
)
4546
from async_substrate_interface.protocols import Keypair
4647
from async_substrate_interface.types import (
@@ -2144,6 +2145,7 @@ async def _make_rpc_request(
21442145
storage_item,
21452146
result_handler,
21462147
)
2148+
21472149
request_manager.add_response(
21482150
item_id, decoded_response, complete
21492151
)
@@ -2230,9 +2232,8 @@ async def rpc_request(
22302232
]
22312233
result = await self._make_rpc_request(payloads, result_handler=result_handler)
22322234
if "error" in result[payload_id][0]:
2233-
if (
2234-
"Failed to get runtime version"
2235-
in result[payload_id][0]["error"]["message"]
2235+
if "Failed to get runtime version" in (
2236+
err_msg := result[payload_id][0]["error"]["message"]
22362237
):
22372238
logger.warning(
22382239
"Failed to get runtime. Re-fetching from chain, and retrying."
@@ -2241,7 +2242,14 @@ async def rpc_request(
22412242
return await self.rpc_request(
22422243
method, params, result_handler, block_hash, reuse_block_hash
22432244
)
2244-
raise SubstrateRequestException(result[payload_id][0]["error"]["message"])
2245+
elif (
2246+
"Client error: Api called for an unknown Block: State already discarded"
2247+
in err_msg
2248+
):
2249+
bh = err_msg.split("State already discarded for ")[1].strip()
2250+
raise StateDiscardedError(bh)
2251+
else:
2252+
raise SubstrateRequestException(err_msg)
22452253
if "result" in result[payload_id][0]:
22462254
return result[payload_id][0]
22472255
else:

async_substrate_interface/errors.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@ def __init__(self):
2222
super().__init__(message)
2323

2424

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+
2535
class StorageFunctionNotFound(ValueError):
2636
pass
2737

async_substrate_interface/substrate_addons.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from websockets.exceptions import ConnectionClosed
1414

1515
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
1717
from async_substrate_interface.sync_substrate import SubstrateInterface
1818

1919
logger = logging.getLogger("async_substrate_interface")
@@ -243,13 +243,17 @@ def __init__(
243243
max_retries: int = 5,
244244
retry_timeout: float = 60.0,
245245
_mock: bool = False,
246+
archive_nodes: Optional[list[str]] = None,
246247
):
247248
fallback_chains = fallback_chains or []
248249
self.fallback_chains = (
249250
iter(fallback_chains)
250251
if not retry_forever
251252
else cycle(fallback_chains + [url])
252253
)
254+
self.archive_nodes = (
255+
iter(archive_nodes) if not retry_forever else cycle(archive_nodes)
256+
)
253257
self.use_remote_preset = use_remote_preset
254258
self.chain_name = chain_name
255259
self._mock = _mock
@@ -272,8 +276,17 @@ def __init__(
272276
for method in RETRY_METHODS:
273277
setattr(self, method, partial(self._retry, method))
274278

275-
async def _reinstantiate_substrate(self, e: Optional[Exception] = None) -> None:
276-
next_network = next(self.fallback_chains)
279+
async def _reinstantiate_substrate(
280+
self, e: Optional[Exception] = None, use_archive: bool = False
281+
) -> None:
282+
if use_archive:
283+
bh = getattr(e, "block_hash", "Unknown Block Hash")
284+
logger.info(
285+
f"Attempt made to {bh} failed for state discarded. Attempting to switch to archive node."
286+
)
287+
next_network = next(self.archive_nodes)
288+
else:
289+
next_network = next(self.fallback_chains)
277290
if e.__class__ == MaxRetriesExceeded:
278291
logger.error(
279292
f"Max retries exceeded with {self.url}. Retrying with {next_network}."
@@ -314,9 +327,11 @@ async def _retry(self, method, *args, **kwargs):
314327
ConnectionClosed,
315328
EOFError,
316329
socket.gaierror,
330+
StateDiscardedError,
317331
) as e:
332+
use_archive = isinstance(e, StateDiscardedError)
318333
try:
319-
await self._reinstantiate_substrate(e)
334+
await self._reinstantiate_substrate(e, use_archive=use_archive)
320335
return await method_(*args, **kwargs)
321336
except StopAsyncIteration:
322337
logger.error(

async_substrate_interface/sync_substrate.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
BlockNotFound,
2525
MaxRetriesExceeded,
2626
MetadataAtVersionNotFound,
27+
StateDiscardedError,
2728
)
2829
from async_substrate_interface.protocols import Keypair
2930
from async_substrate_interface.types import (
@@ -1944,9 +1945,8 @@ def rpc_request(
19441945
]
19451946
result = self._make_rpc_request(payloads, result_handler=result_handler)
19461947
if "error" in result[payload_id][0]:
1947-
if (
1948-
"Failed to get runtime version"
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"]
19501950
):
19511951
logger.warning(
19521952
"Failed to get runtime. Re-fetching from chain, and retrying."
@@ -1955,7 +1955,14 @@ def rpc_request(
19551955
return self.rpc_request(
19561956
method, params, result_handler, block_hash, reuse_block_hash
19571957
)
1958-
raise SubstrateRequestException(result[payload_id][0]["error"]["message"])
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)
19591966
if "result" in result[payload_id][0]:
19601967
return result[payload_id][0]
19611968
else:

0 commit comments

Comments
 (0)