diff --git a/bittensor/core/extrinsics/registration.py b/bittensor/core/extrinsics/registration.py index eef316f387..d5211ee199 100644 --- a/bittensor/core/extrinsics/registration.py +++ b/bittensor/core/extrinsics/registration.py @@ -11,12 +11,12 @@ from bittensor.utils import unlock_key from bittensor.utils.btlogging import logging -from bittensor.utils.registration import log_no_torch_error, torch +from bittensor.utils.registration import create_pow, log_no_torch_error, torch if TYPE_CHECKING: from bittensor_wallet import Wallet from bittensor.core.subtensor import Subtensor - from bittensor.utils.registration.pow import POWSolution, create_pow + from bittensor.utils.registration.pow import POWSolution def _do_burned_register( diff --git a/bittensor/core/extrinsics/staking.py b/bittensor/core/extrinsics/staking.py index 9e85613bb4..9b35cda557 100644 --- a/bittensor/core/extrinsics/staking.py +++ b/bittensor/core/extrinsics/staking.py @@ -93,8 +93,10 @@ def _check_threshold_amount( if amount is None: # Stake it all. staking_balance = Balance.from_tao(old_balance.tao) + elif not isinstance(amount, Balance): + staking_balance = Balance.from_tao(amount) else: - staking_balance = Balance.from_tao(amount.tao) + staking_balance = amount # Leave existential balance to keep key alive. if staking_balance > old_balance - existential_deposit: diff --git a/tests/helpers/helpers.py b/tests/helpers/helpers.py index a6e9f292df..70c8342009 100644 --- a/tests/helpers/helpers.py +++ b/tests/helpers/helpers.py @@ -1,18 +1,18 @@ import asyncio -from collections import deque import json +import time +from collections import deque from typing import Union -from websockets.asyncio.client import ClientConnection, ClientProtocol -from websockets.uri import parse_uri - from bittensor_wallet.mock.wallet_mock import MockWallet as _MockWallet from bittensor_wallet.mock.wallet_mock import get_mock_coldkey from bittensor_wallet.mock.wallet_mock import get_mock_hotkey from bittensor_wallet.mock.wallet_mock import get_mock_wallet +from websockets.asyncio.client import ClientConnection, ClientProtocol +from websockets.uri import parse_uri -from bittensor.utils.balance import Balance from bittensor.core.chain_data import AxonInfo, NeuronInfo, PrometheusInfo +from bittensor.utils.balance import Balance from tests.helpers.integration_websocket_data import WEBSOCKET_RESPONSES, METADATA @@ -118,17 +118,15 @@ def __init__(self, *args, seed, **kwargs): self.received = deque() self._lock = asyncio.Lock() - async def send(self, payload: str, *args, **kwargs): + def send(self, payload: str, *args, **kwargs): received = json.loads(payload) id_ = received.pop("id") - async with self._lock: - self.received.append((received, id_)) + self.received.append((received, id_)) - async def recv(self, *args, **kwargs): + def recv(self, *args, **kwargs): while len(self.received) == 0: - await asyncio.sleep(0.1) - async with self._lock: - item, _id = self.received.pop() + time.sleep(0.1) + item, _id = self.received.pop() try: if item["method"] == "state_getMetadata": response = {"jsonrpc": "2.0", "id": _id, "result": METADATA} @@ -142,5 +140,16 @@ async def recv(self, *args, **kwargs): print("ERROR", self.seed, item["method"], item["params"]) raise - async def close(self, *args, **kwargs): + def close(self, *args, **kwargs): + pass + + +class FakeConnectContextManager: + def __init__(self, seed): + self.seed = seed + + def __enter__(self): + return FakeWebsocket(seed=self.seed) + + def __exit__(self, exc_type, exc, tb): pass diff --git a/tests/helpers/integration_websocket_data.py b/tests/helpers/integration_websocket_data.py index 6bd2e926e5..340072ee96 100644 --- a/tests/helpers/integration_websocket_data.py +++ b/tests/helpers/integration_websocket_data.py @@ -6423,6 +6423,12 @@ } }, "system_chain": {"[]": {"jsonrpc": "2.0", "result": "Bittensor"}}, + "chain_getBlockHash": { + "[3264143]": { + "jsonrpc": "2.0", + "result": None, + } + }, }, "min_allowed_weights": { "chain_getHead": { diff --git a/tests/integration_tests/test_metagraph_integration.py b/tests/integration_tests/test_metagraph_integration.py index 45ce51a6b8..3344ab6ae4 100644 --- a/tests/integration_tests/test_metagraph_integration.py +++ b/tests/integration_tests/test_metagraph_integration.py @@ -33,18 +33,14 @@ def test_sync_block_0(self): self.metagraph.sync(lite=True, block=0, subtensor=self.sub) def test_load_sync_save(self): - with mock.patch.object( - self.sub.async_subtensor, "neurons_lite", return_value=[] - ): + with mock.patch.object(self.sub, "neurons_lite", return_value=[]): self.metagraph.sync(lite=True, subtensor=self.sub) self.metagraph.save() self.metagraph.load() self.metagraph.save() def test_load_sync_save_from_torch(self): - with mock.patch.object( - self.sub.async_subtensor, "neurons_lite", return_value=[] - ): + with mock.patch.object(self.sub, "neurons_lite", return_value=[]): self.metagraph.sync(lite=True, subtensor=self.sub) def deprecated_save_torch(metagraph): diff --git a/tests/integration_tests/test_subtensor_integration.py b/tests/integration_tests/test_subtensor_integration.py index dcf149e62e..a2154b6615 100644 --- a/tests/integration_tests/test_subtensor_integration.py +++ b/tests/integration_tests/test_subtensor_integration.py @@ -1,14 +1,13 @@ -import asyncio import os.path import pytest -from bittensor.utils.balance import Balance -from bittensor.core.chain_data.axon_info import AxonInfo +from bt_decode import PortableRegistry, MetadataV15 from bittensor import NeuronInfo +from bittensor.core.chain_data.axon_info import AxonInfo from bittensor.core.subtensor import Subtensor -from bt_decode import PortableRegistry, MetadataV15 -from tests.helpers.helpers import FakeWebsocket +from bittensor.utils.balance import Balance +from tests.helpers.helpers import FakeConnectContextManager @pytest.fixture @@ -32,12 +31,11 @@ async def prepare_test(mocker, seed): MetadataV15.decode_from_metadata_option(f.read()) ) subtensor = Subtensor("unknown", _mock=True) - mocker.patch.object(subtensor.substrate.ws, "ws", FakeWebsocket(seed=seed)) - mocker.patch.object(subtensor.substrate.ws, "_initialized", True) - mocker.patch.object(subtensor.substrate._async_instance, "registry", registry) - subtensor.substrate.ws._receiving_task = asyncio.create_task( - subtensor.substrate.ws._start_receiving() + mocker.patch( + "async_substrate_interface.sync_substrate.connect", + mocker.Mock(return_value=FakeConnectContextManager(seed=seed)), ) + mocker.patch.object(subtensor.substrate, "registry", registry) return subtensor diff --git a/tests/unit_tests/extrinsics/test_commit_reveal.py b/tests/unit_tests/extrinsics/test_commit_reveal.py index 30bd7c0e63..f3e3266d64 100644 --- a/tests/unit_tests/extrinsics/test_commit_reveal.py +++ b/tests/unit_tests/extrinsics/test_commit_reveal.py @@ -1,48 +1,355 @@ +import numpy as np +import pytest +import torch +from bittensor_wallet import Wallet + +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 +def hyperparams(): + yield SubnetHyperparameters( + rho=0, + kappa=0, + immunity_period=0, + min_allowed_weights=0, + max_weight_limit=0.0, + tempo=0, + min_difficulty=0, + max_difficulty=0, + weights_version=0, + weights_rate_limit=0, + adjustment_interval=0, + activity_cutoff=0, + registration_allowed=False, + target_regs_per_interval=0, + min_burn=0, + max_burn=0, + bonds_moving_avg=0, + max_regs_per_block=0, + serving_rate_limit=0, + max_validators=0, + adjustment_alpha=0, + difficulty=0, + commit_reveal_weights_interval=0, + commit_reveal_weights_enabled=True, + alpha_high=0, + alpha_low=0, + liquid_alpha_enabled=False, + ) + + +def test_do_commit_reveal_v3_success(mocker, subtensor): + """Test successful commit-reveal with wait for finalization.""" + # Preps + fake_wallet = mocker.Mock(autospec=Wallet) + fake_netuid = 1 + fake_commit = b"fake_commit" + fake_reveal_round = 1 + + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_create_signed_extrinsic = mocker.patch.object( + subtensor.substrate, "create_signed_extrinsic" + ) + mocked_submit_extrinsic = mocker.patch.object( + subtensor.substrate, "submit_extrinsic" + ) + + # Call + result = commit_reveal._do_commit_reveal_v3( + subtensor=subtensor, + wallet=fake_wallet, + netuid=fake_netuid, + commit=fake_commit, + reveal_round=fake_reveal_round, + ) + + # Asserts + mocked_compose_call.assert_called_once_with( + call_module="SubtensorModule", + call_function="commit_crv3_weights", + call_params={ + "netuid": fake_netuid, + "commit": fake_commit, + "reveal_round": fake_reveal_round, + }, + ) + mocked_create_signed_extrinsic.assert_called_once_with( + call=mocked_compose_call.return_value, keypair=fake_wallet.hotkey + ) + mocked_submit_extrinsic.assert_called_once_with( + mocked_create_signed_extrinsic.return_value, + wait_for_inclusion=False, + wait_for_finalization=False, + ) + assert result == (True, "") + + +def test_do_commit_reveal_v3_failure_due_to_error(mocker, subtensor): + """Test commit-reveal fails due to an error in submission.""" + # Preps + fake_wallet = mocker.Mock(autospec=Wallet) + fake_netuid = 1 + fake_commit = b"fake_commit" + fake_reveal_round = 1 + + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_create_signed_extrinsic = mocker.patch.object( + subtensor.substrate, "create_signed_extrinsic" + ) + mocked_submit_extrinsic = mocker.patch.object( + subtensor.substrate, + "submit_extrinsic", + return_value=mocker.Mock(is_success=False, error_message="Mocked error"), + ) + mocked_format_error_message = mocker.patch.object( + subtensor_module, "format_error_message", return_value="Formatted error" + ) + + # Call + result = commit_reveal._do_commit_reveal_v3( + subtensor=subtensor, + wallet=fake_wallet, + netuid=fake_netuid, + commit=fake_commit, + reveal_round=fake_reveal_round, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + # Asserts + mocked_compose_call.assert_called_once_with( + call_module="SubtensorModule", + call_function="commit_crv3_weights", + call_params={ + "netuid": fake_netuid, + "commit": fake_commit, + "reveal_round": fake_reveal_round, + }, + ) + mocked_create_signed_extrinsic.assert_called_once_with( + call=mocked_compose_call.return_value, keypair=fake_wallet.hotkey + ) + mocked_submit_extrinsic.assert_called_once_with( + mocked_create_signed_extrinsic.return_value, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + mocked_format_error_message.assert_called_once_with("Mocked error") + assert result == (False, "Formatted error") + + +def test_commit_reveal_v3_extrinsic_success_with_torch(mocker, subtensor, hyperparams): + """Test successful commit-reveal with torch tensors.""" + # Preps + fake_wallet = mocker.Mock(autospec=Wallet) + fake_netuid = 1 + fake_uids = torch.tensor([1, 2, 3], dtype=torch.int64) + fake_weights = torch.tensor([0.1, 0.2, 0.7], dtype=torch.float32) + fake_commit_for_reveal = b"mock_commit_for_reveal" + fake_reveal_round = 1 + + # Mocks + + mocked_uids = mocker.Mock() + mocked_weights = mocker.Mock() + mocked_convert_weights_and_uids_for_emit = mocker.patch.object( + commit_reveal, + "convert_weights_and_uids_for_emit", + return_value=(mocked_uids, mocked_weights), + ) + mocked_get_subnet_reveal_period_epochs = mocker.patch.object( + subtensor, "get_subnet_reveal_period_epochs" + ) + mocked_get_encrypted_commit = mocker.patch.object( + commit_reveal, + "get_encrypted_commit", + return_value=(fake_commit_for_reveal, fake_reveal_round), + ) + mock_do_commit_reveal_v3 = mocker.patch.object( + commit_reveal, "_do_commit_reveal_v3", return_value=(True, "Success") + ) + mock_block = mocker.patch.object(subtensor, "get_current_block", return_value=1) + mock_hyperparams = mocker.patch.object( + subtensor, + "get_subnet_hyperparameters", + return_value=hyperparams, + ) + + # Call + success, message = commit_reveal.commit_reveal_v3_extrinsic( + subtensor=subtensor, + wallet=fake_wallet, + netuid=fake_netuid, + uids=fake_uids, + weights=fake_weights, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + # Asserts + assert success is True + assert message == "reveal_round:1" + mocked_convert_weights_and_uids_for_emit.assert_called_once_with( + fake_uids, fake_weights + ) + mocked_get_encrypted_commit.assert_called_once_with( + uids=mocked_uids, + weights=mocked_weights, + subnet_reveal_period_epochs=mock_hyperparams.return_value.commit_reveal_weights_interval, + version_key=commit_reveal.version_as_int, + tempo=mock_hyperparams.return_value.tempo, + netuid=fake_netuid, + current_block=mock_block.return_value, + ) + mock_do_commit_reveal_v3.assert_called_once_with( + subtensor=subtensor, + wallet=fake_wallet, + netuid=fake_netuid, + commit=fake_commit_for_reveal, + reveal_round=fake_reveal_round, + wait_for_inclusion=True, + wait_for_finalization=True, + ) -def test_commit_reveal_v3_extrinsic(mocker): - """ "Verify that sync `commit_reveal_v3_extrinsic` method calls proper async method.""" +def test_commit_reveal_v3_extrinsic_success_with_numpy(mocker, subtensor, hyperparams): + """Test successful commit-reveal with numpy arrays.""" # Preps - fake_subtensor = mocker.Mock() - fake_wallet = mocker.Mock() - netuid = 1 - uids = [1, 2, 3, 4] - weights = [0.1, 0.2, 0.3, 0.4] - version_key = 2 - wait_for_inclusion = True - wait_for_finalization = True - - mocked_execute_coroutine = mocker.patch.object(commit_reveal, "execute_coroutine") - mocked_commit_reveal_v3_extrinsic = mocker.Mock() - commit_reveal.async_commit_reveal_v3_extrinsic = mocked_commit_reveal_v3_extrinsic + fake_wallet = mocker.Mock(autospec=Wallet) + fake_netuid = 1 + fake_uids = np.array([1, 2, 3], dtype=np.int64) + fake_weights = np.array([0.1, 0.2, 0.7], dtype=np.float32) + + mock_convert = mocker.patch.object( + commit_reveal, + "convert_weights_and_uids_for_emit", + return_value=(fake_uids, fake_weights), + ) + mock_encode_drand = mocker.patch.object( + commit_reveal, "get_encrypted_commit", return_value=(b"commit", 0) + ) + mock_do_commit = mocker.patch.object( + commit_reveal, "_do_commit_reveal_v3", return_value=(True, "Committed!") + ) + mocker.patch.object(subtensor, "get_current_block", return_value=1) + mocker.patch.object( + subtensor, + "get_subnet_hyperparameters", + return_value=hyperparams, + ) # Call - result = commit_reveal.commit_reveal_v3_extrinsic( - subtensor=fake_subtensor, + success, message = commit_reveal.commit_reveal_v3_extrinsic( + subtensor=subtensor, wallet=fake_wallet, - netuid=netuid, - uids=uids, - weights=weights, - version_key=version_key, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, + netuid=fake_netuid, + uids=fake_uids, + weights=fake_weights, + wait_for_inclusion=False, + wait_for_finalization=False, ) # Asserts + assert success is True + assert message == "reveal_round:0" + mock_convert.assert_called_once_with(fake_uids, fake_weights) + mock_encode_drand.assert_called_once() + mock_do_commit.assert_called_once() - mocked_execute_coroutine.assert_called_once_with( - coroutine=mocked_commit_reveal_v3_extrinsic.return_value, - event_loop=fake_subtensor.event_loop, + +def test_commit_reveal_v3_extrinsic_response_false(mocker, subtensor, hyperparams): + """Test unsuccessful commit-reveal with torch.""" + # Preps + fake_wallet = mocker.Mock(autospec=Wallet) + fake_netuid = 1 + fake_uids = torch.tensor([1, 2, 3], dtype=torch.int64) + fake_weights = torch.tensor([0.1, 0.2, 0.7], dtype=torch.float32) + fake_commit_for_reveal = b"mock_commit_for_reveal" + fake_reveal_round = 1 + + # Mocks + mocker.patch.object( + commit_reveal, + "convert_weights_and_uids_for_emit", + return_value=(fake_uids, fake_weights), + ) + mocker.patch.object( + commit_reveal, + "get_encrypted_commit", + return_value=(fake_commit_for_reveal, fake_reveal_round), + ) + mock_do_commit_reveal_v3 = mocker.patch.object( + commit_reveal, "_do_commit_reveal_v3", return_value=(False, "Failed") ) - mocked_commit_reveal_v3_extrinsic.assert_called_once_with( - subtensor=fake_subtensor.async_subtensor, + mocker.patch.object(subtensor, "get_current_block", return_value=1) + mocker.patch.object( + subtensor, + "get_subnet_hyperparameters", + return_value=hyperparams, + ) + + # Call + success, message = commit_reveal.commit_reveal_v3_extrinsic( + subtensor=subtensor, wallet=fake_wallet, - netuid=netuid, - uids=uids, - weights=weights, - version_key=version_key, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, + netuid=fake_netuid, + uids=fake_uids, + weights=fake_weights, + wait_for_inclusion=True, + wait_for_finalization=True, ) - assert result == mocked_execute_coroutine.return_value + + # Asserts + assert success is False + assert message == "Failed" + mock_do_commit_reveal_v3.assert_called_once_with( + subtensor=subtensor, + wallet=fake_wallet, + netuid=fake_netuid, + commit=fake_commit_for_reveal, + reveal_round=fake_reveal_round, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + +def test_commit_reveal_v3_extrinsic_exception(mocker, subtensor): + """Test exception handling in commit-reveal.""" + # Preps + fake_wallet = mocker.Mock(autospec=Wallet) + fake_netuid = 1 + fake_uids = [1, 2, 3] + fake_weights = [0.1, 0.2, 0.7] + + mocker.patch.object( + commit_reveal, + "convert_weights_and_uids_for_emit", + side_effect=Exception("Test Error"), + ) + + # Call + success, message = commit_reveal.commit_reveal_v3_extrinsic( + subtensor=subtensor, + wallet=fake_wallet, + netuid=fake_netuid, + uids=fake_uids, + weights=fake_weights, + ) + + # Asserts + assert success is False + assert "Test Error" in message diff --git a/tests/unit_tests/extrinsics/test_commit_weights.py b/tests/unit_tests/extrinsics/test_commit_weights.py index 54a0b80b74..42cc1f2311 100644 --- a/tests/unit_tests/extrinsics/test_commit_weights.py +++ b/tests/unit_tests/extrinsics/test_commit_weights.py @@ -1,23 +1,45 @@ -from bittensor.core.extrinsics import commit_weights +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, +) -def test_commit_weights_extrinsic(mocker): - """ "Verify that sync `commit_weights_extrinsic` method calls proper async method.""" + +@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_commit_weights(subtensor, mocker): + """Successful _do_commit_weights call.""" # Preps - fake_subtensor = mocker.Mock() - fake_wallet = mocker.Mock() + fake_wallet = mocker.MagicMock() netuid = 1 - commit_hash = "0x1234567890abcdef" + commit_hash = "fake_commit_hash" wait_for_inclusion = True wait_for_finalization = True - mocked_execute_coroutine = mocker.patch.object(commit_weights, "execute_coroutine") - mocked_commit_weights_extrinsic = mocker.Mock() - commit_weights.async_commit_weights_extrinsic = mocked_commit_weights_extrinsic + subtensor.substrate.submit_extrinsic.return_value.is_success = None + + mocked_format_error_message = mocker.Mock() + mocker.patch( + "bittensor.core.extrinsics.commit_weights.format_error_message", + mocked_format_error_message, + ) # Call - result = commit_weights.commit_weights_extrinsic( - subtensor=fake_subtensor, + result = _do_commit_weights( + subtensor=subtensor, wallet=fake_wallet, netuid=netuid, commit_hash=commit_hash, @@ -25,68 +47,101 @@ def test_commit_weights_extrinsic(mocker): wait_for_finalization=wait_for_finalization, ) - # Asserts - - mocked_execute_coroutine.assert_called_once_with( - coroutine=mocked_commit_weights_extrinsic.return_value, - event_loop=fake_subtensor.event_loop, + # Assertions + subtensor.substrate.compose_call.assert_called_once_with( + call_module="SubtensorModule", + call_function="commit_weights", + call_params={ + "netuid": netuid, + "commit_hash": commit_hash, + }, ) - mocked_commit_weights_extrinsic.assert_called_once_with( - subtensor=fake_subtensor.async_subtensor, - wallet=fake_wallet, - netuid=netuid, - commit_hash=commit_hash, + + subtensor.substrate.create_signed_extrinsic.assert_called_once() + _, kwargs = subtensor.substrate.create_signed_extrinsic.call_args + assert kwargs["call"] == subtensor.substrate.compose_call.return_value + assert kwargs["keypair"] == fake_wallet.hotkey + + subtensor.substrate.submit_extrinsic.assert_called_once_with( + extrinsic=subtensor.substrate.create_signed_extrinsic.return_value, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) - assert result == mocked_execute_coroutine.return_value + + mocked_format_error_message.assert_called_once_with( + subtensor.substrate.submit_extrinsic.return_value.error_message, + ) + + assert result == ( + False, + mocked_format_error_message.return_value, + ) -def test_reveal_weights_extrinsic(mocker): - """Verify that sync `reveal_weights_extrinsic` method calls proper async method.""" +def test_do_reveal_weights(subtensor, mocker): + """Verifies that the `_do_reveal_weights` method interacts with the right substrate methods.""" # Preps - fake_subtensor = mocker.Mock() - fake_wallet = mocker.Mock() + fake_wallet = mocker.MagicMock(autospec=Wallet) + fake_wallet.hotkey.ss58_address = "hotkey" + netuid = 1 uids = [1, 2, 3, 4] - weights = [5, 6, 7, 8] - salt = [1, 2, 3, 4] - version_key = 2 + values = [1, 2, 3, 4] + salt = [4, 2, 2, 1] wait_for_inclusion = True wait_for_finalization = True - mocked_execute_coroutine = mocker.patch.object(commit_weights, "execute_coroutine") - mocked_reveal_weights_extrinsic = mocker.Mock() - commit_weights.async_reveal_weights_extrinsic = mocked_reveal_weights_extrinsic + subtensor.substrate.submit_extrinsic.return_value.is_success = None + + mocked_format_error_message = mocker.Mock() + mocker.patch( + "bittensor.core.extrinsics.commit_weights.format_error_message", + mocked_format_error_message, + ) # Call - result = commit_weights.reveal_weights_extrinsic( - subtensor=fake_subtensor, + result = _do_reveal_weights( + subtensor=subtensor, wallet=fake_wallet, netuid=netuid, uids=uids, - weights=weights, + values=values, salt=salt, - version_key=version_key, + version_key=version_as_int, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) # Asserts + subtensor.substrate.compose_call.assert_called_once_with( + call_module="SubtensorModule", + call_function="reveal_weights", + call_params={ + "netuid": netuid, + "uids": uids, + "values": values, + "salt": salt, + "version_key": version_as_int, + }, + ) - mocked_execute_coroutine.assert_called_once_with( - coroutine=mocked_reveal_weights_extrinsic.return_value, - event_loop=fake_subtensor.event_loop, + subtensor.substrate.create_signed_extrinsic.assert_called_once_with( + call=subtensor.substrate.compose_call.return_value, + keypair=fake_wallet.hotkey, + nonce=subtensor.substrate.get_account_next_index.return_value, ) - mocked_reveal_weights_extrinsic.assert_called_once_with( - subtensor=fake_subtensor.async_subtensor, - wallet=fake_wallet, - netuid=netuid, - uids=uids, - weights=weights, - salt=salt, - version_key=version_key, + + subtensor.substrate.submit_extrinsic.assert_called_once_with( + extrinsic=subtensor.substrate.create_signed_extrinsic.return_value, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) - assert result == mocked_execute_coroutine.return_value + + mocked_format_error_message.assert_called_once_with( + subtensor.substrate.submit_extrinsic.return_value.error_message, + ) + + assert result == ( + False, + mocked_format_error_message.return_value, + ) diff --git a/tests/unit_tests/extrinsics/test_registration.py b/tests/unit_tests/extrinsics/test_registration.py index 99b2c3cd1d..18676619de 100644 --- a/tests/unit_tests/extrinsics/test_registration.py +++ b/tests/unit_tests/extrinsics/test_registration.py @@ -1,101 +1,224 @@ +# The MIT License (MIT) +# Copyright © 2024 Opentensor Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the “Software”), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. +# +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +import pytest +from bittensor_wallet import Wallet + from bittensor.core.extrinsics import registration +from bittensor.core.subtensor import Subtensor +from bittensor.utils.registration import POWSolution + + +# Mocking external dependencies +@pytest.fixture +def mock_subtensor(mocker): + mock = mocker.MagicMock(spec=Subtensor) + mock.network = "mock_network" + mock.substrate = mocker.MagicMock() + return mock + + +@pytest.fixture +def mock_wallet(mocker): + mock = mocker.MagicMock(spec=Wallet) + mock.coldkeypub.ss58_address = "mock_address" + mock.coldkey = mocker.MagicMock() + mock.hotkey = mocker.MagicMock() + mock.hotkey.ss58_address = "fake_ss58_address" + return mock + + +@pytest.fixture +def mock_pow_solution(mocker): + mock = mocker.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.fixture +def mock_new_wallet(mocker): + mock = mocker.MagicMock(spec=Wallet) + mock.coldkeypub.ss58_address = "mock_address" + mock.coldkey = mocker.MagicMock() + mock.hotkey = mocker.MagicMock() + return mock + + +@pytest.mark.parametrize( + "subnet_exists, neuron_is_null, cuda_available, expected_result, test_id", + [ + (False, True, True, False, "subnet-does-not-exist"), + (True, False, True, True, "neuron-already-registered"), + (True, True, False, False, "cuda-unavailable"), + ], +) +def test_register_extrinsic_without_pow( + mock_subtensor, + mock_wallet, + subnet_exists, + neuron_is_null, + cuda_available, + expected_result, + test_id, + mocker, +): + # Arrange + with ( + mocker.patch.object( + mock_subtensor, "subnet_exists", return_value=subnet_exists + ), + mocker.patch.object( + mock_subtensor, + "get_neuron_for_pubkey_and_subnet", + return_value=mocker.MagicMock(is_null=neuron_is_null), + ), + mocker.patch("torch.cuda.is_available", return_value=cuda_available), + mocker.patch( + "bittensor.utils.registration.pow._get_block_with_retry", + return_value=(0, 0, "00ff11ee"), + ), + ): + # Act + result = registration.register_extrinsic( + subtensor=mock_subtensor, + wallet=mock_wallet, + netuid=123, + wait_for_inclusion=True, + wait_for_finalization=True, + max_allowed_attempts=3, + output_in_place=True, + cuda=True, + dev_id=0, + tpb=256, + num_processes=None, + update_interval=None, + log_verbose=False, + ) + + # Assert + assert result == expected_result, f"Test failed for test_id: {test_id}" + + +@pytest.mark.parametrize( + "pow_success, pow_stale, registration_success, cuda, hotkey_registered, expected_result, test_id", + [ + (True, False, True, False, False, True, "successful-with-valid-pow"), + (True, False, True, True, False, True, "successful-with-valid-cuda-pow"), + # Pow failed but key was registered already + (False, False, False, False, True, True, "hotkey-registered"), + # Pow was a success but registration failed with error 'key already registered' + (True, False, False, False, False, True, "registration-fail-key-registered"), + ], +) +def test_register_extrinsic_with_pow( + mock_subtensor, + mock_wallet, + mock_pow_solution, + pow_success, + pow_stale, + registration_success, + cuda, + hotkey_registered, + expected_result, + test_id, + mocker, +): + # Arrange + with mocker.patch( + "bittensor.utils.registration.pow._solve_for_difficulty_fast", + return_value=mock_pow_solution if pow_success else None, + ), mocker.patch( + "bittensor.utils.registration.pow._solve_for_difficulty_fast_cuda", + return_value=mock_pow_solution if pow_success else None, + ), mocker.patch( + "bittensor.core.extrinsics.registration._do_pow_register", + return_value=(registration_success, "HotKeyAlreadyRegisteredInSubNet"), + ), mocker.patch("torch.cuda.is_available", return_value=cuda): + # Act + if pow_success: + mock_pow_solution.is_stale.return_value = pow_stale + + if not pow_success and hotkey_registered: + mock_subtensor.is_hotkey_registered = mocker.MagicMock( + return_value=hotkey_registered + ) + + result = registration.register_extrinsic( + subtensor=mock_subtensor, + wallet=mock_wallet, + netuid=123, + wait_for_inclusion=True, + wait_for_finalization=True, + max_allowed_attempts=3, + output_in_place=True, + cuda=cuda, + dev_id=0, + tpb=256, + num_processes=None, + update_interval=None, + log_verbose=False, + ) + + # Assert + assert result == expected_result, f"Test failed for test_id: {test_id}." -def test_burned_register_extrinsic(mocker): - """ "Verify that sync `burned_register_extrinsic` method calls proper async method.""" - # Preps - fake_subtensor = mocker.Mock() - fake_wallet = mocker.Mock() - netuid = 1 - wait_for_inclusion = True - wait_for_finalization = True - - mocked_execute_coroutine = mocker.patch.object(registration, "execute_coroutine") - mocked_burned_register_extrinsic = mocker.Mock() - registration.async_burned_register_extrinsic = mocked_burned_register_extrinsic - - # Call - result = registration.burned_register_extrinsic( - subtensor=fake_subtensor, - wallet=fake_wallet, - netuid=netuid, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - - # Asserts - - mocked_execute_coroutine.assert_called_once_with( - coroutine=mocked_burned_register_extrinsic.return_value, - event_loop=fake_subtensor.event_loop, - ) - mocked_burned_register_extrinsic.assert_called_once_with( - subtensor=fake_subtensor.async_subtensor, - wallet=fake_wallet, - netuid=netuid, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - assert result == mocked_execute_coroutine.return_value - - -def test_register_extrinsic(mocker): - """ "Verify that sync `register_extrinsic` method calls proper async method.""" - # Preps - fake_subtensor = mocker.Mock() - fake_wallet = mocker.Mock() - netuid = 1 - wait_for_inclusion = True - wait_for_finalization = True - max_allowed_attempts = 7 - output_in_place = True - cuda = True - dev_id = 5 - tpb = 12 - num_processes = 8 - update_interval = 2 - log_verbose = True - - mocked_execute_coroutine = mocker.patch.object(registration, "execute_coroutine") - mocked_register_extrinsic = mocker.Mock() - registration.async_register_extrinsic = mocked_register_extrinsic - - # Call - result = registration.register_extrinsic( - subtensor=fake_subtensor, - wallet=fake_wallet, - netuid=netuid, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - max_allowed_attempts=max_allowed_attempts, - output_in_place=output_in_place, - cuda=cuda, - dev_id=dev_id, - tpb=tpb, - num_processes=num_processes, - update_interval=update_interval, - log_verbose=log_verbose, - ) - - # Asserts - - mocked_execute_coroutine.assert_called_once_with( - coroutine=mocked_register_extrinsic.return_value, - event_loop=fake_subtensor.event_loop, - ) - mocked_register_extrinsic.assert_called_once_with( - subtensor=fake_subtensor.async_subtensor, - wallet=fake_wallet, - netuid=netuid, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - max_allowed_attempts=max_allowed_attempts, - output_in_place=output_in_place, - cuda=cuda, - dev_id=dev_id, - tpb=tpb, - num_processes=num_processes, - update_interval=update_interval, - log_verbose=log_verbose, - ) - assert result == mocked_execute_coroutine.return_value +@pytest.mark.parametrize( + "subnet_exists, neuron_is_null, recycle_success, is_registered, expected_result, test_id", + [ + # Happy paths + (True, False, None, None, True, "neuron-not-null"), + (True, True, True, True, True, "happy-path-wallet-registered"), + # Error paths + (False, True, False, None, False, "subnet-non-existence"), + (True, True, False, False, False, "error-path-recycling-failed"), + (True, True, True, False, False, "error-path-not-registered"), + ], +) +def test_burned_register_extrinsic( + mock_subtensor, + mock_wallet, + subnet_exists, + neuron_is_null, + recycle_success, + is_registered, + expected_result, + test_id, + mocker, +): + # Arrange + with mocker.patch.object( + mock_subtensor, "subnet_exists", return_value=subnet_exists + ), mocker.patch.object( + mock_subtensor, + "get_neuron_for_pubkey_and_subnet", + return_value=mocker.MagicMock(is_null=neuron_is_null), + ), mocker.patch( + "bittensor.core.extrinsics.registration._do_burned_register", + return_value=(recycle_success, "Mock error message"), + ), mocker.patch.object( + mock_subtensor, "is_hotkey_registered", return_value=is_registered + ): + # Act + result = registration.burned_register_extrinsic( + subtensor=mock_subtensor, wallet=mock_wallet, netuid=123 + ) + # Assert + assert result == expected_result, f"Test failed for test_id: {test_id}" diff --git a/tests/unit_tests/extrinsics/test_root.py b/tests/unit_tests/extrinsics/test_root.py index 7fae887011..21395735fc 100644 --- a/tests/unit_tests/extrinsics/test_root.py +++ b/tests/unit_tests/extrinsics/test_root.py @@ -3,81 +3,241 @@ from bittensor.core.extrinsics import root -def test_root_register_extrinsic(mocker): - """Verify that sync `root_register_extrinsic` method calls proper async method.""" - # Preps - fake_subtensor = mocker.Mock() - fake_wallet = mocker.Mock() - wait_for_inclusion = True - wait_for_finalization = True +@pytest.fixture +def mock_subtensor(mocker): + mock = mocker.MagicMock(spec=Subtensor) + mock.network = "magic_mock" + mock.substrate = mocker.Mock() + return mock - mocked_execute_coroutine = mocker.patch.object(root, "execute_coroutine") - mocked_root_register_extrinsic = mocker.Mock() - root.async_root_register_extrinsic = mocked_root_register_extrinsic - # Call - result = root.root_register_extrinsic( - subtensor=fake_subtensor, - wallet=fake_wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) +@pytest.fixture +def mock_wallet(mocker): + mock = mocker.MagicMock() + mock.hotkey.ss58_address = "fake_hotkey_address" + return mock - # Asserts - mocked_execute_coroutine.assert_called_once_with( - coroutine=mocked_root_register_extrinsic.return_value, - event_loop=fake_subtensor.event_loop, +@pytest.mark.parametrize( + "wait_for_inclusion, wait_for_finalization, hotkey_registered, registration_success, expected_result", + [ + ( + False, + True, + [True, None], + True, + True, + ), # Already registered after attempt + ( + False, + True, + [False, 1], + True, + True, + ), # Registration succeeds with user confirmation + (False, True, [False, None], False, False), # Registration fails + ( + False, + True, + [False, None], + True, + False, + ), # Registration succeeds but neuron not found + ], + ids=[ + "success-already-registered", + "success-registration-succeeds", + "failure-registration-failed", + "failure-neuron-not-found", + ], +) +def test_root_register_extrinsic( + mock_subtensor, + mock_wallet, + wait_for_inclusion, + wait_for_finalization, + hotkey_registered, + registration_success, + expected_result, + mocker, +): + # Arrange + mock_subtensor.is_hotkey_registered.return_value = hotkey_registered[0] + + # Preps + mocked_sign_and_send_extrinsic = mocker.patch.object( + mock_subtensor, + "sign_and_send_extrinsic", + return_value=(registration_success, "Error registering"), ) - mocked_root_register_extrinsic.assert_called_once_with( - subtensor=fake_subtensor.async_subtensor, - wallet=fake_wallet, - netuid=0, + mocker.patch.object( + mock_subtensor.substrate, + "query", + return_value=hotkey_registered[1], + ) + + # Act + result = root.root_register_extrinsic( + subtensor=mock_subtensor, + wallet=mock_wallet, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) - assert result == mocked_execute_coroutine.return_value + # Assert + assert result == expected_result + if not hotkey_registered[0]: + mock_subtensor.substrate.compose_call.assert_called_once_with( + call_module="SubtensorModule", + call_function="root_register", + call_params={"hotkey": "fake_hotkey_address"}, + ) + mocked_sign_and_send_extrinsic.assert_called_once_with( + mock_subtensor.substrate.compose_call.return_value, + wallet=mock_wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) -def test_set_root_weights_extrinsic(mocker): - """Verify that sync `set_root_weights_extrinsic` method calls proper async method.""" - # Preps - fake_subtensor = mocker.Mock() - fake_wallet = mocker.Mock() - netuids = [1, 2, 3, 4] - weights = [0.1, 0.2, 0.3, 0.4] - version_key = 2 - wait_for_inclusion = True - wait_for_finalization = True - mocked_execute_coroutine = mocker.patch.object(root, "execute_coroutine") - mocked_set_root_weights_extrinsic = mocker.Mock() - root.async_set_root_weights_extrinsic = mocked_set_root_weights_extrinsic +@pytest.mark.parametrize( + "wait_for_inclusion, wait_for_finalization, netuids, weights, expected_success", + [ + (True, False, [1, 2], [0.5, 0.5], True), # Success - weights set + ( + False, + False, + [1, 2], + [0.5, 0.5], + True, + ), # Success - weights set no wait + ( + True, + False, + [1, 2], + [2000, 20], + True, + ), # Success - large value to be normalized + ( + True, + False, + [1, 2], + [2000, 0], + True, + ), # Success - single large value + ( + True, + False, + [1, 2], + [0.5, 0.5], + False, + ), # Failure - setting weights failed + ], + ids=[ + "success-weights-set", + "success-not-wait", + "success-large-value", + "success-single-value", + "failure-setting-weights", + ], +) +def test_set_root_weights_extrinsic( + mock_subtensor, + mock_wallet, + wait_for_inclusion, + wait_for_finalization, + netuids, + weights, + expected_success, + mocker, +): + # Preps + root._do_set_root_weights = mocker.Mock( + return_value=(expected_success, "Mock error") + ) + root._get_limits = mocker.Mock( + return_value=(0, 1), + ) # Call result = root.set_root_weights_extrinsic( - subtensor=fake_subtensor, - wallet=fake_wallet, + subtensor=mock_subtensor, + wallet=mock_wallet, netuids=netuids, weights=weights, - version_key=version_key, + version_key=0, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) # Asserts + assert result == expected_success - mocked_execute_coroutine.assert_called_once_with( - coroutine=mocked_set_root_weights_extrinsic.return_value, - event_loop=fake_subtensor.event_loop, - ) - mocked_set_root_weights_extrinsic.assert_called_once_with( - subtensor=fake_subtensor.async_subtensor, - wallet=fake_wallet, - netuids=netuids, - weights=weights, - version_key=version_key, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, + +@pytest.mark.parametrize( + "wait_for_inclusion, wait_for_finalization, netuids, weights, user_response, expected_success", + [ + (True, False, [1, 2], [0.5, 0.5], True, True), # Success - weights set + ( + False, + False, + [1, 2], + [0.5, 0.5], + None, + True, + ), # Success - weights set no wait + ( + True, + False, + [1, 2], + [2000, 20], + True, + True, + ), # Success - large value to be normalized + ( + True, + False, + [1, 2], + [2000, 0], + True, + True, + ), # Success - single large value + ( + True, + False, + [1, 2], + [0.5, 0.5], + None, + False, + ), # Failure - setting weights failed + ], + ids=[ + "success-weights-set", + "success-not-wait", + "success-large-value", + "success-single-value", + "failure-setting-weights", + ], +) +def test_set_root_weights_extrinsic_torch( + mock_subtensor, + mock_wallet, + wait_for_inclusion, + wait_for_finalization, + netuids, + weights, + user_response, + expected_success, + force_legacy_torch_compatible_api, + mocker, +): + test_set_root_weights_extrinsic( + mock_subtensor, + mock_wallet, + wait_for_inclusion, + wait_for_finalization, + netuids, + weights, + expected_success, + mocker, ) - assert result == mocked_execute_coroutine.return_value diff --git a/tests/unit_tests/extrinsics/test_serving.py b/tests/unit_tests/extrinsics/test_serving.py index 2793844254..6d00e97629 100644 --- a/tests/unit_tests/extrinsics/test_serving.py +++ b/tests/unit_tests/extrinsics/test_serving.py @@ -1,151 +1,376 @@ +# The MIT License (MIT) +# Copyright © 2024 Opentensor Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the “Software”), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. +# +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +from unittest.mock import MagicMock, patch + +import pytest +from bittensor_wallet import Wallet + +from bittensor.core.axon import Axon +from bittensor.core.subtensor import Subtensor from bittensor.core.extrinsics import serving -def test_do_serve_axon(mocker): - """Verify that sync `do_serve_axon` method calls proper async method.""" - # Preps - fake_subtensor = mocker.Mock() - fake_wallet = mocker.Mock() - call_params = mocker.Mock() - wait_for_inclusion = True - wait_for_finalization = True - - mocked_do_serve_axon = mocker.Mock() - serving.async_do_serve_axon = mocked_do_serve_axon - - # Call - result = serving.do_serve_axon( - subtensor=fake_subtensor, - wallet=fake_wallet, - call_params=call_params, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) +@pytest.fixture +def mock_subtensor(mocker): + mock_subtensor = mocker.MagicMock(spec=Subtensor) + mock_subtensor.network = "test_network" + mock_subtensor.substrate = mocker.MagicMock() + return mock_subtensor - # Asserts - mocked_do_serve_axon.assert_called_once_with( - subtensor=fake_subtensor.async_subtensor, - wallet=fake_wallet, - call_params=call_params, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) +@pytest.fixture +def mock_wallet(mocker): + wallet = mocker.MagicMock(spec=Wallet) + wallet.hotkey.ss58_address = "hotkey_address" + wallet.coldkeypub.ss58_address = "coldkey_address" + return wallet -def test_serve_axon_extrinsic(mocker): - """Verify that sync `serve_axon_extrinsic` method calls proper async method.""" - # Preps - fake_subtensor = mocker.Mock() - netuid = 2 - axon = mocker.Mock() - wait_for_inclusion = True - wait_for_finalization = True - certificate = mocker.Mock() - - mocked_execute_coroutine = mocker.patch.object(serving, "execute_coroutine") - mocked_serve_axon_extrinsic = mocker.Mock() - serving.async_serve_axon_extrinsic = mocked_serve_axon_extrinsic - - # Call - result = serving.serve_axon_extrinsic( - subtensor=fake_subtensor, - netuid=netuid, - axon=axon, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - certificate=certificate, - ) - # Asserts +@pytest.fixture +def mock_axon(mock_wallet, mocker): + axon = mocker.MagicMock(spec=Axon) + axon.wallet = mock_wallet() + axon.external_port = 9221 + return axon - mocked_execute_coroutine.assert_called_once_with( - coroutine=mocked_serve_axon_extrinsic.return_value, - event_loop=fake_subtensor.event_loop, - ) - mocked_serve_axon_extrinsic.assert_called_once_with( - subtensor=fake_subtensor.async_subtensor, - netuid=netuid, - axon=axon, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - certificate=certificate, - ) - assert result == mocked_execute_coroutine.return_value - - -def test_publish_metadata(mocker): - """Verify that `publish_metadata` calls proper async method.""" - # Preps - fake_subtensor = mocker.Mock() - fake_wallet = mocker.Mock() - netuid = 2 - data_type = "data_type" - data = b"data" - wait_for_inclusion = True - wait_for_finalization = True - - mocked_execute_coroutine = mocker.patch.object(serving, "execute_coroutine") - mocked_publish_metadata = mocker.Mock() - serving.async_publish_metadata = mocked_publish_metadata - - # Call - result = serving.publish_metadata( - subtensor=fake_subtensor, - wallet=fake_wallet, - netuid=netuid, - data_type=data_type, - data=data, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, + +@pytest.mark.parametrize( + "ip,port,protocol,netuid,placeholder1,placeholder2,wait_for_inclusion,wait_for_finalization,expected,test_id,", + [ + ( + "192.168.1.1", + 9221, + 1, + 0, + 0, + 0, + False, + True, + True, + "happy-path-no-wait", + ), + ( + "192.168.1.2", + 9222, + 2, + 1, + 1, + 1, + True, + False, + True, + "happy-path-wait-for-inclusion", + ), + ( + "192.168.1.3", + 9223, + 3, + 2, + 2, + 2, + False, + True, + True, + "happy-path-wait-for-finalization", + ), + ], + ids=[ + "happy-path-no-wait", + "happy-path-wait-for-inclusion", + "happy-path-wait-for-finalization", + ], +) +def test_serve_extrinsic_happy_path( + mock_subtensor, + mock_wallet, + ip, + port, + protocol, + netuid, + placeholder1, + placeholder2, + wait_for_inclusion, + wait_for_finalization, + expected, + test_id, + mocker, +): + # Arrange + serving.do_serve_axon = mocker.MagicMock(return_value=(True, "")) + # Act + result = serving.serve_extrinsic( + mock_subtensor, + mock_wallet, + ip, + port, + protocol, + netuid, + placeholder1, + placeholder2, + wait_for_inclusion, + wait_for_finalization, ) - # Asserts + # Assert + assert result == expected, f"Test ID: {test_id}" - mocked_execute_coroutine.assert_called_once_with( - coroutine=mocked_publish_metadata.return_value, - event_loop=fake_subtensor.event_loop, - ) - mocked_publish_metadata.assert_called_once_with( - subtensor=fake_subtensor.async_subtensor, - wallet=fake_wallet, - netuid=netuid, - data_type=data_type, - data=data, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - assert result == mocked_execute_coroutine.return_value - - -def test_get_metadata(mocker): - """Verify that `get_metadata` calls proper async method.""" - # Preps - fake_subtensor = mocker.Mock() - netuid = 2 - hotkey = "hotkey" - block = 123 - - mocked_execute_coroutine = mocker.patch.object(serving, "execute_coroutine") - mocked_get_metadata = mocker.Mock() - serving.async_get_metadata = mocked_get_metadata - - # Call - result = serving.get_metadata( - subtensor=fake_subtensor, - netuid=netuid, - hotkey=hotkey, - block=block, - ) - # Asserts - mocked_execute_coroutine.assert_called_once_with( - coroutine=mocked_get_metadata.return_value, - event_loop=fake_subtensor.event_loop, +# Various edge cases +@pytest.mark.parametrize( + "ip,port,protocol,netuid,placeholder1,placeholder2,wait_for_inclusion,wait_for_finalization,expected,test_id,", + [ + ( + "192.168.1.4", + 9224, + 4, + 3, + 3, + 3, + True, + True, + 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, + expected, + test_id, + mocker, +): + # Arrange + serving.do_serve_axon = mocker.MagicMock(return_value=(True, "")) + # Act + result = serving.serve_extrinsic( + mock_subtensor, + mock_wallet, + ip, + port, + protocol, + netuid, + placeholder1, + placeholder2, + wait_for_inclusion, + wait_for_finalization, ) - mocked_get_metadata.assert_called_once_with( - subtensor=fake_subtensor.async_subtensor, - netuid=netuid, - hotkey=hotkey, - block=block, + + # Assert + assert result == expected, f"Test ID: {test_id}" + + +# Various error cases +@pytest.mark.parametrize( + "ip,port,protocol,netuid,placeholder1,placeholder2,wait_for_inclusion,wait_for_finalization,expected_error_message,test_id,", + [ + ( + "192.168.1.5", + 9225, + 5, + 4, + 4, + 4, + True, + True, + 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, + expected_error_message, + test_id, + mocker, +): + # Arrange + serving.do_serve_axon = mocker.MagicMock(return_value=(False, "Error serving axon")) + # Act + result = serving.serve_extrinsic( + mock_subtensor, + mock_wallet, + ip, + port, + protocol, + netuid, + placeholder1, + placeholder2, + wait_for_inclusion, + wait_for_finalization, ) - assert result == mocked_execute_coroutine.return_value + + # Assert + assert result == expected_error_message, f"Test ID: {test_id}" + + +@pytest.mark.parametrize( + "netuid, wait_for_inclusion, wait_for_finalization, external_ip, external_ip_success, serve_success, expected_result, test_id", + [ + # Happy path test + (1, False, True, "192.168.1.1", True, True, True, "happy-ext-ip"), + (1, False, True, None, True, True, True, "happy-net-external-ip"), + # Edge cases + (1, True, True, "192.168.1.1", True, True, True, "edge-case-wait"), + # Error cases + (1, False, True, None, False, True, False, "error-fetching-external-ip"), + ( + 1, + False, + True, + "192.168.1.1", + True, + False, + False, + "error-serving-axon", + ), + ], + ids=[ + "happy-axon-external-ip", + "happy-net-external-ip", + "edge-case-wait", + "error-fetching-external-ip", + "error-serving-axon", + ], +) +def test_serve_axon_extrinsic( + mock_subtensor, + mock_axon, + netuid, + wait_for_inclusion, + wait_for_finalization, + external_ip, + external_ip_success, + serve_success, + expected_result, + test_id, + mocker, +): + mock_axon.external_ip = external_ip + # Arrange + with patch( + "bittensor.utils.networking.get_external_ip", + side_effect=Exception("Failed to fetch IP") + if not external_ip_success + else MagicMock(return_value="192.168.1.1"), + ): + serving.do_serve_axon = mocker.MagicMock(return_value=(serve_success, "")) + # Act + if not external_ip_success: + with pytest.raises(ConnectionError): + serving.serve_axon_extrinsic( + mock_subtensor, + netuid, + mock_axon, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + else: + result = serving.serve_axon_extrinsic( + mock_subtensor, + netuid, + mock_axon, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + + # Assert + assert result == expected_result, f"Test ID: {test_id}" + + +@pytest.mark.parametrize( + "wait_for_inclusion, wait_for_finalization, net_uid, type_u, data, response_success, expected_result, test_id", + [ + ( + True, + True, + 1, + "Sha256", + b"mock_bytes_data", + True, + True, + "happy-path-wait", + ), + ( + False, + False, + 1, + "Sha256", + b"mock_bytes_data", + True, + True, + "happy-path-no-wait", + ), + ], + ids=["happy-path-wait", "happy-path-no-wait"], +) +def test_publish_metadata( + mock_subtensor, + mock_wallet, + wait_for_inclusion, + wait_for_finalization, + net_uid, + type_u, + data, + response_success, + expected_result, + test_id, +): + # Arrange + with 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", + ), + ): + # Act + result = serving.publish_metadata( + subtensor=mock_subtensor, + wallet=mock_wallet, + netuid=net_uid, + data_type=type_u, + data=data, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + # Assert + assert result == expected_result, f"Test ID: {test_id}" diff --git a/tests/unit_tests/extrinsics/test_set_weights.py b/tests/unit_tests/extrinsics/test_set_weights.py index 116065463f..a2aaa4aaab 100644 --- a/tests/unit_tests/extrinsics/test_set_weights.py +++ b/tests/unit_tests/extrinsics/test_set_weights.py @@ -1,47 +1,248 @@ -from bittensor.core.extrinsics import set_weights +from unittest.mock import MagicMock, patch +import pytest +import torch +from bittensor_wallet import Wallet -def test_set_weights_extrinsic(mocker): - """ "Verify that sync `set_weights_extrinsic` method calls proper async method.""" - # Preps - fake_subtensor = mocker.Mock() - fake_wallet = mocker.Mock() - netuid = 2 - uids = [1, 2, 3, 4] - weights = [0.1, 0.2, 0.3, 0.4] - version_key = 2 - wait_for_inclusion = True - wait_for_finalization = True +from bittensor.core import subtensor as subtensor_module +from bittensor.core.extrinsics.set_weights import ( + _do_set_weights, + set_weights_extrinsic, +) +from bittensor.core.settings import version_as_int +from bittensor.core.subtensor import Subtensor - mocked_execute_coroutine = mocker.patch.object(set_weights, "execute_coroutine") - mocked_set_weights_extrinsic = mocker.Mock() - set_weights.async_set_weights_extrinsic = mocked_set_weights_extrinsic + +@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) + return mock + + +@pytest.mark.parametrize( + "uids, weights, version_key, wait_for_inclusion, wait_for_finalization, expected_success, expected_message", + [ + ( + [1, 2], + [0.5, 0.5], + 0, + True, + False, + True, + "Successfully set weights and Finalized.", + ), + ( + [1, 2], + [0.5, 0.4], + 0, + False, + False, + True, + "Not waiting for finalization or inclusion.", + ), + ( + [1, 2], + [0.5, 0.5], + 0, + True, + False, + False, + "Mock error message", + ), + ], + ids=[ + "happy-flow", + "not-waiting-finalization-inclusion", + "error-flow", + ], +) +def test_set_weights_extrinsic( + mock_subtensor, + mock_wallet, + uids, + weights, + version_key, + wait_for_inclusion, + wait_for_finalization, + expected_success, + expected_message, +): + uids_tensor = torch.tensor(uids, dtype=torch.int64) + weights_tensor = torch.tensor(weights, dtype=torch.float32) + with patch( + "bittensor.utils.weight_utils.convert_weights_and_uids_for_emit", + return_value=(uids_tensor, weights_tensor), + ), patch( + "bittensor.core.extrinsics.set_weights._do_set_weights", + return_value=(expected_success, "Mock error message"), + ): + result, message = set_weights_extrinsic( + subtensor=mock_subtensor, + wallet=mock_wallet, + netuid=123, + uids=uids, + weights=weights, + version_key=version_key, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + + assert result == expected_success, f"Test {expected_message} failed." + assert message == expected_message, f"Test {expected_message} failed." + + +def test_do_set_weights_is_success(mock_subtensor, mocker): + """Successful _do_set_weights call.""" + # Prep + fake_wallet = mocker.MagicMock() + fake_uids = [1, 2, 3] + fake_vals = [4, 5, 6] + fake_netuid = 1 + fake_wait_for_inclusion = True + fake_wait_for_finalization = True + + mock_subtensor.substrate.submit_extrinsic.return_value.is_success = True # Call - result = set_weights.set_weights_extrinsic( - subtensor=fake_subtensor, + result = _do_set_weights( + subtensor=mock_subtensor, wallet=fake_wallet, - netuid=netuid, - uids=uids, - weights=weights, - version_key=version_key, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, + uids=fake_uids, + vals=fake_vals, + netuid=fake_netuid, + version_key=version_as_int, + wait_for_inclusion=fake_wait_for_inclusion, + wait_for_finalization=fake_wait_for_finalization, ) # Asserts - mocked_execute_coroutine.assert_called_once_with( - coroutine=mocked_set_weights_extrinsic.return_value, - event_loop=fake_subtensor.event_loop, + mock_subtensor.substrate.compose_call.assert_called_once_with( + call_module="SubtensorModule", + call_function="set_weights", + call_params={ + "dests": fake_uids, + "weights": fake_vals, + "netuid": fake_netuid, + "version_key": version_as_int, + }, ) - mocked_set_weights_extrinsic.assert_called_once_with( - subtensor=fake_subtensor.async_subtensor, + + mock_subtensor.substrate.create_signed_extrinsic.assert_called_once() + _, kwargs = mock_subtensor.substrate.create_signed_extrinsic.call_args + assert kwargs["call"] == mock_subtensor.substrate.compose_call.return_value + assert kwargs["keypair"] == fake_wallet.hotkey + assert kwargs["era"] == {"period": 5} + + assert result == (True, "Successfully set weights.") + + +def test_do_set_weights_is_not_success(mock_subtensor, mocker): + """Unsuccessful _do_set_weights call.""" + # Prep + fake_wallet = mocker.MagicMock() + fake_uids = [1, 2, 3] + fake_vals = [4, 5, 6] + fake_netuid = 1 + fake_wait_for_inclusion = True + fake_wait_for_finalization = True + + mock_subtensor.substrate.submit_extrinsic.return_value.is_success = False + mocked_format_error_message = mocker.MagicMock() + subtensor_module.format_error_message = mocked_format_error_message + + # Call + result = _do_set_weights( + subtensor=mock_subtensor, wallet=fake_wallet, - netuid=netuid, - uids=uids, - weights=weights, - version_key=version_key, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - assert result == mocked_execute_coroutine.return_value + uids=fake_uids, + vals=fake_vals, + netuid=fake_netuid, + version_key=version_as_int, + wait_for_inclusion=fake_wait_for_inclusion, + wait_for_finalization=fake_wait_for_finalization, + ) + + # Asserts + mock_subtensor.substrate.compose_call.assert_called_once_with( + call_module="SubtensorModule", + call_function="set_weights", + call_params={ + "dests": fake_uids, + "weights": fake_vals, + "netuid": fake_netuid, + "version_key": version_as_int, + }, + ) + + mock_subtensor.substrate.create_signed_extrinsic.assert_called_once() + _, kwargs = mock_subtensor.substrate.create_signed_extrinsic.call_args + assert kwargs["call"] == mock_subtensor.substrate.compose_call.return_value + assert kwargs["keypair"] == fake_wallet.hotkey + assert kwargs["era"] == {"period": 5} + + mock_subtensor.substrate.submit_extrinsic.assert_called_once_with( + extrinsic=mock_subtensor.substrate.create_signed_extrinsic.return_value, + wait_for_inclusion=fake_wait_for_inclusion, + wait_for_finalization=fake_wait_for_finalization, + ) + + assert result == ( + False, + "Subtensor returned `UnknownError(UnknownType)` error. This means: `Unknown Description`.", + ) + + +def test_do_set_weights_no_waits(mock_subtensor, mocker): + """Successful _do_set_weights call without wait flags for fake_wait_for_inclusion and fake_wait_for_finalization.""" + # Prep + fake_wallet = mocker.MagicMock() + fake_uids = [1, 2, 3] + fake_vals = [4, 5, 6] + fake_netuid = 1 + fake_wait_for_inclusion = False + fake_wait_for_finalization = False + + # Call + result = _do_set_weights( + subtensor=mock_subtensor, + wallet=fake_wallet, + uids=fake_uids, + vals=fake_vals, + netuid=fake_netuid, + version_key=version_as_int, + wait_for_inclusion=fake_wait_for_inclusion, + wait_for_finalization=fake_wait_for_finalization, + ) + + # Asserts + mock_subtensor.substrate.compose_call.assert_called_once_with( + call_module="SubtensorModule", + call_function="set_weights", + call_params={ + "dests": fake_uids, + "weights": fake_vals, + "netuid": fake_netuid, + "version_key": version_as_int, + }, + ) + + mock_subtensor.substrate.create_signed_extrinsic.assert_called_once() + _, kwargs = mock_subtensor.substrate.create_signed_extrinsic.call_args + assert kwargs["call"] == mock_subtensor.substrate.compose_call.return_value + assert kwargs["keypair"] == fake_wallet.hotkey + assert kwargs["era"] == {"period": 5} + + mock_subtensor.substrate.submit_extrinsic.assert_called_once_with( + extrinsic=mock_subtensor.substrate.create_signed_extrinsic.return_value, + wait_for_inclusion=fake_wait_for_inclusion, + wait_for_finalization=fake_wait_for_finalization, + ) + assert result == (True, "Not waiting for finalization or inclusion.") diff --git a/tests/unit_tests/extrinsics/test_staking.py b/tests/unit_tests/extrinsics/test_staking.py index d30d225ebd..b6fc9cb38f 100644 --- a/tests/unit_tests/extrinsics/test_staking.py +++ b/tests/unit_tests/extrinsics/test_staking.py @@ -1,20 +1,28 @@ from bittensor.core.extrinsics import staking +from bittensor.utils.balance import Balance def test_add_stake_extrinsic(mocker): """Verify that sync `add_stake_extrinsic` method calls proper async method.""" # Preps - fake_subtensor = mocker.Mock() - fake_wallet = mocker.Mock() + fake_subtensor = mocker.Mock( + **{ + "get_balance.return_value": Balance(10), + "get_existential_deposit.return_value": Balance(1), + "get_hotkey_owner.return_value": "hotkey_owner", + "sign_and_send_extrinsic.return_value": (True, ""), + } + ) + fake_wallet = mocker.Mock( + **{ + "coldkeypub.ss58_address": "hotkey_owner", + } + ) hotkey_ss58 = "hotkey" amount = 1.1 wait_for_inclusion = True wait_for_finalization = True - mocked_execute_coroutine = mocker.patch.object(staking, "execute_coroutine") - mocked_add_stake_extrinsic = mocker.Mock() - staking.async_add_stake_extrinsic = mocked_add_stake_extrinsic - # Call result = staking.add_stake_extrinsic( subtensor=fake_subtensor, @@ -26,35 +34,62 @@ def test_add_stake_extrinsic(mocker): ) # Asserts - mocked_execute_coroutine.assert_called_once_with( - coroutine=mocked_add_stake_extrinsic.return_value, - event_loop=fake_subtensor.event_loop, + assert result is True + + fake_subtensor.substrate.compose_call.assert_called_once_with( + call_module="SubtensorModule", + call_function="add_stake", + call_params={ + "hotkey": "hotkey", + "amount_staked": 9, + }, ) - mocked_add_stake_extrinsic.assert_called_once_with( - subtensor=fake_subtensor.async_subtensor, - wallet=fake_wallet, - hotkey_ss58=hotkey_ss58, - amount=amount, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, + fake_subtensor.sign_and_send_extrinsic.assert_called_once_with( + fake_subtensor.substrate.compose_call.return_value, + fake_wallet, + True, + True, ) - assert result == mocked_execute_coroutine.return_value def test_add_stake_multiple_extrinsic(mocker): """Verify that sync `add_stake_multiple_extrinsic` method calls proper async method.""" # Preps - fake_subtensor = mocker.Mock() - fake_wallet = mocker.Mock() + fake_subtensor = mocker.Mock( + **{ + "get_balance.return_value": Balance(10.0), + "sign_and_send_extrinsic.return_value": (True, ""), + "substrate.query_multi.return_value": [ + ( + mocker.Mock( + **{ + "params": ["hotkey1"], + }, + ), + 0, + ), + ( + mocker.Mock( + **{ + "params": ["hotkey2"], + }, + ), + 0, + ), + ], + "substrate.query.return_value": 0, + } + ) + fake_wallet = mocker.Mock( + **{ + "coldkeypub.ss58_address": "hotkey_owner", + } + ) hotkey_ss58s = ["hotkey1", "hotkey2"] amounts = [1.1, 2.2] wait_for_inclusion = True wait_for_finalization = True - mocked_execute_coroutine = mocker.patch.object(staking, "execute_coroutine") - mocked_add_stake_multiple_extrinsic = mocker.Mock() - staking.async_add_stake_multiple_extrinsic = mocked_add_stake_multiple_extrinsic - # Call result = staking.add_stake_multiple_extrinsic( subtensor=fake_subtensor, @@ -66,16 +101,29 @@ def test_add_stake_multiple_extrinsic(mocker): ) # Asserts - mocked_execute_coroutine.assert_called_once_with( - coroutine=mocked_add_stake_multiple_extrinsic.return_value, - event_loop=fake_subtensor.event_loop, + assert result is True + assert fake_subtensor.substrate.compose_call.call_count == 2 + assert fake_subtensor.sign_and_send_extrinsic.call_count == 2 + + fake_subtensor.substrate.compose_call.assert_any_call( + call_module="SubtensorModule", + call_function="add_stake", + call_params={ + "hotkey": "hotkey1", + "amount_staked": 1099999666, + }, ) - mocked_add_stake_multiple_extrinsic.assert_called_once_with( - subtensor=fake_subtensor.async_subtensor, - wallet=fake_wallet, - hotkey_ss58s=hotkey_ss58s, - amounts=amounts, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, + fake_subtensor.substrate.compose_call.assert_any_call( + call_module="SubtensorModule", + call_function="add_stake", + call_params={ + "hotkey": "hotkey2", + "amount_staked": 2199999333, + }, + ) + fake_subtensor.sign_and_send_extrinsic.assert_called_with( + fake_subtensor.substrate.compose_call.return_value, + fake_wallet, + True, + True, ) - assert result == mocked_execute_coroutine.return_value diff --git a/tests/unit_tests/extrinsics/test_transfer.py b/tests/unit_tests/extrinsics/test_transfer.py index f85e3f267e..607d703758 100644 --- a/tests/unit_tests/extrinsics/test_transfer.py +++ b/tests/unit_tests/extrinsics/test_transfer.py @@ -1,47 +1,147 @@ -from bittensor.core.extrinsics import transfer +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 -def test_transfer_extrinsic(mocker): - """Verify that sync `transfer_extrinsic` method calls proper async method.""" - # Preps - fake_subtensor = mocker.Mock() - fake_wallet = mocker.Mock() - dest = "hotkey" - amount = 1.1 - transfer_all = True - wait_for_inclusion = True - wait_for_finalization = True - keep_alive = False - mocked_execute_coroutine = mocker.patch.object(transfer, "execute_coroutine") - mocked_transfer_extrinsic = mocker.Mock() - transfer.async_transfer_extrinsic = mocked_transfer_extrinsic +@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 + fake_wallet = mocker.MagicMock() + fake_dest = "SS58PUBLICKEY" + fake_transfer_balance = Balance(1) + fake_wait_for_inclusion = True + fake_wait_for_finalization = True + + subtensor.substrate.submit_extrinsic.return_value.is_success = True + + # Call + result = _do_transfer( + subtensor, + fake_wallet, + fake_dest, + fake_transfer_balance, + fake_wait_for_inclusion, + fake_wait_for_finalization, + ) + + # Asserts + subtensor.substrate.compose_call.assert_called_once_with( + call_module="Balances", + call_function="transfer_allow_death", + call_params={"dest": fake_dest, "value": fake_transfer_balance.rao}, + ) + subtensor.substrate.create_signed_extrinsic.assert_called_once_with( + call=subtensor.substrate.compose_call.return_value, keypair=fake_wallet.coldkey + ) + subtensor.substrate.submit_extrinsic.assert_called_once_with( + extrinsic=subtensor.substrate.create_signed_extrinsic.return_value, + wait_for_inclusion=fake_wait_for_inclusion, + wait_for_finalization=fake_wait_for_finalization, + ) + # subtensor.substrate.submit_extrinsic.return_value.process_events.assert_called_once() + assert result == ( + True, + subtensor.substrate.submit_extrinsic.return_value.block_hash, + "Success with response.", + ) + + +def test_do_transfer_is_success_false(subtensor, mocker): + """Successful do_transfer call.""" + # Prep + fake_wallet = mocker.MagicMock() + fake_dest = "SS58PUBLICKEY" + fake_transfer_balance = Balance(1) + fake_wait_for_inclusion = True + fake_wait_for_finalization = True + + subtensor.substrate.submit_extrinsic.return_value.is_success = False + + mocked_format_error_message = mocker.Mock() + mocker.patch( + "bittensor.core.extrinsics.transfer.format_error_message", + mocked_format_error_message, + ) # Call - result = transfer.transfer_extrinsic( - subtensor=fake_subtensor, - wallet=fake_wallet, - dest=dest, - amount=amount, - transfer_all=transfer_all, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - keep_alive=keep_alive, + result = _do_transfer( + subtensor, + fake_wallet, + fake_dest, + fake_transfer_balance, + fake_wait_for_inclusion, + fake_wait_for_finalization, ) # Asserts - mocked_execute_coroutine.assert_called_once_with( - coroutine=mocked_transfer_extrinsic.return_value, - event_loop=fake_subtensor.event_loop, - ) - mocked_transfer_extrinsic.assert_called_once_with( - subtensor=fake_subtensor.async_subtensor, - wallet=fake_wallet, - destination=dest, - amount=amount, - transfer_all=transfer_all, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - keep_alive=keep_alive, - ) - assert result == mocked_execute_coroutine.return_value + subtensor.substrate.compose_call.assert_called_once_with( + call_module="Balances", + call_function="transfer_allow_death", + call_params={"dest": fake_dest, "value": fake_transfer_balance.rao}, + ) + subtensor.substrate.create_signed_extrinsic.assert_called_once_with( + call=subtensor.substrate.compose_call.return_value, keypair=fake_wallet.coldkey + ) + subtensor.substrate.submit_extrinsic.assert_called_once_with( + extrinsic=subtensor.substrate.create_signed_extrinsic.return_value, + wait_for_inclusion=fake_wait_for_inclusion, + wait_for_finalization=fake_wait_for_finalization, + ) + mocked_format_error_message.assert_called_once_with( + subtensor.substrate.submit_extrinsic.return_value.error_message + ) + + assert result == ( + False, + "", + mocked_format_error_message.return_value, + ) + + +def test_do_transfer_no_waits(subtensor, mocker): + """Successful do_transfer call.""" + # Prep + fake_wallet = mocker.MagicMock() + fake_dest = "SS58PUBLICKEY" + fake_transfer_balance = Balance(1) + fake_wait_for_inclusion = False + fake_wait_for_finalization = False + + # Call + result = _do_transfer( + subtensor, + fake_wallet, + fake_dest, + fake_transfer_balance, + fake_wait_for_inclusion, + fake_wait_for_finalization, + ) + + # Asserts + subtensor.substrate.compose_call.assert_called_once_with( + call_module="Balances", + call_function="transfer_allow_death", + call_params={"dest": fake_dest, "value": fake_transfer_balance.rao}, + ) + subtensor.substrate.create_signed_extrinsic.assert_called_once_with( + call=subtensor.substrate.compose_call.return_value, keypair=fake_wallet.coldkey + ) + subtensor.substrate.submit_extrinsic.assert_called_once_with( + extrinsic=subtensor.substrate.create_signed_extrinsic.return_value, + wait_for_inclusion=fake_wait_for_inclusion, + wait_for_finalization=fake_wait_for_finalization, + ) + assert result == (True, "", "Success, extrinsic submitted without waiting.") diff --git a/tests/unit_tests/extrinsics/test_unstaking.py b/tests/unit_tests/extrinsics/test_unstaking.py index afd3c23e76..e8c84c3612 100644 --- a/tests/unit_tests/extrinsics/test_unstaking.py +++ b/tests/unit_tests/extrinsics/test_unstaking.py @@ -1,20 +1,23 @@ from bittensor.core.extrinsics import unstaking +from bittensor.utils.balance import Balance def test_unstake_extrinsic(mocker): - """Verify that sync `unstake_extrinsic` method calls proper async method.""" # Preps - fake_subtensor = mocker.Mock() + fake_subtensor = mocker.Mock( + **{ + "get_hotkey_owner.return_value": "hotkey_owner", + "get_stake_for_coldkey_and_hotkey.return_value": Balance(10.0), + "sign_and_send_extrinsic.return_value": (True, ""), + } + ) fake_wallet = mocker.Mock() + fake_wallet.coldkeypub.ss58_address = "hotkey_owner" hotkey_ss58 = "hotkey" amount = 1.1 wait_for_inclusion = True wait_for_finalization = True - mocked_execute_coroutine = mocker.patch.object(unstaking, "execute_coroutine") - mocked_unstake_extrinsic = mocker.Mock() - unstaking.async_unstake_extrinsic = mocked_unstake_extrinsic - # Call result = unstaking.unstake_extrinsic( subtensor=fake_subtensor, @@ -26,35 +29,42 @@ def test_unstake_extrinsic(mocker): ) # Asserts - mocked_execute_coroutine.assert_called_once_with( - coroutine=mocked_unstake_extrinsic.return_value, - event_loop=fake_subtensor.event_loop, + assert result is True + + fake_subtensor.substrate.compose_call.assert_called_once_with( + call_module="SubtensorModule", + call_function="remove_stake", + call_params={ + "hotkey": "hotkey", + "amount_unstaked": 1100000000, + }, ) - mocked_unstake_extrinsic.assert_called_once_with( - subtensor=fake_subtensor.async_subtensor, - wallet=fake_wallet, - hotkey_ss58=hotkey_ss58, - amount=amount, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, + fake_subtensor.sign_and_send_extrinsic.assert_called_once_with( + fake_subtensor.substrate.compose_call.return_value, + fake_wallet, + True, + True, ) - assert result == mocked_execute_coroutine.return_value def test_unstake_multiple_extrinsic(mocker): """Verify that sync `unstake_multiple_extrinsic` method calls proper async method.""" # Preps - fake_subtensor = mocker.Mock() + fake_subtensor = mocker.Mock( + **{ + "get_hotkey_owner.return_value": "hotkey_owner", + "get_stake_for_coldkey_and_hotkey.return_value": Balance(10.0), + "sign_and_send_extrinsic.return_value": (True, ""), + "tx_rate_limit.return_value": 0, + } + ) fake_wallet = mocker.Mock() + fake_wallet.coldkeypub.ss58_address = "hotkey_owner" hotkey_ss58s = ["hotkey1", "hotkey2"] amounts = [1.1, 1.2] wait_for_inclusion = True wait_for_finalization = True - mocked_execute_coroutine = mocker.patch.object(unstaking, "execute_coroutine") - mocked_unstake_multiple_extrinsic = mocker.Mock() - unstaking.async_unstake_multiple_extrinsic = mocked_unstake_multiple_extrinsic - # Call result = unstaking.unstake_multiple_extrinsic( subtensor=fake_subtensor, @@ -66,16 +76,29 @@ def test_unstake_multiple_extrinsic(mocker): ) # Asserts - mocked_execute_coroutine.assert_called_once_with( - coroutine=mocked_unstake_multiple_extrinsic.return_value, - event_loop=fake_subtensor.event_loop, + assert result is True + assert fake_subtensor.substrate.compose_call.call_count == 2 + assert fake_subtensor.sign_and_send_extrinsic.call_count == 2 + + fake_subtensor.substrate.compose_call.assert_any_call( + call_module="SubtensorModule", + call_function="remove_stake", + call_params={ + "hotkey": "hotkey1", + "amount_unstaked": 1100000000, + }, ) - mocked_unstake_multiple_extrinsic.assert_called_once_with( - subtensor=fake_subtensor.async_subtensor, - wallet=fake_wallet, - hotkey_ss58s=hotkey_ss58s, - amounts=amounts, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, + fake_subtensor.substrate.compose_call.assert_any_call( + call_module="SubtensorModule", + call_function="remove_stake", + call_params={ + "hotkey": "hotkey2", + "amount_unstaked": 1200000000, + }, + ) + fake_subtensor.sign_and_send_extrinsic.assert_called_with( + fake_subtensor.substrate.compose_call.return_value, + fake_wallet, + True, + True, ) - assert result == mocked_execute_coroutine.return_value