From 1d5b075d5128238509462d11a8cd211ab0f47e5b Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 6 Jun 2025 17:26:06 +0200 Subject: [PATCH 01/10] Allow for use of the new archive backup endpoints specification. --- bittensor/core/async_subtensor.py | 18 +++++++++++++++--- bittensor/core/subtensor.py | 20 ++++++++++++++++---- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index c4503b4142..6571c1f3a3 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -118,6 +118,7 @@ def __init__( fallback_endpoints: Optional[list[str]] = None, retry_forever: bool = False, _mock: bool = False, + archive_endpoints: Optional[list[str]] = None, ): """ Initializes an instance of the AsyncSubtensor class. @@ -126,9 +127,13 @@ def __init__( network: The network name or type to connect to. config: Configuration object for the AsyncSubtensor instance. log_verbose: Enables or disables verbose logging. - fallback_endpoints: List of fallback endpoints to use if default or provided network is not available. Defaults to `None`. + fallback_endpoints: List of fallback endpoints to use if default or provided network is not available. + Defaults to `None`. retry_forever: Whether to retry forever on connection errors. Defaults to `False`. _mock: Whether this is a mock instance. Mainly just for use in testing. + archive_endpoints: Similar to fallback_endpoints, but specifically only archive nodes. Will be used in cases + where you are requesting a block that is too old for your current (presumably lite) node. Defaults to + `None` Raises: Any exceptions raised during the setup, configuration, or connection process. @@ -151,6 +156,7 @@ def __init__( fallback_endpoints=fallback_endpoints, retry_forever=retry_forever, _mock=_mock, + archive_endpoints=archive_endpoints, ) if self.log_verbose: logging.info( @@ -289,18 +295,23 @@ def _get_substrate( fallback_endpoints: Optional[list[str]] = None, retry_forever: bool = False, _mock: bool = False, + archive_endpoints: Optional[list[str]] = None, ) -> Union[AsyncSubstrateInterface, RetryAsyncSubstrate]: """Creates the Substrate instance based on provided arguments. Arguments: - fallback_endpoints: List of fallback endpoints to use if default or provided network is not available. Defaults to `None`. + fallback_endpoints: List of fallback endpoints to use if default or provided network is not available. + Defaults to `None`. retry_forever: Whether to retry forever on connection errors. Defaults to `False`. _mock: Whether this is a mock instance. Mainly just for use in testing. + archive_endpoints: Similar to fallback_endpoints, but specifically only archive nodes. Will be used in cases + where you are requesting a block that is too old for your current (presumably lite) node. Defaults to + `None` Returns: the instance of the SubstrateInterface or RetrySyncSubstrate class. """ - if fallback_endpoints or retry_forever: + if fallback_endpoints or retry_forever or archive_endpoints: return RetryAsyncSubstrate( url=self.chain_endpoint, fallback_chains=fallback_endpoints, @@ -310,6 +321,7 @@ def _get_substrate( use_remote_preset=True, chain_name="Bittensor", _mock=_mock, + archive_endpoints=archive_endpoints ) return AsyncSubstrateInterface( url=self.chain_endpoint, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index bb0806159d..8598c1f8ea 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -115,11 +115,12 @@ class Subtensor(SubtensorMixin): def __init__( self, network: Optional[str] = None, - config: Optional["Config"] = None, + config: Optional[Config] = None, log_verbose: bool = False, fallback_endpoints: Optional[list[str]] = None, retry_forever: bool = False, _mock: bool = False, + archive_endpoints: Optional[list[str]] = None, ): """ Initializes an instance of the Subtensor class. @@ -128,9 +129,13 @@ def __init__( network: The network name or type to connect to. config: Configuration object for the AsyncSubtensor instance. log_verbose: Enables or disables verbose logging. - fallback_endpoints: List of fallback endpoints to use if default or provided network is not available. Defaults to `None`. + fallback_endpoints: List of fallback endpoints to use if default or provided network is not available. + Defaults to `None`. retry_forever: Whether to retry forever on connection errors. Defaults to `False`. _mock: Whether this is a mock instance. Mainly just for use in testing. + archive_endpoints: Similar to fallback_endpoints, but specifically only archive nodes. Will be used in cases + where you are requesting a block that is too old for your current (presumably lite) node. Defaults to + `None` Raises: Any exceptions raised during the setup, configuration, or connection process. @@ -151,6 +156,7 @@ def __init__( fallback_endpoints=fallback_endpoints, retry_forever=retry_forever, _mock=_mock, + archive_endpoints=archive_endpoints, ) if self.log_verbose: logging.info( @@ -172,18 +178,23 @@ def _get_substrate( fallback_endpoints: Optional[list[str]] = None, retry_forever: bool = False, _mock: bool = False, + archive_endpoints: Optional[list[str]] = None, ) -> Union[SubstrateInterface, RetrySyncSubstrate]: """Creates the Substrate instance based on provided arguments. Arguments: - fallback_endpoints: List of fallback chains endpoints to use if main network isn't available. Defaults to `None`. + fallback_endpoints: List of fallback chains endpoints to use if main network isn't available. Defaults to + `None`. retry_forever: Whether to retry forever on connection errors. Defaults to `False`. _mock: Whether this is a mock instance. Mainly just for use in testing. + archive_endpoints: Similar to fallback_endpoints, but specifically only archive nodes. Will be used in cases + where you are requesting a block that is too old for your current (presumably lite) node. Defaults to + `None` Returns: the instance of the SubstrateInterface or RetrySyncSubstrate class. """ - if fallback_endpoints or retry_forever: + if fallback_endpoints or retry_forever or archive_endpoints: return RetrySyncSubstrate( url=self.chain_endpoint, ss58_format=SS58_FORMAT, @@ -193,6 +204,7 @@ def _get_substrate( fallback_chains=fallback_endpoints, retry_forever=retry_forever, _mock=_mock, + archive_endpoints=archive_endpoints, ) return SubstrateInterface( url=self.chain_endpoint, From c368f351c1fa4c893b356b411e38249dc3a9d5b2 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 6 Jun 2025 17:26:37 +0200 Subject: [PATCH 02/10] Ruff --- bittensor/core/async_subtensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 6571c1f3a3..c9a2e8e3c8 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -321,7 +321,7 @@ def _get_substrate( use_remote_preset=True, chain_name="Bittensor", _mock=_mock, - archive_endpoints=archive_endpoints + archive_endpoints=archive_endpoints, ) return AsyncSubstrateInterface( url=self.chain_endpoint, From d9d57ea89ad8743030da64f24a138857265a6b91 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Tue, 10 Jun 2025 21:53:55 +0200 Subject: [PATCH 03/10] Bump async-substrate-interface req --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 51f6c28bff..3309fad71d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ dependencies = [ "uvicorn", "bittensor-drand>=0.5.0", "bittensor-wallet>=3.0.8", - "async-substrate-interface>=1.2.0" + "async-substrate-interface>=1.3.0" ] [project.optional-dependencies] From 4047dd9e44f7a908fe6eb3d3161d19d1929015b4 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 11 Jun 2025 00:06:28 +0200 Subject: [PATCH 04/10] Adds integration test for RetrySubtensor with Archive Node --- bittensor/core/async_subtensor.py | 2 +- bittensor/core/subtensor.py | 2 +- tests/helpers/helpers.py | 4 + tests/helpers/integration_websocket_data.py | 203 ++++++++++++++++++ .../test_subtensor_integration.py | 14 +- 5 files changed, 221 insertions(+), 4 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index db461b39ee..f91df67952 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -321,7 +321,7 @@ def _get_substrate( use_remote_preset=True, chain_name="Bittensor", _mock=_mock, - archive_endpoints=archive_endpoints, + archive_nodes=archive_endpoints, ) return AsyncSubstrateInterface( url=self.chain_endpoint, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 668f65e054..f0e8163552 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -204,7 +204,7 @@ def _get_substrate( fallback_chains=fallback_endpoints, retry_forever=retry_forever, _mock=_mock, - archive_endpoints=archive_endpoints, + archive_nodes=archive_endpoints, ) return SubstrateInterface( url=self.chain_endpoint, diff --git a/tests/helpers/helpers.py b/tests/helpers/helpers.py index df9cd0a388..fed6638c52 100644 --- a/tests/helpers/helpers.py +++ b/tests/helpers/helpers.py @@ -1,4 +1,5 @@ import asyncio +import itertools import json import time from collections import deque @@ -204,6 +205,9 @@ def recv(self, *args, **kwargs): response = WEBSOCKET_RESPONSES[self.seed][item["method"]][ json.dumps(item["params"]) ] + if isinstance(response, itertools.cycle): + # Allows us to cycle through different responses for the same method/params combo + response = next(response) response["id"] = _id return json.dumps(response) except (KeyError, TypeError): diff --git a/tests/helpers/integration_websocket_data.py b/tests/helpers/integration_websocket_data.py index bede3b684e..41b4f64d1f 100644 --- a/tests/helpers/integration_websocket_data.py +++ b/tests/helpers/integration_websocket_data.py @@ -1,6 +1,10 @@ # This is dictionary of raw websocket send vs recv data for given methods used for integration testing, essentially # stubbing the network I/O only # This is basically a JSON file, but is not a JSON file because of the discrepancies between JSON and Python types + +from itertools import cycle + + WEBSOCKET_RESPONSES = { "blocks_since_last_update": { "chain_getHead": { @@ -9049,4 +9053,203 @@ }, "system_chain": {"[]": {"jsonrpc": "2.0", "result": "Bittensor"}}, }, + "retry_archive": { + "system_chain": {"[]": {"jsonrpc": "2.0", "result": "Bittensor"}}, + "chain_getHead": { + "[]": { + "jsonrpc": "2.0", + "result": "0xae6bd3bf8cc2a7660a315510f72a7e8c8dc31ad58c8d847e86682ee07c6e8938", + } + }, + "chain_getHeader": { + '["0x0f28c25f01cc4b11bb1454c323bd3d04658e4e64b69fcd21ffa01bac0ae47b19"]': { + "jsonrpc": "2.0", + "result": { + "parentHash": "0x3c1304811d6b0f5741c0ce9fcd46416c3fe151561463134b0fa9b68f1bd0188d", + "number": "0x57c8bb", + "stateRoot": "0x48b804f020e871af4eae34fb9d1fae077ff988016e65fafc9e3acbcdc8cfc8d9", + "extrinsicsRoot": "0x9b28450ef2ca7730b29e104d85e65e23cf021d405fb0fb29226dd04b89d36182", + "digest": { + "logs": [ + "0x06617572612023b8b00800000000", + "0x0466726f6e8801446b1f0502f0e66079329d4718fb2f5cd5660a74a1bc0c7556c9ecbd92b54b3700", + "0x05617572610101a8461545e8f4947dd540f2f2131fdf29117e471a9c46970a408ef9684dbf495cdaebeaf8a42efcf0a57fdf75ddf807fb1f4c21fb1fb74b6715cf59a23f24d98e", + ] + }, + }, + }, + "[null]": { + "jsonrpc": "2.0", + "result": { + "parentHash": "0x3c1304811d6b0f5741c0ce9fcd46416c3fe151561463134b0fa9b68f1bd0188d", + "number": "0x57c8bb", + "stateRoot": "0x48b804f020e871af4eae34fb9d1fae077ff988016e65fafc9e3acbcdc8cfc8d9", + "extrinsicsRoot": "0x9b28450ef2ca7730b29e104d85e65e23cf021d405fb0fb29226dd04b89d36182", + "digest": { + "logs": [ + "0x06617572612023b8b00800000000", + "0x0466726f6e8801446b1f0502f0e66079329d4718fb2f5cd5660a74a1bc0c7556c9ecbd92b54b3700", + "0x05617572610101a8461545e8f4947dd540f2f2131fdf29117e471a9c46970a408ef9684dbf495cdaebeaf8a42efcf0a57fdf75ddf807fb1f4c21fb1fb74b6715cf59a23f24d98e", + ] + }, + }, + }, + '["0x0ba73c8d5a0b78f7336a3e394a2a1fa7b7dec0924937b1dfb05a50ac3dd5cfc1"]': { + "jsonrpc": "2.0", + "result": { + "parentHash": "0x6c0da8a9dd5994d374d43210dc401f000b2fe02541bc2e7cd54719655d751a18", + "number": "0x57c4d3", + "stateRoot": "0xc80611aa0e605bc2d0446479dcbce6bee0e1524d07b0a9c6293a190d6a500fef", + "extrinsicsRoot": "0x9ff58560a13b06d4a61af01ca9f0dd2a96cf5bfa6bbc2208fc7e6f763bad5590", + "digest": { + "logs": [ + "0x0661757261203bb4b00800000000", + "0x0466726f6e880102e3f23c7b0347be401daa27b4bd35d09948dc1450bec025807957f499347c5800", + "0x05617572610101bcc856c718c722f327d3fbc3f94c34d97e0bfc794531e94f5590b44b3516c7598ca0ec488b81f9fbf5a9bbb5d18663bd69ca032d1c7134681d9c112387612a87", + ] + }, + }, + }, + '["0xae6bd3bf8cc2a7660a315510f72a7e8c8dc31ad58c8d847e86682ee07c6e8938"]': { + "jsonrpc": "2.0", + "result": { + "parentHash": "0x0f28c25f01cc4b11bb1454c323bd3d04658e4e64b69fcd21ffa01bac0ae47b19", + "number": "0x57c8bc", + "stateRoot": "0x87f0259d14e63525a311f4afabd7273519ac2e98e2dc70cf17292b615889b714", + "extrinsicsRoot": "0x24c53c5248e9e822fb29c381cee462e5f830bba62097d58607dead8da7f3b64b", + "digest": { + "logs": [ + "0x06617572612024b8b00800000000", + "0x0466726f6e88016e91817304fa73ea8595ba3bd2e26fb838c7495ae83501bb266013b2307170f200", + "0x0561757261010152b29d8299665fd87748987347b6b91d206c04a5939ddb543e4776adcfb0ff47196fdaec1a0e2a2225e0485f1fa91b5c4421ff54944df488dcf35ecbc7a57087", + ] + }, + }, + }, + }, + "state_getRuntimeVersion": { + '["0x3c1304811d6b0f5741c0ce9fcd46416c3fe151561463134b0fa9b68f1bd0188d"]': { + "jsonrpc": "2.0", + "result": { + "specName": "node-subtensor", + "implName": "node-subtensor", + "authoringVersion": 1, + "specVersion": 274, + "implVersion": 1, + "apis": [ + ["0xdf6acb689907609b", 5], + ["0x37e397fc7c91f5e4", 2], + ["0x40fe3ad401f8959a", 6], + ["0xfbc577b9d747efd6", 1], + ["0xd2bc9897eed08f15", 3], + ["0xf78b278be53f454c", 2], + ["0xdd718d5cc53262d4", 1], + ["0xab3c0572291feb8b", 1], + ["0xed99c5acb25eedf5", 3], + ["0xbc9d89904f5b923f", 1], + ["0x37c8bb1350a9a2a8", 4], + ["0xf3ff14d5ab527059", 3], + ["0x582211f65bb14b89", 5], + ["0xe65b00e46cedd0aa", 2], + ["0x42e62be4a39e5b60", 1], + ["0x806df4ccaa9ed485", 1], + ["0x8375104b299b74c5", 1], + ["0x5d1fbfbe852f2807", 1], + ["0xc6886e2f8e598b0a", 1], + ], + "transactionVersion": 1, + "stateVersion": 1, + }, + }, + '["0x6c0da8a9dd5994d374d43210dc401f000b2fe02541bc2e7cd54719655d751a18"]': cycle( + ( + { + "jsonrpc": "2.0", + "id": "oj19", + "error": { + "code": 4003, + "message": "Client error: Api called for an unknown Block: State already discarded for 0x6c0da8a9dd5994d374d43210dc401f000b2fe02541bc2e7cd54719655d751a18", + }, + }, + { + "jsonrpc": "2.0", + "result": { + "specName": "node-subtensor", + "implName": "node-subtensor", + "authoringVersion": 1, + "specVersion": 273, + "implVersion": 1, + "apis": [ + ["0xdf6acb689907609b", 5], + ["0x37e397fc7c91f5e4", 2], + ["0x40fe3ad401f8959a", 6], + ["0xfbc577b9d747efd6", 1], + ["0xd2bc9897eed08f15", 3], + ["0xf78b278be53f454c", 2], + ["0xdd718d5cc53262d4", 1], + ["0xab3c0572291feb8b", 1], + ["0xed99c5acb25eedf5", 3], + ["0xbc9d89904f5b923f", 1], + ["0x37c8bb1350a9a2a8", 4], + ["0xf3ff14d5ab527059", 3], + ["0x582211f65bb14b89", 5], + ["0xe65b00e46cedd0aa", 2], + ["0x42e62be4a39e5b60", 1], + ["0x806df4ccaa9ed485", 1], + ["0x8375104b299b74c5", 1], + ["0x5d1fbfbe852f2807", 1], + ["0xc6886e2f8e598b0a", 1], + ], + "transactionVersion": 1, + "stateVersion": 1, + }, + }, + ) + ), + '["0x0f28c25f01cc4b11bb1454c323bd3d04658e4e64b69fcd21ffa01bac0ae47b19"]': { + "jsonrpc": "2.0", + "result": { + "specName": "node-subtensor", + "implName": "node-subtensor", + "authoringVersion": 1, + "specVersion": 274, + "implVersion": 1, + "apis": [ + ["0xdf6acb689907609b", 5], + ["0x37e397fc7c91f5e4", 2], + ["0x40fe3ad401f8959a", 6], + ["0xfbc577b9d747efd6", 1], + ["0xd2bc9897eed08f15", 3], + ["0xf78b278be53f454c", 2], + ["0xdd718d5cc53262d4", 1], + ["0xab3c0572291feb8b", 1], + ["0xed99c5acb25eedf5", 3], + ["0xbc9d89904f5b923f", 1], + ["0x37c8bb1350a9a2a8", 4], + ["0xf3ff14d5ab527059", 3], + ["0x582211f65bb14b89", 5], + ["0xe65b00e46cedd0aa", 2], + ["0x42e62be4a39e5b60", 1], + ["0x806df4ccaa9ed485", 1], + ["0x8375104b299b74c5", 1], + ["0x5d1fbfbe852f2807", 1], + ["0xc6886e2f8e598b0a", 1], + ], + "transactionVersion": 1, + "stateVersion": 1, + }, + }, + }, + "chain_getBlockHash": { + "[5752019]": { + "jsonrpc": "2.0", + "result": "0x0ba73c8d5a0b78f7336a3e394a2a1fa7b7dec0924937b1dfb05a50ac3dd5cfc1", + } + }, + "chain_getBlock": { + '["0x0ba73c8d5a0b78f7336a3e394a2a1fa7b7dec0924937b1dfb05a50ac3dd5cfc1"]': { + "result": {"block": {}} + } + }, + }, } diff --git a/tests/integration_tests/test_subtensor_integration.py b/tests/integration_tests/test_subtensor_integration.py index 0e7c62151a..6c23ae82af 100644 --- a/tests/integration_tests/test_subtensor_integration.py +++ b/tests/integration_tests/test_subtensor_integration.py @@ -17,7 +17,7 @@ def netuid(): yield 23 -async def prepare_test(mocker, seed): +async def prepare_test(mocker, seed, **subtensor_args): """ Helper function: sets up the test environment. """ @@ -25,7 +25,7 @@ async def prepare_test(mocker, seed): "async_substrate_interface.sync_substrate.connect", mocker.Mock(return_value=FakeWebsocket(seed=seed)), ) - subtensor = Subtensor("unknown", _mock=True) + subtensor = Subtensor("unknown", _mock=True, **subtensor_args) return subtensor @@ -151,3 +151,13 @@ def test_mock_subtensor_force_register_neuron(): assert neuron1.coldkey == "cc1" assert neuron2.hotkey == "hk2" assert neuron2.coldkey == "cc2" + + +@pytest.mark.asyncio +async def test_archive_node_retry(mocker): + subtensor = await prepare_test( + mocker, "retry_archive", archive_endpoints=["ws://fake-endpoi.nt"] + ) + current_block = subtensor.substrate.get_block_number() + old_block = current_block - 1000 + assert isinstance((subtensor.substrate.get_block(block_number=old_block)), dict) From a4410d4087f5632007a9b4d637e1f272896958a9 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 11 Jun 2025 22:00:31 +0200 Subject: [PATCH 05/10] Adds archive endpoints to SubtensorAPI --- bittensor/core/subtensor_api/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bittensor/core/subtensor_api/__init__.py b/bittensor/core/subtensor_api/__init__.py index 2d021cd1ce..214ea608b1 100644 --- a/bittensor/core/subtensor_api/__init__.py +++ b/bittensor/core/subtensor_api/__init__.py @@ -75,9 +75,11 @@ def __init__( retry_forever: bool = False, log_verbose: bool = False, mock: bool = False, + archive_endpoints: Optional[list[str]] = None, ): self.network = network self._fallback_endpoints = fallback_endpoints + self._archive_endpoints = archive_endpoints self._retry_forever = retry_forever self._mock = mock self.log_verbose = log_verbose @@ -119,6 +121,7 @@ def _get_subtensor(self) -> Union["_Subtensor", "_AsyncSubtensor"]: fallback_endpoints=self._fallback_endpoints, retry_forever=self._retry_forever, _mock=self._mock, + archive_endpoints=self._archive_endpoints, ) self.initialize = _subtensor.initialize return _subtensor @@ -130,6 +133,7 @@ def _get_subtensor(self) -> Union["_Subtensor", "_AsyncSubtensor"]: fallback_endpoints=self._fallback_endpoints, retry_forever=self._retry_forever, _mock=self._mock, + archive_endpoints=self._archive_endpoints, ) def _determine_chain_endpoint(self) -> str: From 5cfbb74ff2110f3a60aab011d21833299cf3ec98 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 11 Jun 2025 22:10:14 +0200 Subject: [PATCH 06/10] Adds ws_shutdown_timer arg to AsyncSubtensor and SubtensorAPI --- bittensor/core/async_subtensor.py | 7 +++++++ bittensor/core/subtensor_api/__init__.py | 8 ++++++++ pyproject.toml | 2 +- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index f91df67952..fdf9b98a32 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -119,6 +119,7 @@ def __init__( retry_forever: bool = False, _mock: bool = False, archive_endpoints: Optional[list[str]] = None, + websocket_shutdown_timer: float = 5.0, ): """ Initializes an instance of the AsyncSubtensor class. @@ -157,6 +158,7 @@ def __init__( retry_forever=retry_forever, _mock=_mock, archive_endpoints=archive_endpoints, + ws_shutdown_timer=websocket_shutdown_timer ) if self.log_verbose: logging.info( @@ -296,6 +298,7 @@ def _get_substrate( retry_forever: bool = False, _mock: bool = False, archive_endpoints: Optional[list[str]] = None, + ws_shutdown_timer: float = 5.0 ) -> Union[AsyncSubstrateInterface, RetryAsyncSubstrate]: """Creates the Substrate instance based on provided arguments. @@ -307,6 +310,8 @@ def _get_substrate( archive_endpoints: Similar to fallback_endpoints, but specifically only archive nodes. Will be used in cases where you are requesting a block that is too old for your current (presumably lite) node. Defaults to `None` + ws_shutdown_timer: Amount of time, in seconds, to wait after the last response from the chain to close the + connection. Returns: the instance of the SubstrateInterface or RetrySyncSubstrate class. @@ -322,6 +327,7 @@ def _get_substrate( chain_name="Bittensor", _mock=_mock, archive_nodes=archive_endpoints, + ws_shutdown_timer=ws_shutdown_timer ) return AsyncSubstrateInterface( url=self.chain_endpoint, @@ -330,6 +336,7 @@ def _get_substrate( use_remote_preset=True, chain_name="Bittensor", _mock=_mock, + ws_shutdown_timer=ws_shutdown_timer ) # Subtensor queries =========================================================================================== diff --git a/bittensor/core/subtensor_api/__init__.py b/bittensor/core/subtensor_api/__init__.py index 214ea608b1..3f4c8f9900 100644 --- a/bittensor/core/subtensor_api/__init__.py +++ b/bittensor/core/subtensor_api/__init__.py @@ -29,6 +29,11 @@ class SubtensorApi: retry_forever: Whether to retry forever on connection errors. Defaults to `False`. log_verbose: Enables or disables verbose logging. mock: Whether this is a mock instance. Mainly just for use in testing. + archive_endpoints: Similar to fallback_endpoints, but specifically only archive nodes. Will be used in cases + where you are requesting a block that is too old for your current (presumably lite) node. Defaults to + `None` + websocket_shutdown_timer: Amount of time, in seconds, to wait after the last response from the chain to close + the connection. Only applicable to AsyncSubtensor. Example: # sync version @@ -76,11 +81,13 @@ def __init__( log_verbose: bool = False, mock: bool = False, archive_endpoints: Optional[list[str]] = None, + websocket_shutdown_timer: float = 5.0 ): self.network = network self._fallback_endpoints = fallback_endpoints self._archive_endpoints = archive_endpoints self._retry_forever = retry_forever + self._ws_shutdown_timer = websocket_shutdown_timer self._mock = mock self.log_verbose = log_verbose self.is_async = async_subtensor @@ -122,6 +129,7 @@ def _get_subtensor(self) -> Union["_Subtensor", "_AsyncSubtensor"]: retry_forever=self._retry_forever, _mock=self._mock, archive_endpoints=self._archive_endpoints, + websocket_shutdown_timer=self._ws_shutdown_timer ) self.initialize = _subtensor.initialize return _subtensor diff --git a/pyproject.toml b/pyproject.toml index 3309fad71d..91869e8822 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ dependencies = [ "uvicorn", "bittensor-drand>=0.5.0", "bittensor-wallet>=3.0.8", - "async-substrate-interface>=1.3.0" + "async-substrate-interface>=1.3.1" ] [project.optional-dependencies] From e61c9af5139a4c506ff16a480fd59218c03baede Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 11 Jun 2025 22:11:54 +0200 Subject: [PATCH 07/10] Docstring. --- bittensor/core/subtensor_api/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bittensor/core/subtensor_api/__init__.py b/bittensor/core/subtensor_api/__init__.py index 214ea608b1..62ad7c1cda 100644 --- a/bittensor/core/subtensor_api/__init__.py +++ b/bittensor/core/subtensor_api/__init__.py @@ -29,6 +29,9 @@ class SubtensorApi: retry_forever: Whether to retry forever on connection errors. Defaults to `False`. log_verbose: Enables or disables verbose logging. mock: Whether this is a mock instance. Mainly just for use in testing. + archive_endpoints: Similar to fallback_endpoints, but specifically only archive nodes. Will be used in cases + where you are requesting a block that is too old for your current (presumably lite) node. Defaults to + `None` Example: # sync version From 952d956b68fd2496288a4d4e4eb7a09a9830c47e Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 13 Jun 2025 14:23:24 +0200 Subject: [PATCH 08/10] Bumps async-substrate-interface requirement --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3309fad71d..91869e8822 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ dependencies = [ "uvicorn", "bittensor-drand>=0.5.0", "bittensor-wallet>=3.0.8", - "async-substrate-interface>=1.3.0" + "async-substrate-interface>=1.3.1" ] [project.optional-dependencies] From 1ebb4661fdbaae6d7da06b4ed5a8e7b00c390039 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 13 Jun 2025 14:29:53 +0200 Subject: [PATCH 09/10] Docstring update --- bittensor/core/subtensor_api/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bittensor/core/subtensor_api/__init__.py b/bittensor/core/subtensor_api/__init__.py index 62ad7c1cda..464b325b27 100644 --- a/bittensor/core/subtensor_api/__init__.py +++ b/bittensor/core/subtensor_api/__init__.py @@ -30,8 +30,7 @@ class SubtensorApi: log_verbose: Enables or disables verbose logging. mock: Whether this is a mock instance. Mainly just for use in testing. archive_endpoints: Similar to fallback_endpoints, but specifically only archive nodes. Will be used in cases - where you are requesting a block that is too old for your current (presumably lite) node. Defaults to - `None` + where you are requesting a block that is too old for your current (presumably lite) node. Defaults to `None` Example: # sync version From 12297af79c27b3d5aaba9fa0f8e55cb9bf8c3e3d Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 13 Jun 2025 14:37:17 +0200 Subject: [PATCH 10/10] Ruff --- bittensor/core/async_subtensor.py | 8 ++++---- bittensor/core/subtensor_api/__init__.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index fdf9b98a32..11c01f5059 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -158,7 +158,7 @@ def __init__( retry_forever=retry_forever, _mock=_mock, archive_endpoints=archive_endpoints, - ws_shutdown_timer=websocket_shutdown_timer + ws_shutdown_timer=websocket_shutdown_timer, ) if self.log_verbose: logging.info( @@ -298,7 +298,7 @@ def _get_substrate( retry_forever: bool = False, _mock: bool = False, archive_endpoints: Optional[list[str]] = None, - ws_shutdown_timer: float = 5.0 + ws_shutdown_timer: float = 5.0, ) -> Union[AsyncSubstrateInterface, RetryAsyncSubstrate]: """Creates the Substrate instance based on provided arguments. @@ -327,7 +327,7 @@ def _get_substrate( chain_name="Bittensor", _mock=_mock, archive_nodes=archive_endpoints, - ws_shutdown_timer=ws_shutdown_timer + ws_shutdown_timer=ws_shutdown_timer, ) return AsyncSubstrateInterface( url=self.chain_endpoint, @@ -336,7 +336,7 @@ def _get_substrate( use_remote_preset=True, chain_name="Bittensor", _mock=_mock, - ws_shutdown_timer=ws_shutdown_timer + ws_shutdown_timer=ws_shutdown_timer, ) # Subtensor queries =========================================================================================== diff --git a/bittensor/core/subtensor_api/__init__.py b/bittensor/core/subtensor_api/__init__.py index 15d89d71c5..5ae0bf7134 100644 --- a/bittensor/core/subtensor_api/__init__.py +++ b/bittensor/core/subtensor_api/__init__.py @@ -80,7 +80,7 @@ def __init__( log_verbose: bool = False, mock: bool = False, archive_endpoints: Optional[list[str]] = None, - websocket_shutdown_timer: float = 5.0 + websocket_shutdown_timer: float = 5.0, ): self.network = network self._fallback_endpoints = fallback_endpoints @@ -128,7 +128,7 @@ def _get_subtensor(self) -> Union["_Subtensor", "_AsyncSubtensor"]: retry_forever=self._retry_forever, _mock=self._mock, archive_endpoints=self._archive_endpoints, - websocket_shutdown_timer=self._ws_shutdown_timer + websocket_shutdown_timer=self._ws_shutdown_timer, ) self.initialize = _subtensor.initialize return _subtensor