From 6400f72b2ba1ef19cc5f33a9c6fdada06f8fade2 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 12 Feb 2025 16:44:00 +0200 Subject: [PATCH 01/11] Adds nonce protection for staking/unstaking calls. --- bittensor/core/extrinsics/asyncex/staking.py | 11 ++-- .../core/extrinsics/asyncex/unstaking.py | 12 ++-- bittensor/core/extrinsics/asyncex/weights.py | 39 +----------- bittensor/core/extrinsics/commit_weights.py | 25 +------- bittensor/core/extrinsics/unstaking.py | 9 +-- bittensor/core/extrinsics/utils.py | 63 ++++++++++++++++++- 6 files changed, 85 insertions(+), 74 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/staking.py b/bittensor/core/extrinsics/asyncex/staking.py index fc7205cadb..d7f3a01e8b 100644 --- a/bittensor/core/extrinsics/asyncex/staking.py +++ b/bittensor/core/extrinsics/asyncex/staking.py @@ -6,6 +6,9 @@ from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging from bittensor.core.extrinsics.utils import get_old_stakes +from bittensor.core.extrinsics.utils import ( + async_sign_and_send_with_nonce as sign_and_send_with_nonce, +) if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -111,8 +114,8 @@ async def add_stake_extrinsic( "netuid": netuid, }, ) - staking_response, err_msg = await subtensor.sign_and_send_extrinsic( - call, wallet, wait_for_inclusion, wait_for_finalization + staking_response, err_msg = await sign_and_send_with_nonce( + subtensor, call, wallet, wait_for_inclusion, wait_for_finalization ) if staking_response is True: # If we successfully staked. # We only wait here if we expect finalization. @@ -295,8 +298,8 @@ async def add_stake_multiple_extrinsic( "netuid": netuid, }, ) - staking_response, err_msg = await subtensor.sign_and_send_extrinsic( - call, wallet, wait_for_inclusion, wait_for_finalization + staking_response, err_msg = await sign_and_send_with_nonce( + subtensor, call, wallet, wait_for_inclusion, wait_for_finalization ) if staking_response is True: # If we successfully staked. diff --git a/bittensor/core/extrinsics/asyncex/unstaking.py b/bittensor/core/extrinsics/asyncex/unstaking.py index 077e6bfb14..90a01d76ba 100644 --- a/bittensor/core/extrinsics/asyncex/unstaking.py +++ b/bittensor/core/extrinsics/asyncex/unstaking.py @@ -2,6 +2,9 @@ from typing import Optional, TYPE_CHECKING from bittensor.core.errors import StakeError, NotRegisteredError +from bittensor.core.extrinsics.utils import ( + async_sign_and_send_with_nonce as sign_and_send_with_nonce, +) from bittensor.utils import unlock_key from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -97,8 +100,8 @@ async def unstake_extrinsic( "netuid": netuid, }, ) - staking_response, err_msg = await subtensor.sign_and_send_extrinsic( - call, wallet, wait_for_inclusion, wait_for_finalization + staking_response, err_msg = await sign_and_send_with_nonce( + subtensor, call, wallet, wait_for_inclusion, wait_for_finalization ) if staking_response is True: # If we successfully unstaked. @@ -261,8 +264,9 @@ async def unstake_multiple_extrinsic( "netuid": netuid, }, ) - staking_response, err_msg = await subtensor.sign_and_send_extrinsic( - call, wallet, wait_for_inclusion, wait_for_finalization + + staking_response, err_msg = await sign_and_send_with_nonce( + subtensor, call, wallet, wait_for_inclusion, wait_for_finalization ) if staking_response is True: # If we successfully unstaked. diff --git a/bittensor/core/extrinsics/asyncex/weights.py b/bittensor/core/extrinsics/asyncex/weights.py index cb9fe16798..f0d93cd073 100644 --- a/bittensor/core/extrinsics/asyncex/weights.py +++ b/bittensor/core/extrinsics/asyncex/weights.py @@ -9,47 +9,14 @@ from bittensor.core.settings import version_as_int from bittensor.utils import format_error_message from bittensor.utils.btlogging import logging +from bittensor.core.extrinsics.utils import ( + async_sign_and_send_with_nonce as sign_and_send_with_nonce, +) if TYPE_CHECKING: from bittensor_wallet import Wallet from bittensor.core.async_subtensor import AsyncSubtensor from bittensor.utils.registration import torch - from scalecodec.types import GenericCall - - -async def sign_and_send_with_nonce( - subtensor: "AsyncSubtensor", - call: "GenericCall", - wallet: "Wallet", - wait_for_inclusion: bool, - wait_for_finalization: bool, - period: Optional[int] = None, -): - """ - Signs an extrinsic call with the wallet hotkey, adding an optional era for period - """ - next_nonce = await subtensor.substrate.get_account_next_index( - wallet.hotkey.ss58_address - ) - - extrinsic_data = {"call": call, "keypair": wallet.hotkey, "nonce": next_nonce} - if period is not None: - extrinsic_data["era"] = {"period": period} - - extrinsic = await subtensor.substrate.create_signed_extrinsic(**extrinsic_data) - response = await subtensor.substrate.submit_extrinsic( - extrinsic=extrinsic, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - - if not wait_for_finalization and not wait_for_inclusion: - return True, None - - if await response.is_success: - return True, None - - return False, format_error_message(await response.error_message) async def _do_commit_weights( diff --git a/bittensor/core/extrinsics/commit_weights.py b/bittensor/core/extrinsics/commit_weights.py index 4a2c9b9f04..b2f38b15c8 100644 --- a/bittensor/core/extrinsics/commit_weights.py +++ b/bittensor/core/extrinsics/commit_weights.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING, Optional +from bittensor.core.extrinsics.utils import sign_and_send_with_nonce from bittensor.utils import format_error_message from bittensor.utils.btlogging import logging @@ -10,30 +11,6 @@ from bittensor.core.subtensor import Subtensor -def sign_and_send_with_nonce( - subtensor: "Subtensor", call, wallet, wait_for_inclusion, wait_for_finalization -): - next_nonce = subtensor.substrate.get_account_next_index(wallet.hotkey.ss58_address) - extrinsic = subtensor.substrate.create_signed_extrinsic( - call=call, - keypair=wallet.hotkey, - nonce=next_nonce, - ) - response = subtensor.substrate.submit_extrinsic( - extrinsic=extrinsic, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - - if not wait_for_finalization and not wait_for_inclusion: - return True, None - - if response.is_success: - return True, None - - return False, format_error_message(response.error_message) - - def _do_commit_weights( subtensor: "Subtensor", wallet: "Wallet", diff --git a/bittensor/core/extrinsics/unstaking.py b/bittensor/core/extrinsics/unstaking.py index 10f286053e..b5d53df3ad 100644 --- a/bittensor/core/extrinsics/unstaking.py +++ b/bittensor/core/extrinsics/unstaking.py @@ -6,6 +6,7 @@ from bittensor.utils import unlock_key from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging +from bittensor.core.extrinsics.utils import sign_and_send_with_nonce if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -92,8 +93,8 @@ def unstake_extrinsic( "netuid": netuid, }, ) - staking_response, err_msg = subtensor.sign_and_send_extrinsic( - call, wallet, wait_for_inclusion, wait_for_finalization + staking_response, err_msg = sign_and_send_with_nonce( + subtensor, call, wallet, wait_for_inclusion, wait_for_finalization ) if staking_response is True: # If we successfully unstaked. @@ -247,8 +248,8 @@ def unstake_multiple_extrinsic( "netuid": netuid, }, ) - staking_response, err_msg = subtensor.sign_and_send_extrinsic( - call, wallet, wait_for_inclusion, wait_for_finalization + staking_response, err_msg = sign_and_send_with_nonce( + subtensor, call, wallet, wait_for_inclusion, wait_for_finalization ) if staking_response is True: # If we successfully unstaked. diff --git a/bittensor/core/extrinsics/utils.py b/bittensor/core/extrinsics/utils.py index 7cc2e3c4e0..f2745ac634 100644 --- a/bittensor/core/extrinsics/utils.py +++ b/bittensor/core/extrinsics/utils.py @@ -1,6 +1,6 @@ """Module with helper functions for extrinsics.""" -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from async_substrate_interface.errors import SubstrateRequestException @@ -17,7 +17,66 @@ ) from bittensor.core.subtensor import Subtensor from bittensor.core.chain_data import StakeInfo - from scalecodec.types import GenericExtrinsic + from scalecodec.types import GenericExtrinsic, GenericCall + + +async def async_sign_and_send_with_nonce( + subtensor: "AsyncSubtensor", + call: "GenericCall", + wallet: "Wallet", + wait_for_inclusion: bool, + wait_for_finalization: bool, + period: Optional[int] = None, +): + """ + Signs an extrinsic call with the wallet hotkey, adding an optional era for period + """ + next_nonce = await subtensor.substrate.get_account_next_index( + wallet.hotkey.ss58_address + ) + + extrinsic_data = {"call": call, "keypair": wallet.hotkey, "nonce": next_nonce} + if period is not None: + extrinsic_data["era"] = {"period": period} + + extrinsic = await subtensor.substrate.create_signed_extrinsic(**extrinsic_data) + response = await subtensor.substrate.submit_extrinsic( + extrinsic=extrinsic, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + + if not wait_for_finalization and not wait_for_inclusion: + return True, None + + if await response.is_success: + return True, None + + return False, format_error_message(await response.error_message) + + +def sign_and_send_with_nonce( + subtensor: "Subtensor", call, wallet, wait_for_inclusion, wait_for_finalization +): + next_nonce = subtensor.substrate.get_account_next_index(wallet.hotkey.ss58_address) + extrinsic = subtensor.substrate.create_signed_extrinsic( + call=call, + keypair=wallet.hotkey, + nonce=next_nonce, + ) + response = subtensor.substrate.submit_extrinsic( + extrinsic=extrinsic, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + + if not wait_for_finalization and not wait_for_inclusion: + return True, None + + if response.is_success: + return True, None + + return False, format_error_message(response.error_message) def submit_extrinsic( From 8e7bddadda0178d0af02edee2f24e05605712050 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 12 Feb 2025 17:33:05 +0200 Subject: [PATCH 02/11] Get the nonce for staking calls from the coldkeypub rather than the hotkey --- bittensor/core/extrinsics/asyncex/staking.py | 14 ++++++- .../core/extrinsics/asyncex/unstaking.py | 14 ++++++- bittensor/core/extrinsics/staking.py | 20 ++++++--- bittensor/core/extrinsics/unstaking.py | 14 ++++++- bittensor/core/extrinsics/utils.py | 42 ++++++++++++------- 5 files changed, 77 insertions(+), 27 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/staking.py b/bittensor/core/extrinsics/asyncex/staking.py index d7f3a01e8b..4e13d362e6 100644 --- a/bittensor/core/extrinsics/asyncex/staking.py +++ b/bittensor/core/extrinsics/asyncex/staking.py @@ -115,7 +115,12 @@ async def add_stake_extrinsic( }, ) staking_response, err_msg = await sign_and_send_with_nonce( - subtensor, call, wallet, wait_for_inclusion, wait_for_finalization + subtensor, + call, + wallet, + wait_for_inclusion, + wait_for_finalization, + nonce_key="coldkeypub", ) if staking_response is True: # If we successfully staked. # We only wait here if we expect finalization. @@ -299,7 +304,12 @@ async def add_stake_multiple_extrinsic( }, ) staking_response, err_msg = await sign_and_send_with_nonce( - subtensor, call, wallet, wait_for_inclusion, wait_for_finalization + subtensor, + call, + wallet, + wait_for_inclusion, + wait_for_finalization, + nonce_key="coldkeypub", ) if staking_response is True: # If we successfully staked. diff --git a/bittensor/core/extrinsics/asyncex/unstaking.py b/bittensor/core/extrinsics/asyncex/unstaking.py index 90a01d76ba..01dedb210b 100644 --- a/bittensor/core/extrinsics/asyncex/unstaking.py +++ b/bittensor/core/extrinsics/asyncex/unstaking.py @@ -101,7 +101,12 @@ async def unstake_extrinsic( }, ) staking_response, err_msg = await sign_and_send_with_nonce( - subtensor, call, wallet, wait_for_inclusion, wait_for_finalization + subtensor, + call, + wallet, + wait_for_inclusion, + wait_for_finalization, + nonce_key="coldkeypub", ) if staking_response is True: # If we successfully unstaked. @@ -266,7 +271,12 @@ async def unstake_multiple_extrinsic( ) staking_response, err_msg = await sign_and_send_with_nonce( - subtensor, call, wallet, wait_for_inclusion, wait_for_finalization + subtensor, + call, + wallet, + wait_for_inclusion, + wait_for_finalization, + nonce_key="coldkeypub", ) if staking_response is True: # If we successfully unstaked. diff --git a/bittensor/core/extrinsics/staking.py b/bittensor/core/extrinsics/staking.py index 5ca7b250a9..a0b80be173 100644 --- a/bittensor/core/extrinsics/staking.py +++ b/bittensor/core/extrinsics/staking.py @@ -2,7 +2,7 @@ from typing import Optional, TYPE_CHECKING, Sequence from bittensor.core.errors import StakeError, NotRegisteredError -from bittensor.core.extrinsics.utils import get_old_stakes +from bittensor.core.extrinsics.utils import get_old_stakes, sign_and_send_with_nonce from bittensor.utils import unlock_key from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -106,8 +106,13 @@ def add_stake_extrinsic( "netuid": netuid, }, ) - staking_response, err_msg = subtensor.sign_and_send_extrinsic( - call, wallet, wait_for_inclusion, wait_for_finalization + staking_response, err_msg = sign_and_send_with_nonce( + subtensor, + call, + wallet, + wait_for_inclusion, + wait_for_finalization, + nonce_key="coldkeypub", ) if staking_response is True: # If we successfully staked. # We only wait here if we expect finalization. @@ -279,8 +284,13 @@ def add_stake_multiple_extrinsic( "netuid": netuid, }, ) - staking_response, err_msg = subtensor.sign_and_send_extrinsic( - call, wallet, wait_for_inclusion, wait_for_finalization + staking_response, err_msg = sign_and_send_with_nonce( + subtensor, + call, + wallet, + wait_for_inclusion, + wait_for_finalization, + nonce_key="coldkeypub", ) if staking_response is True: # If we successfully staked. diff --git a/bittensor/core/extrinsics/unstaking.py b/bittensor/core/extrinsics/unstaking.py index b5d53df3ad..b799db7ab7 100644 --- a/bittensor/core/extrinsics/unstaking.py +++ b/bittensor/core/extrinsics/unstaking.py @@ -94,7 +94,12 @@ def unstake_extrinsic( }, ) staking_response, err_msg = sign_and_send_with_nonce( - subtensor, call, wallet, wait_for_inclusion, wait_for_finalization + subtensor, + call, + wallet, + wait_for_inclusion, + wait_for_finalization, + nonce_key="coldkeypub", ) if staking_response is True: # If we successfully unstaked. @@ -249,7 +254,12 @@ def unstake_multiple_extrinsic( }, ) staking_response, err_msg = sign_and_send_with_nonce( - subtensor, call, wallet, wait_for_inclusion, wait_for_finalization + subtensor, + call, + wallet, + wait_for_inclusion, + wait_for_finalization, + nonce_key="coldkeypub", ) if staking_response is True: # If we successfully unstaked. diff --git a/bittensor/core/extrinsics/utils.py b/bittensor/core/extrinsics/utils.py index f2745ac634..d9cac1623f 100644 --- a/bittensor/core/extrinsics/utils.py +++ b/bittensor/core/extrinsics/utils.py @@ -27,38 +27,48 @@ async def async_sign_and_send_with_nonce( wait_for_inclusion: bool, wait_for_finalization: bool, period: Optional[int] = None, + nonce_key: str = "hotkey", ): """ - Signs an extrinsic call with the wallet hotkey, adding an optional era for period + Signs an extrinsic call with the wallet keypair (default hotkey), adding an optional era for period """ - next_nonce = await subtensor.substrate.get_account_next_index( - wallet.hotkey.ss58_address - ) + keypair = getattr(wallet, nonce_key) + next_nonce = await subtensor.substrate.get_account_next_index(keypair.ss58_address) extrinsic_data = {"call": call, "keypair": wallet.hotkey, "nonce": next_nonce} if period is not None: extrinsic_data["era"] = {"period": period} extrinsic = await subtensor.substrate.create_signed_extrinsic(**extrinsic_data) - response = await subtensor.substrate.submit_extrinsic( - extrinsic=extrinsic, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - if not wait_for_finalization and not wait_for_inclusion: - return True, None + try: + response = await subtensor.substrate.submit_extrinsic( + extrinsic=extrinsic, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) - if await response.is_success: - return True, None + if not wait_for_finalization and not wait_for_inclusion: + return True, None - return False, format_error_message(await response.error_message) + if await response.is_success: + return True, None + + return False, format_error_message(await response.error_message) + except SubstrateRequestException as e: + return False, format_error_message(e) def sign_and_send_with_nonce( - subtensor: "Subtensor", call, wallet, wait_for_inclusion, wait_for_finalization + subtensor: "Subtensor", + call, + wallet, + wait_for_inclusion, + wait_for_finalization, + nonce_key: str = "hotkey", ): - next_nonce = subtensor.substrate.get_account_next_index(wallet.hotkey.ss58_address) + keypair = getattr(wallet, nonce_key) + next_nonce = subtensor.substrate.get_account_next_index(keypair.ss58_address) extrinsic = subtensor.substrate.create_signed_extrinsic( call=call, keypair=wallet.hotkey, From bbc6f5344cb3de3ff76e0d65592f32a2d1b43271 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 12 Feb 2025 17:42:13 +0200 Subject: [PATCH 03/11] Allow using a different signing key than hotkey --- bittensor/core/extrinsics/asyncex/staking.py | 2 ++ bittensor/core/extrinsics/asyncex/unstaking.py | 2 ++ bittensor/core/extrinsics/staking.py | 2 ++ bittensor/core/extrinsics/unstaking.py | 2 ++ bittensor/core/extrinsics/utils.py | 9 ++++++--- 5 files changed, 14 insertions(+), 3 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/staking.py b/bittensor/core/extrinsics/asyncex/staking.py index 4e13d362e6..dff809fe11 100644 --- a/bittensor/core/extrinsics/asyncex/staking.py +++ b/bittensor/core/extrinsics/asyncex/staking.py @@ -121,6 +121,7 @@ async def add_stake_extrinsic( wait_for_inclusion, wait_for_finalization, nonce_key="coldkeypub", + signing_key="coldkey", ) if staking_response is True: # If we successfully staked. # We only wait here if we expect finalization. @@ -310,6 +311,7 @@ async def add_stake_multiple_extrinsic( wait_for_inclusion, wait_for_finalization, nonce_key="coldkeypub", + signing_key="coldkey", ) if staking_response is True: # If we successfully staked. diff --git a/bittensor/core/extrinsics/asyncex/unstaking.py b/bittensor/core/extrinsics/asyncex/unstaking.py index 01dedb210b..2919f057fd 100644 --- a/bittensor/core/extrinsics/asyncex/unstaking.py +++ b/bittensor/core/extrinsics/asyncex/unstaking.py @@ -107,6 +107,7 @@ async def unstake_extrinsic( wait_for_inclusion, wait_for_finalization, nonce_key="coldkeypub", + signing_key="coldkey", ) if staking_response is True: # If we successfully unstaked. @@ -277,6 +278,7 @@ async def unstake_multiple_extrinsic( wait_for_inclusion, wait_for_finalization, nonce_key="coldkeypub", + signing_key="coldkey", ) if staking_response is True: # If we successfully unstaked. diff --git a/bittensor/core/extrinsics/staking.py b/bittensor/core/extrinsics/staking.py index a0b80be173..d5d67748d7 100644 --- a/bittensor/core/extrinsics/staking.py +++ b/bittensor/core/extrinsics/staking.py @@ -113,6 +113,7 @@ def add_stake_extrinsic( wait_for_inclusion, wait_for_finalization, nonce_key="coldkeypub", + signing_key="coldkey", ) if staking_response is True: # If we successfully staked. # We only wait here if we expect finalization. @@ -291,6 +292,7 @@ def add_stake_multiple_extrinsic( wait_for_inclusion, wait_for_finalization, nonce_key="coldkeypub", + signing_key="coldkey", ) if staking_response is True: # If we successfully staked. diff --git a/bittensor/core/extrinsics/unstaking.py b/bittensor/core/extrinsics/unstaking.py index b799db7ab7..c68c5057f3 100644 --- a/bittensor/core/extrinsics/unstaking.py +++ b/bittensor/core/extrinsics/unstaking.py @@ -100,6 +100,7 @@ def unstake_extrinsic( wait_for_inclusion, wait_for_finalization, nonce_key="coldkeypub", + signing_key="coldkey", ) if staking_response is True: # If we successfully unstaked. @@ -260,6 +261,7 @@ def unstake_multiple_extrinsic( wait_for_inclusion, wait_for_finalization, nonce_key="coldkeypub", + signing_key="coldkey", ) if staking_response is True: # If we successfully unstaked. diff --git a/bittensor/core/extrinsics/utils.py b/bittensor/core/extrinsics/utils.py index d9cac1623f..98fad5f6d8 100644 --- a/bittensor/core/extrinsics/utils.py +++ b/bittensor/core/extrinsics/utils.py @@ -28,14 +28,15 @@ async def async_sign_and_send_with_nonce( wait_for_finalization: bool, period: Optional[int] = None, nonce_key: str = "hotkey", + signing_key: str = "hotkey", ): """ Signs an extrinsic call with the wallet keypair (default hotkey), adding an optional era for period """ keypair = getattr(wallet, nonce_key) next_nonce = await subtensor.substrate.get_account_next_index(keypair.ss58_address) - - extrinsic_data = {"call": call, "keypair": wallet.hotkey, "nonce": next_nonce} + signing_keypair = getattr(wallet, signing_key) + extrinsic_data = {"call": call, "keypair": signing_keypair, "nonce": next_nonce} if period is not None: extrinsic_data["era"] = {"period": period} @@ -66,12 +67,14 @@ def sign_and_send_with_nonce( wait_for_inclusion, wait_for_finalization, nonce_key: str = "hotkey", + signing_key: str = "hotkey", ): keypair = getattr(wallet, nonce_key) next_nonce = subtensor.substrate.get_account_next_index(keypair.ss58_address) + signing_keypair = getattr(wallet, signing_key) extrinsic = subtensor.substrate.create_signed_extrinsic( call=call, - keypair=wallet.hotkey, + keypair=signing_keypair, nonce=next_nonce, ) response = subtensor.substrate.submit_extrinsic( From 4e2938aa27e94b194edcc9b5e6f3146756ec406e Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 12 Feb 2025 20:05:30 +0200 Subject: [PATCH 04/11] WIP check-in --- bittensor/core/async_subtensor.py | 19 +++++++- bittensor/core/extrinsics/asyncex/staking.py | 15 +++---- .../core/extrinsics/asyncex/unstaking.py | 20 +++------ bittensor/core/extrinsics/asyncex/weights.py | 18 ++++---- bittensor/core/extrinsics/commit_weights.py | 12 ++--- bittensor/core/extrinsics/staking.py | 24 +++------- bittensor/core/extrinsics/unstaking.py | 13 +++--- bittensor/core/extrinsics/utils.py | 44 +------------------ bittensor/core/subtensor.py | 20 ++++++++- .../extrinsics/asyncex/test_weights.py | 4 +- .../extrinsics/test_commit_weights.py | 6 +-- tests/unit_tests/extrinsics/test_staking.py | 7 +++ tests/unit_tests/extrinsics/test_unstaking.py | 6 +++ 13 files changed, 95 insertions(+), 113 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index e4c4a1c547..d67ab5e872 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -2683,6 +2683,9 @@ async def sign_and_send_extrinsic( wait_for_inclusion: bool = True, wait_for_finalization: bool = False, sign_with: str = "coldkey", + use_nonce: bool = False, + period: Optional[int] = None, + nonce_key: str = "hotkey" ) -> tuple[bool, str]: """ Helper method to sign and submit an extrinsic call to chain. @@ -2697,13 +2700,25 @@ async def sign_and_send_extrinsic( Returns: (success, error message) """ - if sign_with not in ("coldkey", "hotkey", "coldkeypub"): + possible_keys = ("coldkey", "hotkey", "coldkeypub") + if sign_with not in possible_keys: raise AttributeError( f"'sign_with' must be either 'coldkey', 'hotkey' or 'coldkeypub', not '{sign_with}'" ) + signing_keypair = getattr(wallet, sign_with) + extrinsic_data = {"call": call, "keypair": signing_keypair} + if use_nonce: + if nonce_key not in possible_keys: + raise AttributeError( + f"'nonce_key' must be either 'coldkey', 'hotkey' or 'coldkeypub', not '{nonce_key}'" + ) + next_nonce = await self.substrate.get_account_next_index(getattr(wallet, nonce_key).ss58_address) + extrinsic_data["nonce"] = next_nonce + if period is not None: + extrinsic_data["era"] = {"period": period} extrinsic = await self.substrate.create_signed_extrinsic( - call=call, keypair=getattr(wallet, sign_with) + **extrinsic_data ) try: response = await self.substrate.submit_extrinsic( diff --git a/bittensor/core/extrinsics/asyncex/staking.py b/bittensor/core/extrinsics/asyncex/staking.py index dff809fe11..25ab8662a6 100644 --- a/bittensor/core/extrinsics/asyncex/staking.py +++ b/bittensor/core/extrinsics/asyncex/staking.py @@ -6,9 +6,6 @@ from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging from bittensor.core.extrinsics.utils import get_old_stakes -from bittensor.core.extrinsics.utils import ( - async_sign_and_send_with_nonce as sign_and_send_with_nonce, -) if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -114,14 +111,14 @@ async def add_stake_extrinsic( "netuid": netuid, }, ) - staking_response, err_msg = await sign_and_send_with_nonce( - subtensor, + staking_response, err_msg = await subtensor.sign_and_send_extrinsic( call, wallet, wait_for_inclusion, wait_for_finalization, nonce_key="coldkeypub", - signing_key="coldkey", + sign_with="coldkey", + use_nonce=True ) if staking_response is True: # If we successfully staked. # We only wait here if we expect finalization. @@ -304,14 +301,14 @@ async def add_stake_multiple_extrinsic( "netuid": netuid, }, ) - staking_response, err_msg = await sign_and_send_with_nonce( - subtensor, + staking_response, err_msg = await subtensor.sign_and_send_extrinsic( call, wallet, wait_for_inclusion, wait_for_finalization, nonce_key="coldkeypub", - signing_key="coldkey", + sign_with="coldkey", + use_nonce=True ) if staking_response is True: # If we successfully staked. diff --git a/bittensor/core/extrinsics/asyncex/unstaking.py b/bittensor/core/extrinsics/asyncex/unstaking.py index 2919f057fd..e117d556ad 100644 --- a/bittensor/core/extrinsics/asyncex/unstaking.py +++ b/bittensor/core/extrinsics/asyncex/unstaking.py @@ -2,9 +2,6 @@ from typing import Optional, TYPE_CHECKING from bittensor.core.errors import StakeError, NotRegisteredError -from bittensor.core.extrinsics.utils import ( - async_sign_and_send_with_nonce as sign_and_send_with_nonce, -) from bittensor.utils import unlock_key from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -100,14 +97,9 @@ async def unstake_extrinsic( "netuid": netuid, }, ) - staking_response, err_msg = await sign_and_send_with_nonce( - subtensor, - call, - wallet, - wait_for_inclusion, - wait_for_finalization, - nonce_key="coldkeypub", - signing_key="coldkey", + staking_response, err_msg = await subtensor.sign_and_send_extrinsic( + call, wallet, wait_for_inclusion, wait_for_finalization, + nonce_key="coldkeypub", sign_with="coldkey", use_nonce=True ) if staking_response is True: # If we successfully unstaked. @@ -271,14 +263,14 @@ async def unstake_multiple_extrinsic( }, ) - staking_response, err_msg = await sign_and_send_with_nonce( - subtensor, + staking_response, err_msg = await subtensor.sign_and_send_extrinsic( call, wallet, wait_for_inclusion, wait_for_finalization, nonce_key="coldkeypub", - signing_key="coldkey", + sign_with="coldkey", + use_nonce=True ) if staking_response is True: # If we successfully unstaked. diff --git a/bittensor/core/extrinsics/asyncex/weights.py b/bittensor/core/extrinsics/asyncex/weights.py index f0d93cd073..3299797bf4 100644 --- a/bittensor/core/extrinsics/asyncex/weights.py +++ b/bittensor/core/extrinsics/asyncex/weights.py @@ -9,9 +9,6 @@ from bittensor.core.settings import version_as_int from bittensor.utils import format_error_message from bittensor.utils.btlogging import logging -from bittensor.core.extrinsics.utils import ( - async_sign_and_send_with_nonce as sign_and_send_with_nonce, -) if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -54,8 +51,9 @@ async def _do_commit_weights( "commit_hash": commit_hash, }, ) - return await sign_and_send_with_nonce( - subtensor, call, wallet, wait_for_inclusion, wait_for_finalization + return await subtensor.sign_and_send_extrinsic( + call, wallet, wait_for_inclusion, wait_for_finalization, use_nonce=True, + nonce_key="hotkey", sign_with="hotkey" ) @@ -151,8 +149,9 @@ async def _do_reveal_weights( "version_key": version_key, }, ) - return await sign_and_send_with_nonce( - subtensor, call, wallet, wait_for_inclusion, wait_for_finalization + return await subtensor.sign_and_send_extrinsic( + call, wallet, wait_for_inclusion, wait_for_finalization, + sign_with="hotkey", nonce_key="hotkey", use_nonce=True, ) @@ -257,8 +256,9 @@ async def _do_set_weights( "version_key": version_key, }, ) - return await sign_and_send_with_nonce( - subtensor, call, wallet, wait_for_inclusion, wait_for_finalization, period + return await subtensor.sign_and_send_extrinsic( + call, wallet, wait_for_inclusion, wait_for_finalization, period=period, + use_nonce=True, nonce_key="hotkey", sign_with="hotkey" ) diff --git a/bittensor/core/extrinsics/commit_weights.py b/bittensor/core/extrinsics/commit_weights.py index b2f38b15c8..dd967dd2f4 100644 --- a/bittensor/core/extrinsics/commit_weights.py +++ b/bittensor/core/extrinsics/commit_weights.py @@ -2,7 +2,6 @@ from typing import TYPE_CHECKING, Optional -from bittensor.core.extrinsics.utils import sign_and_send_with_nonce from bittensor.utils import format_error_message from bittensor.utils.btlogging import logging @@ -45,9 +44,9 @@ def _do_commit_weights( "commit_hash": commit_hash, }, ) - - return sign_and_send_with_nonce( - subtensor, call, wallet, wait_for_inclusion, wait_for_finalization + return subtensor.sign_and_send_extrinsic( + call, wallet, wait_for_inclusion, wait_for_finalization, + use_nonce=True, sign_with="hotkey", nonce_key="hotkey" ) @@ -141,8 +140,9 @@ def _do_reveal_weights( "version_key": version_key, }, ) - return sign_and_send_with_nonce( - subtensor, call, wallet, wait_for_inclusion, wait_for_finalization + return subtensor.sign_and_send_extrinsic( + call, wallet, wait_for_inclusion, wait_for_finalization, + use_nonce=True, sign_with="hotkey", nonce_key="hotkey" ) diff --git a/bittensor/core/extrinsics/staking.py b/bittensor/core/extrinsics/staking.py index d5d67748d7..1b99f3723a 100644 --- a/bittensor/core/extrinsics/staking.py +++ b/bittensor/core/extrinsics/staking.py @@ -2,7 +2,7 @@ from typing import Optional, TYPE_CHECKING, Sequence from bittensor.core.errors import StakeError, NotRegisteredError -from bittensor.core.extrinsics.utils import get_old_stakes, sign_and_send_with_nonce +from bittensor.core.extrinsics.utils import get_old_stakes from bittensor.utils import unlock_key from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -106,14 +106,9 @@ def add_stake_extrinsic( "netuid": netuid, }, ) - staking_response, err_msg = sign_and_send_with_nonce( - subtensor, - call, - wallet, - wait_for_inclusion, - wait_for_finalization, - nonce_key="coldkeypub", - signing_key="coldkey", + staking_response, err_msg = subtensor.sign_and_send_extrinsic( + call, wallet, wait_for_inclusion, wait_for_finalization, + use_nonce=True, sign_with="coldkey", nonce_key="coldkeypub" ) if staking_response is True: # If we successfully staked. # We only wait here if we expect finalization. @@ -285,14 +280,9 @@ def add_stake_multiple_extrinsic( "netuid": netuid, }, ) - staking_response, err_msg = sign_and_send_with_nonce( - subtensor, - call, - wallet, - wait_for_inclusion, - wait_for_finalization, - nonce_key="coldkeypub", - signing_key="coldkey", + staking_response, err_msg = subtensor.sign_and_send_extrinsic( + call, wallet, wait_for_inclusion, wait_for_finalization, + use_nonce=True, nonce_key="coldkeypub", sign_with="coldkey" ) if staking_response is True: # If we successfully staked. diff --git a/bittensor/core/extrinsics/unstaking.py b/bittensor/core/extrinsics/unstaking.py index c68c5057f3..86fd137a63 100644 --- a/bittensor/core/extrinsics/unstaking.py +++ b/bittensor/core/extrinsics/unstaking.py @@ -6,7 +6,6 @@ from bittensor.utils import unlock_key from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging -from bittensor.core.extrinsics.utils import sign_and_send_with_nonce if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -93,14 +92,14 @@ def unstake_extrinsic( "netuid": netuid, }, ) - staking_response, err_msg = sign_and_send_with_nonce( - subtensor, + staking_response, err_msg = subtensor.sign_and_send_extrinsic( call, wallet, wait_for_inclusion, wait_for_finalization, nonce_key="coldkeypub", - signing_key="coldkey", + sign_with="coldkey", + use_nonce=True ) if staking_response is True: # If we successfully unstaked. @@ -254,14 +253,14 @@ def unstake_multiple_extrinsic( "netuid": netuid, }, ) - staking_response, err_msg = sign_and_send_with_nonce( - subtensor, + staking_response, err_msg = subtensor.sign_and_send_extrinsic( call, wallet, wait_for_inclusion, wait_for_finalization, nonce_key="coldkeypub", - signing_key="coldkey", + sign_with="coldkey", + use_nonce=True ) if staking_response is True: # If we successfully unstaked. diff --git a/bittensor/core/extrinsics/utils.py b/bittensor/core/extrinsics/utils.py index 98fad5f6d8..adcb332c1d 100644 --- a/bittensor/core/extrinsics/utils.py +++ b/bittensor/core/extrinsics/utils.py @@ -20,51 +20,11 @@ from scalecodec.types import GenericExtrinsic, GenericCall -async def async_sign_and_send_with_nonce( - subtensor: "AsyncSubtensor", +def sign_and_send_with_nonce( + subtensor: "Subtensor", call: "GenericCall", wallet: "Wallet", wait_for_inclusion: bool, - wait_for_finalization: bool, - period: Optional[int] = None, - nonce_key: str = "hotkey", - signing_key: str = "hotkey", -): - """ - Signs an extrinsic call with the wallet keypair (default hotkey), adding an optional era for period - """ - keypair = getattr(wallet, nonce_key) - next_nonce = await subtensor.substrate.get_account_next_index(keypair.ss58_address) - signing_keypair = getattr(wallet, signing_key) - extrinsic_data = {"call": call, "keypair": signing_keypair, "nonce": next_nonce} - if period is not None: - extrinsic_data["era"] = {"period": period} - - extrinsic = await subtensor.substrate.create_signed_extrinsic(**extrinsic_data) - - try: - response = await subtensor.substrate.submit_extrinsic( - extrinsic=extrinsic, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - - if not wait_for_finalization and not wait_for_inclusion: - return True, None - - if await response.is_success: - return True, None - - return False, format_error_message(await response.error_message) - except SubstrateRequestException as e: - return False, format_error_message(e) - - -def sign_and_send_with_nonce( - subtensor: "Subtensor", - call, - wallet, - wait_for_inclusion, wait_for_finalization, nonce_key: str = "hotkey", signing_key: str = "hotkey", diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 15ce88b691..369ee86116 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -2029,6 +2029,9 @@ def sign_and_send_extrinsic( wait_for_inclusion: bool = True, wait_for_finalization: bool = False, sign_with: str = "coldkey", + use_nonce: bool = False, + period: Optional[int] = None, + nonce_key: str = "hotkey" ) -> tuple[bool, str]: """ Helper method to sign and submit an extrinsic call to chain. @@ -2043,13 +2046,26 @@ def sign_and_send_extrinsic( Returns: (success, error message) """ - if sign_with not in ("coldkey", "hotkey", "coldkeypub"): + possible_keys = ("coldkey", "hotkey", "coldkeypub") + if sign_with not in possible_keys: raise AttributeError( f"'sign_with' must be either 'coldkey', 'hotkey' or 'coldkeypub', not '{sign_with}'" ) + signing_keypair = getattr(wallet, sign_with) + extrinsic_data = {"call": call, "keypair": signing_keypair} + if use_nonce: + if nonce_key not in possible_keys: + raise AttributeError( + f"'nonce_key' must be either 'coldkey', 'hotkey' or 'coldkeypub', not '{nonce_key}'" + ) + next_nonce = self.substrate.get_account_next_index(getattr(wallet, nonce_key).ss58_address) + extrinsic_data["nonce"] = next_nonce + if period is not None: + extrinsic_data["era"] = {"period": period} + extrinsic = self.substrate.create_signed_extrinsic( - call=call, keypair=getattr(wallet, sign_with) + **extrinsic_data ) try: response = self.substrate.submit_extrinsic( diff --git a/tests/unit_tests/extrinsics/asyncex/test_weights.py b/tests/unit_tests/extrinsics/asyncex/test_weights.py index 3233519c16..7a4953031e 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_weights.py +++ b/tests/unit_tests/extrinsics/asyncex/test_weights.py @@ -84,7 +84,7 @@ async def fake_is_success(): mocked_format_error_message = mocker.Mock() mocker.patch.object( - async_weights, "format_error_message", mocked_format_error_message + async_subtensor, "format_error_message", mocked_format_error_message ) mocker.patch.object(subtensor.substrate, "compose_call", return_value=fake_call) @@ -340,7 +340,7 @@ async def fake_is_success(): mocked_format_error_message = mocker.Mock(return_value="Formatted error") mocker.patch.object( - async_weights, "format_error_message", mocked_format_error_message + async_subtensor, "format_error_message", mocked_format_error_message ) mocker.patch.object(subtensor.substrate, "compose_call", return_value=fake_call) diff --git a/tests/unit_tests/extrinsics/test_commit_weights.py b/tests/unit_tests/extrinsics/test_commit_weights.py index 42cc1f2311..aa1a37bcc2 100644 --- a/tests/unit_tests/extrinsics/test_commit_weights.py +++ b/tests/unit_tests/extrinsics/test_commit_weights.py @@ -33,7 +33,7 @@ def test_do_commit_weights(subtensor, mocker): mocked_format_error_message = mocker.Mock() mocker.patch( - "bittensor.core.extrinsics.commit_weights.format_error_message", + "bittensor.core.subtensor.format_error_message", mocked_format_error_message, ) @@ -65,7 +65,7 @@ def test_do_commit_weights(subtensor, mocker): subtensor.substrate.submit_extrinsic.assert_called_once_with( extrinsic=subtensor.substrate.create_signed_extrinsic.return_value, wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, + wait_for_finalization=1, ) mocked_format_error_message.assert_called_once_with( @@ -95,7 +95,7 @@ def test_do_reveal_weights(subtensor, mocker): mocked_format_error_message = mocker.Mock() mocker.patch( - "bittensor.core.extrinsics.commit_weights.format_error_message", + "bittensor.core.extrinsics.utils.format_error_message", mocked_format_error_message, ) diff --git a/tests/unit_tests/extrinsics/test_staking.py b/tests/unit_tests/extrinsics/test_staking.py index 6d9522148d..b200080502 100644 --- a/tests/unit_tests/extrinsics/test_staking.py +++ b/tests/unit_tests/extrinsics/test_staking.py @@ -1,4 +1,5 @@ from bittensor.core.extrinsics import staking +from bittensor.core.extrinsics import utils from bittensor.utils.balance import Balance @@ -48,6 +49,9 @@ def test_add_stake_extrinsic(mocker): fake_wallet, True, True, + nonce_key="coldkeypub", + sign_with="coldkey", + use_nonce=True ) @@ -132,4 +136,7 @@ def test_add_stake_multiple_extrinsic(mocker): fake_wallet, True, True, + nonce_key="coldkeypub", + sign_with="coldkey", + use_nonce=True ) diff --git a/tests/unit_tests/extrinsics/test_unstaking.py b/tests/unit_tests/extrinsics/test_unstaking.py index 9d0ed8ec03..0c4c822748 100644 --- a/tests/unit_tests/extrinsics/test_unstaking.py +++ b/tests/unit_tests/extrinsics/test_unstaking.py @@ -48,6 +48,9 @@ def test_unstake_extrinsic(mocker): fake_wallet, True, True, + sign_with="coldkey", + nonce_key="coldkeypub", + use_nonce=True ) @@ -112,4 +115,7 @@ def test_unstake_multiple_extrinsic(mocker): fake_wallet, True, True, + sign_with="coldkey", + nonce_key="coldkeypub", + use_nonce=True ) From 28050cd2d10c81b472a6ab30321dff274c12bea1 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 12 Feb 2025 20:31:20 +0200 Subject: [PATCH 05/11] [WIP] Tests passing --- bittensor/core/async_subtensor.py | 10 +++---- bittensor/core/extrinsics/asyncex/staking.py | 4 +-- .../core/extrinsics/asyncex/unstaking.py | 11 ++++++-- bittensor/core/extrinsics/asyncex/weights.py | 28 +++++++++++++++---- bittensor/core/extrinsics/commit_weights.py | 18 +++++++++--- bittensor/core/extrinsics/staking.py | 18 +++++++++--- bittensor/core/extrinsics/unstaking.py | 4 +-- bittensor/core/subtensor.py | 10 +++---- .../extrinsics/asyncex/test_weights.py | 8 +++--- .../extrinsics/test_commit_weights.py | 8 +++--- tests/unit_tests/extrinsics/test_staking.py | 4 +-- tests/unit_tests/extrinsics/test_unstaking.py | 4 +-- 12 files changed, 84 insertions(+), 43 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index d67ab5e872..c2fca8c1c8 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -2685,7 +2685,7 @@ async def sign_and_send_extrinsic( sign_with: str = "coldkey", use_nonce: bool = False, period: Optional[int] = None, - nonce_key: str = "hotkey" + nonce_key: str = "hotkey", ) -> tuple[bool, str]: """ Helper method to sign and submit an extrinsic call to chain. @@ -2712,14 +2712,14 @@ async def sign_and_send_extrinsic( raise AttributeError( f"'nonce_key' must be either 'coldkey', 'hotkey' or 'coldkeypub', not '{nonce_key}'" ) - next_nonce = await self.substrate.get_account_next_index(getattr(wallet, nonce_key).ss58_address) + next_nonce = await self.substrate.get_account_next_index( + getattr(wallet, nonce_key).ss58_address + ) extrinsic_data["nonce"] = next_nonce if period is not None: extrinsic_data["era"] = {"period": period} - extrinsic = await self.substrate.create_signed_extrinsic( - **extrinsic_data - ) + extrinsic = await self.substrate.create_signed_extrinsic(**extrinsic_data) try: response = await self.substrate.submit_extrinsic( extrinsic, diff --git a/bittensor/core/extrinsics/asyncex/staking.py b/bittensor/core/extrinsics/asyncex/staking.py index 25ab8662a6..944219fcbb 100644 --- a/bittensor/core/extrinsics/asyncex/staking.py +++ b/bittensor/core/extrinsics/asyncex/staking.py @@ -118,7 +118,7 @@ async def add_stake_extrinsic( wait_for_finalization, nonce_key="coldkeypub", sign_with="coldkey", - use_nonce=True + use_nonce=True, ) if staking_response is True: # If we successfully staked. # We only wait here if we expect finalization. @@ -308,7 +308,7 @@ async def add_stake_multiple_extrinsic( wait_for_finalization, nonce_key="coldkeypub", sign_with="coldkey", - use_nonce=True + use_nonce=True, ) if staking_response is True: # If we successfully staked. diff --git a/bittensor/core/extrinsics/asyncex/unstaking.py b/bittensor/core/extrinsics/asyncex/unstaking.py index e117d556ad..01f41297b6 100644 --- a/bittensor/core/extrinsics/asyncex/unstaking.py +++ b/bittensor/core/extrinsics/asyncex/unstaking.py @@ -98,8 +98,13 @@ async def unstake_extrinsic( }, ) staking_response, err_msg = await subtensor.sign_and_send_extrinsic( - call, wallet, wait_for_inclusion, wait_for_finalization, - nonce_key="coldkeypub", sign_with="coldkey", use_nonce=True + call, + wallet, + wait_for_inclusion, + wait_for_finalization, + nonce_key="coldkeypub", + sign_with="coldkey", + use_nonce=True, ) if staking_response is True: # If we successfully unstaked. @@ -270,7 +275,7 @@ async def unstake_multiple_extrinsic( wait_for_finalization, nonce_key="coldkeypub", sign_with="coldkey", - use_nonce=True + use_nonce=True, ) if staking_response is True: # If we successfully unstaked. diff --git a/bittensor/core/extrinsics/asyncex/weights.py b/bittensor/core/extrinsics/asyncex/weights.py index 3299797bf4..05fb283e9e 100644 --- a/bittensor/core/extrinsics/asyncex/weights.py +++ b/bittensor/core/extrinsics/asyncex/weights.py @@ -52,8 +52,13 @@ async def _do_commit_weights( }, ) return await subtensor.sign_and_send_extrinsic( - call, wallet, wait_for_inclusion, wait_for_finalization, use_nonce=True, - nonce_key="hotkey", sign_with="hotkey" + call, + wallet, + wait_for_inclusion, + wait_for_finalization, + use_nonce=True, + nonce_key="hotkey", + sign_with="hotkey", ) @@ -150,8 +155,13 @@ async def _do_reveal_weights( }, ) return await subtensor.sign_and_send_extrinsic( - call, wallet, wait_for_inclusion, wait_for_finalization, - sign_with="hotkey", nonce_key="hotkey", use_nonce=True, + call, + wallet, + wait_for_inclusion, + wait_for_finalization, + sign_with="hotkey", + nonce_key="hotkey", + use_nonce=True, ) @@ -257,8 +267,14 @@ async def _do_set_weights( }, ) return await subtensor.sign_and_send_extrinsic( - call, wallet, wait_for_inclusion, wait_for_finalization, period=period, - use_nonce=True, nonce_key="hotkey", sign_with="hotkey" + call, + wallet, + wait_for_inclusion, + wait_for_finalization, + period=period, + use_nonce=True, + nonce_key="hotkey", + sign_with="hotkey", ) diff --git a/bittensor/core/extrinsics/commit_weights.py b/bittensor/core/extrinsics/commit_weights.py index dd967dd2f4..06e3295eb5 100644 --- a/bittensor/core/extrinsics/commit_weights.py +++ b/bittensor/core/extrinsics/commit_weights.py @@ -45,8 +45,13 @@ def _do_commit_weights( }, ) return subtensor.sign_and_send_extrinsic( - call, wallet, wait_for_inclusion, wait_for_finalization, - use_nonce=True, sign_with="hotkey", nonce_key="hotkey" + call, + wallet, + wait_for_inclusion, + wait_for_finalization, + use_nonce=True, + sign_with="hotkey", + nonce_key="hotkey", ) @@ -141,8 +146,13 @@ def _do_reveal_weights( }, ) return subtensor.sign_and_send_extrinsic( - call, wallet, wait_for_inclusion, wait_for_finalization, - use_nonce=True, sign_with="hotkey", nonce_key="hotkey" + call, + wallet, + wait_for_inclusion, + wait_for_finalization, + use_nonce=True, + sign_with="hotkey", + nonce_key="hotkey", ) diff --git a/bittensor/core/extrinsics/staking.py b/bittensor/core/extrinsics/staking.py index 1b99f3723a..a481978155 100644 --- a/bittensor/core/extrinsics/staking.py +++ b/bittensor/core/extrinsics/staking.py @@ -107,8 +107,13 @@ def add_stake_extrinsic( }, ) staking_response, err_msg = subtensor.sign_and_send_extrinsic( - call, wallet, wait_for_inclusion, wait_for_finalization, - use_nonce=True, sign_with="coldkey", nonce_key="coldkeypub" + call, + wallet, + wait_for_inclusion, + wait_for_finalization, + use_nonce=True, + sign_with="coldkey", + nonce_key="coldkeypub", ) if staking_response is True: # If we successfully staked. # We only wait here if we expect finalization. @@ -281,8 +286,13 @@ def add_stake_multiple_extrinsic( }, ) staking_response, err_msg = subtensor.sign_and_send_extrinsic( - call, wallet, wait_for_inclusion, wait_for_finalization, - use_nonce=True, nonce_key="coldkeypub", sign_with="coldkey" + call, + wallet, + wait_for_inclusion, + wait_for_finalization, + use_nonce=True, + nonce_key="coldkeypub", + sign_with="coldkey", ) if staking_response is True: # If we successfully staked. diff --git a/bittensor/core/extrinsics/unstaking.py b/bittensor/core/extrinsics/unstaking.py index 86fd137a63..51be15cce1 100644 --- a/bittensor/core/extrinsics/unstaking.py +++ b/bittensor/core/extrinsics/unstaking.py @@ -99,7 +99,7 @@ def unstake_extrinsic( wait_for_finalization, nonce_key="coldkeypub", sign_with="coldkey", - use_nonce=True + use_nonce=True, ) if staking_response is True: # If we successfully unstaked. @@ -260,7 +260,7 @@ def unstake_multiple_extrinsic( wait_for_finalization, nonce_key="coldkeypub", sign_with="coldkey", - use_nonce=True + use_nonce=True, ) if staking_response is True: # If we successfully unstaked. diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 369ee86116..11e3ac36b3 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -2031,7 +2031,7 @@ def sign_and_send_extrinsic( sign_with: str = "coldkey", use_nonce: bool = False, period: Optional[int] = None, - nonce_key: str = "hotkey" + nonce_key: str = "hotkey", ) -> tuple[bool, str]: """ Helper method to sign and submit an extrinsic call to chain. @@ -2059,14 +2059,14 @@ def sign_and_send_extrinsic( raise AttributeError( f"'nonce_key' must be either 'coldkey', 'hotkey' or 'coldkeypub', not '{nonce_key}'" ) - next_nonce = self.substrate.get_account_next_index(getattr(wallet, nonce_key).ss58_address) + next_nonce = self.substrate.get_account_next_index( + getattr(wallet, nonce_key).ss58_address + ) extrinsic_data["nonce"] = next_nonce if period is not None: extrinsic_data["era"] = {"period": period} - extrinsic = self.substrate.create_signed_extrinsic( - **extrinsic_data - ) + extrinsic = self.substrate.create_signed_extrinsic(**extrinsic_data) try: response = self.substrate.submit_extrinsic( extrinsic, diff --git a/tests/unit_tests/extrinsics/asyncex/test_weights.py b/tests/unit_tests/extrinsics/asyncex/test_weights.py index 7a4953031e..b654431429 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_weights.py +++ b/tests/unit_tests/extrinsics/asyncex/test_weights.py @@ -56,7 +56,7 @@ async def fake_is_success(): # Asserts assert result is True - assert message is None + assert message is "" @pytest.mark.asyncio @@ -146,7 +146,7 @@ async def test_do_set_weights_no_waiting(subtensor, mocker): # Asserts assert result is True - assert message is None + assert message is "" @pytest.mark.asyncio @@ -316,7 +316,7 @@ async def fake_is_success(): # Asserts assert result is True - assert message is None + assert message is "" @pytest.mark.asyncio @@ -399,7 +399,7 @@ async def test_do_commit_weights_no_waiting(subtensor, mocker): # Asserts assert result is True - assert message is None + assert message is "" @pytest.mark.asyncio diff --git a/tests/unit_tests/extrinsics/test_commit_weights.py b/tests/unit_tests/extrinsics/test_commit_weights.py index aa1a37bcc2..18b9e987dd 100644 --- a/tests/unit_tests/extrinsics/test_commit_weights.py +++ b/tests/unit_tests/extrinsics/test_commit_weights.py @@ -63,9 +63,9 @@ def test_do_commit_weights(subtensor, mocker): assert kwargs["keypair"] == fake_wallet.hotkey subtensor.substrate.submit_extrinsic.assert_called_once_with( - extrinsic=subtensor.substrate.create_signed_extrinsic.return_value, + subtensor.substrate.create_signed_extrinsic.return_value, wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=1, + wait_for_finalization=wait_for_finalization, ) mocked_format_error_message.assert_called_once_with( @@ -95,7 +95,7 @@ def test_do_reveal_weights(subtensor, mocker): mocked_format_error_message = mocker.Mock() mocker.patch( - "bittensor.core.extrinsics.utils.format_error_message", + "bittensor.core.subtensor.format_error_message", mocked_format_error_message, ) @@ -132,7 +132,7 @@ def test_do_reveal_weights(subtensor, mocker): ) subtensor.substrate.submit_extrinsic.assert_called_once_with( - extrinsic=subtensor.substrate.create_signed_extrinsic.return_value, + subtensor.substrate.create_signed_extrinsic.return_value, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) diff --git a/tests/unit_tests/extrinsics/test_staking.py b/tests/unit_tests/extrinsics/test_staking.py index b200080502..9cdd241c89 100644 --- a/tests/unit_tests/extrinsics/test_staking.py +++ b/tests/unit_tests/extrinsics/test_staking.py @@ -51,7 +51,7 @@ def test_add_stake_extrinsic(mocker): True, nonce_key="coldkeypub", sign_with="coldkey", - use_nonce=True + use_nonce=True, ) @@ -138,5 +138,5 @@ def test_add_stake_multiple_extrinsic(mocker): True, nonce_key="coldkeypub", sign_with="coldkey", - use_nonce=True + use_nonce=True, ) diff --git a/tests/unit_tests/extrinsics/test_unstaking.py b/tests/unit_tests/extrinsics/test_unstaking.py index 0c4c822748..b93f335162 100644 --- a/tests/unit_tests/extrinsics/test_unstaking.py +++ b/tests/unit_tests/extrinsics/test_unstaking.py @@ -50,7 +50,7 @@ def test_unstake_extrinsic(mocker): True, sign_with="coldkey", nonce_key="coldkeypub", - use_nonce=True + use_nonce=True, ) @@ -117,5 +117,5 @@ def test_unstake_multiple_extrinsic(mocker): True, sign_with="coldkey", nonce_key="coldkeypub", - use_nonce=True + use_nonce=True, ) From 0046e25a511106bb69910f7bd93e40b1dc8075a8 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 12 Feb 2025 20:47:29 +0200 Subject: [PATCH 06/11] Lint --- bittensor/core/extrinsics/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/core/extrinsics/utils.py b/bittensor/core/extrinsics/utils.py index adcb332c1d..eaf6c52a6f 100644 --- a/bittensor/core/extrinsics/utils.py +++ b/bittensor/core/extrinsics/utils.py @@ -1,6 +1,6 @@ """Module with helper functions for extrinsics.""" -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from async_substrate_interface.errors import SubstrateRequestException From ccd8a00b6304c392aef237da91190ccb7133def4 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Thu, 13 Feb 2025 16:39:36 +0200 Subject: [PATCH 07/11] Removed `sign_and_send_with_nonce` as this functionality has been moved to Subtensor. --- bittensor/core/extrinsics/utils.py | 32 ------------------------------ 1 file changed, 32 deletions(-) diff --git a/bittensor/core/extrinsics/utils.py b/bittensor/core/extrinsics/utils.py index eaf6c52a6f..7204d2b772 100644 --- a/bittensor/core/extrinsics/utils.py +++ b/bittensor/core/extrinsics/utils.py @@ -20,38 +20,6 @@ from scalecodec.types import GenericExtrinsic, GenericCall -def sign_and_send_with_nonce( - subtensor: "Subtensor", - call: "GenericCall", - wallet: "Wallet", - wait_for_inclusion: bool, - wait_for_finalization, - nonce_key: str = "hotkey", - signing_key: str = "hotkey", -): - keypair = getattr(wallet, nonce_key) - next_nonce = subtensor.substrate.get_account_next_index(keypair.ss58_address) - signing_keypair = getattr(wallet, signing_key) - extrinsic = subtensor.substrate.create_signed_extrinsic( - call=call, - keypair=signing_keypair, - nonce=next_nonce, - ) - response = subtensor.substrate.submit_extrinsic( - extrinsic=extrinsic, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - - if not wait_for_finalization and not wait_for_inclusion: - return True, None - - if response.is_success: - return True, None - - return False, format_error_message(response.error_message) - - def submit_extrinsic( subtensor: "Subtensor", extrinsic: "GenericExtrinsic", From fdb5342a2a9a9b03ac3b3cc22eb7ece6d995c6a1 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Thu, 13 Feb 2025 17:02:08 +0200 Subject: [PATCH 08/11] Added `get_stake_for_hotkey` to AsyncSubtensor and Subtensor --- bittensor/core/async_subtensor.py | 34 +++++++++++++++++++++++++++++++ bittensor/core/subtensor.py | 24 +++++++++++++++++++++- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index c2fca8c1c8..ea3e6d9a50 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -1665,6 +1665,40 @@ async def get_stake_for_coldkey( get_stake_info_for_coldkey = get_stake_for_coldkey + async def get_stake_for_hotkey( + self, + hotkey_ss58: str, + netuid: int, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> Balance: + """ + Retrieves the stake information for a given hotkey. + + Args: + hotkey_ss58: The SS58 address of the hotkey. + netuid: The subnet ID to query for. + block: The block number at which to query the stake information. Do not specify if also specifying + block_hash or reuse_block + block_hash: The hash of the blockchain block number for the query. Do not specify if also specifying block + or reuse_block + reuse_block: Whether to reuse for this query the last-used block. Do not specify if also specifying block + or block_hash. + """ + hotkey_alpha_query = await self.query_subtensor( + name="TotalHotkeyAlpha", + params=[hotkey_ss58, netuid], + block=block, + block_hash=block_hash, + reuse_block=reuse_block, + ) + balance = Balance.from_rao(hotkey_alpha_query.value) + balance.set_unit(netuid=netuid) + return balance + + get_hotkey_stake = get_stake_for_hotkey + async def get_subnet_burn_cost( self, block: Optional[int] = None, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 11e3ac36b3..75c28d43d1 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -6,6 +6,7 @@ import requests import scalecodec from async_substrate_interface.errors import SubstrateRequestException +from async_substrate_interface.types import ScaleObj from async_substrate_interface.sync_substrate import SubstrateInterface from async_substrate_interface.utils import json from numpy.typing import NDArray @@ -91,7 +92,6 @@ if TYPE_CHECKING: from bittensor_wallet import Wallet from async_substrate_interface.sync_substrate import QueryMapResult - from async_substrate_interface.types import ScaleObj from scalecodec.types import GenericCall @@ -1281,6 +1281,28 @@ def get_stake_for_coldkey( get_stake_info_for_coldkey = get_stake_for_coldkey + def get_stake_for_hotkey( + self, hotkey_ss58: str, netuid: int, block: Optional[int] = None + ) -> Balance: + """ + Retrieves the stake information for a given hotkey. + + Args: + hotkey_ss58: The SS58 address of the hotkey. + netuid: The subnet ID to query for. + block: The block number at which to query the stake information. Do not specify if also specifying + block_hash or reuse_block + """ + hotkey_alpha_query = self.query_subtensor( + name="TotalHotkeyAlpha", params=[hotkey_ss58, netuid], block=block + ) + hotkey_alpha = cast(ScaleObj, hotkey_alpha_query) + balance = Balance.from_rao(hotkey_alpha.value) + balance.set_unit(netuid=netuid) + return balance + + get_hotkey_stake = get_stake_for_hotkey + def get_subnet_burn_cost(self, block: Optional[int] = None) -> Optional[Balance]: """ Retrieves the burn cost for registering a new subnet within the Bittensor network. This cost represents the From 606f5aa29c5a60246ecb96a0b5ecef30983b44ea Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Thu, 13 Feb 2025 17:02:59 +0200 Subject: [PATCH 09/11] Remove unused import --- bittensor/core/extrinsics/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/core/extrinsics/utils.py b/bittensor/core/extrinsics/utils.py index 7204d2b772..7cc2e3c4e0 100644 --- a/bittensor/core/extrinsics/utils.py +++ b/bittensor/core/extrinsics/utils.py @@ -17,7 +17,7 @@ ) from bittensor.core.subtensor import Subtensor from bittensor.core.chain_data import StakeInfo - from scalecodec.types import GenericExtrinsic, GenericCall + from scalecodec.types import GenericExtrinsic def submit_extrinsic( From dd35bfa5fa9cac8df7b9bbbf3f5082278c7f01f5 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Thu, 13 Feb 2025 17:33:03 +0200 Subject: [PATCH 10/11] Updated `Subtensor` and `AsyncSubtensor`'s `get_children` method to return normalised float values. --- bittensor/core/async_subtensor.py | 7 ++++--- bittensor/core/subtensor.py | 7 ++++--- tests/unit_tests/test_async_subtensor.py | 5 +++-- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index ea3e6d9a50..8e357aa141 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -75,6 +75,7 @@ u16_normalized_float, _decode_hex_identity_dict, Certificate, + u64_normalized_float, ) from bittensor.utils.balance import ( Balance, @@ -891,7 +892,7 @@ async def get_children( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> tuple[bool, list, str]: + ) -> tuple[bool, list[tuple[float, str]], str]: """ This method retrieves the children of a given hotkey and netuid. It queries the SubtensorModule's ChildKeys storage function to get the children and formats them before returning as a tuple. @@ -921,8 +922,8 @@ async def get_children( for proportion, child in children.value: # Convert U64 to int formatted_child = decode_account_id(child[0]) - int_proportion = int(proportion) - formatted_children.append((int_proportion, formatted_child)) + normalized_proportion = u64_normalized_float(proportion) + formatted_children.append((normalized_proportion, formatted_child)) return True, formatted_children, "" else: return True, [], "" diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 75c28d43d1..03c32a6072 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -78,6 +78,7 @@ u16_normalized_float, _decode_hex_identity_dict, Certificate, + u64_normalized_float, ) from bittensor.utils.balance import ( Balance, @@ -678,7 +679,7 @@ def get_hyperparameter( def get_children( self, hotkey: str, netuid: int, block: Optional[int] = None - ) -> tuple[bool, list, str]: + ) -> tuple[bool, list[tuple[float, str]], str]: """ This method retrieves the children of a given hotkey and netuid. It queries the SubtensorModule's ChildKeys storage function to get the children and formats them before returning as a tuple. @@ -704,8 +705,8 @@ def get_children( for proportion, child in children.value: # Convert U64 to int formatted_child = decode_account_id(child[0]) - int_proportion = int(proportion) - formatted_children.append((int_proportion, formatted_child)) + normalized_proportion = u64_normalized_float(proportion) + formatted_children.append((normalized_proportion, formatted_child)) return True, formatted_children, "" else: return True, [], "" diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 10d1ba5e66..53cf43ed4b 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -3,6 +3,7 @@ import pytest from bittensor_wallet import Wallet +from bittensor import u64_normalized_float from bittensor.core import async_subtensor from bittensor.core.async_subtensor import AsyncSubtensor from bittensor.core.chain_data.stake_info import StakeInfo @@ -1765,8 +1766,8 @@ async def test_get_children_success(subtensor, mocker): mocker.patch.object(async_subtensor, "decode_account_id", mocked_decode_account_id) expected_formatted_children = [ - (1000, "decoded_child_key_1"), - (2000, "decoded_child_key_2"), + (u64_normalized_float(1000), "decoded_child_key_1"), + (u64_normalized_float(2000), "decoded_child_key_2"), ] # Call From c30c1bf2ea73799f2bca8e194d0f2ebb41b88d3f Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Thu, 13 Feb 2025 17:47:11 +0200 Subject: [PATCH 11/11] Added `moving_price` to `DynamicInfo` --- bittensor/core/chain_data/dynamic_info.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bittensor/core/chain_data/dynamic_info.py b/bittensor/core/chain_data/dynamic_info.py index cf55ace329..9f2739560b 100644 --- a/bittensor/core/chain_data/dynamic_info.py +++ b/bittensor/core/chain_data/dynamic_info.py @@ -10,7 +10,7 @@ from bittensor.core.chain_data.utils import decode_account_id from bittensor.core.chain_data.subnet_identity import SubnetIdentity -from bittensor.utils.balance import Balance +from bittensor.utils.balance import Balance, fixed_to_float @dataclass @@ -38,6 +38,7 @@ class DynamicInfo(InfoBase): network_registered_at: int subnet_volume: Balance subnet_identity: Optional[SubnetIdentity] + moving_price: float @classmethod def _from_dict(cls, decoded: dict) -> "DynamicInfo": @@ -120,6 +121,7 @@ def _from_dict(cls, decoded: dict) -> "DynamicInfo": network_registered_at=int(decoded["network_registered_at"]), subnet_identity=subnet_identity, subnet_volume=subnet_volume, + moving_price=fixed_to_float(decoded["moving_price"]), ) def tao_to_alpha(self, tao: Union[Balance, float, int]) -> Balance: