From 4f5e449c7a397c89e35a8adcb4f9342cf834bd74 Mon Sep 17 00:00:00 2001 From: BD Himes Date: Mon, 2 Mar 2026 15:51:09 +0200 Subject: [PATCH 1/7] Faster startup --- async_substrate_interface/async_substrate.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/async_substrate_interface/async_substrate.py b/async_substrate_interface/async_substrate.py index 785745c..fb61c12 100644 --- a/async_substrate_interface/async_substrate.py +++ b/async_substrate_interface/async_substrate.py @@ -1214,6 +1214,7 @@ def __init__( self.metadata_version_hex = "0x0f000000" # v15 self._initializing = False self._mock = _mock + self.startup_runtime_task: Optional[asyncio.Task] = None async def __aenter__(self): if not self._mock: @@ -1233,8 +1234,12 @@ async def _initialize(self) -> None: if not self._chain: chain = await self.rpc_request("system_chain", []) self._chain = chain.get("result") - runtime = await self.init_runtime() + block_hash = await self.get_chain_head() + self.startup_runtime_task = asyncio.create_task( + self.init_runtime(block_hash=block_hash, init=True) + ) if self.ss58_format is None: + runtime = await self.init_runtime(block_hash) # Check and apply runtime constants ss58_prefix_constant = await self.get_constant( "System", "SS58Prefix", runtime=runtime @@ -1439,7 +1444,10 @@ async def decode_scale( return obj async def init_runtime( - self, block_hash: Optional[str] = None, block_id: Optional[int] = None + self, + block_hash: Optional[str] = None, + block_id: Optional[int] = None, + init: bool = False, ) -> Runtime: """ This method is used by all other methods that deals with metadata and types defined in the type registry. @@ -1456,6 +1464,9 @@ async def init_runtime( Returns: Runtime object """ + if not init: + if self.startup_runtime_task is not None: + await self.startup_runtime_task if block_id and block_hash: raise ValueError("Cannot provide block_hash and block_id at the same time") From 48ae5df53989913103ec8479dd2183cc2707d300 Mon Sep 17 00:00:00 2001 From: BD Himes Date: Mon, 2 Mar 2026 16:07:55 +0200 Subject: [PATCH 2/7] trigger From 486aa2526e2e3731099c45eb15e7b8c164a300ef Mon Sep 17 00:00:00 2001 From: BD Himes Date: Mon, 2 Mar 2026 16:29:08 +0200 Subject: [PATCH 3/7] Optimise --- async_substrate_interface/async_substrate.py | 17 +++++++++++++---- .../asyncio_/test_substrate_interface.py | 8 ++++++-- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/async_substrate_interface/async_substrate.py b/async_substrate_interface/async_substrate.py index 840b45b..9be9220 100644 --- a/async_substrate_interface/async_substrate.py +++ b/async_substrate_interface/async_substrate.py @@ -11,6 +11,7 @@ import socket import ssl import warnings +from contextlib import suppress from unittest.mock import AsyncMock from hashlib import blake2b from typing import ( @@ -1212,6 +1213,7 @@ def __init__( self._initializing = False self._mock = _mock self.startup_runtime_task: Optional[asyncio.Task] = None + self.startup_block_hash: Optional[str] = None async def __aenter__(self): if not self._mock: @@ -1231,7 +1233,7 @@ async def _initialize(self) -> None: if not self._chain: chain = await self.rpc_request("system_chain", []) self._chain = chain.get("result") - block_hash = await self.get_chain_head() + self.startup_block_hash = block_hash = await self.get_chain_head() self.startup_runtime_task = asyncio.create_task( self.init_runtime(block_hash=block_hash, init=True) ) @@ -1463,9 +1465,12 @@ async def init_runtime( Returns: Runtime object """ - if not init: - if self.startup_runtime_task is not None: - await self.startup_runtime_task + if ( + not init + and self.startup_runtime_task is not None + and block_hash == self.startup_block_hash + ): + await self.startup_runtime_task if block_id and block_hash: raise ValueError("Cannot provide block_hash and block_id at the same time") @@ -4333,6 +4338,10 @@ async def close(self): Closes the substrate connection, and the websocket connection. """ try: + if self.startup_runtime_task is not None: + self.startup_runtime_task.cancel() + with suppress(asyncio.CancelledError): + await self.startup_runtime_task await self.ws.shutdown() except AttributeError: pass diff --git a/tests/unit_tests/asyncio_/test_substrate_interface.py b/tests/unit_tests/asyncio_/test_substrate_interface.py index afefe7a..a0ac123 100644 --- a/tests/unit_tests/asyncio_/test_substrate_interface.py +++ b/tests/unit_tests/asyncio_/test_substrate_interface.py @@ -296,7 +296,9 @@ async def test_get_account_next_index_cached_mode_uses_internal_cache(): substrate.supports_rpc_method = AsyncMock(return_value=True) substrate.rpc_request = AsyncMock(return_value={"result": 5}) - first = await substrate.get_account_next_index("5F3sa2TJAWMqDhXG6jhV4N8ko9NoFz5Y2s8vS8uM9f7v7mA") + first = await substrate.get_account_next_index( + "5F3sa2TJAWMqDhXG6jhV4N8ko9NoFz5Y2s8vS8uM9f7v7mA" + ) second = await substrate.get_account_next_index( "5F3sa2TJAWMqDhXG6jhV4N8ko9NoFz5Y2s8vS8uM9f7v7mA" ) @@ -331,7 +333,9 @@ async def test_get_account_next_index_bypass_mode_does_not_create_or_mutate_cach async def test_get_account_next_index_bypass_mode_raises_on_rpc_error(): substrate = AsyncSubstrateInterface("ws://localhost", _mock=True) substrate.supports_rpc_method = AsyncMock(return_value=True) - substrate.rpc_request = AsyncMock(return_value={"error": {"message": "rpc failure"}}) + substrate.rpc_request = AsyncMock( + return_value={"error": {"message": "rpc failure"}} + ) with pytest.raises(SubstrateRequestException, match="rpc failure"): await substrate.get_account_next_index( From 46053458b1562a22e941ddc2e61f7e747a028347 Mon Sep 17 00:00:00 2001 From: BD Himes Date: Mon, 2 Mar 2026 16:41:29 +0200 Subject: [PATCH 4/7] Reset startup task --- async_substrate_interface/async_substrate.py | 1 + 1 file changed, 1 insertion(+) diff --git a/async_substrate_interface/async_substrate.py b/async_substrate_interface/async_substrate.py index 9be9220..9901619 100644 --- a/async_substrate_interface/async_substrate.py +++ b/async_substrate_interface/async_substrate.py @@ -1471,6 +1471,7 @@ async def init_runtime( and block_hash == self.startup_block_hash ): await self.startup_runtime_task + self.startup_runtime_task = None if block_id and block_hash: raise ValueError("Cannot provide block_hash and block_id at the same time") From 1eba3b2ae158b99d90139cbd165b658772870953 Mon Sep 17 00:00:00 2001 From: BD Himes Date: Thu, 5 Mar 2026 18:17:58 +0200 Subject: [PATCH 5/7] trigger From ef6a7aa20085cd97ab4cc12895f0b9361a525b2d Mon Sep 17 00:00:00 2001 From: BD Himes Date: Thu, 5 Mar 2026 18:25:35 +0200 Subject: [PATCH 6/7] Adjust test for slow ass github actions runner --- tests/integration_tests/test_disk_cache.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/integration_tests/test_disk_cache.py b/tests/integration_tests/test_disk_cache.py index 063eca1..11e7312 100644 --- a/tests/integration_tests/test_disk_cache.py +++ b/tests/integration_tests/test_disk_cache.py @@ -88,44 +88,44 @@ async def test_disk_cache(): start = time.monotonic() new_block_hash = await disk_cached_substrate.get_block_hash(current_block) new_time = time.monotonic() - assert new_time - start < 0.001 + assert new_time - start < 0.002 start = time.monotonic() new_parent_block_hash = await disk_cached_substrate.get_parent_block_hash( block_hash ) new_time = time.monotonic() - assert new_time - start < 0.001 + assert new_time - start < 0.002 start = time.monotonic() new_block_runtime_info = await disk_cached_substrate.get_block_runtime_info( block_hash ) new_time = time.monotonic() - assert new_time - start < 0.001 + assert new_time - start < 0.002 start = time.monotonic() new_block_runtime_version_for = ( await disk_cached_substrate.get_block_runtime_version_for(block_hash) ) new_time = time.monotonic() - assert new_time - start < 0.001 + assert new_time - start < 0.002 start = time.monotonic() new_block_hash_from_cache = await disk_cached_substrate.get_block_hash( current_block ) new_time = time.monotonic() - assert new_time - start < 0.001 + assert new_time - start < 0.002 start = time.monotonic() new_parent_block_hash_from_cache = ( await disk_cached_substrate.get_parent_block_hash(block_hash_from_cache) ) new_time = time.monotonic() - assert new_time - start < 0.001 + assert new_time - start < 0.002 start = time.monotonic() new_block_runtime_info_from_cache = ( await disk_cached_substrate.get_block_runtime_info(block_hash_from_cache) ) new_time = time.monotonic() - assert new_time - start < 0.001 + assert new_time - start < 0.002 start = time.monotonic() new_block_runtime_version_from_cache = ( await disk_cached_substrate.get_block_runtime_version_for( @@ -133,5 +133,5 @@ async def test_disk_cache(): ) ) new_time = time.monotonic() - assert new_time - start < 0.001 + assert new_time - start < 0.002 print("Disk Cache tests passed") From 6894f309ebaa56f0eeb2ff3082d105589d06e5ca Mon Sep 17 00:00:00 2001 From: BD Himes Date: Thu, 5 Mar 2026 19:46:13 +0200 Subject: [PATCH 7/7] trigger