diff --git a/.circleci/config.yml b/.circleci/config.yml
index 90f49d54eb..80bdc8c318 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -275,19 +275,6 @@ jobs:
command: |
[[ $(docker manifest inspect opentensorfdn/bittensor:`cat VERSION` > /dev/null 2> /dev/null ; echo $?) == 1 ]] && echo "Docker image 'opentensorfdn/bittensor:`cat VERSION`' does not exist in dockerhub"
- release-dry-run:
- docker:
- - image: cimg/python:3.10
- steps:
- - checkout
- - setup_remote_docker:
- version: 20.10.14
- docker_layer_caching: true
- - run:
- name: Executing release script
- command: |
- ./scripts/release/release.sh --github-token ${GH_API_ACCESS_TOKEN}
-
workflows:
compatibility_checks:
jobs:
@@ -339,11 +326,6 @@ workflows:
branches:
only:
- /^(release|hotfix)/.*/
- - release-dry-run:
- filters:
- branches:
- only:
- - /^(release|hotfix)/.*/
release-requirements:
jobs:
@@ -352,8 +334,3 @@ workflows:
branches:
only:
- master
- - release-dry-run:
- filters:
- branches:
- only:
- - master
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4c8cb25c2a..7c2b308cf9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,48 @@
# Changelog
+## 8.3.1 /2024-11-14
+
+## What's Changed
+* Fixes broken Subtensor methods by @thewhaleking in https://github.com/opentensor/bittensor/pull/2420
+* [Tests] AsyncSubtensor (Part 7: The final race) by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2418
+
+**Full Changelog**: https://github.com/opentensor/bittensor/compare/v8.3.0...v8.3.1
+
+## 8.3.0 /2024-11-13
+
+## What's Changed
+* Expands the type registry to include all the available options by @thewhaleking in https://github.com/opentensor/bittensor/pull/2353
+* add `Subtensor.register`, `Subtensor.difficulty` and related staff with tests by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2352
+* added to Subtensor: `burned_register`, `get_subnet_burn_cost`, `recycle` and related extrinsics by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2359
+* Poem "Risen from the Past". Act 3. by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2363
+* default port from 9946 to 9944 by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2376
+* remove unused prometheus extrinsic by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2378
+* Replace rich.console to btlogging.loggin by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2377
+* SDK (AsyncSubtensor) Part 1 by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2374
+* SDK (AsyncSubtensor) Part 2 by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2380
+* Handle SSL Error on Connection by @thewhaleking in https://github.com/opentensor/bittensor/pull/2384
+* Avoid using `prompt` in SDK by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2382
+* Backmerge/8.2.0 by @ibraheem-opentensor in https://github.com/opentensor/bittensor/pull/2389
+* Remove `retry` and fix tests by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2392
+* fix: logging weights correctly in utils/weight_utils.py by @grantdfoster in https://github.com/opentensor/bittensor/pull/2362
+* Add `subvortex` subnet and tests by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2395
+* Release/8.2.1 by @ibraheem-opentensor in https://github.com/opentensor/bittensor/pull/2397
+* [Tests] AsyncSubtensor (Part 1) by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2398
+* Extend period for fastblock e2e tests_incentive.py by @opendansor in https://github.com/opentensor/bittensor/pull/2400
+* Remove unused import by @thewhaleking in https://github.com/opentensor/bittensor/pull/2401
+* `Reconnection substrate...` as debug by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2403
+* Handles websockets v14+ in async by @thewhaleking in https://github.com/opentensor/bittensor/pull/2404
+* [Tests] AsyncSubtensor (Part 2) by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2407
+* [Tests] AsyncSubtensor (Part 3) by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2409
+* Handle new PasswordError from btwallet by @thewhaleking in https://github.com/opentensor/bittensor/pull/2406
+* [Tests] AsyncSubtensor (Part 4) by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2410
+* [Tests] AsyncSubtensor (Part 5) by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2411
+* Bringing back lost methods for setting weights by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2412
+* Update bt-decode requirement by @thewhaleking in https://github.com/opentensor/bittensor/pull/2413
+* [Tests] AsyncSubtensor (Part 6) by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2414
+
+**Full Changelog**: https://github.com/opentensor/bittensor/compare/v8.2.1...v8.3.0
+
## 8.2.1 /2024-11-06
## What's Changed
@@ -1143,4 +1186,4 @@ This release refactors the registration code for CPU registration to improve sol
### Synapse update
-##
\ No newline at end of file
+##
diff --git a/VERSION b/VERSION
index 9f4a0fbc18..905c243928 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-8.3.0
\ No newline at end of file
+8.3.1
\ No newline at end of file
diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py
index ae912d5dca..fc59fc121a 100644
--- a/bittensor/core/async_subtensor.py
+++ b/bittensor/core/async_subtensor.py
@@ -46,6 +46,7 @@
format_error_message,
decode_hex_identity_dict,
validate_chain_endpoint,
+ hex_to_bytes,
)
from bittensor.utils.async_substrate_interface import (
AsyncSubstrateInterface,
@@ -351,12 +352,7 @@ async def get_delegates(
reuse_block=reuse_block,
)
if hex_bytes_result is not None:
- try:
- bytes_result = bytes.fromhex(hex_bytes_result[2:])
- except ValueError:
- bytes_result = bytes.fromhex(hex_bytes_result)
-
- return DelegateInfo.list_from_vec_u8(bytes_result)
+ return DelegateInfo.list_from_vec_u8(hex_to_bytes(hex_bytes_result))
else:
return []
@@ -392,12 +388,7 @@ async def get_stake_info_for_coldkey(
if hex_bytes_result is None:
return []
- try:
- bytes_result = bytes.fromhex(hex_bytes_result[2:])
- except ValueError:
- bytes_result = bytes.fromhex(hex_bytes_result)
-
- return StakeInfo.list_from_vec_u8(bytes_result)
+ return StakeInfo.list_from_vec_u8(hex_to_bytes(hex_bytes_result))
async def get_stake_for_coldkey_and_hotkey(
self, hotkey_ss58: str, coldkey_ss58: str, block_hash: Optional[str] = None
@@ -853,12 +844,7 @@ async def neurons_lite(
if hex_bytes_result is None:
return []
- try:
- bytes_result = bytes.fromhex(hex_bytes_result[2:])
- except ValueError:
- bytes_result = bytes.fromhex(hex_bytes_result)
-
- return NeuronInfoLite.list_from_vec_u8(bytes_result)
+ return NeuronInfoLite.list_from_vec_u8(hex_to_bytes(hex_bytes_result))
async def neuron_for_uid(
self, uid: Optional[int], netuid: int, block_hash: Optional[str] = None
@@ -1170,12 +1156,7 @@ async def get_subnet_hyperparameters(
if hex_bytes_result is None:
return []
- if hex_bytes_result.startswith("0x"):
- bytes_result = bytes.fromhex(hex_bytes_result[2:])
- else:
- bytes_result = bytes.fromhex(hex_bytes_result)
-
- return SubnetHyperparameters.from_vec_u8(bytes_result)
+ return SubnetHyperparameters.from_vec_u8(hex_to_bytes(hex_bytes_result))
async def get_vote_data(
self,
@@ -1422,15 +1403,15 @@ async def register(
async def pow_register(
self: "AsyncSubtensor",
- wallet: Wallet,
- netuid,
- processors,
- update_interval,
- output_in_place,
- verbose,
- use_cuda,
- dev_id,
- threads_per_block,
+ wallet: "Wallet",
+ netuid: int,
+ processors: int,
+ update_interval: int,
+ output_in_place: bool,
+ verbose: bool,
+ use_cuda: bool,
+ dev_id: Union[list[int], int],
+ threads_per_block: int,
):
"""Register neuron."""
return await register_extrinsic(
@@ -1481,11 +1462,9 @@ async def set_weights(
retries = 0
success = False
message = "No attempt made. Perhaps it is too soon to set weights!"
- while (
- await self.blocks_since_last_update(netuid, uid)
- > await self.weights_rate_limit(netuid)
- and retries < max_retries
- ):
+ while retries < max_retries and await self.blocks_since_last_update(
+ netuid, uid
+ ) > await self.weights_rate_limit(netuid):
try:
logging.info(
f"Setting weights for subnet #{netuid}. Attempt {retries + 1} of {max_retries}."
diff --git a/bittensor/core/settings.py b/bittensor/core/settings.py
index d3a8a43a3c..8e8843b2e8 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.3.0"
+__version__ = "8.3.1"
import os
import re
diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py
index 67888cd999..efaff369a4 100644
--- a/bittensor/core/subtensor.py
+++ b/bittensor/core/subtensor.py
@@ -71,7 +71,13 @@
transfer_extrinsic,
)
from bittensor.core.metagraph import Metagraph
-from bittensor.utils import networking, torch, ss58_to_vec_u8, u16_normalized_float
+from bittensor.utils import (
+ networking,
+ torch,
+ ss58_to_vec_u8,
+ u16_normalized_float,
+ hex_to_bytes,
+)
from bittensor.utils.balance import Balance
from bittensor.utils.btlogging import logging
from bittensor.utils.registration import legacy_torch_api_compat
@@ -523,13 +529,11 @@ def query_runtime_api(
return None
return_type = call_definition["type"]
-
as_scale_bytes = scalecodec.ScaleBytes(json_result["result"])
rpc_runtime_config = RuntimeConfiguration()
rpc_runtime_config.update_type_registry(load_type_registry_preset("legacy"))
rpc_runtime_config.update_type_registry(custom_rpc_type_registry)
-
obj = rpc_runtime_config.create_scale_object(return_type, as_scale_bytes)
if obj.data.to_hex() == "0x0400": # RPC returned None result
return None
@@ -1227,12 +1231,7 @@ def get_subnet_hyperparameters(
if hex_bytes_result is None:
return []
- if hex_bytes_result.startswith("0x"):
- bytes_result = bytes.fromhex(hex_bytes_result[2:])
- else:
- bytes_result = bytes.fromhex(hex_bytes_result)
-
- return SubnetHyperparameters.from_vec_u8(bytes_result)
+ return SubnetHyperparameters.from_vec_u8(hex_to_bytes(hex_bytes_result))
# Community uses this method
# Returns network ImmunityPeriod hyper parameter.
@@ -1308,10 +1307,13 @@ def get_commitment(self, netuid: int, uid: int, block: Optional[int] = None) ->
hotkey = metagraph.hotkeys[uid] # type: ignore
metadata = get_metadata(self, netuid, hotkey, block)
- commitment = metadata["info"]["fields"][0] # type: ignore
- hex_data = commitment[list(commitment.keys())[0]][2:] # type: ignore
+ try:
+ commitment = metadata["info"]["fields"][0] # type: ignore
+ hex_data = commitment[list(commitment.keys())[0]][2:] # type: ignore
+ return bytes.fromhex(hex_data).decode()
- return bytes.fromhex(hex_data).decode()
+ except TypeError:
+ return ""
# Community uses this via `bittensor.utils.weight_utils.process_weights_for_netuid` function.
def min_allowed_weights(
@@ -1367,7 +1369,7 @@ def get_prometheus_info(
Optional[bittensor.core.chain_data.prometheus_info.PrometheusInfo]: A PrometheusInfo object containing the prometheus information, or ``None`` if the prometheus information is not found.
"""
result = self.query_subtensor("Prometheus", block, [netuid, hotkey_ss58])
- if result is not None and hasattr(result, "value"):
+ if result is not None and getattr(result, "value", None) is not None:
return PrometheusInfo(
ip=networking.int_to_ip(result.value["ip"]),
ip_type=result.value["ip_type"],
@@ -1407,17 +1409,13 @@ def get_all_subnets_info(self, block: Optional[int] = None) -> list[SubnetInfo]:
Gaining insights into the subnets' details assists in understanding the network's composition, the roles of different subnets, and their unique features.
"""
- block_hash = None if block is None else self.substrate.get_block_hash(block)
-
- json_body = self.substrate.rpc_request(
- method="subnetInfo_getSubnetsInfo", # custom rpc method
- params=[block_hash] if block_hash else [],
+ hex_bytes_result = self.query_runtime_api(
+ "SubnetInfoRuntimeApi", "get_subnets_info", params=[], block=block
)
-
- if not (result := json_body.get("result", None)):
+ if not hex_bytes_result:
return []
-
- return SubnetInfo.list_from_vec_u8(result)
+ else:
+ return SubnetInfo.list_from_vec_u8(hex_to_bytes(hex_bytes_result))
# Metagraph uses this method
def bonds(
@@ -1561,12 +1559,7 @@ def neurons_lite(
if hex_bytes_result is None:
return []
- if hex_bytes_result.startswith("0x"):
- bytes_result = bytes.fromhex(hex_bytes_result[2:])
- else:
- bytes_result = bytes.fromhex(hex_bytes_result)
-
- return NeuronInfoLite.list_from_vec_u8(bytes_result) # type: ignore
+ return NeuronInfoLite.list_from_vec_u8(hex_to_bytes(hex_bytes_result)) # type: ignore
# Used in the `neurons` method which is used in metagraph.py
def weights(
@@ -1923,7 +1916,7 @@ def get_delegate_by_hotkey(
if not (result := json_body.get("result", None)):
return None
- return DelegateInfo.from_vec_u8(result)
+ return DelegateInfo.from_vec_u8(bytes(result))
# Subnet 27 uses this method name
_do_serve_axon = do_serve_axon
diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py
index 5c89382987..7a42dff0cf 100644
--- a/bittensor/utils/__init__.py
+++ b/bittensor/utils/__init__.py
@@ -403,3 +403,14 @@ def unlock_key(wallet: "Wallet", unlock_type="coldkey") -> "UnlockStatus":
except KeyFileError:
err_msg = f"{unlock_type.capitalize()} keyfile is corrupt, non-writable, or non-readable, or non-existent."
return UnlockStatus(False, err_msg)
+
+
+def hex_to_bytes(hex_str: str) -> bytes:
+ """
+ Converts a hex-encoded string into bytes. Handles 0x-prefixed and non-prefixed hex-encoded strings.
+ """
+ if hex_str.startswith("0x"):
+ bytes_result = bytes.fromhex(hex_str[2:])
+ else:
+ bytes_result = bytes.fromhex(hex_str)
+ return bytes_result
diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py
index 4db725da39..87813761dc 100644
--- a/tests/unit_tests/test_async_subtensor.py
+++ b/tests/unit_tests/test_async_subtensor.py
@@ -1,5 +1,6 @@
import pytest
+from bittensor import AsyncSubtensor
from bittensor.core import async_subtensor
@@ -69,6 +70,53 @@ def test_decode_hex_identity_dict_with_nested_dict():
assert result["identity"] == "41 4243"
+@pytest.mark.asyncio
+async def test_init_if_unknown_network_is_valid(mocker):
+ """Tests __init__ if passed network unknown and is valid."""
+ # Preps
+ fake_valid_endpoint = "wss://blabla.net"
+ mocker.patch.object(async_subtensor, "AsyncSubstrateInterface")
+
+ # Call
+ subtensor = AsyncSubtensor(fake_valid_endpoint)
+
+ # Asserts
+ assert subtensor.chain_endpoint == fake_valid_endpoint
+ assert subtensor.network == "custom"
+
+
+@pytest.mark.asyncio
+async def test_init_if_unknown_network_is_known_endpoint(mocker):
+ """Tests __init__ if passed network unknown and is valid."""
+ # Preps
+ fake_valid_endpoint = "ws://127.0.0.1:9944"
+ mocker.patch.object(async_subtensor, "AsyncSubstrateInterface")
+
+ # Call
+ subtensor = AsyncSubtensor(fake_valid_endpoint)
+
+ # Asserts
+ assert subtensor.chain_endpoint == fake_valid_endpoint
+ assert subtensor.network == "local"
+
+
+@pytest.mark.asyncio
+async def test_init_if_unknown_network_is_not_valid(mocker):
+ """Tests __init__ if passed network unknown and isn't valid."""
+ # Preps
+ mocker.patch.object(async_subtensor, "AsyncSubstrateInterface")
+
+ # Call
+ subtensor = AsyncSubtensor("blabla-net")
+
+ # Asserts
+ assert (
+ subtensor.chain_endpoint
+ == async_subtensor.NETWORK_MAP[async_subtensor.DEFAULTS.subtensor.network]
+ )
+ assert subtensor.network == async_subtensor.DEFAULTS.subtensor.network
+
+
def test__str__return(subtensor):
"""Simply tests the result if printing subtensor instance."""
# Asserts
@@ -100,6 +148,38 @@ async def test_async_subtensor_magic_methods(mocker):
fake_async_substrate.close.assert_awaited_once()
+@pytest.mark.parametrize(
+ "error",
+ [
+ ConnectionRefusedError,
+ async_subtensor.ssl.SSLError,
+ async_subtensor.TimeoutException,
+ ],
+)
+@pytest.mark.asyncio
+async def test_async_subtensor_aenter_connection_refused_error(
+ subtensor, mocker, error
+):
+ """Tests __aenter__ method handling all errors."""
+ # Preps
+ fake_async_substrate = mocker.AsyncMock(
+ autospec=async_subtensor.AsyncSubstrateInterface,
+ __aenter__=mocker.AsyncMock(side_effect=error),
+ )
+ mocker.patch.object(
+ async_subtensor, "AsyncSubstrateInterface", return_value=fake_async_substrate
+ )
+ # Call
+ subtensor = async_subtensor.AsyncSubtensor(network="local")
+
+ with pytest.raises(ConnectionError):
+ async with subtensor:
+ pass
+
+ # Asserts
+ fake_async_substrate.__aenter__.assert_called_once()
+
+
@pytest.mark.asyncio
async def test_encode_params(subtensor, mocker):
"""Tests encode_params happy path."""
@@ -351,7 +431,7 @@ async def test_get_delegates(subtensor, mocker, fake_hex_bytes_result, response)
@pytest.mark.parametrize(
- "fake_hex_bytes_result, response", [(None, []), ("zz001122", b"\xaa\xbb\xcc\xdd")]
+ "fake_hex_bytes_result, response", [(None, []), ("0x001122", b"\xaa\xbb\xcc\xdd")]
)
@pytest.mark.asyncio
async def test_get_stake_info_for_coldkey(
@@ -2150,3 +2230,436 @@ async def test_blocks_since_last_update_no_last_update(subtensor, mocker):
param_name="LastUpdate", netuid=fake_netuid
)
assert result is None
+
+
+@pytest.mark.asyncio
+async def test_transfer_success(subtensor, mocker):
+ """Tests transfer when the transfer is successful."""
+ # Preps
+ fake_wallet = mocker.Mock()
+ fake_destination = "destination_address"
+ fake_amount = 100.0
+ fake_transfer_all = False
+
+ mocked_transfer_extrinsic = mocker.AsyncMock(return_value=True)
+ mocker.patch.object(
+ async_subtensor, "transfer_extrinsic", mocked_transfer_extrinsic
+ )
+
+ mocked_balance_from_tao = mocker.Mock()
+ mocker.patch.object(
+ async_subtensor.Balance, "from_tao", return_value=mocked_balance_from_tao
+ )
+
+ # Call
+ result = await subtensor.transfer(
+ wallet=fake_wallet,
+ destination=fake_destination,
+ amount=fake_amount,
+ transfer_all=fake_transfer_all,
+ )
+
+ # Asserts
+ mocked_transfer_extrinsic.assert_awaited_once()
+ mocked_transfer_extrinsic.assert_called_once_with(
+ subtensor,
+ fake_wallet,
+ fake_destination,
+ mocked_balance_from_tao,
+ fake_transfer_all,
+ )
+ assert result == mocked_transfer_extrinsic.return_value
+
+
+@pytest.mark.asyncio
+async def test_register_success(subtensor, mocker):
+ """Tests register when there is enough balance and registration succeeds."""
+ # Preps
+ fake_wallet = mocker.Mock(autospec=async_subtensor.Wallet)
+ fake_wallet.coldkeypub.ss58_address = "wallet_address"
+ fake_netuid = 1
+ fake_block_hash = "block_hash"
+ fake_recycle_amount = 100
+ fake_balance = 200
+
+ mocked_get_block_hash = mocker.AsyncMock(return_value=fake_block_hash)
+ subtensor.get_block_hash = mocked_get_block_hash
+
+ mocked_get_hyperparameter = mocker.AsyncMock(return_value=str(fake_recycle_amount))
+ subtensor.get_hyperparameter = mocked_get_hyperparameter
+
+ mocked_get_balance = mocker.AsyncMock(
+ return_value={fake_wallet.coldkeypub.ss58_address: fake_balance}
+ )
+ subtensor.get_balance = mocked_get_balance
+
+ mocked_balance_from_rao = mocker.Mock(return_value=fake_recycle_amount)
+ mocker.patch.object(async_subtensor.Balance, "from_rao", mocked_balance_from_rao)
+
+ # Call
+ result = await subtensor.register(wallet=fake_wallet, netuid=fake_netuid)
+
+ # Asserts
+ mocked_get_block_hash.assert_called_once()
+ mocked_get_hyperparameter.assert_called_once_with(
+ param_name="Burn", netuid=fake_netuid, reuse_block=True
+ )
+ mocked_get_balance.assert_called_once_with(
+ fake_wallet.coldkeypub.ss58_address, block_hash=fake_block_hash
+ )
+ assert result is True
+
+
+@pytest.mark.asyncio
+async def test_register_insufficient_balance(subtensor, mocker):
+ """Tests register when the wallet balance is insufficient."""
+ # Preps
+ fake_wallet = mocker.Mock(autospec=async_subtensor.Wallet)
+ fake_wallet.coldkeypub.ss58_address = "wallet_address"
+ fake_netuid = 1
+ fake_block_hash = "block_hash"
+ fake_recycle_amount = 200
+ fake_balance = 100
+
+ mocked_get_block_hash = mocker.AsyncMock(return_value=fake_block_hash)
+ subtensor.get_block_hash = mocked_get_block_hash
+
+ mocked_get_hyperparameter = mocker.AsyncMock(return_value=str(fake_recycle_amount))
+ subtensor.get_hyperparameter = mocked_get_hyperparameter
+
+ mocked_get_balance = mocker.AsyncMock(
+ return_value={fake_wallet.coldkeypub.ss58_address: fake_balance}
+ )
+ subtensor.get_balance = mocked_get_balance
+
+ mocked_balance_from_rao = mocker.Mock(return_value=fake_recycle_amount)
+ mocker.patch.object(async_subtensor.Balance, "from_rao", mocked_balance_from_rao)
+
+ # Call
+ result = await subtensor.register(wallet=fake_wallet, netuid=fake_netuid)
+
+ # Asserts
+ mocked_get_block_hash.assert_called_once()
+ mocked_get_hyperparameter.assert_called_once_with(
+ param_name="Burn", netuid=fake_netuid, reuse_block=True
+ )
+ mocked_get_balance.assert_called_once_with(
+ fake_wallet.coldkeypub.ss58_address, block_hash=fake_block_hash
+ )
+ assert result is False
+
+
+@pytest.mark.asyncio
+async def test_register_balance_retrieval_error(subtensor, mocker):
+ """Tests register when there is an error retrieving the balance."""
+ # Preps
+ fake_wallet = mocker.Mock(autospec=async_subtensor.Wallet)
+ fake_wallet.coldkeypub.ss58_address = "wallet_address"
+ fake_netuid = 1
+ fake_block_hash = "block_hash"
+ fake_recycle_amount = 100
+
+ mocked_get_block_hash = mocker.AsyncMock(return_value=fake_block_hash)
+ subtensor.get_block_hash = mocked_get_block_hash
+
+ mocked_get_hyperparameter = mocker.AsyncMock(return_value=str(fake_recycle_amount))
+ subtensor.get_hyperparameter = mocked_get_hyperparameter
+
+ mocked_get_balance = mocker.AsyncMock(return_value={})
+ subtensor.get_balance = mocked_get_balance
+
+ # Call
+ result = await subtensor.register(wallet=fake_wallet, netuid=fake_netuid)
+
+ # Asserts
+ mocked_get_block_hash.assert_called_once()
+ mocked_get_hyperparameter.assert_called_once_with(
+ param_name="Burn", netuid=fake_netuid, reuse_block=True
+ )
+ mocked_get_balance.assert_called_once_with(
+ fake_wallet.coldkeypub.ss58_address, block_hash=fake_block_hash
+ )
+ assert result is False
+
+
+@pytest.mark.asyncio
+async def test_pow_register_success(subtensor, mocker):
+ """Tests pow_register when the registration is successful."""
+ # Preps
+ fake_wallet = mocker.Mock(autospec=async_subtensor.Wallet)
+ fake_netuid = 1
+ fake_processors = 4
+ fake_update_interval = 10
+ fake_output_in_place = True
+ fake_verbose = True
+ fake_use_cuda = False
+ fake_dev_id = 0
+ fake_threads_per_block = 128
+
+ mocked_register_extrinsic = mocker.AsyncMock(return_value=True)
+ mocker.patch.object(
+ async_subtensor, "register_extrinsic", mocked_register_extrinsic
+ )
+
+ # Call
+ result = await subtensor.pow_register(
+ wallet=fake_wallet,
+ netuid=fake_netuid,
+ processors=fake_processors,
+ update_interval=fake_update_interval,
+ output_in_place=fake_output_in_place,
+ verbose=fake_verbose,
+ use_cuda=fake_use_cuda,
+ dev_id=fake_dev_id,
+ threads_per_block=fake_threads_per_block,
+ )
+
+ # Asserts
+ mocked_register_extrinsic.assert_awaited_once()
+ mocked_register_extrinsic.assert_called_once_with(
+ subtensor=subtensor,
+ wallet=fake_wallet,
+ netuid=fake_netuid,
+ tpb=fake_threads_per_block,
+ update_interval=fake_update_interval,
+ num_processes=fake_processors,
+ cuda=fake_use_cuda,
+ dev_id=fake_dev_id,
+ output_in_place=fake_output_in_place,
+ log_verbose=fake_verbose,
+ )
+ assert result == mocked_register_extrinsic.return_value
+
+
+@pytest.mark.asyncio
+async def test_set_weights_success(subtensor, mocker):
+ """Tests set_weights with successful weight setting on the first try."""
+ # Preps
+ fake_wallet = mocker.Mock(autospec=async_subtensor.Wallet)
+ fake_netuid = 1
+ fake_uids = [1, 2, 3]
+ fake_weights = [0.3, 0.5, 0.2]
+ max_retries = 1
+
+ mocked_get_uid_for_hotkey_on_subnet = mocker.AsyncMock(return_value=fake_netuid)
+ subtensor.get_uid_for_hotkey_on_subnet = mocked_get_uid_for_hotkey_on_subnet
+
+ mocked_blocks_since_last_update = mocker.AsyncMock(return_value=2)
+ subtensor.blocks_since_last_update = mocked_blocks_since_last_update
+
+ mocked_weights_rate_limit = mocker.AsyncMock(return_value=1)
+ subtensor.weights_rate_limit = mocked_weights_rate_limit
+
+ mocked_set_weights_extrinsic = mocker.AsyncMock(return_value=(True, "Success"))
+ mocker.patch.object(
+ async_subtensor, "set_weights_extrinsic", mocked_set_weights_extrinsic
+ )
+
+ # Call
+ result, message = await subtensor.set_weights(
+ wallet=fake_wallet,
+ netuid=fake_netuid,
+ uids=fake_uids,
+ weights=fake_weights,
+ max_retries=max_retries,
+ )
+
+ # Asserts
+ mocked_get_uid_for_hotkey_on_subnet.assert_called_once_with(
+ fake_wallet.hotkey.ss58_address, fake_netuid
+ )
+ mocked_blocks_since_last_update.assert_called_once_with(
+ fake_netuid, mocked_get_uid_for_hotkey_on_subnet.return_value
+ )
+ mocked_weights_rate_limit.assert_called_once_with(fake_netuid)
+ mocked_set_weights_extrinsic.assert_called_once_with(
+ subtensor=subtensor,
+ wallet=fake_wallet,
+ netuid=fake_netuid,
+ uids=fake_uids,
+ version_key=async_subtensor.version_as_int,
+ wait_for_finalization=False,
+ wait_for_inclusion=False,
+ weights=fake_weights,
+ )
+ mocked_weights_rate_limit.assert_called_once_with(fake_netuid)
+ assert result is True
+ assert message == "Success"
+
+
+@pytest.mark.asyncio
+async def test_set_weights_with_exception(subtensor, mocker):
+ """Tests set_weights when set_weights_extrinsic raises an exception."""
+ # Preps
+ fake_wallet = mocker.Mock(autospec=async_subtensor.Wallet)
+ fake_netuid = 1
+ fake_uids = [1, 2, 3]
+ fake_weights = [0.3, 0.5, 0.2]
+ fake_uid = 10
+ max_retries = 1
+
+ mocked_get_uid_for_hotkey_on_subnet = mocker.AsyncMock(return_value=fake_uid)
+ subtensor.get_uid_for_hotkey_on_subnet = mocked_get_uid_for_hotkey_on_subnet
+
+ mocked_blocks_since_last_update = mocker.AsyncMock(return_value=10)
+ subtensor.blocks_since_last_update = mocked_blocks_since_last_update
+
+ mocked_weights_rate_limit = mocker.AsyncMock(return_value=5)
+ subtensor.weights_rate_limit = mocked_weights_rate_limit
+
+ mocked_set_weights_extrinsic = mocker.AsyncMock(
+ side_effect=Exception("Test exception")
+ )
+ mocker.patch.object(
+ async_subtensor, "set_weights_extrinsic", mocked_set_weights_extrinsic
+ )
+
+ # Call
+ result, message = await subtensor.set_weights(
+ wallet=fake_wallet,
+ netuid=fake_netuid,
+ uids=fake_uids,
+ weights=fake_weights,
+ max_retries=max_retries,
+ )
+
+ # Asserts
+ assert mocked_get_uid_for_hotkey_on_subnet.call_count == 1
+ assert mocked_blocks_since_last_update.call_count == 1
+ assert mocked_weights_rate_limit.call_count == 1
+ assert mocked_set_weights_extrinsic.call_count == max_retries
+ assert result is False
+ assert message == "No attempt made. Perhaps it is too soon to set weights!"
+
+
+@pytest.mark.asyncio
+async def test_root_set_weights_success(subtensor, mocker):
+ """Tests root_set_weights when the setting of weights is successful."""
+ # Preps
+ fake_wallet = mocker.Mock(autospec=async_subtensor.Wallet)
+ fake_netuids = [1, 2, 3]
+ fake_weights = [0.3, 0.5, 0.2]
+
+ mocked_set_root_weights_extrinsic = mocker.AsyncMock()
+ mocker.patch.object(
+ async_subtensor, "set_root_weights_extrinsic", mocked_set_root_weights_extrinsic
+ )
+
+ mocked_np_array_netuids = mocker.Mock(autospec=async_subtensor.np.ndarray)
+ mocked_np_array_weights = mocker.Mock(autospec=async_subtensor.np.ndarray)
+ mocker.patch.object(
+ async_subtensor.np,
+ "array",
+ side_effect=[mocked_np_array_netuids, mocked_np_array_weights],
+ )
+
+ # Call
+ result = await subtensor.root_set_weights(
+ wallet=fake_wallet,
+ netuids=fake_netuids,
+ weights=fake_weights,
+ )
+
+ # Asserts
+ mocked_set_root_weights_extrinsic.assert_awaited_once()
+ mocked_set_root_weights_extrinsic.assert_called_once_with(
+ subtensor=subtensor,
+ wallet=fake_wallet,
+ netuids=mocked_np_array_netuids,
+ weights=mocked_np_array_weights,
+ version_key=0,
+ wait_for_finalization=True,
+ wait_for_inclusion=True,
+ )
+ assert result == mocked_set_root_weights_extrinsic.return_value
+
+
+@pytest.mark.asyncio
+async def test_commit_weights_success(subtensor, mocker):
+ """Tests commit_weights when the weights are committed successfully."""
+ # Preps
+ fake_wallet = mocker.Mock(autospec=async_subtensor.Wallet)
+ fake_netuid = 1
+ fake_salt = [12345, 67890]
+ fake_uids = [1, 2, 3]
+ fake_weights = [100, 200, 300]
+ max_retries = 3
+
+ mocked_generate_weight_hash = mocker.Mock(return_value="fake_commit_hash")
+ mocker.patch.object(
+ async_subtensor, "generate_weight_hash", mocked_generate_weight_hash
+ )
+
+ mocked_commit_weights_extrinsic = mocker.AsyncMock(return_value=(True, "Success"))
+ mocker.patch.object(
+ async_subtensor, "commit_weights_extrinsic", mocked_commit_weights_extrinsic
+ )
+
+ # Call
+ result, message = await subtensor.commit_weights(
+ wallet=fake_wallet,
+ netuid=fake_netuid,
+ salt=fake_salt,
+ uids=fake_uids,
+ weights=fake_weights,
+ max_retries=max_retries,
+ )
+
+ # Asserts
+ mocked_generate_weight_hash.assert_called_once_with(
+ address=fake_wallet.hotkey.ss58_address,
+ netuid=fake_netuid,
+ uids=fake_uids,
+ values=fake_weights,
+ salt=fake_salt,
+ version_key=async_subtensor.version_as_int,
+ )
+ mocked_commit_weights_extrinsic.assert_called_once_with(
+ subtensor=subtensor,
+ wallet=fake_wallet,
+ netuid=fake_netuid,
+ commit_hash="fake_commit_hash",
+ wait_for_inclusion=False,
+ wait_for_finalization=False,
+ )
+ assert result is True
+ assert message == "Success"
+
+
+@pytest.mark.asyncio
+async def test_commit_weights_with_exception(subtensor, mocker):
+ """Tests commit_weights when an exception is raised during weight commitment."""
+ # Preps
+ fake_wallet = mocker.Mock(autospec=async_subtensor.Wallet)
+ fake_netuid = 1
+ fake_salt = [12345, 67890]
+ fake_uids = [1, 2, 3]
+ fake_weights = [100, 200, 300]
+ max_retries = 1
+
+ mocked_generate_weight_hash = mocker.Mock(return_value="fake_commit_hash")
+ mocker.patch.object(
+ async_subtensor, "generate_weight_hash", mocked_generate_weight_hash
+ )
+
+ mocked_commit_weights_extrinsic = mocker.AsyncMock(
+ side_effect=Exception("Test exception")
+ )
+ mocker.patch.object(
+ async_subtensor, "commit_weights_extrinsic", mocked_commit_weights_extrinsic
+ )
+
+ # Call
+ result, message = await subtensor.commit_weights(
+ wallet=fake_wallet,
+ netuid=fake_netuid,
+ salt=fake_salt,
+ uids=fake_uids,
+ weights=fake_weights,
+ max_retries=max_retries,
+ )
+
+ # Asserts
+ assert mocked_commit_weights_extrinsic.call_count == max_retries
+ assert result is False
+ assert "No attempt made. Perhaps it is too soon to commit weights!" in message
diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py
index e3f573c67f..602a3027d8 100644
--- a/tests/unit_tests/test_subtensor.py
+++ b/tests/unit_tests/test_subtensor.py
@@ -2022,12 +2022,12 @@ def test_get_all_subnets_info_success(mocker, subtensor):
"""Test get_all_subnets_info returns correct data when subnet information is found."""
# Prep
block = 123
- subnet_data = [1, 2, 3] # Mocked response data
mocker.patch.object(
subtensor.substrate, "get_block_hash", return_value="mock_block_hash"
)
- mock_response = {"result": subnet_data}
- mocker.patch.object(subtensor.substrate, "rpc_request", return_value=mock_response)
+ hex_bytes_result = "0x010203"
+ bytes_result = bytes.fromhex(hex_bytes_result[2:])
+ mocker.patch.object(subtensor, "query_runtime_api", return_value=hex_bytes_result)
mocker.patch.object(
subtensor_module.SubnetInfo,
"list_from_vec_u8",
@@ -2035,14 +2035,13 @@ def test_get_all_subnets_info_success(mocker, subtensor):
)
# Call
- result = subtensor.get_all_subnets_info(block)
+ subtensor.get_all_subnets_info(block)
# Asserts
- subtensor.substrate.get_block_hash.assert_called_once_with(block)
- subtensor.substrate.rpc_request.assert_called_once_with(
- method="subnetInfo_getSubnetsInfo", params=["mock_block_hash"]
+ subtensor.query_runtime_api.assert_called_once_with(
+ "SubnetInfoRuntimeApi", "get_subnets_info", params=[], block=block
)
- subtensor_module.SubnetInfo.list_from_vec_u8.assert_called_once_with(subnet_data)
+ subtensor_module.SubnetInfo.list_from_vec_u8.assert_called_once_with(bytes_result)
@pytest.mark.parametrize("result_", [[], None])
@@ -2053,18 +2052,17 @@ def test_get_all_subnets_info_no_data(mocker, subtensor, result_):
mocker.patch.object(
subtensor.substrate, "get_block_hash", return_value="mock_block_hash"
)
- mock_response = {"result": result_}
- mocker.patch.object(subtensor.substrate, "rpc_request", return_value=mock_response)
mocker.patch.object(subtensor_module.SubnetInfo, "list_from_vec_u8")
+ mocker.patch.object(subtensor, "query_runtime_api", return_value=result_)
+
# Call
result = subtensor.get_all_subnets_info(block)
# Asserts
assert result == []
- subtensor.substrate.get_block_hash.assert_called_once_with(block)
- subtensor.substrate.rpc_request.assert_called_once_with(
- method="subnetInfo_getSubnetsInfo", params=["mock_block_hash"]
+ subtensor.query_runtime_api.assert_called_once_with(
+ "SubnetInfoRuntimeApi", "get_subnets_info", params=[], block=block
)
subtensor_module.SubnetInfo.list_from_vec_u8.assert_not_called()
diff --git a/tests/unit_tests/utils/test_utils.py b/tests/unit_tests/utils/test_utils.py
index a01b42f31d..eda2eeb100 100644
--- a/tests/unit_tests/utils/test_utils.py
+++ b/tests/unit_tests/utils/test_utils.py
@@ -223,3 +223,15 @@ def test_unlock_key_errors(mocker, side_effect, response):
result = utils.unlock_key(wallet=mock_wallet)
assert result == response
+
+
+@pytest.mark.parametrize(
+ "hex_str, response",
+ [
+ ("5461796c6f72205377696674", b"Taylor Swift"),
+ ("0x5461796c6f72205377696674", b"Taylor Swift"),
+ ],
+)
+def test_hex_to_bytes(hex_str, response):
+ result = utils.hex_to_bytes(hex_str)
+ assert result == response