diff --git a/MIGRATION.md b/MIGRATION.md index 44e7e72e2d..05a9ae2618 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -68,7 +68,7 @@ 13. ✅ Remove `Default is` and `Default to` in docstrings bc parameters enough. 14. ✅ `camfairchild`: TODO, but we should have a grab_metadata if we don't already. Maybe don't decode, but can have a call that removes the Raw prefix, and another just doing grab_metadata_raw (no decoding). `get_commitment_metadata` added. 15. ✅ Resolve an issue where a script using the SDK receives the `--config` or any other CLI parameters used in the SDK. Disable configuration processing. Use default values ​​instead. -16. Find and process all `TODOs` across the entire code base. If in doubt, discuss each one with the team separately. SDK has 29 TODOs. +16. ✅ Find and process all `TODOs` across the entire code base. If in doubt, discuss each one with the team separately. SDK has 29 TODOs. ## New features 1. ✅ Unify extrinsic return values by introducing an ExtrinsicResponse class. Extrinsics currently return either a boolean or a tuple. @@ -274,6 +274,11 @@ Added sub-package `bittensor.core.addons` to host optional extensions and experi - local env variable `BT_CHAIN_ENDPOINT` replaced with `BT_SUBTENSOR_CHAIN_ENDPOINT`. +## Metagraph changes: +- all methods with `mechid` parameter has reordered list of parameters. +- async `_initialize_subtensor` method no longer kill the subtensor instance after use. + + ### Mechid related changes: In the next subtensor methods got updated the parameters order: - `bonds` diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 75a4fb7588..a02d3475fd 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -279,24 +279,7 @@ async def initialize(self): raise ConnectionError async def __aenter__(self): - logging.info( - f"[magenta]Connecting to Substrate:[/magenta] [blue]{self}[/blue][magenta]...[/magenta]" - ) - try: - await self.substrate.initialize() - return self - except TimeoutError: - logging.error( - f"[red]Error[/red]: Timeout occurred connecting to substrate." - f" Verify your chain and network settings: {self}" - ) - raise ConnectionError - except (ConnectionRefusedError, ssl.SSLError) as error: - logging.error( - f"[red]Error[/red]: Connection refused when connecting to substrate. " - f"Verify your chain and network settings: {self}. Error: {error}" - ) - raise ConnectionError + return await self.initialize() async def __aexit__(self, exc_type, exc_val, exc_tb): await self.substrate.close() @@ -3908,12 +3891,12 @@ async def metagraph( decentralized architecture, particularly in relation to neuron interconnectivity and consensus processes. """ metagraph = AsyncMetagraph( - network=self.chain_endpoint, netuid=netuid, + mechid=mechid, + network=self.chain_endpoint, lite=lite, sync=False, subtensor=self, - mechid=mechid, ) await metagraph.sync(block=block, lite=lite, subtensor=self) @@ -4379,7 +4362,6 @@ async def weights( """ storage_index = get_mechid_storage_index(netuid, mechid) block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - # TODO look into seeing if we can speed this up with storage query w_map_encoded = await self.substrate.query_map( module="SubtensorModule", storage_function="Weights", @@ -5647,7 +5629,7 @@ async def _blocks_weight_limit() -> bool: and await _blocks_weight_limit() ): logging.debug( - f"Committing weights for subnet [blue]{netuid}[/blue]. " + f"Committing weights {weights} for subnet [blue]{netuid}[/blue]. " f"Attempt [blue]{retries + 1}[blue] of [green]{max_retries}[/green]." ) try: diff --git a/bittensor/core/metagraph.py b/bittensor/core/metagraph.py index 15f8080107..268659e7e5 100644 --- a/bittensor/core/metagraph.py +++ b/bittensor/core/metagraph.py @@ -526,11 +526,11 @@ def addresses(self) -> list[str]: def __init__( self, netuid: int, + mechid: int = 0, network: str = settings.DEFAULT_NETWORK, lite: bool = True, sync: bool = True, subtensor: Optional[Union["AsyncSubtensor", "Subtensor"]] = None, - mechid: int = 0, ): """ Initializes a new instance of the metagraph object, setting up the basic structure and parameters based on the @@ -671,7 +671,6 @@ def _create_tensor(data, dtype) -> Tensor: self.stake = self._create_tensor(neuron_stakes, dtype=np.float32) """ - # TODO: Check and test the creation of tensor return ( torch.nn.Parameter(torch.tensor(data, dtype=dtype), requires_grad=False) if use_torch() @@ -706,7 +705,6 @@ def _process_weights_or_bonds(self, data, attribute: str) -> Tensor: data_array.append(np.zeros(len(self.neurons), dtype=np.float32)) else: uids, values = zip(*item) - # TODO: Validate and test the conversion of uids and values to tensor if attribute == "weights": data_array.append( convert_weight_uids_and_vals_to_tensor( @@ -1033,11 +1031,11 @@ class TorchMetagraph(MetagraphMixin, BaseClass): def __init__( self, netuid: int, + mechid: int = 0, network: str = settings.DEFAULT_NETWORK, lite: bool = True, sync: bool = True, subtensor: Optional[Union["AsyncSubtensor", "Subtensor"]] = None, - mechid: int = 0, ): """ Initializes a new instance of the metagraph object, setting up the basic structure and parameters based on the @@ -1199,11 +1197,11 @@ class NonTorchMetagraph(MetagraphMixin): def __init__( self, netuid: int, + mechid: int = 0, network: str = settings.DEFAULT_NETWORK, lite: bool = True, sync: bool = True, subtensor: Optional[Union["AsyncSubtensor", "Subtensor"]] = None, - mechid: int = 0, ): """ Initializes a new instance of the metagraph object, setting up the basic structure and parameters based on the @@ -1227,7 +1225,7 @@ def __init__( metagraph = Metagraph(netuid=123, network="finney", lite=True, sync=True) """ - MetagraphMixin.__init__(self, netuid, network, lite, sync, subtensor, mechid) + MetagraphMixin.__init__(self, netuid, mechid, network, lite, sync, subtensor) self.netuid = netuid self.network, self.chain_endpoint = determine_chain_endpoint_and_network( @@ -1330,19 +1328,29 @@ def load_from_path(self, dir_path: str) -> "MetagraphMixin": class AsyncMetagraph(NumpyOrTorch): """ - TODO docstring. Advise user to use `async_metagraph` factory fn if they want to sync at init + Asynchronous version of the Metagraph class for non-blocking synchronization with the Bittensor network state. + + This class allows developers to fetch and update metagraph data using async operations, enabling concurrent + execution in event-driven environments. + + Note: + Prefer using the factory function `async_metagraph()` for initialization, which handles async synchronization + automatically. + + Example: + metagraph = await async_metagraph(netuid=1, network="finney") """ def __init__( self, netuid: int, + mechid: int = 0, network: str = settings.DEFAULT_NETWORK, lite: bool = True, sync: bool = True, subtensor: Optional["AsyncSubtensor"] = None, - mechid: int = 0, ): - super().__init__(netuid, network, lite, sync, subtensor, mechid) + super().__init__(netuid, mechid, network, lite, sync, subtensor) async def __aenter__(self): if self.should_sync: @@ -1467,8 +1475,9 @@ async def _initialize_subtensor( # Lazy import due to circular import (subtensor -> metagraph, metagraph -> subtensor) from bittensor.core.async_subtensor import AsyncSubtensor - async with AsyncSubtensor(network=self.chain_endpoint) as subtensor: - self.subtensor = subtensor + self.subtensor = AsyncSubtensor(network=self.chain_endpoint) + await self.subtensor.initialize() + self.subtensor = subtensor return subtensor async def _assign_neurons( @@ -1518,7 +1527,6 @@ async def _set_weights_and_bonds(self, subtensor: "AsyncSubtensor", block: int): self._set_weights_and_bonds(subtensor=subtensor) """ - # TODO: Check and test the computation of weights and bonds if self.netuid == 0: self.weights = await self._process_root_weights( [neuron.weights for neuron in self.neurons], @@ -1568,7 +1576,6 @@ async def _process_root_weights( data_array.append(np.zeros(n_subnets, dtype=np.float32)) else: uids, values = zip(*item) - # TODO: Validate and test the conversion of uids and values to tensor data_array.append( convert_root_weight_uids_and_vals_to_tensor( n_subnets, list(uids), list(values), subnets @@ -1656,16 +1663,28 @@ async def _apply_extra_info(self, block: int): class Metagraph(NumpyOrTorch): + """ + Synchronous implementation of the Metagraph, representing the current state of a Bittensor subnet. + + The Metagraph encapsulates neuron attributes such as stake, trust, incentive, weights, and connectivity, and + provides methods to synchronize these values directly from the blockchain via a Subtensor instance. + + Example: + from bittensor.core.subtensor import Subtensor + subtensor = Subtensor(network="finney") + metagraph = Metagraph(netuid=1, network="finney", sync=True, subtensor=subtensor) + """ + def __init__( self, netuid: int, + mechid: int = 0, network: str = settings.DEFAULT_NETWORK, lite: bool = True, sync: bool = True, subtensor: Optional["Subtensor"] = None, - mechid: int = 0, ): - super().__init__(netuid, network, lite, sync, subtensor, mechid) + super().__init__(netuid, mechid, network, lite, sync, subtensor) if self.should_sync: self.sync() @@ -1877,7 +1896,6 @@ def _process_root_weights( data_array.append(np.zeros(n_subnets, dtype=np.float32)) else: uids, values = zip(*item) - # TODO: Validate and test the conversion of uids and values to tensor data_array.append( convert_root_weight_uids_and_vals_to_tensor( n_subnets, list(uids), list(values), subnets @@ -1966,6 +1984,7 @@ def _apply_extra_info(self, block: int): async def async_metagraph( netuid: int, + mechid: int = 0, network: str = settings.DEFAULT_NETWORK, lite: bool = True, sync: bool = True, @@ -1975,7 +1994,12 @@ async def async_metagraph( Factory function to create an instantiated AsyncMetagraph, mainly for the ability to use sync at instantiation. """ metagraph_ = AsyncMetagraph( - netuid=netuid, network=network, lite=lite, sync=sync, subtensor=subtensor + netuid=netuid, + mechid=mechid, + network=network, + lite=lite, + sync=sync, + subtensor=subtensor, ) if sync: await metagraph_.sync() diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index a6e0087ca9..af0b7757b6 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -1693,11 +1693,11 @@ def get_mechanism_count( def get_metagraph_info( self, netuid: int, + mechid: int = 0, selected_indices: Optional[ Union[list[SelectiveMetagraphIndex], list[int]] ] = None, block: Optional[int] = None, - mechid: int = 0, ) -> Optional[MetagraphInfo]: """ Retrieves full or partial metagraph information for the specified subnet mechanism (netuid, mechid). @@ -2868,12 +2868,12 @@ def metagraph( decentralized architecture, particularly in relation to neuron interconnectivity and consensus processes. """ metagraph = Metagraph( - network=self.chain_endpoint, netuid=netuid, + mechid=mechid, + network=self.chain_endpoint, lite=lite, sync=False, subtensor=self, - mechid=mechid, ) metagraph.sync(block=block, lite=lite, subtensor=self) @@ -4428,7 +4428,7 @@ def _blocks_weight_limit() -> bool: and _blocks_weight_limit() ): logging.debug( - f"Committing weights for subnet [blue]{netuid}[/blue]. " + f"Committing weights {weights} for subnet [blue]{netuid}[/blue]. " f"Attempt [blue]{retries + 1}[blue] of [green]{max_retries}[/green]." ) try: diff --git a/bittensor/core/types.py b/bittensor/core/types.py index 98ec97922f..46ff6585a2 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -58,7 +58,7 @@ def _check_and_log_network_settings(self): "This increases decentralization and resilience of the network." ) - @staticmethod # TODO can this be a class method? + @staticmethod def config() -> "Config": """ Creates and returns a Bittensor configuration object. @@ -72,7 +72,7 @@ def config() -> "Config": return Config(parser) @staticmethod - def setup_config(network: Optional[str], config: "Config"): + def setup_config(network: Optional[str], config: "Config") -> tuple[str, str]: """ Sets up and returns the configuration for the Subtensor network and endpoint. diff --git a/bittensor/extras/dev_framework/subnet.py b/bittensor/extras/dev_framework/subnet.py index c68d48a005..7e248ae39e 100644 --- a/bittensor/extras/dev_framework/subnet.py +++ b/bittensor/extras/dev_framework/subnet.py @@ -47,16 +47,21 @@ def __init__( self.wait_for_finalization = wait_for_finalization self._netuid: Optional[int] = None + self._owner: Optional[Wallet] = None self._calls: list[CALL_RECORD] = [] @property - def calls(self): + def calls(self) -> list[CALL_RECORD]: return self._calls @property - def netuid(self): + def netuid(self) -> int: return self._netuid + @property + def owner(self) -> Wallet: + return self._owner + def execute_steps(self, steps: list[Union[STEPS, tuple]]): """Executes a multiple steps synchronously.""" for step in steps: @@ -214,6 +219,7 @@ def _register_subnet( else: self._netuid = self.s.subnets.get_total_subnets() - 1 if response.success: + self._owner = owner_wallet logging.console.info(f"Subnet [blue]{self._netuid}[/blue] was registered.") self._add_call_record(REGISTER_SUBNET.__name__, response) return response @@ -243,6 +249,7 @@ async def _async_register_subnet( else: self._netuid = self.s.subnets.get_total_subnets() - 1 if response.success: + self._owner = owner_wallet logging.console.info(f"Subnet [blue]{self._netuid}[/blue] was registered.") self._add_call_record(REGISTER_SUBNET.__name__, response) return response diff --git a/bittensor/utils/mock/subtensor_mock.py b/bittensor/utils/mock/subtensor_mock.py index 69ed181d7e..4fd1277975 100644 --- a/bittensor/utils/mock/subtensor_mock.py +++ b/bittensor/utils/mock/subtensor_mock.py @@ -976,7 +976,6 @@ def neuron_for_uid_lite( neuron_info = self._neuron_subnet_exists(uid, netuid, block) if neuron_info is None: - # TODO Why does this return None here but a null neuron earlier? return None else: diff --git a/tests/e2e_tests/test_hotkeys.py b/tests/e2e_tests/test_hotkeys.py index 0154c0936d..cc006ea8fe 100644 --- a/tests/e2e_tests/test_hotkeys.py +++ b/tests/e2e_tests/test_hotkeys.py @@ -5,7 +5,6 @@ RegistrationNotPermittedOnRootSubnet, SubnetNotExists, InvalidChild, - TooManyChildren, ProportionOverflow, DuplicateChild, TxRateLimitExceeded, @@ -227,7 +226,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): ) with pytest.raises(DuplicateChild): - subtensor.set_children( + subtensor.wallets.set_children( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_sn.netuid, @@ -532,7 +531,7 @@ async def test_children_async(async_subtensor, alice_wallet, bob_wallet, dave_wa raise_error=True, ) - with pytest.raises(TooManyChildren): + with pytest.raises(DuplicateChild): await async_subtensor.extrinsics.set_children( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, diff --git a/tests/e2e_tests/test_metagraph.py b/tests/e2e_tests/test_metagraph.py index 4a82aa4b4c..e35f629483 100644 --- a/tests/e2e_tests/test_metagraph.py +++ b/tests/e2e_tests/test_metagraph.py @@ -1,24 +1,35 @@ import os.path -import re import shutil import time - +import numpy as np import pytest from bittensor.core.chain_data import SelectiveMetagraphIndex from bittensor.core.chain_data.metagraph_info import MetagraphInfo +from bittensor.extras.dev_framework import ( + SUDO_SET_WEIGHTS_SET_RATE_LIMIT, + SUDO_SET_TEMPO, +) from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging +from bittensor.utils.registration.pow import LazyLoadedTorch +from bittensor.utils.weight_utils import convert_and_normalize_weights_and_uids from tests.e2e_tests.utils import ( + AdminUtils, + NETUID, TestSubnet, ACTIVATE_SUBNET, REGISTER_SUBNET, REGISTER_NEURON, + SUDO_SET_ADMIN_FREEZE_WINDOW, ) NULL_KEY = tuple(bytearray(32)) +torch = LazyLoadedTorch() + + def neuron_to_dict(neuron): """ Convert a neuron object to a dictionary, excluding private attributes, methods, and specific fields. @@ -182,8 +193,6 @@ def test_metagraph(subtensor, alice_wallet, bob_wallet, dave_wallet): "Neurons don't match after save and load" ) - logging.console.info("✅ Passed [blue]test_metagraph[/blue]") - @pytest.mark.asyncio async def test_metagraph_async(async_subtensor, alice_wallet, bob_wallet, dave_wallet): @@ -338,7 +347,252 @@ async def test_metagraph_async(async_subtensor, alice_wallet, bob_wallet, dave_w "Neurons don't match after save and load" ) - logging.console.info("✅ Passed [blue]test_metagraph_async[/blue]") + +def test_metagraph_weights_bonds( + subtensor, alice_wallet, bob_wallet, charlie_wallet, dave_wallet +): + """ + Tests that weights and bonds matrices are computed correctly when the metagraph is initialized with lite=False. + + Test: + - Disable the admin freeze window (set to 0). + - Register a new subnet owned by Bob. + - Update subnet tempo to a custom value. + - Activate the subnet. + - Register Charlie and Dave as new neurons in the subnet. + - Disable weights rate limit (set_weights_rate_limit = 0). + - Set weights for Charlie and Dave (20% / 80%) using Bob. + - Wait for commit-reveal completion and ensure version is 4. + - Initialize the metagraph with lite=False. + - Verify: + - Shape and consistency of weights and bonds tensors. + - Validator (Alice) has non-zero outgoing weights. + - Miners have zero rows (no outgoing weights). + - All bonds are non-negative. + - Validator weight rows are normalized to 1. + """ + logging.set_debug() + TEMPO_TO_SET, BLOCK_TIME = ( + (100, 0.25) if subtensor.chain.is_fast_blocks() else (20, 12) + ) + + bob_sn = TestSubnet(subtensor) + bob_sn.execute_steps( + [ + SUDO_SET_ADMIN_FREEZE_WINDOW(alice_wallet, AdminUtils, True, 0), + REGISTER_SUBNET(bob_wallet), + SUDO_SET_TEMPO(alice_wallet, AdminUtils, True, NETUID, TEMPO_TO_SET), + ACTIVATE_SUBNET(bob_wallet), + REGISTER_NEURON(charlie_wallet), + REGISTER_NEURON(dave_wallet), + SUDO_SET_WEIGHTS_SET_RATE_LIMIT(alice_wallet, AdminUtils, True, NETUID, 0), + ] + ) + + # wait before CRv4 works in new subnets + bob_sn.wait_next_epoch() + bob_sn.wait_next_epoch() + + cr_version = subtensor.substrate.query( + module="SubtensorModule", storage_function="CommitRevealWeightsVersion" + ) + assert cr_version == 4, "Commit reveal weights version is not 4" + assert subtensor.subnets.weights_rate_limit(netuid=bob_sn.netuid) == 0 + tempo = subtensor.subnets.get_subnet_hyperparameters(netuid=bob_sn.netuid).tempo + assert tempo == TEMPO_TO_SET, "SN tempos has not been changed." + + metagraph = subtensor.metagraphs.metagraph(netuid=bob_sn.netuid, lite=False) + + # Check that the metagraph is instantiated correctly. + assert metagraph.weights.shape == (metagraph.n.item(), metagraph.n.item()) + assert metagraph.bonds.shape == (metagraph.n.item(), metagraph.n.item()) + + uids = [1, 2] + weights = [20, 80] + + response = subtensor.extrinsics.set_weights( + wallet=bob_wallet, + netuid=bob_sn.netuid, + uids=uids, + weights=weights, + block_time=BLOCK_TIME, + ) + logging.console.info(f"Response: {response}") + + assert response.success, response.message + + expected_reveal_round = response.data.get("reveal_round") + last_drand_round = subtensor.chain.last_drand_round() + + while expected_reveal_round > last_drand_round + 24: # drand offset for fast blocks + last_drand_round = subtensor.chain.last_drand_round() + subtensor.wait_for_block() + logging.console.debug( + f"expected_reveal_round: {expected_reveal_round}, last_drand_round: {last_drand_round}" + ) + + counter = TEMPO_TO_SET + while True: + weights = subtensor.subnets.weights(bob_sn.netuid) + counter -= 1 + + if weights or counter == 0: + break + + subtensor.wait_for_block() + logging.console.debug(f"Weights: {weights}, block: {subtensor.block}") + + metagraph.sync() + + # Ensure the validator has at least one non-zero weight + assert metagraph.weights[0].sum().item() > 0.0, "Validator has no outgoing weights." + + # Ensure miner rows are all zeros (miners don't set weights) + if metagraph.n.item() > 1: + assert np.allclose( + metagraph.weights[1], + np.zeros_like(metagraph.weights[1]), + ), "Miner row should be all zeros" + + # Ensure bond matrix contains no negative values + assert (metagraph.bonds >= 0).all(), "Bond matrix contains negative values" + + # Ensure validator weight rows are normalized to 1 + row_sums = metagraph.weights.sum(axis=1) + validator_mask = metagraph.validator_permit + assert np.allclose( + row_sums[validator_mask], + np.ones_like(row_sums[validator_mask]), + atol=1e-6, + ), "Validator weight rows are not normalized to 1" + + +@pytest.mark.asyncio +async def test_metagraph_weights_bonds_async( + async_subtensor, alice_wallet, bob_wallet, charlie_wallet, dave_wallet +): + """ + Tests that weights and bonds matrices are computed correctly when the metagraph is initialized with lite=False. + + Test: + - Disable the admin freeze window (set to 0). + - Register a new subnet owned by Bob. + - Update subnet tempo to a custom value. + - Activate the subnet. + - Register Charlie and Dave as new neurons in the subnet. + - Disable weights rate limit (set_weights_rate_limit = 0). + - Set weights for Charlie and Dave (20% / 80%) using Bob. + - Wait for commit-reveal completion and ensure version is 4. + - Initialize the metagraph with lite=False. + - Verify: + - Shape and consistency of weights and bonds tensors. + - Validator (Alice) has non-zero outgoing weights. + - Miners have zero rows (no outgoing weights). + - All bonds are non-negative. + - Validator weight rows are normalized to 1. + """ + logging.set_debug() + TEMPO_TO_SET, BLOCK_TIME = ( + (100, 0.25) if await async_subtensor.chain.is_fast_blocks() else (20, 12) + ) + + bob_sn = TestSubnet(async_subtensor) + await bob_sn.async_execute_steps( + [ + SUDO_SET_ADMIN_FREEZE_WINDOW(alice_wallet, AdminUtils, True, 0), + REGISTER_SUBNET(bob_wallet), + SUDO_SET_TEMPO(alice_wallet, AdminUtils, True, NETUID, TEMPO_TO_SET), + ACTIVATE_SUBNET(bob_wallet), + REGISTER_NEURON(charlie_wallet), + REGISTER_NEURON(dave_wallet), + SUDO_SET_WEIGHTS_SET_RATE_LIMIT(alice_wallet, AdminUtils, True, NETUID, 0), + ] + ) + + # wait before CRv4 works in new subnets + await bob_sn.async_wait_next_epoch() + await bob_sn.async_wait_next_epoch() + + cr_version = await async_subtensor.substrate.query( + module="SubtensorModule", storage_function="CommitRevealWeightsVersion" + ) + assert cr_version == 4, "Commit reveal weights version is not 4" + assert await async_subtensor.subnets.weights_rate_limit(netuid=bob_sn.netuid) == 0 + tempo = ( + await async_subtensor.subnets.get_subnet_hyperparameters(netuid=bob_sn.netuid) + ).tempo + assert tempo == TEMPO_TO_SET, "SN tempos has not been changed." + + metagraph = await async_subtensor.metagraphs.metagraph( + netuid=bob_sn.netuid, lite=False + ) + + # Check that the metagraph is instantiated correctly. + assert metagraph.weights.shape == (metagraph.n.item(), metagraph.n.item()) + assert metagraph.bonds.shape == (metagraph.n.item(), metagraph.n.item()) + + uids = [1, 2] + weights = [20, 80] + + response = await async_subtensor.extrinsics.set_weights( + wallet=bob_wallet, + netuid=bob_sn.netuid, + uids=uids, + weights=weights, + block_time=BLOCK_TIME, + period=TEMPO_TO_SET, + wait_for_finalization=False, + ) + logging.console.info(f"Response: {response}") + + assert response.success, response.message + + expected_reveal_round = response.data.get("reveal_round") + last_drand_round = await async_subtensor.chain.last_drand_round() + + while expected_reveal_round > last_drand_round + 24: # drand offset for fast blocks + last_drand_round = await async_subtensor.chain.last_drand_round() + await async_subtensor.wait_for_block() + logging.console.debug( + f"expected_reveal_round: {expected_reveal_round}, last_drand_round: {last_drand_round}" + ) + + counter = TEMPO_TO_SET + while True: + weights = await async_subtensor.subnets.weights(bob_sn.netuid) + counter -= 1 + + if weights or counter == 0: + break + + await async_subtensor.wait_for_block() + logging.console.debug( + f"Weights: {weights}, block: {await async_subtensor.block}" + ) + + await metagraph.sync() + + # Ensure the validator has at least one non-zero weight + assert metagraph.weights[0].sum().item() > 0.0, "Validator has no outgoing weights." + + # Ensure miner rows are all zeros (miners don't set weights) + if metagraph.n.item() > 1: + assert np.allclose( + metagraph.weights[1], + np.zeros_like(metagraph.weights[1]), + ), "Miner row should be all zeros" + + # Ensure bond matrix contains no negative values + assert (metagraph.bonds >= 0).all(), "Bond matrix contains negative values" + + # Ensure validator weight rows are normalized to 1 + row_sums = metagraph.weights.sum(axis=1) + validator_mask = metagraph.validator_permit + assert np.allclose( + row_sums[validator_mask], + np.ones_like(row_sums[validator_mask]), + atol=1e-6, + ), "Validator weight rows are not normalized to 1" def test_metagraph_info(subtensor, alice_wallet, bob_wallet): @@ -586,8 +840,6 @@ def test_metagraph_info(subtensor, alice_wallet, bob_wallet): assert metagraph_info is None - logging.console.info("✅ Passed [blue]test_metagraph_info[/blue]") - @pytest.mark.asyncio async def test_metagraph_info_async(async_subtensor, alice_wallet, bob_wallet): @@ -843,8 +1095,6 @@ async def test_metagraph_info_async(async_subtensor, alice_wallet, bob_wallet): assert metagraph_info is None - logging.console.info("✅ Passed [blue]test_metagraph_info_async[/blue]") - def test_metagraph_info_with_indexes(subtensor, alice_wallet, bob_wallet): """ @@ -1071,8 +1321,6 @@ def test_metagraph_info_with_indexes(subtensor, alice_wallet, bob_wallet): commitments=None, ) - logging.console.info("✅ Passed [blue]test_metagraph_info_with_indexes[/blue]") - @pytest.mark.asyncio async def test_metagraph_info_with_indexes_async( @@ -1303,7 +1551,3 @@ async def test_metagraph_info_with_indexes_async( validators=None, commitments=None, ) - - logging.console.info( - "✅ Passed [blue]test_metagraph_info_with_indexes_async[/blue]" - ) diff --git a/tests/e2e_tests/test_root_set_weights.py b/tests/e2e_tests/test_root_set_weights.py index d33d3c5a81..a67053855c 100644 --- a/tests/e2e_tests/test_root_set_weights.py +++ b/tests/e2e_tests/test_root_set_weights.py @@ -2,6 +2,7 @@ import pytest +from bittensor.extras.dev_framework import REGISTER_NEURON from bittensor.utils.balance import Balance from tests.e2e_tests.utils import ( TestSubnet, @@ -107,7 +108,7 @@ async def test_root_reg_hyperparams(subtensor, templates, alice_wallet, bob_wall assert block_since_update is not None # Use subnetwork_n hyperparam to check sn creation - assert subtensor.subnets.subnetwork_n(alice_sn.netuid) == 1 # TODO? + assert subtensor.subnets.subnetwork_n(alice_sn.netuid) == 1 assert subtensor.subnets.subnetwork_n(alice_sn.netuid + 1) is None # Ensure correct hyperparams are being fetched regarding weights @@ -138,6 +139,9 @@ async def test_root_reg_hyperparams(subtensor, templates, alice_wallet, bob_wall assert sn_one_neurons[alice_uid_sn_2].hotkey == alice_wallet.hotkey.ss58_address assert sn_one_neurons[alice_uid_sn_2].validator_permit is True + alice_sn.execute_one(REGISTER_NEURON(bob_wallet)) + assert subtensor.subnets.subnetwork_n(alice_sn.netuid) == 2 + @pytest.mark.asyncio async def test_root_reg_hyperparams_async( @@ -215,7 +219,7 @@ async def test_root_reg_hyperparams_async( assert block_since_update is not None # Use subnetwork_n hyperparam to check sn creation - assert await async_subtensor.subnets.subnetwork_n(alice_sn.netuid) == 1 # TODO? + assert await async_subtensor.subnets.subnetwork_n(alice_sn.netuid) == 1 assert await async_subtensor.subnets.subnetwork_n(alice_sn.netuid + 1) is None # Ensure correct hyperparams are being fetched regarding weights @@ -248,3 +252,6 @@ async def test_root_reg_hyperparams_async( ) assert sn_one_neurons[alice_uid_sn_2].hotkey == alice_wallet.hotkey.ss58_address assert sn_one_neurons[alice_uid_sn_2].validator_permit is True + + await alice_sn.async_execute_one(REGISTER_NEURON(bob_wallet)) + assert await async_subtensor.subnets.subnetwork_n(alice_sn.netuid) == 2 diff --git a/tests/e2e_tests/test_set_weights.py b/tests/e2e_tests/test_set_weights.py index 466c278fab..93cb3ed86b 100644 --- a/tests/e2e_tests/test_set_weights.py +++ b/tests/e2e_tests/test_set_weights.py @@ -279,9 +279,9 @@ async def set_weights_(): mechid=mechid_, uids=weight_uids, weights=weight_vals, + period=subnet_tempo, wait_for_inclusion=True, wait_for_finalization=False, - period=subnet_tempo, ) assert success_ is True, message_ diff --git a/tests/unit_tests/test_metagraph.py b/tests/unit_tests/test_metagraph.py index acf62e22d0..280e885265 100644 --- a/tests/unit_tests/test_metagraph.py +++ b/tests/unit_tests/test_metagraph.py @@ -97,7 +97,6 @@ def test_process_weights_or_bonds(mock_environment): assert weights.shape[1] == len( neurons ) # Number of columns should be equal to number of neurons - # TODO: Add more checks to ensure the weights have been processed correctly # Test bonds processing bonds = metagraph._process_weights_or_bonds( @@ -110,8 +109,6 @@ def test_process_weights_or_bonds(mock_environment): neurons ) # Number of columns should be equal to number of neurons - # TODO: Add more checks to ensure the bonds have been processed correctly - # Mocking the bittensor.Subtensor class for testing purposes @pytest.fixture