meshtensor-cli 9.18.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- meshtensor_cli/__init__.py +22 -0
- meshtensor_cli/cli.py +10742 -0
- meshtensor_cli/doc_generation_helper.py +4 -0
- meshtensor_cli/src/__init__.py +1085 -0
- meshtensor_cli/src/commands/__init__.py +0 -0
- meshtensor_cli/src/commands/axon/__init__.py +0 -0
- meshtensor_cli/src/commands/axon/axon.py +132 -0
- meshtensor_cli/src/commands/crowd/__init__.py +0 -0
- meshtensor_cli/src/commands/crowd/contribute.py +621 -0
- meshtensor_cli/src/commands/crowd/contributors.py +200 -0
- meshtensor_cli/src/commands/crowd/create.py +783 -0
- meshtensor_cli/src/commands/crowd/dissolve.py +219 -0
- meshtensor_cli/src/commands/crowd/refund.py +233 -0
- meshtensor_cli/src/commands/crowd/update.py +418 -0
- meshtensor_cli/src/commands/crowd/utils.py +124 -0
- meshtensor_cli/src/commands/crowd/view.py +991 -0
- meshtensor_cli/src/commands/governance/__init__.py +0 -0
- meshtensor_cli/src/commands/governance/governance.py +794 -0
- meshtensor_cli/src/commands/liquidity/__init__.py +0 -0
- meshtensor_cli/src/commands/liquidity/liquidity.py +699 -0
- meshtensor_cli/src/commands/liquidity/utils.py +202 -0
- meshtensor_cli/src/commands/proxy.py +700 -0
- meshtensor_cli/src/commands/stake/__init__.py +0 -0
- meshtensor_cli/src/commands/stake/add.py +799 -0
- meshtensor_cli/src/commands/stake/auto_staking.py +306 -0
- meshtensor_cli/src/commands/stake/children_hotkeys.py +865 -0
- meshtensor_cli/src/commands/stake/claim.py +770 -0
- meshtensor_cli/src/commands/stake/list.py +738 -0
- meshtensor_cli/src/commands/stake/move.py +1211 -0
- meshtensor_cli/src/commands/stake/remove.py +1466 -0
- meshtensor_cli/src/commands/stake/wizard.py +323 -0
- meshtensor_cli/src/commands/subnets/__init__.py +0 -0
- meshtensor_cli/src/commands/subnets/mechanisms.py +515 -0
- meshtensor_cli/src/commands/subnets/price.py +733 -0
- meshtensor_cli/src/commands/subnets/subnets.py +2908 -0
- meshtensor_cli/src/commands/sudo.py +1294 -0
- meshtensor_cli/src/commands/tc/__init__.py +0 -0
- meshtensor_cli/src/commands/tc/tc.py +190 -0
- meshtensor_cli/src/commands/treasury/__init__.py +0 -0
- meshtensor_cli/src/commands/treasury/treasury.py +194 -0
- meshtensor_cli/src/commands/view.py +354 -0
- meshtensor_cli/src/commands/wallets.py +2311 -0
- meshtensor_cli/src/commands/weights.py +467 -0
- meshtensor_cli/src/meshtensor/__init__.py +0 -0
- meshtensor_cli/src/meshtensor/balances.py +313 -0
- meshtensor_cli/src/meshtensor/chain_data.py +1263 -0
- meshtensor_cli/src/meshtensor/extrinsics/__init__.py +0 -0
- meshtensor_cli/src/meshtensor/extrinsics/mev_shield.py +174 -0
- meshtensor_cli/src/meshtensor/extrinsics/registration.py +1861 -0
- meshtensor_cli/src/meshtensor/extrinsics/root.py +550 -0
- meshtensor_cli/src/meshtensor/extrinsics/serving.py +255 -0
- meshtensor_cli/src/meshtensor/extrinsics/transfer.py +239 -0
- meshtensor_cli/src/meshtensor/meshtensor_interface.py +2598 -0
- meshtensor_cli/src/meshtensor/minigraph.py +254 -0
- meshtensor_cli/src/meshtensor/networking.py +12 -0
- meshtensor_cli/src/meshtensor/templates/main-filters.j2 +24 -0
- meshtensor_cli/src/meshtensor/templates/main-header.j2 +36 -0
- meshtensor_cli/src/meshtensor/templates/neuron-details.j2 +111 -0
- meshtensor_cli/src/meshtensor/templates/price-multi.j2 +113 -0
- meshtensor_cli/src/meshtensor/templates/price-single.j2 +99 -0
- meshtensor_cli/src/meshtensor/templates/subnet-details-header.j2 +49 -0
- meshtensor_cli/src/meshtensor/templates/subnet-details.j2 +32 -0
- meshtensor_cli/src/meshtensor/templates/subnet-metrics.j2 +57 -0
- meshtensor_cli/src/meshtensor/templates/subnets-table.j2 +28 -0
- meshtensor_cli/src/meshtensor/templates/table.j2 +267 -0
- meshtensor_cli/src/meshtensor/templates/view.css +1058 -0
- meshtensor_cli/src/meshtensor/templates/view.j2 +43 -0
- meshtensor_cli/src/meshtensor/templates/view.js +1053 -0
- meshtensor_cli/src/meshtensor/utils.py +2007 -0
- meshtensor_cli/version.py +23 -0
- meshtensor_cli-9.18.1.dist-info/METADATA +261 -0
- meshtensor_cli-9.18.1.dist-info/RECORD +74 -0
- meshtensor_cli-9.18.1.dist-info/WHEEL +4 -0
- meshtensor_cli-9.18.1.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,1294 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
import sys
|
|
5
|
+
from typing import TYPE_CHECKING, Union, Optional, Type
|
|
6
|
+
|
|
7
|
+
from async_substrate_interface import AsyncExtrinsicReceipt
|
|
8
|
+
from meshtensor_wallet import Wallet
|
|
9
|
+
from rich import box
|
|
10
|
+
from rich.table import Column, Table
|
|
11
|
+
from scalecodec import GenericCall
|
|
12
|
+
|
|
13
|
+
from meshtensor_cli.src import (
|
|
14
|
+
HYPERPARAMS,
|
|
15
|
+
HYPERPARAMS_MODULE,
|
|
16
|
+
HYPERPARAMS_METADATA,
|
|
17
|
+
RootSudoOnly,
|
|
18
|
+
DelegatesDetails,
|
|
19
|
+
COLOR_PALETTE,
|
|
20
|
+
)
|
|
21
|
+
from meshtensor_cli.src.meshtensor.chain_data import decode_account_id
|
|
22
|
+
from meshtensor_cli.src.meshtensor.utils import (
|
|
23
|
+
confirm_action,
|
|
24
|
+
console,
|
|
25
|
+
print_error,
|
|
26
|
+
print_success,
|
|
27
|
+
print_verbose,
|
|
28
|
+
normalize_hyperparameters,
|
|
29
|
+
unlock_key,
|
|
30
|
+
blocks_to_duration,
|
|
31
|
+
json_console,
|
|
32
|
+
string_to_u16,
|
|
33
|
+
string_to_u64,
|
|
34
|
+
get_hotkey_pub_ss58,
|
|
35
|
+
print_extrinsic_id,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
if TYPE_CHECKING:
|
|
39
|
+
from meshtensor_cli.src.meshtensor.meshtensor_interface import (
|
|
40
|
+
MeshtensorInterface,
|
|
41
|
+
ProposalVoteData,
|
|
42
|
+
)
|
|
43
|
+
from scalecodec.types import GenericMetadataVersioned
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# helpers and extrinsics
|
|
47
|
+
DEFAULT_PALLET = "AdminUtils"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def allowed_value(
|
|
51
|
+
param: str, value: Union[str, bool]
|
|
52
|
+
) -> tuple[bool, Union[str, list[float], float, bool]]:
|
|
53
|
+
"""
|
|
54
|
+
Check the allowed values on hyperparameters. Return False if value is out of bounds.
|
|
55
|
+
|
|
56
|
+
Reminder error message ends like: Value is {value} but must be {error_message}. (the second part of return
|
|
57
|
+
statement)
|
|
58
|
+
|
|
59
|
+
Check if value is a boolean, only allow boolean and floats
|
|
60
|
+
"""
|
|
61
|
+
try:
|
|
62
|
+
if not isinstance(value, bool):
|
|
63
|
+
if param == "alpha_values":
|
|
64
|
+
# Split the string into individual values
|
|
65
|
+
alpha_low_str, alpha_high_str = value.split(",")
|
|
66
|
+
alpha_high = float(alpha_high_str)
|
|
67
|
+
alpha_low = float(alpha_low_str)
|
|
68
|
+
|
|
69
|
+
# Check alpha_high value
|
|
70
|
+
if alpha_high <= 52428 or alpha_high >= 65535:
|
|
71
|
+
return (
|
|
72
|
+
False,
|
|
73
|
+
f"between 52428 and 65535 for alpha_high (but is {alpha_high})",
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Check alpha_low value
|
|
77
|
+
if alpha_low < 0 or alpha_low > 52428:
|
|
78
|
+
return (
|
|
79
|
+
False,
|
|
80
|
+
f"between 0 and 52428 for alpha_low (but is {alpha_low})",
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return True, [alpha_low, alpha_high]
|
|
84
|
+
except ValueError:
|
|
85
|
+
return False, "a number or a boolean"
|
|
86
|
+
|
|
87
|
+
return True, value
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def string_to_bool(val) -> Union[bool, Type[ValueError]]:
|
|
91
|
+
try:
|
|
92
|
+
return {"true": True, "1": True, "0": False, "false": False}[val.lower()]
|
|
93
|
+
except KeyError:
|
|
94
|
+
return ValueError
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def search_metadata(
|
|
98
|
+
param_name: str,
|
|
99
|
+
value: Union[str, bool, float, list[float]],
|
|
100
|
+
netuid: int,
|
|
101
|
+
metadata: "GenericMetadataVersioned",
|
|
102
|
+
pallet_name: str = DEFAULT_PALLET,
|
|
103
|
+
) -> tuple[bool, Optional[dict]]:
|
|
104
|
+
"""
|
|
105
|
+
Searches the substrate metadata AdminUtils pallet for a given parameter name. Crafts a response dict to be used
|
|
106
|
+
as call parameters for setting this hyperparameter.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
param_name: the name of the hyperparameter
|
|
110
|
+
value: the value to set the hyperparameter
|
|
111
|
+
netuid: the specified netuid
|
|
112
|
+
metadata: the meshtensor.substrate.metadata
|
|
113
|
+
pallet_name: the name of the module to use for the query. If not set, the default value is DEFAULT_PALLET
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
(success, dict of call params)
|
|
117
|
+
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
def type_converter_with_retry(type_, val, arg_name):
|
|
121
|
+
try:
|
|
122
|
+
if val is None:
|
|
123
|
+
val = input(
|
|
124
|
+
f"Enter a value for field '{arg_name}' with type '{arg_type_output[type_]}': "
|
|
125
|
+
)
|
|
126
|
+
return arg_types[type_](val)
|
|
127
|
+
except ValueError:
|
|
128
|
+
return type_converter_with_retry(type_, None, arg_name)
|
|
129
|
+
|
|
130
|
+
arg_types = {"bool": string_to_bool, "u16": string_to_u16, "u64": string_to_u64}
|
|
131
|
+
arg_type_output = {"bool": "bool", "u16": "float", "u64": "float"}
|
|
132
|
+
|
|
133
|
+
call_crafter = {"netuid": netuid}
|
|
134
|
+
|
|
135
|
+
pallet = metadata.get_metadata_pallet(pallet_name)
|
|
136
|
+
for call in pallet.calls:
|
|
137
|
+
if call.name == param_name:
|
|
138
|
+
if "netuid" not in [x.name for x in call.args]:
|
|
139
|
+
return False, None
|
|
140
|
+
call_args = [arg for arg in call.args if arg.value["name"] != "netuid"]
|
|
141
|
+
if len(call_args) == 1:
|
|
142
|
+
arg = call_args[0].value
|
|
143
|
+
call_crafter[arg["name"]] = type_converter_with_retry(
|
|
144
|
+
arg["typeName"], value, arg["name"]
|
|
145
|
+
)
|
|
146
|
+
else:
|
|
147
|
+
for arg_ in call_args:
|
|
148
|
+
arg = arg_.value
|
|
149
|
+
call_crafter[arg["name"]] = type_converter_with_retry(
|
|
150
|
+
arg["typeName"], None, arg["name"]
|
|
151
|
+
)
|
|
152
|
+
return True, call_crafter
|
|
153
|
+
else:
|
|
154
|
+
return False, None
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def requires_bool(metadata, param_name, pallet: str = DEFAULT_PALLET) -> bool:
|
|
158
|
+
"""
|
|
159
|
+
Determines whether a given hyperparam takes a single arg (besides netuid) that is of bool type.
|
|
160
|
+
"""
|
|
161
|
+
pallet = metadata.get_metadata_pallet(pallet)
|
|
162
|
+
for call in pallet.calls:
|
|
163
|
+
if call.name == param_name:
|
|
164
|
+
if "netuid" not in [x.name for x in call.args]:
|
|
165
|
+
return False
|
|
166
|
+
call_args = [arg for arg in call.args if arg.value["name"] != "netuid"]
|
|
167
|
+
if len(call_args) != 1:
|
|
168
|
+
return False
|
|
169
|
+
else:
|
|
170
|
+
arg = call_args[0].value
|
|
171
|
+
if arg["typeName"] == "bool":
|
|
172
|
+
return True
|
|
173
|
+
else:
|
|
174
|
+
return False
|
|
175
|
+
raise ValueError(f"{param_name} not found in pallet.")
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
async def set_mechanism_count_extrinsic(
|
|
179
|
+
meshtensor: "MeshtensorInterface",
|
|
180
|
+
wallet: "Wallet",
|
|
181
|
+
netuid: int,
|
|
182
|
+
proxy: Optional[str],
|
|
183
|
+
mech_count: int,
|
|
184
|
+
wait_for_inclusion: bool = True,
|
|
185
|
+
wait_for_finalization: bool = True,
|
|
186
|
+
) -> tuple[bool, str, Optional[AsyncExtrinsicReceipt]]:
|
|
187
|
+
"""Sets the number of mechanisms for a subnet via AdminUtils."""
|
|
188
|
+
|
|
189
|
+
unlock_result = unlock_key(wallet)
|
|
190
|
+
if not unlock_result.success:
|
|
191
|
+
return False, unlock_result.message, None
|
|
192
|
+
|
|
193
|
+
substrate = meshtensor.substrate
|
|
194
|
+
call_params = {"netuid": netuid, "mechanism_count": mech_count}
|
|
195
|
+
|
|
196
|
+
with console.status(
|
|
197
|
+
f":satellite: Setting mechanism count to [white]{mech_count}[/white] on "
|
|
198
|
+
f"[{COLOR_PALETTE.G.SUBHEAD}]{netuid}[/{COLOR_PALETTE.G.SUBHEAD}] ...",
|
|
199
|
+
spinner="earth",
|
|
200
|
+
):
|
|
201
|
+
call = await substrate.compose_call(
|
|
202
|
+
call_module=DEFAULT_PALLET,
|
|
203
|
+
call_function="sudo_set_mechanism_count",
|
|
204
|
+
call_params=call_params,
|
|
205
|
+
)
|
|
206
|
+
success, err_msg, ext_receipt = await meshtensor.sign_and_send_extrinsic(
|
|
207
|
+
call,
|
|
208
|
+
wallet,
|
|
209
|
+
wait_for_inclusion=wait_for_inclusion,
|
|
210
|
+
wait_for_finalization=wait_for_finalization,
|
|
211
|
+
proxy=proxy,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
if not success:
|
|
215
|
+
return False, err_msg, None
|
|
216
|
+
|
|
217
|
+
return True, "", ext_receipt
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
async def set_mechanism_emission_extrinsic(
|
|
221
|
+
meshtensor: "MeshtensorInterface",
|
|
222
|
+
wallet: "Wallet",
|
|
223
|
+
netuid: int,
|
|
224
|
+
proxy: Optional[str],
|
|
225
|
+
split: list[int],
|
|
226
|
+
wait_for_inclusion: bool = True,
|
|
227
|
+
wait_for_finalization: bool = True,
|
|
228
|
+
) -> tuple[bool, str, Optional[AsyncExtrinsicReceipt]]:
|
|
229
|
+
"""Sets the emission split for a subnet's mechanisms via AdminUtils."""
|
|
230
|
+
|
|
231
|
+
unlock_result = unlock_key(wallet)
|
|
232
|
+
if not unlock_result.success:
|
|
233
|
+
return False, unlock_result.message, None
|
|
234
|
+
|
|
235
|
+
substrate = meshtensor.substrate
|
|
236
|
+
|
|
237
|
+
with console.status(
|
|
238
|
+
f":satellite: Setting emission split for subnet {netuid}...",
|
|
239
|
+
spinner="earth",
|
|
240
|
+
):
|
|
241
|
+
call = await substrate.compose_call(
|
|
242
|
+
call_module=DEFAULT_PALLET,
|
|
243
|
+
call_function="sudo_set_mechanism_emission_split",
|
|
244
|
+
call_params={"netuid": netuid, "maybe_split": split},
|
|
245
|
+
)
|
|
246
|
+
success, err_msg, ext_receipt = await meshtensor.sign_and_send_extrinsic(
|
|
247
|
+
call,
|
|
248
|
+
wallet,
|
|
249
|
+
wait_for_inclusion=wait_for_inclusion,
|
|
250
|
+
wait_for_finalization=wait_for_finalization,
|
|
251
|
+
proxy=proxy,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
if not success:
|
|
255
|
+
return False, err_msg, None
|
|
256
|
+
|
|
257
|
+
return True, "", ext_receipt
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
async def set_hyperparameter_extrinsic(
|
|
261
|
+
meshtensor: "MeshtensorInterface",
|
|
262
|
+
wallet: "Wallet",
|
|
263
|
+
netuid: int,
|
|
264
|
+
proxy: Optional[str],
|
|
265
|
+
parameter: str,
|
|
266
|
+
value: Optional[Union[str, float, list[float]]],
|
|
267
|
+
wait_for_inclusion: bool = False,
|
|
268
|
+
wait_for_finalization: bool = True,
|
|
269
|
+
prompt: bool = True,
|
|
270
|
+
decline: bool = False,
|
|
271
|
+
quiet: bool = False,
|
|
272
|
+
) -> tuple[bool, str, Optional[str]]:
|
|
273
|
+
"""Sets a hyperparameter for a specific subnetwork.
|
|
274
|
+
|
|
275
|
+
:param meshtensor: initialized MeshtensorInterface object
|
|
276
|
+
:param wallet: meshtensor wallet object.
|
|
277
|
+
:param netuid: Subnetwork `uid`.
|
|
278
|
+
:param proxy: Optional proxy to use for this extrinsic submission.
|
|
279
|
+
:param parameter: Hyperparameter name.
|
|
280
|
+
:param value: New hyperparameter value.
|
|
281
|
+
:param wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`, or returns
|
|
282
|
+
`False` if the extrinsic fails to enter the block within the timeout.
|
|
283
|
+
:param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning `True`,
|
|
284
|
+
or returns `False` if the extrinsic fails to be finalized within the timeout.
|
|
285
|
+
:param prompt: If set to False, will not prompt the user.
|
|
286
|
+
|
|
287
|
+
:return: tuple including:
|
|
288
|
+
success: `True` if extrinsic was finalized or included in the block. If we did not wait for
|
|
289
|
+
finalization/inclusion, the response is `True`.
|
|
290
|
+
message: error message if the extrinsic failed
|
|
291
|
+
extrinsic_identifier: optional extrinsic identifier if the extrinsic was included
|
|
292
|
+
"""
|
|
293
|
+
print_verbose("Confirming subnet owner")
|
|
294
|
+
coldkey_ss58 = proxy or wallet.coldkeypub.ss58_address
|
|
295
|
+
subnet_owner = await meshtensor.query(
|
|
296
|
+
module="MeshtensorModule",
|
|
297
|
+
storage_function="SubnetOwner",
|
|
298
|
+
params=[netuid],
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
if not (ulw := unlock_key(wallet)).success:
|
|
302
|
+
return False, ulw.message, None
|
|
303
|
+
|
|
304
|
+
arbitrary_extrinsic = False
|
|
305
|
+
|
|
306
|
+
extrinsic, sudo_ = HYPERPARAMS.get(parameter, ("", RootSudoOnly.FALSE))
|
|
307
|
+
call_params = {"netuid": netuid}
|
|
308
|
+
if not extrinsic:
|
|
309
|
+
arbitrary_extrinsic, call_params = search_metadata(
|
|
310
|
+
parameter, value, netuid, meshtensor.substrate.metadata
|
|
311
|
+
)
|
|
312
|
+
extrinsic = parameter
|
|
313
|
+
if not arbitrary_extrinsic:
|
|
314
|
+
err_msg = "Invalid hyperparameter specified."
|
|
315
|
+
print_error(err_msg)
|
|
316
|
+
return False, err_msg, None
|
|
317
|
+
if sudo_ is RootSudoOnly.TRUE and prompt:
|
|
318
|
+
if not confirm_action(
|
|
319
|
+
"This hyperparam is only settable by root sudo users. If you are not, this will fail. Please confirm",
|
|
320
|
+
decline=decline,
|
|
321
|
+
quiet=quiet,
|
|
322
|
+
):
|
|
323
|
+
return False, "This hyperparam is only settable by root sudo users", None
|
|
324
|
+
|
|
325
|
+
substrate = meshtensor.substrate
|
|
326
|
+
msg_value = value if not arbitrary_extrinsic else call_params
|
|
327
|
+
pallet = HYPERPARAMS_MODULE.get(parameter) or DEFAULT_PALLET
|
|
328
|
+
|
|
329
|
+
if not arbitrary_extrinsic:
|
|
330
|
+
extrinsic_params = await substrate.get_metadata_call_function(
|
|
331
|
+
module_name=pallet, call_function_name=extrinsic
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
# if input value is a list, iterate through the list and assign values
|
|
335
|
+
if isinstance(value, list):
|
|
336
|
+
# Ensure that there are enough values for all non-netuid parameters
|
|
337
|
+
non_netuid_fields = [
|
|
338
|
+
pn_str
|
|
339
|
+
for param in extrinsic_params["fields"]
|
|
340
|
+
if "netuid" not in (pn_str := str(param["name"]))
|
|
341
|
+
]
|
|
342
|
+
|
|
343
|
+
if len(value) < len(non_netuid_fields):
|
|
344
|
+
err_msg = "Not enough values provided in the list for all parameters"
|
|
345
|
+
print_error(err_msg)
|
|
346
|
+
return False, err_msg, None
|
|
347
|
+
|
|
348
|
+
call_params.update(
|
|
349
|
+
{name: val for name, val in zip(non_netuid_fields, value)}
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
else:
|
|
353
|
+
if requires_bool(
|
|
354
|
+
substrate.metadata, param_name=extrinsic, pallet=pallet
|
|
355
|
+
) and isinstance(value, str):
|
|
356
|
+
value = string_to_bool(value)
|
|
357
|
+
value_argument = extrinsic_params["fields"][
|
|
358
|
+
len(extrinsic_params["fields"]) - 1
|
|
359
|
+
]
|
|
360
|
+
call_params[str(value_argument["name"])] = value
|
|
361
|
+
# create extrinsic call
|
|
362
|
+
call_ = await substrate.compose_call(
|
|
363
|
+
call_module=pallet,
|
|
364
|
+
call_function=extrinsic,
|
|
365
|
+
call_params=call_params,
|
|
366
|
+
)
|
|
367
|
+
if sudo_ is RootSudoOnly.TRUE:
|
|
368
|
+
call = await substrate.compose_call(
|
|
369
|
+
call_module="Sudo", call_function="sudo", call_params={"call": call_}
|
|
370
|
+
)
|
|
371
|
+
elif sudo_ is RootSudoOnly.COMPLICATED:
|
|
372
|
+
if not prompt:
|
|
373
|
+
to_sudo_or_not_to_sudo = True # default to sudo true when no-prompt is set
|
|
374
|
+
else:
|
|
375
|
+
to_sudo_or_not_to_sudo = confirm_action(
|
|
376
|
+
"This hyperparam can be executed as sudo or not. Do you want to execute as sudo [y] or not [n]?",
|
|
377
|
+
decline=decline,
|
|
378
|
+
quiet=quiet,
|
|
379
|
+
)
|
|
380
|
+
if to_sudo_or_not_to_sudo:
|
|
381
|
+
call = await substrate.compose_call(
|
|
382
|
+
call_module="Sudo",
|
|
383
|
+
call_function="sudo",
|
|
384
|
+
call_params={"call": call_},
|
|
385
|
+
)
|
|
386
|
+
else:
|
|
387
|
+
if subnet_owner != coldkey_ss58:
|
|
388
|
+
err_msg = "This wallet doesn't own the specified subnet."
|
|
389
|
+
print_error(err_msg)
|
|
390
|
+
return False, err_msg, None
|
|
391
|
+
call = call_
|
|
392
|
+
else:
|
|
393
|
+
if subnet_owner != coldkey_ss58:
|
|
394
|
+
err_msg = "This wallet doesn't own the specified subnet."
|
|
395
|
+
print_error(err_msg)
|
|
396
|
+
return False, err_msg, None
|
|
397
|
+
call = call_
|
|
398
|
+
with console.status(
|
|
399
|
+
f":satellite: Setting hyperparameter [{COLOR_PALETTE.G.SUBHEAD}]{parameter}[/{COLOR_PALETTE.G.SUBHEAD}]"
|
|
400
|
+
f" to [{COLOR_PALETTE.G.SUBHEAD}]{msg_value}[/{COLOR_PALETTE.G.SUBHEAD}]"
|
|
401
|
+
f" on subnet: [{COLOR_PALETTE.G.SUBHEAD}]{netuid}[/{COLOR_PALETTE.G.SUBHEAD}] ...",
|
|
402
|
+
spinner="earth",
|
|
403
|
+
):
|
|
404
|
+
success, err_msg, ext_receipt = await meshtensor.sign_and_send_extrinsic(
|
|
405
|
+
call, wallet, wait_for_inclusion, wait_for_finalization, proxy=proxy
|
|
406
|
+
)
|
|
407
|
+
if not success:
|
|
408
|
+
print_error(f"Failed: {err_msg}")
|
|
409
|
+
return False, err_msg, None
|
|
410
|
+
else:
|
|
411
|
+
ext_id = await ext_receipt.get_extrinsic_identifier()
|
|
412
|
+
await print_extrinsic_id(ext_receipt)
|
|
413
|
+
if arbitrary_extrinsic:
|
|
414
|
+
print_success(
|
|
415
|
+
f"[dark_sea_green3]Hyperparameter {parameter} values changed to {call_params}[/dark_sea_green3]"
|
|
416
|
+
)
|
|
417
|
+
return True, "", ext_id
|
|
418
|
+
# Successful registration, final check for membership
|
|
419
|
+
else:
|
|
420
|
+
print_success(
|
|
421
|
+
f"[dark_sea_green3]Hyperparameter {parameter} changed to {value}[/dark_sea_green3]"
|
|
422
|
+
)
|
|
423
|
+
return True, "", ext_id
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
async def _get_senate_members(
|
|
427
|
+
meshtensor: "MeshtensorInterface", block_hash: Optional[str] = None
|
|
428
|
+
) -> list[str]:
|
|
429
|
+
"""
|
|
430
|
+
Gets all members of the senate on the given meshtensor's network
|
|
431
|
+
|
|
432
|
+
:param meshtensor: MeshtensorInterface object to use for the query
|
|
433
|
+
|
|
434
|
+
:return: list of the senate members' ss58 addresses
|
|
435
|
+
"""
|
|
436
|
+
senate_members = await meshtensor.query(
|
|
437
|
+
module="SenateMembers",
|
|
438
|
+
storage_function="Members",
|
|
439
|
+
params=None,
|
|
440
|
+
block_hash=block_hash,
|
|
441
|
+
)
|
|
442
|
+
try:
|
|
443
|
+
return [
|
|
444
|
+
decode_account_id(i[x][0]) for i in senate_members for x in range(len(i))
|
|
445
|
+
]
|
|
446
|
+
except (IndexError, TypeError):
|
|
447
|
+
print_error("Unable to retrieve senate members.")
|
|
448
|
+
return []
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
async def _get_proposals(
|
|
452
|
+
meshtensor: "MeshtensorInterface", block_hash: str
|
|
453
|
+
) -> dict[str, tuple[dict, "ProposalVoteData"]]:
|
|
454
|
+
async def get_proposal_call_data(p_hash: str) -> Optional[GenericCall]:
|
|
455
|
+
proposal_data = await meshtensor.query(
|
|
456
|
+
module="Triumvirate",
|
|
457
|
+
storage_function="ProposalOf",
|
|
458
|
+
block_hash=block_hash,
|
|
459
|
+
params=[p_hash],
|
|
460
|
+
)
|
|
461
|
+
return proposal_data
|
|
462
|
+
|
|
463
|
+
ph = await meshtensor.query(
|
|
464
|
+
module="Triumvirate",
|
|
465
|
+
storage_function="Proposals",
|
|
466
|
+
params=None,
|
|
467
|
+
block_hash=block_hash,
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
try:
|
|
471
|
+
proposal_hashes: list[str] = [
|
|
472
|
+
f"0x{bytes(ph[0][x][0]).hex()}" for x in range(len(ph[0]))
|
|
473
|
+
]
|
|
474
|
+
except (IndexError, TypeError):
|
|
475
|
+
print_error("Unable to retrieve proposal vote data")
|
|
476
|
+
return {}
|
|
477
|
+
|
|
478
|
+
call_data_, vote_data_ = await asyncio.gather(
|
|
479
|
+
asyncio.gather(*[get_proposal_call_data(h) for h in proposal_hashes]),
|
|
480
|
+
asyncio.gather(*[meshtensor.get_vote_data(h) for h in proposal_hashes]),
|
|
481
|
+
)
|
|
482
|
+
return {
|
|
483
|
+
proposal_hash: (cd, vd)
|
|
484
|
+
for cd, vd, proposal_hash in zip(call_data_, vote_data_, proposal_hashes)
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def display_votes(
|
|
489
|
+
vote_data: "ProposalVoteData", delegate_info: dict[str, DelegatesDetails]
|
|
490
|
+
) -> str:
|
|
491
|
+
vote_list = list()
|
|
492
|
+
|
|
493
|
+
for address in vote_data.ayes:
|
|
494
|
+
vote_list.append(
|
|
495
|
+
"{}: {}".format(
|
|
496
|
+
delegate_info[address].display if address in delegate_info else address,
|
|
497
|
+
"[bold green]Aye[/bold green]",
|
|
498
|
+
)
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
for address in vote_data.nays:
|
|
502
|
+
vote_list.append(
|
|
503
|
+
"{}: {}".format(
|
|
504
|
+
delegate_info[address].display if address in delegate_info else address,
|
|
505
|
+
"[bold red]Nay[/bold red]",
|
|
506
|
+
)
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
return "\n".join(vote_list)
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def serialize_vote_data(
|
|
513
|
+
vote_data: "ProposalVoteData", delegate_info: dict[str, DelegatesDetails]
|
|
514
|
+
) -> list[dict[str, bool]]:
|
|
515
|
+
vote_list = {}
|
|
516
|
+
for address in vote_data.ayes:
|
|
517
|
+
f_add = delegate_info[address].display if address in delegate_info else address
|
|
518
|
+
vote_list[f_add] = True
|
|
519
|
+
for address in vote_data.nays:
|
|
520
|
+
f_add = delegate_info[address].display if address in delegate_info else address
|
|
521
|
+
vote_list[f_add] = False
|
|
522
|
+
return vote_list
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def format_call_data(call_data: dict) -> str:
|
|
526
|
+
# Extract the module and call details
|
|
527
|
+
module, call_details = next(iter(call_data.items()))
|
|
528
|
+
|
|
529
|
+
# Extract the call function name and arguments
|
|
530
|
+
call_info = call_details[0]
|
|
531
|
+
call_function, call_args = next(iter(call_info.items()))
|
|
532
|
+
|
|
533
|
+
# Format arguments, handle nested/large payloads
|
|
534
|
+
formatted_args = []
|
|
535
|
+
for arg_name, arg_value in call_args.items():
|
|
536
|
+
if isinstance(arg_value, (tuple, list, dict)):
|
|
537
|
+
# For large nested, show abbreviated version
|
|
538
|
+
content_str = str(arg_value)
|
|
539
|
+
if len(content_str) > 20:
|
|
540
|
+
formatted_args.append(f"{arg_name}: ... [{len(content_str)}] ...")
|
|
541
|
+
else:
|
|
542
|
+
formatted_args.append(f"{arg_name}: {arg_value}")
|
|
543
|
+
else:
|
|
544
|
+
formatted_args.append(f"{arg_name}: {arg_value}")
|
|
545
|
+
|
|
546
|
+
# Format the final output string
|
|
547
|
+
args_str = ", ".join(formatted_args)
|
|
548
|
+
return f"{module}.{call_function}({args_str})"
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def _validate_proposal_hash(proposal_hash: str) -> bool:
|
|
552
|
+
if proposal_hash[0:2] != "0x" or len(proposal_hash) != 66:
|
|
553
|
+
return False
|
|
554
|
+
else:
|
|
555
|
+
return True
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
async def _is_senate_member(meshtensor: "MeshtensorInterface", hotkey_ss58: str) -> bool:
|
|
559
|
+
"""
|
|
560
|
+
Checks if a given neuron (identified by its hotkey SS58 address) is a member of the Meshtensor senate.
|
|
561
|
+
The senate is a key governance body within the Meshtensor network, responsible for overseeing and
|
|
562
|
+
approving various network operations and proposals.
|
|
563
|
+
|
|
564
|
+
:param meshtensor: MeshtensorInterface object to use for the query
|
|
565
|
+
:param hotkey_ss58: The `SS58` address of the neuron's hotkey.
|
|
566
|
+
|
|
567
|
+
:return: `True` if the neuron is a senate member at the given block, `False` otherwise.
|
|
568
|
+
|
|
569
|
+
This function is crucial for understanding the governance dynamics of the Meshtensor network and for
|
|
570
|
+
identifying the neurons that hold decision-making power within the network.
|
|
571
|
+
"""
|
|
572
|
+
|
|
573
|
+
senate_members = await _get_senate_members(meshtensor)
|
|
574
|
+
|
|
575
|
+
if not hasattr(senate_members, "count"):
|
|
576
|
+
return False
|
|
577
|
+
|
|
578
|
+
return senate_members.count(hotkey_ss58) > 0
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
async def vote_senate_extrinsic(
|
|
582
|
+
meshtensor: "MeshtensorInterface",
|
|
583
|
+
wallet: Wallet,
|
|
584
|
+
proxy: Optional[str],
|
|
585
|
+
proposal_hash: str,
|
|
586
|
+
proposal_idx: int,
|
|
587
|
+
vote: bool,
|
|
588
|
+
wait_for_inclusion: bool = False,
|
|
589
|
+
wait_for_finalization: bool = True,
|
|
590
|
+
prompt: bool = False,
|
|
591
|
+
decline: bool = False,
|
|
592
|
+
quiet: bool = False,
|
|
593
|
+
) -> bool:
|
|
594
|
+
"""Votes ayes or nays on proposals.
|
|
595
|
+
|
|
596
|
+
:param meshtensor: The MeshtensorInterface object to use for the query
|
|
597
|
+
:param wallet: Meshtensor wallet object, with coldkey and hotkey unlocked.
|
|
598
|
+
:param proxy: Optional proxy address to use for the extrinsic submission
|
|
599
|
+
:param proposal_hash: The hash of the proposal for which voting data is requested.
|
|
600
|
+
:param proposal_idx: The index of the proposal to vote.
|
|
601
|
+
:param vote: Whether to vote aye or nay.
|
|
602
|
+
:param wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`, or returns
|
|
603
|
+
`False` if the extrinsic fails to enter the block within the timeout.
|
|
604
|
+
:param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning `True`,
|
|
605
|
+
or returns `False` if the extrinsic fails to be finalized within the timeout.
|
|
606
|
+
:param prompt: If `True`, the call waits for confirmation from the user before proceeding.
|
|
607
|
+
|
|
608
|
+
:return: Flag is `True` if extrinsic was finalized or included in the block. If we did not wait for
|
|
609
|
+
finalization/inclusion, the response is `True`.
|
|
610
|
+
"""
|
|
611
|
+
|
|
612
|
+
if prompt:
|
|
613
|
+
# Prompt user for confirmation.
|
|
614
|
+
if not confirm_action(f"Cast a vote of {vote}?", decline=decline, quiet=quiet):
|
|
615
|
+
return False
|
|
616
|
+
|
|
617
|
+
with console.status(":satellite: Casting vote..", spinner="aesthetic"):
|
|
618
|
+
call = await meshtensor.substrate.compose_call(
|
|
619
|
+
call_module="MeshtensorModule",
|
|
620
|
+
call_function="vote",
|
|
621
|
+
call_params={
|
|
622
|
+
"hotkey": get_hotkey_pub_ss58(wallet),
|
|
623
|
+
"proposal": proposal_hash,
|
|
624
|
+
"index": proposal_idx,
|
|
625
|
+
"approve": vote,
|
|
626
|
+
},
|
|
627
|
+
)
|
|
628
|
+
success, err_msg, ext_receipt = await meshtensor.sign_and_send_extrinsic(
|
|
629
|
+
call, wallet, wait_for_inclusion, wait_for_finalization, proxy=proxy
|
|
630
|
+
)
|
|
631
|
+
if not success:
|
|
632
|
+
print_error(f"Failed: {err_msg}")
|
|
633
|
+
return False
|
|
634
|
+
# Successful vote, final check for data
|
|
635
|
+
else:
|
|
636
|
+
await print_extrinsic_id(ext_receipt)
|
|
637
|
+
if vote_data := await meshtensor.get_vote_data(proposal_hash):
|
|
638
|
+
hotkey_ss58 = get_hotkey_pub_ss58(wallet)
|
|
639
|
+
if (
|
|
640
|
+
vote_data.ayes.count(hotkey_ss58) > 0
|
|
641
|
+
or vote_data.nays.count(hotkey_ss58) > 0
|
|
642
|
+
):
|
|
643
|
+
print_success("Vote cast.")
|
|
644
|
+
return True
|
|
645
|
+
else:
|
|
646
|
+
# hotkey not found in ayes/nays
|
|
647
|
+
print_error("Unknown error. Couldn't find vote.")
|
|
648
|
+
return False
|
|
649
|
+
else:
|
|
650
|
+
return False
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
async def set_take_extrinsic(
|
|
654
|
+
meshtensor: "MeshtensorInterface",
|
|
655
|
+
wallet: Wallet,
|
|
656
|
+
delegate_ss58: str,
|
|
657
|
+
take: float = 0.0,
|
|
658
|
+
proxy: Optional[str] = None,
|
|
659
|
+
) -> tuple[bool, Optional[str]]:
|
|
660
|
+
"""
|
|
661
|
+
Set delegate hotkey take
|
|
662
|
+
|
|
663
|
+
:param meshtensor: MeshtensorInterface (initialized)
|
|
664
|
+
:param wallet: The wallet containing the hotkey to be nominated.
|
|
665
|
+
:param delegate_ss58: Hotkey
|
|
666
|
+
:param take: Delegate take on subnet ID
|
|
667
|
+
:param proxy: Optional proxy address to use for the extrinsic submission
|
|
668
|
+
|
|
669
|
+
:return: `True` if the process is successful, `False` otherwise.
|
|
670
|
+
|
|
671
|
+
This function is a key part of the decentralized governance mechanism of Meshtensor, allowing for the
|
|
672
|
+
dynamic selection and participation of validators in the network's consensus process.
|
|
673
|
+
"""
|
|
674
|
+
|
|
675
|
+
# Calculate u16 representation of the take
|
|
676
|
+
take_u16 = int(take * 0xFFFF)
|
|
677
|
+
|
|
678
|
+
print_verbose("Checking current take")
|
|
679
|
+
# Check if the new take is greater or lower than existing take or if existing is set
|
|
680
|
+
current_take = await get_current_take(meshtensor, wallet)
|
|
681
|
+
current_take_u16 = int(float(current_take) * 0xFFFF)
|
|
682
|
+
|
|
683
|
+
if take_u16 == current_take_u16:
|
|
684
|
+
console.print("Nothing to do, take hasn't changed")
|
|
685
|
+
return True, None
|
|
686
|
+
|
|
687
|
+
if current_take_u16 < take_u16:
|
|
688
|
+
console.print(
|
|
689
|
+
f"Current take is [{COLOR_PALETTE.P.RATE}]{current_take * 100.0:.2f}%[/{COLOR_PALETTE.P.RATE}]. "
|
|
690
|
+
f"Increasing to [{COLOR_PALETTE.P.RATE}]{take * 100:.2f}%."
|
|
691
|
+
)
|
|
692
|
+
with console.status(
|
|
693
|
+
f":satellite: Sending decrease_take_extrinsic call on [white]{meshtensor}[/white] ..."
|
|
694
|
+
):
|
|
695
|
+
call = await meshtensor.substrate.compose_call(
|
|
696
|
+
call_module="MeshtensorModule",
|
|
697
|
+
call_function="increase_take",
|
|
698
|
+
call_params={
|
|
699
|
+
"hotkey": delegate_ss58,
|
|
700
|
+
"take": take_u16,
|
|
701
|
+
},
|
|
702
|
+
)
|
|
703
|
+
success, err, ext_receipt = await meshtensor.sign_and_send_extrinsic(
|
|
704
|
+
call, wallet, proxy=proxy
|
|
705
|
+
)
|
|
706
|
+
|
|
707
|
+
else:
|
|
708
|
+
console.print(
|
|
709
|
+
f"Current take is [{COLOR_PALETTE.P.RATE}]{current_take * 100.0:.2f}%[/{COLOR_PALETTE.P.RATE}]. "
|
|
710
|
+
f"Decreasing to [{COLOR_PALETTE.P.RATE}]{take * 100:.2f}%."
|
|
711
|
+
)
|
|
712
|
+
with console.status(
|
|
713
|
+
f":satellite: Sending increase_take_extrinsic call on [white]{meshtensor}[/white] ..."
|
|
714
|
+
):
|
|
715
|
+
call = await meshtensor.substrate.compose_call(
|
|
716
|
+
call_module="MeshtensorModule",
|
|
717
|
+
call_function="decrease_take",
|
|
718
|
+
call_params={
|
|
719
|
+
"hotkey": delegate_ss58,
|
|
720
|
+
"take": take_u16,
|
|
721
|
+
},
|
|
722
|
+
)
|
|
723
|
+
success, err, ext_receipt = await meshtensor.sign_and_send_extrinsic(
|
|
724
|
+
call, wallet, proxy=proxy
|
|
725
|
+
)
|
|
726
|
+
|
|
727
|
+
if not success:
|
|
728
|
+
print_error(err)
|
|
729
|
+
ext_id = None
|
|
730
|
+
else:
|
|
731
|
+
print_success("Success")
|
|
732
|
+
ext_id = await ext_receipt.get_extrinsic_identifier()
|
|
733
|
+
await print_extrinsic_id(ext_receipt)
|
|
734
|
+
return success, ext_id
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
# commands
|
|
738
|
+
|
|
739
|
+
|
|
740
|
+
async def sudo_set_hyperparameter(
|
|
741
|
+
wallet: Wallet,
|
|
742
|
+
meshtensor: "MeshtensorInterface",
|
|
743
|
+
netuid: int,
|
|
744
|
+
proxy: Optional[str],
|
|
745
|
+
param_name: str,
|
|
746
|
+
param_value: Optional[str],
|
|
747
|
+
prompt: bool,
|
|
748
|
+
json_output: bool,
|
|
749
|
+
) -> tuple[bool, str, Optional[str]]:
|
|
750
|
+
"""Set subnet hyperparameters."""
|
|
751
|
+
is_allowed_value, value = allowed_value(param_name, param_value)
|
|
752
|
+
if not is_allowed_value:
|
|
753
|
+
err_msg = (
|
|
754
|
+
f"Hyperparameter [dark_orange]{param_name}[/dark_orange] value is not within bounds. "
|
|
755
|
+
f"Value is {param_value} but must be {value}"
|
|
756
|
+
)
|
|
757
|
+
if json_output:
|
|
758
|
+
json_str = json.dumps(
|
|
759
|
+
{"success": False, "err_msg": err_msg, "extrinsic_identifier": None},
|
|
760
|
+
ensure_ascii=True,
|
|
761
|
+
)
|
|
762
|
+
sys.stdout.write(json_str + "\n")
|
|
763
|
+
sys.stdout.flush()
|
|
764
|
+
else:
|
|
765
|
+
print_error(err_msg)
|
|
766
|
+
return False, err_msg, None
|
|
767
|
+
if json_output:
|
|
768
|
+
prompt = False
|
|
769
|
+
success, err_msg, ext_id = await set_hyperparameter_extrinsic(
|
|
770
|
+
meshtensor, wallet, netuid, proxy, param_name, value, prompt=prompt
|
|
771
|
+
)
|
|
772
|
+
if json_output:
|
|
773
|
+
return success, err_msg, ext_id
|
|
774
|
+
if success:
|
|
775
|
+
console.print("\n")
|
|
776
|
+
print_verbose("Fetching hyperparameters")
|
|
777
|
+
await get_hyperparameters(meshtensor, netuid=netuid)
|
|
778
|
+
return success, err_msg, ext_id
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
def _sanitize_json_string(
|
|
782
|
+
value: Union[str, int, float, bool, None],
|
|
783
|
+
) -> Union[str, int, float, bool, None]:
|
|
784
|
+
"""Sanitize string values for JSON output by removing control characters.
|
|
785
|
+
|
|
786
|
+
Non-string values are returned as-is.
|
|
787
|
+
"""
|
|
788
|
+
if isinstance(value, str):
|
|
789
|
+
# Remove all control characters (0x00-0x1F and 0x7F-0x9F) and replace with space
|
|
790
|
+
sanitized = re.sub(r"[\x00-\x1f\x7f-\x9f]", " ", value)
|
|
791
|
+
# Collapse multiple spaces into single space
|
|
792
|
+
sanitized = " ".join(sanitized.split())
|
|
793
|
+
return sanitized
|
|
794
|
+
return value
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
async def get_hyperparameters(
|
|
798
|
+
meshtensor: "MeshtensorInterface",
|
|
799
|
+
netuid: int,
|
|
800
|
+
json_output: bool = False,
|
|
801
|
+
show_descriptions: bool = True,
|
|
802
|
+
) -> bool:
|
|
803
|
+
"""View hyperparameters of a subnetwork."""
|
|
804
|
+
print_verbose("Fetching hyperparameters")
|
|
805
|
+
try:
|
|
806
|
+
if not await meshtensor.subnet_exists(netuid):
|
|
807
|
+
error_msg = f"Subnet with netuid {netuid} does not exist."
|
|
808
|
+
if json_output:
|
|
809
|
+
json_str = json.dumps({"error": error_msg}, ensure_ascii=True)
|
|
810
|
+
sys.stdout.write(json_str + "\n")
|
|
811
|
+
sys.stdout.flush()
|
|
812
|
+
else:
|
|
813
|
+
print_error(error_msg)
|
|
814
|
+
return False
|
|
815
|
+
subnet, subnet_info = await asyncio.gather(
|
|
816
|
+
meshtensor.get_subnet_hyperparameters(netuid), meshtensor.subnet(netuid)
|
|
817
|
+
)
|
|
818
|
+
if subnet_info is None:
|
|
819
|
+
error_msg = f"Subnet with netuid {netuid} does not exist."
|
|
820
|
+
if json_output:
|
|
821
|
+
json_str = json.dumps({"error": error_msg}, ensure_ascii=True)
|
|
822
|
+
sys.stdout.write(json_str + "\n")
|
|
823
|
+
sys.stdout.flush()
|
|
824
|
+
else:
|
|
825
|
+
print_error(error_msg)
|
|
826
|
+
return False
|
|
827
|
+
except Exception as e:
|
|
828
|
+
if json_output:
|
|
829
|
+
json_str = json.dumps({"error": str(e)}, ensure_ascii=True)
|
|
830
|
+
sys.stdout.write(json_str + "\n")
|
|
831
|
+
sys.stdout.flush()
|
|
832
|
+
else:
|
|
833
|
+
raise
|
|
834
|
+
return False
|
|
835
|
+
|
|
836
|
+
# Determine if we should show extended info (descriptions, ownership)
|
|
837
|
+
show_extended = show_descriptions and not json_output
|
|
838
|
+
|
|
839
|
+
if show_extended:
|
|
840
|
+
table = Table(
|
|
841
|
+
Column("[white]HYPERPARAMETER", style=COLOR_PALETTE.SU.HYPERPARAMETER),
|
|
842
|
+
Column("[white]VALUE", style=COLOR_PALETTE.SU.VALUE),
|
|
843
|
+
Column("[white]NORMALIZED", style=COLOR_PALETTE.SU.NORMAL),
|
|
844
|
+
Column("[white]OWNER SETTABLE", style="bright_cyan"),
|
|
845
|
+
Column("[white]DESCRIPTION", style="dim", overflow="fold"),
|
|
846
|
+
title=f"[{COLOR_PALETTE.G.HEADER}]\nSubnet Hyperparameters\n NETUID: "
|
|
847
|
+
f"[{COLOR_PALETTE.G.SUBHEAD}]{netuid}"
|
|
848
|
+
f"{f' ({subnet_info.subnet_name})' if subnet_info.subnet_name is not None else ''}"
|
|
849
|
+
f"[/{COLOR_PALETTE.G.SUBHEAD}]"
|
|
850
|
+
f" - Network: [{COLOR_PALETTE.G.SUBHEAD}]{meshtensor.network}[/{COLOR_PALETTE.G.SUBHEAD}]\n",
|
|
851
|
+
show_footer=True,
|
|
852
|
+
width=None,
|
|
853
|
+
pad_edge=False,
|
|
854
|
+
box=box.SIMPLE,
|
|
855
|
+
show_edge=True,
|
|
856
|
+
)
|
|
857
|
+
else:
|
|
858
|
+
table = Table(
|
|
859
|
+
Column("[white]HYPERPARAMETER", style=COLOR_PALETTE.SU.HYPERPARAMETER),
|
|
860
|
+
Column("[white]VALUE", style=COLOR_PALETTE.SU.VALUE),
|
|
861
|
+
Column("[white]NORMALIZED", style=COLOR_PALETTE.SU.NORMAL),
|
|
862
|
+
title=f"[{COLOR_PALETTE.G.HEADER}]\nSubnet Hyperparameters\n NETUID: "
|
|
863
|
+
f"[{COLOR_PALETTE.G.SUBHEAD}]{netuid}"
|
|
864
|
+
f"{f' ({subnet_info.subnet_name})' if subnet_info.subnet_name is not None else ''}"
|
|
865
|
+
f"[/{COLOR_PALETTE.G.SUBHEAD}]"
|
|
866
|
+
f" - Network: [{COLOR_PALETTE.G.SUBHEAD}]{meshtensor.network}[/{COLOR_PALETTE.G.SUBHEAD}]\n",
|
|
867
|
+
show_footer=True,
|
|
868
|
+
width=None,
|
|
869
|
+
pad_edge=False,
|
|
870
|
+
box=box.SIMPLE,
|
|
871
|
+
show_edge=True,
|
|
872
|
+
)
|
|
873
|
+
dict_out = []
|
|
874
|
+
|
|
875
|
+
normalized_values = normalize_hyperparameters(subnet, json_output=json_output)
|
|
876
|
+
sorted_values = sorted(normalized_values, key=lambda x: x[0])
|
|
877
|
+
for param, value, norm_value in sorted_values:
|
|
878
|
+
if not json_output:
|
|
879
|
+
if show_extended:
|
|
880
|
+
# Get metadata for this hyperparameter
|
|
881
|
+
metadata = HYPERPARAMS_METADATA.get(param, {})
|
|
882
|
+
description = metadata.get("description", "No description available.")
|
|
883
|
+
|
|
884
|
+
# Check actual ownership from HYPERPARAMS
|
|
885
|
+
_, root_sudo = HYPERPARAMS.get(param, ("", RootSudoOnly.FALSE))
|
|
886
|
+
if root_sudo == RootSudoOnly.TRUE:
|
|
887
|
+
owner_settable_str = "[red]No (Root Only)[/red]"
|
|
888
|
+
elif root_sudo == RootSudoOnly.COMPLICATED:
|
|
889
|
+
owner_settable_str = "[yellow]COMPLICATED (Owner/Sudo)[/yellow]"
|
|
890
|
+
else:
|
|
891
|
+
owner_settable_str = "[green]Yes[/green]"
|
|
892
|
+
|
|
893
|
+
# Format description with docs link if available
|
|
894
|
+
docs_link = metadata.get("docs_link", "")
|
|
895
|
+
if docs_link:
|
|
896
|
+
# Use Rich markup to create description with clickable bright blue [link] at the end
|
|
897
|
+
description_with_link = f"{description} [bright_blue underline link=https://{docs_link}]link[/]"
|
|
898
|
+
else:
|
|
899
|
+
description_with_link = description
|
|
900
|
+
|
|
901
|
+
table.add_row(
|
|
902
|
+
" " + param,
|
|
903
|
+
value,
|
|
904
|
+
norm_value,
|
|
905
|
+
owner_settable_str,
|
|
906
|
+
description_with_link,
|
|
907
|
+
)
|
|
908
|
+
else:
|
|
909
|
+
table.add_row(" " + param, value, norm_value)
|
|
910
|
+
else:
|
|
911
|
+
metadata = HYPERPARAMS_METADATA.get(param, {})
|
|
912
|
+
# Sanitize all string fields for JSON output - remove control characters
|
|
913
|
+
description = metadata.get("description", "No description available.")
|
|
914
|
+
side_effects = metadata.get("side_effects", "No side effects documented.")
|
|
915
|
+
docs_link = metadata.get("docs_link", "")
|
|
916
|
+
|
|
917
|
+
# Sanitize all string values to ensure valid JSON output
|
|
918
|
+
dict_out.append(
|
|
919
|
+
{
|
|
920
|
+
"hyperparameter": _sanitize_json_string(str(param)),
|
|
921
|
+
"value": _sanitize_json_string(value),
|
|
922
|
+
"normalized_value": _sanitize_json_string(norm_value),
|
|
923
|
+
"owner_settable": bool(metadata.get("owner_settable", False)),
|
|
924
|
+
"description": _sanitize_json_string(description),
|
|
925
|
+
"side_effects": _sanitize_json_string(side_effects),
|
|
926
|
+
"docs_link": _sanitize_json_string(docs_link),
|
|
927
|
+
}
|
|
928
|
+
)
|
|
929
|
+
if json_output:
|
|
930
|
+
# Use ensure_ascii=True to properly escape all non-ASCII and control characters
|
|
931
|
+
# Write directly to stdout to avoid any Rich Console formatting
|
|
932
|
+
import sys
|
|
933
|
+
|
|
934
|
+
json_str = json.dumps(dict_out, ensure_ascii=True)
|
|
935
|
+
sys.stdout.write(json_str + "\n")
|
|
936
|
+
sys.stdout.flush()
|
|
937
|
+
return True
|
|
938
|
+
else:
|
|
939
|
+
console.print(table)
|
|
940
|
+
if show_extended:
|
|
941
|
+
console.print(
|
|
942
|
+
"\n[dim]💡 Tip: Use [bold]meshcli sudo set --param <name> --value <value>[/bold] to modify hyperparameters."
|
|
943
|
+
)
|
|
944
|
+
console.print(
|
|
945
|
+
"[dim]💡 Tip: Subnet owners can set parameters marked '[green]Yes[/green]'. "
|
|
946
|
+
"Parameters marked '[red]No (Root Only)[/red]' require root sudo access."
|
|
947
|
+
)
|
|
948
|
+
console.print(
|
|
949
|
+
"[dim]💡 Tip: To set custom hyperparameters not in this list, use the exact parameter name from the chain metadata."
|
|
950
|
+
)
|
|
951
|
+
console.print(
|
|
952
|
+
f"[dim] Example: [bold]meshcli sudo set --netuid {netuid} --param custom_param_name --value 123[/bold]"
|
|
953
|
+
)
|
|
954
|
+
console.print(
|
|
955
|
+
"[dim] The parameter name must match exactly as defined in the chain's AdminUtils pallet metadata."
|
|
956
|
+
)
|
|
957
|
+
console.print(
|
|
958
|
+
"[dim]📚 For detailed documentation, visit: [link]https://docs.meshtensor.com[/link]"
|
|
959
|
+
)
|
|
960
|
+
return True
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
async def get_senate(
|
|
964
|
+
meshtensor: "MeshtensorInterface", json_output: bool = False
|
|
965
|
+
) -> None:
|
|
966
|
+
"""View Meshtensor's senate members"""
|
|
967
|
+
with console.status(
|
|
968
|
+
f":satellite: Syncing with chain: [white]{meshtensor}[/white] ...",
|
|
969
|
+
spinner="aesthetic",
|
|
970
|
+
) as status:
|
|
971
|
+
print_verbose("Fetching senate members", status)
|
|
972
|
+
senate_members = await _get_senate_members(meshtensor)
|
|
973
|
+
|
|
974
|
+
print_verbose("Fetching member details from Github and on-chain identities")
|
|
975
|
+
delegate_info: dict[
|
|
976
|
+
str, DelegatesDetails
|
|
977
|
+
] = await meshtensor.get_delegate_identities()
|
|
978
|
+
|
|
979
|
+
table = Table(
|
|
980
|
+
Column(
|
|
981
|
+
"[bold white]NAME",
|
|
982
|
+
style="bright_cyan",
|
|
983
|
+
no_wrap=True,
|
|
984
|
+
),
|
|
985
|
+
Column(
|
|
986
|
+
"[bold white]ADDRESS",
|
|
987
|
+
style="bright_magenta",
|
|
988
|
+
no_wrap=True,
|
|
989
|
+
),
|
|
990
|
+
title=f"[underline dark_orange]Senate[/underline dark_orange]\n[dark_orange]Network: {meshtensor.network}\n",
|
|
991
|
+
show_footer=True,
|
|
992
|
+
show_edge=False,
|
|
993
|
+
expand=False,
|
|
994
|
+
border_style="bright_black",
|
|
995
|
+
leading=True,
|
|
996
|
+
)
|
|
997
|
+
dict_output = []
|
|
998
|
+
|
|
999
|
+
for ss58_address in senate_members:
|
|
1000
|
+
member_name = (
|
|
1001
|
+
delegate_info[ss58_address].display
|
|
1002
|
+
if ss58_address in delegate_info
|
|
1003
|
+
else "~"
|
|
1004
|
+
)
|
|
1005
|
+
table.add_row(
|
|
1006
|
+
member_name,
|
|
1007
|
+
ss58_address,
|
|
1008
|
+
)
|
|
1009
|
+
dict_output.append({"name": member_name, "ss58_address": ss58_address})
|
|
1010
|
+
if json_output:
|
|
1011
|
+
json_console.print(json.dumps(dict_output, ensure_ascii=True))
|
|
1012
|
+
return console.print(table)
|
|
1013
|
+
|
|
1014
|
+
|
|
1015
|
+
async def proposals(
|
|
1016
|
+
meshtensor: "MeshtensorInterface", verbose: bool, json_output: bool = False
|
|
1017
|
+
) -> None:
|
|
1018
|
+
console.print(
|
|
1019
|
+
":satellite: Syncing with chain: [white]{}[/white] ...".format(
|
|
1020
|
+
meshtensor.network
|
|
1021
|
+
)
|
|
1022
|
+
)
|
|
1023
|
+
block_hash = await meshtensor.substrate.get_chain_head()
|
|
1024
|
+
senate_members, all_proposals, current_block = await asyncio.gather(
|
|
1025
|
+
_get_senate_members(meshtensor, block_hash),
|
|
1026
|
+
_get_proposals(meshtensor, block_hash),
|
|
1027
|
+
meshtensor.substrate.get_block_number(block_hash),
|
|
1028
|
+
)
|
|
1029
|
+
|
|
1030
|
+
registered_delegate_info: dict[
|
|
1031
|
+
str, DelegatesDetails
|
|
1032
|
+
] = await meshtensor.get_delegate_identities()
|
|
1033
|
+
|
|
1034
|
+
title = (
|
|
1035
|
+
f"[bold #4196D6]Meshtensor Governance Proposals[/bold #4196D6]\n"
|
|
1036
|
+
f"[steel_blue3]Current Block:[/steel_blue3] {current_block}\t"
|
|
1037
|
+
f"[steel_blue3]Network:[/steel_blue3] {meshtensor.network}\n\n"
|
|
1038
|
+
f"[steel_blue3]Active Proposals:[/steel_blue3] {len(all_proposals)}\t"
|
|
1039
|
+
f"[steel_blue3]Senate Size:[/steel_blue3] {len(senate_members)}\n"
|
|
1040
|
+
)
|
|
1041
|
+
table = Table(
|
|
1042
|
+
Column(
|
|
1043
|
+
"[white]HASH",
|
|
1044
|
+
style="light_goldenrod2",
|
|
1045
|
+
no_wrap=True,
|
|
1046
|
+
),
|
|
1047
|
+
Column("[white]THRESHOLD", style="rgb(42,161,152)"),
|
|
1048
|
+
Column("[white]AYES", style="green"),
|
|
1049
|
+
Column("[white]NAYS", style="red"),
|
|
1050
|
+
Column(
|
|
1051
|
+
"[white]VOTES",
|
|
1052
|
+
style="rgb(50,163,219)",
|
|
1053
|
+
),
|
|
1054
|
+
Column("[white]END", style="bright_cyan"),
|
|
1055
|
+
Column("[white]CALLDATA", style="dark_sea_green", width=30),
|
|
1056
|
+
title=title,
|
|
1057
|
+
show_footer=True,
|
|
1058
|
+
box=box.SIMPLE_HEAVY,
|
|
1059
|
+
pad_edge=False,
|
|
1060
|
+
width=None,
|
|
1061
|
+
border_style="bright_black",
|
|
1062
|
+
)
|
|
1063
|
+
dict_output = []
|
|
1064
|
+
for hash_, (call_data, vote_data) in all_proposals.items():
|
|
1065
|
+
blocks_remaining = vote_data.end - current_block
|
|
1066
|
+
if blocks_remaining > 0:
|
|
1067
|
+
duration_str = blocks_to_duration(blocks_remaining)
|
|
1068
|
+
vote_end_cell = f"{vote_data.end} [dim](in {duration_str})[/dim]"
|
|
1069
|
+
else:
|
|
1070
|
+
vote_end_cell = f"{vote_data.end} [red](expired)[/red]"
|
|
1071
|
+
|
|
1072
|
+
ayes_threshold = (
|
|
1073
|
+
(len(vote_data.ayes) / vote_data.threshold * 100)
|
|
1074
|
+
if vote_data.threshold > 0
|
|
1075
|
+
else 0
|
|
1076
|
+
)
|
|
1077
|
+
nays_threshold = (
|
|
1078
|
+
(len(vote_data.nays) / vote_data.threshold * 100)
|
|
1079
|
+
if vote_data.threshold > 0
|
|
1080
|
+
else 0
|
|
1081
|
+
)
|
|
1082
|
+
f_call_data = format_call_data(call_data)
|
|
1083
|
+
table.add_row(
|
|
1084
|
+
hash_ if verbose else f"{hash_[:4]}...{hash_[-4:]}",
|
|
1085
|
+
str(vote_data.threshold),
|
|
1086
|
+
f"{len(vote_data.ayes)} ({ayes_threshold:.2f}%)",
|
|
1087
|
+
f"{len(vote_data.nays)} ({nays_threshold:.2f}%)",
|
|
1088
|
+
display_votes(vote_data, registered_delegate_info),
|
|
1089
|
+
vote_end_cell,
|
|
1090
|
+
f_call_data,
|
|
1091
|
+
)
|
|
1092
|
+
dict_output.append(
|
|
1093
|
+
{
|
|
1094
|
+
"hash": hash_,
|
|
1095
|
+
"threshold": vote_data.threshold,
|
|
1096
|
+
"ayes": len(vote_data.ayes),
|
|
1097
|
+
"nays": len(vote_data.nays),
|
|
1098
|
+
"votes": serialize_vote_data(vote_data, registered_delegate_info),
|
|
1099
|
+
"end": vote_data.end,
|
|
1100
|
+
"call_data": f_call_data,
|
|
1101
|
+
}
|
|
1102
|
+
)
|
|
1103
|
+
if json_output:
|
|
1104
|
+
json_console.print(json.dumps(dict_output, ensure_ascii=True))
|
|
1105
|
+
console.print(table)
|
|
1106
|
+
console.print(
|
|
1107
|
+
"\n[dim]* Both Ayes and Nays percentages are calculated relative to the proposal's threshold.[/dim]"
|
|
1108
|
+
)
|
|
1109
|
+
|
|
1110
|
+
|
|
1111
|
+
async def senate_vote(
|
|
1112
|
+
wallet: Wallet,
|
|
1113
|
+
meshtensor: "MeshtensorInterface",
|
|
1114
|
+
proxy: Optional[str],
|
|
1115
|
+
proposal_hash: str,
|
|
1116
|
+
vote: bool,
|
|
1117
|
+
prompt: bool,
|
|
1118
|
+
) -> bool:
|
|
1119
|
+
"""Vote in Meshtensor's governance protocol proposals"""
|
|
1120
|
+
|
|
1121
|
+
if not proposal_hash:
|
|
1122
|
+
print_error(
|
|
1123
|
+
"Aborting: Proposal hash not specified. View all proposals with the `proposals` command."
|
|
1124
|
+
)
|
|
1125
|
+
return False
|
|
1126
|
+
elif not _validate_proposal_hash(proposal_hash):
|
|
1127
|
+
print_error(
|
|
1128
|
+
"Aborting. Proposal hash is invalid. Proposal hashes should start with '0x' and be 32 bytes long"
|
|
1129
|
+
)
|
|
1130
|
+
return False
|
|
1131
|
+
|
|
1132
|
+
print_verbose(f"Fetching senate status of {wallet.hotkey_str}")
|
|
1133
|
+
hotkey_ss58 = get_hotkey_pub_ss58(wallet)
|
|
1134
|
+
if not await _is_senate_member(meshtensor, hotkey_ss58=hotkey_ss58):
|
|
1135
|
+
print_error(f"Aborting: Hotkey {hotkey_ss58} isn't a senate member.")
|
|
1136
|
+
return False
|
|
1137
|
+
|
|
1138
|
+
# Unlock the wallet.
|
|
1139
|
+
if not unlock_key(wallet, "hot").success and unlock_key(wallet, "cold").success:
|
|
1140
|
+
return False
|
|
1141
|
+
|
|
1142
|
+
console.print(f"Fetching proposals in [dark_orange]network: {meshtensor.network}")
|
|
1143
|
+
vote_data = await meshtensor.get_vote_data(proposal_hash, reuse_block=True)
|
|
1144
|
+
if not vote_data:
|
|
1145
|
+
print_error("Failed: Proposal not found.")
|
|
1146
|
+
return False
|
|
1147
|
+
|
|
1148
|
+
success = await vote_senate_extrinsic(
|
|
1149
|
+
meshtensor=meshtensor,
|
|
1150
|
+
wallet=wallet,
|
|
1151
|
+
proxy=proxy,
|
|
1152
|
+
proposal_hash=proposal_hash,
|
|
1153
|
+
proposal_idx=vote_data.index,
|
|
1154
|
+
vote=vote,
|
|
1155
|
+
wait_for_inclusion=True,
|
|
1156
|
+
wait_for_finalization=False,
|
|
1157
|
+
prompt=prompt,
|
|
1158
|
+
)
|
|
1159
|
+
|
|
1160
|
+
return success
|
|
1161
|
+
|
|
1162
|
+
|
|
1163
|
+
async def get_current_take(meshtensor: "MeshtensorInterface", wallet: Wallet):
|
|
1164
|
+
current_take = await meshtensor.current_take(get_hotkey_pub_ss58(wallet))
|
|
1165
|
+
return current_take
|
|
1166
|
+
|
|
1167
|
+
|
|
1168
|
+
async def display_current_take(meshtensor: "MeshtensorInterface", wallet: Wallet) -> None:
|
|
1169
|
+
current_take = await get_current_take(meshtensor, wallet)
|
|
1170
|
+
console.print(
|
|
1171
|
+
f"Current take is [{COLOR_PALETTE.P.RATE}]{current_take * 100.0:.2f}%"
|
|
1172
|
+
)
|
|
1173
|
+
|
|
1174
|
+
|
|
1175
|
+
async def set_take(
|
|
1176
|
+
wallet: Wallet, meshtensor: "MeshtensorInterface", take: float, proxy: Optional[str]
|
|
1177
|
+
) -> tuple[bool, Optional[str]]:
|
|
1178
|
+
"""Set delegate take."""
|
|
1179
|
+
|
|
1180
|
+
async def _do_set_take() -> tuple[bool, Optional[str]]:
|
|
1181
|
+
if take > 0.18 or take < 0:
|
|
1182
|
+
print_error("ERROR: Take value should not exceed 18% or be below 0%")
|
|
1183
|
+
return False, None
|
|
1184
|
+
|
|
1185
|
+
block_hash = await meshtensor.substrate.get_chain_head()
|
|
1186
|
+
hotkey_ss58 = get_hotkey_pub_ss58(wallet)
|
|
1187
|
+
netuids_registered = await meshtensor.get_netuids_for_hotkey(
|
|
1188
|
+
hotkey_ss58, block_hash=block_hash
|
|
1189
|
+
)
|
|
1190
|
+
if not len(netuids_registered) > 0:
|
|
1191
|
+
print_error(
|
|
1192
|
+
f"Hotkey [{COLOR_PALETTE.G.HK}]{hotkey_ss58}[/{COLOR_PALETTE.G.HK}] is not registered to"
|
|
1193
|
+
f" any subnet. Please register using [{COLOR_PALETTE.G.SUBHEAD}]`meshcli subnets register`"
|
|
1194
|
+
f"[{COLOR_PALETTE.G.SUBHEAD}] and try again."
|
|
1195
|
+
)
|
|
1196
|
+
return False, None
|
|
1197
|
+
|
|
1198
|
+
result: tuple[bool, Optional[str]] = await set_take_extrinsic(
|
|
1199
|
+
meshtensor=meshtensor,
|
|
1200
|
+
wallet=wallet,
|
|
1201
|
+
delegate_ss58=hotkey_ss58,
|
|
1202
|
+
take=take,
|
|
1203
|
+
proxy=proxy,
|
|
1204
|
+
)
|
|
1205
|
+
success, ext_id = result
|
|
1206
|
+
|
|
1207
|
+
if not success:
|
|
1208
|
+
print_error("Could not set the take")
|
|
1209
|
+
return False, None
|
|
1210
|
+
else:
|
|
1211
|
+
new_take = await get_current_take(meshtensor, wallet)
|
|
1212
|
+
console.print(
|
|
1213
|
+
f"New take is [{COLOR_PALETTE.P.RATE}]{new_take * 100.0:.2f}%"
|
|
1214
|
+
)
|
|
1215
|
+
return True, ext_id
|
|
1216
|
+
|
|
1217
|
+
console.print(
|
|
1218
|
+
f"Setting take on [{COLOR_PALETTE.G.LINKS}]network: {meshtensor.network}"
|
|
1219
|
+
)
|
|
1220
|
+
|
|
1221
|
+
if not unlock_key(wallet, "hot").success and unlock_key(wallet, "cold").success:
|
|
1222
|
+
return False, None
|
|
1223
|
+
|
|
1224
|
+
return await _do_set_take()
|
|
1225
|
+
|
|
1226
|
+
|
|
1227
|
+
async def trim(
|
|
1228
|
+
wallet: Wallet,
|
|
1229
|
+
meshtensor: "MeshtensorInterface",
|
|
1230
|
+
netuid: int,
|
|
1231
|
+
proxy: Optional[str],
|
|
1232
|
+
max_n: int,
|
|
1233
|
+
period: int,
|
|
1234
|
+
prompt: bool,
|
|
1235
|
+
decline: bool,
|
|
1236
|
+
quiet: bool,
|
|
1237
|
+
json_output: bool,
|
|
1238
|
+
) -> bool:
|
|
1239
|
+
"""
|
|
1240
|
+
Trims a subnet's UIDs to a specified amount
|
|
1241
|
+
"""
|
|
1242
|
+
print_verbose("Confirming subnet owner")
|
|
1243
|
+
subnet_owner = await meshtensor.query(
|
|
1244
|
+
module="MeshtensorModule",
|
|
1245
|
+
storage_function="SubnetOwner",
|
|
1246
|
+
params=[netuid],
|
|
1247
|
+
)
|
|
1248
|
+
# TODO should this check proxy also?
|
|
1249
|
+
if subnet_owner != wallet.coldkeypub.ss58_address:
|
|
1250
|
+
err_msg = "This wallet doesn't own the specified subnet."
|
|
1251
|
+
if json_output:
|
|
1252
|
+
json_console.print_json(data={"success": False, "message": err_msg})
|
|
1253
|
+
else:
|
|
1254
|
+
print_error(err_msg)
|
|
1255
|
+
return False
|
|
1256
|
+
if prompt and not json_output:
|
|
1257
|
+
if not confirm_action(
|
|
1258
|
+
f"You are about to trim UIDs on SN{netuid} to a limit of {max_n}",
|
|
1259
|
+
default=False,
|
|
1260
|
+
decline=decline,
|
|
1261
|
+
quiet=quiet,
|
|
1262
|
+
):
|
|
1263
|
+
print_error("User aborted.")
|
|
1264
|
+
call = await meshtensor.substrate.compose_call(
|
|
1265
|
+
call_module="AdminUtils",
|
|
1266
|
+
call_function="sudo_trim_to_max_allowed_uids",
|
|
1267
|
+
call_params={"netuid": netuid, "max_n": max_n},
|
|
1268
|
+
)
|
|
1269
|
+
success, err_msg, ext_receipt = await meshtensor.sign_and_send_extrinsic(
|
|
1270
|
+
call=call, wallet=wallet, era={"period": period}, proxy=proxy
|
|
1271
|
+
)
|
|
1272
|
+
if not success:
|
|
1273
|
+
if json_output:
|
|
1274
|
+
json_console.print_json(
|
|
1275
|
+
data={
|
|
1276
|
+
"success": False,
|
|
1277
|
+
"message": err_msg,
|
|
1278
|
+
"extrinsic_identifier": None,
|
|
1279
|
+
}
|
|
1280
|
+
)
|
|
1281
|
+
else:
|
|
1282
|
+
print_error(err_msg)
|
|
1283
|
+
return False
|
|
1284
|
+
else:
|
|
1285
|
+
ext_id = await ext_receipt.get_extrinsic_identifier()
|
|
1286
|
+
msg = f"Successfully trimmed UIDs on SN{netuid} to {max_n}"
|
|
1287
|
+
if json_output:
|
|
1288
|
+
json_console.print_json(
|
|
1289
|
+
data={"success": True, "message": msg, "extrinsic_identifier": ext_id}
|
|
1290
|
+
)
|
|
1291
|
+
else:
|
|
1292
|
+
await print_extrinsic_id(ext_receipt)
|
|
1293
|
+
print_success(f"[dark_sea_green3]{msg}[/dark_sea_green3]")
|
|
1294
|
+
return True
|