Skip to content

Commit fad97f6

Browse files
authoredFeb 20, 2025··
Merge pull request #2686 from opentensor/release/9.0.1
Release/9.0.1
2 parents 13b6b3c + c228c6c commit fad97f6

23 files changed

+315
-117
lines changed
 

‎CHANGELOG.md

+20
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
11
# Changelog
22

3+
## What's Changed
4+
* Release/9.0.0 by @ibraheem-opentensor in https://github.com/opentensor/bittensor/pull/2671
5+
* fix e2e test by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2673
6+
* fix e2e test incentive by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2674
7+
* Add compatibility for read-only systems by @Arthurdw in https://github.com/opentensor/bittensor/pull/2676
8+
* test: use asynccontextmanager for FastAPI lifespan by @zyzniewski-reef in https://github.com/opentensor/bittensor/pull/2597
9+
* test(2472): offline unittests by @zyzniewski-reef in https://github.com/opentensor/bittensor/pull/2596
10+
* Removes redundant assignments in Metagraph by @ibraheem-opentensor in https://github.com/opentensor/bittensor/pull/2680
11+
* Alpha str formatting by @thewhaleking in https://github.com/opentensor/bittensor/pull/2672
12+
* Add method for fetching all Neuron Certificates on a Netuid by @thewhaleking in https://github.com/opentensor/bittensor/pull/2677
13+
* Updates tao_stake in MetagraphInfo by @ibraheem-opentensor in https://github.com/opentensor/bittensor/pull/2682
14+
* fix(2188): configure uvicorn event loop by @zyzniewski-reef in https://github.com/opentensor/bittensor/pull/2679
15+
* Refactor AsyncSubtensor aenter logic by @thewhaleking in https://github.com/opentensor/bittensor/pull/2684
16+
* Backmerge master to staging 900 by @ibraheem-opentensor in https://github.com/opentensor/bittensor/pull/2685
17+
18+
## New Contributors
19+
* @Arthurdw made their first contribution in https://github.com/opentensor/bittensor/pull/2676
20+
21+
**Full Changelog**: https://github.com/opentensor/bittensor/compare/v9.0.0...v9.0.1
22+
323
## 9.0.0 /2025-02-13
424

525
## What's Changed

‎VERSION

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
9.0.0
1+
9.0.1

‎bittensor/core/async_subtensor.py

+37-2
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,8 @@ async def __aenter__(self):
173173
f"[magenta]Connecting to Substrate:[/magenta] [blue]{self}[/blue][magenta]...[/magenta]"
174174
)
175175
try:
176-
async with self.substrate:
177-
return self
176+
await self.substrate.initialize()
177+
return self
178178
except TimeoutError:
179179
logging.error(
180180
f"[red]Error[/red]: Timeout occurred connecting to substrate."
@@ -1479,6 +1479,41 @@ async def get_neuron_certificate(
14791479
return None
14801480
return None
14811481

1482+
async def get_all_neuron_certificates(
1483+
self,
1484+
netuid: int,
1485+
block: Optional[int] = None,
1486+
block_hash: Optional[str] = None,
1487+
reuse_block: bool = False,
1488+
) -> dict[str, Certificate]:
1489+
"""
1490+
Retrieves the TLS certificates for neurons within a specified subnet (netuid) of the Bittensor network.
1491+
1492+
Arguments:
1493+
netuid: The unique identifier of the subnet.
1494+
block: The blockchain block number for the query.
1495+
block_hash: The hash of the block to retrieve the parameter from. Do not specify if using block or
1496+
reuse_block.
1497+
reuse_block: Whether to use the last-used block. Do not set if using block_hash or block.
1498+
1499+
Returns:
1500+
{ss58: Certificate} for the key/Certificate pairs on the subnet
1501+
1502+
This function is used for certificate discovery for setting up mutual tls communication between neurons.
1503+
"""
1504+
query_certificates = await self.query_map(
1505+
module="SubtensorModule",
1506+
name="NeuronCertificates",
1507+
params=[netuid],
1508+
block=block,
1509+
block_hash=block_hash,
1510+
reuse_block=reuse_block,
1511+
)
1512+
output = {}
1513+
async for key, item in query_certificates:
1514+
output[decode_account_id(key)] = Certificate(item.value)
1515+
return output
1516+
14821517
async def get_neuron_for_pubkey_and_subnet(
14831518
self,
14841519
hotkey_ss58: str,

‎bittensor/core/axon.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,11 @@ def __init__(
386386
self.app = FastAPI()
387387
log_level = "trace" if logging.__trace_on__ else "critical"
388388
self.fast_config = uvicorn.Config(
389-
self.app, host="0.0.0.0", port=self._config.axon.port, log_level=log_level
389+
self.app,
390+
host="0.0.0.0",
391+
log_level=log_level,
392+
loop="none",
393+
port=self._config.axon.port,
390394
)
391395
self.fast_server = FastAPIThreadedServer(config=self.fast_config)
392396
self.router = APIRouter()

‎bittensor/core/chain_data/metagraph_info.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from dataclasses import dataclass
22
from typing import Optional, Union
33

4+
from bittensor.core import settings
45
from bittensor.core.chain_data.axon_info import AxonInfo
56
from bittensor.core.chain_data.chain_identity import ChainIdentity
67
from bittensor.core.chain_data.info_base import InfoBase
@@ -234,7 +235,10 @@ def _from_dict(cls, decoded: dict) -> "MetagraphInfo":
234235
rank=[u16tf(rk) for rk in decoded.get("rank", [])],
235236
block_at_registration=decoded["block_at_registration"],
236237
alpha_stake=[_tbwu(ast, _netuid) for ast in decoded["alpha_stake"]],
237-
tao_stake=[_tbwu(ts) for ts in decoded["tao_stake"]],
238+
tao_stake=[
239+
_tbwu(ts) * settings.ROOT_TAO_STAKE_WEIGHT
240+
for ts in decoded["tao_stake"]
241+
],
238242
total_stake=[_tbwu(ts, _netuid) for ts in decoded["total_stake"]],
239243
# Dividend break down
240244
tao_dividends_per_hotkey=[

‎bittensor/core/metagraph.py

+9-12
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242

4343

4444
Tensor = Union["torch.nn.Parameter", NDArray]
45-
ROOT_TAO_STAKES_WEIGHT = 0.18
4645

4746

4847
METAGRAPH_STATE_DICT_NDARRAY_KEYS = [
@@ -773,14 +772,6 @@ def _set_metagraph_attributes(self, block: int):
773772
[neuron.validator_trust for neuron in self.neurons],
774773
dtype=self._dtype_registry["float32"],
775774
)
776-
self.total_stake = self._create_tensor(
777-
[neuron.total_stake.tao for neuron in self.neurons],
778-
dtype=self._dtype_registry["float32"],
779-
)
780-
self.stake = self._create_tensor(
781-
[neuron.stake.tao for neuron in self.neurons],
782-
dtype=self._dtype_registry["float32"],
783-
)
784775
self.axons = [n.axon_info for n in self.neurons]
785776

786777
def save(self, root_dir: Optional[list[str]] = None) -> "MetagraphMixin":
@@ -1606,7 +1597,10 @@ async def _get_all_stakes_from_chain(self):
16061597
dtype=self._dtype_registry["float32"],
16071598
)
16081599
self.tao_stake = self._create_tensor(
1609-
[b.tao * ROOT_TAO_STAKES_WEIGHT for b in subnet_state.tao_stake],
1600+
[
1601+
b.tao * settings.ROOT_TAO_STAKE_WEIGHT
1602+
for b in subnet_state.tao_stake
1603+
],
16101604
dtype=self._dtype_registry["float32"],
16111605
)
16121606
self.total_stake = self.stake = self._create_tensor(
@@ -1634,7 +1628,7 @@ def __init__(
16341628
subtensor: Optional["Subtensor"] = None,
16351629
):
16361630
super().__init__(netuid, network, lite, sync, subtensor)
1637-
if sync:
1631+
if self.should_sync:
16381632
self.sync()
16391633

16401634
def sync(
@@ -1910,7 +1904,10 @@ def _get_all_stakes_from_chain(self):
19101904
dtype=self._dtype_registry["float32"],
19111905
)
19121906
self.tao_stake = self._create_tensor(
1913-
[b.tao * ROOT_TAO_STAKES_WEIGHT for b in subnet_state.tao_stake],
1907+
[
1908+
b.tao * settings.ROOT_TAO_STAKE_WEIGHT
1909+
for b in subnet_state.tao_stake
1910+
],
19141911
dtype=self._dtype_registry["float32"],
19151912
)
19161913
self.total_stake = self.stake = self._create_tensor(

‎bittensor/core/settings.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
1-
__version__ = "9.0.0"
1+
__version__ = "9.0.1"
22

33
import os
44
import re
55
from pathlib import Path
66

77
from munch import munchify
88

9+
ROOT_TAO_STAKE_WEIGHT = 0.18
10+
11+
READ_ONLY = os.getenv("READ_ONLY") == "1"
912

1013
HOME_DIR = Path.home()
1114
USER_BITTENSOR_DIR = HOME_DIR / ".bittensor"
1215
WALLETS_DIR = USER_BITTENSOR_DIR / "wallets"
1316
MINERS_DIR = USER_BITTENSOR_DIR / "miners"
1417

1518

16-
# Create dirs if they don't exist
17-
WALLETS_DIR.mkdir(parents=True, exist_ok=True)
18-
MINERS_DIR.mkdir(parents=True, exist_ok=True)
19+
if not READ_ONLY:
20+
# Create dirs if they don't exist
21+
WALLETS_DIR.mkdir(parents=True, exist_ok=True)
22+
MINERS_DIR.mkdir(parents=True, exist_ok=True)
1923

2024
# Bittensor networks name
2125
NETWORKS = ["finney", "test", "archive", "local", "subvortex", "rao", "latent-lite"]

‎bittensor/core/subtensor.py

+26
Original file line numberDiff line numberDiff line change
@@ -1123,6 +1123,32 @@ def get_neuron_certificate(
11231123
return None
11241124
return None
11251125

1126+
def get_all_neuron_certificates(
1127+
self, netuid: int, block: Optional[int] = None
1128+
) -> dict[str, Certificate]:
1129+
"""
1130+
Retrieves the TLS certificates for neurons within a specified subnet (netuid) of the Bittensor network.
1131+
1132+
Arguments:
1133+
netuid: The unique identifier of the subnet.
1134+
block: The blockchain block number for the query.
1135+
1136+
Returns:
1137+
{ss58: Certificate} for the key/Certificate pairs on the subnet
1138+
1139+
This function is used for certificate discovery for setting up mutual tls communication between neurons.
1140+
"""
1141+
query_certificates = self.query_map(
1142+
module="SubtensorModule",
1143+
name="NeuronCertificates",
1144+
params=[netuid],
1145+
block=block,
1146+
)
1147+
output = {}
1148+
for key, item in query_certificates:
1149+
output[decode_account_id(key)] = Certificate(item.value)
1150+
return output
1151+
11261152
def get_neuron_for_pubkey_and_subnet(
11271153
self, hotkey_ss58: str, netuid: int, block: Optional[int] = None
11281154
) -> Optional["NeuronInfo"]:

‎bittensor/utils/balance.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,13 @@ def __float__(self):
5353
return self.tao
5454

5555
def __str__(self):
56-
"""Returns the Balance object as a string in the format "symbolvalue", where the value is in tao."""
57-
return f"{self.unit}{float(self.tao):,.9f}"
56+
"""
57+
Returns the Balance object as a string in the format "symbolvalue", where the value is in tao.
58+
"""
59+
if self.unit == units[0]:
60+
return f"{self.unit}{float(self.tao):,.9f}"
61+
else:
62+
return f"\u200e{float(self.tao):,.9f}{self.unit}\u200e"
5863

5964
def __rich__(self):
6065
int_tao, fract_tao = format(float(self.tao), "f").split(".")

‎bittensor/utils/btlogging/loggingmachine.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
from statemachine import State, StateMachine
1818

19+
from bittensor.core.settings import READ_ONLY
1920
from bittensor.core.config import Config
2021
from bittensor.utils.btlogging.console import BittensorConsole
2122
from .defines import (
@@ -584,9 +585,12 @@ def add_args(cls, parser: argparse.ArgumentParser, prefix: str = None):
584585
default_logging_info = os.getenv("BT_LOGGING_INFO") or False
585586
default_logging_trace = os.getenv("BT_LOGGING_TRACE") or False
586587
default_logging_record_log = os.getenv("BT_LOGGING_RECORD_LOG") or False
587-
default_logging_logging_dir = os.getenv(
588-
"BT_LOGGING_LOGGING_DIR"
589-
) or os.path.join("~", ".bittensor", "miners")
588+
default_logging_logging_dir = (
589+
None
590+
if READ_ONLY
591+
else os.getenv("BT_LOGGING_LOGGING_DIR")
592+
or os.path.join("~", ".bittensor", "miners")
593+
)
590594
parser.add_argument(
591595
"--" + prefix_str + "logging.debug",
592596
action="store_true",

‎tests/e2e_tests/test_commit_weights.py

+3
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,9 @@ async def test_commit_weights_uses_next_nonce(local_chain, subtensor, alice_wall
167167
Raises:
168168
AssertionError: If any of the checks or verifications fail
169169
"""
170+
# Wait for 2 tempos to pass as CR3 only reveals weights after 2 tempos
171+
subtensor.wait_for_block(20)
172+
170173
netuid = 2
171174
print("Testing test_commit_and_reveal_weights")
172175
# Register root as Alice

‎tests/e2e_tests/test_incentive.py

+3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ async def test_incentive(local_chain, subtensor, templates, alice_wallet, bob_wa
2323
AssertionError: If any of the checks or verifications fail
2424
"""
2525

26+
# Wait for 2 tempos to spin up chain properly
27+
subtensor.wait_for_block(20)
28+
2629
print("Testing test_incentive")
2730
netuid = 2
2831

‎tests/e2e_tests/test_neuron_certificate.py

+3
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,8 @@ async def test_neuron_certificate(subtensor, alice_wallet):
4747
)
4848
== encoded_certificate
4949
)
50+
all_certs_query = subtensor.get_all_neuron_certificates(netuid=netuid)
51+
assert alice_wallet.hotkey.ss58_address in all_certs_query.keys()
52+
assert all_certs_query[alice_wallet.hotkey.ss58_address] == encoded_certificate
5053

5154
logging.info("✅ Passed test_neuron_certificate")

‎tests/unit_tests/conftest.py

+30
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import pytest
22
from aioresponses import aioresponses
3+
from async_substrate_interface.sync_substrate import SubstrateInterface
4+
5+
import bittensor.core.subtensor
36

47

58
@pytest.fixture
@@ -11,3 +14,30 @@ def force_legacy_torch_compatible_api(monkeypatch):
1114
def mock_aio_response():
1215
with aioresponses() as m:
1316
yield m
17+
18+
19+
@pytest.fixture
20+
def mock_substrate_interface(mocker):
21+
mocked = mocker.MagicMock(
22+
autospec=SubstrateInterface,
23+
)
24+
25+
mocker.patch("bittensor.core.subtensor.SubstrateInterface", return_value=mocked)
26+
27+
return mocked
28+
29+
30+
@pytest.fixture
31+
def subtensor(mock_substrate_interface):
32+
return bittensor.core.subtensor.Subtensor()
33+
34+
35+
@pytest.fixture
36+
def mock_get_external_ip(mocker):
37+
mocked = mocker.Mock(
38+
return_value="192.168.1.1",
39+
)
40+
41+
mocker.patch("bittensor.utils.networking.get_external_ip", mocked)
42+
43+
return mocked

‎tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
@pytest.fixture
1212
def subtensor(mocker):
1313
fake_substrate = mocker.AsyncMock()
14-
fake_substrate.websocket.sock.getsockopt.return_value = 0
14+
fake_substrate.websocket.socket.getsockopt.return_value = 0
1515
mocker.patch.object(
1616
subtensor_module, "AsyncSubstrateInterface", return_value=fake_substrate
1717
)

‎tests/unit_tests/extrinsics/test_commit_reveal.py

-11
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,6 @@
66
from bittensor.core import subtensor as subtensor_module
77
from bittensor.core.chain_data import SubnetHyperparameters
88
from bittensor.core.extrinsics import commit_reveal
9-
from bittensor.core.subtensor import Subtensor
10-
11-
12-
@pytest.fixture
13-
def subtensor(mocker):
14-
fake_substrate = mocker.MagicMock()
15-
fake_substrate.websocket.sock.getsockopt.return_value = 0
16-
mocker.patch.object(
17-
subtensor_module, "SubstrateInterface", return_value=fake_substrate
18-
)
19-
yield Subtensor()
209

2110

2211
@pytest.fixture

‎tests/unit_tests/extrinsics/test_commit_weights.py

+1-14
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,10 @@
1-
import pytest
21
from bittensor_wallet import Wallet
32

4-
from bittensor.core import subtensor as subtensor_module
5-
from bittensor.core.settings import version_as_int
6-
from bittensor.core.subtensor import Subtensor
73
from bittensor.core.extrinsics.commit_weights import (
84
_do_commit_weights,
95
_do_reveal_weights,
106
)
11-
12-
13-
@pytest.fixture
14-
def subtensor(mocker):
15-
fake_substrate = mocker.MagicMock()
16-
fake_substrate.websocket.sock.getsockopt.return_value = 0
17-
mocker.patch.object(
18-
subtensor_module, "SubstrateInterface", return_value=fake_substrate
19-
)
20-
return Subtensor()
7+
from bittensor.core.settings import version_as_int
218

229

2310
def test_do_commit_weights(subtensor, mocker):

‎tests/unit_tests/extrinsics/test_transfer.py

-14
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,7 @@
1-
import pytest
2-
3-
from bittensor.core import subtensor as subtensor_module
41
from bittensor.core.extrinsics.transfer import _do_transfer
5-
from bittensor.core.subtensor import Subtensor
62
from bittensor.utils.balance import Balance
73

84

9-
@pytest.fixture
10-
def subtensor(mocker):
11-
fake_substrate = mocker.MagicMock()
12-
fake_substrate.websocket.sock.getsockopt.return_value = 0
13-
mocker.patch.object(
14-
subtensor_module, "SubstrateInterface", return_value=fake_substrate
15-
)
16-
return Subtensor()
17-
18-
195
def test_do_transfer_is_success_true(subtensor, mocker):
206
"""Successful do_transfer call."""
217
# Prep

‎tests/unit_tests/test_async_subtensor.py

+19-5
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,8 @@ async def test_async_subtensor_magic_methods(mocker):
128128
pass
129129

130130
# Asserts
131-
fake_async_substrate.__aenter__.assert_called_once()
132-
fake_async_substrate.__aexit__.assert_called_once()
133-
fake_async_substrate.close.assert_awaited_once()
131+
fake_async_substrate.initialize.assert_called_once()
132+
fake_async_substrate.close.assert_called_once()
134133

135134

136135
@pytest.mark.parametrize(
@@ -145,7 +144,7 @@ async def test_async_subtensor_aenter_connection_refused_error(
145144
# Preps
146145
fake_async_substrate = mocker.AsyncMock(
147146
autospec=async_subtensor.AsyncSubstrateInterface,
148-
__aenter__=mocker.AsyncMock(side_effect=error),
147+
initialize=mocker.AsyncMock(side_effect=error),
149148
)
150149
mocker.patch.object(
151150
async_subtensor, "AsyncSubstrateInterface", return_value=fake_async_substrate
@@ -158,7 +157,7 @@ async def test_async_subtensor_aenter_connection_refused_error(
158157
pass
159158

160159
# Asserts
161-
fake_async_substrate.__aenter__.assert_called_once()
160+
fake_async_substrate.initialize.assert_awaited_once()
162161

163162

164163
@pytest.mark.asyncio
@@ -2676,3 +2675,18 @@ async def test_set_subnet_identity(mocker, subtensor):
26762675
wait_for_inclusion=False,
26772676
)
26782677
assert result == mocked_extrinsic.return_value
2678+
2679+
2680+
@pytest.mark.asyncio
2681+
async def test_get_all_neuron_certificates(mocker, subtensor):
2682+
fake_netuid = 12
2683+
mocked_query_map_subtensor = mocker.AsyncMock()
2684+
mocker.patch.object(subtensor.substrate, "query_map", mocked_query_map_subtensor)
2685+
await subtensor.get_all_neuron_certificates(fake_netuid)
2686+
mocked_query_map_subtensor.assert_awaited_once_with(
2687+
module="SubtensorModule",
2688+
storage_function="NeuronCertificates",
2689+
params=[fake_netuid],
2690+
block_hash=None,
2691+
reuse_block_hash=False,
2692+
)

‎tests/unit_tests/test_axon.py

+68-7
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
1+
import asyncio
2+
import contextlib
13
import re
4+
import threading
25
import time
36
from dataclasses import dataclass
47
from typing import Any, Optional, Tuple
58
from unittest import IsolatedAsyncioTestCase
69
from unittest.mock import AsyncMock, MagicMock, patch
710

11+
import aiohttp
812
import fastapi
913
import netaddr
1014
import pydantic
1115
import pytest
16+
import uvicorn
1217
from fastapi.testclient import TestClient
1318
from starlette.requests import Request
1419

15-
from bittensor.core.axon import AxonMiddleware, Axon
20+
from bittensor.core.axon import Axon, AxonMiddleware, FastAPIThreadedServer
1621
from bittensor.core.errors import RunException
1722
from bittensor.core.settings import version_as_int
1823
from bittensor.core.stream import StreamingSynapse
@@ -26,7 +31,7 @@
2631
)
2732

2833

29-
def test_attach_initial():
34+
def test_attach_initial(mock_get_external_ip):
3035
# Create a mock AxonServer instance
3136
server = Axon()
3237

@@ -71,7 +76,7 @@ def wrong_verify_fn(synapse: TestSynapse) -> bool:
7176
server.attach(forward_fn, blacklist_fn, priority_fn, wrong_verify_fn)
7277

7378

74-
def test_attach():
79+
def test_attach(mock_get_external_ip):
7580
# Create a mock AxonServer instance
7681
server = Axon()
7782

@@ -144,7 +149,7 @@ def mock_request():
144149

145150

146151
@pytest.fixture
147-
def axon_instance():
152+
def axon_instance(mock_get_external_ip):
148153
axon = Axon()
149154
axon.required_hash_fields = {"test_endpoint": ["field1", "field2"]}
150155
axon.forward_class_types = {
@@ -329,7 +334,7 @@ async def test_verify_body_integrity_error_cases(
329334
(MockInfo(), "MockInfoString", "edge_case_empty_string"),
330335
],
331336
)
332-
def test_to_string(info_return, expected_output, test_id):
337+
def test_to_string(info_return, expected_output, test_id, mock_get_external_ip):
333338
# Arrange
334339
axon = Axon()
335340
with patch.object(axon, "info", return_value=info_return):
@@ -358,7 +363,9 @@ def test_to_string(info_return, expected_output, test_id):
358363
),
359364
],
360365
)
361-
def test_valid_ipv4_and_ipv6_address(ip, port, expected_ip_type, test_id):
366+
def test_valid_ipv4_and_ipv6_address(
367+
ip, port, expected_ip_type, test_id, mock_get_external_ip
368+
):
362369
# Arrange
363370
hotkey = MockHotkey("5EemgxS7cmYbD34esCFoBgUZZC8JdnGtQvV5Qw3QFUCRRtGP")
364371
coldkey = MockHotkey("5EemgxS7cmYbD34esCFoBgUZZC8JdnGtQvV5Qw3QFUCRRtGP")
@@ -431,7 +438,14 @@ def test_invalid_ip_address(ip, port, expected_exception):
431438
],
432439
)
433440
def test_axon_str_representation(
434-
ip, port, ss58_address, started, forward_fns, expected_str, test_id
441+
ip,
442+
port,
443+
ss58_address,
444+
started,
445+
forward_fns,
446+
expected_str,
447+
test_id,
448+
mock_get_external_ip,
435449
):
436450
# Arrange
437451
hotkey = MockHotkey(ss58_address)
@@ -765,3 +779,50 @@ async def forward_fn(synapse: streaming_synapse_cls):
765779
"computed_body_hash": "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a",
766780
},
767781
)
782+
783+
784+
@pytest.mark.asyncio
785+
async def test_threaded_fastapi():
786+
server_started = threading.Event()
787+
server_stopped = threading.Event()
788+
789+
@contextlib.asynccontextmanager
790+
async def lifespan(app):
791+
server_started.set()
792+
yield
793+
server_stopped.set()
794+
795+
app = fastapi.FastAPI(
796+
lifespan=lifespan,
797+
)
798+
app.get("/")(lambda: "Hello World")
799+
800+
server = FastAPIThreadedServer(
801+
uvicorn.Config(app, loop="none"),
802+
)
803+
server.start()
804+
805+
server_started.wait(3.0)
806+
807+
async def wait_for_server():
808+
while not (server.started or server_stopped.is_set()):
809+
await asyncio.sleep(1.0)
810+
811+
await asyncio.wait_for(wait_for_server(), 7.0)
812+
813+
assert server.is_running is True
814+
815+
async with aiohttp.ClientSession(
816+
base_url="http://127.0.0.1:8000",
817+
) as session:
818+
async with session.get("/") as response:
819+
assert await response.text() == '"Hello World"'
820+
821+
server.stop()
822+
823+
assert server.should_exit is True
824+
825+
server_stopped.wait()
826+
827+
with pytest.raises(aiohttp.ClientConnectorError):
828+
await session.get("/")

‎tests/unit_tests/test_dendrite.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def dummy(synapse: SynapseDummy) -> SynapseDummy:
2929

3030

3131
@pytest.fixture
32-
def setup_dendrite():
32+
def setup_dendrite(mock_get_external_ip):
3333
# Assuming bittensor.Wallet() returns a wallet object
3434
user_wallet = get_mock_wallet()
3535
dendrite_obj = Dendrite(user_wallet)
@@ -51,7 +51,10 @@ def axon_info():
5151
@pytest.fixture(scope="session")
5252
def setup_axon():
5353
wallet = get_mock_wallet()
54-
axon = Axon(wallet)
54+
axon = Axon(
55+
wallet,
56+
external_ip="192.168.1.1",
57+
)
5558
axon.attach(forward_fn=dummy)
5659
axon.start()
5760
yield axon
@@ -103,15 +106,15 @@ def __await__(self):
103106
return self().__await__()
104107

105108

106-
def test_dendrite_create_wallet():
109+
def test_dendrite_create_wallet(mock_get_external_ip):
107110
d = Dendrite(get_mock_wallet())
108111
d = Dendrite(get_mock_wallet().hotkey)
109112
d = Dendrite(get_mock_wallet().coldkeypub)
110113
assert d.__str__() == d.__repr__()
111114

112115

113116
@pytest.mark.asyncio
114-
async def test_forward_many():
117+
async def test_forward_many(mock_get_external_ip):
115118
n = 10
116119
d = Dendrite(wallet=get_mock_wallet())
117120
d.call = AsyncMock()
@@ -128,7 +131,7 @@ async def test_forward_many():
128131
assert len([resp]) == 1
129132

130133

131-
def test_pre_process_synapse():
134+
def test_pre_process_synapse(mock_get_external_ip):
132135
d = Dendrite(wallet=get_mock_wallet())
133136
s = Synapse()
134137
synapse = d.preprocess_synapse_for_request(

‎tests/unit_tests/test_subtensor.py

+19-30
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def call_params_with_certificate():
7373
return params
7474

7575

76-
def test_methods_comparable(mocker):
76+
def test_methods_comparable(mock_substrate_interface):
7777
"""Verifies that methods in sync and async Subtensors are comparable."""
7878
# Preps
7979
subtensor = Subtensor(_mock=True)
@@ -142,9 +142,7 @@ def test_serve_axon_with_external_ip_set():
142142
assert axon_info.ip == external_ip
143143

144144

145-
def test_serve_axon_with_external_port_set():
146-
external_ip: str = "2001:0db8:85a3:0000:0000:8a2e:0370:7334"
147-
145+
def test_serve_axon_with_external_port_set(mock_get_external_ip):
148146
internal_port: int = 1234
149147
external_port: int = 5678
150148

@@ -179,14 +177,10 @@ def test_serve_axon_with_external_port_set():
179177
config=mock_config,
180178
)
181179

182-
with mock.patch(
183-
"bittensor.utils.networking.get_external_ip", return_value=external_ip
184-
):
185-
# mock the get_external_ip function to return the external ip
186-
mock_subtensor.serve_axon(
187-
netuid=-1,
188-
axon=mock_axon_with_external_port_set,
189-
)
180+
mock_subtensor.serve_axon(
181+
netuid=-1,
182+
axon=mock_axon_with_external_port_set,
183+
)
190184

191185
mock_serve_axon.assert_called_once()
192186
# verify that the axon is served to the network with the external port
@@ -294,24 +288,6 @@ def test_determine_chain_endpoint_and_network(
294288
assert result_endpoint == expected_endpoint
295289

296290

297-
@pytest.fixture
298-
def subtensor(mocker):
299-
fake_substrate = mocker.MagicMock()
300-
fake_substrate.websocket.sock.getsockopt.return_value = 0
301-
mocker.patch.object(
302-
subtensor_module, "SubstrateInterface", return_value=fake_substrate
303-
)
304-
mocker.patch.object(
305-
sync_substrate,
306-
"connect",
307-
return_value=mocker.MagicMock(),
308-
)
309-
fake_websocket = mocker.MagicMock()
310-
fake_websocket.client.connect.return_value = 0
311-
# mocker.patch.object(subtensor_module, "ws_client", return_value=fake_websocket)
312-
return Subtensor()
313-
314-
315291
@pytest.fixture
316292
def mock_logger():
317293
with mock.patch.object(logging, "warning") as mock_warning:
@@ -3084,3 +3060,16 @@ def test_set_subnet_identity(mocker, subtensor):
30843060
wait_for_inclusion=False,
30853061
)
30863062
assert result == mocked_extrinsic.return_value
3063+
3064+
3065+
def test_get_all_neuron_certificates(mocker, subtensor):
3066+
fake_netuid = 12
3067+
mocked_query_map_subtensor = mocker.MagicMock()
3068+
mocker.patch.object(subtensor.substrate, "query_map", mocked_query_map_subtensor)
3069+
subtensor.get_all_neuron_certificates(fake_netuid)
3070+
mocked_query_map_subtensor.assert_called_once_with(
3071+
module="SubtensorModule",
3072+
storage_function="NeuronCertificates",
3073+
params=[fake_netuid],
3074+
block_hash=None,
3075+
)

‎tests/unit_tests/utils/test_networking.py

+35-4
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,42 @@ def test_int_to_ip6_underflow():
8383

8484

8585
# Test getting external IP address
86-
def test_get_external_ip():
86+
def test_get_external_ip(mocker):
8787
"""Test getting the external IP address."""
88-
assert utils.networking.get_external_ip()
88+
mocked_requests_get = mock.Mock(
89+
return_value=mock.Mock(
90+
**{
91+
"text": "192.168.1.1",
92+
},
93+
),
94+
)
95+
96+
mocker.patch.object(
97+
requests,
98+
"get",
99+
mocked_requests_get,
100+
)
101+
102+
assert utils.networking.get_external_ip() == "192.168.1.1"
103+
104+
mocked_requests_get.assert_called_once_with("https://checkip.amazonaws.com")
89105

90106

91-
def test_get_external_ip_os_broken():
107+
def test_get_external_ip_os_broken(mocker):
92108
"""Test getting the external IP address when os.popen is broken."""
109+
mocked_requests_get = mock.Mock(
110+
return_value=mock.Mock(
111+
**{
112+
"text": "192.168.1.1",
113+
},
114+
),
115+
)
116+
117+
mocker.patch.object(
118+
requests,
119+
"get",
120+
mocked_requests_get,
121+
)
93122

94123
class FakeReadline:
95124
def readline(self):
@@ -99,7 +128,9 @@ def mock_call():
99128
return FakeReadline()
100129

101130
with mock.patch.object(os, "popen", new=mock_call):
102-
assert utils.networking.get_external_ip()
131+
assert utils.networking.get_external_ip() == "192.168.1.1"
132+
133+
mocked_requests_get.assert_called_once_with("https://checkip.amazonaws.com")
103134

104135

105136
def test_get_external_ip_os_request_urllib_broken():

0 commit comments

Comments
 (0)
Please sign in to comment.