diff --git a/CHANGELOG.md b/CHANGELOG.md index c042ed9bd0..70d29a1875 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## What's Changed +* Release/9.0.0 by @ibraheem-opentensor in https://github.com/opentensor/bittensor/pull/2671 +* fix e2e test by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2673 +* fix e2e test incentive by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2674 +* Add compatibility for read-only systems by @Arthurdw in https://github.com/opentensor/bittensor/pull/2676 +* test: use asynccontextmanager for FastAPI lifespan by @zyzniewski-reef in https://github.com/opentensor/bittensor/pull/2597 +* test(2472): offline unittests by @zyzniewski-reef in https://github.com/opentensor/bittensor/pull/2596 +* Removes redundant assignments in Metagraph by @ibraheem-opentensor in https://github.com/opentensor/bittensor/pull/2680 +* Alpha str formatting by @thewhaleking in https://github.com/opentensor/bittensor/pull/2672 +* Add method for fetching all Neuron Certificates on a Netuid by @thewhaleking in https://github.com/opentensor/bittensor/pull/2677 +* Updates tao_stake in MetagraphInfo by @ibraheem-opentensor in https://github.com/opentensor/bittensor/pull/2682 +* fix(2188): configure uvicorn event loop by @zyzniewski-reef in https://github.com/opentensor/bittensor/pull/2679 +* Refactor AsyncSubtensor aenter logic by @thewhaleking in https://github.com/opentensor/bittensor/pull/2684 +* Backmerge master to staging 900 by @ibraheem-opentensor in https://github.com/opentensor/bittensor/pull/2685 + +## New Contributors +* @Arthurdw made their first contribution in https://github.com/opentensor/bittensor/pull/2676 + +**Full Changelog**: https://github.com/opentensor/bittensor/compare/v9.0.0...v9.0.1 + ## 9.0.0 /2025-02-13 ## What's Changed diff --git a/VERSION b/VERSION index c9277c5a60..6768f7e446 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -9.0.0 \ No newline at end of file +9.0.1 \ No newline at end of file diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 8e357aa141..2f718d7c29 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -173,8 +173,8 @@ async def __aenter__(self): f"[magenta]Connecting to Substrate:[/magenta] [blue]{self}[/blue][magenta]...[/magenta]" ) try: - async with self.substrate: - return self + await self.substrate.initialize() + return self except TimeoutError: logging.error( f"[red]Error[/red]: Timeout occurred connecting to substrate." @@ -1479,6 +1479,41 @@ async def get_neuron_certificate( return None return None + async def get_all_neuron_certificates( + self, + netuid: int, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> dict[str, Certificate]: + """ + Retrieves the TLS certificates for neurons within a specified subnet (netuid) of the Bittensor network. + + Arguments: + netuid: The unique identifier of the subnet. + block: The blockchain block number for the query. + block_hash: The hash of the block to retrieve the parameter from. Do not specify if using block or + reuse_block. + reuse_block: Whether to use the last-used block. Do not set if using block_hash or block. + + Returns: + {ss58: Certificate} for the key/Certificate pairs on the subnet + + This function is used for certificate discovery for setting up mutual tls communication between neurons. + """ + query_certificates = await self.query_map( + module="SubtensorModule", + name="NeuronCertificates", + params=[netuid], + block=block, + block_hash=block_hash, + reuse_block=reuse_block, + ) + output = {} + async for key, item in query_certificates: + output[decode_account_id(key)] = Certificate(item.value) + return output + async def get_neuron_for_pubkey_and_subnet( self, hotkey_ss58: str, diff --git a/bittensor/core/axon.py b/bittensor/core/axon.py index 5e4a64805b..a51f1c3e2d 100644 --- a/bittensor/core/axon.py +++ b/bittensor/core/axon.py @@ -386,7 +386,11 @@ def __init__( self.app = FastAPI() log_level = "trace" if logging.__trace_on__ else "critical" self.fast_config = uvicorn.Config( - self.app, host="0.0.0.0", port=self._config.axon.port, log_level=log_level + self.app, + host="0.0.0.0", + log_level=log_level, + loop="none", + port=self._config.axon.port, ) self.fast_server = FastAPIThreadedServer(config=self.fast_config) self.router = APIRouter() diff --git a/bittensor/core/chain_data/metagraph_info.py b/bittensor/core/chain_data/metagraph_info.py index f79f0d4cc4..1ed2733124 100644 --- a/bittensor/core/chain_data/metagraph_info.py +++ b/bittensor/core/chain_data/metagraph_info.py @@ -1,6 +1,7 @@ from dataclasses import dataclass from typing import Optional, Union +from bittensor.core import settings from bittensor.core.chain_data.axon_info import AxonInfo from bittensor.core.chain_data.chain_identity import ChainIdentity from bittensor.core.chain_data.info_base import InfoBase @@ -234,7 +235,10 @@ def _from_dict(cls, decoded: dict) -> "MetagraphInfo": rank=[u16tf(rk) for rk in decoded.get("rank", [])], block_at_registration=decoded["block_at_registration"], alpha_stake=[_tbwu(ast, _netuid) for ast in decoded["alpha_stake"]], - tao_stake=[_tbwu(ts) for ts in decoded["tao_stake"]], + tao_stake=[ + _tbwu(ts) * settings.ROOT_TAO_STAKE_WEIGHT + for ts in decoded["tao_stake"] + ], total_stake=[_tbwu(ts, _netuid) for ts in decoded["total_stake"]], # Dividend break down tao_dividends_per_hotkey=[ diff --git a/bittensor/core/metagraph.py b/bittensor/core/metagraph.py index 93128f6f01..06b9a66bb2 100644 --- a/bittensor/core/metagraph.py +++ b/bittensor/core/metagraph.py @@ -42,7 +42,6 @@ Tensor = Union["torch.nn.Parameter", NDArray] -ROOT_TAO_STAKES_WEIGHT = 0.18 METAGRAPH_STATE_DICT_NDARRAY_KEYS = [ @@ -773,14 +772,6 @@ def _set_metagraph_attributes(self, block: int): [neuron.validator_trust for neuron in self.neurons], dtype=self._dtype_registry["float32"], ) - self.total_stake = self._create_tensor( - [neuron.total_stake.tao for neuron in self.neurons], - dtype=self._dtype_registry["float32"], - ) - self.stake = self._create_tensor( - [neuron.stake.tao for neuron in self.neurons], - dtype=self._dtype_registry["float32"], - ) self.axons = [n.axon_info for n in self.neurons] def save(self, root_dir: Optional[list[str]] = None) -> "MetagraphMixin": @@ -1606,7 +1597,10 @@ async def _get_all_stakes_from_chain(self): dtype=self._dtype_registry["float32"], ) self.tao_stake = self._create_tensor( - [b.tao * ROOT_TAO_STAKES_WEIGHT for b in subnet_state.tao_stake], + [ + b.tao * settings.ROOT_TAO_STAKE_WEIGHT + for b in subnet_state.tao_stake + ], dtype=self._dtype_registry["float32"], ) self.total_stake = self.stake = self._create_tensor( @@ -1634,7 +1628,7 @@ def __init__( subtensor: Optional["Subtensor"] = None, ): super().__init__(netuid, network, lite, sync, subtensor) - if sync: + if self.should_sync: self.sync() def sync( @@ -1910,7 +1904,10 @@ def _get_all_stakes_from_chain(self): dtype=self._dtype_registry["float32"], ) self.tao_stake = self._create_tensor( - [b.tao * ROOT_TAO_STAKES_WEIGHT for b in subnet_state.tao_stake], + [ + b.tao * settings.ROOT_TAO_STAKE_WEIGHT + for b in subnet_state.tao_stake + ], dtype=self._dtype_registry["float32"], ) self.total_stake = self.stake = self._create_tensor( diff --git a/bittensor/core/settings.py b/bittensor/core/settings.py index 42c4141921..f505f23f56 100644 --- a/bittensor/core/settings.py +++ b/bittensor/core/settings.py @@ -1,4 +1,4 @@ -__version__ = "9.0.0" +__version__ = "9.0.1" import os import re @@ -6,6 +6,9 @@ from munch import munchify +ROOT_TAO_STAKE_WEIGHT = 0.18 + +READ_ONLY = os.getenv("READ_ONLY") == "1" HOME_DIR = Path.home() USER_BITTENSOR_DIR = HOME_DIR / ".bittensor" @@ -13,9 +16,10 @@ MINERS_DIR = USER_BITTENSOR_DIR / "miners" -# Create dirs if they don't exist -WALLETS_DIR.mkdir(parents=True, exist_ok=True) -MINERS_DIR.mkdir(parents=True, exist_ok=True) +if not READ_ONLY: + # Create dirs if they don't exist + WALLETS_DIR.mkdir(parents=True, exist_ok=True) + MINERS_DIR.mkdir(parents=True, exist_ok=True) # Bittensor networks name NETWORKS = ["finney", "test", "archive", "local", "subvortex", "rao", "latent-lite"] diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 03c32a6072..e2d9ffda19 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -1123,6 +1123,32 @@ def get_neuron_certificate( return None return None + def get_all_neuron_certificates( + self, netuid: int, block: Optional[int] = None + ) -> dict[str, Certificate]: + """ + Retrieves the TLS certificates for neurons within a specified subnet (netuid) of the Bittensor network. + + Arguments: + netuid: The unique identifier of the subnet. + block: The blockchain block number for the query. + + Returns: + {ss58: Certificate} for the key/Certificate pairs on the subnet + + This function is used for certificate discovery for setting up mutual tls communication between neurons. + """ + query_certificates = self.query_map( + module="SubtensorModule", + name="NeuronCertificates", + params=[netuid], + block=block, + ) + output = {} + for key, item in query_certificates: + output[decode_account_id(key)] = Certificate(item.value) + return output + def get_neuron_for_pubkey_and_subnet( self, hotkey_ss58: str, netuid: int, block: Optional[int] = None ) -> Optional["NeuronInfo"]: diff --git a/bittensor/utils/balance.py b/bittensor/utils/balance.py index 29ce2faeaa..33d56be13c 100644 --- a/bittensor/utils/balance.py +++ b/bittensor/utils/balance.py @@ -53,8 +53,13 @@ def __float__(self): return self.tao def __str__(self): - """Returns the Balance object as a string in the format "symbolvalue", where the value is in tao.""" - return f"{self.unit}{float(self.tao):,.9f}" + """ + Returns the Balance object as a string in the format "symbolvalue", where the value is in tao. + """ + if self.unit == units[0]: + return f"{self.unit}{float(self.tao):,.9f}" + else: + return f"\u200e{float(self.tao):,.9f}{self.unit}\u200e" def __rich__(self): int_tao, fract_tao = format(float(self.tao), "f").split(".") diff --git a/bittensor/utils/btlogging/loggingmachine.py b/bittensor/utils/btlogging/loggingmachine.py index de33f39905..a8803e70fd 100644 --- a/bittensor/utils/btlogging/loggingmachine.py +++ b/bittensor/utils/btlogging/loggingmachine.py @@ -16,6 +16,7 @@ from statemachine import State, StateMachine +from bittensor.core.settings import READ_ONLY from bittensor.core.config import Config from bittensor.utils.btlogging.console import BittensorConsole from .defines import ( @@ -584,9 +585,12 @@ def add_args(cls, parser: argparse.ArgumentParser, prefix: str = None): default_logging_info = os.getenv("BT_LOGGING_INFO") or False default_logging_trace = os.getenv("BT_LOGGING_TRACE") or False default_logging_record_log = os.getenv("BT_LOGGING_RECORD_LOG") or False - default_logging_logging_dir = os.getenv( - "BT_LOGGING_LOGGING_DIR" - ) or os.path.join("~", ".bittensor", "miners") + default_logging_logging_dir = ( + None + if READ_ONLY + else os.getenv("BT_LOGGING_LOGGING_DIR") + or os.path.join("~", ".bittensor", "miners") + ) parser.add_argument( "--" + prefix_str + "logging.debug", action="store_true", diff --git a/tests/e2e_tests/test_commit_weights.py b/tests/e2e_tests/test_commit_weights.py index 2f7594b727..7e02411ac3 100644 --- a/tests/e2e_tests/test_commit_weights.py +++ b/tests/e2e_tests/test_commit_weights.py @@ -167,6 +167,9 @@ async def test_commit_weights_uses_next_nonce(local_chain, subtensor, alice_wall Raises: AssertionError: If any of the checks or verifications fail """ + # Wait for 2 tempos to pass as CR3 only reveals weights after 2 tempos + subtensor.wait_for_block(20) + netuid = 2 print("Testing test_commit_and_reveal_weights") # Register root as Alice diff --git a/tests/e2e_tests/test_incentive.py b/tests/e2e_tests/test_incentive.py index 7824e8e797..5245139d36 100644 --- a/tests/e2e_tests/test_incentive.py +++ b/tests/e2e_tests/test_incentive.py @@ -23,6 +23,9 @@ async def test_incentive(local_chain, subtensor, templates, alice_wallet, bob_wa AssertionError: If any of the checks or verifications fail """ + # Wait for 2 tempos to spin up chain properly + subtensor.wait_for_block(20) + print("Testing test_incentive") netuid = 2 diff --git a/tests/e2e_tests/test_neuron_certificate.py b/tests/e2e_tests/test_neuron_certificate.py index 245f9bc9fa..674a4ce27c 100644 --- a/tests/e2e_tests/test_neuron_certificate.py +++ b/tests/e2e_tests/test_neuron_certificate.py @@ -47,5 +47,8 @@ async def test_neuron_certificate(subtensor, alice_wallet): ) == encoded_certificate ) + all_certs_query = subtensor.get_all_neuron_certificates(netuid=netuid) + assert alice_wallet.hotkey.ss58_address in all_certs_query.keys() + assert all_certs_query[alice_wallet.hotkey.ss58_address] == encoded_certificate logging.info("✅ Passed test_neuron_certificate") diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py index a5503f8961..9933746279 100644 --- a/tests/unit_tests/conftest.py +++ b/tests/unit_tests/conftest.py @@ -1,5 +1,8 @@ import pytest from aioresponses import aioresponses +from async_substrate_interface.sync_substrate import SubstrateInterface + +import bittensor.core.subtensor @pytest.fixture @@ -11,3 +14,30 @@ def force_legacy_torch_compatible_api(monkeypatch): def mock_aio_response(): with aioresponses() as m: yield m + + +@pytest.fixture +def mock_substrate_interface(mocker): + mocked = mocker.MagicMock( + autospec=SubstrateInterface, + ) + + mocker.patch("bittensor.core.subtensor.SubstrateInterface", return_value=mocked) + + return mocked + + +@pytest.fixture +def subtensor(mock_substrate_interface): + return bittensor.core.subtensor.Subtensor() + + +@pytest.fixture +def mock_get_external_ip(mocker): + mocked = mocker.Mock( + return_value="192.168.1.1", + ) + + mocker.patch("bittensor.utils.networking.get_external_ip", mocked) + + return mocked diff --git a/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py b/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py index 5df1225b18..fd09bd106b 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py +++ b/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py @@ -11,7 +11,7 @@ @pytest.fixture def subtensor(mocker): fake_substrate = mocker.AsyncMock() - fake_substrate.websocket.sock.getsockopt.return_value = 0 + fake_substrate.websocket.socket.getsockopt.return_value = 0 mocker.patch.object( subtensor_module, "AsyncSubstrateInterface", return_value=fake_substrate ) diff --git a/tests/unit_tests/extrinsics/test_commit_reveal.py b/tests/unit_tests/extrinsics/test_commit_reveal.py index d91dde2673..33825dfd53 100644 --- a/tests/unit_tests/extrinsics/test_commit_reveal.py +++ b/tests/unit_tests/extrinsics/test_commit_reveal.py @@ -6,17 +6,6 @@ from bittensor.core import subtensor as subtensor_module from bittensor.core.chain_data import SubnetHyperparameters from bittensor.core.extrinsics import commit_reveal -from bittensor.core.subtensor import Subtensor - - -@pytest.fixture -def subtensor(mocker): - fake_substrate = mocker.MagicMock() - fake_substrate.websocket.sock.getsockopt.return_value = 0 - mocker.patch.object( - subtensor_module, "SubstrateInterface", return_value=fake_substrate - ) - yield Subtensor() @pytest.fixture diff --git a/tests/unit_tests/extrinsics/test_commit_weights.py b/tests/unit_tests/extrinsics/test_commit_weights.py index 18b9e987dd..14993562a2 100644 --- a/tests/unit_tests/extrinsics/test_commit_weights.py +++ b/tests/unit_tests/extrinsics/test_commit_weights.py @@ -1,23 +1,10 @@ -import pytest from bittensor_wallet import Wallet -from bittensor.core import subtensor as subtensor_module -from bittensor.core.settings import version_as_int -from bittensor.core.subtensor import Subtensor from bittensor.core.extrinsics.commit_weights import ( _do_commit_weights, _do_reveal_weights, ) - - -@pytest.fixture -def subtensor(mocker): - fake_substrate = mocker.MagicMock() - fake_substrate.websocket.sock.getsockopt.return_value = 0 - mocker.patch.object( - subtensor_module, "SubstrateInterface", return_value=fake_substrate - ) - return Subtensor() +from bittensor.core.settings import version_as_int def test_do_commit_weights(subtensor, mocker): diff --git a/tests/unit_tests/extrinsics/test_transfer.py b/tests/unit_tests/extrinsics/test_transfer.py index 607d703758..95f1cf8de5 100644 --- a/tests/unit_tests/extrinsics/test_transfer.py +++ b/tests/unit_tests/extrinsics/test_transfer.py @@ -1,21 +1,7 @@ -import pytest - -from bittensor.core import subtensor as subtensor_module from bittensor.core.extrinsics.transfer import _do_transfer -from bittensor.core.subtensor import Subtensor from bittensor.utils.balance import Balance -@pytest.fixture -def subtensor(mocker): - fake_substrate = mocker.MagicMock() - fake_substrate.websocket.sock.getsockopt.return_value = 0 - mocker.patch.object( - subtensor_module, "SubstrateInterface", return_value=fake_substrate - ) - return Subtensor() - - def test_do_transfer_is_success_true(subtensor, mocker): """Successful do_transfer call.""" # Prep diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 83a29f3298..5123ce7607 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -128,9 +128,8 @@ async def test_async_subtensor_magic_methods(mocker): pass # Asserts - fake_async_substrate.__aenter__.assert_called_once() - fake_async_substrate.__aexit__.assert_called_once() - fake_async_substrate.close.assert_awaited_once() + fake_async_substrate.initialize.assert_called_once() + fake_async_substrate.close.assert_called_once() @pytest.mark.parametrize( @@ -145,7 +144,7 @@ async def test_async_subtensor_aenter_connection_refused_error( # Preps fake_async_substrate = mocker.AsyncMock( autospec=async_subtensor.AsyncSubstrateInterface, - __aenter__=mocker.AsyncMock(side_effect=error), + initialize=mocker.AsyncMock(side_effect=error), ) mocker.patch.object( async_subtensor, "AsyncSubstrateInterface", return_value=fake_async_substrate @@ -158,7 +157,7 @@ async def test_async_subtensor_aenter_connection_refused_error( pass # Asserts - fake_async_substrate.__aenter__.assert_called_once() + fake_async_substrate.initialize.assert_awaited_once() @pytest.mark.asyncio @@ -2676,3 +2675,18 @@ async def test_set_subnet_identity(mocker, subtensor): wait_for_inclusion=False, ) assert result == mocked_extrinsic.return_value + + +@pytest.mark.asyncio +async def test_get_all_neuron_certificates(mocker, subtensor): + fake_netuid = 12 + mocked_query_map_subtensor = mocker.AsyncMock() + mocker.patch.object(subtensor.substrate, "query_map", mocked_query_map_subtensor) + await subtensor.get_all_neuron_certificates(fake_netuid) + mocked_query_map_subtensor.assert_awaited_once_with( + module="SubtensorModule", + storage_function="NeuronCertificates", + params=[fake_netuid], + block_hash=None, + reuse_block_hash=False, + ) diff --git a/tests/unit_tests/test_axon.py b/tests/unit_tests/test_axon.py index 512b4635ae..ed34f3e437 100644 --- a/tests/unit_tests/test_axon.py +++ b/tests/unit_tests/test_axon.py @@ -1,18 +1,23 @@ +import asyncio +import contextlib import re +import threading import time from dataclasses import dataclass from typing import Any, Optional, Tuple from unittest import IsolatedAsyncioTestCase from unittest.mock import AsyncMock, MagicMock, patch +import aiohttp import fastapi import netaddr import pydantic import pytest +import uvicorn from fastapi.testclient import TestClient from starlette.requests import Request -from bittensor.core.axon import AxonMiddleware, Axon +from bittensor.core.axon import Axon, AxonMiddleware, FastAPIThreadedServer from bittensor.core.errors import RunException from bittensor.core.settings import version_as_int from bittensor.core.stream import StreamingSynapse @@ -26,7 +31,7 @@ ) -def test_attach_initial(): +def test_attach_initial(mock_get_external_ip): # Create a mock AxonServer instance server = Axon() @@ -71,7 +76,7 @@ def wrong_verify_fn(synapse: TestSynapse) -> bool: server.attach(forward_fn, blacklist_fn, priority_fn, wrong_verify_fn) -def test_attach(): +def test_attach(mock_get_external_ip): # Create a mock AxonServer instance server = Axon() @@ -144,7 +149,7 @@ def mock_request(): @pytest.fixture -def axon_instance(): +def axon_instance(mock_get_external_ip): axon = Axon() axon.required_hash_fields = {"test_endpoint": ["field1", "field2"]} axon.forward_class_types = { @@ -329,7 +334,7 @@ async def test_verify_body_integrity_error_cases( (MockInfo(), "MockInfoString", "edge_case_empty_string"), ], ) -def test_to_string(info_return, expected_output, test_id): +def test_to_string(info_return, expected_output, test_id, mock_get_external_ip): # Arrange axon = Axon() with patch.object(axon, "info", return_value=info_return): @@ -358,7 +363,9 @@ def test_to_string(info_return, expected_output, test_id): ), ], ) -def test_valid_ipv4_and_ipv6_address(ip, port, expected_ip_type, test_id): +def test_valid_ipv4_and_ipv6_address( + ip, port, expected_ip_type, test_id, mock_get_external_ip +): # Arrange hotkey = MockHotkey("5EemgxS7cmYbD34esCFoBgUZZC8JdnGtQvV5Qw3QFUCRRtGP") coldkey = MockHotkey("5EemgxS7cmYbD34esCFoBgUZZC8JdnGtQvV5Qw3QFUCRRtGP") @@ -431,7 +438,14 @@ def test_invalid_ip_address(ip, port, expected_exception): ], ) def test_axon_str_representation( - ip, port, ss58_address, started, forward_fns, expected_str, test_id + ip, + port, + ss58_address, + started, + forward_fns, + expected_str, + test_id, + mock_get_external_ip, ): # Arrange hotkey = MockHotkey(ss58_address) @@ -765,3 +779,50 @@ async def forward_fn(synapse: streaming_synapse_cls): "computed_body_hash": "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a", }, ) + + +@pytest.mark.asyncio +async def test_threaded_fastapi(): + server_started = threading.Event() + server_stopped = threading.Event() + + @contextlib.asynccontextmanager + async def lifespan(app): + server_started.set() + yield + server_stopped.set() + + app = fastapi.FastAPI( + lifespan=lifespan, + ) + app.get("/")(lambda: "Hello World") + + server = FastAPIThreadedServer( + uvicorn.Config(app, loop="none"), + ) + server.start() + + server_started.wait(3.0) + + async def wait_for_server(): + while not (server.started or server_stopped.is_set()): + await asyncio.sleep(1.0) + + await asyncio.wait_for(wait_for_server(), 7.0) + + assert server.is_running is True + + async with aiohttp.ClientSession( + base_url="http://127.0.0.1:8000", + ) as session: + async with session.get("/") as response: + assert await response.text() == '"Hello World"' + + server.stop() + + assert server.should_exit is True + + server_stopped.wait() + + with pytest.raises(aiohttp.ClientConnectorError): + await session.get("/") diff --git a/tests/unit_tests/test_dendrite.py b/tests/unit_tests/test_dendrite.py index 113138bef1..2f5018337e 100644 --- a/tests/unit_tests/test_dendrite.py +++ b/tests/unit_tests/test_dendrite.py @@ -29,7 +29,7 @@ def dummy(synapse: SynapseDummy) -> SynapseDummy: @pytest.fixture -def setup_dendrite(): +def setup_dendrite(mock_get_external_ip): # Assuming bittensor.Wallet() returns a wallet object user_wallet = get_mock_wallet() dendrite_obj = Dendrite(user_wallet) @@ -51,7 +51,10 @@ def axon_info(): @pytest.fixture(scope="session") def setup_axon(): wallet = get_mock_wallet() - axon = Axon(wallet) + axon = Axon( + wallet, + external_ip="192.168.1.1", + ) axon.attach(forward_fn=dummy) axon.start() yield axon @@ -103,7 +106,7 @@ def __await__(self): return self().__await__() -def test_dendrite_create_wallet(): +def test_dendrite_create_wallet(mock_get_external_ip): d = Dendrite(get_mock_wallet()) d = Dendrite(get_mock_wallet().hotkey) d = Dendrite(get_mock_wallet().coldkeypub) @@ -111,7 +114,7 @@ def test_dendrite_create_wallet(): @pytest.mark.asyncio -async def test_forward_many(): +async def test_forward_many(mock_get_external_ip): n = 10 d = Dendrite(wallet=get_mock_wallet()) d.call = AsyncMock() @@ -128,7 +131,7 @@ async def test_forward_many(): assert len([resp]) == 1 -def test_pre_process_synapse(): +def test_pre_process_synapse(mock_get_external_ip): d = Dendrite(wallet=get_mock_wallet()) s = Synapse() synapse = d.preprocess_synapse_for_request( diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 385bb45501..0bd533c534 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -73,7 +73,7 @@ def call_params_with_certificate(): return params -def test_methods_comparable(mocker): +def test_methods_comparable(mock_substrate_interface): """Verifies that methods in sync and async Subtensors are comparable.""" # Preps subtensor = Subtensor(_mock=True) @@ -142,9 +142,7 @@ def test_serve_axon_with_external_ip_set(): assert axon_info.ip == external_ip -def test_serve_axon_with_external_port_set(): - external_ip: str = "2001:0db8:85a3:0000:0000:8a2e:0370:7334" - +def test_serve_axon_with_external_port_set(mock_get_external_ip): internal_port: int = 1234 external_port: int = 5678 @@ -179,14 +177,10 @@ def test_serve_axon_with_external_port_set(): config=mock_config, ) - with mock.patch( - "bittensor.utils.networking.get_external_ip", return_value=external_ip - ): - # mock the get_external_ip function to return the external ip - mock_subtensor.serve_axon( - netuid=-1, - axon=mock_axon_with_external_port_set, - ) + mock_subtensor.serve_axon( + netuid=-1, + axon=mock_axon_with_external_port_set, + ) mock_serve_axon.assert_called_once() # verify that the axon is served to the network with the external port @@ -294,24 +288,6 @@ def test_determine_chain_endpoint_and_network( assert result_endpoint == expected_endpoint -@pytest.fixture -def subtensor(mocker): - fake_substrate = mocker.MagicMock() - fake_substrate.websocket.sock.getsockopt.return_value = 0 - mocker.patch.object( - subtensor_module, "SubstrateInterface", return_value=fake_substrate - ) - mocker.patch.object( - sync_substrate, - "connect", - return_value=mocker.MagicMock(), - ) - fake_websocket = mocker.MagicMock() - fake_websocket.client.connect.return_value = 0 - # mocker.patch.object(subtensor_module, "ws_client", return_value=fake_websocket) - return Subtensor() - - @pytest.fixture def mock_logger(): with mock.patch.object(logging, "warning") as mock_warning: @@ -3084,3 +3060,16 @@ def test_set_subnet_identity(mocker, subtensor): wait_for_inclusion=False, ) assert result == mocked_extrinsic.return_value + + +def test_get_all_neuron_certificates(mocker, subtensor): + fake_netuid = 12 + mocked_query_map_subtensor = mocker.MagicMock() + mocker.patch.object(subtensor.substrate, "query_map", mocked_query_map_subtensor) + subtensor.get_all_neuron_certificates(fake_netuid) + mocked_query_map_subtensor.assert_called_once_with( + module="SubtensorModule", + storage_function="NeuronCertificates", + params=[fake_netuid], + block_hash=None, + ) diff --git a/tests/unit_tests/utils/test_networking.py b/tests/unit_tests/utils/test_networking.py index 2037718578..a3f2c54ac6 100644 --- a/tests/unit_tests/utils/test_networking.py +++ b/tests/unit_tests/utils/test_networking.py @@ -83,13 +83,42 @@ def test_int_to_ip6_underflow(): # Test getting external IP address -def test_get_external_ip(): +def test_get_external_ip(mocker): """Test getting the external IP address.""" - assert utils.networking.get_external_ip() + mocked_requests_get = mock.Mock( + return_value=mock.Mock( + **{ + "text": "192.168.1.1", + }, + ), + ) + + mocker.patch.object( + requests, + "get", + mocked_requests_get, + ) + + assert utils.networking.get_external_ip() == "192.168.1.1" + + mocked_requests_get.assert_called_once_with("https://checkip.amazonaws.com") -def test_get_external_ip_os_broken(): +def test_get_external_ip_os_broken(mocker): """Test getting the external IP address when os.popen is broken.""" + mocked_requests_get = mock.Mock( + return_value=mock.Mock( + **{ + "text": "192.168.1.1", + }, + ), + ) + + mocker.patch.object( + requests, + "get", + mocked_requests_get, + ) class FakeReadline: def readline(self): @@ -99,7 +128,9 @@ def mock_call(): return FakeReadline() with mock.patch.object(os, "popen", new=mock_call): - assert utils.networking.get_external_ip() + assert utils.networking.get_external_ip() == "192.168.1.1" + + mocked_requests_get.assert_called_once_with("https://checkip.amazonaws.com") def test_get_external_ip_os_request_urllib_broken():