diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index dbf13be3b..0753c1471 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -279,6 +279,9 @@ class Options: "--dashboard.path", help="Path to save the dashboard HTML file. For example: `~/.bittensor/dashboard`.", ) + era: int = typer.Option( + 3, help="Length (in blocks) for which the transaction should be valid." + ) def list_prompt(init_var: list, list_type: type, help_text: str) -> list: @@ -1749,6 +1752,7 @@ def wallet_transfer( transfer_all: bool = typer.Option( False, "--all", prompt=False, help="Transfer all available balance." ), + era: int = Options.era, wallet_name: str = Options.wallet_name, wallet_path: str = Options.wallet_path, wallet_hotkey: str = Options.wallet_hotkey, @@ -1797,12 +1801,13 @@ def wallet_transfer( amount = FloatPrompt.ask("Enter amount (in TAO) to transfer.") return self._run_command( wallets.transfer( - wallet, - subtensor, - destination_ss58_address, - amount, - transfer_all, - prompt, + wallet=wallet, + subtensor=subtensor, + destination=destination_ss58_address, + amount=amount, + transfer_all=transfer_all, + era=era, + prompt=prompt, ) ) @@ -3112,6 +3117,7 @@ def stake_add( rate_tolerance: Optional[float] = Options.rate_tolerance, safe_staking: Optional[bool] = Options.safe_staking, allow_partial_stake: Optional[bool] = Options.allow_partial_stake, + era: int = Options.era, prompt: bool = Options.prompt, quiet: bool = Options.quiet, verbose: bool = Options.verbose, @@ -3305,6 +3311,7 @@ def stake_add( safe_staking, rate_tolerance, allow_partial_stake, + era, ) ) @@ -3356,6 +3363,7 @@ def stake_remove( rate_tolerance: Optional[float] = Options.rate_tolerance, safe_staking: Optional[bool] = Options.safe_staking, allow_partial_stake: Optional[bool] = Options.allow_partial_stake, + era: int = Options.era, prompt: bool = Options.prompt, interactive: bool = typer.Option( False, @@ -3545,6 +3553,7 @@ def stake_remove( include_hotkeys=include_hotkeys, exclude_hotkeys=exclude_hotkeys, prompt=prompt, + era=era, ) ) elif ( @@ -3599,6 +3608,7 @@ def stake_remove( safe_staking=safe_staking, rate_tolerance=rate_tolerance, allow_partial_stake=allow_partial_stake, + era=era, ) ) @@ -3626,6 +3636,7 @@ def stake_move( stake_all: bool = typer.Option( False, "--stake-all", "--all", help="Stake all", prompt=False ), + era: int = Options.era, prompt: bool = Options.prompt, quiet: bool = Options.quiet, verbose: bool = Options.verbose, @@ -3753,6 +3764,7 @@ def stake_move( destination_hotkey=destination_hotkey, amount=amount, stake_all=stake_all, + era=era, interactive_selection=interactive_selection, prompt=prompt, ) @@ -3790,6 +3802,7 @@ def stake_transfer( stake_all: bool = typer.Option( False, "--stake-all", "--all", help="Stake all", prompt=False ), + era: int = Options.era, prompt: bool = Options.prompt, quiet: bool = Options.quiet, verbose: bool = Options.verbose, @@ -3910,6 +3923,7 @@ def stake_transfer( dest_netuid=dest_netuid, dest_coldkey_ss58=dest_ss58, amount=amount, + era=era, interactive_selection=interactive_selection, stake_all=stake_all, prompt=prompt, @@ -3948,6 +3962,7 @@ def stake_swap( "--all", help="Swap all available stake", ), + era: int = Options.era, prompt: bool = Options.prompt, wait_for_inclusion: bool = Options.wait_for_inclusion, wait_for_finalization: bool = Options.wait_for_finalization, @@ -4010,6 +4025,7 @@ def stake_swap( destination_netuid=dest_netuid, amount=amount, swap_all=swap_all, + era=era, interactive_selection=interactive_selection, prompt=prompt, wait_for_inclusion=wait_for_inclusion, @@ -5041,6 +5057,13 @@ def subnets_register( wallet_hotkey: str = Options.wallet_hotkey, network: Optional[list[str]] = Options.network, netuid: int = Options.netuid, + era: Optional[ + int + ] = typer.Option( # Should not be Options.era bc this needs to be an Optional[int] + None, + help="Length (in blocks) for which the transaction should be valid. Note that it is possible that if you " + "use an era for this transaction that you may pay a different fee to register than the one stated.", + ), prompt: bool = Options.prompt, quiet: bool = Options.quiet, verbose: bool = Options.verbose, @@ -5069,6 +5092,7 @@ def subnets_register( wallet, self.initialize_chain(network), netuid, + era, prompt, ) ) diff --git a/bittensor_cli/src/bittensor/extrinsics/registration.py b/bittensor_cli/src/bittensor/extrinsics/registration.py index 65d8f348d..bcd809bdd 100644 --- a/bittensor_cli/src/bittensor/extrinsics/registration.py +++ b/bittensor_cli/src/bittensor/extrinsics/registration.py @@ -676,6 +676,7 @@ async def burned_register_extrinsic( old_balance: Balance, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + era: Optional[int] = None, prompt: bool = False, ) -> bool: """Registers the wallet to chain by recycling TAO. @@ -704,13 +705,32 @@ async def burned_register_extrinsic( my_uid = await subtensor.query( "SubtensorModule", "Uids", [netuid, wallet.hotkey.ss58_address] ) + block_hash = await subtensor.substrate.get_chain_head() print_verbose("Checking if already registered", status) neuron = await subtensor.neuron_for_uid( - uid=my_uid, - netuid=netuid, - block_hash=subtensor.substrate.last_block_hash, + uid=my_uid, netuid=netuid, block_hash=block_hash ) + if not era: + current_block, tempo, blocks_since_last_step = await asyncio.gather( + subtensor.substrate.get_block_number(block_hash=block_hash), + subtensor.get_hyperparameter( + "Tempo", netuid=netuid, block_hash=block_hash + ), + subtensor.query( + "SubtensorModule", + "BlocksSinceLastStep", + [netuid], + block_hash=block_hash, + ), + ) + validity_period = tempo - blocks_since_last_step + era_ = { + "period": validity_period, + "current": current_block, + } + else: + era_ = {"period": era} if not neuron.is_null: console.print( @@ -734,7 +754,7 @@ async def burned_register_extrinsic( }, ) success, err_msg = await subtensor.sign_and_send_extrinsic( - call, wallet, wait_for_inclusion, wait_for_finalization + call, wallet, wait_for_inclusion, wait_for_finalization, era=era_ ) if not success: diff --git a/bittensor_cli/src/bittensor/extrinsics/transfer.py b/bittensor_cli/src/bittensor/extrinsics/transfer.py index 5a167f0b2..0deefc467 100644 --- a/bittensor_cli/src/bittensor/extrinsics/transfer.py +++ b/bittensor_cli/src/bittensor/extrinsics/transfer.py @@ -24,6 +24,7 @@ async def transfer_extrinsic( wallet: Wallet, destination: str, amount: Balance, + era: int = 3, transfer_all: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, @@ -84,7 +85,7 @@ async def do_transfer() -> tuple[bool, str, str]: call_params={"dest": destination, "value": amount.rao}, ) extrinsic = await subtensor.substrate.create_signed_extrinsic( - call=call, keypair=wallet.coldkey + call=call, keypair=wallet.coldkey, era={"period": era} ) response = await subtensor.substrate.submit_extrinsic( extrinsic, diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index a19dc8b85..ecd58f7c7 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -1048,6 +1048,7 @@ async def sign_and_send_extrinsic( wallet: Wallet, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, + era: Optional[dict[str, int]] = None, ) -> tuple[bool, str]: """ Helper method to sign and submit an extrinsic call to chain. @@ -1059,8 +1060,11 @@ async def sign_and_send_extrinsic( :return: (success, error message) """ + call_args = {"call": call, "keypair": wallet.coldkey} + if era is not None: + call_args["era"] = era extrinsic = await self.substrate.create_signed_extrinsic( - call=call, keypair=wallet.coldkey + **call_args ) # sign with coldkey try: response = await self.substrate.submit_extrinsic( diff --git a/bittensor_cli/src/commands/stake/add.py b/bittensor_cli/src/commands/stake/add.py index 2451d51b2..79d6fd3cb 100644 --- a/bittensor_cli/src/commands/stake/add.py +++ b/bittensor_cli/src/commands/stake/add.py @@ -38,6 +38,7 @@ async def stake_add( safe_staking: bool, rate_tolerance: float, allow_partial_stake: bool, + era: int, ): """ Args: @@ -53,6 +54,7 @@ async def stake_add( safe_staking: whether to use safe staking rate_tolerance: rate tolerance percentage for stake operations allow_partial_stake: whether to allow partial stake + era: Blocks for which the transaction should be valid. Returns: bool: True if stake operation is successful, False otherwise @@ -86,7 +88,10 @@ async def safe_stake_extrinsic( }, ) extrinsic = await subtensor.substrate.create_signed_extrinsic( - call=call, keypair=wallet.coldkey, nonce=next_nonce + call=call, + keypair=wallet.coldkey, + nonce=next_nonce, + era={"period": era}, ) try: response = await subtensor.substrate.submit_extrinsic( @@ -105,7 +110,6 @@ async def safe_stake_extrinsic( err_out(f"\n{failure_prelude} with error: {format_error_message(e)}") return else: - await response.process_events() if not await response.is_success: err_out( f"\n{failure_prelude} with error: {format_error_message(await response.error_message)}" @@ -166,7 +170,7 @@ async def stake_extrinsic( }, ) extrinsic = await subtensor.substrate.create_signed_extrinsic( - call=call, keypair=wallet.coldkey, nonce=next_nonce + call=call, keypair=wallet.coldkey, nonce=next_nonce, era={"period": era} ) try: response = await subtensor.substrate.submit_extrinsic( diff --git a/bittensor_cli/src/commands/stake/move.py b/bittensor_cli/src/commands/stake/move.py index 403dc9668..f67ac305f 100644 --- a/bittensor_cli/src/commands/stake/move.py +++ b/bittensor_cli/src/commands/stake/move.py @@ -444,6 +444,7 @@ async def move_stake( destination_hotkey: str, amount: float, stake_all: bool, + era: int, interactive_selection: bool = False, prompt: bool = True, ): @@ -563,7 +564,7 @@ async def move_stake( }, ) extrinsic = await subtensor.substrate.create_signed_extrinsic( - call=call, keypair=wallet.coldkey + call=call, keypair=wallet.coldkey, era={"period": era} ) response = await subtensor.substrate.submit_extrinsic( extrinsic, wait_for_inclusion=True, wait_for_finalization=False @@ -622,6 +623,7 @@ async def transfer_stake( origin_netuid: int, dest_netuid: int, dest_coldkey_ss58: str, + era: int, interactive_selection: bool = False, stake_all: bool = False, prompt: bool = True, @@ -747,7 +749,7 @@ async def transfer_stake( ) extrinsic = await subtensor.substrate.create_signed_extrinsic( - call=call, keypair=wallet.coldkey + call=call, keypair=wallet.coldkey, era={"period": era} ) response = await subtensor.substrate.submit_extrinsic( @@ -798,6 +800,7 @@ async def swap_stake( destination_netuid: int, amount: float, swap_all: bool = False, + era: int = 3, interactive_selection: bool = False, prompt: bool = True, wait_for_inclusion: bool = True, @@ -917,7 +920,7 @@ async def swap_stake( ) extrinsic = await subtensor.substrate.create_signed_extrinsic( - call=call, keypair=wallet.coldkey + call=call, keypair=wallet.coldkey, era={"period": era} ) response = await subtensor.substrate.submit_extrinsic( diff --git a/bittensor_cli/src/commands/stake/remove.py b/bittensor_cli/src/commands/stake/remove.py index 898af9cf8..3488546a7 100644 --- a/bittensor_cli/src/commands/stake/remove.py +++ b/bittensor_cli/src/commands/stake/remove.py @@ -41,6 +41,7 @@ async def unstake( safe_staking: bool, rate_tolerance: float, allow_partial_stake: bool, + era: int, ): """Unstake from hotkey(s).""" with console.status( @@ -302,6 +303,7 @@ async def unstake( current_stake=op["current_stake_balance"], hotkey_ss58=op["hotkey_ss58"], status=status, + era=era, ) else: await _safe_unstake_extrinsic( @@ -313,6 +315,7 @@ async def unstake( price_limit=op["price_with_tolerance"], allow_partial_stake=allow_partial_stake, status=status, + era=era, ) else: for op in unstake_operations: @@ -324,6 +327,7 @@ async def unstake( current_stake=op["current_stake_balance"], hotkey_ss58=op["hotkey_ss58"], status=status, + era=era, ) console.print( f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]Unstaking operations completed." @@ -338,6 +342,7 @@ async def unstake_all( all_hotkeys: bool = False, include_hotkeys: Optional[list[str]] = None, exclude_hotkeys: Optional[list[str]] = None, + era: int = 3, prompt: bool = True, ) -> bool: """Unstakes all stakes from all hotkeys in all subnets.""" @@ -512,6 +517,7 @@ async def unstake_all( hotkey_name=hotkey_names.get(hotkey_ss58, hotkey_ss58), unstake_all_alpha=unstake_all_alpha, status=status, + era=era, ) @@ -524,6 +530,7 @@ async def _unstake_extrinsic( current_stake: Balance, hotkey_ss58: str, status=None, + era: int = 3, ) -> None: """Execute a standard unstake extrinsic. @@ -535,6 +542,7 @@ async def _unstake_extrinsic( wallet: Wallet instance subtensor: Subtensor interface status: Optional status for console updates + era: blocks for which the transaction is valid """ err_out = partial(print_error, status=status) failure_prelude = ( @@ -557,7 +565,7 @@ async def _unstake_extrinsic( }, ) extrinsic = await subtensor.substrate.create_signed_extrinsic( - call=call, keypair=wallet.coldkey + call=call, keypair=wallet.coldkey, era={"period": era} ) try: @@ -607,6 +615,7 @@ async def _safe_unstake_extrinsic( price_limit: Balance, allow_partial_stake: bool, status=None, + era: int = 3, ) -> None: """Execute a safe unstake extrinsic with price limit. @@ -655,7 +664,7 @@ async def _safe_unstake_extrinsic( ) extrinsic = await subtensor.substrate.create_signed_extrinsic( - call=call, keypair=wallet.coldkey, nonce=next_nonce + call=call, keypair=wallet.coldkey, nonce=next_nonce, era={"period": era} ) try: @@ -720,6 +729,7 @@ async def _unstake_all_extrinsic( hotkey_name: str, unstake_all_alpha: bool, status=None, + era: int = 3, ) -> None: """Execute an unstake all extrinsic. @@ -770,8 +780,7 @@ async def _unstake_all_extrinsic( try: response = await subtensor.substrate.submit_extrinsic( extrinsic=await subtensor.substrate.create_signed_extrinsic( - call=call, - keypair=wallet.coldkey, + call=call, keypair=wallet.coldkey, era={"period": era} ), wait_for_inclusion=True, wait_for_finalization=False, diff --git a/bittensor_cli/src/commands/subnets/subnets.py b/bittensor_cli/src/commands/subnets/subnets.py index 81043d8c2..174622a3d 100644 --- a/bittensor_cli/src/commands/subnets/subnets.py +++ b/bittensor_cli/src/commands/subnets/subnets.py @@ -1436,7 +1436,11 @@ async def pow_register( async def register( - wallet: Wallet, subtensor: "SubtensorInterface", netuid: int, prompt: bool + wallet: Wallet, + subtensor: "SubtensorInterface", + netuid: int, + era: Optional[int], + prompt: bool, ): """Register neuron by recycling some TAO.""" @@ -1534,6 +1538,7 @@ async def register( netuid=netuid, prompt=False, old_balance=balance, + era=era, ) diff --git a/bittensor_cli/src/commands/wallets.py b/bittensor_cli/src/commands/wallets.py index 333e5e0ff..28cb71304 100644 --- a/bittensor_cli/src/commands/wallets.py +++ b/bittensor_cli/src/commands/wallets.py @@ -1188,6 +1188,7 @@ async def transfer( destination: str, amount: float, transfer_all: bool, + era: int, prompt: bool, ): """Transfer token of amount to destination.""" @@ -1197,6 +1198,7 @@ async def transfer( destination=destination, amount=Balance.from_tao(amount), transfer_all=transfer_all, + era=era, prompt=prompt, ) diff --git a/tests/e2e_tests/test_senate.py b/tests/e2e_tests/test_senate.py index dfd2cfa57..d2badc696 100644 --- a/tests/e2e_tests/test_senate.py +++ b/tests/e2e_tests/test_senate.py @@ -222,9 +222,10 @@ def test_senate(local_chain, wallet_setup): assert proposals_after_nay_output[9].split()[4] == "1" # Assert Alice has voted Nay - assert proposals_after_nay_output[10].split()[0].strip( - ":" - ) == wallet_alice.hotkey.ss58_address + assert ( + proposals_after_nay_output[10].split()[0].strip(":") + == wallet_alice.hotkey.ss58_address + ) # Assert vote casted as Nay assert proposals_after_nay_output[10].split()[1] == "Nay" diff --git a/tests/e2e_tests/test_staking_sudo.py b/tests/e2e_tests/test_staking_sudo.py index 5ab214604..0f1d817af 100644 --- a/tests/e2e_tests/test_staking_sudo.py +++ b/tests/e2e_tests/test_staking_sudo.py @@ -112,9 +112,11 @@ def test_staking(local_chain, wallet_setup): "0.1", "--partial", "--no-prompt", + "--era", + "144", ], ) - assert "✅ Finalized" in add_stake.stdout + assert "✅ Finalized" in add_stake.stdout, add_stake.stderr # Execute stake show for Alice's wallet show_stake = exec_command_alice( @@ -157,6 +159,8 @@ def test_staking(local_chain, wallet_setup): "0.1", "--partial", "--no-prompt", + "--era", + "144", ], ) assert "✅ Finalized" in remove_stake.stdout diff --git a/tests/e2e_tests/test_unstaking.py b/tests/e2e_tests/test_unstaking.py index 91eb406ea..bf29d1858 100644 --- a/tests/e2e_tests/test_unstaking.py +++ b/tests/e2e_tests/test_unstaking.py @@ -139,6 +139,8 @@ def test_unstaking(local_chain, wallet_setup): "--partial", "--tolerance", "0.5", + "--era", + "144", ], ) assert "✅ Finalized" in stake_result.stdout @@ -184,6 +186,8 @@ def test_unstaking(local_chain, wallet_setup): "--partial", "--tolerance", "0.5", + "--era", + "144", ], ) assert "✅ Finalized" in partial_unstake_netuid_2.stdout @@ -229,6 +233,8 @@ def test_unstaking(local_chain, wallet_setup): "--all-alpha", "--no-prompt", "--verbose", + "--era", + "144", ], ) @@ -258,6 +264,8 @@ def test_unstaking(local_chain, wallet_setup): "--partial", "--tolerance", "0.5", + "--era", + "144", ], ) assert "✅ Finalized" in stake_result.stdout @@ -277,6 +285,8 @@ def test_unstaking(local_chain, wallet_setup): "ws://127.0.0.1:9945", "--all", "--no-prompt", + "--era", + "144", ], ) assert "✅ Finalized: Successfully unstaked all stakes from" in unstake_all.stdout diff --git a/tests/e2e_tests/test_wallet_interactions.py b/tests/e2e_tests/test_wallet_interactions.py index 19cdcc22a..3d08591c0 100644 --- a/tests/e2e_tests/test_wallet_interactions.py +++ b/tests/e2e_tests/test_wallet_interactions.py @@ -245,6 +245,8 @@ def test_wallet_transfer(local_chain, wallet_setup): "--amount", "100", "--no-prompt", + "--era", + "144", ], )