Skip to content

Commit a555e5e

Browse files
committed
Merge remote-tracking branch 'origin/staging' into process_weights_for_netuid
2 parents e0ea5ae + a625991 commit a555e5e

24 files changed

+824
-98
lines changed

.circleci/config.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,9 @@ workflows:
290290
- check_compatibility:
291291
python_version: "3.12"
292292
name: check-compatibility-3.12
293+
- check_compatibility:
294+
python_version: "3.13"
295+
name: check-compatibility-3.13
293296

294297

295298
pr-requirements:
@@ -302,7 +305,7 @@ workflows:
302305
- build-and-test:
303306
matrix:
304307
parameters:
305-
python-version: [ "3.9.13", "3.10.6", "3.11.4", "3.12.7" ]
308+
python-version: [ "3.9.13", "3.10.6", "3.11.4", "3.12.7"]
306309
requires:
307310
- check-if-pr-is-draft
308311
- unit-tests-all-python-versions:
@@ -311,7 +314,7 @@ workflows:
311314
- lint-and-type-check:
312315
matrix:
313316
parameters:
314-
python-version: [ "3.9.13", "3.10.6", "3.11.4", "3.12.7" ]
317+
python-version: [ "3.9.13", "3.10.6", "3.11.4", "3.12.7"]
315318
requires:
316319
- check-if-pr-is-draft
317320
#- coveralls:

bittensor/core/async_subtensor.py

Lines changed: 195 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import asyncio
22
import copy
3-
from datetime import datetime, timezone
43
import ssl
4+
from datetime import datetime, timezone
55
from functools import partial
66
from typing import Optional, Any, Union, Iterable, TYPE_CHECKING
77

88
import asyncstdlib as a
99
import numpy as np
1010
import scalecodec
1111
from async_substrate_interface import AsyncSubstrateInterface
12+
from bittensor_commit_reveal import get_encrypted_commitment
1213
from bittensor_wallet.utils import SS58_FORMAT
1314
from numpy.typing import NDArray
1415
from scalecodec import GenericCall
@@ -29,21 +30,25 @@
2930
)
3031
from bittensor.core.chain_data.chain_identity import ChainIdentity
3132
from bittensor.core.chain_data.delegate_info import DelegatedInfo
32-
from bittensor.core.chain_data.utils import decode_metadata
33+
from bittensor.core.chain_data.utils import (
34+
decode_metadata,
35+
decode_revealed_commitment,
36+
decode_revealed_commitment_with_hotkey,
37+
)
3338
from bittensor.core.config import Config
3439
from bittensor.core.errors import ChainError, SubstrateRequestException
3540
from bittensor.core.extrinsics.asyncex.commit_reveal import commit_reveal_v3_extrinsic
41+
from bittensor.core.extrinsics.asyncex.move_stake import (
42+
transfer_stake_extrinsic,
43+
swap_stake_extrinsic,
44+
move_stake_extrinsic,
45+
)
3646
from bittensor.core.extrinsics.asyncex.registration import (
3747
burned_register_extrinsic,
3848
register_extrinsic,
3949
register_subnet_extrinsic,
4050
set_subnet_identity_extrinsic,
4151
)
42-
from bittensor.core.extrinsics.asyncex.move_stake import (
43-
transfer_stake_extrinsic,
44-
swap_stake_extrinsic,
45-
move_stake_extrinsic,
46-
)
4752
from bittensor.core.extrinsics.asyncex.root import (
4853
set_root_weights_extrinsic,
4954
root_register_extrinsic,
@@ -79,6 +84,7 @@
7984
decode_hex_identity_dict,
8085
float_to_u64,
8186
format_error_message,
87+
is_valid_ss58_address,
8288
torch,
8389
u16_normalized_float,
8490
u64_normalized_float,
@@ -1059,6 +1065,115 @@ async def get_all_commitments(
10591065
result[decode_account_id(id_[0])] = decode_metadata(value)
10601066
return result
10611067

1068+
async def get_revealed_commitment_by_hotkey(
1069+
self,
1070+
netuid: int,
1071+
hotkey_ss58_address: Optional[str] = None,
1072+
block: Optional[int] = None,
1073+
block_hash: Optional[str] = None,
1074+
reuse_block: bool = False,
1075+
) -> Optional[tuple[tuple[int, str], ...]]:
1076+
"""Returns hotkey related revealed commitment for a given netuid.
1077+
1078+
Arguments:
1079+
netuid (int): The unique identifier of the subnetwork.
1080+
block (Optional[int]): The block number to retrieve the commitment from. Default is ``None``.
1081+
hotkey_ss58_address (str): The ss58 address of the committee member.
1082+
block_hash (Optional[str]): The hash of the block to retrieve the subnet unique identifiers from.
1083+
reuse_block (bool): Whether to reuse the last-used block hash.
1084+
1085+
Returns:
1086+
result (tuple[int, str): A tuple of reveal block and commitment message.
1087+
"""
1088+
if not is_valid_ss58_address(address=hotkey_ss58_address):
1089+
raise ValueError(f"Invalid ss58 address {hotkey_ss58_address} provided.")
1090+
1091+
query = await self.query_module(
1092+
module="Commitments",
1093+
name="RevealedCommitments",
1094+
params=[netuid, hotkey_ss58_address],
1095+
block=block,
1096+
block_hash=block_hash,
1097+
reuse_block=reuse_block,
1098+
)
1099+
if query is None:
1100+
return None
1101+
return tuple(decode_revealed_commitment(pair) for pair in query)
1102+
1103+
async def get_revealed_commitment(
1104+
self,
1105+
netuid: int,
1106+
uid: int,
1107+
block: Optional[int] = None,
1108+
) -> Optional[tuple[tuple[int, str], ...]]:
1109+
"""Returns uid related revealed commitment for a given netuid.
1110+
1111+
Arguments:
1112+
netuid (int): The unique identifier of the subnetwork.
1113+
uid (int): The neuron uid to retrieve the commitment from.
1114+
block (Optional[int]): The block number to retrieve the commitment from. Default is ``None``.
1115+
1116+
Returns:
1117+
result (Optional[tuple[int, str]]: A tuple of reveal block and commitment message.
1118+
1119+
Example of result:
1120+
( (12, "Alice message 1"), (152, "Alice message 2") )
1121+
( (12, "Bob message 1"), (147, "Bob message 2") )
1122+
"""
1123+
try:
1124+
meta_info = await self.get_metagraph_info(netuid, block=block)
1125+
if meta_info:
1126+
hotkey_ss58_address = meta_info.hotkeys[uid]
1127+
else:
1128+
raise ValueError(f"Subnet with netuid {netuid} does not exist.")
1129+
except IndexError:
1130+
raise ValueError(f"Subnet {netuid} does not have a neuron with uid {uid}.")
1131+
1132+
return await self.get_revealed_commitment_by_hotkey(
1133+
netuid=netuid, hotkey_ss58_address=hotkey_ss58_address, block=block
1134+
)
1135+
1136+
async def get_all_revealed_commitments(
1137+
self,
1138+
netuid: int,
1139+
block: Optional[int] = None,
1140+
block_hash: Optional[str] = None,
1141+
reuse_block: bool = False,
1142+
) -> dict[str, tuple[tuple[int, str], ...]]:
1143+
"""Returns all revealed commitments for a given netuid.
1144+
1145+
Arguments:
1146+
netuid (int): The unique identifier of the subnetwork.
1147+
block (Optional[int]): The block number to retrieve the commitment from. Default is ``None``.
1148+
block_hash (Optional[str]): The hash of the block to retrieve the subnet unique identifiers from.
1149+
reuse_block (bool): Whether to reuse the last-used block hash.
1150+
1151+
Returns:
1152+
result (dict): A dictionary of all revealed commitments in view {ss58_address: (reveal block, commitment message)}.
1153+
1154+
Example of result:
1155+
{
1156+
"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY": ( (12, "Alice message 1"), (152, "Alice message 2") ),
1157+
"5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty": ( (12, "Bob message 1"), (147, "Bob message 2") ),
1158+
}
1159+
"""
1160+
query = await self.query_map(
1161+
module="Commitments",
1162+
name="RevealedCommitments",
1163+
params=[netuid],
1164+
block=block,
1165+
block_hash=block_hash,
1166+
reuse_block=reuse_block,
1167+
)
1168+
1169+
result = {}
1170+
async for pair in query:
1171+
hotkey_ss58_address, commitment_message = (
1172+
decode_revealed_commitment_with_hotkey(pair)
1173+
)
1174+
result[hotkey_ss58_address] = commitment_message
1175+
return result
1176+
10621177
async def get_current_weight_commit_info(
10631178
self,
10641179
netuid: int,
@@ -1572,6 +1687,36 @@ async def get_neuron_for_pubkey_and_subnet(
15721687
reuse_block=reuse_block,
15731688
)
15741689

1690+
async def get_owned_hotkeys(
1691+
self,
1692+
coldkey_ss58: str,
1693+
block: Optional[int] = None,
1694+
block_hash: Optional[str] = None,
1695+
reuse_block: bool = False,
1696+
) -> list[str]:
1697+
"""
1698+
Retrieves all hotkeys owned by a specific coldkey address.
1699+
1700+
Args:
1701+
coldkey_ss58 (str): The SS58 address of the coldkey to query.
1702+
block (int): The blockchain block number for the query.
1703+
block_hash (str): The hash of the blockchain block number for the query.
1704+
reuse_block (bool): Whether to reuse the last-used blockchain block hash.
1705+
1706+
Returns:
1707+
list[str]: A list of hotkey SS58 addresses owned by the coldkey.
1708+
"""
1709+
block_hash = await self.determine_block_hash(block, block_hash, reuse_block)
1710+
owned_hotkeys = await self.substrate.query(
1711+
module="SubtensorModule",
1712+
storage_function="OwnedHotkeys",
1713+
params=[coldkey_ss58],
1714+
block_hash=block_hash,
1715+
reuse_block_hash=reuse_block,
1716+
)
1717+
1718+
return [decode_account_id(hotkey[0]) for hotkey in owned_hotkeys or []]
1719+
15751720
async def get_stake(
15761721
self,
15771722
coldkey_ss58: str,
@@ -2620,6 +2765,46 @@ async def recycle(
26202765
)
26212766
return None if call is None else Balance.from_rao(int(call))
26222767

2768+
async def set_reveal_commitment(
2769+
self,
2770+
wallet,
2771+
netuid: int,
2772+
data: str,
2773+
blocks_until_reveal: int = 360,
2774+
block_time: Union[int, float] = 12,
2775+
) -> tuple[bool, int]:
2776+
"""
2777+
Commits arbitrary data to the Bittensor network by publishing metadata.
2778+
2779+
Arguments:
2780+
wallet (bittensor_wallet.Wallet): The wallet associated with the neuron committing the data.
2781+
netuid (int): The unique identifier of the subnetwork.
2782+
data (str): The data to be committed to the network.
2783+
blocks_until_reveal (int): The number of blocks from now after which the data will be revealed. Defaults to `360`.
2784+
Then amount of blocks in one epoch.
2785+
block_time (Union[int, float]): The number of seconds between each block. Defaults to `12`.
2786+
2787+
Returns:
2788+
bool: `True` if the commitment was successful, `False` otherwise.
2789+
2790+
Note: A commitment can be set once per subnet epoch and is reset at the next epoch in the chain automatically.
2791+
"""
2792+
2793+
encrypted, reveal_round = get_encrypted_commitment(
2794+
data, blocks_until_reveal, block_time
2795+
)
2796+
2797+
# increase reveal_round in return + 1 because we want to fetch data from the chain after that round was revealed
2798+
# and stored.
2799+
data_ = {"encrypted": encrypted, "reveal_round": reveal_round}
2800+
return await publish_metadata(
2801+
subtensor=self,
2802+
wallet=wallet,
2803+
netuid=netuid,
2804+
data_type=f"TimelockEncrypted",
2805+
data=data_,
2806+
), reveal_round
2807+
26232808
async def subnet(
26242809
self,
26252810
netuid: int,
@@ -3627,6 +3812,7 @@ async def set_weights(
36273812
wait_for_inclusion: bool = False,
36283813
wait_for_finalization: bool = False,
36293814
max_retries: int = 5,
3815+
block_time: float = 12.0,
36303816
):
36313817
"""
36323818
Sets the inter-neuronal weights for the specified neuron. This process involves specifying the influence or
@@ -3646,6 +3832,7 @@ async def set_weights(
36463832
wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. Default is
36473833
``False``.
36483834
max_retries (int): The number of maximum attempts to set weights. Default is ``5``.
3835+
block_time (float): The amount of seconds for block duration. Default is 12.0 seconds.
36493836
36503837
Returns:
36513838
tuple[bool, str]: ``True`` if the setting of weights is successful, False otherwise. And `msg`, a string
@@ -3694,6 +3881,7 @@ async def _blocks_weight_limit() -> bool:
36943881
version_key=version_key,
36953882
wait_for_inclusion=wait_for_inclusion,
36963883
wait_for_finalization=wait_for_finalization,
3884+
block_time=block_time,
36973885
)
36983886
retries += 1
36993887
return success, message

bittensor/core/axon.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -976,7 +976,9 @@ async def default_verify(self, synapse: "Synapse"):
976976
):
977977
raise Exception("Nonce is too old, a newer one was last processed")
978978

979-
if not keypair.verify(message, synapse.dendrite.signature):
979+
if synapse.dendrite.signature and not keypair.verify(
980+
message, synapse.dendrite.signature
981+
):
980982
raise Exception(
981983
f"Signature mismatch with {message} and {synapse.dendrite.signature}"
982984
)

bittensor/core/chain_data/utils.py

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Chain data helper functions and data."""
22

33
from enum import Enum
4-
from typing import Optional, Union
4+
from typing import Optional, Union, TYPE_CHECKING
55

66
from scalecodec.base import RuntimeConfiguration, ScaleBytes
77
from scalecodec.type_registry import load_type_registry_preset
@@ -10,6 +10,9 @@
1010
from bittensor.core.settings import SS58_FORMAT
1111
from bittensor.utils.balance import Balance
1212

13+
if TYPE_CHECKING:
14+
from async_substrate_interface.sync_substrate import QueryMapResult
15+
1316

1417
class ChainDataType(Enum):
1518
NeuronInfo = 1
@@ -135,3 +138,49 @@ def decode_metadata(metadata: dict) -> str:
135138
commitment = metadata["info"]["fields"][0][0]
136139
bytes_tuple = commitment[next(iter(commitment.keys()))][0]
137140
return bytes(bytes_tuple).decode()
141+
142+
143+
def decode_revealed_commitment(encoded_data) -> tuple[int, str]:
144+
"""
145+
Decode the revealed commitment data from the given input if it is not None.
146+
147+
Arguments:
148+
encoded_data (tuple[bytes, int]): A tuple containing the revealed message and the block number.
149+
150+
Returns:
151+
tuple[int, str]: A tuple containing the revealed block number and decoded commitment message.
152+
"""
153+
154+
def scale_decode_offset(data: bytes) -> int:
155+
"""Decodes the scale offset from a given byte data sequence."""
156+
first_byte = data[0]
157+
mode = first_byte & 0b11
158+
if mode == 0:
159+
return 1
160+
elif mode == 1:
161+
return 2
162+
else:
163+
return 4
164+
165+
com_bytes, revealed_block = encoded_data
166+
offset = scale_decode_offset(com_bytes)
167+
168+
revealed_commitment = bytes(com_bytes[offset:]).decode("utf-8", errors="ignore")
169+
return revealed_block, revealed_commitment
170+
171+
172+
def decode_revealed_commitment_with_hotkey(
173+
encoded_data: "QueryMapResult",
174+
) -> tuple[str, tuple[tuple[int, str], ...]]:
175+
"""
176+
Decode revealed commitment using a hotkey.
177+
178+
Returns:
179+
tuple[str, tuple[tuple[int, str], ...]]: A tuple containing the hotkey (ss58 address) and a tuple of block
180+
numbers and their corresponding revealed commitments.
181+
"""
182+
key, data = encoded_data
183+
184+
ss58_address = decode_account_id(next(iter(key)))
185+
block_data = tuple(decode_revealed_commitment(p) for p in data.value)
186+
return ss58_address, block_data

0 commit comments

Comments
 (0)