diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e7ccd6c02..5190ea2525 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,18 @@ # Changelog -## 9.5.0 /2025-05-15 +## 9.6.1 /2025-05-22 + +## What's Changed +* Release/9.6.0 by @ibraheem-abe in https://github.com/opentensor/bittensor/pull/2882 +* Add stake before check `validator_permit` by @basfroman in https://github.com/opentensor/bittensor/pull/2884 +* Add defaults for endpoint and network from local env by @basfroman in https://github.com/opentensor/bittensor/pull/2886 +* Improve error message by @basfroman in https://github.com/opentensor/bittensor/pull/2888 +* Make `unstake` and `unstake_multiple` for all Alphas more clear by @basfroman in https://github.com/opentensor/bittensor/pull/2885 +* fix publish metadata by @basfroman in https://github.com/opentensor/bittensor/pull/2890 + +**Full Changelog**: https://github.com/opentensor/bittensor/compare/v9.6.0...v9.6.1 + +## 9.6.0 /2025-05-15 * Add manual way to show the size of virtual environments in the PR by @basfroman in https://github.com/opentensor/bittensor/pull/2875 * Improve `Monitor SDK Requirements Size` workflow by @basfroman in https://github.com/opentensor/bittensor/pull/2878 diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 0d99e887c3..aa5fa9c5f1 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -3119,6 +3119,9 @@ async def wait_for_block(self, block: Optional[int] = None): bool: True if the target block was reached, False if timeout occurred. Example: + import bittensor as bt + subtensor = bt.Subtensor() + await subtensor.wait_for_block() # Waits for next block await subtensor.wait_for_block(block=1234) # Waits for a specific block """ @@ -3380,27 +3383,27 @@ async def add_stake( period: Optional[int] = None, ) -> bool: """ - Adds the specified amount of stake to a neuron identified by the hotkey ``SS58`` address. - Staking is a fundamental process in the Bittensor network that enables neurons to participate actively and earn - incentives. + Adds a stake from the specified wallet to the neuron identified by the SS58 address of its hotkey in specified subnet. + Staking is a fundamental process in the Bittensor network that enables neurons to participate actively and earn incentives. Args: - wallet (bittensor_wallet.Wallet): The wallet to be used for staking. - hotkey_ss58 (Optional[str]): The ``SS58`` address of the hotkey associated with the neuron. - netuid: subnet UID - amount (Balance): The amount of TAO to stake. - wait_for_inclusion (bool): Waits for the transaction to be included in a block. - wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. - safe_staking (bool): If true, enables price safety checks to protect against fluctuating prices. The stake - will only execute if the price change doesn't exceed the rate tolerance. Default is False. - allow_partial_stake (bool): If true and safe_staking is enabled, allows partial staking when - the full amount would exceed the price threshold. If false, the entire stake fails if it would - exceed the threshold. Default is False. - rate_tolerance (float): The maximum allowed price change ratio when staking. For example, - 0.005 = 0.5% maximum price increase. Only used when safe_staking is True. Default is 0.005. - period (Optional[int]): The number of blocks during which the transaction will remain valid after it's - submitted. If the transaction is not included in a block within that number of blocks, it will expire - and be rejected. You can think of it as an expiration date for the transaction. + wallet: The wallet to be used for staking. + hotkey_ss58: The SS58 address of the hotkey associated with the neuron to which you intend to delegate your + stake. If not specified, the wallet's hotkey will be used. Defaults to ``None``. + netuid: The unique identifier of the subnet to which the neuron belongs. + amount: The amount of TAO to stake. + wait_for_inclusion: Waits for the transaction to be included in a block. Defaults to ``True``. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Defaults to ``False``. + safe_staking: If true, enables price safety checks to protect against fluctuating prices. The stake will + only execute if the price change doesn't exceed the rate tolerance. Default is ``False``. + allow_partial_stake: If true and safe_staking is enabled, allows partial staking when the full amount would + exceed the price tolerance. If false, the entire stake fails if it would exceed the tolerance. + Default is ``False``. + rate_tolerance: The maximum allowed price change ratio when staking. For example, + 0.005 = 0.5% maximum price increase. Only used when safe_staking is True. Default is ``0.005``. + period: The number of blocks during which the transaction will remain valid after it's submitted. If the + transaction is not included in a block within that number of blocks, it will expire and be rejected. You + can think of it as an expiration date for the transaction. Defaults to ``None``. Returns: bool: ``True`` if the staking is successful, False otherwise. @@ -4428,6 +4431,7 @@ async def unstake( allow_partial_stake: bool = False, rate_tolerance: float = 0.005, period: Optional[int] = None, + unstake_all: bool = False, ) -> bool: """ Removes a specified amount of stake from a single hotkey account. This function is critical for adjusting @@ -4451,6 +4455,7 @@ async def unstake( period (Optional[int]): The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. + unstake_all: If true, unstakes all tokens. Default is ``False``. If `True` amount is ignored. Returns: bool: ``True`` if the unstaking process is successful, False otherwise. @@ -4471,6 +4476,7 @@ async def unstake( allow_partial_stake=allow_partial_stake, rate_tolerance=rate_tolerance, period=period, + unstake_all=unstake_all, ) async def unstake_multiple( @@ -4482,6 +4488,7 @@ async def unstake_multiple( wait_for_inclusion: bool = True, wait_for_finalization: bool = False, period: Optional[int] = None, + unstake_all: bool = False, ) -> bool: """ Performs batch unstaking from multiple hotkey accounts, allowing a neuron to reduce its staked amounts @@ -4499,6 +4506,7 @@ async def unstake_multiple( period (Optional[int]): The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. + unstake_all: If true, unstakes all tokens. Default is ``False``. If `True` amounts are ignored. Returns: bool: ``True`` if the batch unstaking is successful, False otherwise. @@ -4515,6 +4523,7 @@ async def unstake_multiple( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, + unstake_all=unstake_all, ) diff --git a/bittensor/core/extrinsics/asyncex/serving.py b/bittensor/core/extrinsics/asyncex/serving.py index b942a88dc1..6fd5410838 100644 --- a/bittensor/core/extrinsics/asyncex/serving.py +++ b/bittensor/core/extrinsics/asyncex/serving.py @@ -282,6 +282,7 @@ async def publish_metadata( success, message = await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, + sign_with="hotkey", wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, diff --git a/bittensor/core/extrinsics/asyncex/staking.py b/bittensor/core/extrinsics/asyncex/staking.py index 41076f0178..b257f8610c 100644 --- a/bittensor/core/extrinsics/asyncex/staking.py +++ b/bittensor/core/extrinsics/asyncex/staking.py @@ -28,25 +28,27 @@ async def add_stake_extrinsic( period: Optional[int] = None, ) -> bool: """ - Adds the specified amount of stake to passed hotkey `uid`. + Adds a stake from the specified wallet to the neuron identified by the SS58 address of its hotkey in specified subnet. + Staking is a fundamental process in the Bittensor network that enables neurons to participate actively and earn incentives. Arguments: - subtensor: the initialized SubtensorInterface object to use + subtensor: Subtensor instance with the connection to the chain. wallet: Bittensor wallet object. old_balance: the balance prior to the staking - hotkey_ss58: The `ss58` address of the hotkey account to stake to defaults to the wallet's hotkey. - netuid: The netuid of the stake to be added - amount: Amount to stake as Bittensor balance, `None` if staking all. + hotkey_ss58: The `ss58` address of the hotkey account to stake to default to the wallet's hotkey. If not + specified, the wallet's hotkey will be used. Defaults to ``None``. + netuid: The unique identifier of the subnet to which the neuron belongs. + amount: Amount to stake as Bittensor balance in TAO always, `None` if staking all. Defaults is ``None``. wait_for_inclusion: 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. + `False` if the extrinsic fails to enter the block within the timeout. Defaults to ``True``. wait_for_finalization: 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. - safe_staking: If set, uses safe staking logic - allow_partial_stake: If set, allows partial stake - rate_tolerance: The rate tolerance for safe staking + or returns `False` if the extrinsic fails to be finalized within the timeout. Defaults to ``False``. + safe_staking: If True, enables price safety checks. Default is ``False``. + allow_partial_stake: If True, allows partial unstaking if price tolerance exceeded. Default is ``False``. + rate_tolerance: Maximum allowed price increase percentage (0.005 = 0.5%). Default is ``0.005``. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. - You can think of it as an expiration date for the transaction. + You can think of it as an expiration date for the transaction. Defaults to ``None``. Returns: success: Flag is `True` if extrinsic was finalized or included in the block. If we did not wait for @@ -93,7 +95,6 @@ async def add_stake_extrinsic( ) else: staking_balance = amount - staking_balance.set_unit(netuid) # Leave existential balance to keep key alive. if staking_balance > old_balance - existential_deposit: diff --git a/bittensor/core/extrinsics/asyncex/unstaking.py b/bittensor/core/extrinsics/asyncex/unstaking.py index 8125b603e9..0d0bf6670e 100644 --- a/bittensor/core/extrinsics/asyncex/unstaking.py +++ b/bittensor/core/extrinsics/asyncex/unstaking.py @@ -25,6 +25,7 @@ async def unstake_extrinsic( allow_partial_stake: bool = False, rate_tolerance: float = 0.005, period: Optional[int] = None, + unstake_all: bool = False, ) -> bool: """Removes stake into the wallet coldkey from the specified hotkey ``uid``. @@ -45,11 +46,15 @@ async def unstake_extrinsic( period (Optional[int]): The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. + unstake_all: If true, unstakes all tokens. Default is ``False``. Returns: success (bool): Flag is ``True`` if extrinsic was finalized or included in the block. If we did not wait for finalization / inclusion, the response is ``True``. """ + if amount and unstake_all: + raise ValueError("Cannot specify both `amount` and `unstake_all`.") + # Decrypt keys, if not (unlock := unlock_key(wallet)).success: logging.error(unlock.message) @@ -204,6 +209,7 @@ async def unstake_multiple_extrinsic( wait_for_inclusion: bool = True, wait_for_finalization: bool = False, period: Optional[int] = None, + unstake_all: bool = False, ) -> bool: """Removes stake from each ``hotkey_ss58`` in the list, using each amount, to a common coldkey. @@ -220,11 +226,15 @@ async def unstake_multiple_extrinsic( period (Optional[int]): The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. + unstake_all: If true, unstakes all tokens. Default is ``False``. 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 amounts and unstake_all: + raise ValueError("Cannot specify both `amounts` and `unstake_all`.") + if not isinstance(hotkey_ss58s, list) or not all( isinstance(hotkey_ss58, str) for hotkey_ss58 in hotkey_ss58s ): diff --git a/bittensor/core/extrinsics/serving.py b/bittensor/core/extrinsics/serving.py index b49196bf89..88da8997bc 100644 --- a/bittensor/core/extrinsics/serving.py +++ b/bittensor/core/extrinsics/serving.py @@ -278,6 +278,7 @@ def publish_metadata( success, message = subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, + sign_with="hotkey", wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, diff --git a/bittensor/core/extrinsics/staking.py b/bittensor/core/extrinsics/staking.py index 58f5ed0b71..0e582fff5c 100644 --- a/bittensor/core/extrinsics/staking.py +++ b/bittensor/core/extrinsics/staking.py @@ -26,24 +26,26 @@ def add_stake_extrinsic( period: Optional[int] = None, ) -> bool: """ - Adds the specified amount of stake to passed hotkey `uid`. + Adds a stake from the specified wallet to the neuron identified by the SS58 address of its hotkey in specified subnet. + Staking is a fundamental process in the Bittensor network that enables neurons to participate actively and earn incentives. Arguments: - subtensor: the Subtensor object to use + subtensor: Subtensor instance with the connection to the chain. wallet: Bittensor wallet object. - hotkey_ss58: The `ss58` address of the hotkey account to stake to default to the wallet's hotkey. - netuid (Optional[int]): Subnet unique ID. - amount: Amount to stake as Bittensor balance, `None` if staking all. + hotkey_ss58: The `ss58` address of the hotkey account to stake to default to the wallet's hotkey. If not + specified, the wallet's hotkey will be used. Defaults to ``None``. + netuid: The unique identifier of the subnet to which the neuron belongs. + amount: Amount to stake as Bittensor balance in TAO always, `None` if staking all. Defaults is ``None``. wait_for_inclusion: 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. + `False` if the extrinsic fails to enter the block within the timeout. Defaults to ``True``. wait_for_finalization: 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. - safe_staking (bool): If true, enables price safety checks - allow_partial_stake (bool): If true, allows partial unstaking if price tolerance exceeded - rate_tolerance (float): Maximum allowed price increase percentage (0.005 = 0.5%) + or returns `False` if the extrinsic fails to be finalized within the timeout. Defaults to ``False``. + safe_staking: If True, enables price safety checks. Default is ``False``. + allow_partial_stake: If True, allows partial unstaking if price tolerance exceeded. Default is ``False``. + rate_tolerance: Maximum allowed price increase percentage (0.005 = 0.5%). Default is ``0.005``. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. - You can think of it as an expiration date for the transaction. + You can think of it as an expiration date for the transaction. Defaults to ``None``. Returns: success: Flag is `True` if extrinsic was finalized or included in the block. If we did not wait for @@ -87,14 +89,11 @@ def add_stake_extrinsic( ) else: staking_balance = amount - staking_balance.set_unit(netuid) # Leave existential balance to keep key alive. if staking_balance > old_balance - existential_deposit: # If we are staking all, we need to leave at least the existential deposit. staking_balance = old_balance - existential_deposit - else: - staking_balance = staking_balance # Check enough to stake. if staking_balance > old_balance: @@ -200,7 +199,7 @@ def add_stake_extrinsic( except SubstrateRequestException as error: logging.error( - f":cross_mark: [red]Add Stake Error: {format_error_message((error))}[/red]" + f":cross_mark: [red]Add Stake Error: {format_error_message(error)}[/red]" ) return False diff --git a/bittensor/core/extrinsics/unstaking.py b/bittensor/core/extrinsics/unstaking.py index 34fe47d7ac..9e252cdaaf 100644 --- a/bittensor/core/extrinsics/unstaking.py +++ b/bittensor/core/extrinsics/unstaking.py @@ -24,6 +24,7 @@ def unstake_extrinsic( allow_partial_stake: bool = False, rate_tolerance: float = 0.005, period: Optional[int] = None, + unstake_all: bool = False, ) -> bool: """Removes stake into the wallet coldkey from the specified hotkey ``uid``. @@ -44,11 +45,15 @@ def unstake_extrinsic( period (Optional[int]): The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. + unstake_all: If true, unstakes all tokens. Default is ``False``. Returns: success (bool): Flag is ``True`` if extrinsic was finalized or included in the block. If we did not wait for finalization / inclusion, the response is ``True``. """ + if amount and unstake_all: + raise ValueError("Cannot specify both `amount` and `unstake_all`.") + # Decrypt keys, if not (unlock := unlock_key(wallet)).success: logging.error(unlock.message) @@ -201,6 +206,7 @@ def unstake_multiple_extrinsic( wait_for_inclusion: bool = True, wait_for_finalization: bool = False, period: Optional[int] = None, + unstake_all: bool = False, ) -> bool: """Removes stake from each ``hotkey_ss58`` in the list, using each amount, to a common coldkey. @@ -217,11 +223,14 @@ def unstake_multiple_extrinsic( period (Optional[int]): The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. + unstake_all: If true, unstakes all tokens. Default is ``False``. 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 amounts and unstake_all: + raise ValueError("Cannot specify both `amounts` and `unstake_all`.") if not isinstance(hotkey_ss58s, list) or not all( isinstance(hotkey_ss58, str) for hotkey_ss58 in hotkey_ss58s diff --git a/bittensor/core/settings.py b/bittensor/core/settings.py index 39798ed295..bf0d66bebf 100644 --- a/bittensor/core/settings.py +++ b/bittensor/core/settings.py @@ -121,8 +121,8 @@ "maxsize": int(_BT_PRIORITY_MAXSIZE) if _BT_PRIORITY_MAXSIZE else 10, }, "subtensor": { - "chain_endpoint": DEFAULT_ENDPOINT, - "network": DEFAULT_NETWORK, + "chain_endpoint": os.getenv("BT_CHAIN_ENDPOINT") or DEFAULT_ENDPOINT, + "network": os.getenv("BT_NETWORK") or DEFAULT_NETWORK, "_mock": False, }, "wallet": { diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 5e356f9666..015b918cd7 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -610,14 +610,14 @@ def get_all_subnets_info(self, block: Optional[int] = None) -> list["SubnetInfo" def get_balance(self, address: str, block: Optional[int] = None) -> Balance: """ - Retrieves the balance for given coldkey. + Retrieves the balance for given coldkey. Always in TAO. Arguments: - address (str): coldkey address. + address: coldkey address. block (Optional[int]): The blockchain block number for the query. Returns: - Balance object. + Balance object in TAO. """ balance = self.substrate.query( module="System", @@ -1122,7 +1122,7 @@ def get_delegates(self, block: Optional[int] = None) -> list["DelegateInfo"]: def get_existential_deposit(self, block: Optional[int] = None) -> Optional[Balance]: """ - Retrieves the existential deposit amount for the Bittensor blockchain. + Retrieves the existential deposit amount for the Bittensor blockchain. Always in TAO. The existential deposit is the minimum amount of TAO required for an account to exist on the blockchain. Accounts with balances below this threshold can be reaped to conserve network resources. @@ -1130,7 +1130,7 @@ def get_existential_deposit(self, block: Optional[int] = None) -> Optional[Balan block (Optional[int]): The blockchain block number for the query. Returns: - The existential deposit amount. + The existential deposit amount. Always in TAO. The existential deposit is a fundamental economic parameter in the Bittensor network, ensuring efficient use of storage and preventing the proliferation of dust accounts. @@ -1424,16 +1424,19 @@ def get_stake( block: Optional[int] = None, ) -> Balance: """ - Returns the stake under a coldkey - hotkey pairing. + Returns the amount of Alpha staked by a specific coldkey to a specific hotkey within a given subnet. + This function retrieves the delegated stake balance, referred to as the 'Alpha' value. Args: - hotkey_ss58 (str): The SS58 address of the hotkey. - coldkey_ss58 (str): The SS58 address of the coldkey. - netuid (int): The subnet ID - block (Optional[int]): The block number at which to query the stake information. + coldkey_ss58: The SS58 address of the coldkey that delegated the stake. This address owns the stake. + hotkey_ss58: The ss58 address of the hotkey which the stake is on. + netuid: The unique identifier of the subnet to query. + block: The specific block number at which to retrieve the stake information. If None, the current stake at + the latest block is returned. Defaults to ``None``. Returns: - Balance: The stake under the coldkey - hotkey pairing. + An object representing the amount of Alpha (TAO ONLY if the subnet's netuid is 0) currently staked from the + specified coldkey to the specified hotkey within the given subnet. """ alpha_shares_query = self.query_module( module="SubtensorModule", @@ -2409,8 +2412,11 @@ def wait_for_block(self, block: Optional[int] = None): bool: True if the target block was reached, False if timeout occurred. Example: - >>> subtensor.wait_for_block() # Waits for the next block - >>> subtensor.wait_for_block(block=1234) # Waits for a specific block + import bittensor as bt + subtensor = bt.Subtensor() + + subtensor.wait_for_block() # Waits for the next block + subtensor.wait_for_block(block=1234) # Waits for a specific block """ def handler(block_data: dict): @@ -2428,7 +2434,7 @@ def handler(block_data: dict): else: target_block = current_block["header"]["number"] + 1 - self.substrate._get_block_handler( + self.substrate.get_block_handler( current_block_hash, header_only=True, subscription_handler=handler ) return True @@ -2632,27 +2638,27 @@ def add_stake( period: Optional[int] = None, ) -> bool: """ - Adds the specified amount of stake to a neuron identified by the hotkey ``SS58`` address. - Staking is a fundamental process in the Bittensor network that enables neurons to participate actively and earn - incentives. + Adds a stake from the specified wallet to the neuron identified by the SS58 address of its hotkey in specified subnet. + Staking is a fundamental process in the Bittensor network that enables neurons to participate actively and earn incentives. Args: - wallet (bittensor_wallet.Wallet): The wallet to be used for staking. - hotkey_ss58 (Optional[str]): The ``SS58`` address of the hotkey associated with the neuron. - netuid (Optional[int]): The unique identifier of the subnet to which the neuron belongs. - amount (Balance): The amount of TAO to stake. - wait_for_inclusion (bool): Waits for the transaction to be included in a block. - wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. - safe_staking (bool): If true, enables price safety checks to protect against fluctuating prices. The stake - will only execute if the price change doesn't exceed the rate tolerance. Default is False. - allow_partial_stake (bool): If true and safe_staking is enabled, allows partial staking when - the full amount would exceed the price tolerance. If false, the entire stake fails if it would - exceed the tolerance. Default is False. - rate_tolerance (float): The maximum allowed price change ratio when staking. For example, - 0.005 = 0.5% maximum price increase. Only used when safe_staking is True. Default is 0.005. - period (Optional[int]): The number of blocks during which the transaction will remain valid after it's - submitted. If the transaction is not included in a block within that number of blocks, it will expire - and be rejected. You can think of it as an expiration date for the transaction. + wallet: The wallet to be used for staking. + hotkey_ss58: The SS58 address of the hotkey associated with the neuron to which you intend to delegate your + stake. If not specified, the wallet's hotkey will be used. Defaults to ``None``. + netuid: The unique identifier of the subnet to which the neuron belongs. + amount: The amount of TAO to stake. + wait_for_inclusion: Waits for the transaction to be included in a block. Defaults to ``True``. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Defaults to ``False``. + safe_staking: If true, enables price safety checks to protect against fluctuating prices. The stake will + only execute if the price change doesn't exceed the rate tolerance. Default is ``False``. + allow_partial_stake: If true and safe_staking is enabled, allows partial staking when the full amount would + exceed the price tolerance. If false, the entire stake fails if it would exceed the tolerance. + Default is ``False``. + rate_tolerance: The maximum allowed price change ratio when staking. For example, + 0.005 = 0.5% maximum price increase. Only used when safe_staking is True. Default is ``0.005``. + period: The number of blocks during which the transaction will remain valid after it's submitted. If the + transaction is not included in a block within that number of blocks, it will expire and be rejected. You + can think of it as an expiration date for the transaction. Defaults to ``None``. Returns: bool: True if the staking is successful, False otherwise. @@ -3668,6 +3674,7 @@ def unstake( allow_partial_stake: bool = False, rate_tolerance: float = 0.005, period: Optional[int] = None, + unstake_all: bool = False, ) -> bool: """ Removes a specified amount of stake from a single hotkey account. This function is critical for adjusting @@ -3678,7 +3685,7 @@ def unstake( removed. hotkey_ss58 (Optional[str]): The ``SS58`` address of the hotkey account to unstake from. netuid (Optional[int]): The unique identifier of the subnet. - amount (Balance): The amount of alpha to unstake. If not specified, unstakes all. + amount (Balance): The amount of alpha to unstake. If not specified, unstakes all. Alpha amount. wait_for_inclusion (bool): Waits for the transaction to be included in a block. wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. safe_staking (bool): If true, enables price safety checks to protect against fluctuating prices. The unstake @@ -3691,6 +3698,7 @@ def unstake( period (Optional[int]): The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. + unstake_all: If true, unstakes all tokens. Default is ``False``. If `True` amount is ignored. Returns: bool: ``True`` if the unstaking process is successful, False otherwise. @@ -3712,6 +3720,7 @@ def unstake( allow_partial_stake=allow_partial_stake, rate_tolerance=rate_tolerance, period=period, + unstake_all=unstake_all, ) def unstake_multiple( @@ -3723,6 +3732,7 @@ def unstake_multiple( wait_for_inclusion: bool = True, wait_for_finalization: bool = False, period: Optional[int] = None, + unstake_all: bool = False, ) -> bool: """ Performs batch unstaking from multiple hotkey accounts, allowing a neuron to reduce its staked amounts @@ -3740,6 +3750,7 @@ def unstake_multiple( period (Optional[int]): The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. + unstake_all: If true, unstakes all tokens. Default is ``False``. If `True` amounts are ignored. Returns: bool: ``True`` if the batch unstaking is successful, False otherwise. @@ -3756,4 +3767,5 @@ def unstake_multiple( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, + unstake_all=unstake_all, ) diff --git a/bittensor/core/types.py b/bittensor/core/types.py index db5ab0b24e..d0c8b79cc9 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -124,8 +124,8 @@ def add_args(cls, parser: "argparse.ArgumentParser", prefix: Optional[str] = Non """ prefix_str = "" if prefix is None else f"{prefix}." try: - default_network = settings.DEFAULT_NETWORK - default_chain_endpoint = settings.FINNEY_ENTRYPOINT + default_network = settings.DEFAULTS.subtensor.network + default_chain_endpoint = settings.DEFAULTS.subtensor.chain_endpoint parser.add_argument( f"--{prefix_str}subtensor.network", diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 5efe1cbb25..14fb383847 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -22,6 +22,8 @@ if TYPE_CHECKING: from bittensor_wallet import Wallet +BT_DOCS_LINK = "https://docs.bittensor.com" + # redundant aliases logging = logging @@ -219,6 +221,7 @@ def format_error_message(error_message: Union[dict, Exception]) -> str: pass if new_error_message is None: return_val = " ".join(error_message.args) + return f"Subtensor returned: {return_val}" else: error_message = new_error_message @@ -236,7 +239,9 @@ def format_error_message(error_message: Union[dict, Exception]) -> str: # subtensor custom error marker if err_data.startswith("Custom error:"): - err_description = f"{err_data} | Please consult https://docs.bittensor.com/subtensor-nodes/subtensor-error-messages" + err_description = ( + f"{err_data} | Please consult {BT_DOCS_LINK}/errors/custom" + ) else: err_description = err_data @@ -249,6 +254,9 @@ def format_error_message(error_message: Union[dict, Exception]) -> str: err_name = error_message.get("name", err_name) err_docs = error_message.get("docs", [err_description]) err_description = " ".join(err_docs) + err_description += ( + f" | Please consult {BT_DOCS_LINK}/errors/subtensor#{err_name.lower()}" + ) elif error_message.get("code") and error_message.get("message"): err_type = error_message.get("code", err_name) diff --git a/pyproject.toml b/pyproject.toml index 4b3d8fbe99..244fcd3a53 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "bittensor" -version = "9.6.0" +version = "9.6.1" description = "Bittensor" readme = "README.md" authors = [ diff --git a/tests/e2e_tests/test_root_set_weights.py b/tests/e2e_tests/test_root_set_weights.py index 5973a144cb..bec739148e 100644 --- a/tests/e2e_tests/test_root_set_weights.py +++ b/tests/e2e_tests/test_root_set_weights.py @@ -1,10 +1,13 @@ import asyncio + import pytest +from bittensor.utils.balance import Balance from tests.e2e_tests.utils.chain_interactions import ( wait_epoch, sudo_set_hyperparameter_values, ) +from tests.e2e_tests.utils.e2e_test_utils import wait_to_start_call FAST_BLOCKS_SPEEDUP_FACTOR = 5 @@ -58,9 +61,9 @@ async def test_root_reg_hyperparams( """ print("Testing root register, weights, and hyperparams") - netuid = 2 + netuid = subtensor.get_total_subnets() # 2 - # Default immunity period & tempo set through the subtensor side + # Default immunity period and tempo set through the subtensor side default_immunity_period = 5000 default_tempo = 10 if subtensor.is_fast_blocks() else 360 @@ -79,10 +82,24 @@ async def test_root_reg_hyperparams( # Create netuid = 2 assert subtensor.register_subnet(alice_wallet) - # Ensure correct immunity period & tempo is being fetched + assert wait_to_start_call( + subtensor=subtensor, subnet_owner_wallet=alice_wallet, netuid=netuid + ) + + # Ensure correct immunity period and tempo is being fetched assert subtensor.immunity_period(netuid=netuid) == default_immunity_period assert subtensor.tempo(netuid=netuid) == default_tempo + assert subtensor.add_stake( + wallet=bob_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=netuid, + amount=Balance.from_tao(1), + wait_for_inclusion=True, + wait_for_finalization=True, + period=16, + ), "Unable to stake from Bob to Alice" + async with templates.validator(alice_wallet, netuid): await asyncio.sleep(5) # Wait a bit for chain to process data diff --git a/tests/unit_tests/extrinsics/test__init__.py b/tests/unit_tests/extrinsics/test__init__.py index 9eb994a2a3..ba17c001be 100644 --- a/tests/unit_tests/extrinsics/test__init__.py +++ b/tests/unit_tests/extrinsics/test__init__.py @@ -1,6 +1,6 @@ """Tests for bittensor/extrinsics/__ini__ module.""" -from bittensor.utils import format_error_message +from bittensor.utils import format_error_message, BT_DOCS_LINK def test_format_error_message_with_right_error_message(): @@ -22,8 +22,9 @@ def test_format_error_message_with_right_error_message(): # Assertions assert ( - result - == "Subtensor returned `SomeErrorName(SomeType)` error. This means: `Some error description. I'm second part. Hah, I'm the last one.`." + result == "Subtensor returned `SomeErrorName(SomeType)` error. " + "This means: `Some error description. I'm second part. Hah, I'm the last one." + f" | Please consult {BT_DOCS_LINK}/errors/subtensor#someerrorname`." ) @@ -79,8 +80,7 @@ def test_format_error_message_with_custom_error_message_with_index(): assert ( result == f"Subtensor returned `SubstrateRequestException({fake_subtensor_error['name']})` error. This means: " - f"`{fake_custom_error['data']} | Please consult " - f"https://docs.bittensor.com/subtensor-nodes/subtensor-error-messages`." + f"`{fake_custom_error['data']} | Please consult {BT_DOCS_LINK}/errors/custom`." ) diff --git a/tests/unit_tests/extrinsics/test_serving.py b/tests/unit_tests/extrinsics/test_serving.py index c1688fd3d4..7095ea0bc1 100644 --- a/tests/unit_tests/extrinsics/test_serving.py +++ b/tests/unit_tests/extrinsics/test_serving.py @@ -355,7 +355,7 @@ def test_publish_metadata( patch.object(mock_subtensor.substrate, "compose_call"), patch.object( mock_subtensor, "sign_and_send_extrinsic", return_value=response_success - ), + ) as mocked_sign_and_send_extrinsic, ): # Act result = serving.publish_metadata( @@ -369,3 +369,11 @@ def test_publish_metadata( ) # Assert assert result == expected_result, f"Test ID: {test_id}" + mocked_sign_and_send_extrinsic.assert_called_once_with( + call=mock_subtensor.substrate.compose_call.return_value, + wallet=mock_wallet, + sign_with="hotkey", + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=None, + ) diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index b1020d7648..0db7c0cafe 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -2945,6 +2945,7 @@ def test_unstake_success(mocker, subtensor, fake_wallet): allow_partial_stake=False, rate_tolerance=0.005, period=None, + unstake_all=False, ) assert result == mock_unstake_extrinsic.return_value @@ -2982,6 +2983,7 @@ def test_unstake_with_safe_staking(mocker, subtensor, fake_wallet): allow_partial_stake=True, rate_tolerance=fake_rate_tolerance, period=None, + unstake_all=False, ) assert result == mock_unstake_extrinsic.return_value @@ -3105,6 +3107,7 @@ def test_unstake_multiple_success(mocker, subtensor, fake_wallet): wait_for_inclusion=True, wait_for_finalization=False, period=None, + unstake_all=False, ) assert result == mock_unstake_multiple_extrinsic.return_value diff --git a/tests/unit_tests/test_subtensor_api.py b/tests/unit_tests/test_subtensor_api.py index 35d6c6a15c..5007370d96 100644 --- a/tests/unit_tests/test_subtensor_api.py +++ b/tests/unit_tests/test_subtensor_api.py @@ -6,8 +6,12 @@ def test_properties_methods_comparable(other_class: "Subtensor" = None): """Verifies that methods in SubtensorApi and its properties contains all Subtensors methods.""" # Preps - subtensor = other_class(_mock=True) if other_class else Subtensor(_mock=True) - subtensor_api = SubtensorApi(mock=True) + subtensor = ( + other_class(network="latent-lite", _mock=True) + if other_class + else Subtensor(network="latent-lite", _mock=True) + ) + subtensor_api = SubtensorApi(network="latent-lite", mock=True) subtensor_methods = [m for m in dir(subtensor) if not m.startswith("_")] @@ -62,8 +66,12 @@ def test__methods_comparable_with_passed_legacy_methods( ): """Verifies that methods in SubtensorApi contains all Subtensors methods if `legacy_methods=True` is passed.""" # Preps - subtensor = other_class(mock=True) if other_class else Subtensor(_mock=True) - subtensor_api = SubtensorApi(mock=True, legacy_methods=True) + subtensor = ( + other_class(network="latent-lite", mock=True) + if other_class + else Subtensor(network="latent-lite", _mock=True) + ) + subtensor_api = SubtensorApi(network="latent-lite", mock=True, legacy_methods=True) subtensor_methods = [m for m in dir(subtensor) if not m.startswith("_")] subtensor_api_methods = [m for m in dir(subtensor_api) if not m.startswith("_")] diff --git a/tests/unit_tests/test_subtensor_extended.py b/tests/unit_tests/test_subtensor_extended.py index 45f65f24db..78851574cf 100644 --- a/tests/unit_tests/test_subtensor_extended.py +++ b/tests/unit_tests/test_subtensor_extended.py @@ -1564,7 +1564,7 @@ def get_block_handler( }, }, ] - mock_substrate._get_block_handler.side_effect = get_block_handler + mock_substrate.get_block_handler.side_effect = get_block_handler subtensor.wait_for_block(block=9)