diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml
index 0bc467a94d..157f2ad955 100644
--- a/.github/workflows/e2e-subtensor-tests.yaml
+++ b/.github/workflows/e2e-subtensor-tests.yaml
@@ -90,7 +90,7 @@ jobs:
- name: Setup subtensor repo
working-directory: ${{ github.workspace }}/subtensor
- run: git checkout testnet
+ run: git checkout main
- name: Run tests
run: |
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0976792d33..b961f0bbc1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,24 @@
# Changelog
+## 8.5.2 /2025-01-17
+
+## What's Changed
+* Feat/use tx pool for set weights by @camfairchild in https://github.com/opentensor/bittensor/pull/2534
+* fix get_delegates result decoding by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2551
+* [SDK] Handle server connection limit by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2553
+* Backmerge master to staging post 851 by @ibraheem-opentensor in https://github.com/opentensor/bittensor/pull/2557
+* [SDK] Improve InvalidStatus handler by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2558
+* [SDK] Add async version of commit reveal v3 by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2560
+* Use apt-get instead of apt for scripts by @camfairchild in https://github.com/opentensor/bittensor/pull/2571
+* fix _do_stake incorrect arguments error in staking.py by @Assh-codes in https://github.com/opentensor/bittensor/pull/2574
+* Updates tests for btwallet 3.0.0 by @ibraheem-opentensor in https://github.com/opentensor/bittensor/pull/2540
+* Bumps cr3 FFI by @ibraheem-opentensor in https://github.com/opentensor/bittensor/pull/2583
+
+## New Contributors
+* @Assh-codes made their first contribution in https://github.com/opentensor/bittensor/pull/2574
+
+**Full Changelog**: https://github.com/opentensor/bittensor/compare/v8.5.1...v8.5.2
+
## 8.5.1 /2024-12-16
## What's Changed
diff --git a/Dockerfile b/Dockerfile
index 5a355b5cf6..03c0533746 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -12,10 +12,10 @@ LABEL bittensor.image.authors="bittensor.com" \
ARG DEBIAN_FRONTEND=noninteractive
# Update the base image
-RUN apt update && apt upgrade -y
+RUN apt-get update && apt-get upgrade -y
# Install bittensor
## Install dependencies
-RUN apt install -y curl sudo nano git htop netcat-openbsd wget unzip tmux apt-utils cmake build-essential
+RUN apt-get install -y curl sudo nano git htop netcat-openbsd wget unzip tmux apt-utils cmake build-essential
## Upgrade pip
RUN pip3 install --upgrade pip
diff --git a/VERSION b/VERSION
index e0741a834a..bd0b85a9b1 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-8.5.1
\ No newline at end of file
+8.5.2
\ No newline at end of file
diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py
index 2141055cca..43a0ff9252 100644
--- a/bittensor/core/async_subtensor.py
+++ b/bittensor/core/async_subtensor.py
@@ -22,6 +22,7 @@
SubnetHyperparameters,
decode_account_id,
)
+from bittensor.core.extrinsics.async_commit_reveal import commit_reveal_v3_extrinsic
from bittensor.core.extrinsics.async_registration import register_extrinsic
from bittensor.core.extrinsics.async_root import (
set_root_weights_extrinsic,
@@ -1596,18 +1597,44 @@ async def set_weights(
This function is crucial in shaping the network's collective intelligence, where each neuron's learning and contribution are influenced by the weights it sets towards others【81†source】.
"""
+ retries = 0
+ success = False
+ if (
+ uid := await self.get_uid_for_hotkey_on_subnet(
+ wallet.hotkey.ss58_address, netuid
+ )
+ ) is None:
+ return (
+ False,
+ f"Hotkey {wallet.hotkey.ss58_address} not registered in subnet {netuid}",
+ )
+
if (await self.commit_reveal_enabled(netuid=netuid)) is True:
# go with `commit reveal v3` extrinsic
- raise NotImplementedError(
- "Not implemented yet for AsyncSubtensor. Coming soon."
- )
+ message = "No attempt made. Perhaps it is too soon to commit weights!"
+ while (
+ await self.blocks_since_last_update(netuid, uid)
+ > await self.weights_rate_limit(netuid)
+ and retries < max_retries
+ and success is False
+ ):
+ logging.info(
+ f"Committing weights for subnet #{netuid}. Attempt {retries + 1} of {max_retries}."
+ )
+ success, message = await commit_reveal_v3_extrinsic(
+ subtensor=self,
+ wallet=wallet,
+ netuid=netuid,
+ uids=uids,
+ weights=weights,
+ version_key=version_key,
+ wait_for_inclusion=wait_for_inclusion,
+ wait_for_finalization=wait_for_finalization,
+ )
+ retries += 1
+ return success, message
else:
# go with classic `set weights extrinsic`
- uid = await self.get_uid_for_hotkey_on_subnet(
- wallet.hotkey.ss58_address, netuid
- )
- retries = 0
- success = False
message = "No attempt made. Perhaps it is too soon to set weights!"
while (
retries < max_retries
diff --git a/bittensor/core/extrinsics/async_commit_reveal.py b/bittensor/core/extrinsics/async_commit_reveal.py
new file mode 100644
index 0000000000..b1c2bea094
--- /dev/null
+++ b/bittensor/core/extrinsics/async_commit_reveal.py
@@ -0,0 +1,152 @@
+from typing import Optional, Union, TYPE_CHECKING
+
+import numpy as np
+from bittensor_commit_reveal import get_encrypted_commit
+from numpy.typing import NDArray
+
+from bittensor.core.settings import version_as_int
+from bittensor.utils import format_error_message
+from bittensor.utils.btlogging import logging
+from bittensor.utils.weight_utils import convert_weights_and_uids_for_emit
+
+if TYPE_CHECKING:
+ from bittensor_wallet import Wallet
+ from bittensor.core.async_subtensor import AsyncSubtensor
+ from bittensor.utils.registration import torch
+
+
+async def _do_commit_reveal_v3(
+ subtensor: "AsyncSubtensor",
+ wallet: "Wallet",
+ netuid: int,
+ commit: bytes,
+ reveal_round: int,
+ wait_for_inclusion: bool = False,
+ wait_for_finalization: bool = False,
+) -> tuple[bool, Optional[str]]:
+ """
+ Executes the commit-reveal phase 3 for a given netuid and commit, and optionally waits for extrinsic inclusion or finalization.
+
+ Arguments:
+ subtensor: An instance of the Subtensor class.
+ wallet: Wallet An instance of the Wallet class containing the user's keypair.
+ netuid: int The network unique identifier.
+ commit bytes The commit data in bytes format.
+ reveal_round: int The round number for the reveal phase.
+ wait_for_inclusion: bool, optional Flag indicating whether to wait for the extrinsic to be included in a block.
+ wait_for_finalization: bool, optional Flag indicating whether to wait for the extrinsic to be finalized.
+
+ Returns:
+ A tuple where the first element is a boolean indicating success or failure, and the second element is an optional string containing error message if any.
+ """
+ logging.info(
+ f"Committing weights hash [blue]{commit.hex()}[/blue] for subnet #[blue]{netuid}[/blue] with "
+ f"reveal round [blue]{reveal_round}[/blue]..."
+ )
+
+ call = await subtensor.substrate.compose_call(
+ call_module="SubtensorModule",
+ call_function="commit_crv3_weights",
+ call_params={
+ "netuid": netuid,
+ "commit": commit,
+ "reveal_round": reveal_round,
+ },
+ )
+ extrinsic = await subtensor.substrate.create_signed_extrinsic(
+ call=call,
+ keypair=wallet.hotkey,
+ )
+
+ response = await subtensor.substrate.submit_extrinsic(
+ subtensor=subtensor,
+ 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, "Not waiting for finalization or inclusion."
+
+ if await response.is_success:
+ return True, None
+
+ return False, format_error_message(await response.error_message)
+
+
+async def commit_reveal_v3_extrinsic(
+ subtensor: "AsyncSubtensor",
+ wallet: "Wallet",
+ netuid: int,
+ uids: Union[NDArray[np.int64], "torch.LongTensor", list],
+ weights: Union[NDArray[np.float32], "torch.FloatTensor", list],
+ version_key: int = version_as_int,
+ wait_for_inclusion: bool = False,
+ wait_for_finalization: bool = False,
+) -> tuple[bool, str]:
+ """
+ Commits and reveals weights for given subtensor and wallet with provided uids and weights.
+
+ Arguments:
+ subtensor: The Subtensor instance.
+ wallet: The wallet to use for committing and revealing.
+ netuid: The id of the network.
+ uids: The uids to commit.
+ weights: The weights associated with the uids.
+ version_key: The version key to use for committing and revealing. Default is version_as_int.
+ wait_for_inclusion: Whether to wait for the inclusion of the transaction. Default is False.
+ wait_for_finalization: Whether to wait for the finalization of the transaction. Default is False.
+
+ Returns:
+ tuple[bool, str]: A tuple where the first element is a boolean indicating success or failure, and the second element is a message associated with the result.
+ """
+ try:
+ # Convert uids and weights
+ if isinstance(uids, list):
+ uids = np.array(uids, dtype=np.int64)
+ if isinstance(weights, list):
+ weights = np.array(weights, dtype=np.float32)
+
+ # Reformat and normalize.
+ uids, weights = convert_weights_and_uids_for_emit(uids, weights)
+
+ current_block = await subtensor.substrate.get_block_number(None)
+ subnet_hyperparameters = await subtensor.get_subnet_hyperparameters(netuid)
+ tempo = subnet_hyperparameters.tempo
+ subnet_reveal_period_epochs = (
+ subnet_hyperparameters.commit_reveal_weights_interval
+ )
+
+ # Encrypt `commit_hash` with t-lock and `get reveal_round`
+ commit_for_reveal, reveal_round = get_encrypted_commit(
+ uids=uids,
+ weights=weights,
+ version_key=version_key,
+ tempo=tempo,
+ current_block=current_block,
+ netuid=netuid,
+ subnet_reveal_period_epochs=subnet_reveal_period_epochs,
+ )
+
+ success, message = await _do_commit_reveal_v3(
+ subtensor=subtensor,
+ wallet=wallet,
+ netuid=netuid,
+ commit=commit_for_reveal,
+ reveal_round=reveal_round,
+ wait_for_inclusion=wait_for_inclusion,
+ wait_for_finalization=wait_for_finalization,
+ )
+
+ if success is not True:
+ logging.error(message)
+ return False, message
+
+ logging.success(
+ f"[green]Finalized![/green] Weights commited with reveal round [blue]{reveal_round}[/blue]."
+ )
+ return True, f"reveal_round:{reveal_round}"
+
+ except Exception as e:
+ logging.error(f":cross_mark: [red]Failed. Error:[/red] {e}")
+ return False, str(e)
diff --git a/bittensor/core/extrinsics/async_weights.py b/bittensor/core/extrinsics/async_weights.py
index 572266c3f6..82934edfc5 100644
--- a/bittensor/core/extrinsics/async_weights.py
+++ b/bittensor/core/extrinsics/async_weights.py
@@ -58,11 +58,17 @@ async def _do_set_weights(
"version_key": version_key,
},
)
+
+ next_nonce = await subtensor.substrate.get_account_next_index(
+ wallet.hotkey.ss58_address
+ )
+
# Period dictates how long the extrinsic will stay as part of waiting pool
extrinsic = await subtensor.substrate.create_signed_extrinsic(
call=call,
keypair=wallet.hotkey,
era={"period": 5},
+ nonce=next_nonce,
)
response = await subtensor.substrate.submit_extrinsic(
extrinsic,
@@ -180,9 +186,15 @@ async def _do_commit_weights(
"commit_hash": commit_hash,
},
)
+
+ next_nonce = await subtensor.substrate.get_account_next_index(
+ wallet.hotkey.ss58_address
+ )
+
extrinsic = await subtensor.substrate.create_signed_extrinsic(
call=call,
keypair=wallet.hotkey,
+ nonce=next_nonce,
)
response = await subtensor.substrate.submit_extrinsic(
substrate=subtensor.substrate,
diff --git a/bittensor/core/extrinsics/commit_weights.py b/bittensor/core/extrinsics/commit_weights.py
index 0ad6ad5add..4136e0c348 100644
--- a/bittensor/core/extrinsics/commit_weights.py
+++ b/bittensor/core/extrinsics/commit_weights.py
@@ -66,9 +66,11 @@ def do_commit_weights(
"commit_hash": commit_hash,
},
)
+ next_nonce = self.get_account_next_index(wallet.hotkey.ss58_address)
extrinsic = self.substrate.create_signed_extrinsic(
call=call,
keypair=wallet.hotkey,
+ nonce=next_nonce,
)
response = submit_extrinsic(
subtensor=self,
diff --git a/bittensor/core/extrinsics/set_weights.py b/bittensor/core/extrinsics/set_weights.py
index 64f318f8d6..9cb291a299 100644
--- a/bittensor/core/extrinsics/set_weights.py
+++ b/bittensor/core/extrinsics/set_weights.py
@@ -76,11 +76,13 @@ def do_set_weights(
"version_key": version_key,
},
)
+ next_nonce = self.get_account_next_index(wallet.hotkey.ss58_address)
# Period dictates how long the extrinsic will stay as part of waiting pool
extrinsic = self.substrate.create_signed_extrinsic(
call=call,
keypair=wallet.hotkey,
era={"period": period},
+ nonce=next_nonce,
)
response = submit_extrinsic(
self,
diff --git a/bittensor/core/extrinsics/staking.py b/bittensor/core/extrinsics/staking.py
index 81bbc39745..771d73dc76 100644
--- a/bittensor/core/extrinsics/staking.py
+++ b/bittensor/core/extrinsics/staking.py
@@ -122,6 +122,7 @@ def __do_add_stake_single(
raise NotDelegateError("Hotkey: {} is not a delegate.".format(hotkey_ss58))
success = _do_stake(
+ self=subtensor,
wallet=wallet,
hotkey_ss58=hotkey_ss58,
amount=amount,
diff --git a/bittensor/core/settings.py b/bittensor/core/settings.py
index 04d94436ef..c5e9182678 100644
--- a/bittensor/core/settings.py
+++ b/bittensor/core/settings.py
@@ -15,7 +15,7 @@
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
-__version__ = "8.5.1"
+__version__ = "8.5.2"
import os
import re
diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py
index ff17c8e896..1a29694b99 100644
--- a/bittensor/core/subtensor.py
+++ b/bittensor/core/subtensor.py
@@ -16,7 +16,9 @@
from scalecodec.exceptions import RemainingScaleBytesNotEmptyException
from scalecodec.type_registry import load_type_registry_preset
from scalecodec.types import ScaleType
+from substrateinterface import Keypair
from substrateinterface.base import QueryMapResult, SubstrateInterface
+from websockets.exceptions import InvalidStatus
from websockets.sync import client as ws_client
from bittensor.core import settings
@@ -233,6 +235,7 @@ def _get_substrate(self, force: bool = False):
open_timeout=self._connection_timeout,
max_size=2**32,
)
+
self.substrate = SubstrateInterface(
ss58_format=settings.SS58_FORMAT,
use_remote_preset=True,
@@ -244,19 +247,27 @@ def _get_substrate(self, force: bool = False):
f"Connected to {self.network} network and {self.chain_endpoint}."
)
- except (ConnectionRefusedError, ssl.SSLError) as error:
- logging.error(
- f"Could not connect to {self.network} network with {self.chain_endpoint} chain endpoint.",
+ except ConnectionRefusedError as error:
+ logging.critical(
+ f"[red]Could not connect to[/red] [blue]{self.network}[/blue] [red]network with[/red] [blue]{self.chain_endpoint}[/blue] [red]chain endpoint.[/red]",
)
raise ConnectionRefusedError(error.args)
- except ssl.SSLError as e:
+
+ except ssl.SSLError as error:
logging.critical(
"SSL error occurred. To resolve this issue, run the following command in your terminal:"
)
logging.critical("[blue]sudo python -m bittensor certifi[/blue]")
raise RuntimeError(
"SSL configuration issue, please follow the instructions above."
- ) from e
+ ) from error
+
+ except InvalidStatus as error:
+ logging.critical(
+ f"Error [red]'{error.response.reason_phrase}'[/red] with status code [red]{error.response.status_code}[/red]."
+ )
+ logging.debug(f"Server response is '{error.response}'.")
+ raise
@staticmethod
def config() -> "Config":
@@ -661,6 +672,16 @@ def query_module(
),
)
+ @networking.ensure_connected
+ def get_account_next_index(self, address: str) -> int:
+ """
+ Returns the next nonce for an account, taking into account the transaction pool.
+ """
+ if not self.substrate.supports_rpc_method("account_nextIndex"):
+ raise Exception("account_nextIndex not supported")
+
+ return self.substrate.rpc_request("account_nextIndex", [address])["result"]
+
# Common subtensor methods =========================================================================================
def metagraph(
self, netuid: int, lite: bool = True, block: Optional[int] = None
@@ -1512,9 +1533,11 @@ def get_transfer_fee(
call_params={"dest": dest, "value": value.rao},
)
+ temp_keypair = Keypair(ss58_address=wallet.coldkeypub.ss58_address)
+
try:
payment_info = self.substrate.get_payment_info(
- call=call, keypair=wallet.coldkeypub
+ call=call, keypair=temp_keypair
)
except Exception as e:
logging.error(f"[red]Failed to get payment info.[/red] {e}")
@@ -1764,7 +1787,7 @@ def get_delegates(self, block: Optional[int] = None) -> list[DelegateInfo]:
if not (result := json_body.get("result", None)):
return []
- return DelegateInfo.list_from_vec_u8(result)
+ return DelegateInfo.list_from_vec_u8(bytes(result))
def is_hotkey_delegate(self, hotkey_ss58: str, block: Optional[int] = None) -> bool:
"""
@@ -1816,7 +1839,13 @@ def set_weights(
"""
retries = 0
success = False
- uid = self.get_uid_for_hotkey_on_subnet(wallet.hotkey.ss58_address, netuid)
+ if (
+ uid := self.get_uid_for_hotkey_on_subnet(wallet.hotkey.ss58_address, netuid)
+ ) is None:
+ return (
+ False,
+ f"Hotkey {wallet.hotkey.ss58_address} not registered in subnet {netuid}",
+ )
if self.commit_reveal_enabled(netuid=netuid) is True:
# go with `commit reveal v3` extrinsic
diff --git a/bittensor/utils/async_substrate_interface.py b/bittensor/utils/async_substrate_interface.py
index 05fd963212..eeb5eb1068 100644
--- a/bittensor/utils/async_substrate_interface.py
+++ b/bittensor/utils/async_substrate_interface.py
@@ -13,6 +13,7 @@
from hashlib import blake2b
from typing import Optional, Any, Union, Callable, Awaitable, cast, TYPE_CHECKING
+import asyncstdlib as a
from async_property import async_property
from bittensor_wallet import Keypair
from bt_decode import PortableRegistry, decode as decode_by_type_string, MetadataV15
@@ -1704,6 +1705,24 @@ def make_payload(id_: str, method: str, params: list) -> dict:
"payload": {"jsonrpc": "2.0", "method": method, "params": params},
}
+ @a.lru_cache(maxsize=512) # RPC methods are unlikely to change often
+ async def supports_rpc_method(self, name: str) -> bool:
+ """
+ Check if substrate RPC supports given method
+ Parameters
+ ----------
+ name: name of method to check
+
+ Returns
+ -------
+ bool
+ """
+ result = await self.rpc_request("rpc_methods", []).get("result")
+ if result:
+ self.config["rpc_methods"] = result.get("methods", [])
+
+ return name in self.config["rpc_methods"]
+
async def rpc_request(
self,
method: str,
@@ -2296,10 +2315,33 @@ async def get_account_nonce(self, account_address: str) -> int:
Returns:
Nonce for given account address
"""
- nonce_obj = await self.runtime_call(
- "AccountNonceApi", "account_nonce", [account_address]
- )
- return nonce_obj.value
+ if await self.supports_rpc_method("state_call"):
+ nonce_obj = await self.runtime_call(
+ "AccountNonceApi", "account_nonce", [account_address]
+ )
+ return nonce_obj
+ else:
+ response = await self.query(
+ module="System", storage_function="Account", params=[account_address]
+ )
+ return response["nonce"]
+
+ async def get_account_next_index(self, account_address: str) -> int:
+ """
+ Returns next index for the given account address, taking into account the transaction pool.
+
+ Args:
+ account_address: SS58 formatted address
+
+ Returns:
+ Next index for the given account address
+ """
+ if not await self.supports_rpc_method("account_nextIndex"):
+ # Unlikely to happen, this is a common RPC method
+ raise Exception("account_nextIndex not supported")
+
+ nonce_obj = await self.rpc_request("account_nextIndex", [account_address])
+ return nonce_obj["result"]
async def get_metadata_constant(self, module_name, constant_name, block_hash=None):
"""
diff --git a/requirements/prod.txt b/requirements/prod.txt
index c57ce611f9..c447d8093f 100644
--- a/requirements/prod.txt
+++ b/requirements/prod.txt
@@ -1,6 +1,7 @@
wheel
setuptools~=70.0.0
aiohttp~=3.9
+asyncstdlib~=3.13.0
async-property==0.2.2
bittensor-cli
bt-decode==0.4.0
@@ -24,5 +25,5 @@ scalecodec==1.2.11
substrate-interface~=1.7.9
uvicorn
websockets>=14.1
-bittensor-wallet>=2.1.3
-bittensor-commit-reveal>=0.1.0
+bittensor-commit-reveal>=0.2.0
+bittensor-wallet>=3.0.0
diff --git a/scripts/install.sh b/scripts/install.sh
index 5111d75afb..9021fb499c 100755
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -175,9 +175,9 @@ mac_install_bittensor() {
OS="$(uname)"
if [[ "$OS" == "Linux" ]]; then
- which -s apt
+ which -s apt-get
if [[ $? == 0 ]] ; then
- abort "This linux based install requires apt. To run with other distros (centos, arch, etc), you will need to manually install the requirements"
+ abort "This linux based install requires apt-get. To run with other distros (centos, arch, etc), you will need to manually install the requirements"
fi
echo """
diff --git a/tests/e2e_tests/test_commit_weights.py b/tests/e2e_tests/test_commit_weights.py
index c6737e01ae..8a5371283e 100644
--- a/tests/e2e_tests/test_commit_weights.py
+++ b/tests/e2e_tests/test_commit_weights.py
@@ -2,6 +2,9 @@
import numpy as np
import pytest
+
+import asyncio
+
from bittensor.core.subtensor import Subtensor
from bittensor.utils.balance import Balance
from bittensor.utils.weight_utils import convert_weights_and_uids_for_emit
@@ -171,3 +174,158 @@ async def test_commit_and_reveal_weights_legacy(local_chain):
weight_vals[0] == revealed_weights.value[0][1]
), f"Incorrect revealed weights. Expected: {weights[0]}, Actual: {revealed_weights.value[0][1]}"
print("✅ Passed test_commit_and_reveal_weights")
+
+
+@pytest.mark.asyncio
+async def test_commit_weights_uses_next_nonce(local_chain):
+ """
+ Tests that commiting weights doesn't re-use a nonce in the transaction pool.
+
+ Steps:
+ 1. Register a subnet through Alice
+ 2. Register Alice's neuron and add stake
+ 3. Enable commit-reveal mechanism on the subnet
+ 4. Lower the commit_reveal interval and rate limit
+ 5. Commit weights three times
+ 6. Assert that all commits succeeded
+ Raises:
+ AssertionError: If any of the checks or verifications fail
+ """
+ netuid = 1
+ utils.EXTRINSIC_SUBMISSION_TIMEOUT = 12 # handle fast blocks
+ print("Testing test_commit_and_reveal_weights")
+ # Register root as Alice
+ keypair, alice_wallet = setup_wallet("//Alice")
+ assert register_subnet(local_chain, alice_wallet), "Unable to register the subnet"
+
+ # Verify subnet 1 created successfully
+ assert local_chain.query(
+ "SubtensorModule", "NetworksAdded", [1]
+ ).serialize(), "Subnet wasn't created successfully"
+
+ subtensor = Subtensor(network="ws://localhost:9945")
+
+ # Register Alice to the subnet
+ assert subtensor.burned_register(
+ alice_wallet, netuid
+ ), "Unable to register Alice as a neuron"
+
+ # Stake to become to top neuron after the first epoch
+ add_stake(local_chain, alice_wallet, Balance.from_tao(100_000))
+
+ # Enable commit_reveal on the subnet
+ assert sudo_set_hyperparameter_bool(
+ local_chain,
+ alice_wallet,
+ "sudo_set_commit_reveal_weights_enabled",
+ True,
+ netuid,
+ ), "Unable to enable commit reveal on the subnet"
+
+ assert subtensor.get_subnet_hyperparameters(
+ netuid=netuid,
+ ).commit_reveal_weights_enabled, "Failed to enable commit/reveal"
+
+ # Lower the commit_reveal interval
+ assert sudo_set_hyperparameter_values(
+ local_chain,
+ alice_wallet,
+ call_function="sudo_set_commit_reveal_weights_interval",
+ call_params={"netuid": netuid, "interval": "1"},
+ return_error_message=True,
+ )
+
+ assert (
+ subtensor.get_subnet_hyperparameters(
+ netuid=netuid
+ ).commit_reveal_weights_interval
+ == 1
+ ), "Failed to set commit/reveal periods"
+
+ assert (
+ subtensor.weights_rate_limit(netuid=netuid) > 0
+ ), "Weights rate limit is below 0"
+ # Lower the rate limit
+ assert sudo_set_hyperparameter_values(
+ local_chain,
+ alice_wallet,
+ call_function="sudo_set_weights_set_rate_limit",
+ call_params={"netuid": netuid, "weights_set_rate_limit": "0"},
+ return_error_message=True,
+ )
+
+ assert (
+ subtensor.get_subnet_hyperparameters(netuid=netuid).weights_rate_limit == 0
+ ), "Failed to set weights_rate_limit"
+ assert subtensor.weights_rate_limit(netuid=netuid) == 0
+
+ # Commit-reveal values
+ uids = np.array([0], dtype=np.int64)
+ weights = np.array([0.1], dtype=np.float32)
+ salt = [18, 179, 107, 0, 165, 211, 141, 197]
+ weight_uids, weight_vals = convert_weights_and_uids_for_emit(
+ uids=uids, weights=weights
+ )
+
+ # Make a second salt
+ salt2 = salt.copy()
+ salt2[0] += 1 # Increment the first byte to produce a different commit hash
+
+ # Make a third salt
+ salt3 = salt.copy()
+ salt3[0] += 2 # Increment the first byte to produce a different commit hash
+
+ # Commit all three salts
+ success, message = subtensor.commit_weights(
+ alice_wallet,
+ netuid,
+ salt=salt,
+ uids=weight_uids,
+ weights=weight_vals,
+ wait_for_inclusion=False, # Don't wait for inclusion, we are testing the nonce when there is a tx in the pool
+ wait_for_finalization=False,
+ )
+
+ assert success is True
+
+ success, message = subtensor.commit_weights(
+ alice_wallet,
+ netuid,
+ salt=salt2,
+ uids=weight_uids,
+ weights=weight_vals,
+ wait_for_inclusion=False,
+ wait_for_finalization=False,
+ )
+
+ assert success is True
+
+ # Commit the third salt
+ success, message = subtensor.commit_weights(
+ alice_wallet,
+ netuid,
+ salt=salt3,
+ uids=weight_uids,
+ weights=weight_vals,
+ wait_for_inclusion=False,
+ wait_for_finalization=False,
+ )
+
+ assert success is True
+
+ # Wait a few blocks
+ await asyncio.sleep(2) # Wait for the txs to be included in the chain
+
+ # Query the WeightCommits storage map for all three salts
+ weight_commits = subtensor.query_module(
+ module="SubtensorModule",
+ name="WeightCommits",
+ params=[netuid, alice_wallet.hotkey.ss58_address],
+ )
+ # Assert that the committed weights are set correctly
+ assert weight_commits.value is not None, "Weight commit not found in storage"
+ commit_hash, commit_block, reveal_block, expire_block = weight_commits.value[0]
+ assert commit_block > 0, f"Invalid block number: {commit_block}"
+
+ # Check for three commits in the WeightCommits storage map
+ assert len(weight_commits.value) == 3, "Expected 3 weight commits"
diff --git a/tests/e2e_tests/test_incentive.py b/tests/e2e_tests/test_incentive.py
index ab557a56fd..cfafef42b5 100644
--- a/tests/e2e_tests/test_incentive.py
+++ b/tests/e2e_tests/test_incentive.py
@@ -15,6 +15,7 @@
templates_repo,
)
from bittensor.utils.balance import Balance
+from bittensor.core.extrinsics import utils
from bittensor.core.extrinsics.set_weights import do_set_weights
from bittensor.core.metagraph import Metagraph
@@ -40,6 +41,8 @@ async def test_incentive(local_chain):
print("Testing test_incentive")
netuid = 1
+ utils.EXTRINSIC_SUBMISSION_TIMEOUT = 12 # handle fast blocks
+
# Register root as Alice - the subnet owner and validator
alice_keypair, alice_wallet = setup_wallet("//Alice")
register_subnet(local_chain, alice_wallet)
diff --git a/tests/e2e_tests/test_set_weights.py b/tests/e2e_tests/test_set_weights.py
new file mode 100644
index 0000000000..edf208ba8d
--- /dev/null
+++ b/tests/e2e_tests/test_set_weights.py
@@ -0,0 +1,157 @@
+import numpy as np
+import pytest
+
+import asyncio
+
+from bittensor.core.subtensor import Subtensor
+from bittensor.utils.balance import Balance
+from bittensor.utils.weight_utils import convert_weights_and_uids_for_emit
+from bittensor.core.extrinsics import utils
+from tests.e2e_tests.utils.chain_interactions import (
+ add_stake,
+ register_subnet,
+ sudo_set_hyperparameter_bool,
+ sudo_set_hyperparameter_values,
+ sudo_set_admin_utils,
+)
+from tests.e2e_tests.utils.e2e_test_utils import setup_wallet
+
+
+@pytest.mark.asyncio
+async def test_set_weights_uses_next_nonce(local_chain):
+ """
+ Tests that setting weights doesn't re-use a nonce in the transaction pool.
+
+ Steps:
+ 1. Register three subnets through Alice
+ 2. Register Alice's neuron on each subnet and add stake
+ 3. Verify Alice has a vpermit on each subnet
+ 4. Lower the set weights rate limit on each subnet
+ 5. Set weights on each subnet
+ 6. Assert that all the set weights succeeded
+ Raises:
+ AssertionError: If any of the checks or verifications fail
+ """
+ netuids = [1, 2]
+ utils.EXTRINSIC_SUBMISSION_TIMEOUT = 12 # handle fast blocks
+ print("Testing test_set_weights_uses_next_nonce")
+ # Register root as Alice
+ keypair, alice_wallet = setup_wallet("//Alice")
+
+ # Lower the network registration rate limit and cost
+ sudo_set_admin_utils(
+ local_chain,
+ alice_wallet,
+ call_function="sudo_set_network_rate_limit",
+ call_params={"rate_limit": "0"}, # No limit
+ return_error_message=True,
+ )
+ # Set lock reduction interval
+ sudo_set_admin_utils(
+ local_chain,
+ alice_wallet,
+ call_function="sudo_set_lock_reduction_interval",
+ call_params={"interval": "1"}, # 1 block # reduce lock every block
+ return_error_message=True,
+ )
+ # Try to register the subnets
+ for _ in netuids:
+ assert register_subnet(
+ local_chain, alice_wallet
+ ), "Unable to register the subnet"
+
+ # Verify all subnets created successfully
+ assert local_chain.query(
+ "SubtensorModule", "NetworksAdded", [3]
+ ).serialize(), "Subnet wasn't created successfully"
+
+ subtensor = Subtensor(network="ws://localhost:9945")
+
+ for netuid in netuids:
+ # Allow registration on the subnet
+ assert sudo_set_hyperparameter_values(
+ local_chain,
+ alice_wallet,
+ "sudo_set_network_registration_allowed",
+ {"netuid": netuid, "registration_allowed": True},
+ return_error_message=True,
+ )
+
+ # This should give a gap for the calls above to be included in the chain
+ await asyncio.sleep(2)
+
+ for netuid in netuids:
+ # Register Alice to the subnet
+ assert subtensor.burned_register(
+ alice_wallet, netuid
+ ), f"Unable to register Alice as a neuron on SN{netuid}"
+
+ # Stake to become to top neuron after the first epoch
+ add_stake(local_chain, alice_wallet, Balance.from_tao(100_000))
+
+ # Set weight hyperparameters per subnet
+ for netuid in netuids:
+ assert sudo_set_hyperparameter_bool(
+ local_chain,
+ alice_wallet,
+ "sudo_set_commit_reveal_weights_enabled",
+ False,
+ netuid,
+ ), "Unable to enable commit reveal on the subnet"
+
+ assert not subtensor.get_subnet_hyperparameters(
+ netuid=netuid,
+ ).commit_reveal_weights_enabled, "Failed to enable commit/reveal"
+
+ assert (
+ subtensor.weights_rate_limit(netuid=netuid) > 0
+ ), "Weights rate limit is below 0"
+ # Lower the rate limit
+ assert sudo_set_hyperparameter_values(
+ local_chain,
+ alice_wallet,
+ call_function="sudo_set_weights_set_rate_limit",
+ call_params={"netuid": netuid, "weights_set_rate_limit": "0"},
+ return_error_message=True,
+ )
+
+ assert (
+ subtensor.get_subnet_hyperparameters(netuid=netuid).weights_rate_limit == 0
+ ), "Failed to set weights_rate_limit"
+ assert subtensor.weights_rate_limit(netuid=netuid) == 0
+
+ # Weights values
+ uids = np.array([0], dtype=np.int64)
+ weights = np.array([0.1], dtype=np.float32)
+ weight_uids, weight_vals = convert_weights_and_uids_for_emit(
+ uids=uids, weights=weights
+ )
+
+ # Set weights for each subnet
+ for netuid in netuids:
+ success, message = subtensor.set_weights(
+ alice_wallet,
+ netuid,
+ uids=weight_uids,
+ weights=weight_vals,
+ wait_for_inclusion=False, # Don't wait for inclusion, we are testing the nonce when there is a tx in the pool
+ wait_for_finalization=False,
+ )
+
+ assert success is True, f"Failed to set weights for subnet {netuid}"
+
+ # Wait for the txs to be included in the chain
+ await asyncio.sleep(4)
+
+ for netuid in netuids:
+ # Query the Weights storage map for all three subnets
+ weights = subtensor.query_module(
+ module="SubtensorModule",
+ name="Weights",
+ params=[netuid, 0], # Alice should be the only UID
+ )
+
+ assert weights is not None, f"Weights not found for subnet {netuid}"
+ assert weights == list(
+ zip(weight_uids, weight_vals)
+ ), f"Weights do not match for subnet {netuid}"
diff --git a/tests/e2e_tests/test_transfer.py b/tests/e2e_tests/test_transfer.py
index 5a18db386e..7b95ae75bd 100644
--- a/tests/e2e_tests/test_transfer.py
+++ b/tests/e2e_tests/test_transfer.py
@@ -1,6 +1,9 @@
from bittensor.core.subtensor import Subtensor
from bittensor.utils.balance import Balance
from tests.e2e_tests.utils.e2e_test_utils import setup_wallet
+from bittensor import logging
+
+logging.set_trace()
def test_transfer(local_chain):
diff --git a/tests/e2e_tests/utils/chain_interactions.py b/tests/e2e_tests/utils/chain_interactions.py
index bae60c5443..bd5829e219 100644
--- a/tests/e2e_tests/utils/chain_interactions.py
+++ b/tests/e2e_tests/utils/chain_interactions.py
@@ -215,6 +215,35 @@ def sudo_set_admin_utils(
extrinsic = substrate.create_signed_extrinsic(
call=sudo_call, keypair=wallet.coldkey
)
+ response = substrate.submit_extrinsic(
+ extrinsic,
+ wait_for_inclusion=True,
+ wait_for_finalization=True,
+ )
+ response.process_events()
+
+ if return_error_message:
+ return response.is_success, response.error_message
+
+ return response.is_success
+
+
+async def root_set_subtensor_hyperparameter_values(
+ substrate: "SubstrateInterface",
+ wallet: "Wallet",
+ call_function: str,
+ call_params: dict,
+ return_error_message: bool = False,
+) -> Union[bool, tuple[bool, Optional[str]]]:
+ """
+ Sets liquid alpha values using AdminUtils. Mimics setting hyperparams
+ """
+ call = substrate.compose_call(
+ call_module="SubtensorModule",
+ call_function=call_function,
+ call_params=call_params,
+ )
+ extrinsic = substrate.create_signed_extrinsic(call=call, keypair=wallet.coldkey)
response = substrate.submit_extrinsic(
extrinsic,
diff --git a/tests/integration_tests/utils/test_init.py b/tests/integration_tests/utils/test_init.py
index 000e94b3d8..59d613ca89 100644
--- a/tests/integration_tests/utils/test_init.py
+++ b/tests/integration_tests/utils/test_init.py
@@ -7,33 +7,51 @@
from bittensor import utils
-def test_unlock_key(monkeypatch):
+def test_unlock_through_env():
# Ensure path is clean before we run the tests
- if os.path.exists("/tmp/bittensor-tests-wallets"):
+ if os.path.exists("/tmp/bittensor-tests-wallets/"):
shutil.rmtree("/tmp/bittensor-tests-wallets")
wallet = Wallet(path="/tmp/bittensor-tests-wallets")
+
+ # Set up the coldkey
cold_kf = Keyfile("/tmp/bittensor-tests-wallets/default/coldkey", name="default")
kp = Keypair.create_from_mnemonic(
"stool feel open east woman high can denial forget screen trust salt"
)
cold_kf.set_keypair(kp, False, False)
cold_kf.encrypt("1234password1234")
- hot_kf = Keyfile("/tmp/bittensor-tests-wallets/default/hotkey", name="default")
+
+ # Set up the hotkey
+ hot_kf = Keyfile(
+ "/tmp/bittensor-tests-wallets/default/hotkeys/default", name="default"
+ )
hkp = Keypair.create_from_mnemonic(
"stool feel open east woman high can denial forget screen trust salt"
)
hot_kf.set_keypair(hkp, False, False)
hot_kf.encrypt("1234hotkey1234")
- monkeypatch.setattr("getpass.getpass", lambda _: "badpassword1234")
+
+ # Save a wrong password to the environment for CK
+ cold_kf.save_password_to_env("badpassword")
result = utils.unlock_key(wallet)
assert result.success is False
- monkeypatch.setattr("getpass.getpass", lambda _: "1234password1234")
+
+ # Save correct password to the environment for CK
+ cold_kf.save_password_to_env("1234password1234")
result = utils.unlock_key(wallet)
assert result.success is True
- monkeypatch.setattr("getpass.getpass", lambda _: "badpassword1234")
+
+ # Save a wrong password to the environment for HK
+ hot_kf.save_password_to_env("badpassword")
result = utils.unlock_key(wallet, "hotkey")
assert result.success is False
+
+ # Save correct password to the environment for HK
+ hot_kf.save_password_to_env("1234hotkey1234")
+ result = utils.unlock_key(wallet, "hotkey")
+ assert result.success is True
+
with pytest.raises(ValueError):
utils.unlock_key(wallet, "mycoldkey")
diff --git a/tests/unit_tests/extrinsics/test_async_commit_reveal.py b/tests/unit_tests/extrinsics/test_async_commit_reveal.py
new file mode 100644
index 0000000000..ce4a5ccbfa
--- /dev/null
+++ b/tests/unit_tests/extrinsics/test_async_commit_reveal.py
@@ -0,0 +1,373 @@
+from bittensor.core import async_subtensor as subtensor_module
+from bittensor.core.chain_data import SubnetHyperparameters
+from bittensor.core.async_subtensor import AsyncSubtensor
+from bittensor.core.extrinsics import async_commit_reveal
+import pytest
+import torch
+import numpy as np
+
+
+@pytest.fixture
+def subtensor(mocker):
+ fake_substrate = mocker.AsyncMock()
+ fake_substrate.websocket.sock.getsockopt.return_value = 0
+ mocker.patch.object(
+ subtensor_module, "AsyncSubstrateInterface", return_value=fake_substrate
+ )
+ yield AsyncSubtensor()
+
+
+@pytest.fixture
+def hyperparams():
+ yield SubnetHyperparameters(
+ rho=0,
+ kappa=0,
+ immunity_period=0,
+ min_allowed_weights=0,
+ max_weight_limit=0.0,
+ tempo=0,
+ min_difficulty=0,
+ max_difficulty=0,
+ weights_version=0,
+ weights_rate_limit=0,
+ adjustment_interval=0,
+ activity_cutoff=0,
+ registration_allowed=False,
+ target_regs_per_interval=0,
+ min_burn=0,
+ max_burn=0,
+ bonds_moving_avg=0,
+ max_regs_per_block=0,
+ serving_rate_limit=0,
+ max_validators=0,
+ adjustment_alpha=0,
+ difficulty=0,
+ commit_reveal_weights_interval=0,
+ commit_reveal_weights_enabled=True,
+ alpha_high=0,
+ alpha_low=0,
+ liquid_alpha_enabled=False,
+ )
+
+
+@pytest.mark.asyncio
+async def test_do_commit_reveal_v3_success(mocker, subtensor):
+ """Test successful commit-reveal with wait for finalization."""
+ # Preps
+ fake_wallet = mocker.Mock(autospec=subtensor_module.Wallet)
+ fake_netuid = 1
+ fake_commit = b"fake_commit"
+ fake_reveal_round = 1
+
+ mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call")
+ mocked_create_signed_extrinsic = mocker.patch.object(
+ subtensor.substrate, "create_signed_extrinsic"
+ )
+ mocked_submit_extrinsic = mocker.patch.object(
+ subtensor.substrate, "submit_extrinsic"
+ )
+
+ # Call
+ result = await async_commit_reveal._do_commit_reveal_v3(
+ subtensor=subtensor,
+ wallet=fake_wallet,
+ netuid=fake_netuid,
+ commit=fake_commit,
+ reveal_round=fake_reveal_round,
+ )
+
+ # Asserts
+ mocked_compose_call.assert_awaited_once_with(
+ call_module="SubtensorModule",
+ call_function="commit_crv3_weights",
+ call_params={
+ "netuid": fake_netuid,
+ "commit": fake_commit,
+ "reveal_round": fake_reveal_round,
+ },
+ )
+ mocked_create_signed_extrinsic.assert_awaited_once_with(
+ call=mocked_compose_call.return_value, keypair=fake_wallet.hotkey
+ )
+ mocked_submit_extrinsic.assert_awaited_once_with(
+ subtensor=subtensor,
+ extrinsic=mocked_create_signed_extrinsic.return_value,
+ wait_for_inclusion=False,
+ wait_for_finalization=False,
+ )
+ assert result == (True, "Not waiting for finalization or inclusion.")
+
+
+@pytest.mark.asyncio
+async def test_do_commit_reveal_v3_failure_due_to_error(mocker, subtensor):
+ """Test commit-reveal fails due to an error in submission."""
+ # Preps
+ fake_wallet = mocker.Mock(autospec=subtensor_module.Wallet)
+ fake_netuid = 1
+ fake_commit = b"fake_commit"
+ fake_reveal_round = 1
+
+ mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call")
+ mocked_create_signed_extrinsic = mocker.patch.object(
+ subtensor.substrate, "create_signed_extrinsic"
+ )
+ mocked_submit_extrinsic = mocker.patch.object(
+ subtensor.substrate,
+ "submit_extrinsic",
+ return_value=mocker.Mock(
+ is_success=mocker.AsyncMock(return_value=False)(),
+ error_message=mocker.AsyncMock(return_value="Mocked error")(),
+ ),
+ )
+
+ mocked_format_error_message = mocker.patch.object(
+ async_commit_reveal, "format_error_message", return_value="Formatted error"
+ )
+
+ # Call
+ result = await async_commit_reveal._do_commit_reveal_v3(
+ subtensor=subtensor,
+ wallet=fake_wallet,
+ netuid=fake_netuid,
+ commit=fake_commit,
+ reveal_round=fake_reveal_round,
+ wait_for_inclusion=True,
+ wait_for_finalization=True,
+ )
+
+ # Asserts
+ mocked_compose_call.assert_awaited_once_with(
+ call_module="SubtensorModule",
+ call_function="commit_crv3_weights",
+ call_params={
+ "netuid": fake_netuid,
+ "commit": fake_commit,
+ "reveal_round": fake_reveal_round,
+ },
+ )
+ mocked_create_signed_extrinsic.assert_awaited_once_with(
+ call=mocked_compose_call.return_value, keypair=fake_wallet.hotkey
+ )
+ mocked_submit_extrinsic.assert_awaited_once_with(
+ subtensor=subtensor,
+ extrinsic=mocked_create_signed_extrinsic.return_value,
+ wait_for_inclusion=True,
+ wait_for_finalization=True,
+ )
+ mocked_format_error_message.assert_called_once_with("Mocked error")
+ assert result == (False, "Formatted error")
+
+
+@pytest.mark.asyncio
+async def test_commit_reveal_v3_extrinsic_success_with_torch(
+ mocker, subtensor, hyperparams
+):
+ """Test successful commit-reveal with torch tensors."""
+ # Preps
+ fake_wallet = mocker.Mock(autospec=subtensor_module.Wallet)
+ fake_netuid = 1
+ fake_uids = torch.tensor([1, 2, 3], dtype=torch.int64)
+ fake_weights = torch.tensor([0.1, 0.2, 0.7], dtype=torch.float32)
+ fake_commit_for_reveal = b"mock_commit_for_reveal"
+ fake_reveal_round = 1
+
+ # Mocks
+
+ mocked_uids = mocker.Mock()
+ mocked_weights = mocker.Mock()
+ mocked_convert_weights_and_uids_for_emit = mocker.patch.object(
+ async_commit_reveal,
+ "convert_weights_and_uids_for_emit",
+ return_value=(mocked_uids, mocked_weights),
+ )
+ mocked_get_subnet_reveal_period_epochs = mocker.patch.object(
+ subtensor, "get_subnet_reveal_period_epochs"
+ )
+ mocked_get_encrypted_commit = mocker.patch.object(
+ async_commit_reveal,
+ "get_encrypted_commit",
+ return_value=(fake_commit_for_reveal, fake_reveal_round),
+ )
+ mock_do_commit_reveal_v3 = mocker.patch.object(
+ async_commit_reveal, "_do_commit_reveal_v3", return_value=(True, "Success")
+ )
+ mock_block = mocker.patch.object(
+ subtensor.substrate, "get_block_number", return_value=1
+ )
+ mock_hyperparams = mocker.patch.object(
+ subtensor,
+ "get_subnet_hyperparameters",
+ return_value=hyperparams,
+ )
+
+ # Call
+ success, message = await async_commit_reveal.commit_reveal_v3_extrinsic(
+ subtensor=subtensor,
+ wallet=fake_wallet,
+ netuid=fake_netuid,
+ uids=fake_uids,
+ weights=fake_weights,
+ wait_for_inclusion=True,
+ wait_for_finalization=True,
+ )
+
+ # Asserts
+ assert success is True
+ assert message == "reveal_round:1"
+ mocked_convert_weights_and_uids_for_emit.assert_called_once_with(
+ fake_uids, fake_weights
+ )
+ mocked_get_encrypted_commit.assert_called_once_with(
+ uids=mocked_uids,
+ weights=mocked_weights,
+ subnet_reveal_period_epochs=mock_hyperparams.return_value.commit_reveal_weights_interval,
+ version_key=async_commit_reveal.version_as_int,
+ tempo=mock_hyperparams.return_value.tempo,
+ netuid=fake_netuid,
+ current_block=mock_block.return_value,
+ )
+ mock_do_commit_reveal_v3.assert_awaited_once_with(
+ subtensor=subtensor,
+ wallet=fake_wallet,
+ netuid=fake_netuid,
+ commit=fake_commit_for_reveal,
+ reveal_round=fake_reveal_round,
+ wait_for_inclusion=True,
+ wait_for_finalization=True,
+ )
+
+
+@pytest.mark.asyncio
+async def test_commit_reveal_v3_extrinsic_success_with_numpy(
+ mocker, subtensor, hyperparams
+):
+ """Test successful commit-reveal with numpy arrays."""
+ # Preps
+ fake_wallet = mocker.Mock(autospec=subtensor_module.Wallet)
+ fake_netuid = 1
+ fake_uids = np.array([1, 2, 3], dtype=np.int64)
+ fake_weights = np.array([0.1, 0.2, 0.7], dtype=np.float32)
+
+ mock_convert = mocker.patch.object(
+ async_commit_reveal,
+ "convert_weights_and_uids_for_emit",
+ return_value=(fake_uids, fake_weights),
+ )
+ mock_encode_drand = mocker.patch.object(
+ async_commit_reveal, "get_encrypted_commit", return_value=(b"commit", 0)
+ )
+ mock_do_commit = mocker.patch.object(
+ async_commit_reveal, "_do_commit_reveal_v3", return_value=(True, "Committed!")
+ )
+ mocker.patch.object(subtensor.substrate, "get_block_number", return_value=1)
+ mocker.patch.object(
+ subtensor,
+ "get_subnet_hyperparameters",
+ return_value=hyperparams,
+ )
+
+ # Call
+ success, message = await async_commit_reveal.commit_reveal_v3_extrinsic(
+ subtensor=subtensor,
+ wallet=fake_wallet,
+ netuid=fake_netuid,
+ uids=fake_uids,
+ weights=fake_weights,
+ wait_for_inclusion=False,
+ wait_for_finalization=False,
+ )
+
+ # Asserts
+ assert success is True
+ assert message == "reveal_round:0"
+ mock_convert.assert_called_once_with(fake_uids, fake_weights)
+ mock_encode_drand.assert_called_once()
+ mock_do_commit.assert_awaited_once()
+
+
+@pytest.mark.asyncio
+async def test_commit_reveal_v3_extrinsic_response_false(
+ mocker, subtensor, hyperparams
+):
+ """Test unsuccessful commit-reveal with torch."""
+ # Preps
+ fake_wallet = mocker.Mock(autospec=subtensor_module.Wallet)
+ fake_netuid = 1
+ fake_uids = torch.tensor([1, 2, 3], dtype=torch.int64)
+ fake_weights = torch.tensor([0.1, 0.2, 0.7], dtype=torch.float32)
+ fake_commit_for_reveal = b"mock_commit_for_reveal"
+ fake_reveal_round = 1
+
+ # Mocks
+ mocker.patch.object(
+ async_commit_reveal,
+ "convert_weights_and_uids_for_emit",
+ return_value=(fake_uids, fake_weights),
+ )
+ mocker.patch.object(
+ async_commit_reveal,
+ "get_encrypted_commit",
+ return_value=(fake_commit_for_reveal, fake_reveal_round),
+ )
+ mock_do_commit_reveal_v3 = mocker.patch.object(
+ async_commit_reveal, "_do_commit_reveal_v3", return_value=(False, "Failed")
+ )
+ mocker.patch.object(subtensor.substrate, "get_block_number", return_value=1)
+ mocker.patch.object(
+ subtensor,
+ "get_subnet_hyperparameters",
+ return_value=hyperparams,
+ )
+
+ # Call
+ success, message = await async_commit_reveal.commit_reveal_v3_extrinsic(
+ subtensor=subtensor,
+ wallet=fake_wallet,
+ netuid=fake_netuid,
+ uids=fake_uids,
+ weights=fake_weights,
+ wait_for_inclusion=True,
+ wait_for_finalization=True,
+ )
+
+ # Asserts
+ assert success is False
+ assert message == "Failed"
+ mock_do_commit_reveal_v3.assert_awaited_once_with(
+ subtensor=subtensor,
+ wallet=fake_wallet,
+ netuid=fake_netuid,
+ commit=fake_commit_for_reveal,
+ reveal_round=fake_reveal_round,
+ wait_for_inclusion=True,
+ wait_for_finalization=True,
+ )
+
+
+@pytest.mark.asyncio
+async def test_commit_reveal_v3_extrinsic_exception(mocker, subtensor):
+ """Test exception handling in commit-reveal."""
+ # Preps
+ fake_wallet = mocker.Mock(autospec=subtensor_module.Wallet)
+ fake_netuid = 1
+ fake_uids = [1, 2, 3]
+ fake_weights = [0.1, 0.2, 0.7]
+
+ mocker.patch.object(
+ async_commit_reveal,
+ "convert_weights_and_uids_for_emit",
+ side_effect=Exception("Test Error"),
+ )
+
+ # Call
+ success, message = await async_commit_reveal.commit_reveal_v3_extrinsic(
+ subtensor=subtensor,
+ wallet=fake_wallet,
+ netuid=fake_netuid,
+ uids=fake_uids,
+ weights=fake_weights,
+ )
+
+ # Asserts
+ assert success is False
+ assert "Test Error" in message
diff --git a/tests/unit_tests/extrinsics/test_commit_weights.py b/tests/unit_tests/extrinsics/test_commit_weights.py
index 35a1d4d426..57d78a8013 100644
--- a/tests/unit_tests/extrinsics/test_commit_weights.py
+++ b/tests/unit_tests/extrinsics/test_commit_weights.py
@@ -53,9 +53,10 @@ def test_do_commit_weights(subtensor, mocker):
},
)
- subtensor.substrate.create_signed_extrinsic.assert_called_once_with(
- call=subtensor.substrate.compose_call.return_value, keypair=fake_wallet.hotkey
- )
+ subtensor.substrate.create_signed_extrinsic.assert_called_once()
+ _, kwargs = subtensor.substrate.create_signed_extrinsic.call_args
+ assert kwargs["call"] == subtensor.substrate.compose_call.return_value
+ assert kwargs["keypair"] == fake_wallet.hotkey
subtensor.substrate.submit_extrinsic.assert_called_once_with(
subtensor.substrate.create_signed_extrinsic.return_value,
diff --git a/tests/unit_tests/extrinsics/test_set_weights.py b/tests/unit_tests/extrinsics/test_set_weights.py
index f447915d2f..6c070bf5c4 100644
--- a/tests/unit_tests/extrinsics/test_set_weights.py
+++ b/tests/unit_tests/extrinsics/test_set_weights.py
@@ -135,17 +135,11 @@ def test_do_set_weights_is_success(mock_subtensor, mocker):
},
)
- mock_subtensor.substrate.create_signed_extrinsic.assert_called_once_with(
- call=mock_subtensor.substrate.compose_call.return_value,
- keypair=fake_wallet.hotkey,
- era={"period": 5},
- )
-
- mock_subtensor.substrate.submit_extrinsic.assert_called_once_with(
- mock_subtensor.substrate.create_signed_extrinsic.return_value,
- wait_for_inclusion=fake_wait_for_inclusion,
- wait_for_finalization=fake_wait_for_finalization,
- )
+ mock_subtensor.substrate.create_signed_extrinsic.assert_called_once()
+ _, kwargs = mock_subtensor.substrate.create_signed_extrinsic.call_args
+ assert kwargs["call"] == mock_subtensor.substrate.compose_call.return_value
+ assert kwargs["keypair"] == fake_wallet.hotkey
+ assert kwargs["era"] == {"period": 5}
mock_subtensor.substrate.submit_extrinsic.return_value.process_events.assert_called_once()
assert result == (True, "Successfully set weights.")
@@ -189,11 +183,11 @@ def test_do_set_weights_is_not_success(mock_subtensor, mocker):
},
)
- mock_subtensor.substrate.create_signed_extrinsic.assert_called_once_with(
- call=mock_subtensor.substrate.compose_call.return_value,
- keypair=fake_wallet.hotkey,
- era={"period": 5},
- )
+ mock_subtensor.substrate.create_signed_extrinsic.assert_called_once()
+ _, kwargs = mock_subtensor.substrate.create_signed_extrinsic.call_args
+ assert kwargs["call"] == mock_subtensor.substrate.compose_call.return_value
+ assert kwargs["keypair"] == fake_wallet.hotkey
+ assert kwargs["era"] == {"period": 5}
mock_subtensor.substrate.submit_extrinsic.assert_called_once_with(
mock_subtensor.substrate.create_signed_extrinsic.return_value,
@@ -242,11 +236,11 @@ def test_do_set_weights_no_waits(mock_subtensor, mocker):
},
)
- mock_subtensor.substrate.create_signed_extrinsic.assert_called_once_with(
- call=mock_subtensor.substrate.compose_call.return_value,
- keypair=fake_wallet.hotkey,
- era={"period": 5},
- )
+ mock_subtensor.substrate.create_signed_extrinsic.assert_called_once()
+ _, kwargs = mock_subtensor.substrate.create_signed_extrinsic.call_args
+ assert kwargs["call"] == mock_subtensor.substrate.compose_call.return_value
+ assert kwargs["keypair"] == fake_wallet.hotkey
+ assert kwargs["era"] == {"period": 5}
mock_subtensor.substrate.submit_extrinsic.assert_called_once_with(
mock_subtensor.substrate.create_signed_extrinsic.return_value,
diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py
index d5b89c8d99..bf672beb56 100644
--- a/tests/unit_tests/test_async_subtensor.py
+++ b/tests/unit_tests/test_async_subtensor.py
@@ -2614,7 +2614,9 @@ async def test_set_weights_success(subtensor, mocker):
fake_weights = [0.3, 0.5, 0.2]
max_retries = 1
- mocked_get_uid_for_hotkey_on_subnet = mocker.AsyncMock(return_value=fake_netuid)
+ mocked_get_uid_for_hotkey_on_subnet = mocker.patch.object(
+ subtensor, "get_uid_for_hotkey_on_subnet"
+ )
subtensor.get_uid_for_hotkey_on_subnet = mocked_get_uid_for_hotkey_on_subnet
mocked_blocks_since_last_update = mocker.AsyncMock(return_value=2)
diff --git a/tests/unit_tests/test_config.py b/tests/unit_tests/test_config.py
index 53754764e1..fc0ad7c6f7 100644
--- a/tests/unit_tests/test_config.py
+++ b/tests/unit_tests/test_config.py
@@ -19,9 +19,14 @@ def test_py_config_parsed_successfully_rust_wallet():
config.wallet.hotkey = "new_hotkey"
config.wallet.path = "/some/not_default/path"
+ # Pass in the whole bittensor config
wallet = bittensor.wallet(config=config)
-
- # Asserts
assert wallet.name == config.wallet.name
assert wallet.hotkey_str == config.wallet.hotkey
assert wallet.path == config.wallet.path
+
+ # Pass in only the btwallet's config
+ wallet_two = bittensor.wallet(config=config.wallet)
+ assert wallet_two.name == config.wallet.name
+ assert wallet_two.hotkey_str == config.wallet.hotkey
+ assert wallet_two.path == config.wallet.path
diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py
index bf156e2122..7cd0ed19f5 100644
--- a/tests/unit_tests/test_subtensor.py
+++ b/tests/unit_tests/test_subtensor.py
@@ -1739,6 +1739,8 @@ def test_get_transfer_fee(subtensor, mocker):
fake_payment_info = {"partialFee": int(2e10)}
subtensor.substrate.get_payment_info.return_value = fake_payment_info
+ mocker.patch.object(subtensor_module, "Keypair", return_value=mocker.MagicMock())
+
# Call
result = subtensor.get_transfer_fee(wallet=fake_wallet, dest=fake_dest, value=value)
@@ -1751,7 +1753,7 @@ def test_get_transfer_fee(subtensor, mocker):
subtensor.substrate.get_payment_info.assert_called_once_with(
call=subtensor.substrate.compose_call.return_value,
- keypair=fake_wallet.coldkeypub,
+ keypair=subtensor_module.Keypair.return_value,
)
assert result == 2e10
@@ -2548,7 +2550,7 @@ def test_get_delegates_success(mocker, subtensor):
fake_block = 123
fake_block_hash = "0xabc123"
fake_json_body = {
- "result": "mock_encoded_delegates",
+ "result": b"mock_encoded_delegates",
}
# Mocks
@@ -2616,7 +2618,7 @@ def test_get_delegates_latest_block(mocker, subtensor):
"""Test when no block is provided (latest block)."""
# Mock data
fake_json_body = {
- "result": "mock_encoded_delegates",
+ "result": b"mock_encoded_delegates",
}
# Mocks
@@ -2880,3 +2882,27 @@ def test_set_weights_with_commit_reveal_enabled(subtensor, mocker):
wait_for_finalization=fake_wait_for_finalization,
)
assert result == mocked_commit_reveal_v3_extrinsic.return_value
+
+
+def test_connection_limit(mocker):
+ """Test connection limit is not exceeded."""
+ # Technically speaking, this test should exist in integration tests. But to reduce server costs we will leave this
+ # test here.
+
+ # Preps
+ mocker.patch.object(
+ subtensor_module.ws_client,
+ "connect",
+ side_effect=subtensor_module.InvalidStatus(
+ response=mocker.Mock(
+ response=mocker.Mock(
+ status_code=429, message="test connection limit error"
+ )
+ )
+ ),
+ )
+ # Call with assertions
+
+ with pytest.raises(subtensor_module.InvalidStatus):
+ for i in range(2):
+ Subtensor("test")