From f6ab8a1162241ff158095a3ace6e60629ebf4c56 Mon Sep 17 00:00:00 2001 From: ifrit98 Date: Wed, 25 Oct 2023 20:43:01 +0000 Subject: [PATCH 01/11] Remove PoW registration cli and associated extrinsic --- README.md | 5 +- bittensor/cli.py | 1 - bittensor/commands/__init__.py | 2 +- bittensor/commands/register.py | 146 +------------------- bittensor/extrinsics/registration.py | 190 --------------------------- bittensor/subtensor.py | 35 ----- tests/integration_tests/test_cli.py | 25 ---- 7 files changed, 9 insertions(+), 395 deletions(-) diff --git a/README.md b/README.md index 35c18c1958..671e170db0 100644 --- a/README.md +++ b/README.md @@ -183,17 +183,16 @@ For example: ```bash btcli subnets --help -usage: btcli subnets [-h] {list,metagraph,lock_cost,create,register,recycle_register,hyperparameters} ... +usage: btcli subnets [-h] {list,metagraph,lock_cost,create,register,hyperparameters} ... positional arguments: - {list,metagraph,lock_cost,create,register,recycle_register,hyperparameters} + {list,metagraph,lock_cost,create,register,hyperparameters} Commands for managing and viewing subnetworks. list List all subnets on the network metagraph View a subnet metagraph information. lock_cost Return the lock cost to register a subnet create Create a new bittensor subnetwork on this chain. register Register a wallet to a network. - recycle_register Register a wallet to a network. hyperparameters View subnet hyperparameters options: diff --git a/bittensor/cli.py b/bittensor/cli.py index 673fa31419..33111fb3e1 100644 --- a/bittensor/cli.py +++ b/bittensor/cli.py @@ -54,7 +54,6 @@ "lock_cost": SubnetLockCostCommand, "create": RegisterSubnetworkCommand, "register": RegisterCommand, - "recycle_register": RecycleRegisterCommand, "hyperparameters": SubnetHyperparamsCommand, }, }, diff --git a/bittensor/commands/__init__.py b/bittensor/commands/__init__.py index b9e7721547..0441792e62 100644 --- a/bittensor/commands/__init__.py +++ b/bittensor/commands/__init__.py @@ -65,7 +65,7 @@ from .stake import StakeCommand, StakeShow from .unstake import UnStakeCommand from .overview import OverviewCommand -from .register import RegisterCommand, RecycleRegisterCommand, RunFaucetCommand +from .register import RegisterCommand, RunFaucetCommand from .delegates import ( NominateCommand, ListDelegatesCommand, diff --git a/bittensor/commands/register.py b/bittensor/commands/register.py index 2fc615fd26..b6f89ab491 100644 --- a/bittensor/commands/register.py +++ b/bittensor/commands/register.py @@ -26,142 +26,8 @@ console = bittensor.__console__ -class RegisterCommand: - @staticmethod - def run(cli): - r"""Register neuron.""" - wallet = bittensor.wallet(config=cli.config) - subtensor = bittensor.subtensor(config=cli.config) - - # Verify subnet exists - if not subtensor.subnet_exists(netuid=cli.config.netuid): - bittensor.__console__.print( - f"[red]Subnet {cli.config.netuid} does not exist[/red]" - ) - sys.exit(1) - - subtensor.register( - wallet=wallet, - netuid=cli.config.netuid, - prompt=not cli.config.no_prompt, - TPB=cli.config.register.cuda.get("TPB", None), - update_interval=cli.config.register.get("update_interval", None), - num_processes=cli.config.register.get("num_processes", None), - cuda=cli.config.register.cuda.get( - "use_cuda", defaults.register.cuda.use_cuda - ), - dev_id=cli.config.register.cuda.get("dev_id", None), - output_in_place=cli.config.register.get( - "output_in_place", defaults.register.output_in_place - ), - log_verbose=cli.config.register.get("verbose", defaults.register.verbose), - ) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - register_parser = parser.add_parser( - "register", help="""Register a wallet to a network.""" - ) - register_parser.add_argument( - "--netuid", - type=int, - help="netuid for subnet to serve this neuron on", - default=argparse.SUPPRESS, - ) - register_parser.add_argument( - "--register.num_processes", - "-n", - dest="register.num_processes", - help="Number of processors to use for POW registration", - type=int, - default=defaults.register.num_processes, - ) - register_parser.add_argument( - "--register.update_interval", - "--register.cuda.update_interval", - "--cuda.update_interval", - "-u", - help="The number of nonces to process before checking for next block during registration", - type=int, - default=defaults.register.update_interval, - ) - register_parser.add_argument( - "--register.no_output_in_place", - "--no_output_in_place", - dest="register.output_in_place", - help="Whether to not ouput the registration statistics in-place. Set flag to disable output in-place.", - action="store_false", - required=False, - default=defaults.register.output_in_place, - ) - register_parser.add_argument( - "--register.verbose", - help="Whether to ouput the registration statistics verbosely.", - action="store_true", - required=False, - default=defaults.register.verbose, - ) - ## Registration args for CUDA registration. - register_parser.add_argument( - "--register.cuda.use_cuda", - "--cuda", - "--cuda.use_cuda", - dest="register.cuda.use_cuda", - default=defaults.register.cuda.use_cuda, - help="""Set flag to use CUDA to register.""", - action="store_true", - required=False, - ) - register_parser.add_argument( - "--register.cuda.no_cuda", - "--no_cuda", - "--cuda.no_cuda", - dest="register.cuda.use_cuda", - default=not defaults.register.cuda.use_cuda, - help="""Set flag to not use CUDA for registration""", - action="store_false", - required=False, - ) - - register_parser.add_argument( - "--register.cuda.dev_id", - "--cuda.dev_id", - type=int, - nargs="+", - default=defaults.register.cuda.dev_id, - help="""Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest).""", - required=False, - ) - register_parser.add_argument( - "--register.cuda.TPB", - "--cuda.TPB", - type=int, - default=defaults.register.cuda.TPB, - help="""Set the number of Threads Per Block for CUDA.""", - required=False, - ) - - bittensor.wallet.add_args(register_parser) - bittensor.subtensor.add_args(register_parser) - - @staticmethod - def check_config(config: "bittensor.config"): - check_netuid_set(config, subtensor=bittensor.subtensor(config=config)) - - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - - if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) - config.wallet.hotkey = str(hotkey) - - if not config.no_prompt: - check_for_cuda_reg_config(config) - - -class RecycleRegisterCommand: +class RegisterCommand: @staticmethod def run(cli): r"""Register neuron by recycling some TAO.""" @@ -202,18 +68,18 @@ def run(cli): @staticmethod def add_args(parser: argparse.ArgumentParser): - recycle_register_parser = parser.add_parser( - "recycle_register", help="""Register a wallet to a network.""" + register_parser = parser.add_parser( + "register", help="""Register a wallet to a network.""" ) - recycle_register_parser.add_argument( + register_parser.add_argument( "--netuid", type=int, help="netuid for subnet to serve this neuron on", default=argparse.SUPPRESS, ) - bittensor.wallet.add_args(recycle_register_parser) - bittensor.subtensor.add_args(recycle_register_parser) + bittensor.wallet.add_args(register_parser) + bittensor.subtensor.add_args(register_parser) @staticmethod def check_config(config: "bittensor.config"): diff --git a/bittensor/extrinsics/registration.py b/bittensor/extrinsics/registration.py index 9799234363..8b9ec36fcc 100644 --- a/bittensor/extrinsics/registration.py +++ b/bittensor/extrinsics/registration.py @@ -25,196 +25,6 @@ from bittensor.utils.registration import POWSolution, create_pow -def register_extrinsic( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", - netuid: int, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, - prompt: bool = False, - max_allowed_attempts: int = 3, - output_in_place: bool = True, - cuda: bool = False, - dev_id: Union[List[int], int] = 0, - TPB: int = 256, - num_processes: Optional[int] = None, - update_interval: Optional[int] = None, - log_verbose: bool = False, -) -> bool: - r"""Registers the wallet to chain. - Args: - wallet (bittensor.wallet): - bittensor wallet object. - netuid (int): - The netuid of the subnet to register on. - wait_for_inclusion (bool): - If set, waits for the extrinsic to enter a block before returning true, - or returns false if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): - If set, waits for the extrinsic to be finalized on the chain before returning true, - or returns false if the extrinsic fails to be finalized within the timeout. - prompt (bool): - If true, the call waits for confirmation from the user before proceeding. - max_allowed_attempts (int): - Maximum number of attempts to register the wallet. - cuda (bool): - If true, the wallet should be registered using CUDA device(s). - dev_id (Union[List[int], int]): - The CUDA device id to use, or a list of device ids. - TPB (int): - The number of threads per block (CUDA). - num_processes (int): - The number of processes to use to register. - update_interval (int): - The number of nonces to solve between updates. - log_verbose (bool): - If true, the registration process will log more information. - Returns: - success (bool): - flag is true if extrinsic was finalized or uncluded in the block. - If we did not wait for finalization / inclusion, the response is true. - """ - if not subtensor.subnet_exists(netuid): - bittensor.__console__.print( - ":cross_mark: [red]Failed[/red]: error: [bold white]subnet:{}[/bold white] does not exist.".format( - netuid - ) - ) - return False - - with bittensor.__console__.status( - f":satellite: Checking Account on [bold]subnet:{netuid}[/bold]..." - ): - neuron = subtensor.get_neuron_for_pubkey_and_subnet( - wallet.hotkey.ss58_address, netuid=netuid - ) - if not neuron.is_null: - bittensor.logging.debug( - f"Wallet {wallet} is already registered on {neuron.netuid} with {neuron.uid}" - ) - return True - - if prompt: - if not Confirm.ask( - "Continue Registration?\n hotkey: [bold white]{}[/bold white]\n coldkey: [bold white]{}[/bold white]\n network: [bold white]{}[/bold white]".format( - wallet.hotkey.ss58_address, - wallet.coldkeypub.ss58_address, - subtensor.network, - ) - ): - return False - - # Attempt rolling registration. - attempts = 1 - while True: - bittensor.__console__.print( - ":satellite: Registering...({}/{})".format(attempts, max_allowed_attempts) - ) - # Solve latest POW. - if cuda: - if not torch.cuda.is_available(): - if prompt: - bittensor.__console__.error("CUDA is not available.") - return False - pow_result: Optional[POWSolution] = create_pow( - subtensor, - wallet, - netuid, - output_in_place, - cuda=cuda, - dev_id=dev_id, - TPB=TPB, - num_processes=num_processes, - update_interval=update_interval, - log_verbose=log_verbose, - ) - else: - pow_result: Optional[POWSolution] = create_pow( - subtensor, - wallet, - netuid, - output_in_place, - cuda=cuda, - num_processes=num_processes, - update_interval=update_interval, - log_verbose=log_verbose, - ) - - # pow failed - if not pow_result: - # might be registered already on this subnet - is_registered = subtensor.is_hotkey_registered( - netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address - ) - if is_registered: - bittensor.__console__.print( - f":white_heavy_check_mark: [green]Already registered on netuid:{netuid}[/green]" - ) - return True - - # pow successful, proceed to submit pow to chain for registration - else: - with bittensor.__console__.status(":satellite: Submitting POW..."): - # check if pow result is still valid - while not pow_result.is_stale(subtensor=subtensor): - result: Tuple[bool, Optional[str]] = subtensor._do_pow_register( - netuid=netuid, - wallet=wallet, - pow_result=pow_result, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - success, err_msg = result - - if success != True or success == False: - if "key is already registered" in err_msg: - # Error meant that the key is already registered. - bittensor.__console__.print( - f":white_heavy_check_mark: [green]Already Registered on [bold]subnet:{netuid}[/bold][/green]" - ) - return True - - bittensor.__console__.print( - ":cross_mark: [red]Failed[/red]: error:{}".format(err_msg) - ) - time.sleep(0.5) - - # Successful registration, final check for neuron and pubkey - else: - bittensor.__console__.print(":satellite: Checking Balance...") - is_registered = subtensor.is_hotkey_registered( - netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address - ) - if is_registered: - bittensor.__console__.print( - ":white_heavy_check_mark: [green]Registered[/green]" - ) - return True - else: - # neuron not found, try again - bittensor.__console__.print( - ":cross_mark: [red]Unknown error. Neuron not found.[/red]" - ) - continue - else: - # Exited loop because pow is no longer valid. - bittensor.__console__.print("[red]POW is stale.[/red]") - # Try again. - continue - - if attempts < max_allowed_attempts: - # Failed registration, retry pow - attempts += 1 - bittensor.__console__.print( - ":satellite: Failed registration, retrying pow ...({}/{})".format( - attempts, max_allowed_attempts - ) - ) - else: - # Failed to register after max attempts. - bittensor.__console__.print("[red]No more attempts.[/red]") - return False - def burned_register_extrinsic( subtensor: "bittensor.subtensor", diff --git a/bittensor/subtensor.py b/bittensor/subtensor.py index 99b15d5060..d9d0db09b7 100644 --- a/bittensor/subtensor.py +++ b/bittensor/subtensor.py @@ -54,7 +54,6 @@ from .extrinsics.unstaking import unstake_extrinsic, unstake_multiple_extrinsic from .extrinsics.serving import serve_extrinsic, serve_axon_extrinsic from .extrinsics.registration import ( - register_extrinsic, burned_register_extrinsic, run_faucet_extrinsic, ) @@ -425,40 +424,6 @@ def make_substrate_call_with_retry(): ###################### #### Registration #### ###################### - def register( - self, - wallet: "bittensor.wallet", - netuid: int, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, - prompt: bool = False, - max_allowed_attempts: int = 3, - output_in_place: bool = True, - cuda: bool = False, - dev_id: Union[List[int], int] = 0, - TPB: int = 256, - num_processes: Optional[int] = None, - update_interval: Optional[int] = None, - log_verbose: bool = False, - ) -> bool: - """Registers the wallet to chain.""" - return register_extrinsic( - subtensor=self, - wallet=wallet, - netuid=netuid, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - prompt=prompt, - 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, - ) - def run_faucet( self, wallet: "bittensor.wallet", diff --git a/tests/integration_tests/test_cli.py b/tests/integration_tests/test_cli.py index ffc973d78c..fa1b16fc7a 100644 --- a/tests/integration_tests/test_cli.py +++ b/tests/integration_tests/test_cli.py @@ -1913,31 +1913,6 @@ def test_register(self, _): config = self.config config.command = "subnets" config.subcommand = "register" - config.register.num_processes = 1 - config.register.update_interval = 50_000 - config.no_prompt = True - - mock_wallet = generate_wallet(hotkey=_get_mock_keypair(100, self.id())) - - class MockException(Exception): - pass - - with patch("bittensor.wallet", return_value=mock_wallet) as mock_create_wallet: - with patch( - "bittensor.extrinsics.registration.POWSolution.is_stale", - side_effect=MockException, - ) as mock_is_stale: - with pytest.raises(MockException): - cli = bittensor.cli(config) - cli.run() - mock_create_wallet.assert_called_once() - - self.assertEqual(mock_is_stale.call_count, 1) - - def test_recycle_register(self, _): - config = self.config - config.command = "subnets" - config.subcommand = "recycle_register" config.no_prompt = True mock_wallet = generate_wallet(hotkey=_get_mock_keypair(100, self.id())) From ef85e03ca703c5429b7615e447f7d64a1ebedd7b Mon Sep 17 00:00:00 2001 From: ifrit98 Date: Wed, 25 Oct 2023 20:48:37 +0000 Subject: [PATCH 02/11] run black --- bittensor/commands/register.py | 1 - bittensor/extrinsics/registration.py | 1 - 2 files changed, 2 deletions(-) diff --git a/bittensor/commands/register.py b/bittensor/commands/register.py index b6f89ab491..b374863911 100644 --- a/bittensor/commands/register.py +++ b/bittensor/commands/register.py @@ -26,7 +26,6 @@ console = bittensor.__console__ - class RegisterCommand: @staticmethod def run(cli): diff --git a/bittensor/extrinsics/registration.py b/bittensor/extrinsics/registration.py index 8b9ec36fcc..162783cf63 100644 --- a/bittensor/extrinsics/registration.py +++ b/bittensor/extrinsics/registration.py @@ -25,7 +25,6 @@ from bittensor.utils.registration import POWSolution, create_pow - def burned_register_extrinsic( subtensor: "bittensor.subtensor", wallet: "bittensor.wallet", From c0776d3d35045893b9c6699eda29f528bea56f4d Mon Sep 17 00:00:00 2001 From: ifrit98 Date: Wed, 25 Oct 2023 22:59:17 +0000 Subject: [PATCH 03/11] no mo pow, no mo pow tests --- tests/unit_tests/utils/test_utils.py | 265 --------------------------- 1 file changed, 265 deletions(-) diff --git a/tests/unit_tests/utils/test_utils.py b/tests/unit_tests/utils/test_utils.py index 307891a13c..6c2c5bd9c0 100644 --- a/tests/unit_tests/utils/test_utils.py +++ b/tests/unit_tests/utils/test_utils.py @@ -584,54 +584,6 @@ def setUp(self) -> None: self._subtensor.reset() self._subtensor.create_subnet(netuid=99) - def test_pow_called_for_cuda(self, mock_cuda_available): - class MockException(Exception): - pass - - mock_pow_register_call = MagicMock(side_effect=MockException) - - mock_subtensor = MockSubtensor() - mock_subtensor.reset() - mock_subtensor.create_subnet(netuid=99) - mock_subtensor.get_neuron_for_pubkey_and_subnet = MagicMock(is_null=True) - mock_subtensor._do_pow_register = mock_pow_register_call - - mock_wallet = SimpleNamespace( - hotkey=bittensor.Keypair.create_from_seed( - "0x" + "0" * 64, ss58_format=bittensor.__ss58_format__ - ), - coldkeypub=SimpleNamespace(ss58_address=""), - ) - - mock_pow_is_stale = MagicMock(return_value=False) - - mock_result = MagicMock( - spec=bittensor.utils.registration.POWSolution, - block_number=1, - nonce=random.randint(0, pow(2, 32)), - difficulty=1, - seal=b"\x00" * 64, - is_stale=mock_pow_is_stale, - ) - - with patch( - "bittensor.extrinsics.registration.create_pow", return_value=mock_result - ) as mock_create_pow: - # Should exit early - with pytest.raises(MockException): - mock_subtensor.register(mock_wallet, netuid=99, cuda=True, prompt=False) - - mock_pow_is_stale.assert_called_once() - mock_create_pow.assert_called_once() - mock_cuda_available.assert_called_once() - - call0 = mock_pow_is_stale.call_args - _, kwargs = call0 - assert kwargs["subtensor"] == mock_subtensor - - mock_pow_register_call.assert_called_once() - _, kwargs = mock_pow_register_call.call_args - kwargs["pow_result"].nonce == mock_result.nonce class TestCUDASolverRun(unittest.TestCase): @@ -762,222 +714,5 @@ def test_get_explorer_url_for_network_by_network_and_block_hash( ) -class TestWalletReregister(unittest.TestCase): - _mock_subtensor: MockSubtensor - - def setUp(self): - self.subtensor = MockSubtensor() # own instance per test - - @classmethod - def setUpClass(cls) -> None: - # Keeps the same mock network for all tests. This stops the network from being re-setup for each test. - cls._mock_subtensor = MockSubtensor() - - cls._do_setup_subnet() - - @classmethod - def _do_setup_subnet(cls): - # reset the mock subtensor - cls._mock_subtensor.reset() - # Setup the mock subnet 3 - cls._mock_subtensor.create_subnet(netuid=3) - - def test_wallet_reregister_reregister_false(self): - mock_wallet = _generate_wallet(hotkey=_get_mock_keypair(100, self.id())) - - class MockException(Exception): - pass - - # Determine the correct string for patch based on Python version - if sys.version_info >= (3, 11): - patch_string = "bittensor.subtensor.subtensor.register" - else: - patch_string = "bittensor.subtensor.register" - - with patch(patch_string, side_effect=MockException) as mock_register: - with pytest.raises(SystemExit): # should exit because it's not registered - bittensor.utils.reregister( - wallet=mock_wallet, - subtensor=self._mock_subtensor, - netuid=3, - reregister=False, - ) - - mock_register.assert_not_called() # should not call register - - def test_wallet_reregister_reregister_false_and_registered_already(self): - mock_wallet = _generate_wallet(hotkey=_get_mock_keypair(100, self.id())) - - class MockException(Exception): - pass - - self._mock_subtensor.force_register_neuron( - netuid=3, - hotkey=mock_wallet.hotkey.ss58_address, - coldkey=mock_wallet.coldkeypub.ss58_address, - ) - self.assertTrue( - self._mock_subtensor.is_hotkey_registered_on_subnet( - netuid=3, - hotkey_ss58=mock_wallet.hotkey.ss58_address, - ) - ) - - # Determine the correct string for patch based on Python version - if sys.version_info >= (3, 11): - patch_string = "bittensor.subtensor.subtensor.register" - else: - patch_string = "bittensor.subtensor.register" - - with patch(patch_string, side_effect=MockException) as mock_register: - bittensor.utils.reregister( - wallet=mock_wallet, - subtensor=self._mock_subtensor, - netuid=3, - reregister=False, - ) # Should not exit because it's registered - - mock_register.assert_not_called() # should not call register - - def test_wallet_reregister_reregister_true_and_registered_already(self): - mock_wallet = _generate_wallet(hotkey=_get_mock_keypair(100, self.id())) - - class MockException(Exception): - pass - - self._mock_subtensor.force_register_neuron( - netuid=3, - hotkey=mock_wallet.hotkey.ss58_address, - coldkey=mock_wallet.coldkeypub.ss58_address, - ) - self.assertTrue( - self._mock_subtensor.is_hotkey_registered_on_subnet( - netuid=3, - hotkey_ss58=mock_wallet.hotkey.ss58_address, - ) - ) - - # Determine the correct string for patch based on Python version - if sys.version_info >= (3, 11): - patch_string = "bittensor.subtensor.subtensor.register" - else: - patch_string = "bittensor.subtensor.register" - - with patch(patch_string, side_effect=MockException) as mock_register: - bittensor.utils.reregister( - wallet=mock_wallet, - subtensor=self._mock_subtensor, - netuid=3, - reregister=True, - ) # Should not exit because it's registered - - mock_register.assert_not_called() # should not call register - - def test_wallet_reregister_no_params(self): - mock_wallet = _generate_wallet(hotkey=_get_mock_keypair(100, self.id())) - - class MockException(Exception): - pass - - # Determine the correct string for patch based on Python version - if sys.version_info >= (3, 11): - patch_string = "bittensor.subtensor.subtensor.register" - else: - patch_string = "bittensor.subtensor.register" - - with patch(patch_string, side_effect=MockException) as mock_register: - # Should be able to set without argument - with pytest.raises(MockException): - bittensor.utils.reregister( - wallet=mock_wallet, - subtensor=self._mock_subtensor, - netuid=3, - reregister=True, - # didn't pass any register params - ) - - mock_register.assert_called_once() # should call register once - - def test_wallet_reregister_use_cuda_flag_true(self): - mock_wallet = _generate_wallet(hotkey=_get_mock_keypair(100, self.id())) - - class MockException(Exception): - pass - - with patch("torch.cuda.is_available", return_value=True) as mock_cuda_available: - with patch( - "bittensor.extrinsics.registration.create_pow", - side_effect=MockException, - ) as mock_create_pow: - # Should be able to set without argument - with pytest.raises(MockException): - bittensor.utils.reregister( - wallet=mock_wallet, - subtensor=self._mock_subtensor, - netuid=3, - dev_id=0, - cuda=True, - reregister=True, - ) - - call_args = mock_create_pow.call_args - _, kwargs = call_args - - mock_create_pow.assert_called_once() - self.assertIn("cuda", kwargs) - self.assertEqual(kwargs["cuda"], True) - - def test_wallet_reregister_use_cuda_flag_false(self): - mock_wallet = _generate_wallet(hotkey=_get_mock_keypair(100, self.id())) - - class MockException(Exception): - pass - - with patch( - "bittensor.extrinsics.registration.create_pow", side_effect=MockException - ) as mock_create_pow: - # Should be able to set without argument - with pytest.raises(MockException): - bittensor.utils.reregister( - wallet=mock_wallet, - subtensor=self._mock_subtensor, - netuid=3, - dev_id=0, - cuda=False, - reregister=True, - ) - - call_args = mock_create_pow.call_args - _, kwargs = call_args - - mock_create_pow.assert_called_once() - self.assertEqual(kwargs["cuda"], False) - - def test_wallet_reregister_cuda_arg_not_specified_should_be_false(self): - mock_wallet = _generate_wallet(hotkey=_get_mock_keypair(100, self.id())) - - class MockException(Exception): - pass - - with patch( - "bittensor.extrinsics.registration.create_pow", side_effect=MockException - ) as mock_create_pow: - # Should be able to set without argument - with pytest.raises(MockException): - bittensor.utils.reregister( - wallet=mock_wallet, - subtensor=self._mock_subtensor, - netuid=3, - dev_id=0, - reregister=True, - ) - - call_args = mock_create_pow.call_args - _, kwargs = call_args - - mock_create_pow.assert_called_once() - self.assertEqual(kwargs["cuda"], False) # should be False by default - - if __name__ == "__main__": unittest.main() From de6010af055eace7ee398da229ff0d4c353f193c Mon Sep 17 00:00:00 2001 From: ifrit98 Date: Wed, 25 Oct 2023 23:11:17 +0000 Subject: [PATCH 04/11] remove now deprecated PoW reregister routine --- bittensor/utils/__init__.py | 2 +- bittensor/utils/registration.py | 48 --------------------------------- 2 files changed, 1 insertion(+), 49 deletions(-) diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 6324461965..dea833d72b 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -26,7 +26,7 @@ from substrateinterface.utils import ss58 as ss58 from .wallet_utils import * -from .registration import create_pow as create_pow, __reregister_wallet as reregister +from .registration import create_pow as create_pow RAOPERTAO = 1e9 U16_MAX = 65535 diff --git a/bittensor/utils/registration.py b/bittensor/utils/registration.py index d86f7d85fc..29a1d5d7f3 100644 --- a/bittensor/utils/registration.py +++ b/bittensor/utils/registration.py @@ -1087,51 +1087,3 @@ def create_pow( ) return solution - - -def __reregister_wallet( - netuid: int, - wallet: "bittensor.wallet", - subtensor: "bittensor.subtensor", - reregister: bool = False, - prompt: bool = False, - **registration_args: Any, -) -> Optional["bittensor.wallet"]: - """Re-register this a wallet on the chain, or exits. - Exits if the wallet is not registered on the chain AND - reregister is set to False. - Args: - netuid (int): - The network uid of the subnet to register on. - wallet( 'bittensor.wallet' ): - Bittensor wallet to re-register - reregister (bool, default=False): - If true, re-registers the wallet on the chain. - Exits if False and the wallet is not registered on the chain. - prompt (bool): - If true, the call waits for confirmation from the user before proceeding. - **registration_args (Any): - The registration arguments to pass to the subtensor register function. - Return: - wallet (bittensor.wallet): - The wallet - - Raises: - SytemExit(0): - If the wallet is not registered on the chain AND - the config.subtensor.reregister flag is set to False. - """ - wallet.hotkey - - if not subtensor.is_hotkey_registered_on_subnet( - hotkey_ss58=wallet.hotkey.ss58_address, netuid=netuid - ): - # Check if the wallet should reregister - if not reregister: - sys.exit(0) - - subtensor.register( - wallet=wallet, netuid=netuid, prompt=prompt, **registration_args - ) - - return wallet From be6297e8223e67eed1dc9cbc0d9587d53041abd2 Mon Sep 17 00:00:00 2001 From: ifrit98 Date: Wed, 25 Oct 2023 23:25:45 +0000 Subject: [PATCH 05/11] remove deprecated tests --- .../integration_tests/test_cli_no_network.py | 43 ----- .../test_subtensor_integration.py | 177 +----------------- 2 files changed, 4 insertions(+), 216 deletions(-) diff --git a/tests/integration_tests/test_cli_no_network.py b/tests/integration_tests/test_cli_no_network.py index 5aa8571211..86dfae9140 100644 --- a/tests/integration_tests/test_cli_no_network.py +++ b/tests/integration_tests/test_cli_no_network.py @@ -334,49 +334,6 @@ def test_btcli_help(self, _, __): set(extracted_commands) ), "Duplicate commands found in help output" - @patch("torch.cuda.is_available", return_value=True) - def test_register_cuda_use_cuda_flag(self, _, __, patched_sub): - base_args = [ - "subnets", - "register", - "--wallet.path", - "tmp/walletpath", - "--wallet.name", - "mock", - "--wallet.hotkey", - "hk0", - "--no_prompt", - "--cuda.dev_id", - "0", - ] - - patched_sub.return_value = MagicMock( - get_subnets=MagicMock(return_value=[1]), - subnet_exists=MagicMock(return_value=True), - register=MagicMock(side_effect=MockException), - ) - - # Should be able to set true without argument - args = base_args + [ - "--register.cuda.use_cuda", # should be True without any arugment - ] - with pytest.raises(MockException): - cli = bittensor.cli(args=args) - cli.run() - - self.assertEqual(cli.config.register.cuda.get("use_cuda"), True) - - # Should be able to set to false with no argument - - args = base_args + [ - "--register.cuda.no_cuda", - ] - with pytest.raises(MockException): - cli = bittensor.cli(args=args) - cli.run() - - self.assertEqual(cli.config.register.cuda.get("use_cuda"), False) - def return_mock_sub_2(*args, **kwargs): return MagicMock( diff --git a/tests/integration_tests/test_subtensor_integration.py b/tests/integration_tests/test_subtensor_integration.py index 8448f9098b..9e0dcd9cc1 100644 --- a/tests/integration_tests/test_subtensor_integration.py +++ b/tests/integration_tests/test_subtensor_integration.py @@ -418,180 +418,11 @@ def test_is_hotkey_registered_not_registered(self): ) self.assertFalse(registered, msg="Hotkey should not be registered") - def test_registration_multiprocessed_already_registered(self): - workblocks_before_is_registered = random.randint(5, 10) - # return False each work block but return True after a random number of blocks - is_registered_return_values = ( - [False for _ in range(workblocks_before_is_registered)] - + [True] - + [True, False] - ) - # this should pass the initial False check in the subtensor class and then return True because the neuron is already registered - - mock_neuron = MagicMock() - mock_neuron.is_null = True - - # patch solution queue to return None - with patch( - "multiprocessing.queues.Queue.get", return_value=None - ) as mock_queue_get: - # patch time queue get to raise Empty exception - with patch( - "multiprocessing.queues.Queue.get_nowait", side_effect=QueueEmpty - ) as mock_queue_get_nowait: - wallet = _get_mock_wallet( - hotkey=_get_mock_keypair(0, self.id()), - coldkey=_get_mock_keypair(1, self.id()), - ) - self.subtensor.is_hotkey_registered = MagicMock( - side_effect=is_registered_return_values - ) - - self.subtensor.difficulty = MagicMock(return_value=1) - self.subtensor.get_neuron_for_pubkey_and_subnet = MagicMock( - side_effect=mock_neuron - ) - self.subtensor._do_pow_register = MagicMock(return_value=(True, None)) - - with patch("bittensor.__console__.status") as mock_set_status: - # Need to patch the console status to avoid opening a parallel live display - mock_set_status.__enter__ = MagicMock(return_value=True) - mock_set_status.__exit__ = MagicMock(return_value=True) - - # should return True - assert ( - self.subtensor.register( - wallet=wallet, netuid=3, num_processes=3, update_interval=5 - ) - == True - ) - - # calls until True and once again before exiting subtensor class - # This assertion is currently broken when difficulty is too low - assert ( - self.subtensor.is_hotkey_registered.call_count - == workblocks_before_is_registered + 2 - ) - - def test_registration_partly_failed(self): - do_pow_register_mock = MagicMock( - side_effect=[(False, "Failed"), (False, "Failed"), (True, None)] - ) - def is_registered_side_effect(*args, **kwargs): - nonlocal do_pow_register_mock - return do_pow_register_mock.call_count < 3 - - current_block = [i for i in range(0, 100)] - - wallet = _get_mock_wallet( - hotkey=_get_mock_keypair(0, self.id()), - coldkey=_get_mock_keypair(1, self.id()), - ) - - self.subtensor.get_neuron_for_pubkey_and_subnet = MagicMock( - return_value=bittensor.NeuronInfo._null_neuron() - ) - self.subtensor.is_hotkey_registered = MagicMock( - side_effect=is_registered_side_effect - ) - - self.subtensor.difficulty = MagicMock(return_value=1) - self.subtensor.get_current_block = MagicMock(side_effect=current_block) - self.subtensor._do_pow_register = do_pow_register_mock - - # should return True - self.assertTrue( - self.subtensor.register( - wallet=wallet, netuid=3, num_processes=3, update_interval=5 - ), - msg="Registration should succeed", - ) - - def test_registration_failed(self): - is_registered_return_values = [False for _ in range(100)] - current_block = [i for i in range(0, 100)] - mock_neuron = MagicMock() - mock_neuron.is_null = True - - with patch( - "bittensor.extrinsics.registration.create_pow", return_value=None - ) as mock_create_pow: - wallet = _get_mock_wallet( - hotkey=_get_mock_keypair(0, self.id()), - coldkey=_get_mock_keypair(1, self.id()), - ) - - self.subtensor.is_hotkey_registered = MagicMock( - side_effect=is_registered_return_values - ) - - self.subtensor.get_current_block = MagicMock(side_effect=current_block) - self.subtensor.get_neuron_for_pubkey_and_subnet = MagicMock( - return_value=mock_neuron - ) - self.subtensor.substrate.get_block_hash = MagicMock( - return_value="0x" + "0" * 64 - ) - self.subtensor._do_pow_register = MagicMock(return_value=(False, "Failed")) - - # should return True - self.assertIsNot( - self.subtensor.register(wallet=wallet, netuid=3), - True, - msg="Registration should fail", - ) - self.assertEqual(mock_create_pow.call_count, 3) - - def test_registration_stale_then_continue(self): - # verifty that after a stale solution, the solve will continue without exiting - - class ExitEarly(Exception): - pass - - mock_is_stale = MagicMock(side_effect=[True, False]) - - mock_do_pow_register = MagicMock(side_effect=ExitEarly()) - - mock_subtensor_self = MagicMock( - neuron_for_pubkey=MagicMock( - return_value=MagicMock(is_null=True) - ), # not registered - _do_pow_register=mock_do_pow_register, - substrate=MagicMock( - get_block_hash=MagicMock(return_value="0x" + "0" * 64), - ), - ) - - mock_wallet = MagicMock() - - mock_create_pow = MagicMock(return_value=MagicMock(is_stale=mock_is_stale)) - - with patch("bittensor.extrinsics.registration.create_pow", mock_create_pow): - # should create a pow and check if it is stale - # then should create a new pow and check if it is stale - # then should enter substrate and exit early because of test - self.subtensor.get_neuron_for_pubkey_and_subnet = MagicMock( - return_value=bittensor.NeuronInfo._null_neuron() - ) - with pytest.raises(ExitEarly): - bittensor.subtensor.register(mock_subtensor_self, mock_wallet, netuid=3) - self.assertEqual( - mock_create_pow.call_count, 2, msg="must try another pow after stale" - ) - self.assertEqual(mock_is_stale.call_count, 2) - self.assertEqual( - mock_do_pow_register.call_count, - 1, - msg="only tries to submit once, then exits", - ) - - -# # This test was flaking, please check to_defaults before reactiving the test -# def _test_defaults_to_finney(): -# sub = bittensor.subtensor() -# assert sub.network == 'finney' -# assert sub.chain_endpoint == bittensor.__finney_entrypoint__ + def test_defaults_to_finney(self): + sub = bittensor.subtensor() + assert sub.network == 'finney' + assert sub.chain_endpoint == bittensor.__finney_entrypoint__ if __name__ == "__main__": unittest.main() From 12070080c3d8d18249cf08c84f0d2b14e9c1d61d Mon Sep 17 00:00:00 2001 From: ifrit98 Date: Wed, 25 Oct 2023 23:51:00 +0000 Subject: [PATCH 06/11] more test fixes --- tests/integration_tests/test_subtensor_integration.py | 4 ++-- tests/unit_tests/utils/test_utils.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/integration_tests/test_subtensor_integration.py b/tests/integration_tests/test_subtensor_integration.py index 9e0dcd9cc1..bc284481d2 100644 --- a/tests/integration_tests/test_subtensor_integration.py +++ b/tests/integration_tests/test_subtensor_integration.py @@ -418,11 +418,11 @@ def test_is_hotkey_registered_not_registered(self): ) self.assertFalse(registered, msg="Hotkey should not be registered") - def test_defaults_to_finney(self): sub = bittensor.subtensor() - assert sub.network == 'finney' + assert sub.network == "finney" assert sub.chain_endpoint == bittensor.__finney_entrypoint__ + if __name__ == "__main__": unittest.main() diff --git a/tests/unit_tests/utils/test_utils.py b/tests/unit_tests/utils/test_utils.py index 6c2c5bd9c0..4496d03cb7 100644 --- a/tests/unit_tests/utils/test_utils.py +++ b/tests/unit_tests/utils/test_utils.py @@ -585,7 +585,6 @@ def setUp(self) -> None: self._subtensor.create_subnet(netuid=99) - class TestCUDASolverRun(unittest.TestCase): def test_multi_cuda_run_updates_nonce_start(self): class MockException(Exception): From 46c55c1cd6399aca68c75b63c04be80b0351a18f Mon Sep 17 00:00:00 2001 From: ifrit98 Date: Thu, 26 Oct 2023 00:10:27 +0000 Subject: [PATCH 07/11] remove _do_pow call --- bittensor/mock/subtensor_mock.py | 22 ------------ bittensor/subtensor.py | 59 -------------------------------- 2 files changed, 81 deletions(-) diff --git a/bittensor/mock/subtensor_mock.py b/bittensor/mock/subtensor_mock.py index e91576cb8c..6e1a6a2bbe 100644 --- a/bittensor/mock/subtensor_mock.py +++ b/bittensor/mock/subtensor_mock.py @@ -1073,28 +1073,6 @@ def _do_transfer( return True, None, None - def _do_pow_register( - self, - netuid: int, - wallet: "wallet", - pow_result: "POWSolution", - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, - ) -> Tuple[bool, Optional[str]]: - # Assume pow result is valid - - subtensor_state = self.chain_state["SubtensorModule"] - if netuid not in subtensor_state["NetworksAdded"]: - raise Exception("Subnet does not exist") - - self._register_neuron( - netuid=netuid, - hotkey=wallet.hotkey.ss58_address, - coldkey=wallet.coldkeypub.ss58_address, - ) - - return True, None - def _do_burned_register( self, netuid: int, diff --git a/bittensor/subtensor.py b/bittensor/subtensor.py index d9d0db09b7..a29bdb9a7d 100644 --- a/bittensor/subtensor.py +++ b/bittensor/subtensor.py @@ -474,65 +474,6 @@ def burned_register( prompt=prompt, ) - def _do_pow_register( - self, - netuid: int, - wallet: "bittensor.wallet", - pow_result: POWSolution, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, - ) -> Tuple[bool, Optional[str]]: - """Sends a (POW) register extrinsic to the chain. - Args: - netuid (int): the subnet to register on. - wallet (bittensor.wallet): the wallet to register. - pow_result (POWSolution): the pow result to register. - wait_for_inclusion (bool): if true, waits for the extrinsic to be included in a block. - wait_for_finalization (bool): if true, waits for the extrinsic to be finalized. - Returns: - success (bool): True if the extrinsic was included in a block. - error (Optional[str]): None on success or not waiting for inclusion/finalization, otherwise the error message. - """ - - @retry(delay=2, tries=3, backoff=2, max_delay=4) - def make_substrate_call_with_retry(): - with self.substrate as substrate: - # create extrinsic call - call = substrate.compose_call( - call_module="SubtensorModule", - call_function="register", - call_params={ - "netuid": netuid, - "block_number": pow_result.block_number, - "nonce": pow_result.nonce, - "work": [int(byte_) for byte_ in pow_result.seal], - "hotkey": wallet.hotkey.ss58_address, - "coldkey": wallet.coldkeypub.ss58_address, - }, - ) - extrinsic = substrate.create_signed_extrinsic( - call=call, keypair=wallet.hotkey - ) - response = substrate.submit_extrinsic( - extrinsic, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - return True, None - - # process if registration successful, try again if pow is still valid - response.process_events() - if not response.is_success: - return False, response.error_message - # Successful registration - else: - return True, None - - return make_substrate_call_with_retry() - def _do_burned_register( self, netuid: int, From d50c61acc9450beaed4c4617c45bf5903a810530 Mon Sep 17 00:00:00 2001 From: ifrit98 Date: Thu, 26 Oct 2023 23:45:12 +0000 Subject: [PATCH 08/11] return PoW but still kill reregister (unused) --- README.md | 11 +- bittensor/cli.py | 1 + bittensor/commands/__init__.py | 4 +- bittensor/commands/register.py | 197 +++++++++++++++--- bittensor/extrinsics/registration.py | 191 +++++++++++++++++ bittensor/mock/subtensor_mock.py | 22 ++ bittensor/subtensor.py | 35 ++++ tests/integration_tests/test_cli.py | 25 +++ .../integration_tests/test_cli_no_network.py | 42 ++++ .../test_subtensor_integration.py | 168 +++++++++++++++ tests/unit_tests/utils/test_utils.py | 48 +++++ 11 files changed, 706 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 671e170db0..5825b95a7d 100644 --- a/README.md +++ b/README.md @@ -183,17 +183,18 @@ For example: ```bash btcli subnets --help -usage: btcli subnets [-h] {list,metagraph,lock_cost,create,register,hyperparameters} ... +usage: btcli subnets [-h] {list,metagraph,lock_cost,create,register,pow_register,hyperparameters} ... positional arguments: - {list,metagraph,lock_cost,create,register,hyperparameters} + {list,metagraph,lock_cost,create,register,pow_register,hyperparameters} Commands for managing and viewing subnetworks. - list List all subnets on the network + list List all subnets on the network. metagraph View a subnet metagraph information. - lock_cost Return the lock cost to register a subnet + lock_cost Return the lock cost to register a subnet. create Create a new bittensor subnetwork on this chain. register Register a wallet to a network. - hyperparameters View subnet hyperparameters + register Register a wallet to a network using PoW. + hyperparameters View subnet hyperparameters. options: -h, --help show this help message and exit diff --git a/bittensor/cli.py b/bittensor/cli.py index 33111fb3e1..e4513f5ec0 100644 --- a/bittensor/cli.py +++ b/bittensor/cli.py @@ -53,6 +53,7 @@ "metagraph": MetagraphCommand, "lock_cost": SubnetLockCostCommand, "create": RegisterSubnetworkCommand, + "pow_register": PowRegisterCommand, "register": RegisterCommand, "hyperparameters": SubnetHyperparamsCommand, }, diff --git a/bittensor/commands/__init__.py b/bittensor/commands/__init__.py index 0441792e62..86e41f91a8 100644 --- a/bittensor/commands/__init__.py +++ b/bittensor/commands/__init__.py @@ -21,7 +21,7 @@ { "netuid": 1, "subtensor": {"network": "finney", "chain_endpoint": None, "_mock": False}, - "register": { + "pow_register": { "num_processes": None, "update_interval": 50000, "output_in_place": True, @@ -65,7 +65,7 @@ from .stake import StakeCommand, StakeShow from .unstake import UnStakeCommand from .overview import OverviewCommand -from .register import RegisterCommand, RunFaucetCommand +from .register import PowRegisterCommand, RegisterCommand, RunFaucetCommand from .delegates import ( NominateCommand, ListDelegatesCommand, diff --git a/bittensor/commands/register.py b/bittensor/commands/register.py index b374863911..2799af2767 100644 --- a/bittensor/commands/register.py +++ b/bittensor/commands/register.py @@ -104,6 +104,141 @@ def check_config(config: "bittensor.config"): config.wallet.hotkey = str(hotkey) +class PowRegisterCommand: + @staticmethod + def run(cli): + r"""Register neuron.""" + wallet = bittensor.wallet(config=cli.config) + subtensor = bittensor.subtensor(config=cli.config) + + # Verify subnet exists + if not subtensor.subnet_exists(netuid=cli.config.netuid): + bittensor.__console__.print( + f"[red]Subnet {cli.config.netuid} does not exist[/red]" + ) + sys.exit(1) + + subtensor.register( + wallet=wallet, + netuid=cli.config.netuid, + prompt=not cli.config.no_prompt, + TPB=cli.config.pow_register.cuda.get("TPB", None), + update_interval=cli.config.pow_register.get("update_interval", None), + num_processes=cli.config.pow_register.get("num_processes", None), + cuda=cli.config.pow_register.cuda.get( + "use_cuda", defaults.pow_register.cuda.use_cuda + ), + dev_id=cli.config.pow_register.cuda.get("dev_id", None), + output_in_place=cli.config.pow_register.get( + "output_in_place", defaults.pow_register.output_in_place + ), + log_verbose=cli.config.pow_register.get("verbose", defaults.pow_register.verbose), + ) + + @staticmethod + def add_args(parser: argparse.ArgumentParser): + register_parser = parser.add_parser( + "pow_register", help="""Register a wallet to a network using PoW.""" + ) + register_parser.add_argument( + "--netuid", + type=int, + help="netuid for subnet to serve this neuron on", + default=argparse.SUPPRESS, + ) + register_parser.add_argument( + "--pow_register.num_processes", + "-n", + dest="pow_register.num_processes", + help="Number of processors to use for POW registration", + type=int, + default=defaults.pow_register.num_processes, + ) + register_parser.add_argument( + "--pow_register.update_interval", + "--pow_register.cuda.update_interval", + "--cuda.update_interval", + "-u", + help="The number of nonces to process before checking for next block during registration", + type=int, + default=defaults.pow_register.update_interval, + ) + register_parser.add_argument( + "--pow_register.no_output_in_place", + "--no_output_in_place", + dest="pow_register.output_in_place", + help="Whether to not ouput the registration statistics in-place. Set flag to disable output in-place.", + action="store_false", + required=False, + default=defaults.pow_register.output_in_place, + ) + register_parser.add_argument( + "--pow_register.verbose", + help="Whether to ouput the registration statistics verbosely.", + action="store_true", + required=False, + default=defaults.pow_register.verbose, + ) + + ## Registration args for CUDA registration. + register_parser.add_argument( + "--pow_register.cuda.use_cuda", + "--cuda", + "--cuda.use_cuda", + dest="pow_register.cuda.use_cuda", + default=defaults.pow_register.cuda.use_cuda, + help="""Set flag to use CUDA to register.""", + action="store_true", + required=False, + ) + register_parser.add_argument( + "--pow_register.cuda.no_cuda", + "--no_cuda", + "--cuda.no_cuda", + dest="pow_register.cuda.use_cuda", + default=not defaults.pow_register.cuda.use_cuda, + help="""Set flag to not use CUDA for registration""", + action="store_false", + required=False, + ) + + register_parser.add_argument( + "--pow_register.cuda.dev_id", + "--cuda.dev_id", + type=int, + nargs="+", + default=defaults.pow_register.cuda.dev_id, + help="""Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest).""", + required=False, + ) + register_parser.add_argument( + "--pow_register.cuda.TPB", + "--cuda.TPB", + type=int, + default=defaults.pow_register.cuda.TPB, + help="""Set the number of Threads Per Block for CUDA.""", + required=False, + ) + + bittensor.wallet.add_args(register_parser) + bittensor.subtensor.add_args(register_parser) + + @staticmethod + def check_config(config: "bittensor.config"): + check_netuid_set(config, subtensor=bittensor.subtensor(config=config)) + + if not config.is_set("wallet.name") and not config.no_prompt: + wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) + config.wallet.name = str(wallet_name) + + if not config.is_set("wallet.hotkey") and not config.no_prompt: + hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) + config.wallet.hotkey = str(hotkey) + + if not config.no_prompt: + check_for_cuda_reg_config(config) + + class RunFaucetCommand: @staticmethod def run(cli): @@ -113,17 +248,17 @@ def run(cli): subtensor.run_faucet( wallet=wallet, prompt=not cli.config.no_prompt, - TPB=cli.config.register.cuda.get("TPB", None), - update_interval=cli.config.register.get("update_interval", None), - num_processes=cli.config.register.get("num_processes", None), - cuda=cli.config.register.cuda.get( - "use_cuda", defaults.register.cuda.use_cuda + TPB=cli.config.pow_register.cuda.get("TPB", None), + update_interval=cli.config.pow_register.get("update_interval", None), + num_processes=cli.config.pow_register.get("num_processes", None), + cuda=cli.config.pow_register.cuda.get( + "use_cuda", defaults.pow_register.cuda.use_cuda ), - dev_id=cli.config.register.cuda.get("dev_id", None), - output_in_place=cli.config.register.get( - "output_in_place", defaults.register.output_in_place + dev_id=cli.config.pow_register.cuda.get("dev_id", None), + output_in_place=cli.config.pow_register.get( + "output_in_place", defaults.pow_register.output_in_place ), - log_verbose=cli.config.register.get("verbose", defaults.register.verbose), + log_verbose=cli.config.pow_register.get("verbose", defaults.pow_register.verbose), ) @staticmethod @@ -132,74 +267,74 @@ def add_args(parser: argparse.ArgumentParser): "faucet", help="""Perform PoW to receieve test TAO in your wallet.""" ) run_faucet_parser.add_argument( - "--register.num_processes", + "--faucet.num_processes", "-n", - dest="register.num_processes", + dest="pow_register.num_processes", help="Number of processors to use for POW registration", type=int, - default=defaults.register.num_processes, + default=defaults.pow_register.num_processes, ) run_faucet_parser.add_argument( - "--register.update_interval", - "--register.cuda.update_interval", + "--faucet.update_interval", + "--faucet.cuda.update_interval", "--cuda.update_interval", "-u", help="The number of nonces to process before checking for next block during registration", type=int, - default=defaults.register.update_interval, + default=defaults.pow_register.update_interval, ) run_faucet_parser.add_argument( - "--register.no_output_in_place", + "--faucet.no_output_in_place", "--no_output_in_place", - dest="register.output_in_place", + dest="pow_register.output_in_place", help="Whether to not ouput the registration statistics in-place. Set flag to disable output in-place.", action="store_false", required=False, - default=defaults.register.output_in_place, + default=defaults.pow_register.output_in_place, ) run_faucet_parser.add_argument( - "--register.verbose", + "--faucet.verbose", help="Whether to ouput the registration statistics verbosely.", action="store_true", required=False, - default=defaults.register.verbose, + default=defaults.pow_register.verbose, ) ## Registration args for CUDA registration. run_faucet_parser.add_argument( - "--register.cuda.use_cuda", + "--faucet.cuda.use_cuda", "--cuda", "--cuda.use_cuda", - dest="register.cuda.use_cuda", - default=defaults.register.cuda.use_cuda, - help="""Set flag to use CUDA to register.""", + dest="pow_register.cuda.use_cuda", + default=defaults.pow_register.cuda.use_cuda, + help="""Set flag to use CUDA to pow_register.""", action="store_true", required=False, ) run_faucet_parser.add_argument( - "--register.cuda.no_cuda", + "--faucet.cuda.no_cuda", "--no_cuda", "--cuda.no_cuda", - dest="register.cuda.use_cuda", - default=not defaults.register.cuda.use_cuda, + dest="pow_register.cuda.use_cuda", + default=not defaults.pow_register.cuda.use_cuda, help="""Set flag to not use CUDA for registration""", action="store_false", required=False, ) run_faucet_parser.add_argument( - "--register.cuda.dev_id", + "--faucet.cuda.dev_id", "--cuda.dev_id", type=int, nargs="+", - default=defaults.register.cuda.dev_id, + default=defaults.pow_register.cuda.dev_id, help="""Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest).""", required=False, ) run_faucet_parser.add_argument( - "--register.cuda.TPB", + "--faucet.cuda.TPB", "--cuda.TPB", type=int, - default=defaults.register.cuda.TPB, + default=defaults.pow_register.cuda.TPB, help="""Set the number of Threads Per Block for CUDA.""", required=False, ) diff --git a/bittensor/extrinsics/registration.py b/bittensor/extrinsics/registration.py index 162783cf63..d1f9de3b53 100644 --- a/bittensor/extrinsics/registration.py +++ b/bittensor/extrinsics/registration.py @@ -25,6 +25,197 @@ from bittensor.utils.registration import POWSolution, create_pow +def register_extrinsic( + subtensor: "bittensor.subtensor", + wallet: "bittensor.wallet", + netuid: int, + wait_for_inclusion: bool = False, + wait_for_finalization: bool = True, + prompt: bool = False, + max_allowed_attempts: int = 3, + output_in_place: bool = True, + cuda: bool = False, + dev_id: Union[List[int], int] = 0, + TPB: int = 256, + num_processes: Optional[int] = None, + update_interval: Optional[int] = None, + log_verbose: bool = False, + ) -> bool: + r"""Registers the wallet to chain. + Args: + wallet (bittensor.wallet): + bittensor wallet object. + netuid (int): + The netuid of the subnet to register on. + wait_for_inclusion (bool): + If set, waits for the extrinsic to enter a block before returning true, + or returns false if the extrinsic fails to enter the block within the timeout. + wait_for_finalization (bool): + If set, waits for the extrinsic to be finalized on the chain before returning true, + or returns false if the extrinsic fails to be finalized within the timeout. + prompt (bool): + If true, the call waits for confirmation from the user before proceeding. + max_allowed_attempts (int): + Maximum number of attempts to register the wallet. + cuda (bool): + If true, the wallet should be registered using CUDA device(s). + dev_id (Union[List[int], int]): + The CUDA device id to use, or a list of device ids. + TPB (int): + The number of threads per block (CUDA). + num_processes (int): + The number of processes to use to register. + update_interval (int): + The number of nonces to solve between updates. + log_verbose (bool): + If true, the registration process will log more information. + Returns: + success (bool): + flag is true if extrinsic was finalized or uncluded in the block. + If we did not wait for finalization / inclusion, the response is true. + """ + if not subtensor.subnet_exists(netuid): + bittensor.__console__.print( + ":cross_mark: [red]Failed[/red]: error: [bold white]subnet:{}[/bold white] does not exist.".format( + netuid + ) + ) + return False + + with bittensor.__console__.status( + f":satellite: Checking Account on [bold]subnet:{netuid}[/bold]..." + ): + neuron = subtensor.get_neuron_for_pubkey_and_subnet( + wallet.hotkey.ss58_address, netuid=netuid + ) + if not neuron.is_null: + bittensor.logging.debug( + f"Wallet {wallet} is already registered on {neuron.netuid} with {neuron.uid}" + ) + return True + + if prompt: + if not Confirm.ask( + "Continue Registration?\n hotkey: [bold white]{}[/bold white]\n coldkey: [bold white]{}[/bold white]\n network: [bold white]{}[/bold white]".format( + wallet.hotkey.ss58_address, + wallet.coldkeypub.ss58_address, + subtensor.network, + ) + ): + return False + + # Attempt rolling registration. + attempts = 1 + while True: + bittensor.__console__.print( + ":satellite: Registering...({}/{})".format(attempts, max_allowed_attempts) + ) + # Solve latest POW. + if cuda: + if not torch.cuda.is_available(): + if prompt: + bittensor.__console__.error("CUDA is not available.") + return False + pow_result: Optional[POWSolution] = create_pow( + subtensor, + wallet, + netuid, + output_in_place, + cuda=cuda, + dev_id=dev_id, + TPB=TPB, + num_processes=num_processes, + update_interval=update_interval, + log_verbose=log_verbose, + ) + else: + pow_result: Optional[POWSolution] = create_pow( + subtensor, + wallet, + netuid, + output_in_place, + cuda=cuda, + num_processes=num_processes, + update_interval=update_interval, + log_verbose=log_verbose, + ) + + # pow failed + if not pow_result: + # might be registered already on this subnet + is_registered = subtensor.is_hotkey_registered( + netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address + ) + if is_registered: + bittensor.__console__.print( + f":white_heavy_check_mark: [green]Already registered on netuid:{netuid}[/green]" + ) + return True + + # pow successful, proceed to submit pow to chain for registration + else: + with bittensor.__console__.status(":satellite: Submitting POW..."): + # check if pow result is still valid + while not pow_result.is_stale(subtensor=subtensor): + result: Tuple[bool, Optional[str]] = subtensor._do_pow_register( + netuid=netuid, + wallet=wallet, + pow_result=pow_result, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + success, err_msg = result + + if success != True or success == False: + if "key is already registered" in err_msg: + # Error meant that the key is already registered. + bittensor.__console__.print( + f":white_heavy_check_mark: [green]Already Registered on [bold]subnet:{netuid}[/bold][/green]" + ) + return True + + bittensor.__console__.print( + ":cross_mark: [red]Failed[/red]: error:{}".format(err_msg) + ) + time.sleep(0.5) + + # Successful registration, final check for neuron and pubkey + else: + bittensor.__console__.print(":satellite: Checking Balance...") + is_registered = subtensor.is_hotkey_registered( + netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address + ) + if is_registered: + bittensor.__console__.print( + ":white_heavy_check_mark: [green]Registered[/green]" + ) + return True + else: + # neuron not found, try again + bittensor.__console__.print( + ":cross_mark: [red]Unknown error. Neuron not found.[/red]" + ) + continue + else: + # Exited loop because pow is no longer valid. + bittensor.__console__.print("[red]POW is stale.[/red]") + # Try again. + continue + + if attempts < max_allowed_attempts: + # Failed registration, retry pow + attempts += 1 + bittensor.__console__.print( + ":satellite: Failed registration, retrying pow ...({}/{})".format( + attempts, max_allowed_attempts + ) + ) + else: + # Failed to register after max attempts. + bittensor.__console__.print("[red]No more attempts.[/red]") + return False + + def burned_register_extrinsic( subtensor: "bittensor.subtensor", wallet: "bittensor.wallet", diff --git a/bittensor/mock/subtensor_mock.py b/bittensor/mock/subtensor_mock.py index 6e1a6a2bbe..08ff5a9818 100644 --- a/bittensor/mock/subtensor_mock.py +++ b/bittensor/mock/subtensor_mock.py @@ -1073,6 +1073,28 @@ def _do_transfer( return True, None, None + def _do_pow_register( + self, + netuid: int, + wallet: "wallet", + pow_result: "POWSolution", + wait_for_inclusion: bool = False, + wait_for_finalization: bool = True, + ) -> Tuple[bool, Optional[str]]: + # Assume pow result is valid + + subtensor_state = self.chain_state["SubtensorModule"] + if netuid not in subtensor_state["NetworksAdded"]: + raise Exception("Subnet does not exist") + + self._register_neuron( + netuid=netuid, + hotkey=wallet.hotkey.ss58_address, + coldkey=wallet.coldkeypub.ss58_address, + ) + + return True, None + def _do_burned_register( self, netuid: int, diff --git a/bittensor/subtensor.py b/bittensor/subtensor.py index a29bdb9a7d..fb09f51d9b 100644 --- a/bittensor/subtensor.py +++ b/bittensor/subtensor.py @@ -54,6 +54,7 @@ from .extrinsics.unstaking import unstake_extrinsic, unstake_multiple_extrinsic from .extrinsics.serving import serve_extrinsic, serve_axon_extrinsic from .extrinsics.registration import ( + register_extrinsic, burned_register_extrinsic, run_faucet_extrinsic, ) @@ -424,6 +425,40 @@ def make_substrate_call_with_retry(): ###################### #### Registration #### ###################### + def register( + self, + wallet: "bittensor.wallet", + netuid: int, + wait_for_inclusion: bool = False, + wait_for_finalization: bool = True, + prompt: bool = False, + max_allowed_attempts: int = 3, + output_in_place: bool = True, + cuda: bool = False, + dev_id: Union[List[int], int] = 0, + TPB: int = 256, + num_processes: Optional[int] = None, + update_interval: Optional[int] = None, + log_verbose: bool = False, + ) -> bool: + """Registers the wallet to chain.""" + return register_extrinsic( + subtensor=self, + wallet=wallet, + netuid=netuid, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + prompt=prompt, + 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, + ) + def run_faucet( self, wallet: "bittensor.wallet", diff --git a/tests/integration_tests/test_cli.py b/tests/integration_tests/test_cli.py index fa1b16fc7a..3f872881a5 100644 --- a/tests/integration_tests/test_cli.py +++ b/tests/integration_tests/test_cli.py @@ -1936,6 +1936,31 @@ def test_register(self, _): self.assertTrue(registered) + def test_pow_register(self, _): + config = self.config + config.command = "subnets" + config.subcommand = "pow_register" + config.pow_register.num_processes = 1 + config.pow_register.update_interval = 50_000 + config.no_prompt = True + + mock_wallet = generate_wallet(hotkey=_get_mock_keypair(100, self.id())) + + class MockException(Exception): + pass + + with patch("bittensor.wallet", return_value=mock_wallet) as mock_create_wallet: + with patch( + "bittensor.extrinsics.registration.POWSolution.is_stale", + side_effect=MockException, + ) as mock_is_stale: + with pytest.raises(MockException): + cli = bittensor.cli(config) + cli.run() + mock_create_wallet.assert_called_once() + + self.assertEqual(mock_is_stale.call_count, 1) + def test_stake(self, _): amount_to_stake: Balance = Balance.from_tao(0.5) config = self.config diff --git a/tests/integration_tests/test_cli_no_network.py b/tests/integration_tests/test_cli_no_network.py index 86dfae9140..ee380e2bee 100644 --- a/tests/integration_tests/test_cli_no_network.py +++ b/tests/integration_tests/test_cli_no_network.py @@ -334,6 +334,48 @@ def test_btcli_help(self, _, __): set(extracted_commands) ), "Duplicate commands found in help output" + @patch("torch.cuda.is_available", return_value=True) + def test_register_cuda_use_cuda_flag(self, _, __, patched_sub): + base_args = [ + "subnets", + "pow_register", + "--wallet.path", + "tmp/walletpath", + "--wallet.name", + "mock", + "--wallet.hotkey", + "hk0", + "--no_prompt", + "--cuda.dev_id", + "0", + ] + + patched_sub.return_value = MagicMock( + get_subnets=MagicMock(return_value=[1]), + subnet_exists=MagicMock(return_value=True), + register=MagicMock(side_effect=MockException), + ) + + # Should be able to set true without argument + args = base_args + [ + "--pow_register.cuda.use_cuda", # should be True without any arugment + ] + with pytest.raises(MockException): + cli = bittensor.cli(args=args) + cli.run() + + self.assertEqual(cli.config.pow_register.cuda.get("use_cuda"), True) + + # Should be able to set to false with no argument + + args = base_args + [ + "--pow_register.cuda.no_cuda", + ] + with pytest.raises(MockException): + cli = bittensor.cli(args=args) + cli.run() + + self.assertEqual(cli.config.pow_register.cuda.get("use_cuda"), False) def return_mock_sub_2(*args, **kwargs): return MagicMock( diff --git a/tests/integration_tests/test_subtensor_integration.py b/tests/integration_tests/test_subtensor_integration.py index bc284481d2..195469e822 100644 --- a/tests/integration_tests/test_subtensor_integration.py +++ b/tests/integration_tests/test_subtensor_integration.py @@ -418,6 +418,174 @@ def test_is_hotkey_registered_not_registered(self): ) self.assertFalse(registered, msg="Hotkey should not be registered") + def test_registration_multiprocessed_already_registered(self): + workblocks_before_is_registered = random.randint(5, 10) + # return False each work block but return True after a random number of blocks + is_registered_return_values = ( + [False for _ in range(workblocks_before_is_registered)] + + [True] + + [True, False] + ) + # this should pass the initial False check in the subtensor class and then return True because the neuron is already registered + + mock_neuron = MagicMock() + mock_neuron.is_null = True + + # patch solution queue to return None + with patch( + "multiprocessing.queues.Queue.get", return_value=None + ) as mock_queue_get: + # patch time queue get to raise Empty exception + with patch( + "multiprocessing.queues.Queue.get_nowait", side_effect=QueueEmpty + ) as mock_queue_get_nowait: + wallet = _get_mock_wallet( + hotkey=_get_mock_keypair(0, self.id()), + coldkey=_get_mock_keypair(1, self.id()), + ) + self.subtensor.is_hotkey_registered = MagicMock( + side_effect=is_registered_return_values + ) + + self.subtensor.difficulty = MagicMock(return_value=1) + self.subtensor.get_neuron_for_pubkey_and_subnet = MagicMock( + side_effect=mock_neuron + ) + self.subtensor._do_pow_register = MagicMock(return_value=(True, None)) + + with patch("bittensor.__console__.status") as mock_set_status: + # Need to patch the console status to avoid opening a parallel live display + mock_set_status.__enter__ = MagicMock(return_value=True) + mock_set_status.__exit__ = MagicMock(return_value=True) + + # should return True + assert ( + self.subtensor.register( + wallet=wallet, netuid=3, num_processes=3, update_interval=5 + ) + == True + ) + + # calls until True and once again before exiting subtensor class + # This assertion is currently broken when difficulty is too low + assert ( + self.subtensor.is_hotkey_registered.call_count + == workblocks_before_is_registered + 2 + ) + + def test_registration_partly_failed(self): + do_pow_register_mock = MagicMock( + side_effect=[(False, "Failed"), (False, "Failed"), (True, None)] + ) + + def is_registered_side_effect(*args, **kwargs): + nonlocal do_pow_register_mock + return do_pow_register_mock.call_count < 3 + + current_block = [i for i in range(0, 100)] + + wallet = _get_mock_wallet( + hotkey=_get_mock_keypair(0, self.id()), + coldkey=_get_mock_keypair(1, self.id()), + ) + + self.subtensor.get_neuron_for_pubkey_and_subnet = MagicMock( + return_value=bittensor.NeuronInfo._null_neuron() + ) + self.subtensor.is_hotkey_registered = MagicMock( + side_effect=is_registered_side_effect + ) + + self.subtensor.difficulty = MagicMock(return_value=1) + self.subtensor.get_current_block = MagicMock(side_effect=current_block) + self.subtensor._do_pow_register = do_pow_register_mock + + # should return True + self.assertTrue( + self.subtensor.register( + wallet=wallet, netuid=3, num_processes=3, update_interval=5 + ), + msg="Registration should succeed", + ) + + def test_registration_failed(self): + is_registered_return_values = [False for _ in range(100)] + current_block = [i for i in range(0, 100)] + mock_neuron = MagicMock() + mock_neuron.is_null = True + + with patch( + "bittensor.extrinsics.registration.create_pow", return_value=None + ) as mock_create_pow: + wallet = _get_mock_wallet( + hotkey=_get_mock_keypair(0, self.id()), + coldkey=_get_mock_keypair(1, self.id()), + ) + + self.subtensor.is_hotkey_registered = MagicMock( + side_effect=is_registered_return_values + ) + + self.subtensor.get_current_block = MagicMock(side_effect=current_block) + self.subtensor.get_neuron_for_pubkey_and_subnet = MagicMock( + return_value=mock_neuron + ) + self.subtensor.substrate.get_block_hash = MagicMock( + return_value="0x" + "0" * 64 + ) + self.subtensor._do_pow_register = MagicMock(return_value=(False, "Failed")) + + # should return True + self.assertIsNot( + self.subtensor.register(wallet=wallet, netuid=3), + True, + msg="Registration should fail", + ) + self.assertEqual(mock_create_pow.call_count, 3) + + def test_registration_stale_then_continue(self): + # verifty that after a stale solution, the solve will continue without exiting + + class ExitEarly(Exception): + pass + + mock_is_stale = MagicMock(side_effect=[True, False]) + + mock_do_pow_register = MagicMock(side_effect=ExitEarly()) + + mock_subtensor_self = MagicMock( + neuron_for_pubkey=MagicMock( + return_value=MagicMock(is_null=True) + ), # not registered + _do_pow_register=mock_do_pow_register, + substrate=MagicMock( + get_block_hash=MagicMock(return_value="0x" + "0" * 64), + ), + ) + + mock_wallet = MagicMock() + + mock_create_pow = MagicMock(return_value=MagicMock(is_stale=mock_is_stale)) + + with patch("bittensor.extrinsics.registration.create_pow", mock_create_pow): + # should create a pow and check if it is stale + # then should create a new pow and check if it is stale + # then should enter substrate and exit early because of test + self.subtensor.get_neuron_for_pubkey_and_subnet = MagicMock( + return_value=bittensor.NeuronInfo._null_neuron() + ) + with pytest.raises(ExitEarly): + bittensor.subtensor.register(mock_subtensor_self, mock_wallet, netuid=3) + self.assertEqual( + mock_create_pow.call_count, 2, msg="must try another pow after stale" + ) + self.assertEqual(mock_is_stale.call_count, 2) + self.assertEqual( + mock_do_pow_register.call_count, + 1, + msg="only tries to submit once, then exits", + ) + def test_defaults_to_finney(self): sub = bittensor.subtensor() assert sub.network == "finney" diff --git a/tests/unit_tests/utils/test_utils.py b/tests/unit_tests/utils/test_utils.py index 4496d03cb7..d1d59f0bfc 100644 --- a/tests/unit_tests/utils/test_utils.py +++ b/tests/unit_tests/utils/test_utils.py @@ -584,6 +584,54 @@ def setUp(self) -> None: self._subtensor.reset() self._subtensor.create_subnet(netuid=99) + def test_pow_called_for_cuda(self, mock_cuda_available): + class MockException(Exception): + pass + + mock_pow_register_call = MagicMock(side_effect=MockException) + + mock_subtensor = MockSubtensor() + mock_subtensor.reset() + mock_subtensor.create_subnet(netuid=99) + mock_subtensor.get_neuron_for_pubkey_and_subnet = MagicMock(is_null=True) + mock_subtensor._do_pow_register = mock_pow_register_call + + mock_wallet = SimpleNamespace( + hotkey=bittensor.Keypair.create_from_seed( + "0x" + "0" * 64, ss58_format=bittensor.__ss58_format__ + ), + coldkeypub=SimpleNamespace(ss58_address=""), + ) + + mock_pow_is_stale = MagicMock(return_value=False) + + mock_result = MagicMock( + spec=bittensor.utils.registration.POWSolution, + block_number=1, + nonce=random.randint(0, pow(2, 32)), + difficulty=1, + seal=b"\x00" * 64, + is_stale=mock_pow_is_stale, + ) + + with patch( + "bittensor.extrinsics.registration.create_pow", return_value=mock_result + ) as mock_create_pow: + # Should exit early + with pytest.raises(MockException): + mock_subtensor.register(mock_wallet, netuid=99, cuda=True, prompt=False) + + mock_pow_is_stale.assert_called_once() + mock_create_pow.assert_called_once() + mock_cuda_available.assert_called_once() + + call0 = mock_pow_is_stale.call_args + _, kwargs = call0 + assert kwargs["subtensor"] == mock_subtensor + + mock_pow_register_call.assert_called_once() + _, kwargs = mock_pow_register_call.call_args + kwargs["pow_result"].nonce == mock_result.nonce class TestCUDASolverRun(unittest.TestCase): def test_multi_cuda_run_updates_nonce_start(self): From 4f805a4a2d7d83c67a555141fdfe7aefd056f09e Mon Sep 17 00:00:00 2001 From: ifrit98 Date: Thu, 26 Oct 2023 23:46:58 +0000 Subject: [PATCH 09/11] run black --- bittensor/commands/register.py | 8 +- bittensor/extrinsics/registration.py | 376 +++++++++--------- bittensor/mock/subtensor_mock.py | 40 +- bittensor/subtensor.py | 64 +-- tests/integration_tests/test_cli.py | 46 +-- .../integration_tests/test_cli_no_network.py | 1 + .../test_subtensor_integration.py | 106 ++--- tests/unit_tests/utils/test_utils.py | 95 ++--- 8 files changed, 371 insertions(+), 365 deletions(-) diff --git a/bittensor/commands/register.py b/bittensor/commands/register.py index 2799af2767..7801dfd460 100644 --- a/bittensor/commands/register.py +++ b/bittensor/commands/register.py @@ -132,7 +132,9 @@ def run(cli): output_in_place=cli.config.pow_register.get( "output_in_place", defaults.pow_register.output_in_place ), - log_verbose=cli.config.pow_register.get("verbose", defaults.pow_register.verbose), + log_verbose=cli.config.pow_register.get( + "verbose", defaults.pow_register.verbose + ), ) @staticmethod @@ -258,7 +260,9 @@ def run(cli): output_in_place=cli.config.pow_register.get( "output_in_place", defaults.pow_register.output_in_place ), - log_verbose=cli.config.pow_register.get("verbose", defaults.pow_register.verbose), + log_verbose=cli.config.pow_register.get( + "verbose", defaults.pow_register.verbose + ), ) @staticmethod diff --git a/bittensor/extrinsics/registration.py b/bittensor/extrinsics/registration.py index d1f9de3b53..9799234363 100644 --- a/bittensor/extrinsics/registration.py +++ b/bittensor/extrinsics/registration.py @@ -26,194 +26,194 @@ def register_extrinsic( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", - netuid: int, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, - prompt: bool = False, - max_allowed_attempts: int = 3, - output_in_place: bool = True, - cuda: bool = False, - dev_id: Union[List[int], int] = 0, - TPB: int = 256, - num_processes: Optional[int] = None, - update_interval: Optional[int] = None, - log_verbose: bool = False, - ) -> bool: - r"""Registers the wallet to chain. - Args: - wallet (bittensor.wallet): - bittensor wallet object. - netuid (int): - The netuid of the subnet to register on. - wait_for_inclusion (bool): - If set, waits for the extrinsic to enter a block before returning true, - or returns false if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): - If set, waits for the extrinsic to be finalized on the chain before returning true, - or returns false if the extrinsic fails to be finalized within the timeout. - prompt (bool): - If true, the call waits for confirmation from the user before proceeding. - max_allowed_attempts (int): - Maximum number of attempts to register the wallet. - cuda (bool): - If true, the wallet should be registered using CUDA device(s). - dev_id (Union[List[int], int]): - The CUDA device id to use, or a list of device ids. - TPB (int): - The number of threads per block (CUDA). - num_processes (int): - The number of processes to use to register. - update_interval (int): - The number of nonces to solve between updates. - log_verbose (bool): - If true, the registration process will log more information. - Returns: - success (bool): - flag is true if extrinsic was finalized or uncluded in the block. - If we did not wait for finalization / inclusion, the response is true. - """ - if not subtensor.subnet_exists(netuid): - bittensor.__console__.print( - ":cross_mark: [red]Failed[/red]: error: [bold white]subnet:{}[/bold white] does not exist.".format( - netuid - ) - ) - return False - - with bittensor.__console__.status( - f":satellite: Checking Account on [bold]subnet:{netuid}[/bold]..." - ): - neuron = subtensor.get_neuron_for_pubkey_and_subnet( - wallet.hotkey.ss58_address, netuid=netuid - ) - if not neuron.is_null: - bittensor.logging.debug( - f"Wallet {wallet} is already registered on {neuron.netuid} with {neuron.uid}" - ) - return True - - if prompt: - if not Confirm.ask( - "Continue Registration?\n hotkey: [bold white]{}[/bold white]\n coldkey: [bold white]{}[/bold white]\n network: [bold white]{}[/bold white]".format( - wallet.hotkey.ss58_address, - wallet.coldkeypub.ss58_address, - subtensor.network, - ) - ): - return False - - # Attempt rolling registration. - attempts = 1 - while True: - bittensor.__console__.print( - ":satellite: Registering...({}/{})".format(attempts, max_allowed_attempts) - ) - # Solve latest POW. - if cuda: - if not torch.cuda.is_available(): - if prompt: - bittensor.__console__.error("CUDA is not available.") - return False - pow_result: Optional[POWSolution] = create_pow( - subtensor, - wallet, - netuid, - output_in_place, - cuda=cuda, - dev_id=dev_id, - TPB=TPB, - num_processes=num_processes, - update_interval=update_interval, - log_verbose=log_verbose, - ) - else: - pow_result: Optional[POWSolution] = create_pow( - subtensor, - wallet, - netuid, - output_in_place, - cuda=cuda, - num_processes=num_processes, - update_interval=update_interval, - log_verbose=log_verbose, - ) - - # pow failed - if not pow_result: - # might be registered already on this subnet - is_registered = subtensor.is_hotkey_registered( - netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address - ) - if is_registered: - bittensor.__console__.print( - f":white_heavy_check_mark: [green]Already registered on netuid:{netuid}[/green]" - ) - return True - - # pow successful, proceed to submit pow to chain for registration - else: - with bittensor.__console__.status(":satellite: Submitting POW..."): - # check if pow result is still valid - while not pow_result.is_stale(subtensor=subtensor): - result: Tuple[bool, Optional[str]] = subtensor._do_pow_register( - netuid=netuid, - wallet=wallet, - pow_result=pow_result, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - success, err_msg = result - - if success != True or success == False: - if "key is already registered" in err_msg: - # Error meant that the key is already registered. - bittensor.__console__.print( - f":white_heavy_check_mark: [green]Already Registered on [bold]subnet:{netuid}[/bold][/green]" - ) - return True - - bittensor.__console__.print( - ":cross_mark: [red]Failed[/red]: error:{}".format(err_msg) - ) - time.sleep(0.5) - - # Successful registration, final check for neuron and pubkey - else: - bittensor.__console__.print(":satellite: Checking Balance...") - is_registered = subtensor.is_hotkey_registered( - netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address - ) - if is_registered: - bittensor.__console__.print( - ":white_heavy_check_mark: [green]Registered[/green]" - ) - return True - else: - # neuron not found, try again - bittensor.__console__.print( - ":cross_mark: [red]Unknown error. Neuron not found.[/red]" - ) - continue - else: - # Exited loop because pow is no longer valid. - bittensor.__console__.print("[red]POW is stale.[/red]") - # Try again. - continue - - if attempts < max_allowed_attempts: - # Failed registration, retry pow - attempts += 1 - bittensor.__console__.print( - ":satellite: Failed registration, retrying pow ...({}/{})".format( - attempts, max_allowed_attempts - ) - ) - else: - # Failed to register after max attempts. - bittensor.__console__.print("[red]No more attempts.[/red]") - return False + subtensor: "bittensor.subtensor", + wallet: "bittensor.wallet", + netuid: int, + wait_for_inclusion: bool = False, + wait_for_finalization: bool = True, + prompt: bool = False, + max_allowed_attempts: int = 3, + output_in_place: bool = True, + cuda: bool = False, + dev_id: Union[List[int], int] = 0, + TPB: int = 256, + num_processes: Optional[int] = None, + update_interval: Optional[int] = None, + log_verbose: bool = False, +) -> bool: + r"""Registers the wallet to chain. + Args: + wallet (bittensor.wallet): + bittensor wallet object. + netuid (int): + The netuid of the subnet to register on. + wait_for_inclusion (bool): + If set, waits for the extrinsic to enter a block before returning true, + or returns false if the extrinsic fails to enter the block within the timeout. + wait_for_finalization (bool): + If set, waits for the extrinsic to be finalized on the chain before returning true, + or returns false if the extrinsic fails to be finalized within the timeout. + prompt (bool): + If true, the call waits for confirmation from the user before proceeding. + max_allowed_attempts (int): + Maximum number of attempts to register the wallet. + cuda (bool): + If true, the wallet should be registered using CUDA device(s). + dev_id (Union[List[int], int]): + The CUDA device id to use, or a list of device ids. + TPB (int): + The number of threads per block (CUDA). + num_processes (int): + The number of processes to use to register. + update_interval (int): + The number of nonces to solve between updates. + log_verbose (bool): + If true, the registration process will log more information. + Returns: + success (bool): + flag is true if extrinsic was finalized or uncluded in the block. + If we did not wait for finalization / inclusion, the response is true. + """ + if not subtensor.subnet_exists(netuid): + bittensor.__console__.print( + ":cross_mark: [red]Failed[/red]: error: [bold white]subnet:{}[/bold white] does not exist.".format( + netuid + ) + ) + return False + + with bittensor.__console__.status( + f":satellite: Checking Account on [bold]subnet:{netuid}[/bold]..." + ): + neuron = subtensor.get_neuron_for_pubkey_and_subnet( + wallet.hotkey.ss58_address, netuid=netuid + ) + if not neuron.is_null: + bittensor.logging.debug( + f"Wallet {wallet} is already registered on {neuron.netuid} with {neuron.uid}" + ) + return True + + if prompt: + if not Confirm.ask( + "Continue Registration?\n hotkey: [bold white]{}[/bold white]\n coldkey: [bold white]{}[/bold white]\n network: [bold white]{}[/bold white]".format( + wallet.hotkey.ss58_address, + wallet.coldkeypub.ss58_address, + subtensor.network, + ) + ): + return False + + # Attempt rolling registration. + attempts = 1 + while True: + bittensor.__console__.print( + ":satellite: Registering...({}/{})".format(attempts, max_allowed_attempts) + ) + # Solve latest POW. + if cuda: + if not torch.cuda.is_available(): + if prompt: + bittensor.__console__.error("CUDA is not available.") + return False + pow_result: Optional[POWSolution] = create_pow( + subtensor, + wallet, + netuid, + output_in_place, + cuda=cuda, + dev_id=dev_id, + TPB=TPB, + num_processes=num_processes, + update_interval=update_interval, + log_verbose=log_verbose, + ) + else: + pow_result: Optional[POWSolution] = create_pow( + subtensor, + wallet, + netuid, + output_in_place, + cuda=cuda, + num_processes=num_processes, + update_interval=update_interval, + log_verbose=log_verbose, + ) + + # pow failed + if not pow_result: + # might be registered already on this subnet + is_registered = subtensor.is_hotkey_registered( + netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address + ) + if is_registered: + bittensor.__console__.print( + f":white_heavy_check_mark: [green]Already registered on netuid:{netuid}[/green]" + ) + return True + + # pow successful, proceed to submit pow to chain for registration + else: + with bittensor.__console__.status(":satellite: Submitting POW..."): + # check if pow result is still valid + while not pow_result.is_stale(subtensor=subtensor): + result: Tuple[bool, Optional[str]] = subtensor._do_pow_register( + netuid=netuid, + wallet=wallet, + pow_result=pow_result, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + success, err_msg = result + + if success != True or success == False: + if "key is already registered" in err_msg: + # Error meant that the key is already registered. + bittensor.__console__.print( + f":white_heavy_check_mark: [green]Already Registered on [bold]subnet:{netuid}[/bold][/green]" + ) + return True + + bittensor.__console__.print( + ":cross_mark: [red]Failed[/red]: error:{}".format(err_msg) + ) + time.sleep(0.5) + + # Successful registration, final check for neuron and pubkey + else: + bittensor.__console__.print(":satellite: Checking Balance...") + is_registered = subtensor.is_hotkey_registered( + netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address + ) + if is_registered: + bittensor.__console__.print( + ":white_heavy_check_mark: [green]Registered[/green]" + ) + return True + else: + # neuron not found, try again + bittensor.__console__.print( + ":cross_mark: [red]Unknown error. Neuron not found.[/red]" + ) + continue + else: + # Exited loop because pow is no longer valid. + bittensor.__console__.print("[red]POW is stale.[/red]") + # Try again. + continue + + if attempts < max_allowed_attempts: + # Failed registration, retry pow + attempts += 1 + bittensor.__console__.print( + ":satellite: Failed registration, retrying pow ...({}/{})".format( + attempts, max_allowed_attempts + ) + ) + else: + # Failed to register after max attempts. + bittensor.__console__.print("[red]No more attempts.[/red]") + return False def burned_register_extrinsic( diff --git a/bittensor/mock/subtensor_mock.py b/bittensor/mock/subtensor_mock.py index 08ff5a9818..e91576cb8c 100644 --- a/bittensor/mock/subtensor_mock.py +++ b/bittensor/mock/subtensor_mock.py @@ -1074,26 +1074,26 @@ def _do_transfer( return True, None, None def _do_pow_register( - self, - netuid: int, - wallet: "wallet", - pow_result: "POWSolution", - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, - ) -> Tuple[bool, Optional[str]]: - # Assume pow result is valid - - subtensor_state = self.chain_state["SubtensorModule"] - if netuid not in subtensor_state["NetworksAdded"]: - raise Exception("Subnet does not exist") - - self._register_neuron( - netuid=netuid, - hotkey=wallet.hotkey.ss58_address, - coldkey=wallet.coldkeypub.ss58_address, - ) - - return True, None + self, + netuid: int, + wallet: "wallet", + pow_result: "POWSolution", + wait_for_inclusion: bool = False, + wait_for_finalization: bool = True, + ) -> Tuple[bool, Optional[str]]: + # Assume pow result is valid + + subtensor_state = self.chain_state["SubtensorModule"] + if netuid not in subtensor_state["NetworksAdded"]: + raise Exception("Subnet does not exist") + + self._register_neuron( + netuid=netuid, + hotkey=wallet.hotkey.ss58_address, + coldkey=wallet.coldkeypub.ss58_address, + ) + + return True, None def _do_burned_register( self, diff --git a/bittensor/subtensor.py b/bittensor/subtensor.py index fb09f51d9b..1d37f9457c 100644 --- a/bittensor/subtensor.py +++ b/bittensor/subtensor.py @@ -426,38 +426,38 @@ def make_substrate_call_with_retry(): #### Registration #### ###################### def register( - self, - wallet: "bittensor.wallet", - netuid: int, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, - prompt: bool = False, - max_allowed_attempts: int = 3, - output_in_place: bool = True, - cuda: bool = False, - dev_id: Union[List[int], int] = 0, - TPB: int = 256, - num_processes: Optional[int] = None, - update_interval: Optional[int] = None, - log_verbose: bool = False, - ) -> bool: - """Registers the wallet to chain.""" - return register_extrinsic( - subtensor=self, - wallet=wallet, - netuid=netuid, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - prompt=prompt, - 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, - ) + self, + wallet: "bittensor.wallet", + netuid: int, + wait_for_inclusion: bool = False, + wait_for_finalization: bool = True, + prompt: bool = False, + max_allowed_attempts: int = 3, + output_in_place: bool = True, + cuda: bool = False, + dev_id: Union[List[int], int] = 0, + TPB: int = 256, + num_processes: Optional[int] = None, + update_interval: Optional[int] = None, + log_verbose: bool = False, + ) -> bool: + """Registers the wallet to chain.""" + return register_extrinsic( + subtensor=self, + wallet=wallet, + netuid=netuid, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + prompt=prompt, + 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, + ) def run_faucet( self, diff --git a/tests/integration_tests/test_cli.py b/tests/integration_tests/test_cli.py index 3f872881a5..4dfb4caba9 100644 --- a/tests/integration_tests/test_cli.py +++ b/tests/integration_tests/test_cli.py @@ -1937,29 +1937,29 @@ def test_register(self, _): self.assertTrue(registered) def test_pow_register(self, _): - config = self.config - config.command = "subnets" - config.subcommand = "pow_register" - config.pow_register.num_processes = 1 - config.pow_register.update_interval = 50_000 - config.no_prompt = True - - mock_wallet = generate_wallet(hotkey=_get_mock_keypair(100, self.id())) - - class MockException(Exception): - pass - - with patch("bittensor.wallet", return_value=mock_wallet) as mock_create_wallet: - with patch( - "bittensor.extrinsics.registration.POWSolution.is_stale", - side_effect=MockException, - ) as mock_is_stale: - with pytest.raises(MockException): - cli = bittensor.cli(config) - cli.run() - mock_create_wallet.assert_called_once() - - self.assertEqual(mock_is_stale.call_count, 1) + config = self.config + config.command = "subnets" + config.subcommand = "pow_register" + config.pow_register.num_processes = 1 + config.pow_register.update_interval = 50_000 + config.no_prompt = True + + mock_wallet = generate_wallet(hotkey=_get_mock_keypair(100, self.id())) + + class MockException(Exception): + pass + + with patch("bittensor.wallet", return_value=mock_wallet) as mock_create_wallet: + with patch( + "bittensor.extrinsics.registration.POWSolution.is_stale", + side_effect=MockException, + ) as mock_is_stale: + with pytest.raises(MockException): + cli = bittensor.cli(config) + cli.run() + mock_create_wallet.assert_called_once() + + self.assertEqual(mock_is_stale.call_count, 1) def test_stake(self, _): amount_to_stake: Balance = Balance.from_tao(0.5) diff --git a/tests/integration_tests/test_cli_no_network.py b/tests/integration_tests/test_cli_no_network.py index ee380e2bee..8fc551cde3 100644 --- a/tests/integration_tests/test_cli_no_network.py +++ b/tests/integration_tests/test_cli_no_network.py @@ -377,6 +377,7 @@ def test_register_cuda_use_cuda_flag(self, _, __, patched_sub): self.assertEqual(cli.config.pow_register.cuda.get("use_cuda"), False) + def return_mock_sub_2(*args, **kwargs): return MagicMock( return_value=MagicMock( diff --git a/tests/integration_tests/test_subtensor_integration.py b/tests/integration_tests/test_subtensor_integration.py index 195469e822..e0c633e3b2 100644 --- a/tests/integration_tests/test_subtensor_integration.py +++ b/tests/integration_tests/test_subtensor_integration.py @@ -419,59 +419,59 @@ def test_is_hotkey_registered_not_registered(self): self.assertFalse(registered, msg="Hotkey should not be registered") def test_registration_multiprocessed_already_registered(self): - workblocks_before_is_registered = random.randint(5, 10) - # return False each work block but return True after a random number of blocks - is_registered_return_values = ( - [False for _ in range(workblocks_before_is_registered)] - + [True] - + [True, False] - ) - # this should pass the initial False check in the subtensor class and then return True because the neuron is already registered - - mock_neuron = MagicMock() - mock_neuron.is_null = True - - # patch solution queue to return None - with patch( - "multiprocessing.queues.Queue.get", return_value=None - ) as mock_queue_get: - # patch time queue get to raise Empty exception - with patch( - "multiprocessing.queues.Queue.get_nowait", side_effect=QueueEmpty - ) as mock_queue_get_nowait: - wallet = _get_mock_wallet( - hotkey=_get_mock_keypair(0, self.id()), - coldkey=_get_mock_keypair(1, self.id()), - ) - self.subtensor.is_hotkey_registered = MagicMock( - side_effect=is_registered_return_values - ) - - self.subtensor.difficulty = MagicMock(return_value=1) - self.subtensor.get_neuron_for_pubkey_and_subnet = MagicMock( - side_effect=mock_neuron - ) - self.subtensor._do_pow_register = MagicMock(return_value=(True, None)) - - with patch("bittensor.__console__.status") as mock_set_status: - # Need to patch the console status to avoid opening a parallel live display - mock_set_status.__enter__ = MagicMock(return_value=True) - mock_set_status.__exit__ = MagicMock(return_value=True) - - # should return True - assert ( - self.subtensor.register( - wallet=wallet, netuid=3, num_processes=3, update_interval=5 - ) - == True - ) - - # calls until True and once again before exiting subtensor class - # This assertion is currently broken when difficulty is too low - assert ( - self.subtensor.is_hotkey_registered.call_count - == workblocks_before_is_registered + 2 - ) + workblocks_before_is_registered = random.randint(5, 10) + # return False each work block but return True after a random number of blocks + is_registered_return_values = ( + [False for _ in range(workblocks_before_is_registered)] + + [True] + + [True, False] + ) + # this should pass the initial False check in the subtensor class and then return True because the neuron is already registered + + mock_neuron = MagicMock() + mock_neuron.is_null = True + + # patch solution queue to return None + with patch( + "multiprocessing.queues.Queue.get", return_value=None + ) as mock_queue_get: + # patch time queue get to raise Empty exception + with patch( + "multiprocessing.queues.Queue.get_nowait", side_effect=QueueEmpty + ) as mock_queue_get_nowait: + wallet = _get_mock_wallet( + hotkey=_get_mock_keypair(0, self.id()), + coldkey=_get_mock_keypair(1, self.id()), + ) + self.subtensor.is_hotkey_registered = MagicMock( + side_effect=is_registered_return_values + ) + + self.subtensor.difficulty = MagicMock(return_value=1) + self.subtensor.get_neuron_for_pubkey_and_subnet = MagicMock( + side_effect=mock_neuron + ) + self.subtensor._do_pow_register = MagicMock(return_value=(True, None)) + + with patch("bittensor.__console__.status") as mock_set_status: + # Need to patch the console status to avoid opening a parallel live display + mock_set_status.__enter__ = MagicMock(return_value=True) + mock_set_status.__exit__ = MagicMock(return_value=True) + + # should return True + assert ( + self.subtensor.register( + wallet=wallet, netuid=3, num_processes=3, update_interval=5 + ) + == True + ) + + # calls until True and once again before exiting subtensor class + # This assertion is currently broken when difficulty is too low + assert ( + self.subtensor.is_hotkey_registered.call_count + == workblocks_before_is_registered + 2 + ) def test_registration_partly_failed(self): do_pow_register_mock = MagicMock( diff --git a/tests/unit_tests/utils/test_utils.py b/tests/unit_tests/utils/test_utils.py index d1d59f0bfc..cad54216c4 100644 --- a/tests/unit_tests/utils/test_utils.py +++ b/tests/unit_tests/utils/test_utils.py @@ -585,53 +585,54 @@ def setUp(self) -> None: self._subtensor.create_subnet(netuid=99) def test_pow_called_for_cuda(self, mock_cuda_available): - class MockException(Exception): - pass - - mock_pow_register_call = MagicMock(side_effect=MockException) - - mock_subtensor = MockSubtensor() - mock_subtensor.reset() - mock_subtensor.create_subnet(netuid=99) - mock_subtensor.get_neuron_for_pubkey_and_subnet = MagicMock(is_null=True) - mock_subtensor._do_pow_register = mock_pow_register_call - - mock_wallet = SimpleNamespace( - hotkey=bittensor.Keypair.create_from_seed( - "0x" + "0" * 64, ss58_format=bittensor.__ss58_format__ - ), - coldkeypub=SimpleNamespace(ss58_address=""), - ) - - mock_pow_is_stale = MagicMock(return_value=False) - - mock_result = MagicMock( - spec=bittensor.utils.registration.POWSolution, - block_number=1, - nonce=random.randint(0, pow(2, 32)), - difficulty=1, - seal=b"\x00" * 64, - is_stale=mock_pow_is_stale, - ) - - with patch( - "bittensor.extrinsics.registration.create_pow", return_value=mock_result - ) as mock_create_pow: - # Should exit early - with pytest.raises(MockException): - mock_subtensor.register(mock_wallet, netuid=99, cuda=True, prompt=False) - - mock_pow_is_stale.assert_called_once() - mock_create_pow.assert_called_once() - mock_cuda_available.assert_called_once() - - call0 = mock_pow_is_stale.call_args - _, kwargs = call0 - assert kwargs["subtensor"] == mock_subtensor - - mock_pow_register_call.assert_called_once() - _, kwargs = mock_pow_register_call.call_args - kwargs["pow_result"].nonce == mock_result.nonce + class MockException(Exception): + pass + + mock_pow_register_call = MagicMock(side_effect=MockException) + + mock_subtensor = MockSubtensor() + mock_subtensor.reset() + mock_subtensor.create_subnet(netuid=99) + mock_subtensor.get_neuron_for_pubkey_and_subnet = MagicMock(is_null=True) + mock_subtensor._do_pow_register = mock_pow_register_call + + mock_wallet = SimpleNamespace( + hotkey=bittensor.Keypair.create_from_seed( + "0x" + "0" * 64, ss58_format=bittensor.__ss58_format__ + ), + coldkeypub=SimpleNamespace(ss58_address=""), + ) + + mock_pow_is_stale = MagicMock(return_value=False) + + mock_result = MagicMock( + spec=bittensor.utils.registration.POWSolution, + block_number=1, + nonce=random.randint(0, pow(2, 32)), + difficulty=1, + seal=b"\x00" * 64, + is_stale=mock_pow_is_stale, + ) + + with patch( + "bittensor.extrinsics.registration.create_pow", return_value=mock_result + ) as mock_create_pow: + # Should exit early + with pytest.raises(MockException): + mock_subtensor.register(mock_wallet, netuid=99, cuda=True, prompt=False) + + mock_pow_is_stale.assert_called_once() + mock_create_pow.assert_called_once() + mock_cuda_available.assert_called_once() + + call0 = mock_pow_is_stale.call_args + _, kwargs = call0 + assert kwargs["subtensor"] == mock_subtensor + + mock_pow_register_call.assert_called_once() + _, kwargs = mock_pow_register_call.call_args + kwargs["pow_result"].nonce == mock_result.nonce + class TestCUDASolverRun(unittest.TestCase): def test_multi_cuda_run_updates_nonce_start(self): From 5b8ae6974c2f414143f084c8695e27ee384efec1 Mon Sep 17 00:00:00 2001 From: ifrit98 Date: Fri, 27 Oct 2023 00:03:13 +0000 Subject: [PATCH 10/11] return test to networks choices in btcli, fix chain_endpoint selection --- bittensor/__init__.py | 2 +- bittensor/commands/register.py | 19 +++++++++++ bittensor/subtensor.py | 59 ++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/bittensor/__init__.py b/bittensor/__init__.py index 600e5e8797..93f01c9d84 100644 --- a/bittensor/__init__.py +++ b/bittensor/__init__.py @@ -87,7 +87,7 @@ def debug(on: bool = True): # Wallet ss58 address length __ss58_address_length__ = 48 -__networks__ = ["local", "finney"] +__networks__ = ["local", "finney", "test"] __finney_entrypoint__ = "wss://entrypoint-finney.opentensor.ai:443" diff --git a/bittensor/commands/register.py b/bittensor/commands/register.py index 7801dfd460..32c7f97fc4 100644 --- a/bittensor/commands/register.py +++ b/bittensor/commands/register.py @@ -92,6 +92,10 @@ def check_config(config: "bittensor.config"): choices=bittensor.__networks__, default=defaults.subtensor.network, ) + _, endpoint = bittensor.subtensor.determine_chain_endpoint_and_network( + config.subtensor.network + ) + config.subtensor.chain_endpoint = endpoint check_netuid_set(config, subtensor=bittensor.subtensor(config=config)) @@ -227,6 +231,21 @@ def add_args(parser: argparse.ArgumentParser): @staticmethod def check_config(config: "bittensor.config"): + if ( + not config.is_set("subtensor.network") + and not config.is_set("subtensor.chain_endpoint") + and not config.no_prompt + ): + config.subtensor.network = Prompt.ask( + "Enter subtensor network", + choices=bittensor.__networks__, + default=defaults.subtensor.network, + ) + _, endpoint = bittensor.subtensor.determine_chain_endpoint_and_network( + config.subtensor.network + ) + config.subtensor.chain_endpoint = endpoint + check_netuid_set(config, subtensor=bittensor.subtensor(config=config)) if not config.is_set("wallet.name") and not config.no_prompt: diff --git a/bittensor/subtensor.py b/bittensor/subtensor.py index 1d37f9457c..99b15d5060 100644 --- a/bittensor/subtensor.py +++ b/bittensor/subtensor.py @@ -509,6 +509,65 @@ def burned_register( prompt=prompt, ) + def _do_pow_register( + self, + netuid: int, + wallet: "bittensor.wallet", + pow_result: POWSolution, + wait_for_inclusion: bool = False, + wait_for_finalization: bool = True, + ) -> Tuple[bool, Optional[str]]: + """Sends a (POW) register extrinsic to the chain. + Args: + netuid (int): the subnet to register on. + wallet (bittensor.wallet): the wallet to register. + pow_result (POWSolution): the pow result to register. + wait_for_inclusion (bool): if true, waits for the extrinsic to be included in a block. + wait_for_finalization (bool): if true, waits for the extrinsic to be finalized. + Returns: + success (bool): True if the extrinsic was included in a block. + error (Optional[str]): None on success or not waiting for inclusion/finalization, otherwise the error message. + """ + + @retry(delay=2, tries=3, backoff=2, max_delay=4) + def make_substrate_call_with_retry(): + with self.substrate as substrate: + # create extrinsic call + call = substrate.compose_call( + call_module="SubtensorModule", + call_function="register", + call_params={ + "netuid": netuid, + "block_number": pow_result.block_number, + "nonce": pow_result.nonce, + "work": [int(byte_) for byte_ in pow_result.seal], + "hotkey": wallet.hotkey.ss58_address, + "coldkey": wallet.coldkeypub.ss58_address, + }, + ) + extrinsic = substrate.create_signed_extrinsic( + call=call, keypair=wallet.hotkey + ) + response = substrate.submit_extrinsic( + extrinsic, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + + # We only wait here if we expect finalization. + if not wait_for_finalization and not wait_for_inclusion: + return True, None + + # process if registration successful, try again if pow is still valid + response.process_events() + if not response.is_success: + return False, response.error_message + # Successful registration + else: + return True, None + + return make_substrate_call_with_retry() + def _do_burned_register( self, netuid: int, From 08b58e64e14edcf97a16591fde42aaf50aad4479 Mon Sep 17 00:00:00 2001 From: ifrit98 Date: Fri, 27 Oct 2023 00:08:48 +0000 Subject: [PATCH 11/11] fix pow args --- bittensor/commands/utils.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/bittensor/commands/utils.py b/bittensor/commands/utils.py index b54053622a..732d43d68a 100644 --- a/bittensor/commands/utils.py +++ b/bittensor/commands/utils.py @@ -80,15 +80,15 @@ def check_for_cuda_reg_config(config: "bittensor.config") -> None: """Checks, when CUDA is available, if the user would like to register with their CUDA device.""" if torch.cuda.is_available(): if not config.no_prompt: - if config.register.cuda.get("use_cuda") == None: # flag not set + if config.pow_register.cuda.get("use_cuda") == None: # flag not set # Ask about cuda registration only if a CUDA device is available. cuda = Confirm.ask("Detected CUDA device, use CUDA for registration?\n") - config.register.cuda.use_cuda = cuda + config.pow_register.cuda.use_cuda = cuda # Only ask about which CUDA device if the user has more than one CUDA device. if ( - config.register.cuda.use_cuda - and config.register.cuda.get("dev_id") is None + config.pow_register.cuda.use_cuda + and config.pow_register.cuda.get("dev_id") is None ): devices: List[str] = [str(x) for x in range(torch.cuda.device_count())] device_names: List[str] = [ @@ -122,11 +122,11 @@ def check_for_cuda_reg_config(config: "bittensor.config") -> None: ) ) sys.exit(1) - config.register.cuda.dev_id = dev_id + config.pow_register.cuda.dev_id = dev_id else: # flag was not set, use default value. - if config.register.cuda.get("use_cuda") is None: - config.register.cuda.use_cuda = defaults.register.cuda.use_cuda + if config.pow_register.cuda.get("use_cuda") is None: + config.pow_register.cuda.use_cuda = defaults.pow_register.cuda.use_cuda def get_hotkey_wallets_for_wallet(wallet) -> List["bittensor.wallet"]: