From 3ff642245b14ef7edb935952c0ebdf8de321379d Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Tue, 15 Nov 2022 16:12:18 -0500 Subject: [PATCH 01/11] fix max stake for a single key --- bittensor/_cli/cli_impl.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/bittensor/_cli/cli_impl.py b/bittensor/_cli/cli_impl.py index 8309181ee4..f3f8a76fd6 100644 --- a/bittensor/_cli/cli_impl.py +++ b/bittensor/_cli/cli_impl.py @@ -347,9 +347,7 @@ def stake( self ): bittensor.wallet( config = self.config, hotkey = hotkey ) for hotkey in self.config.wallet.get('hotkeys') ] else: - # Do regular stake - subtensor.add_stake( wallet, amount = None if self.config.get('stake_all') else self.config.get('amount'), wait_for_inclusion = True, prompt = not self.config.no_prompt ) - return None + wallets_to_stake_to = [ bittensor.wallet( config = self.config ) ] # Otherwise we stake to multiple wallets @@ -374,7 +372,8 @@ def stake( self ): stake_amount_tao: float = self.config.get('amount') if self.config.get('max_stake'): wallet_stake: Balance = wallet.get_stake() - stake_amount_tao: float = self.config.get('max_stake') - wallet_stake.tao + stake_amount_tao: float = self.config.get('max_stake') - wallet_stake.tao + # If the max_stake is greater than the current wallet balance, stake the entire balance. stake_amount_tao: float = min(stake_amount_tao, wallet_balance.tao) if stake_amount_tao <= 0.00001: # Threshold because of fees, might create a loop otherwise @@ -384,6 +383,11 @@ def stake( self ): final_amounts.append(stake_amount_tao) final_wallets.append(wallet) + if len(final_wallets) == 0: + # No wallets to stake to. + bittensor.__console__.print("Not enough balance to stake to any hotkeys or max_stake is less than current stake.") + return None + # Ask to stake if not self.config.no_prompt: if not Confirm.ask(f"Do you want to stake to the following keys from {wallet_0.name}:\n" + \ @@ -392,6 +396,10 @@ def stake( self ): ]) ): return None + + if len(final_wallets) == 1: + # do regular stake + return subtensor.add_stake( final_wallets[0], amount = None if self.config.get('stake_all') else final_amounts[0], wait_for_inclusion = True, prompt = not self.config.no_prompt ) subtensor.add_stake_multiple( wallets = final_wallets, amounts = None if self.config.get('stake_all') else final_amounts, wait_for_inclusion = True, prompt = False ) From 31cf9b6b6ec2ee16e1fd3a2155b4c52f9524395d Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Tue, 15 Nov 2022 16:24:45 -0500 Subject: [PATCH 02/11] use kwarg --- 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 f3f8a76fd6..b6cd199ef6 100644 --- a/bittensor/_cli/cli_impl.py +++ b/bittensor/_cli/cli_impl.py @@ -399,7 +399,7 @@ def stake( self ): if len(final_wallets) == 1: # do regular stake - return subtensor.add_stake( final_wallets[0], amount = None if self.config.get('stake_all') else final_amounts[0], wait_for_inclusion = True, prompt = not self.config.no_prompt ) + return subtensor.add_stake( wallet=final_wallets[0], amount = None if self.config.get('stake_all') else final_amounts[0], wait_for_inclusion = True, prompt = not self.config.no_prompt ) subtensor.add_stake_multiple( wallets = final_wallets, amounts = None if self.config.get('stake_all') else final_amounts, wait_for_inclusion = True, prompt = False ) From 5309722e94ec6afd3363d978394afca883ca17c4 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Tue, 15 Nov 2022 16:31:09 -0500 Subject: [PATCH 03/11] use tuple unpack --- tests/integration_tests/test_cli.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/integration_tests/test_cli.py b/tests/integration_tests/test_cli.py index 111c45896c..d656a3bffa 100644 --- a/tests/integration_tests/test_cli.py +++ b/tests/integration_tests/test_cli.py @@ -740,7 +740,8 @@ 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][2]['wallets'] + args, kwargs = mock_unstake.call_args + mock_wallets_ = kwargs['wallets'] # We shouldn't unstake from hk1 as it has less than max_stake staked @@ -1092,11 +1093,8 @@ def test_stake_with_multiple_hotkeys_max_stake_not_enough_balance( self ): total_staked = 0.0 - # 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'] + args, kwargs = mock_add_stake.call_args + amounts_passed = kwargs['amounts'] total_staked = sum(amounts_passed) From 908c23e5686ccb49d89c0415622a3d7a748bb9c7 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Tue, 15 Nov 2022 16:31:52 -0500 Subject: [PATCH 04/11] add tests for max stake fixes --- tests/integration_tests/test_cli.py | 254 ++++++++++++++++++++++++++++ 1 file changed, 254 insertions(+) diff --git a/tests/integration_tests/test_cli.py b/tests/integration_tests/test_cli.py index d656a3bffa..774c53a44d 100644 --- a/tests/integration_tests/test_cli.py +++ b/tests/integration_tests/test_cli.py @@ -1101,6 +1101,260 @@ def test_stake_with_multiple_hotkeys_max_stake_not_enough_balance( self ): # We should not try to stake more than the mock_balance self.assertAlmostEqual(total_staked, mock_balance.tao, delta=0.001) + def test_stake_with_single_hotkey_max_stake( self ): + bittensor.subtensor.register = MagicMock(return_value = True) + + config = self.config + config.command = "stake" + config.subtensor._mock = True + config.subtensor.network = "mock" + config.no_prompt = True + # Notie amount is not specified + config.max_stake = 15.0 # The keys should have at most 15.0 tao staked after + config.wallet.name = "fake_wallet" + config.wallet.hotkeys = [ + 'hk0' + ] + config.wallet.all_hotkeys = False + # Notice no max_stake specified + config.no_version_checking = False + + mock_balance = bittensor.Balance(15.0) # Enough to stake 15.0 on one hotkey + + mock_coldkey = "" # Not None + + mock_stakes: Dict[str, float] = { + # All have more than 5.0 stake + 'hk0': bittensor.Balance.from_float(10.0), + } + + mock_wallets = [ + SimpleNamespace( + name = config.wallet.name, + hotkey_str = config.wallet.hotkeys[0], + get_stake = MagicMock( + return_value = mock_stakes[config.wallet.hotkeys[0]] + ), + is_registered = MagicMock( + return_value = True + ), + + _coldkey = mock_coldkey, + coldkey = MagicMock( + return_value=mock_coldkey + ) + ) + ] + [ + SimpleNamespace( + name = config.wallet.name, + hotkey_str = hk, + get_stake = MagicMock( + return_value = mock_stakes[hk] + ), + is_registered = MagicMock( + return_value = True + ) + ) for hk in config.wallet.hotkeys + ] + + # The 0th wallet is created twice during unstake + mock_wallets[1]._coldkey = mock_coldkey + mock_wallets[1].coldkey = MagicMock( + return_value=mock_coldkey + ) + mock_wallets[1].get_balance = MagicMock( + return_value = mock_balance + ) + + cli = bittensor.cli(config) + + 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: + cli.run() + mock_create_wallet.assert_has_calls( + [ + call(config=ANY, hotkey=hk) for hk in config.wallet.hotkeys + ], + any_order=True + ) + mock_add_stake.assert_has_calls( + [call(wallet=mock_wallets[1], amount=CLOSE_IN_VALUE((config.max_stake - mock_stakes[mock_wallets[1].hotkey_str].tao), 0.001), wait_for_inclusion=True, prompt=False)], + any_order = True + ) + + def test_stake_with_single_hotkey_max_stake_not_enough_balance( self ): + bittensor.subtensor.register = MagicMock(return_value = True) + + config = self.config + config.command = "stake" + config.subtensor._mock = True + config.subtensor.network = "mock" + config.no_prompt = True + # Notie amount is not specified + config.max_stake = 15.0 # The keys should have at most 15.0 tao staked after + config.wallet.name = "fake_wallet" + config.wallet.hotkeys = [ + 'hk0' + ] + config.wallet.all_hotkeys = False + config.no_version_checking = False + + # Notice no max_stake specified + + mock_balance = bittensor.Balance(1.0) # Not enough to stake 15.0 on the hotkey + + mock_coldkey = "" # Not None + + mock_stakes: Dict[str, float] = { + # has 5.0 stake + 'hk0': bittensor.Balance.from_float(5.0) + } + + mock_wallets = [ + SimpleNamespace( + name = config.wallet.name, + hotkey_str = config.wallet.hotkeys[0], + get_stake = MagicMock( + return_value = mock_stakes[config.wallet.hotkeys[0]] + ), + is_registered = MagicMock( + return_value = True + ), + + _coldkey = mock_coldkey, + coldkey = MagicMock( + return_value=mock_coldkey + ) + ) + ] + [ + SimpleNamespace( + name = config.wallet.name, + hotkey_str = hk, + get_stake = MagicMock( + return_value = mock_stakes[hk] + ), + is_registered = MagicMock( + return_value = True + ) + ) for hk in config.wallet.hotkeys + ] + + # The 0th wallet is created twice during unstake + mock_wallets[1]._coldkey = mock_coldkey + mock_wallets[1].coldkey = MagicMock( + return_value=mock_coldkey + ) + mock_wallets[1].get_balance = MagicMock( + return_value = mock_balance + ) + + cli = bittensor.cli(config) + + 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: + cli.run() + mock_create_wallet.assert_has_calls( + [ + call(config=ANY, hotkey=hk) for hk in config.wallet.hotkeys + ], + any_order=True + ) + # We should stake what we have in the balance + mock_add_stake.assert_called_once() + + total_staked = 0.0 + + args, kwargs = mock_add_stake.call_args + total_staked = kwargs['amount'] + + # We should not try to stake more than the mock_balance + self.assertAlmostEqual(total_staked, mock_balance.tao, delta=0.001) + + def test_stake_with_single_hotkey_max_stake_enough_stake( self ): + # tests max stake when stake >= max_stake already + bittensor.subtensor.register = MagicMock(return_value = True) + + config = self.config + config.command = "stake" + config.subtensor._mock = True + config.subtensor.network = "mock" + config.no_prompt = True + # Notie amount is not specified + config.max_stake = 15.0 # The keys should have at most 15.0 tao staked after + config.wallet.name = "fake_wallet" + config.wallet.hotkeys = [ + 'hk0' + ] + config.wallet.all_hotkeys = False + config.no_version_checking = False + + # Notice no max_stake specified + + mock_balance = bittensor.Balance(30.0) # enough to stake 15.0 on the hotkey + + mock_coldkey = "" # Not None + + mock_stakes: Dict[str, float] = { + # already has 15.0 stake + 'hk0': bittensor.Balance.from_float(15.0) + } + + mock_wallets = [ + SimpleNamespace( + name = config.wallet.name, + hotkey_str = config.wallet.hotkeys[0], + get_stake = MagicMock( + return_value = mock_stakes[config.wallet.hotkeys[0]] + ), + is_registered = MagicMock( + return_value = True + ), + + _coldkey = mock_coldkey, + coldkey = MagicMock( + return_value=mock_coldkey + ) + ) + ] + [ + SimpleNamespace( + name = config.wallet.name, + hotkey_str = hk, + get_stake = MagicMock( + return_value = mock_stakes[hk] + ), + is_registered = MagicMock( + return_value = True + ) + ) for hk in config.wallet.hotkeys + ] + + # The 0th wallet is created twice during unstake + mock_wallets[1]._coldkey = mock_coldkey + mock_wallets[1].coldkey = MagicMock( + return_value=mock_coldkey + ) + mock_wallets[1].get_balance = MagicMock( + return_value = mock_balance + ) + + cli = bittensor.cli(config) + + 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: + cli.run() + mock_create_wallet.assert_has_calls( + [ + call(config=ANY, hotkey=hk) for hk in config.wallet.hotkeys + ], + any_order=True + ) + # We should stake what we have in the balance + mock_add_stake.assert_not_called() + + def test_register( self ): config = self.config From ae3e96aeb51ebeff12514763b032cb3f21865ef2 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Wed, 16 Nov 2022 10:45:19 -0500 Subject: [PATCH 05/11] fix test mock --- 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 774c53a44d..2798fabf7a 100644 --- a/tests/integration_tests/test_cli.py +++ b/tests/integration_tests/test_cli.py @@ -1080,7 +1080,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_multiple', return_value=True) as mock_add_stake: + with patch('bittensor.Subtensor.add_stake', return_value=True) as mock_add_stake: cli.run() mock_create_wallet.assert_has_calls( [ From 96c8e76fe7f09c8fc88f2466c6ffc5fde93c2243 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Wed, 16 Nov 2022 10:45:42 -0500 Subject: [PATCH 06/11] change testcase name --- 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 2798fabf7a..e7fd43c8b0 100644 --- a/tests/integration_tests/test_cli.py +++ b/tests/integration_tests/test_cli.py @@ -30,7 +30,7 @@ from tests.helpers import CLOSE_IN_VALUE -class TestCli(unittest.TestCase): +class TestCLI(unittest.TestCase): def setUp(self): mock_subtensor.kill_global_mock_process() From f142e8396022f562f57c6728db23c9d7332ec613 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Wed, 16 Nov 2022 11:14:50 -0500 Subject: [PATCH 07/11] undo rename oops --- tests/integration_tests/test_cli.py | 33 +---------------------------- 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/tests/integration_tests/test_cli.py b/tests/integration_tests/test_cli.py index 7440123b13..2798fabf7a 100644 --- a/tests/integration_tests/test_cli.py +++ b/tests/integration_tests/test_cli.py @@ -30,7 +30,7 @@ from tests.helpers import CLOSE_IN_VALUE -class TestCLI(unittest.TestCase): +class TestCli(unittest.TestCase): def setUp(self): mock_subtensor.kill_global_mock_process() @@ -1716,37 +1716,6 @@ def test_run_reregister_false(self): # args[0] should be self => the wallet assert args[0].config.wallet.reregister == False - def test_run_synapse_all(self): - """ - Verify that setting --synapse All works - """ - - class MockException(Exception): - """Raised by mocked function to exit early""" - pass - - with patch('bittensor.neurons.core_server.neuron', MagicMock(side_effect=MockException("should exit early"))) as mock_neuron: - with patch('bittensor.Wallet.is_registered', MagicMock(return_value=True)): # mock registered - with pytest.raises(MockException): - cli = bittensor.cli(args=[ - 'run', - '--wallet.name', 'mock', - '--wallet.hotkey', 'mock_hotkey', - '--wallet._mock', 'True', - '--subtensor.network', 'mock', - '--subtensor._mock', 'True', - '--cuda.no_cuda', - '--no_prompt', - '--model', 'core_server', - '--synapse', 'All', - ]) - cli.run() - - assert mock_neuron.call_count == 1 - args, kwargs = mock_neuron.call_args - - assert len(args) == 0 and len(kwargs) == 0 # should not have any args; indicates that "All" synapses are being used - if __name__ == '__main__': unittest.main() \ No newline at end of file From 1d932198eeba1dd6d791a624668457dc125c4b30 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Wed, 16 Nov 2022 11:21:56 -0500 Subject: [PATCH 08/11] grab amount kwarg instead --- tests/integration_tests/test_cli.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/integration_tests/test_cli.py b/tests/integration_tests/test_cli.py index 2798fabf7a..8c2d30115c 100644 --- a/tests/integration_tests/test_cli.py +++ b/tests/integration_tests/test_cli.py @@ -1094,9 +1094,7 @@ def test_stake_with_multiple_hotkeys_max_stake_not_enough_balance( self ): total_staked = 0.0 args, kwargs = mock_add_stake.call_args - amounts_passed = kwargs['amounts'] - - total_staked = sum(amounts_passed) + total_staked = kwargs['amount'] # We should not try to stake more than the mock_balance self.assertAlmostEqual(total_staked, mock_balance.tao, delta=0.001) From 988b51338ba34116a66fac2150a7263c244aaf6f Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Tue, 6 Dec 2022 13:38:28 -0500 Subject: [PATCH 09/11] add comment/assert about single hk --- bittensor/_cli/cli_impl.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bittensor/_cli/cli_impl.py b/bittensor/_cli/cli_impl.py index b6cd199ef6..c73f8dfc69 100644 --- a/bittensor/_cli/cli_impl.py +++ b/bittensor/_cli/cli_impl.py @@ -347,6 +347,9 @@ def stake( self ): bittensor.wallet( config = self.config, hotkey = hotkey ) for hotkey in self.config.wallet.get('hotkeys') ] else: + # Only self.config.hotkey is specified. + # so we stake to that single hotkey. + assert self.config.hotkey is not None wallets_to_stake_to = [ bittensor.wallet( config = self.config ) ] # Otherwise we stake to multiple wallets From 31fe75645e866248b7df84f61e0c5c92a56329a5 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Tue, 6 Dec 2022 13:41:27 -0500 Subject: [PATCH 10/11] dont remove synapse all test --- tests/integration_tests/test_cli.py | 31 +++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/integration_tests/test_cli.py b/tests/integration_tests/test_cli.py index 8c2d30115c..40f613f5bb 100644 --- a/tests/integration_tests/test_cli.py +++ b/tests/integration_tests/test_cli.py @@ -1714,6 +1714,37 @@ def test_run_reregister_false(self): # args[0] should be self => the wallet assert args[0].config.wallet.reregister == False + def test_run_synapse_all(self): + """ + Verify that setting --synapse All works + """ + + class MockException(Exception): + """Raised by mocked function to exit early""" + pass + + with patch('bittensor.neurons.core_server.neuron', MagicMock(side_effect=MockException("should exit early"))) as mock_neuron: + with patch('bittensor.Wallet.is_registered', MagicMock(return_value=True)): # mock registered + with pytest.raises(MockException): + cli = bittensor.cli(args=[ + 'run', + '--wallet.name', 'mock', + '--wallet.hotkey', 'mock_hotkey', + '--wallet._mock', 'True', + '--subtensor.network', 'mock', + '--subtensor._mock', 'True', + '--cuda.no_cuda', + '--no_prompt', + '--model', 'core_server', + '--synapse', 'All', + ]) + cli.run() + + assert mock_neuron.call_count == 1 + args, kwargs = mock_neuron.call_args + + assert len(args) == 0 and len(kwargs) == 0 # should not have any args; indicates that "All" synapses are being used + if __name__ == '__main__': unittest.main() \ No newline at end of file From 8363ffa4c5c01e6b77717cc48265600231a88a3d Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Tue, 6 Dec 2022 14:00:07 -0500 Subject: [PATCH 11/11] fix accessor --- bittensor/_cli/cli_impl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bittensor/_cli/cli_impl.py b/bittensor/_cli/cli_impl.py index c73f8dfc69..846d5111ee 100644 --- a/bittensor/_cli/cli_impl.py +++ b/bittensor/_cli/cli_impl.py @@ -347,9 +347,9 @@ def stake( self ): bittensor.wallet( config = self.config, hotkey = hotkey ) for hotkey in self.config.wallet.get('hotkeys') ] else: - # Only self.config.hotkey is specified. + # Only self.config.wallet.hotkey is specified. # so we stake to that single hotkey. - assert self.config.hotkey is not None + assert self.config.wallet.hotkey is not None wallets_to_stake_to = [ bittensor.wallet( config = self.config ) ] # Otherwise we stake to multiple wallets