Skip to content

Commit 3e17001

Browse files
authored
Merge branch 'staging' into f
2 parents c2b3597 + 6b8a88a commit 3e17001

20 files changed

+781
-48
lines changed

bittensor/core/async_subtensor.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
publish_metadata,
5858
get_metadata,
5959
)
60+
from bittensor.core.extrinsics.asyncex.start_call import start_call_extrinsic
6061
from bittensor.core.extrinsics.asyncex.serving import serve_axon_extrinsic
6162
from bittensor.core.extrinsics.asyncex.staking import (
6263
add_stake_extrinsic,
@@ -3181,7 +3182,7 @@ async def sign_and_send_extrinsic(
31813182
return True, ""
31823183

31833184
if raise_error:
3184-
raise ChainError.from_error(response.error_message)
3185+
raise ChainError.from_error(await response.error_message)
31853186

31863187
return False, format_error_message(await response.error_message)
31873188

@@ -3986,6 +3987,36 @@ async def serve_axon(
39863987
certificate=certificate,
39873988
)
39883989

3990+
async def start_call(
3991+
self,
3992+
wallet: "Wallet",
3993+
netuid: int,
3994+
wait_for_inclusion: bool = True,
3995+
wait_for_finalization: bool = False,
3996+
) -> tuple[bool, str]:
3997+
"""
3998+
Submits a start_call extrinsic to the blockchain, to trigger the start call process for a subnet (used to start a
3999+
new subnet's emission mechanism).
4000+
4001+
Args:
4002+
wallet (Wallet): The wallet used to sign the extrinsic (must be unlocked).
4003+
netuid (int): The UID of the target subnet for which the call is being initiated.
4004+
wait_for_inclusion (bool, optional): Whether to wait for the extrinsic to be included in a block. Defaults to True.
4005+
wait_for_finalization (bool, optional): Whether to wait for finalization of the extrinsic. Defaults to False.
4006+
4007+
Returns:
4008+
Tuple[bool, str]:
4009+
- True and a success message if the extrinsic is successfully submitted or processed.
4010+
- False and an error message if the submission fails or the wallet cannot be unlocked.
4011+
"""
4012+
return await start_call_extrinsic(
4013+
subtensor=self,
4014+
wallet=wallet,
4015+
netuid=netuid,
4016+
wait_for_inclusion=wait_for_inclusion,
4017+
wait_for_finalization=wait_for_finalization,
4018+
)
4019+
39894020
async def swap_stake(
39904021
self,
39914022
wallet: "Wallet",

bittensor/core/chain_data/metagraph_info.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def _chr_str(codes: tuple[int]) -> str:
2525
def process_nested(data: Union[tuple, dict], chr_transform):
2626
"""Processes nested data structures by applying a transformation function to their elements."""
2727
if isinstance(data, (list, tuple)):
28-
if len(data) > 0 and isinstance(data[0], dict):
28+
if len(data) > 0:
2929
return [
3030
{k: chr_transform(v) for k, v in item.items()}
3131
if item is not None

bittensor/core/extrinsics/asyncex/registration.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,7 @@ async def set_subnet_identity_extrinsic(
498498
},
499499
)
500500

501-
response = await subtensor.substrate.submit_extrinsic(
501+
success, error_message = await subtensor.sign_and_send_extrinsic(
502502
call=call,
503503
wallet=wallet,
504504
wait_for_inclusion=wait_for_inclusion,
@@ -508,14 +508,13 @@ async def set_subnet_identity_extrinsic(
508508
if not wait_for_finalization and not wait_for_inclusion:
509509
return True, f"Identities for subnet {netuid} are sent to the chain."
510510

511-
if await response.is_success:
511+
if success:
512512
logging.success(
513513
f":white_heavy_check_mark: [green]Identities for subnet[/green] [blue]{netuid}[/blue] [green]are set.[/green]"
514514
)
515515
return True, f"Identities for subnet {netuid} are set."
516-
else:
517-
error_message = await response.error_message
518-
logging.error(
519-
f":cross_mark: Failed to set identity for subnet [blue]{netuid}[/blue]: {error_message}"
520-
)
521-
return False, f"Failed to set identity for subnet {netuid}: {error_message}"
516+
517+
logging.error(
518+
f":cross_mark: Failed to set identity for subnet [blue]{netuid}[/blue]: {error_message}"
519+
)
520+
return False, f"Failed to set identity for subnet {netuid}: {error_message}"
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
from typing import TYPE_CHECKING
2+
3+
from bittensor.utils import unlock_key
4+
from bittensor.utils.btlogging import logging
5+
6+
if TYPE_CHECKING:
7+
from bittensor_wallet import Wallet
8+
from bittensor.core.async_subtensor import AsyncSubtensor
9+
10+
11+
async def start_call_extrinsic(
12+
subtensor: "AsyncSubtensor",
13+
wallet: "Wallet",
14+
netuid: int,
15+
wait_for_inclusion: bool = True,
16+
wait_for_finalization: bool = False,
17+
) -> tuple[bool, str]:
18+
"""
19+
Submits a start_call extrinsic to the blockchain, to trigger the start call process for a subnet (used to start a
20+
new subnet's emission mechanism).
21+
22+
Args:
23+
subtensor (Subtensor): The Subtensor client instance used for blockchain interaction.
24+
wallet (Wallet): The wallet used to sign the extrinsic (must be unlocked).
25+
netuid (int): The UID of the target subnet for which the call is being initiated.
26+
wait_for_inclusion (bool, optional): Whether to wait for the extrinsic to be included in a block. Defaults to True.
27+
wait_for_finalization (bool, optional): Whether to wait for finalization of the extrinsic. Defaults to False.
28+
29+
Returns:
30+
Tuple[bool, str]:
31+
- True and a success message if the extrinsic is successfully submitted or processed.
32+
- False and an error message if the submission fails or the wallet cannot be unlocked.
33+
"""
34+
if not (unlock := unlock_key(wallet)).success:
35+
logging.error(unlock.message)
36+
return False, unlock.message
37+
38+
async with subtensor.substrate as substrate:
39+
start_call = await substrate.compose_call(
40+
call_module="SubtensorModule",
41+
call_function="start_call",
42+
call_params={"netuid": netuid},
43+
)
44+
signed_ext = await substrate.create_signed_extrinsic(
45+
call=start_call,
46+
keypair=wallet.coldkey,
47+
)
48+
49+
response = await substrate.submit_extrinsic(
50+
extrinsic=signed_ext,
51+
wait_for_inclusion=wait_for_inclusion,
52+
wait_for_finalization=wait_for_finalization,
53+
)
54+
55+
if not wait_for_finalization and not wait_for_inclusion:
56+
return True, "Not waiting for finalization or inclusion."
57+
58+
if await response.is_success:
59+
return True, "Success with `start_call` response."
60+
61+
return False, await response.error_message
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from typing import TYPE_CHECKING
2+
3+
from bittensor.utils import unlock_key
4+
from bittensor.utils.btlogging import logging
5+
6+
if TYPE_CHECKING:
7+
from bittensor_wallet import Wallet
8+
from bittensor.core.subtensor import Subtensor
9+
10+
11+
def start_call_extrinsic(
12+
subtensor: "Subtensor",
13+
wallet: "Wallet",
14+
netuid: int,
15+
wait_for_inclusion: bool = True,
16+
wait_for_finalization: bool = False,
17+
) -> tuple[bool, str]:
18+
"""
19+
Submits a start_call extrinsic to the blockchain, to trigger the start call process for a subnet (used to start a
20+
new subnet's emission mechanism).
21+
22+
Args:
23+
subtensor (Subtensor): The Subtensor client instance used for blockchain interaction.
24+
wallet (Wallet): The wallet used to sign the extrinsic (must be unlocked).
25+
netuid (int): The UID of the target subnet for which the call is being initiated.
26+
wait_for_inclusion (bool, optional): Whether to wait for the extrinsic to be included in a block. Defaults to True.
27+
wait_for_finalization (bool, optional): Whether to wait for finalization of the extrinsic. Defaults to False.
28+
29+
Returns:
30+
Tuple[bool, str]:
31+
- True and a success message if the extrinsic is successfully submitted or processed.
32+
- False and an error message if the submission fails or the wallet cannot be unlocked.
33+
"""
34+
if not (unlock := unlock_key(wallet)).success:
35+
logging.error(unlock.message)
36+
return False, unlock.message
37+
38+
with subtensor.substrate as substrate:
39+
start_call = substrate.compose_call(
40+
call_module="SubtensorModule",
41+
call_function="start_call",
42+
call_params={"netuid": netuid},
43+
)
44+
45+
signed_ext = substrate.create_signed_extrinsic(
46+
call=start_call,
47+
keypair=wallet.coldkey,
48+
)
49+
50+
response = substrate.submit_extrinsic(
51+
extrinsic=signed_ext,
52+
wait_for_inclusion=wait_for_inclusion,
53+
wait_for_finalization=wait_for_finalization,
54+
)
55+
56+
if not wait_for_finalization and not wait_for_inclusion:
57+
return True, "Not waiting for finalization or inclusion."
58+
59+
if response.is_success:
60+
return True, "Success with `start_call` response."
61+
62+
return False, response.error_message

bittensor/core/subtensor.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
get_metadata,
6161
serve_axon_extrinsic,
6262
)
63+
from bittensor.core.extrinsics.start_call import start_call_extrinsic
6364
from bittensor.core.extrinsics.set_weights import set_weights_extrinsic
6465
from bittensor.core.extrinsics.staking import (
6566
add_stake_extrinsic,
@@ -3252,6 +3253,36 @@ def serve_axon(
32523253
certificate=certificate,
32533254
)
32543255

3256+
def start_call(
3257+
self,
3258+
wallet: "Wallet",
3259+
netuid: int,
3260+
wait_for_inclusion: bool = True,
3261+
wait_for_finalization: bool = False,
3262+
) -> tuple[bool, str]:
3263+
"""
3264+
Submits a start_call extrinsic to the blockchain, to trigger the start call process for a subnet (used to start a
3265+
new subnet's emission mechanism).
3266+
3267+
Args:
3268+
wallet (Wallet): The wallet used to sign the extrinsic (must be unlocked).
3269+
netuid (int): The UID of the target subnet for which the call is being initiated.
3270+
wait_for_inclusion (bool, optional): Whether to wait for the extrinsic to be included in a block. Defaults to True.
3271+
wait_for_finalization (bool, optional): Whether to wait for finalization of the extrinsic. Defaults to False.
3272+
3273+
Returns:
3274+
Tuple[bool, str]:
3275+
- True and a success message if the extrinsic is successfully submitted or processed.
3276+
- False and an error message if the submission fails or the wallet cannot be unlocked.
3277+
"""
3278+
return start_call_extrinsic(
3279+
subtensor=self,
3280+
wallet=wallet,
3281+
netuid=netuid,
3282+
wait_for_inclusion=wait_for_inclusion,
3283+
wait_for_finalization=wait_for_finalization,
3284+
)
3285+
32553286
def swap_stake(
32563287
self,
32573288
wallet: "Wallet",

bittensor/core/timelock.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import struct
2+
import time
3+
from typing import Optional, Union
4+
5+
from bittensor_commit_reveal import (
6+
encrypt as _btr_encrypt,
7+
decrypt as _btr_decrypt,
8+
get_latest_round,
9+
)
10+
11+
TLE_ENCRYPTED_DATA_SUFFIX = b"AES_GCM_"
12+
13+
14+
def encrypt(
15+
data: Union[bytes, str], n_blocks: int, block_time: Union[int, float] = 12.0
16+
) -> tuple[bytes, int]:
17+
"""Encrypts data using TimeLock Encryption
18+
19+
Arguments:
20+
data: Any bytes data to be encrypted.
21+
n_blocks: Number of blocks to encrypt.
22+
block_time: Time in seconds for each block. Default is `12.0` seconds.
23+
24+
Returns:
25+
tuple: A tuple containing the encrypted data and reveal TimeLock reveal round.
26+
27+
Raises:
28+
PyValueError: If failed to encrypt data.
29+
30+
Usage:
31+
data = "From Cortex to Bittensor"
32+
33+
# default usage
34+
encrypted_data, reveal_round = encrypt(data, 10)
35+
36+
# passing block_time for fast-blocks node
37+
encrypted_data, reveal_round = encrypt(data, 15, block_time=0.25)
38+
39+
encrypted_data, reveal_round = encrypt(data, 5)
40+
41+
42+
Note:
43+
For using this function with fast-blocks node you need to set block_time to 0.25 seconds.
44+
data, round = encrypt(data, n_blocks, block_time=0.25)
45+
"""
46+
if isinstance(data, str):
47+
data = data.encode()
48+
return _btr_encrypt(data, n_blocks, block_time)
49+
50+
51+
def decrypt(
52+
encrypted_data: bytes, no_errors: bool = True, return_str: bool = False
53+
) -> Optional[Union[bytes, str]]:
54+
"""Decrypts encrypted data using TimeLock Decryption
55+
56+
Arguments:
57+
encrypted_data: Encrypted data to be decrypted.
58+
no_errors: If True, no errors will be raised during decryption.
59+
return_str: convert decrypted data to string if `True`. Default is `False`.
60+
61+
Returns:
62+
decrypted_data: Decrypted data, when reveled round is reached.
63+
64+
Usage:
65+
# default usage
66+
decrypted_data = decrypt(encrypted_data)
67+
68+
# passing no_errors=False for raising errors during decryption
69+
decrypted_data = decrypt(encrypted_data, no_errors=False)
70+
71+
# passing return_str=True for returning decrypted data as string
72+
decrypted_data = decrypt(encrypted_data, return_str=True)
73+
"""
74+
result = _btr_decrypt(encrypted_data, no_errors)
75+
if result is None:
76+
return None
77+
if return_str:
78+
return result.decode()
79+
return result
80+
81+
82+
def wait_reveal_and_decrypt(
83+
encrypted_data: bytes,
84+
reveal_round: Optional[int] = None,
85+
no_errors: bool = True,
86+
return_str: bool = False,
87+
) -> bytes:
88+
"""
89+
Waits for reveal round and decrypts data using TimeLock Decryption.
90+
91+
Arguments:
92+
encrypted_data: Encrypted data to be decrypted.
93+
reveal_round: Reveal round to wait for. If None, will be parsed from encrypted data.
94+
no_errors: If True, no errors will be raised during decryption.
95+
return_str: convert decrypted data to string if `True`. Default is `False`.
96+
97+
Raises:
98+
struct.error: If failed to parse reveal round from encrypted data.
99+
TypeError: If reveal_round is None or wrong type.
100+
IndexError: If provided encrypted_data does not contain reveal round.
101+
102+
Returns:
103+
bytes: Decrypted data.
104+
105+
Usage:
106+
import bittensor as bt
107+
encrypted, reveal_round = bt.timelock.encrypt("Cortex is power", 3)
108+
"""
109+
if reveal_round is None:
110+
try:
111+
reveal_round = struct.unpack(
112+
"<Q", encrypted_data.split(TLE_ENCRYPTED_DATA_SUFFIX)[-1]
113+
)[0]
114+
except (struct.error, TypeError, IndexError):
115+
raise ValueError("Failed to parse reveal round from encrypted data.")
116+
117+
while get_latest_round() <= reveal_round:
118+
# sleep Drand QuickNet period time (3 sec)
119+
time.sleep(3)
120+
121+
return decrypt(encrypted_data, no_errors, return_str)
122+
123+
124+
__all__ = [
125+
"decrypt",
126+
"encrypt",
127+
"get_latest_round",
128+
"wait_reveal_and_decrypt",
129+
]

0 commit comments

Comments
 (0)