diff --git a/CHANGELOG.md b/CHANGELOG.md index c601550..019fccf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 1.6.3 /2025-02-24 + +## What's Changed +* Fix: Supp for third-party runtimes by @ibraheem-abe in https://github.com/opentensor/async-substrate-interface/pull/274 +* Extend `get_account_next_index` logic by @basfroman in https://github.com/opentensor/async-substrate-interface/pull/273 + +**Full Changelog**: https://github.com/opentensor/async-substrate-interface/compare/v1.6.2...v1.6.3 + ## 1.6.2 /2025-02-19 ## What's Changed diff --git a/async_substrate_interface/async_substrate.py b/async_substrate_interface/async_substrate.py index dcc5ceb..90ac49d 100644 --- a/async_substrate_interface/async_substrate.py +++ b/async_substrate_interface/async_substrate.py @@ -3467,7 +3467,9 @@ async def get_account_nonce(self, account_address: str) -> int: ) return response["nonce"] - async def get_account_next_index(self, account_address: str) -> int: + async def get_account_next_index( + self, account_address: str, use_cache: bool = True + ) -> int: """ This method maintains a cache of nonces for each account ss58address. Upon subsequent calls, it will return the cached nonce + 1 instead of fetching from the chain. @@ -3475,22 +3477,30 @@ async def get_account_next_index(self, account_address: str) -> int: Args: account_address: SS58 formatted address + use_cache: If True, bypass local nonce cache and always request fresh value from RPC. Returns: Next index for the given account address """ + + async def _get_account_next_index(): + """Inner RPC call to get `account_nextIndex`.""" + nonce_obj_ = await self.rpc_request("account_nextIndex", [account_address]) + if "error" in nonce_obj_: + raise SubstrateRequestException(nonce_obj_["error"]["message"]) + return nonce_obj_["result"] + if not await self.supports_rpc_method("account_nextIndex"): # Unlikely to happen, this is a common RPC method raise Exception("account_nextIndex not supported") + if not use_cache: + return await _get_account_next_index() + async with self._lock: if self._nonces.get(account_address) is None: - nonce_obj = await self.rpc_request( - "account_nextIndex", [account_address] - ) - if "error" in nonce_obj: - raise SubstrateRequestException(nonce_obj["error"]["message"]) - self._nonces[account_address] = nonce_obj["result"] + nonce_obj = await _get_account_next_index() + self._nonces[account_address] = nonce_obj else: self._nonces[account_address] += 1 return self._nonces[account_address] diff --git a/async_substrate_interface/sync_substrate.py b/async_substrate_interface/sync_substrate.py index f6ff5b6..d393c25 100644 --- a/async_substrate_interface/sync_substrate.py +++ b/async_substrate_interface/sync_substrate.py @@ -764,9 +764,12 @@ def decode_scale( return ss58_encode(scale_bytes, self.ss58_format) else: if self.runtime.metadata_v15 is not None and force_legacy is False: - obj = decode_by_type_string( - type_string, self.runtime.registry, scale_bytes - ) + try: + obj = decode_by_type_string( + type_string, self.runtime.registry, scale_bytes + ) + except ValueError: + obj = legacy_scale_decode(type_string, scale_bytes, self.runtime) if self.decode_ss58: try: type_str_int = int(type_string.split("::")[1]) diff --git a/pyproject.toml b/pyproject.toml index c7e6eef..a00d36b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "async-substrate-interface" -version = "1.6.2" +version = "1.6.3" description = "Asyncio library for interacting with substrate. Mostly API-compatible with py-substrate-interface" readme = "README.md" license = { file = "LICENSE" } diff --git a/tests/unit_tests/asyncio_/test_substrate_interface.py b/tests/unit_tests/asyncio_/test_substrate_interface.py index 632af81..afefe7a 100644 --- a/tests/unit_tests/asyncio_/test_substrate_interface.py +++ b/tests/unit_tests/asyncio_/test_substrate_interface.py @@ -11,6 +11,7 @@ AsyncSubstrateInterface, get_async_substrate_interface, ) +from async_substrate_interface.errors import SubstrateRequestException from async_substrate_interface.types import ScaleObj from tests.helpers.settings import ARCHIVE_ENTRYPOINT, LATENT_LITE_ENTRYPOINT @@ -287,3 +288,53 @@ async def test_cache_miss_fetches_and_stores(self, substrate): substrate.runtime_cache.add_item.assert_called_once_with( block_hash="0xABC", block=100 ) + + +@pytest.mark.asyncio +async def test_get_account_next_index_cached_mode_uses_internal_cache(): + substrate = AsyncSubstrateInterface("ws://localhost", _mock=True) + substrate.supports_rpc_method = AsyncMock(return_value=True) + substrate.rpc_request = AsyncMock(return_value={"result": 5}) + + first = await substrate.get_account_next_index("5F3sa2TJAWMqDhXG6jhV4N8ko9NoFz5Y2s8vS8uM9f7v7mA") + second = await substrate.get_account_next_index( + "5F3sa2TJAWMqDhXG6jhV4N8ko9NoFz5Y2s8vS8uM9f7v7mA" + ) + + assert first == 5 + assert second == 6 + substrate.rpc_request.assert_awaited_once_with( + "account_nextIndex", ["5F3sa2TJAWMqDhXG6jhV4N8ko9NoFz5Y2s8vS8uM9f7v7mA"] + ) + + +@pytest.mark.asyncio +async def test_get_account_next_index_bypass_mode_does_not_create_or_mutate_cache(): + substrate = AsyncSubstrateInterface("ws://localhost", _mock=True) + substrate.supports_rpc_method = AsyncMock(return_value=True) + substrate.rpc_request = AsyncMock(return_value={"result": 10}) + + address = "5F3sa2TJAWMqDhXG6jhV4N8ko9NoFz5Y2s8vS8uM9f7v7mA" + assert address not in substrate._nonces + + result = await substrate.get_account_next_index( + address, + use_cache=False, + ) + + assert result == 10 + assert address not in substrate._nonces + substrate.rpc_request.assert_awaited_once_with("account_nextIndex", [address]) + + +@pytest.mark.asyncio +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"}}) + + with pytest.raises(SubstrateRequestException, match="rpc failure"): + await substrate.get_account_next_index( + "5F3sa2TJAWMqDhXG6jhV4N8ko9NoFz5Y2s8vS8uM9f7v7mA", + use_cache=False, + )