From 559ec182bbda95f37de2ecd693aae6f430b6f437 Mon Sep 17 00:00:00 2001 From: ifrit98 Date: Mon, 5 Feb 2024 22:08:45 +0000 Subject: [PATCH 01/16] return messages with subtensor extrinsic to set weights --- bittensor/extrinsics/set_weights.py | 32 ++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/bittensor/extrinsics/set_weights.py b/bittensor/extrinsics/set_weights.py index b93942a677..32f61c8146 100644 --- a/bittensor/extrinsics/set_weights.py +++ b/bittensor/extrinsics/set_weights.py @@ -20,7 +20,7 @@ import torch from rich.prompt import Confirm -from typing import Union +from typing import Union, Tuple import bittensor.utils.weight_utils as weight_utils import multiprocessing @@ -42,7 +42,15 @@ def ttl_set_weights_extrinsic( ttl: int = 100, ): r"""Sets the given weights and values on chain for wallet hotkey account.""" + + def target(queue, *args): + result = set_weights_extrinsic(*args) + queue.put(result) + + queue = multiprocessing.Queue() + args = ( + queue, subtensor.chain_endpoint, wallet, netuid, @@ -53,14 +61,18 @@ def ttl_set_weights_extrinsic( wait_for_finalization, prompt, ) - process = multiprocessing.Process(target=set_weights_extrinsic, args=args) + process = multiprocessing.Process(target=target, args=args) process.start() process.join(timeout=ttl) + success, error_message = False, "Timeout or unknown error" + if process.is_alive(): process.terminate() process.join() - return False - return True + else: + success, error_message = queue.get() + + return success, error_message def set_weights_extrinsic( @@ -73,7 +85,7 @@ def set_weights_extrinsic( wait_for_inclusion: bool = False, wait_for_finalization: bool = False, prompt: bool = False, -) -> bool: +) -> Tuple[bool, str]: r"""Sets the given weights and values on chain for wallet hotkey account. Args: @@ -119,7 +131,7 @@ def set_weights_extrinsic( [float(v / 65535) for v in weight_vals], weight_uids ) ): - return False + return False, "Prompt refused." with bittensor.__console__.status( ":satellite: Setting weights on [white]{}[/white] ...".format(subtensor.network) @@ -136,7 +148,7 @@ def set_weights_extrinsic( ) if not wait_for_finalization and not wait_for_inclusion: - return True + return True, "Not waiting for finalization or inclusion. Assume successful." if success == True: bittensor.__console__.print( @@ -146,7 +158,7 @@ def set_weights_extrinsic( prefix="Set weights", sufix="Finalized: " + str(success), ) - return True + return True, "Success." else: bittensor.__console__.print( ":cross_mark: [red]Failed[/red]: error:{}".format(error_message) @@ -155,7 +167,7 @@ def set_weights_extrinsic( prefix="Set weights", sufix="Failed: " + str(error_message), ) - return False + return False, str(error_message) except Exception as e: # TODO( devs ): lets remove all of the bittensor.__console__ calls and replace with loguru. @@ -165,4 +177,4 @@ def set_weights_extrinsic( bittensor.logging.warning( prefix="Set weights", sufix="Failed: " + str(e) ) - return False + return False, str(e) From d202bdfad4e9f14a22fe8bf26c87a733755688a8 Mon Sep 17 00:00:00 2001 From: ifrit98 Date: Mon, 5 Feb 2024 22:12:55 +0000 Subject: [PATCH 02/16] black format --- bittensor/extrinsics/set_weights.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bittensor/extrinsics/set_weights.py b/bittensor/extrinsics/set_weights.py index 32f61c8146..54b1c6d435 100644 --- a/bittensor/extrinsics/set_weights.py +++ b/bittensor/extrinsics/set_weights.py @@ -148,7 +148,10 @@ def set_weights_extrinsic( ) if not wait_for_finalization and not wait_for_inclusion: - return True, "Not waiting for finalization or inclusion. Assume successful." + return ( + True, + "Not waiting for finalization or inclusion. Assume successful.", + ) if success == True: bittensor.__console__.print( From 00ffc375fc943fe0eac6542acfd13342e440b967 Mon Sep 17 00:00:00 2001 From: ifrit98 Date: Mon, 5 Feb 2024 22:18:57 +0000 Subject: [PATCH 03/16] proper type hints --- bittensor/extrinsics/set_weights.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/extrinsics/set_weights.py b/bittensor/extrinsics/set_weights.py index 54b1c6d435..fbaf4de351 100644 --- a/bittensor/extrinsics/set_weights.py +++ b/bittensor/extrinsics/set_weights.py @@ -40,7 +40,7 @@ def ttl_set_weights_extrinsic( wait_for_finalization: bool = False, prompt: bool = False, ttl: int = 100, -): +) -> Tuple[bool, str]: r"""Sets the given weights and values on chain for wallet hotkey account.""" def target(queue, *args): From 92ada18ddb4cdf27ab20e59144bc48e8ed44d0da Mon Sep 17 00:00:00 2001 From: ifrit98 Date: Wed, 7 Feb 2024 23:48:37 +0000 Subject: [PATCH 04/16] trace logging for axon events --- bittensor/axon.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bittensor/axon.py b/bittensor/axon.py index 46d331d653..df496d3e01 100644 --- a/bittensor/axon.py +++ b/bittensor/axon.py @@ -1029,7 +1029,7 @@ async def dispatch( synapse: bittensor.Synapse = await self.preprocess(request) # Logs the start of the request processing - bittensor.logging.debug( + bittensor.logging.trace( f"axon | <-- | {request.headers.get('content-length', -1)} B | {synapse.name} | {synapse.dendrite.hotkey} | {synapse.dendrite.ip}:{synapse.dendrite.port} | 200 | Success " ) @@ -1101,7 +1101,7 @@ async def dispatch( finally: # Log the details of the processed synapse, including total size, name, hotkey, IP, port, # status code, and status message, using the debug level of the logger. - bittensor.logging.debug( + bittensor.logging.trace( f"axon | --> | {response.headers.get('content-length', -1)} B | {synapse.name} | {synapse.dendrite.hotkey} | {synapse.dendrite.ip}:{synapse.dendrite.port} | {synapse.axon.status_code} | {synapse.axon.status_message}" ) From 789a98c0994df0e3291dc80e801575a25596803d Mon Sep 17 00:00:00 2001 From: ifrit98 Date: Wed, 7 Feb 2024 23:49:26 +0000 Subject: [PATCH 05/16] dendrite trace log events --- bittensor/dendrite.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bittensor/dendrite.py b/bittensor/dendrite.py index b96e819ce9..cf5f2e0559 100644 --- a/bittensor/dendrite.py +++ b/bittensor/dendrite.py @@ -270,7 +270,7 @@ def _log_outgoing_request(self, synapse): Args: synapse: The synapse object representing the request being sent. """ - bittensor.logging.debug( + bittensor.logging.trace( f"dendrite | --> | {synapse.get_total_size()} B | {synapse.name} | {synapse.axon.hotkey} | {synapse.axon.ip}:{str(synapse.axon.port)} | 0 | Success" ) @@ -285,7 +285,7 @@ def _log_incoming_response(self, synapse): Args: synapse: The synapse object representing the received response. """ - bittensor.logging.debug( + bittensor.logging.trace( f"dendrite | <-- | {synapse.get_total_size()} B | {synapse.name} | {synapse.axon.hotkey} | {synapse.axon.ip}:{str(synapse.axon.port)} | {synapse.dendrite.status_code} | {synapse.dendrite.status_message}" ) From e2faf7f50a8b54c4c98c8e393cc3721d29cb30e0 Mon Sep 17 00:00:00 2001 From: ifrit98 Date: Thu, 8 Feb 2024 00:00:06 +0000 Subject: [PATCH 06/16] release branch setup --- VERSION | 2 +- bittensor/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 1d42024266..9f0cbcf8bf 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.7.1 \ No newline at end of file +6.7.2 \ No newline at end of file diff --git a/bittensor/__init__.py b/bittensor/__init__.py index 71d0dec8dc..2d6d07b98d 100644 --- a/bittensor/__init__.py +++ b/bittensor/__init__.py @@ -27,7 +27,7 @@ nest_asyncio.apply() # Bittensor code and protocol version. -__version__ = "6.7.1" +__version__ = "6.7.2" version_split = __version__.split(".") __version_as_int__ = ( From d144975863457c8182761e68918629a9831e1343 Mon Sep 17 00:00:00 2001 From: ifrit98 Date: Thu, 8 Feb 2024 00:00:49 +0000 Subject: [PATCH 07/16] changelog update --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8d6992139..1ebc3ec2f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## 6.7.2 / 2024-02-08 + +## What's Changed +* Release/6.7.1 by @ifrit98 in https://github.com/opentensor/bittensor/pull/1688 +* Increases test coverage for cli & chain_data by @gus-opentensor in https://github.com/opentensor/bittensor/pull/1690 +* Subtensor/update pysubstrate latest/phil by @ifrit98 in https://github.com/opentensor/bittensor/pull/1684 +* Update staging to latest master by @ifrit98 in https://github.com/opentensor/bittensor/pull/1691 +* return messages with subtensor extrinsic to set weights by @ifrit98 in https://github.com/opentensor/bittensor/pull/1692 +* Logging/debug to trace axon by @ifrit98 in https://github.com/opentensor/bittensor/pull/1694 + + +**Full Changelog**: https://github.com/opentensor/bittensor/compare/v6.7.1...v6.7.2 + + ## 6.7.1 / 2024-02-02 ## What's Changed From 7b98420d63dc6345ddc0a52b597244dcadd40092 Mon Sep 17 00:00:00 2001 From: Gus Date: Wed, 7 Feb 2024 19:22:15 -0500 Subject: [PATCH 08/16] fix: Adds exception class MaxAttemptedException --- bittensor/extrinsics/registration.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bittensor/extrinsics/registration.py b/bittensor/extrinsics/registration.py index 75cc464a13..072bece523 100644 --- a/bittensor/extrinsics/registration.py +++ b/bittensor/extrinsics/registration.py @@ -325,6 +325,10 @@ class MaxAttemptsException(Exception): pass +class MaxAttemptedException(Exception): + pass + + def run_faucet_extrinsic( subtensor: "bittensor.subtensor", wallet: "bittensor.wallet", From 9c8ea52767a1ca5786db4477ec8b0f9168de46c9 Mon Sep 17 00:00:00 2001 From: Gus Date: Wed, 7 Feb 2024 19:26:53 -0500 Subject: [PATCH 09/16] naming --- bittensor/extrinsics/registration.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/bittensor/extrinsics/registration.py b/bittensor/extrinsics/registration.py index 072bece523..d5dcb06e2c 100644 --- a/bittensor/extrinsics/registration.py +++ b/bittensor/extrinsics/registration.py @@ -325,10 +325,6 @@ class MaxAttemptsException(Exception): pass -class MaxAttemptedException(Exception): - pass - - def run_faucet_extrinsic( subtensor: "bittensor.subtensor", wallet: "bittensor.wallet", @@ -470,7 +466,7 @@ def run_faucet_extrinsic( except MaxSuccessException: return True, f"Max successes reached: {3}" - except MaxAttemptedException: + except MaxAttemptsException: return False, f"Max attempts reached: {max_allowed_attempts}" From fedce65c12416d08201ecb30d28b782d477bb25d Mon Sep 17 00:00:00 2001 From: ifrit98 Date: Thu, 8 Feb 2024 06:19:41 +0000 Subject: [PATCH 10/16] typo fix bittenst --- bittensor/dendrite.py | 2 +- bittensor/subnets_api.py | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 bittensor/subnets_api.py diff --git a/bittensor/dendrite.py b/bittensor/dendrite.py index cf5f2e0559..75488401f0 100644 --- a/bittensor/dendrite.py +++ b/bittensor/dendrite.py @@ -335,7 +335,7 @@ async def forward( deserialize: bool = True, run_async: bool = True, streaming: bool = False, - ) -> List[Union[AsyncGenerator[Any], bittenst.Synapse, bittensor.StreamingSynapse]]: + ) -> List[Union[AsyncGenerator[Any], bittensor.Synapse, bittensor.StreamingSynapse]]: """ Asynchronously sends requests to one or multiple Axons and collates their responses. diff --git a/bittensor/subnets_api.py b/bittensor/subnets_api.py new file mode 100644 index 0000000000..debf14bed2 --- /dev/null +++ b/bittensor/subnets_api.py @@ -0,0 +1,21 @@ +from abc import ABC, abstractmethod +from typing import Any, List, Union + +class SynapseHandler(ABC): + def __init__(self, wallet: "bt.wallet"): + self.wallet = wallet + self.dendrite = bittensor.dendrite(wallet=wallet) + + @abstractmethod + def prepare_synapse(self, *args, **kwargs) -> Any: + """ + Prepare the synapse-specific payload. + """ + ... + + @abstractmethod + def process_responses(self, responses: List[Union["bt.Synapse", Any]]) -> Any: + """ + Process the responses from the network. + """ + ... \ No newline at end of file From f830267f79fdf26d15e7426485e9b6f13651c0e6 Mon Sep 17 00:00:00 2001 From: ifrit98 Date: Thu, 8 Feb 2024 06:20:36 +0000 Subject: [PATCH 11/16] oops commit --- bittensor/subnets_api.py | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 bittensor/subnets_api.py diff --git a/bittensor/subnets_api.py b/bittensor/subnets_api.py deleted file mode 100644 index debf14bed2..0000000000 --- a/bittensor/subnets_api.py +++ /dev/null @@ -1,21 +0,0 @@ -from abc import ABC, abstractmethod -from typing import Any, List, Union - -class SynapseHandler(ABC): - def __init__(self, wallet: "bt.wallet"): - self.wallet = wallet - self.dendrite = bittensor.dendrite(wallet=wallet) - - @abstractmethod - def prepare_synapse(self, *args, **kwargs) -> Any: - """ - Prepare the synapse-specific payload. - """ - ... - - @abstractmethod - def process_responses(self, responses: List[Union["bt.Synapse", Any]]) -> Any: - """ - Process the responses from the network. - """ - ... \ No newline at end of file From cc053a082f8036e322139c8598451786e7fdf9a0 Mon Sep 17 00:00:00 2001 From: ifrit98 Date: Thu, 8 Feb 2024 06:22:33 +0000 Subject: [PATCH 12/16] black --- bittensor/dendrite.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bittensor/dendrite.py b/bittensor/dendrite.py index 75488401f0..dcbea23895 100644 --- a/bittensor/dendrite.py +++ b/bittensor/dendrite.py @@ -335,7 +335,9 @@ async def forward( deserialize: bool = True, run_async: bool = True, streaming: bool = False, - ) -> List[Union[AsyncGenerator[Any], bittensor.Synapse, bittensor.StreamingSynapse]]: + ) -> List[ + Union[AsyncGenerator[Any], bittensor.Synapse, bittensor.StreamingSynapse] + ]: """ Asynchronously sends requests to one or multiple Axons and collates their responses. From f78ef920571de5dda882b641f01988622a5b1453 Mon Sep 17 00:00:00 2001 From: ifrit98 Date: Thu, 8 Feb 2024 06:31:02 +0000 Subject: [PATCH 13/16] update aiohttp for python 311 --- requirements/prod.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/prod.txt b/requirements/prod.txt index dec7766b29..b4c6731d59 100644 --- a/requirements/prod.txt +++ b/requirements/prod.txt @@ -1,4 +1,4 @@ -aiohttp==3.9.0 +aiohttp==3.9.0b0 ansible==6.7.0 ansible_vault==2.1.0 backoff From 4bca694c9ed4687e7057675cf46ac81343fe5b3e Mon Sep 17 00:00:00 2001 From: Gus Date: Wed, 7 Feb 2024 19:19:53 -0500 Subject: [PATCH 14/16] coverage extended to extrinsics & increases global coverage 65% -> 66% (higher once skipped tests enabled) adds extrinsics/test_senate.py --- bittensor/extrinsics/registration.py | 4 + tests/unit_tests/extrinsics/test_network.py | 92 ++++++++ .../unit_tests/extrinsics/test_prometheus.py | 154 ++++++++++++ .../extrinsics/test_registration.py | 170 +++++++++++++ tests/unit_tests/extrinsics/test_senate.py | 81 +++++++ tests/unit_tests/extrinsics/test_serving.py | 223 ++++++++++++++++++ tests/unit_tests/utils/test_networking.py | 2 +- 7 files changed, 725 insertions(+), 1 deletion(-) create mode 100644 tests/unit_tests/extrinsics/test_network.py create mode 100644 tests/unit_tests/extrinsics/test_prometheus.py create mode 100644 tests/unit_tests/extrinsics/test_registration.py create mode 100644 tests/unit_tests/extrinsics/test_senate.py create mode 100644 tests/unit_tests/extrinsics/test_serving.py diff --git a/bittensor/extrinsics/registration.py b/bittensor/extrinsics/registration.py index d5dcb06e2c..104bb273c6 100644 --- a/bittensor/extrinsics/registration.py +++ b/bittensor/extrinsics/registration.py @@ -325,6 +325,10 @@ class MaxAttemptsException(Exception): pass +class MaxAttemptedException(Exception): + pass + + def run_faucet_extrinsic( subtensor: "bittensor.subtensor", wallet: "bittensor.wallet", diff --git a/tests/unit_tests/extrinsics/test_network.py b/tests/unit_tests/extrinsics/test_network.py new file mode 100644 index 0000000000..160348c3e6 --- /dev/null +++ b/tests/unit_tests/extrinsics/test_network.py @@ -0,0 +1,92 @@ +import pytest +from unittest.mock import MagicMock, patch +from bittensor.subtensor import subtensor as Subtensor +from bittensor.wallet import wallet as Wallet + + +# Mock the bittensor and related modules to avoid real network calls and wallet operations +@pytest.fixture +def mock_subtensor(): + subtensor = MagicMock(spec=Subtensor) + subtensor.get_balance.return_value = 100 + subtensor.get_subnet_burn_cost.return_value = 10 + return subtensor + + +@pytest.fixture +def mock_wallet(): + wallet = MagicMock(spec=Wallet) + wallet.coldkeypub.ss58_address = "fake_address" + wallet.coldkey = MagicMock() + return wallet + + +@pytest.mark.parametrize( + "test_id, wait_for_inclusion, wait_for_finalization, prompt, expected", + [ + ("happy-path-01", False, True, False, True), + ("happy-path-02", True, False, False, True), + ("happy-path-03", False, False, False, True), + ("happy-path-04", True, True, False, True), + ], +) +def test_register_subnetwork_extrinsic_happy_path( + mock_subtensor, + mock_wallet, + test_id, + wait_for_inclusion, + wait_for_finalization, + prompt, + expected, +): + # Arrange + mock_subtensor.substrate = MagicMock( + get_block_hash=MagicMock(return_value="0x" + "0" * 64), + submit_extrinsic=MagicMock(), + ) + mock_subtensor.substrate.submit_extrinsic.return_value.is_success = True + + # Act + from bittensor.extrinsics.network import register_subnetwork_extrinsic + + result = register_subnetwork_extrinsic( + mock_subtensor, mock_wallet, wait_for_inclusion, wait_for_finalization, prompt + ) + + # Assert + assert result == expected + + +# Edge cases +@pytest.mark.parametrize( + "test_id, balance, burn_cost, prompt_input, expected", + [ + ("edge-case-01", 0, 10, False, False), # Balance is zero + ("edge-case-02", 10, 10, False, False), # Balance equals burn cost + ("edge-case-03", 9, 10, False, False), # Balance less than burn cost + ("edge-case-04", 100, 10, True, True), # User declines prompt + ], +) +def test_register_subnetwork_extrinsic_edge_cases( + mock_subtensor, + mock_wallet, + test_id, + balance, + burn_cost, + prompt_input, + expected, + monkeypatch, +): + # Arrange + mock_subtensor.get_balance.return_value = balance + mock_subtensor.get_subnet_burn_cost.return_value = burn_cost + monkeypatch.setattr("rich.prompt.Confirm.ask", lambda x: prompt_input) + mock_subtensor.substrate = MagicMock() + + # Act + from bittensor.extrinsics.network import register_subnetwork_extrinsic + + result = register_subnetwork_extrinsic(mock_subtensor, mock_wallet, prompt=True) + + # Assert + assert result == expected diff --git a/tests/unit_tests/extrinsics/test_prometheus.py b/tests/unit_tests/extrinsics/test_prometheus.py new file mode 100644 index 0000000000..0458206701 --- /dev/null +++ b/tests/unit_tests/extrinsics/test_prometheus.py @@ -0,0 +1,154 @@ +import pytest +from unittest.mock import MagicMock, patch +import bittensor +from bittensor.subtensor import subtensor as Subtensor +from bittensor.wallet import wallet as Wallet +from bittensor.extrinsics.prometheus import prometheus_extrinsic + + +# Mocking the bittensor and networking modules +@pytest.fixture +def mock_bittensor(): + with patch("bittensor.subtensor") as mock: + yield mock + + +@pytest.fixture +def mock_wallet(): + with patch("bittensor.wallet") as mock: + yield mock + + +@pytest.fixture +def mock_net(): + with patch("bittensor.utils.networking") as mock: + yield mock + + +@pytest.mark.parametrize( + "ip, port, netuid, wait_for_inclusion, wait_for_finalization, expected_result, test_id", + [ + (None, 9221, 0, False, True, True, "happy-path-default-ip"), + ("192.168.0.1", 9221, 0, False, True, True, "happy-path-custom-ip"), + (None, 9221, 0, True, False, True, "happy-path-wait-for-inclusion"), + (None, 9221, 0, False, False, True, "happy-path-no-waiting"), + ], +) +def test_prometheus_extrinsic_happy_path( + mock_bittensor, + mock_wallet, + mock_net, + ip, + port, + netuid, + wait_for_inclusion, + wait_for_finalization, + expected_result, + test_id, +): + # Arrange + subtensor = MagicMock(spec=Subtensor) + subtensor.network = "test_network" + wallet = MagicMock(spec=Wallet) + mock_net.get_external_ip.return_value = "192.168.0.1" + mock_net.ip_to_int.return_value = 3232235521 # IP in integer form + mock_net.ip_version.return_value = 4 + neuron = MagicMock() + neuron.is_null = False + neuron.prometheus_info.version = bittensor.__version_as_int__ + neuron.prometheus_info.ip = 3232235521 + neuron.prometheus_info.port = port + neuron.prometheus_info.ip_type = 4 + subtensor.get_neuron_for_pubkey_and_subnet.return_value = neuron + subtensor._do_serve_prometheus.return_value = (True, None) + + # Act + result = prometheus_extrinsic( + subtensor=subtensor, + wallet=wallet, + ip=ip, + port=port, + netuid=netuid, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + + # Assert + assert result == expected_result, f"Test ID: {test_id}" + + +# Edge cases +@pytest.mark.parametrize( + "ip, port, netuid, test_id", + [ + ("0.0.0.0", 0, 0, "edge-case-min-values"), + ("255.255.255.255", 65535, 2147483647, "edge-case-max-values"), + ], +) +def test_prometheus_extrinsic_edge_cases( + mock_bittensor, mock_wallet, mock_net, ip, port, netuid, test_id +): + # Arrange + subtensor = MagicMock(spec=Subtensor) + subtensor.network = "test_network" + wallet = MagicMock(spec=Wallet) + mock_net.get_external_ip.return_value = ip + mock_net.ip_to_int.return_value = 3232235521 # IP in integer form + mock_net.ip_version.return_value = 4 + neuron = MagicMock() + neuron.is_null = True + subtensor.get_neuron_for_pubkey_and_subnet.return_value = neuron + subtensor._do_serve_prometheus.return_value = (True, None) + + # Act + result = prometheus_extrinsic( + subtensor=subtensor, + wallet=wallet, + ip=ip, + port=port, + netuid=netuid, + wait_for_inclusion=False, + wait_for_finalization=True, + ) + + # Assert + assert result == True, f"Test ID: {test_id}" + + +# Error cases +@pytest.mark.parametrize( + "ip, port, netuid, exception, test_id", + [ + ( + None, + 9221, + 0, + RuntimeError("Unable to attain your external ip."), + "error-case-no-external-ip", + ), + ], +) +def test_prometheus_extrinsic_error_cases( + mock_bittensor, mock_wallet, mock_net, ip, port, netuid, exception, test_id +): + # Arrange + subtensor = MagicMock(spec=Subtensor) + subtensor.network = "test_network" + wallet = MagicMock(spec=Wallet) + mock_net.get_external_ip.side_effect = exception + neuron = MagicMock() + neuron.is_null = True + subtensor.get_neuron_for_pubkey_and_subnet.return_value = neuron + subtensor._do_serve_prometheus.return_value = (True,) + + # Act & Assert + with pytest.raises(ValueError): + prometheus_extrinsic( + subtensor=subtensor, + wallet=wallet, + ip=ip, + port=port, + netuid=netuid, + wait_for_inclusion=False, + wait_for_finalization=True, + ) diff --git a/tests/unit_tests/extrinsics/test_registration.py b/tests/unit_tests/extrinsics/test_registration.py new file mode 100644 index 0000000000..4e6b890ddf --- /dev/null +++ b/tests/unit_tests/extrinsics/test_registration.py @@ -0,0 +1,170 @@ +import pytest +from unittest.mock import MagicMock, patch +from bittensor.subtensor import subtensor as Subtensor +from bittensor.wallet import wallet as Wallet +from bittensor.utils.registration import POWSolution +from bittensor.extrinsics.registration import ( + MaxSuccessException, + MaxAttemptsException, +) + + +# Mocking external dependencies +@pytest.fixture +def mock_subtensor(): + mock = MagicMock(spec=Subtensor) + mock.network = "mock_network" + mock.substrate = MagicMock() + return mock + + +@pytest.fixture +def mock_wallet(): + mock = MagicMock(spec=Wallet) + mock.coldkeypub.ss58_address = "mock_address" + return mock + + +@pytest.fixture +def mock_pow_solution(): + mock = MagicMock(spec=POWSolution) + mock.block_number = 123 + mock.nonce = 456 + mock.seal = [0, 1, 2, 3] + mock.is_stale.return_value = False + return mock + + +@pytest.mark.parametrize( + "wait_for_inclusion,wait_for_finalization,prompt,cuda,dev_id,tpb,num_processes,update_interval,log_verbose,expected", + [ + ( + False, + True, + False, + False, + 0, + 256, + None, + None, + False, + True, + ), + (True, False, False, True, [0], 256, 1, 100, True, False), + (False, False, False, True, 1, 512, 2, 200, False, False), + ], + ids=["happy-path-1", "happy-path-2", "happy-path-3"], +) +@pytest.mark.skip(reason="Waiting for fix to MaxAttemptedException") +def test_run_faucet_extrinsic_happy_path( + mock_subtensor, + mock_wallet, + mock_pow_solution, + wait_for_inclusion, + wait_for_finalization, + prompt, + cuda, + dev_id, + tpb, + num_processes, + update_interval, + log_verbose, + expected, +): + with patch( + "bittensor.utils.registration.create_pow", return_value=mock_pow_solution + ) as mock_create_pow, patch("rich.prompt.Confirm.ask", return_value=True): + from bittensor.extrinsics.registration import run_faucet_extrinsic + + # Arrange + mock_subtensor.get_balance.return_value = 100 + mock_subtensor.substrate.submit_extrinsic.return_value.is_success = True + + # Act + result = run_faucet_extrinsic( + subtensor=mock_subtensor, + wallet=mock_wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + prompt=prompt, + cuda=cuda, + dev_id=dev_id, + tpb=tpb, + num_processes=num_processes, + update_interval=update_interval, + log_verbose=log_verbose, + ) + + # Assert + assert result == expected + mock_subtensor.get_balance.assert_called_with("mock_address") + mock_subtensor.substrate.submit_extrinsic.assert_called() + + +@pytest.mark.parametrize( + "cuda,torch_cuda_available,prompt_response,expected", + [ + ( + True, + False, + False, + False, + ), # ID: edge-case-1: CUDA required but not available, user declines prompt + ( + True, + False, + True, + False, + ), # ID: edge-case-2: CUDA required but not available, user accepts prompt but fails due to CUDA unavailability + ], + ids=["edge-case-1", "edge-case-2"], +) +def test_run_faucet_extrinsic_edge_cases( + mock_subtensor, mock_wallet, cuda, torch_cuda_available, prompt_response, expected +): + with patch("torch.cuda.is_available", return_value=torch_cuda_available), patch( + "rich.prompt.Confirm.ask", return_value=prompt_response + ): + from bittensor.extrinsics.registration import run_faucet_extrinsic + + # Act + result = run_faucet_extrinsic( + subtensor=mock_subtensor, wallet=mock_wallet, cuda=cuda + ) + + # Assert + assert result == expected + + +@pytest.mark.parametrize( + "exception,expected", + [ + (KeyboardInterrupt, (True, "Done")), # ID: error-1: User interrupts the process + ( + MaxSuccessException, + (True, "Max successes reached: 3"), + ), # ID: error-2: Maximum successes reached + ( + MaxAttemptsException, + (False, "Max attempts reached: 3"), + ), # ID: error-3: Maximum attempts reached + ], + ids=["error-1", "error-2", "error-3"], +) +@pytest.mark.skip(reason="Waiting for fix to MaxAttemptedException") +def test_run_faucet_extrinsic_error_cases( + mock_subtensor, mock_wallet, mock_pow_solution, exception, expected +): + with patch( + "bittensor.utils.registration.create_pow", + side_effect=[mock_pow_solution, exception], + ): + from bittensor.extrinsics.registration import run_faucet_extrinsic + + # Act + result = run_faucet_extrinsic( + subtensor=mock_subtensor, wallet=mock_wallet, max_allowed_attempts=3 + ) + + # Assert + assert result == expected diff --git a/tests/unit_tests/extrinsics/test_senate.py b/tests/unit_tests/extrinsics/test_senate.py new file mode 100644 index 0000000000..a6a7ae066c --- /dev/null +++ b/tests/unit_tests/extrinsics/test_senate.py @@ -0,0 +1,81 @@ +import pytest +from unittest.mock import MagicMock, patch +from bittensor import subtensor, wallet +from bittensor.extrinsics.senate import register_senate_extrinsic + + +# Mocking external dependencies +@pytest.fixture +def mock_subtensor(): + mock = MagicMock(spec=subtensor) + mock.substrate = MagicMock() + return mock + + +@pytest.fixture +def mock_wallet(): + mock = MagicMock(spec=wallet) + mock.coldkey = MagicMock() + mock.hotkey = MagicMock() + mock.hotkey.ss58_address = "fake_hotkey_address" + mock.is_senate_member = None + return mock + + +# Parametrized test cases +@pytest.mark.parametrize( + "wait_for_inclusion,wait_for_finalization,prompt,response_success,is_registered,expected_result, test_id", + [ + # Happy path tests + (False, True, False, True, True, True, "happy-path-finalization-true"), + (True, False, False, True, True, True, "happy-path-inclusion-true"), + (False, False, False, True, True, True, "happy-path-no_wait"), + # Edge cases + (True, True, False, True, True, True, "edge-both-waits-true"), + # Error cases + (False, True, False, False, False, None, "error-finalization-failed"), + (True, False, False, False, False, None, "error-inclusion-failed"), + (False, True, True, True, False, False, "error-prompt-declined"), + ], +) +def test_register_senate_extrinsic( + mock_subtensor, + mock_wallet, + wait_for_inclusion, + wait_for_finalization, + prompt, + response_success, + is_registered, + expected_result, + test_id, +): + # Arrange + with patch( + "bittensor.extrinsics.senate.Confirm.ask", return_value=not prompt + ), patch("bittensor.extrinsics.senate.time.sleep"), patch.object( + mock_subtensor.substrate, "compose_call" + ), patch.object( + mock_subtensor.substrate, "create_signed_extrinsic" + ), patch.object( + mock_subtensor.substrate, + "submit_extrinsic", + return_value=MagicMock( + is_success=response_success, + process_events=MagicMock(), + error_message="error", + ), + ) as mock_submit_extrinsic, patch.object( + mock_wallet, "is_senate_member", return_value=is_registered + ): + + # Act + result = register_senate_extrinsic( + subtensor=mock_subtensor, + wallet=mock_wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + prompt=prompt, + ) + + # Assert + assert result == expected_result, f"Test ID: {test_id}" diff --git a/tests/unit_tests/extrinsics/test_serving.py b/tests/unit_tests/extrinsics/test_serving.py new file mode 100644 index 0000000000..4d95b80d73 --- /dev/null +++ b/tests/unit_tests/extrinsics/test_serving.py @@ -0,0 +1,223 @@ +import pytest +from unittest.mock import MagicMock, patch +from bittensor.subtensor import subtensor as Subtensor +from bittensor.wallet import wallet as Wallet +from bittensor.extrinsics.serving import serve_extrinsic + + +@pytest.fixture +def mock_subtensor(): + mock_subtensor = MagicMock(spec=Subtensor) + mock_subtensor.network = "test_network" + return mock_subtensor + + +@pytest.fixture +def mock_wallet(): + wallet = MagicMock(spec=Wallet) + wallet.hotkey.ss58_address = "hotkey_address" + wallet.coldkeypub.ss58_address = "coldkey_address" + return wallet + + +@pytest.mark.parametrize( + "ip,port,protocol,netuid,placeholder1,placeholder2,wait_for_inclusion,wait_for_finalization,prompt,expected,test_id,", + [ + ( + "192.168.1.1", + 9221, + 1, + 0, + 0, + 0, + False, + True, + False, + True, + "happy-path-no-wait", + ), + ( + "192.168.1.2", + 9222, + 2, + 1, + 1, + 1, + True, + False, + False, + True, + "happy-path-wait-for-inclusion", + ), + ( + "192.168.1.3", + 9223, + 3, + 2, + 2, + 2, + False, + True, + True, + True, + "happy-path-wait-for-finalization-and-prompt", + ), + ], + ids=[ + "happy-path-no-wait", + "happy-path-wait-for-inclusion", + "happy-path-wait-for-finalization-and-prompt", + ], +) +def test_serve_extrinsic_happy_path( + mock_subtensor, + mock_wallet, + ip, + port, + protocol, + netuid, + placeholder1, + placeholder2, + wait_for_inclusion, + wait_for_finalization, + prompt, + expected, + test_id, +): + # Arrange + mock_subtensor._do_serve_axon.return_value = (True, "") + with patch("bittensor.extrinsics.serving.Confirm.ask", return_value=True): + + # Act + result = serve_extrinsic( + mock_subtensor, + mock_wallet, + ip, + port, + protocol, + netuid, + placeholder1, + placeholder2, + wait_for_inclusion, + wait_for_finalization, + prompt, + ) + + # Assert + assert result == expected, f"Test ID: {test_id}" + + +# Various edge cases +@pytest.mark.parametrize( + "ip,port,protocol,netuid,placeholder1,placeholder2,wait_for_inclusion,wait_for_finalization,prompt,expected,test_id,", + [ + ( + "192.168.1.4", + 9224, + 4, + 3, + 3, + 3, + True, + True, + False, + True, + "edge_case_max_values", + ), + ], + ids=["edge-case-max-values"], +) +def test_serve_extrinsic_edge_cases( + mock_subtensor, + mock_wallet, + ip, + port, + protocol, + netuid, + placeholder1, + placeholder2, + wait_for_inclusion, + wait_for_finalization, + prompt, + expected, + test_id, +): + # Arrange + mock_subtensor._do_serve_axon.return_value = (True, "") + with patch("bittensor.extrinsics.serving.Confirm.ask", return_value=True): + + # Act + result = serve_extrinsic( + mock_subtensor, + mock_wallet, + ip, + port, + protocol, + netuid, + placeholder1, + placeholder2, + wait_for_inclusion, + wait_for_finalization, + prompt, + ) + + # Assert + assert result == expected + + +# Various error cases +@pytest.mark.parametrize( + "ip,port,protocol,netuid,placeholder1,placeholder2,wait_for_inclusion,wait_for_finalization,prompt,expected_error_message,test_id,", + [ + ( + "192.168.1.5", + 9225, + 5, + 4, + 4, + 4, + True, + True, + False, + False, + "error-case-failed-serve", + ), + ], + ids=["error-case-failed-serve"], +) +def test_serve_extrinsic_error_cases( + mock_subtensor, + mock_wallet, + ip, + port, + protocol, + netuid, + placeholder1, + placeholder2, + wait_for_inclusion, + wait_for_finalization, + prompt, + expected_error_message, + test_id, +): + # Arrange + mock_subtensor._do_serve_axon.return_value = (False, "Error serving axon") + with patch("bittensor.extrinsics.serving.Confirm.ask", return_value=True): + + # Act + result = serve_extrinsic( + mock_subtensor, + mock_wallet, + ip, + port, + protocol, + netuid, + placeholder1, + placeholder2, + wait_for_inclusion, + wait_for_finalization, + prompt, + ) + + # Assert + assert result == expected_error_message diff --git a/tests/unit_tests/utils/test_networking.py b/tests/unit_tests/utils/test_networking.py index 8e729a8594..2037718578 100644 --- a/tests/unit_tests/utils/test_networking.py +++ b/tests/unit_tests/utils/test_networking.py @@ -4,7 +4,7 @@ import requests import unittest.mock as mock from bittensor import utils -from unittest.mock import MagicMock, PropertyMock +from unittest.mock import MagicMock # Test conversion functions for IPv4 From 7e34e9c28d6c2a807d7689e63a5cc8c03308b3c7 Mon Sep 17 00:00:00 2001 From: Gus Date: Fri, 9 Feb 2024 12:09:24 -0500 Subject: [PATCH 15/16] rm unneeded exception --- bittensor/extrinsics/registration.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/bittensor/extrinsics/registration.py b/bittensor/extrinsics/registration.py index 104bb273c6..d5dcb06e2c 100644 --- a/bittensor/extrinsics/registration.py +++ b/bittensor/extrinsics/registration.py @@ -325,10 +325,6 @@ class MaxAttemptsException(Exception): pass -class MaxAttemptedException(Exception): - pass - - def run_faucet_extrinsic( subtensor: "bittensor.subtensor", wallet: "bittensor.wallet", From a4e5c2a543e4514f269ba8c23466589dc6d8af45 Mon Sep 17 00:00:00 2001 From: Gus Date: Fri, 9 Feb 2024 12:34:11 -0500 Subject: [PATCH 16/16] black --- tests/unit_tests/extrinsics/test_senate.py | 1 - tests/unit_tests/extrinsics/test_serving.py | 3 --- 2 files changed, 4 deletions(-) diff --git a/tests/unit_tests/extrinsics/test_senate.py b/tests/unit_tests/extrinsics/test_senate.py index a6a7ae066c..cd29fa880d 100644 --- a/tests/unit_tests/extrinsics/test_senate.py +++ b/tests/unit_tests/extrinsics/test_senate.py @@ -67,7 +67,6 @@ def test_register_senate_extrinsic( ) as mock_submit_extrinsic, patch.object( mock_wallet, "is_senate_member", return_value=is_registered ): - # Act result = register_senate_extrinsic( subtensor=mock_subtensor, diff --git a/tests/unit_tests/extrinsics/test_serving.py b/tests/unit_tests/extrinsics/test_serving.py index 4d95b80d73..4c1368d6c0 100644 --- a/tests/unit_tests/extrinsics/test_serving.py +++ b/tests/unit_tests/extrinsics/test_serving.py @@ -87,7 +87,6 @@ def test_serve_extrinsic_happy_path( # Arrange mock_subtensor._do_serve_axon.return_value = (True, "") with patch("bittensor.extrinsics.serving.Confirm.ask", return_value=True): - # Act result = serve_extrinsic( mock_subtensor, @@ -145,7 +144,6 @@ def test_serve_extrinsic_edge_cases( # Arrange mock_subtensor._do_serve_axon.return_value = (True, "") with patch("bittensor.extrinsics.serving.Confirm.ask", return_value=True): - # Act result = serve_extrinsic( mock_subtensor, @@ -203,7 +201,6 @@ def test_serve_extrinsic_error_cases( # Arrange mock_subtensor._do_serve_axon.return_value = (False, "Error serving axon") with patch("bittensor.extrinsics.serving.Confirm.ask", return_value=True): - # Act result = serve_extrinsic( mock_subtensor,