From 14ae9a097fc9c5fbf4096d15b91f2e89849fc83f Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 4 Aug 2022 10:48:12 -0400 Subject: [PATCH 01/19] move cli args to CLI and fix overview --- bittensor/_cli/__init__.py | 23 ++++++++++++++++++++++- bittensor/_cli/cli_impl.py | 27 +++++++++++++-------------- bittensor/_wallet/__init__.py | 9 +++------ 3 files changed, 38 insertions(+), 21 deletions(-) diff --git a/bittensor/_cli/__init__.py b/bittensor/_cli/__init__.py index 765df518cb..d71e02306b 100644 --- a/bittensor/_cli/__init__.py +++ b/bittensor/_cli/__init__.py @@ -88,6 +88,27 @@ def config() -> 'bittensor.config': help='''Set the output width of the overview. Defaults to automatic width from terminal.''', default=None, ) + overview_parser.add_argument( + '--sort_by', + '--wallet.sort_by', + dest='sort_by', + required=False, + action='store', + default="", + type=str, + help='''Sort the hotkeys by the specified column title (e.g. name, uid, axon).''' + ) + overview_parser.add_argument( + '--sort_order', + '--wallet.sort_order', + dest="sort_order", + required=False, + action='store', + default="ascending", + type=str, + help='''Sort the hotkeys in the specified ordering. (ascending/asc or descending/desc/reverse)''' + ) + bittensor.wallet.add_args( overview_parser ) bittensor.subtensor.add_args( overview_parser ) @@ -692,7 +713,7 @@ def check_unstake_config( config: 'bittensor.Config' ): if config.wallet.get('all_hotkeys'): hotkeys = "all hotkeys" elif config.wallet.get('hotkeys'): - hotkeys = str(config.hotkeys).replace('[', '').replace(']', '') + hotkeys = str(config.walet.hotkeys).replace('[', '').replace(']', '') else: hotkeys = str(config.wallet.hotkey) if not Confirm.ask("Unstake all Tao from: [bold]'{}'[/bold]?".format(hotkeys)): diff --git a/bittensor/_cli/cli_impl.py b/bittensor/_cli/cli_impl.py index 54682b1b8f..e82474ee9d 100644 --- a/bittensor/_cli/cli_impl.py +++ b/bittensor/_cli/cli_impl.py @@ -596,24 +596,14 @@ def overview(self): all_hotkeys = [] total_balance = bittensor.Balance(0) - - # We are printing for every wallet. + + # We are printing for every coldkey. if self.config.all: cold_wallets = CLI._get_coldkey_wallets_for_path(self.config.wallet.path) for cold_wallet in tqdm(cold_wallets, desc="Pulling balances"): if cold_wallet.coldkeypub_file.exists_on_device() and not cold_wallet.coldkeypub_file.is_encrypted(): total_balance = total_balance + subtensor.get_balance( cold_wallet.coldkeypub.ss58_address ) all_hotkeys = CLI._get_all_wallets_for_path( self.config.wallet.path ) - - # We are printing for a select number of hotkeys. - elif self.config.wallet.hotkeys: - # Only show hotkeys for wallets in the list - all_hotkeys = [hotkey for hotkey in all_hotkeys if hotkey.hotkey_str in self.config.wallet.hotkeys] - coldkey_wallet = bittensor.wallet( config = self.config ) - if coldkey_wallet.coldkeypub_file.exists_on_device() and not coldkey_wallet.coldkeypub_file.is_encrypted(): - total_balance = subtensor.get_balance( coldkey_wallet.coldkeypub.ss58_address ) - - # We are printing for all keys under the wallet. else: # We are only printing keys for a single coldkey coldkey_wallet = bittensor.wallet( config = self.config ) @@ -624,6 +614,15 @@ def overview(self): return all_hotkeys = CLI._get_hotkey_wallets_for_wallet( coldkey_wallet ) + # We are printing for a select number of hotkeys from all_hotkeys. + if self.config.wallet.hotkeys: + if not self.config.wallet.all_hotkeys: + # We are only showing hotkeys that are specified. + all_hotkeys = [hotkey for hotkey in all_hotkeys if hotkey.hotkey_str in self.config.wallet.hotkeys] + else: + # We are excluding the specified hotkeys from all_hotkeys. + all_hotkeys = [hotkey for hotkey in all_hotkeys if hotkey.hotkey_str not in self.config.wallet.hotkeys] + # Check we have keys to display. if len(all_hotkeys) == 0: console.print("[red]No wallets found.[/red]") @@ -732,8 +731,8 @@ def overview(self): console.clear() - sort_by: str = self.config.wallet.sort_by - sort_order: str = self.config.wallet.sort_order + sort_by: str = self.config.sort_by + sort_order: str = self.config.sort_order if sort_by != "": column_to_sort_by: int = 0 diff --git a/bittensor/_wallet/__init__.py b/bittensor/_wallet/__init__.py index df8fb4fdf7..9849d50664 100644 --- a/bittensor/_wallet/__init__.py +++ b/bittensor/_wallet/__init__.py @@ -111,11 +111,11 @@ def add_args(cls, parser: argparse.ArgumentParser, prefix: str = None ): parser.add_argument('--' + prefix_str + 'wallet.hotkey', required=False, default=bittensor.defaults.wallet.hotkey, help='''The name of wallet's hotkey.''') parser.add_argument('--' + prefix_str + 'wallet.path', required=False, default=bittensor.defaults.wallet.path, help='''The path to your bittensor wallets''') parser.add_argument('--' + prefix_str + 'wallet._mock', action='store_true', default=bittensor.defaults.wallet._mock, help='To turn on wallet mocking for testing purposes.') + parser.add_argument('--' + prefix_str + 'wallet.hotkeys', '--' + prefix_str + 'wallet.exclude_hotkeys', required=False, action='store', default=bittensor.defaults.wallet.hotkeys, type=str, nargs='*', help='''Specify the hotkeys by name. (e.g. hk1 hk2 hk3)''') parser.add_argument('--' + prefix_str + 'wallet.all_hotkeys', required=False, action='store_true', default=bittensor.defaults.wallet.all_hotkeys, help='''To specify all hotkeys. Specifying hotkeys will exclude them from this all.''') - parser.add_argument('--' + prefix_str + 'wallet.sort_by', required=False, action='store', default=bittensor.defaults.wallet.sort_by, type=str, help='''Sort the hotkeys by the specified column title (e.g. name, uid, axon).''') - parser.add_argument('--' + prefix_str + 'wallet.sort_order', required=False, action='store', default=bittensor.defaults.wallet.sort_order, type=str, help='''Sort the hotkeys in the specified ordering. (ascending/asc or descending/desc/reverse)''') parser.add_argument('--' + prefix_str + 'wallet.reregister', required=False, action='store', default=bittensor.defaults.wallet.reregister, type=bool, help='''Whether to reregister the wallet if it is not already registered.''') + except argparse.ArgumentError as e: import pdb #pdb.set_trace() @@ -134,8 +134,6 @@ def add_defaults(cls, defaults): # CLI defaults for Overview defaults.wallet.hotkeys = [] defaults.wallet.all_hotkeys = False - defaults.wallet.sort_by = "" - defaults.wallet.sort_order = "ascending" # Defaults for registration defaults.wallet.reregister = True @@ -148,6 +146,5 @@ def check_config(cls, config: 'bittensor.Config' ): assert isinstance(config.wallet.get('hotkey', bittensor.defaults.wallet.hotkey), str ) or config.wallet.get('hotkey', bittensor.defaults.wallet.hotkey) == None assert isinstance(config.wallet.path, str) assert isinstance(config.wallet.hotkeys, list) - assert isinstance(config.wallet.sort_by, str) - assert isinstance(config.wallet.sort_order, str) assert isinstance(config.wallet.reregister, bool) + assert isinstance(config.wallet.all_hotkeys, bool) From ad6372e22db46886635ad6dd4b97a35f0e820c67 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 4 Aug 2022 10:49:55 -0400 Subject: [PATCH 02/19] use dot get --- bittensor/_cli/cli_impl.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bittensor/_cli/cli_impl.py b/bittensor/_cli/cli_impl.py index e82474ee9d..87e5808246 100644 --- a/bittensor/_cli/cli_impl.py +++ b/bittensor/_cli/cli_impl.py @@ -615,8 +615,9 @@ def overview(self): all_hotkeys = CLI._get_hotkey_wallets_for_wallet( coldkey_wallet ) # We are printing for a select number of hotkeys from all_hotkeys. - if self.config.wallet.hotkeys: - if not self.config.wallet.all_hotkeys: + + if self.config.wallet.get('hotkeys', []): + if not self.config.get('all_hotkeys', False): # We are only showing hotkeys that are specified. all_hotkeys = [hotkey for hotkey in all_hotkeys if hotkey.hotkey_str in self.config.wallet.hotkeys] else: From c81b1814ec9ef36d9deca6c7434e401e54b22712 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 4 Aug 2022 21:08:41 -0400 Subject: [PATCH 03/19] fix tests --- bittensor/_cli/cli_impl.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bittensor/_cli/cli_impl.py b/bittensor/_cli/cli_impl.py index 87e5808246..215a63c944 100644 --- a/bittensor/_cli/cli_impl.py +++ b/bittensor/_cli/cli_impl.py @@ -17,7 +17,7 @@ import os import sys -from typing import List, Union +from typing import List, Union, Optional from cachetools import Cache @@ -732,10 +732,10 @@ def overview(self): console.clear() - sort_by: str = self.config.sort_by - sort_order: str = self.config.sort_order + sort_by: Optional[str] = self.config.get('sort_by', None) + sort_order: Optional[str] = self.config.get('sort_order', None) - if sort_by != "": + if sort_by is not None and sort_by != "": column_to_sort_by: int = 0 highest_matching_ratio: int = 0 sort_descending: bool = False # Default sort_order to ascending From f8e0558f0dc92eae74c6cf8144348696a35f6806 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Fri, 5 Aug 2022 12:08:34 -0400 Subject: [PATCH 04/19] add hotkeys/all_hotkeys to (un)stake --- bittensor/_cli/__init__.py | 44 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/bittensor/_cli/__init__.py b/bittensor/_cli/__init__.py index d71e02306b..4a75b3a623 100644 --- a/bittensor/_cli/__init__.py +++ b/bittensor/_cli/__init__.py @@ -518,6 +518,28 @@ def config() -> 'bittensor.config': help='''Set true to avoid prompting the user.''', default=False, ) + unstake_parser.add_argument( + '--hotkeys', + '--wallet.hotkeys', + '--exclude_hotkeys', + '--wallet.exclude_hotkeys', + dest='hotkeys', + required=False, + action='store', + default=[], + type=str, + nargs='*', + help='''Specify the hotkeys by name. (e.g. hk1 hk2 hk3)''' + ) + unstake_parser.add_argument( + '--all_hotkeys', + '--wallet.all_hotkeys', + dest='all_hotkeys', + required=False, + action='store_true', + default=False, + help='''To specify all hotkeys. Specifying hotkeys will exclude them from this all.''' + ) bittensor.wallet.add_args( unstake_parser ) bittensor.subtensor.add_args( unstake_parser ) @@ -556,6 +578,28 @@ def config() -> 'bittensor.config': help='''Set true to avoid prompting the user.''', default=False, ) + stake_parser.add_argument( + '--hotkeys', + '--wallet.hotkeys', + '--exclude_hotkeys', + '--wallet.exclude_hotkeys', + dest='hotkeys', + required=False, + action='store', + default=[], + type=str, + nargs='*', + help='''Specify the hotkeys by name. (e.g. hk1 hk2 hk3)''' + ) + stake_parser.add_argument( + '--all_hotkeys', + '--wallet.all_hotkeys', + dest='all_hotkeys', + required=False, + action='store_true', + default=False, + help='''To specify all hotkeys. Specifying hotkeys will exclude them from this all.''' + ) bittensor.wallet.add_args( stake_parser ) bittensor.subtensor.add_args( stake_parser ) From 38210a2fa24d8bc7873c3ac8dd31a89f82bd4788 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Fri, 5 Aug 2022 12:44:23 -0400 Subject: [PATCH 05/19] fix default --- bittensor/_cli/cli_impl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/_cli/cli_impl.py b/bittensor/_cli/cli_impl.py index 215a63c944..6af0f90624 100644 --- a/bittensor/_cli/cli_impl.py +++ b/bittensor/_cli/cli_impl.py @@ -331,7 +331,7 @@ def stake( self ): all_hotkeys: List[bittensor.wallet] = self._get_hotkey_wallets_for_wallet( wallet = wallet ) # Exclude hotkeys that are specified. wallets_to_stake_to = [ - wallet for wallet in all_hotkeys if wallet.hotkey_str not in self.config.wallet.get('hotkeys') + wallet for wallet in all_hotkeys if wallet.hotkey_str not in self.config.wallet.get('hotkeys', []) ] elif self.config.wallet.get('hotkeys'): From 79e8a9f912f7d2d72b2c80556343b05d923e3065 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Fri, 5 Aug 2022 13:02:03 -0400 Subject: [PATCH 06/19] fix default in unstake --- bittensor/_cli/cli_impl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/_cli/cli_impl.py b/bittensor/_cli/cli_impl.py index 6af0f90624..e554f782a0 100644 --- a/bittensor/_cli/cli_impl.py +++ b/bittensor/_cli/cli_impl.py @@ -263,7 +263,7 @@ def unstake( self ): all_hotkeys: List[bittensor.wallet] = self._get_hotkey_wallets_for_wallet( wallet = wallet ) # Exclude hotkeys that are specified. wallets_to_unstake_from = [ - wallet for wallet in all_hotkeys if wallet.hotkey_str not in self.config.wallet.get('hotkeys') + wallet for wallet in all_hotkeys if wallet.hotkey_str not in self.config.wallet.get('hotkeys', []) ] elif self.config.wallet.get('hotkeys'): From d324d79276f5e44cb831f3ce154ad14f5edc7e71 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Wed, 10 Aug 2022 17:07:43 -0400 Subject: [PATCH 07/19] add unstake multiple --- bittensor/_cli/cli_impl.py | 15 +-- bittensor/_subtensor/subtensor_impl.py | 146 +++++++++++++++++++++++++ 2 files changed, 150 insertions(+), 11 deletions(-) diff --git a/bittensor/_cli/cli_impl.py b/bittensor/_cli/cli_impl.py index e554f782a0..c1fd705dd4 100644 --- a/bittensor/_cli/cli_impl.py +++ b/bittensor/_cli/cli_impl.py @@ -251,7 +251,6 @@ def transfer( self ): def unstake( self ): r""" Unstake token of amount from hotkey(s). """ - # TODO: Implement this without re-unlocking the coldkey. config = self.config.copy() config.hotkey = None wallet = bittensor.wallet( config = self.config ) @@ -276,9 +275,7 @@ def unstake( self ): subtensor.unstake( wallet, amount = None if self.config.get('unstake_all') else self.config.get('amount'), wait_for_inclusion = True, prompt = not self.config.no_prompt ) return None - wallet_0: 'bittensor.wallet' = wallets_to_unstake_from[0] - # Decrypt coldkey for all wallet(s) to use - wallet_0.coldkey + final_wallets: List['bittensor.wallet'] = [] final_amounts: List[Union[float, Balance]] = [] @@ -287,9 +284,6 @@ def unstake( self ): if not wallet.is_registered(): # Skip unregistered hotkeys. continue - # Assign decrypted coldkey from wallet_0 - # so we don't have to decrypt again - wallet._coldkey = wallet_0._coldkey unstake_amount_tao: float = self.config.get('amount') if self.config.get('max_stake'): @@ -307,13 +301,12 @@ def unstake( self ): if not self.config.no_prompt: if not Confirm.ask("Do you want to unstake from the following keys:\n" + \ "".join([ - f" [bold white]- {wallet.hotkey_str}: {amount}𝜏[/bold white]\n" for wallet, amount in zip(final_wallets, final_amounts) + f" [bold white]- {wallet.hotkey_str}: {amount.tao}𝜏[/bold white]\n" for wallet, amount in zip(final_wallets, final_amounts) ]) ): return None - - for wallet, amount in zip(final_wallets, final_amounts): - subtensor.unstake( wallet, amount = None if self.config.get('unstake_all') else amount, wait_for_inclusion = True, prompt = False ) + + subtensor.unstake_multiple( wallets = final_wallets, amounts = None if self.config.get('unstake_all') else final_amounts, wait_for_inclusion = True, prompt = False ) def stake( self ): diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index e71fcca7fa..a754a8e88e 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -957,6 +957,152 @@ def unstake ( return True return False + + def unstake_multiple ( + self, + wallets: List['bittensor.wallet'], + amounts: List[Union[Balance, float]] = None, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = False, + prompt: bool = False, + ) -> bool: + r""" Removes stake from each wallet coldkey in the list, using each amount. + Args: + wallets (List[bittensor.wallet]): + List of wallets to unstake. + amounts (List[Union[Balance, float]]): + List of amounts to unstake. If None, unstake all. + 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. + Returns: + success (bool): + flag is true if extrinsic was finalized or included in the block. + flag is true if any wallet was unstaked. + If we did not wait for finalization / inclusion, the response is true. + """ + if not isinstance(wallets, list): + raise TypeError("wallets must be a list of bittensor.wallet") + + if len(wallets) == 0: + return True + + if amounts is not None and len(amounts) != len(wallets): + raise ValueError("amounts must be a list of the same length as wallets") + + + wallet_0: 'bittensor.wallet' = wallets[0] + # Decrypt coldkey for all wallet(s) to use + wallet_0.coldkey + + if amounts is None: + amounts = [None] * len(wallets) + + neurons = [] + with bittensor.__console__.status(":satellite: Syncing with chain: [white]{}[/white] ...".format(self.network)): + old_balance = self.get_balance( wallet.coldkey.ss58_address ) + + for wallet in wallets: + neuron = self.neuron_for_pubkey( ss58_hotkey = wallet.hotkey.ss58_address ) + + if neuron.is_null: + neurons.append( None ) + continue + + neurons.append( neuron ) + + successfull_unstakes = 0 + for wallet, amount, neuron in zip(wallets, amounts, neurons): + if neuron is None: + bittensor.__console__.print(":cross_mark: [red]Hotkey: {} is not registered. Skipping ...[/red]".format( wallet.hotkey_str )) + continue + + # Assign decrypted coldkey from wallet_0 + # so we don't have to decrypt again + wallet._coldkey = wallet_0._coldkey + + # Covert to bittensor.Balance + if amount == None: + # Unstake it all. + unstaking_balance = bittensor.Balance.from_tao( neuron.stake ) + elif not isinstance(amount, bittensor.Balance ): + unstaking_balance = bittensor.Balance.from_tao( amount ) + else: + unstaking_balance = amount + + # Check enough to unstake. + stake_on_uid = bittensor.Balance.from_tao( neuron.stake ) + if unstaking_balance > stake_on_uid: + bittensor.__console__.print(":cross_mark: [red]Not enough stake[/red]: [green]{}[/green] to unstake: [blue]{}[/blue] from hotkey: [white]{}[/white]".format(stake_on_uid, unstaking_balance, wallet.hotkey_str)) + continue + + # Estimate unstaking fee. + unstake_fee = None # To be filled. + with bittensor.__console__.status(":satellite: Estimating Staking Fees..."): + with self.substrate as substrate: + call = substrate.compose_call( + call_module='SubtensorModule', + call_function='remove_stake', + call_params={ + 'hotkey': wallet.hotkey.ss58_address, + 'ammount_unstaked': unstaking_balance.rao + } + ) + payment_info = substrate.get_payment_info(call = call, keypair = wallet.coldkey) + if payment_info: + unstake_fee = bittensor.Balance.from_rao(payment_info['partialFee']) + bittensor.__console__.print("[green]Estimated Fee: {}[/green]".format( unstake_fee )) + else: + unstake_fee = bittensor.Balance.from_tao( 0.2 ) + bittensor.__console__.print(":cross_mark: [red]Failed[/red]: could not estimate staking fee, assuming base fee of 0.2") + + # Ask before moving on. + if prompt: + if not Confirm.ask("Do you want to unstake:\n[bold white] amount: {}\n hotkey: {}\n fee: {}[/bold white ]?".format( unstaking_balance, wallet.hotkey_str, unstake_fee) ): + continue + + with bittensor.__console__.status(":satellite: Unstaking from chain: [white]{}[/white] ...".format(self.network)): + with self.substrate as substrate: + call = substrate.compose_call( + call_module='SubtensorModule', + call_function='remove_stake', + call_params={ + 'hotkey': wallet.hotkey.ss58_address, + 'ammount_unstaked': unstaking_balance.rao + } + ) + extrinsic = substrate.create_signed_extrinsic( call = call, keypair = wallet.coldkey ) + 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: + bittensor.__console__.print(":white_heavy_check_mark: [green]Sent[/green]") + successfull_unstakes += 1 + continue + + response.process_events() + if response.is_success: + bittensor.__console__.print(":white_heavy_check_mark: [green]Finalized[/green]") + else: + bittensor.__console__.print(":cross_mark: [red]Failed[/red]: error:{}".format(response.error_message)) + + if response.is_success: + block = self.get_current_block() + new_stake = bittensor.Balance.from_tao( self.neuron_for_uid( uid = neuron.uid, block = block ).stake) + bittensor.__console__.print("Stake ({}): [blue]{}[/blue] :arrow_right: [green]{}[/green]".format( neuron.uid, stake_on_uid, new_stake )) + successfull_unstakes += 1 + + if successfull_unstakes != 0: + with bittensor.__console__.status(":satellite: Checking Balance on: ([white]{}[/white] ...".format(self.network)): + new_balance = self.get_balance( wallet.coldkey.ss58_address ) + bittensor.__console__.print("Balance: [blue]{}[/blue] :arrow_right: [green]{}[/green]".format( old_balance, new_balance )) + return True + + return False def set_weights( self, From 6b772bfd07f89bdd8667698a9dc932356a99aa0e Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Wed, 10 Aug 2022 18:06:33 -0400 Subject: [PATCH 08/19] add add stake multiple --- bittensor/_cli/cli_impl.py | 4 +- bittensor/_subtensor/subtensor_impl.py | 198 ++++++++++++++++++++++++- 2 files changed, 198 insertions(+), 4 deletions(-) diff --git a/bittensor/_cli/cli_impl.py b/bittensor/_cli/cli_impl.py index c1fd705dd4..e595f48a9d 100644 --- a/bittensor/_cli/cli_impl.py +++ b/bittensor/_cli/cli_impl.py @@ -312,7 +312,6 @@ def unstake( self ): def stake( self ): r""" Stake token of amount to hotkey(s). """ - # TODO: Implement this without re-unlocking the coldkey. config = self.config.copy() config.hotkey = None wallet = bittensor.wallet( config = config ) @@ -379,8 +378,7 @@ def stake( self ): ): return None - for wallet, amount in zip(final_wallets, final_amounts): - subtensor.add_stake( wallet, amount = None if self.config.get('stake_all') else amount, wait_for_inclusion = True, prompt = False ) + subtensor.add_stake_multiple( wallets = final_wallets, amounts = None if self.config.get('stake_all') else final_amounts, wait_for_inclusion = True, prompt = False ) def set_weights( self ): diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index a754a8e88e..5968c15e4b 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -729,6 +729,198 @@ def add_stake( return False + def add_stake_multiple ( + self, + wallets: List['bittensor.wallet'], + amounts: List[Union[Balance, float]] = None, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = False, + prompt: bool = False, + ) -> bool: + r""" Adds stake to each wallet hotkey in the list, using each amount, from the common coldkey. + Args: + wallets (List[bittensor.wallet]): + List of wallets to stake. + amounts (List[Union[Balance, float]]): + List of amounts to stake. If None, stake all to the first hotkey. + 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. + Returns: + success (bool): + flag is true if extrinsic was finalized or included in the block. + flag is true if any wallet was staked. + If we did not wait for finalization / inclusion, the response is true. + """ + if not isinstance(wallets, list): + raise TypeError("wallets must be a list of bittensor.wallet") + + if len(wallets) == 0: + return True + + if amounts is not None and len(amounts) != len(wallets): + raise ValueError("amounts must be a list of the same length as wallets") + + if amounts is not None and not all(isinstance(amount, (Balance, float)) for amount in amounts): + raise TypeError("amounts must be a [list of bittensor.Balance or float] or None") + + if amounts is not None and sum(amount.tao for amount in amounts) == 0: + # Staking 0 tao + return True + + wallet_0: 'bittensor.wallet' = wallets[0] + # Decrypt coldkey for all wallet(s) to use + wallet_0.coldkey + + if amounts is None: + amounts = [None] * len(wallets) + else: + # Convert to Balance + amounts = [bittensor.Balance.from_tao(amount) if isinstance(amount, float) else amount for amount in amounts ] + + neurons = [] + with bittensor.__console__.status(":satellite: Syncing with chain: [white]{}[/white] ...".format(self.network)): + old_balance = self.get_balance( wallet.coldkey.ss58_address ) + + for wallet in wallets: + neuron = self.neuron_for_pubkey( ss58_hotkey = wallet.hotkey.ss58_address ) + + if neuron.is_null: + neurons.append( None ) + continue + + neurons.append( neuron ) + + # Remove existential balance to keep key alive. + ## Keys must maintain a balance of at least 1000 rao to stay alive. + total_staking_rao = sum([amount.rao if amount is not None else 0 for amount in amounts]) + if total_staking_rao == 0: + # Staking all to the first wallet. + if old_balance.rao > 1000: + old_balance -= bittensor.Balance.from_rao(1000) + + elif total_staking_rao < 1000: + # Staking less than 1000 rao to the wallets. + pass + else: + # Staking more than 1000 rao to the wallets. + ## Reduce the amount to stake to each wallet to keep the balance above 1000 rao. + percent_reduction = 1 - (1000 / total_staking_rao) + amounts = [amount * percent_reduction for amount in amounts] + + successfull_stakes = 0 + for wallet, amount, neuron in zip(wallets, amounts, neurons): + if neuron is None: + bittensor.__console__.print(":cross_mark: [red]Hotkey: {} is not registered. Skipping ...[/red]".format( wallet.hotkey_str )) + continue + + if wallet.coldkeypub.ss58_address != wallet_0.coldkeypub.ss58_address: + bittensor.__console__.print(":cross_mark: [red]Hotkey: {} is not under the same coldkey. Skipping ...[/red]".format( wallet.hotkey_str )) + continue + + # Assign decrypted coldkey from wallet_0 + # so we don't have to decrypt again + wallet._coldkey = wallet_0._coldkey + staking_all = False + # Convert to bittensor.Balance + if amount == None: + # Stake it all. + staking_balance = bittensor.Balance.from_tao( old_balance.tao ) + staking_all = True + + elif not isinstance(amount, bittensor.Balance ): + staking_balance = bittensor.Balance.from_tao( amount ) + else: + staking_balance = amount + + # Estimate staking fee. + stake_fee = None # To be filled. + with bittensor.__console__.status(":satellite: Estimating Staking Fees..."): + with self.substrate as substrate: + call = substrate.compose_call( + call_module='SubtensorModule', + call_function='add_stake', + call_params={ + 'hotkey': wallet.hotkey.ss58_address, + 'ammount_staked': staking_balance.rao + } + ) + payment_info = substrate.get_payment_info(call = call, keypair = wallet.coldkey) + if payment_info: + stake_fee = bittensor.Balance.from_rao(payment_info['partialFee']) + bittensor.__console__.print("[green]Estimated Fee: {}[/green]".format( stake_fee )) + else: + stake_fee = bittensor.Balance.from_tao( 0.2 ) + bittensor.__console__.print(":cross_mark: [red]Failed[/red]: could not estimate staking fee, assuming base fee of 0.2") + + # Check enough to stake + if staking_all: + staking_balance -= stake_fee + max(staking_balance, bittensor.Balance.from_tao(0)) + + if staking_balance > old_balance - stake_fee: + bittensor.__console__.print(":cross_mark: [red]Not enough balance[/red]: [green]{}[/green] to stake: [blue]{}[/blue] from coldkey: [white]{}[/white]".format(old_balance, staking_balance, wallet.name)) + continue + + # Ask before moving on. + if prompt: + if not Confirm.ask("Do you want to stake:\n[bold white] amount: {}\n hotkey: {}\n fee: {}[/bold white ]?".format( staking_balance, wallet.hotkey_str, stake_fee) ): + continue + + with bittensor.__console__.status(":satellite: Staking to chain: [white]{}[/white] ...".format(self.network)): + with self.substrate as substrate: + call = substrate.compose_call( + call_module='SubtensorModule', + call_function='add_stake', + call_params={ + 'hotkey': wallet.hotkey.ss58_address, + 'ammount_staked': staking_balance.rao + } + ) + extrinsic = substrate.create_signed_extrinsic( call = call, keypair = wallet.coldkey ) + 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: + bittensor.__console__.print(":white_heavy_check_mark: [green]Sent[/green]") + old_balance -= staking_balance + stake_fee + successfull_stakes += 1 + if staking_all: + # If staked all, no need to continue + break + + continue + + response.process_events() + if response.is_success: + bittensor.__console__.print(":white_heavy_check_mark: [green]Finalized[/green]") + else: + bittensor.__console__.print(":cross_mark: [red]Failed[/red]: error:{}".format(response.error_message)) + + if response.is_success: + block = self.get_current_block() + new_stake = bittensor.Balance.from_tao( self.neuron_for_uid( uid = neuron.uid, block = block ).stake) + new_balance = self.get_balance( wallet.coldkey.ss58_address ) + bittensor.__console__.print("Stake ({}): [blue]{}[/blue] :arrow_right: [green]{}[/green]".format( neuron.uid, neuron.stake, new_stake )) + old_balance = new_balance + successfull_stakes += 1 + if staking_all: + # If staked all, no need to continue + break + + if successfull_stakes != 0: + with bittensor.__console__.status(":satellite: Checking Balance on: ([white]{}[/white] ...".format(self.network)): + new_balance = self.get_balance( wallet.coldkey.ss58_address ) + bittensor.__console__.print("Balance: [blue]{}[/blue] :arrow_right: [green]{}[/green]".format( old_balance, new_balance )) + return True + + return False + + def transfer( self, wallet: 'bittensor.wallet', @@ -966,7 +1158,7 @@ def unstake_multiple ( wait_for_finalization: bool = False, prompt: bool = False, ) -> bool: - r""" Removes stake from each wallet coldkey in the list, using each amount. + r""" Removes stake from each wallet hotkey in the list, using each amount, to their common coldkey. Args: wallets (List[bittensor.wallet]): List of wallets to unstake. @@ -1022,6 +1214,10 @@ def unstake_multiple ( bittensor.__console__.print(":cross_mark: [red]Hotkey: {} is not registered. Skipping ...[/red]".format( wallet.hotkey_str )) continue + if wallet.coldkeypub.ss58_address != wallet_0.coldkeypub.ss58_address: + bittensor.__console__.print(":cross_mark: [red]Hotkey: {} is not under the same coldkey. Skipping ...[/red]".format( wallet.hotkey_str )) + continue + # Assign decrypted coldkey from wallet_0 # so we don't have to decrypt again wallet._coldkey = wallet_0._coldkey From 88b1ffa76376bcef580d76745ab7d4d2202c6405 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 11:51:35 -0400 Subject: [PATCH 09/19] move all/hotkeys back to wallet args --- bittensor/_cli/__init__.py | 46 ++------------------------------------ 1 file changed, 2 insertions(+), 44 deletions(-) diff --git a/bittensor/_cli/__init__.py b/bittensor/_cli/__init__.py index 4a75b3a623..9e87419fd3 100644 --- a/bittensor/_cli/__init__.py +++ b/bittensor/_cli/__init__.py @@ -518,28 +518,7 @@ def config() -> 'bittensor.config': help='''Set true to avoid prompting the user.''', default=False, ) - unstake_parser.add_argument( - '--hotkeys', - '--wallet.hotkeys', - '--exclude_hotkeys', - '--wallet.exclude_hotkeys', - dest='hotkeys', - required=False, - action='store', - default=[], - type=str, - nargs='*', - help='''Specify the hotkeys by name. (e.g. hk1 hk2 hk3)''' - ) - unstake_parser.add_argument( - '--all_hotkeys', - '--wallet.all_hotkeys', - dest='all_hotkeys', - required=False, - action='store_true', - default=False, - help='''To specify all hotkeys. Specifying hotkeys will exclude them from this all.''' - ) + bittensor.wallet.add_args( unstake_parser ) bittensor.subtensor.add_args( unstake_parser ) @@ -578,28 +557,7 @@ def config() -> 'bittensor.config': help='''Set true to avoid prompting the user.''', default=False, ) - stake_parser.add_argument( - '--hotkeys', - '--wallet.hotkeys', - '--exclude_hotkeys', - '--wallet.exclude_hotkeys', - dest='hotkeys', - required=False, - action='store', - default=[], - type=str, - nargs='*', - help='''Specify the hotkeys by name. (e.g. hk1 hk2 hk3)''' - ) - stake_parser.add_argument( - '--all_hotkeys', - '--wallet.all_hotkeys', - dest='all_hotkeys', - required=False, - action='store_true', - default=False, - help='''To specify all hotkeys. Specifying hotkeys will exclude them from this all.''' - ) + bittensor.wallet.add_args( stake_parser ) bittensor.subtensor.add_args( stake_parser ) From 4db91c8ba8b0b37ce9b561e06826b2185e41a199 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 12:07:34 -0400 Subject: [PATCH 10/19] convert to balance first add catch for unstake multi --- bittensor/_subtensor/subtensor_impl.py | 33 ++++++++++++++++---------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index 5968c15e4b..6ab2844356 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -769,20 +769,20 @@ def add_stake_multiple ( if amounts is not None and not all(isinstance(amount, (Balance, float)) for amount in amounts): raise TypeError("amounts must be a [list of bittensor.Balance or float] or None") - if amounts is not None and sum(amount.tao for amount in amounts) == 0: - # Staking 0 tao - return True - - wallet_0: 'bittensor.wallet' = wallets[0] - # Decrypt coldkey for all wallet(s) to use - wallet_0.coldkey - if amounts is None: amounts = [None] * len(wallets) else: # Convert to Balance amounts = [bittensor.Balance.from_tao(amount) if isinstance(amount, float) else amount for amount in amounts ] + if sum(amount.tao for amount in amounts) == 0: + # Staking 0 tao + return True + + wallet_0: 'bittensor.wallet' = wallets[0] + # Decrypt coldkey for all wallet(s) to use + wallet_0.coldkey + neurons = [] with bittensor.__console__.status(":satellite: Syncing with chain: [white]{}[/white] ...".format(self.network)): old_balance = self.get_balance( wallet.coldkey.ss58_address ) @@ -920,7 +920,6 @@ def add_stake_multiple ( return False - def transfer( self, wallet: 'bittensor.wallet', @@ -1187,14 +1186,24 @@ def unstake_multiple ( if amounts is not None and len(amounts) != len(wallets): raise ValueError("amounts must be a list of the same length as wallets") + if amounts is not None and not all(isinstance(amount, (Balance, float)) for amount in amounts): + raise TypeError("amounts must be a [list of bittensor.Balance or float] or None") + + if amounts is None: + amounts = [None] * len(wallets) + else: + # Convert to Balance + amounts = [bittensor.Balance.from_tao(amount) if isinstance(amount, float) else amount for amount in amounts ] + + if sum(amount.tao for amount in amounts) == 0: + # Staking 0 tao + return True + wallet_0: 'bittensor.wallet' = wallets[0] # Decrypt coldkey for all wallet(s) to use wallet_0.coldkey - if amounts is None: - amounts = [None] * len(wallets) - neurons = [] with bittensor.__console__.status(":satellite: Syncing with chain: [white]{}[/white] ...".format(self.network)): old_balance = self.get_balance( wallet.coldkey.ss58_address ) From 50abacf2e3be124eef5b171d64cf8066718da787 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 12:08:32 -0400 Subject: [PATCH 11/19] fix ref to wallet --- bittensor/_subtensor/subtensor_impl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index 6ab2844356..ec6403d188 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -785,7 +785,7 @@ def add_stake_multiple ( neurons = [] with bittensor.__console__.status(":satellite: Syncing with chain: [white]{}[/white] ...".format(self.network)): - old_balance = self.get_balance( wallet.coldkey.ss58_address ) + old_balance = self.get_balance( wallet_0.coldkey.ss58_address ) for wallet in wallets: neuron = self.neuron_for_pubkey( ss58_hotkey = wallet.hotkey.ss58_address ) @@ -1206,7 +1206,7 @@ def unstake_multiple ( neurons = [] with bittensor.__console__.status(":satellite: Syncing with chain: [white]{}[/white] ...".format(self.network)): - old_balance = self.get_balance( wallet.coldkey.ss58_address ) + old_balance = self.get_balance( wallet_0.coldkey.ss58_address ) for wallet in wallets: neuron = self.neuron_for_pubkey( ss58_hotkey = wallet.hotkey.ss58_address ) From 74ba3047529b262e42eefc25867659bceb333e27 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 12:32:40 -0400 Subject: [PATCH 12/19] fix test patch for multi hotkeys --- tests/integration_tests/test_cli.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/integration_tests/test_cli.py b/tests/integration_tests/test_cli.py index ef76720630..ae934ac676 100644 --- a/tests/integration_tests/test_cli.py +++ b/tests/integration_tests/test_cli.py @@ -442,7 +442,7 @@ def test_unstake_with_specific_hotkeys( self ): with patch('bittensor.wallet') as mock_create_wallet: mock_create_wallet.side_effect = mock_wallets - with patch('bittensor.Subtensor.unstake', return_value=True) as mock_unstake: + with patch('bittensor.Subtensor.unstake_multiple', return_value=True) as mock_unstake: cli.run() mock_create_wallet.assert_has_calls( [ @@ -500,7 +500,7 @@ def test_unstake_with_all_hotkeys( self ): with patch.object(cli, '_get_hotkey_wallets_for_wallet') as mock_get_all_wallets: mock_get_all_wallets.return_value = mock_wallets - with patch('bittensor.Subtensor.unstake', return_value=True) as mock_unstake: + with patch('bittensor.Subtensor.unstake_multiple', return_value=True) as mock_unstake: cli.run() mock_get_all_wallets.assert_called_once() mock_unstake.assert_has_calls( @@ -553,7 +553,7 @@ def test_unstake_with_exclude_hotkeys_from_all( self ): with patch.object(cli, '_get_hotkey_wallets_for_wallet') as mock_get_all_wallets: mock_get_all_wallets.return_value = mock_wallets - with patch('bittensor.Subtensor.unstake', return_value=True) as mock_unstake: + with patch('bittensor.Subtensor.unstake_multiple', return_value=True) as mock_unstake: cli.run() mock_get_all_wallets.assert_called_once() mock_unstake.assert_has_calls( @@ -628,7 +628,7 @@ def test_unstake_with_multiple_hotkeys_max_stake( self ): with patch('bittensor.wallet') as mock_create_wallet: mock_create_wallet.side_effect = mock_wallets - with patch('bittensor.Subtensor.unstake', return_value=True) as mock_unstake: + with patch('bittensor.Subtensor.unstake_multiple', return_value=True) as mock_unstake: cli.run() mock_create_wallet.assert_has_calls( [ @@ -706,7 +706,7 @@ def test_unstake_with_multiple_hotkeys_max_stake_not_enough_stake( self ): with patch('bittensor.wallet') as mock_create_wallet: mock_create_wallet.side_effect = mock_wallets - with patch('bittensor.Subtensor.unstake', return_value=True) as mock_unstake: + with patch('bittensor.Subtensor.unstake_multiple', return_value=True) as mock_unstake: cli.run() mock_create_wallet.assert_has_calls( [ @@ -782,7 +782,7 @@ def test_stake_with_specific_hotkeys( self ): with patch('bittensor.wallet') as mock_create_wallet: mock_create_wallet.side_effect = mock_wallets - with patch('bittensor.Subtensor.add_stake', return_value=True) as mock_add_stake: + with patch('bittensor.Subtensor.add_stake_multiple', return_value=True) as mock_add_stake: cli.run() mock_create_wallet.assert_has_calls( [ @@ -839,7 +839,7 @@ def test_stake_with_all_hotkeys( self ): with patch.object(cli, '_get_hotkey_wallets_for_wallet') as mock_get_all_wallets: mock_get_all_wallets.return_value = mock_wallets - with patch('bittensor.Subtensor.add_stake', return_value=True) as mock_add_stake: + with patch('bittensor.Subtensor.add_stake_multiple', return_value=True) as mock_add_stake: cli.run() mock_get_all_wallets.assert_called_once() mock_add_stake.assert_has_calls( @@ -891,7 +891,7 @@ def test_stake_with_exclude_hotkeys_from_all( self ): with patch.object(cli, '_get_hotkey_wallets_for_wallet') as mock_get_all_wallets: mock_get_all_wallets.return_value = mock_wallets - with patch('bittensor.Subtensor.add_stake', return_value=True) as mock_add_stake: + with patch('bittensor.Subtensor.add_stake_multiple', return_value=True) as mock_add_stake: cli.run() mock_get_all_wallets.assert_called_once() mock_add_stake.assert_has_calls( @@ -971,7 +971,7 @@ def test_stake_with_multiple_hotkeys_max_stake( self ): with patch('bittensor.wallet') as mock_create_wallet: mock_create_wallet.side_effect = mock_wallets - with patch('bittensor.Subtensor.add_stake', return_value=True) as mock_add_stake: + with patch('bittensor.Subtensor.add_stake_multiple', return_value=True) as mock_add_stake: cli.run() mock_create_wallet.assert_has_calls( [ @@ -1054,7 +1054,7 @@ def test_stake_with_multiple_hotkeys_max_stake_not_enough_balance( self ): with patch('bittensor.wallet') as mock_create_wallet: mock_create_wallet.side_effect = mock_wallets - with patch('bittensor.Subtensor.add_stake', return_value=True) as mock_add_stake: + with patch('bittensor.Subtensor.add_stake_multiple', return_value=True) as mock_add_stake: cli.run() mock_create_wallet.assert_has_calls( [ From 6ff1d441a0713deb2150908f8eeb159a4ba93f5c Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 13:06:51 -0400 Subject: [PATCH 13/19] try to fix tests --- tests/integration_tests/test_cli.py | 53 ++++++++++++++--------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/tests/integration_tests/test_cli.py b/tests/integration_tests/test_cli.py index ae934ac676..8b54efce54 100644 --- a/tests/integration_tests/test_cli.py +++ b/tests/integration_tests/test_cli.py @@ -426,7 +426,7 @@ def test_unstake_with_specific_hotkeys( self ): is_registered = MagicMock( return_value = True ) - ) for hk in config.wallet.hotkeys + ) for hk in config.wallet.hotkeys[1:] ] # The 0th wallet is created twice during unstake @@ -451,7 +451,7 @@ def test_unstake_with_specific_hotkeys( self ): any_order=True ) mock_unstake.assert_has_calls( - [call(mock_wallets[i+1], amount=5.0, wait_for_inclusion=True, prompt=False) for i in range(len(mock_wallets[1:]))], + [call(wallets=mock_wallets, amounts=[5.0]*len(mock_wallets), wait_for_inclusion=True, prompt=False)], any_order = True ) @@ -504,7 +504,7 @@ def test_unstake_with_all_hotkeys( self ): cli.run() mock_get_all_wallets.assert_called_once() mock_unstake.assert_has_calls( - [call(mock_wallets[i+1], amount=5.0, wait_for_inclusion=True, prompt=False) for i in range(len(mock_wallets[1:]))], + [call(wallets=mock_wallets, amounts=[5.0]*len(mock_wallets), wait_for_inclusion=True, prompt=False)], any_order = True ) @@ -557,8 +557,7 @@ def test_unstake_with_exclude_hotkeys_from_all( self ): cli.run() mock_get_all_wallets.assert_called_once() mock_unstake.assert_has_calls( - [call(mock_wallets[i], amount=5.0, wait_for_inclusion=True, prompt=False) - for i in (0, 2) # Don't call for hk1 + [call([mock_wallets[0], mock_wallets[2]], amounts=[5.0, 5.0], wait_for_inclusion=True, prompt=False) ], any_order = True ) @@ -615,7 +614,7 @@ def test_unstake_with_multiple_hotkeys_max_stake( self ): is_registered = MagicMock( return_value = True ) - ) for hk in config.wallet.hotkeys + ) for hk in config.wallet.hotkeys[1:] ] # The 0th wallet is created twice during unstake @@ -637,7 +636,7 @@ def test_unstake_with_multiple_hotkeys_max_stake( self ): any_order=True ) mock_unstake.assert_has_calls( - [call(mock_wallet, amount=CLOSE_IN_VALUE((mock_stakes[mock_wallet.hotkey_str] - config.max_stake).tao, 0.001), wait_for_inclusion=True, prompt=False) for mock_wallet in mock_wallets[1:]], + [call(wallets=mock_wallets, amounts=[CLOSE_IN_VALUE((mock_stakes[mock_wallet.hotkey_str] - config.max_stake).tao, 0.001) for mock_wallet in mock_wallets], wait_for_inclusion=True, prompt=False)], any_order = True ) @@ -693,7 +692,7 @@ def test_unstake_with_multiple_hotkeys_max_stake_not_enough_stake( self ): is_registered = MagicMock( return_value = True ) - ) for hk in config.wallet.hotkeys + ) for hk in config.wallet.hotkeys[1:] ] # The 0th wallet is created twice during unstake @@ -720,10 +719,10 @@ def test_unstake_with_multiple_hotkeys_max_stake_not_enough_stake( self ): ## https://docs.python.org/3.7/library/unittest.mock.html#call ## Uses the 1st index as args list ## call.args only works in Python 3.8+ - mock_wallet = mock_call[1][0] + mock_wallets = mock_call[0][0] # We shouldn't unstake from hk1 as it has less than max_stake staked - assert mock_wallet.hotkey_str != 'hk1' + assert all(mock_wallet.hotkey_str != 'hk1' for mock_wallet in mock_wallets) def test_stake_with_specific_hotkeys( self ): bittensor.subtensor.register = MagicMock(return_value = True) @@ -766,7 +765,7 @@ def test_stake_with_specific_hotkeys( self ): is_registered = MagicMock( return_value = True ) - ) for hk in config.wallet.hotkeys + ) for hk in config.wallet.hotkeys[1:] ] # The 0th wallet is created twice during unstake @@ -791,7 +790,7 @@ def test_stake_with_specific_hotkeys( self ): any_order=True ) mock_add_stake.assert_has_calls( - [call(mock_wallets[i+1], amount=5.0, wait_for_inclusion=True, prompt=False) for i in range(len(mock_wallets[1:]))], + [call(wallets=mock_wallets, amounts=[5.0] * (len(mock_wallets) - 1), wait_for_inclusion=True, prompt=False)], any_order = True ) @@ -826,7 +825,6 @@ def test_stake_with_all_hotkeys( self ): ) for hk in mock_hotkeys ] - # The 0th wallet is created twice during unstake mock_wallets[0]._coldkey = mock_coldkey mock_wallets[0].coldkey = MagicMock( return_value=mock_coldkey @@ -843,7 +841,7 @@ def test_stake_with_all_hotkeys( self ): cli.run() mock_get_all_wallets.assert_called_once() mock_add_stake.assert_has_calls( - [call(mock_wallets[i+1], amount=5.0, wait_for_inclusion=True, prompt=False) for i in range(len(mock_wallets[1:]))], + [call(wallets=mock_wallets, amounts=[5.0] * len(mock_wallets - 1), wait_for_inclusion=True, prompt=False)], any_order = True ) @@ -895,9 +893,7 @@ def test_stake_with_exclude_hotkeys_from_all( self ): cli.run() mock_get_all_wallets.assert_called_once() mock_add_stake.assert_has_calls( - [call(mock_wallets[i], amount=5.0, wait_for_inclusion=True, prompt=False) - for i in (0, 2) # Don't call stake for hk1 - ], + [call(wallets=[mock_wallets[0], mock_wallets[2]], amounts=[5.0, 5.0], wait_for_inclusion=True, prompt=False)], any_order = True ) @@ -955,7 +951,7 @@ def test_stake_with_multiple_hotkeys_max_stake( self ): is_registered = MagicMock( return_value = True ) - ) for hk in config.wallet.hotkeys + ) for hk in config.wallet.hotkeys[1:] ] # The 0th wallet is created twice during unstake @@ -980,7 +976,7 @@ def test_stake_with_multiple_hotkeys_max_stake( self ): any_order=True ) mock_add_stake.assert_has_calls( - [call(mock_wallet, amount=CLOSE_IN_VALUE((config.max_stake - mock_stakes[mock_wallet.hotkey_str].tao), 0.001), wait_for_inclusion=True, prompt=False) for mock_wallet in mock_wallets[1:]], + [call(wallets=mock_wallets, amounts=[CLOSE_IN_VALUE((config.max_stake - mock_stakes[mock_wallet.hotkey_str].tao), 0.001) for mock_wallet in mock_wallets], wait_for_inclusion=True, prompt=False)], any_order = True ) @@ -1038,7 +1034,7 @@ def test_stake_with_multiple_hotkeys_max_stake_not_enough_balance( self ): is_registered = MagicMock( return_value = True ) - ) for hk in config.wallet.hotkeys + ) for hk in config.wallet.hotkeys[1:] ] # The 0th wallet is created twice during unstake @@ -1066,14 +1062,15 @@ def test_stake_with_multiple_hotkeys_max_stake_not_enough_balance( self ): mock_add_stake.assert_called_once() total_staked = 0.0 - for mock_call in mock_add_stake.mock_calls: - # Python 3.7 - ## https://docs.python.org/3.7/library/unittest.mock.html#call - ## Uses the 2nd index as kwargs dict - ## call.kwargs only works in Python 3.8+ - staked_this_call = mock_call[2]['amount'] - - total_staked += staked_this_call + + # Python 3.7 + ## https://docs.python.org/3.7/library/unittest.mock.html#call + ## Uses the 2nd index as kwargs dict + ## call.kwargs only works in Python 3.8+ + amounts_passed = mock_add_stake.mock_calls[0][2]['amounts'] + + total_staked = sum(amounts_passed) + # We should not try to stake more than the mock_balance assert CLOSE_IN_VALUE(total_staked, 0.001) == mock_balance.tao From d4c94f42f5e814f34b417013b627305d2545cf94 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 13:28:53 -0400 Subject: [PATCH 14/19] fix tests patch --- tests/integration_tests/test_cli.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tests/integration_tests/test_cli.py b/tests/integration_tests/test_cli.py index 8b54efce54..a2be203072 100644 --- a/tests/integration_tests/test_cli.py +++ b/tests/integration_tests/test_cli.py @@ -557,8 +557,7 @@ def test_unstake_with_exclude_hotkeys_from_all( self ): cli.run() mock_get_all_wallets.assert_called_once() mock_unstake.assert_has_calls( - [call([mock_wallets[0], mock_wallets[2]], amounts=[5.0, 5.0], wait_for_inclusion=True, prompt=False) - ], + [call(wallets=[mock_wallets[0], mock_wallets[2]], amounts=[5.0, 5.0], wait_for_inclusion=True, prompt=False)], any_order = True ) @@ -625,7 +624,7 @@ def test_unstake_with_multiple_hotkeys_max_stake( self ): cli = bittensor.cli(config) - with patch('bittensor.wallet') as mock_create_wallet: + with patch('bittensor.wallet.__new__') as mock_create_wallet: mock_create_wallet.side_effect = mock_wallets with patch('bittensor.Subtensor.unstake_multiple', return_value=True) as mock_unstake: cli.run() @@ -703,7 +702,7 @@ def test_unstake_with_multiple_hotkeys_max_stake_not_enough_stake( self ): cli = bittensor.cli(config) - with patch('bittensor.wallet') as mock_create_wallet: + with patch('bittensor.wallet.__new__') as mock_create_wallet: mock_create_wallet.side_effect = mock_wallets with patch('bittensor.Subtensor.unstake_multiple', return_value=True) as mock_unstake: cli.run() @@ -779,7 +778,7 @@ def test_stake_with_specific_hotkeys( self ): cli = bittensor.cli(config) - with patch('bittensor.wallet') as mock_create_wallet: + with patch('bittensor.wallet.__new__') as mock_create_wallet: mock_create_wallet.side_effect = mock_wallets with patch('bittensor.Subtensor.add_stake_multiple', return_value=True) as mock_add_stake: cli.run() @@ -790,7 +789,7 @@ def test_stake_with_specific_hotkeys( self ): any_order=True ) mock_add_stake.assert_has_calls( - [call(wallets=mock_wallets, amounts=[5.0] * (len(mock_wallets) - 1), wait_for_inclusion=True, prompt=False)], + [call(wallets=mock_wallets, amounts=[5.0] * len(mock_wallets), wait_for_inclusion=True, prompt=False)], any_order = True ) @@ -841,7 +840,7 @@ def test_stake_with_all_hotkeys( self ): cli.run() mock_get_all_wallets.assert_called_once() mock_add_stake.assert_has_calls( - [call(wallets=mock_wallets, amounts=[5.0] * len(mock_wallets - 1), wait_for_inclusion=True, prompt=False)], + [call(wallets=mock_wallets, amounts=[5.0] * len(mock_wallets), wait_for_inclusion=True, prompt=False)], any_order = True ) @@ -965,7 +964,7 @@ def test_stake_with_multiple_hotkeys_max_stake( self ): cli = bittensor.cli(config) - with patch('bittensor.wallet') as mock_create_wallet: + with patch('bittensor.wallet.__new__') as mock_create_wallet: mock_create_wallet.side_effect = mock_wallets with patch('bittensor.Subtensor.add_stake_multiple', return_value=True) as mock_add_stake: cli.run() @@ -1048,7 +1047,7 @@ def test_stake_with_multiple_hotkeys_max_stake_not_enough_balance( self ): cli = bittensor.cli(config) - with patch('bittensor.wallet') as mock_create_wallet: + with patch('bittensor.wallet.__new__') as mock_create_wallet: mock_create_wallet.side_effect = mock_wallets with patch('bittensor.Subtensor.add_stake_multiple', return_value=True) as mock_add_stake: cli.run() From 0f8b67a8ee8350776b2214dbe7bc5a7ee6496edc Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 13:46:49 -0400 Subject: [PATCH 15/19] fix mock wallet length --- tests/integration_tests/test_cli.py | 37 +++++++++++++++-------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/tests/integration_tests/test_cli.py b/tests/integration_tests/test_cli.py index a2be203072..e162c1dcb3 100644 --- a/tests/integration_tests/test_cli.py +++ b/tests/integration_tests/test_cli.py @@ -426,7 +426,7 @@ def test_unstake_with_specific_hotkeys( self ): is_registered = MagicMock( return_value = True ) - ) for hk in config.wallet.hotkeys[1:] + ) for hk in config.wallet.hotkeys ] # The 0th wallet is created twice during unstake @@ -451,7 +451,7 @@ def test_unstake_with_specific_hotkeys( self ): any_order=True ) mock_unstake.assert_has_calls( - [call(wallets=mock_wallets, amounts=[5.0]*len(mock_wallets), wait_for_inclusion=True, prompt=False)], + [call(wallets=mock_wallets[1:], amounts=[5.0]*len(mock_wallets[1:]), wait_for_inclusion=True, prompt=False)], any_order = True ) @@ -613,7 +613,7 @@ def test_unstake_with_multiple_hotkeys_max_stake( self ): is_registered = MagicMock( return_value = True ) - ) for hk in config.wallet.hotkeys[1:] + ) for hk in config.wallet.hotkeys ] # The 0th wallet is created twice during unstake @@ -635,7 +635,7 @@ def test_unstake_with_multiple_hotkeys_max_stake( self ): any_order=True ) mock_unstake.assert_has_calls( - [call(wallets=mock_wallets, amounts=[CLOSE_IN_VALUE((mock_stakes[mock_wallet.hotkey_str] - config.max_stake).tao, 0.001) for mock_wallet in mock_wallets], wait_for_inclusion=True, prompt=False)], + [call(wallets=mock_wallets[1:], amounts=[CLOSE_IN_VALUE((mock_stakes[mock_wallet.hotkey_str] - config.max_stake).tao, 0.001) for mock_wallet in mock_wallets[1:]], wait_for_inclusion=True, prompt=False)], any_order = True ) @@ -691,7 +691,7 @@ def test_unstake_with_multiple_hotkeys_max_stake_not_enough_stake( self ): is_registered = MagicMock( return_value = True ) - ) for hk in config.wallet.hotkeys[1:] + ) for hk in config.wallet.hotkeys ] # The 0th wallet is created twice during unstake @@ -713,15 +713,16 @@ def test_unstake_with_multiple_hotkeys_max_stake_not_enough_stake( self ): any_order=True ) mock_unstake.assert_called() - for mock_call in mock_unstake.mock_calls: - # Python 3.7 - ## https://docs.python.org/3.7/library/unittest.mock.html#call - ## Uses the 1st index as args list - ## call.args only works in Python 3.8+ - mock_wallets = mock_call[0][0] - # We shouldn't unstake from hk1 as it has less than max_stake staked - assert all(mock_wallet.hotkey_str != 'hk1' for mock_wallet in mock_wallets) + # Python 3.7 + ## https://docs.python.org/3.7/library/unittest.mock.html#call + ## Uses the 1st index as args list + ## call.args only works in Python 3.8+ + mock_wallets_ = mock_unstake.mock_calls[0][0][0] + + + # We shouldn't unstake from hk1 as it has less than max_stake staked + assert all(mock_wallet.hotkey_str != 'hk1' for mock_wallet in mock_wallets_) def test_stake_with_specific_hotkeys( self ): bittensor.subtensor.register = MagicMock(return_value = True) @@ -764,7 +765,7 @@ def test_stake_with_specific_hotkeys( self ): is_registered = MagicMock( return_value = True ) - ) for hk in config.wallet.hotkeys[1:] + ) for hk in config.wallet.hotkeys ] # The 0th wallet is created twice during unstake @@ -789,7 +790,7 @@ def test_stake_with_specific_hotkeys( self ): any_order=True ) mock_add_stake.assert_has_calls( - [call(wallets=mock_wallets, amounts=[5.0] * len(mock_wallets), wait_for_inclusion=True, prompt=False)], + [call(wallets=mock_wallets[1:], amounts=[5.0] * len(mock_wallets[1:]), wait_for_inclusion=True, prompt=False)], any_order = True ) @@ -950,7 +951,7 @@ def test_stake_with_multiple_hotkeys_max_stake( self ): is_registered = MagicMock( return_value = True ) - ) for hk in config.wallet.hotkeys[1:] + ) for hk in config.wallet.hotkeys ] # The 0th wallet is created twice during unstake @@ -975,7 +976,7 @@ def test_stake_with_multiple_hotkeys_max_stake( self ): any_order=True ) mock_add_stake.assert_has_calls( - [call(wallets=mock_wallets, amounts=[CLOSE_IN_VALUE((config.max_stake - mock_stakes[mock_wallet.hotkey_str].tao), 0.001) for mock_wallet in mock_wallets], wait_for_inclusion=True, prompt=False)], + [call(wallets=mock_wallets[1:], amounts=[CLOSE_IN_VALUE((config.max_stake - mock_stakes[mock_wallet.hotkey_str].tao), 0.001) for mock_wallet in mock_wallets[1:]], wait_for_inclusion=True, prompt=False)], any_order = True ) @@ -1033,7 +1034,7 @@ def test_stake_with_multiple_hotkeys_max_stake_not_enough_balance( self ): is_registered = MagicMock( return_value = True ) - ) for hk in config.wallet.hotkeys[1:] + ) for hk in config.wallet.hotkeys ] # The 0th wallet is created twice during unstake From a2d43b7c568e9048ab0b6d0b63b1ddba49749764 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 14:00:26 -0400 Subject: [PATCH 16/19] don't use new? --- tests/integration_tests/test_cli.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/integration_tests/test_cli.py b/tests/integration_tests/test_cli.py index e162c1dcb3..31f2c4ccae 100644 --- a/tests/integration_tests/test_cli.py +++ b/tests/integration_tests/test_cli.py @@ -624,7 +624,7 @@ def test_unstake_with_multiple_hotkeys_max_stake( self ): cli = bittensor.cli(config) - with patch('bittensor.wallet.__new__') as mock_create_wallet: + with patch('bittensor.wallet') as mock_create_wallet: mock_create_wallet.side_effect = mock_wallets with patch('bittensor.Subtensor.unstake_multiple', return_value=True) as mock_unstake: cli.run() @@ -702,7 +702,7 @@ def test_unstake_with_multiple_hotkeys_max_stake_not_enough_stake( self ): cli = bittensor.cli(config) - with patch('bittensor.wallet.__new__') as mock_create_wallet: + with patch('bittensor.wallet') as mock_create_wallet: mock_create_wallet.side_effect = mock_wallets with patch('bittensor.Subtensor.unstake_multiple', return_value=True) as mock_unstake: cli.run() @@ -779,7 +779,7 @@ def test_stake_with_specific_hotkeys( self ): cli = bittensor.cli(config) - with patch('bittensor.wallet.__new__') as mock_create_wallet: + with patch('bittensor.wallet') as mock_create_wallet: mock_create_wallet.side_effect = mock_wallets with patch('bittensor.Subtensor.add_stake_multiple', return_value=True) as mock_add_stake: cli.run() @@ -965,7 +965,7 @@ def test_stake_with_multiple_hotkeys_max_stake( self ): cli = bittensor.cli(config) - with patch('bittensor.wallet.__new__') as mock_create_wallet: + with patch('bittensor.wallet') as mock_create_wallet: mock_create_wallet.side_effect = mock_wallets with patch('bittensor.Subtensor.add_stake_multiple', return_value=True) as mock_add_stake: cli.run() @@ -1048,7 +1048,7 @@ def test_stake_with_multiple_hotkeys_max_stake_not_enough_balance( self ): cli = bittensor.cli(config) - with patch('bittensor.wallet.__new__') as mock_create_wallet: + with patch('bittensor.wallet') as mock_create_wallet: mock_create_wallet.side_effect = mock_wallets with patch('bittensor.Subtensor.add_stake_multiple', return_value=True) as mock_add_stake: cli.run() @@ -1262,7 +1262,7 @@ def test_inspect( self ): def test_list( self ): # Mock IO for wallet - with patch('bittensor.wallet.__new__', side_effect=[MagicMock( + with patch('bittensor.wallet', side_effect=[MagicMock( coldkeypub_file=MagicMock( exists_on_device=MagicMock( return_value=True # Wallet exists From 1f23cba97e1482d2c04899380ad1d035eadb3862 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 14:09:14 -0400 Subject: [PATCH 17/19] fix call args get --- tests/integration_tests/test_cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration_tests/test_cli.py b/tests/integration_tests/test_cli.py index 31f2c4ccae..d8276422e0 100644 --- a/tests/integration_tests/test_cli.py +++ b/tests/integration_tests/test_cli.py @@ -718,7 +718,7 @@ def test_unstake_with_multiple_hotkeys_max_stake_not_enough_stake( self ): ## https://docs.python.org/3.7/library/unittest.mock.html#call ## Uses the 1st index as args list ## call.args only works in Python 3.8+ - mock_wallets_ = mock_unstake.mock_calls[0][0][0] + mock_wallets_ = mock_unstake.mock_calls[0][2]['wallets'] # We shouldn't unstake from hk1 as it has less than max_stake staked From a13c412929d94a155a0b409ca7c36b8790511bc8 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 14:22:57 -0400 Subject: [PATCH 18/19] typo --- bittensor/_cli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/_cli/__init__.py b/bittensor/_cli/__init__.py index 3674de4d49..97ccd03ead 100644 --- a/bittensor/_cli/__init__.py +++ b/bittensor/_cli/__init__.py @@ -719,7 +719,7 @@ def check_unstake_config( config: 'bittensor.Config' ): if config.wallet.get('all_hotkeys'): hotkeys = "all hotkeys" elif config.wallet.get('hotkeys'): - hotkeys = str(config.walet.hotkeys).replace('[', '').replace(']', '') + hotkeys = str(config.wallet.hotkeys).replace('[', '').replace(']', '') else: hotkeys = str(config.wallet.hotkey) if not Confirm.ask("Unstake all Tao from: [bold]'{}'[/bold]?".format(hotkeys)): From 5434765d6a7ad9f18d93e4c02aaf975836622289 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 14:35:25 -0400 Subject: [PATCH 19/19] fix typo --- bittensor/_subtensor/subtensor_impl.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index b87b087e76..baac5d3b70 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -846,7 +846,7 @@ def add_stake_multiple ( percent_reduction = 1 - (1000 / total_staking_rao) amounts = [amount * percent_reduction for amount in amounts] - successfull_stakes = 0 + successful_stakes = 0 for wallet, amount, neuron in zip(wallets, amounts, neurons): if neuron is None: bittensor.__console__.print(":cross_mark: [red]Hotkey: {} is not registered. Skipping ...[/red]".format( wallet.hotkey_str )) @@ -921,7 +921,7 @@ def add_stake_multiple ( if not wait_for_finalization and not wait_for_inclusion: bittensor.__console__.print(":white_heavy_check_mark: [green]Sent[/green]") old_balance -= staking_balance + stake_fee - successfull_stakes += 1 + successful_stakes += 1 if staking_all: # If staked all, no need to continue break @@ -940,12 +940,12 @@ def add_stake_multiple ( new_balance = self.get_balance( wallet.coldkey.ss58_address ) bittensor.__console__.print("Stake ({}): [blue]{}[/blue] :arrow_right: [green]{}[/green]".format( neuron.uid, neuron.stake, new_stake )) old_balance = new_balance - successfull_stakes += 1 + successful_stakes += 1 if staking_all: # If staked all, no need to continue break - if successfull_stakes != 0: + if successful_stakes != 0: with bittensor.__console__.status(":satellite: Checking Balance on: ([white]{}[/white] ...".format(self.network)): new_balance = self.get_balance( wallet.coldkey.ss58_address ) bittensor.__console__.print("Balance: [blue]{}[/blue] :arrow_right: [green]{}[/green]".format( old_balance, new_balance )) @@ -1250,7 +1250,7 @@ def unstake_multiple ( neurons.append( neuron ) - successfull_unstakes = 0 + successful_unstakes = 0 for wallet, amount, neuron in zip(wallets, amounts, neurons): if neuron is None: bittensor.__console__.print(":cross_mark: [red]Hotkey: {} is not registered. Skipping ...[/red]".format( wallet.hotkey_str )) @@ -1319,7 +1319,7 @@ def unstake_multiple ( # We only wait here if we expect finalization. if not wait_for_finalization and not wait_for_inclusion: bittensor.__console__.print(":white_heavy_check_mark: [green]Sent[/green]") - successfull_unstakes += 1 + successful_unstakes += 1 continue response.process_events() @@ -1332,9 +1332,9 @@ def unstake_multiple ( block = self.get_current_block() new_stake = bittensor.Balance.from_tao( self.neuron_for_uid( uid = neuron.uid, block = block ).stake) bittensor.__console__.print("Stake ({}): [blue]{}[/blue] :arrow_right: [green]{}[/green]".format( neuron.uid, stake_on_uid, new_stake )) - successfull_unstakes += 1 + successful_unstakes += 1 - if successfull_unstakes != 0: + if successful_unstakes != 0: with bittensor.__console__.status(":satellite: Checking Balance on: ([white]{}[/white] ...".format(self.network)): new_balance = self.get_balance( wallet.coldkey.ss58_address ) bittensor.__console__.print("Balance: [blue]{}[/blue] :arrow_right: [green]{}[/green]".format( old_balance, new_balance ))