bittensor-cli 8.4.3__py3-none-any.whl → 9.0.0rc2__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.
- bittensor_cli/__init__.py +2 -2
- bittensor_cli/cli.py +1508 -1385
- bittensor_cli/src/__init__.py +627 -197
- bittensor_cli/src/bittensor/balances.py +41 -8
- bittensor_cli/src/bittensor/chain_data.py +557 -428
- bittensor_cli/src/bittensor/extrinsics/registration.py +161 -47
- bittensor_cli/src/bittensor/extrinsics/root.py +14 -8
- bittensor_cli/src/bittensor/extrinsics/transfer.py +14 -21
- bittensor_cli/src/bittensor/minigraph.py +46 -8
- bittensor_cli/src/bittensor/subtensor_interface.py +572 -253
- bittensor_cli/src/bittensor/utils.py +326 -75
- bittensor_cli/src/commands/stake/__init__.py +154 -0
- bittensor_cli/src/commands/stake/children_hotkeys.py +121 -87
- bittensor_cli/src/commands/stake/move.py +1000 -0
- bittensor_cli/src/commands/stake/stake.py +1637 -1264
- bittensor_cli/src/commands/subnets/__init__.py +0 -0
- bittensor_cli/src/commands/subnets/price.py +867 -0
- bittensor_cli/src/commands/subnets/subnets.py +2055 -0
- bittensor_cli/src/commands/sudo.py +529 -26
- bittensor_cli/src/commands/wallets.py +234 -544
- bittensor_cli/src/commands/weights.py +15 -11
- {bittensor_cli-8.4.3.dist-info → bittensor_cli-9.0.0rc2.dist-info}/METADATA +7 -4
- bittensor_cli-9.0.0rc2.dist-info/RECORD +32 -0
- bittensor_cli/src/bittensor/async_substrate_interface.py +0 -2748
- bittensor_cli/src/commands/root.py +0 -1752
- bittensor_cli/src/commands/subnets.py +0 -897
- bittensor_cli-8.4.3.dist-info/RECORD +0 -31
- {bittensor_cli-8.4.3.dist-info → bittensor_cli-9.0.0rc2.dist-info}/WHEEL +0 -0
- {bittensor_cli-8.4.3.dist-info → bittensor_cli-9.0.0rc2.dist-info}/entry_points.txt +0 -0
- {bittensor_cli-8.4.3.dist-info → bittensor_cli-9.0.0rc2.dist-info}/top_level.txt +0 -0
@@ -1,11 +1,15 @@
|
|
1
1
|
import asyncio
|
2
|
-
from typing import TYPE_CHECKING, Union
|
2
|
+
from typing import TYPE_CHECKING, Union, Optional
|
3
3
|
|
4
|
+
import typer
|
4
5
|
from bittensor_wallet import Wallet
|
6
|
+
from bittensor_wallet.errors import KeyFileError
|
5
7
|
from rich import box
|
6
8
|
from rich.table import Column, Table
|
9
|
+
from rich.prompt import Confirm
|
10
|
+
from scalecodec import GenericCall
|
7
11
|
|
8
|
-
from bittensor_cli.src import HYPERPARAMS
|
12
|
+
from bittensor_cli.src import HYPERPARAMS, DelegatesDetails, COLOR_PALETTE
|
9
13
|
from bittensor_cli.src.bittensor.chain_data import decode_account_id
|
10
14
|
from bittensor_cli.src.bittensor.utils import (
|
11
15
|
console,
|
@@ -13,11 +17,13 @@ from bittensor_cli.src.bittensor.utils import (
|
|
13
17
|
print_error,
|
14
18
|
print_verbose,
|
15
19
|
normalize_hyperparameters,
|
16
|
-
unlock_key,
|
17
20
|
)
|
18
21
|
|
19
22
|
if TYPE_CHECKING:
|
20
|
-
from bittensor_cli.src.bittensor.subtensor_interface import
|
23
|
+
from bittensor_cli.src.bittensor.subtensor_interface import (
|
24
|
+
SubtensorInterface,
|
25
|
+
ProposalVoteData,
|
26
|
+
)
|
21
27
|
|
22
28
|
|
23
29
|
# helpers and extrinsics
|
@@ -89,28 +95,30 @@ async def set_hyperparameter_extrinsic(
|
|
89
95
|
finalization/inclusion, the response is `True`.
|
90
96
|
"""
|
91
97
|
print_verbose("Confirming subnet owner")
|
92
|
-
|
98
|
+
subnet_owner = await subtensor.query(
|
93
99
|
module="SubtensorModule",
|
94
100
|
storage_function="SubnetOwner",
|
95
101
|
params=[netuid],
|
96
102
|
)
|
97
|
-
subnet_owner = decode_account_id(subnet_owner_[0])
|
98
103
|
if subnet_owner != wallet.coldkeypub.ss58_address:
|
99
104
|
err_console.print(
|
100
105
|
":cross_mark: [red]This wallet doesn't own the specified subnet.[/red]"
|
101
106
|
)
|
102
107
|
return False
|
103
108
|
|
104
|
-
|
109
|
+
try:
|
110
|
+
wallet.unlock_coldkey()
|
111
|
+
except KeyFileError:
|
112
|
+
err_console.print("Error decrypting coldkey (possibly incorrect password)")
|
105
113
|
return False
|
106
114
|
|
107
|
-
extrinsic
|
115
|
+
extrinsic = HYPERPARAMS.get(parameter)
|
108
116
|
if extrinsic is None:
|
109
117
|
err_console.print(":cross_mark: [red]Invalid hyperparameter specified.[/red]")
|
110
118
|
return False
|
111
119
|
|
112
120
|
with console.status(
|
113
|
-
f":satellite: Setting hyperparameter {parameter} to {value} on subnet: {netuid} ...",
|
121
|
+
f":satellite: Setting hyperparameter [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{parameter}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] to [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{value}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] on subnet: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] ...",
|
114
122
|
spinner="earth",
|
115
123
|
):
|
116
124
|
substrate = subtensor.substrate
|
@@ -144,17 +152,11 @@ async def set_hyperparameter_extrinsic(
|
|
144
152
|
call_params[str(value_argument["name"])] = value
|
145
153
|
|
146
154
|
# create extrinsic call
|
147
|
-
|
155
|
+
call = await substrate.compose_call(
|
148
156
|
call_module="AdminUtils",
|
149
157
|
call_function=extrinsic,
|
150
158
|
call_params=call_params,
|
151
159
|
)
|
152
|
-
if sudo_:
|
153
|
-
call = await substrate.compose_call(
|
154
|
-
call_module="Sudo", call_function="sudo", call_params={"call": call_}
|
155
|
-
)
|
156
|
-
else:
|
157
|
-
call = call_
|
158
160
|
success, err_msg = await subtensor.sign_and_send_extrinsic(
|
159
161
|
call, wallet, wait_for_inclusion, wait_for_finalization
|
160
162
|
)
|
@@ -165,11 +167,289 @@ async def set_hyperparameter_extrinsic(
|
|
165
167
|
# Successful registration, final check for membership
|
166
168
|
else:
|
167
169
|
console.print(
|
168
|
-
f":white_heavy_check_mark: [
|
170
|
+
f":white_heavy_check_mark: [dark_sea_green3]Hyperparameter {parameter} changed to {value}[/dark_sea_green3]"
|
169
171
|
)
|
170
172
|
return True
|
171
173
|
|
172
174
|
|
175
|
+
async def _get_senate_members(
|
176
|
+
subtensor: "SubtensorInterface", block_hash: Optional[str] = None
|
177
|
+
) -> list[str]:
|
178
|
+
"""
|
179
|
+
Gets all members of the senate on the given subtensor's network
|
180
|
+
|
181
|
+
:param subtensor: SubtensorInterface object to use for the query
|
182
|
+
|
183
|
+
:return: list of the senate members' ss58 addresses
|
184
|
+
"""
|
185
|
+
senate_members = await subtensor.query(
|
186
|
+
module="SenateMembers",
|
187
|
+
storage_function="Members",
|
188
|
+
params=None,
|
189
|
+
block_hash=block_hash,
|
190
|
+
)
|
191
|
+
try:
|
192
|
+
return [
|
193
|
+
decode_account_id(i[x][0]) for i in senate_members for x in range(len(i))
|
194
|
+
]
|
195
|
+
except (IndexError, TypeError):
|
196
|
+
err_console.print("Unable to retrieve senate members.")
|
197
|
+
return []
|
198
|
+
|
199
|
+
|
200
|
+
async def _get_proposals(
|
201
|
+
subtensor: "SubtensorInterface", block_hash: str
|
202
|
+
) -> dict[str, tuple[dict, "ProposalVoteData"]]:
|
203
|
+
async def get_proposal_call_data(p_hash: str) -> Optional[GenericCall]:
|
204
|
+
proposal_data = await subtensor.query(
|
205
|
+
module="Triumvirate",
|
206
|
+
storage_function="ProposalOf",
|
207
|
+
block_hash=block_hash,
|
208
|
+
params=[p_hash],
|
209
|
+
)
|
210
|
+
return proposal_data
|
211
|
+
|
212
|
+
ph = await subtensor.query(
|
213
|
+
module="Triumvirate",
|
214
|
+
storage_function="Proposals",
|
215
|
+
params=None,
|
216
|
+
block_hash=block_hash,
|
217
|
+
)
|
218
|
+
|
219
|
+
try:
|
220
|
+
proposal_hashes: list[str] = [
|
221
|
+
f"0x{bytes(ph[0][x][0]).hex()}" for x in range(len(ph[0]))
|
222
|
+
]
|
223
|
+
except (IndexError, TypeError):
|
224
|
+
err_console.print("Unable to retrieve proposal vote data")
|
225
|
+
return {}
|
226
|
+
|
227
|
+
call_data_, vote_data_ = await asyncio.gather(
|
228
|
+
asyncio.gather(*[get_proposal_call_data(h) for h in proposal_hashes]),
|
229
|
+
asyncio.gather(*[subtensor.get_vote_data(h) for h in proposal_hashes]),
|
230
|
+
)
|
231
|
+
return {
|
232
|
+
proposal_hash: (cd, vd)
|
233
|
+
for cd, vd, proposal_hash in zip(call_data_, vote_data_, proposal_hashes)
|
234
|
+
}
|
235
|
+
|
236
|
+
|
237
|
+
def display_votes(
|
238
|
+
vote_data: "ProposalVoteData", delegate_info: dict[str, DelegatesDetails]
|
239
|
+
) -> str:
|
240
|
+
vote_list = list()
|
241
|
+
|
242
|
+
for address in vote_data.ayes:
|
243
|
+
vote_list.append(
|
244
|
+
"{}: {}".format(
|
245
|
+
delegate_info[address].display if address in delegate_info else address,
|
246
|
+
"[bold green]Aye[/bold green]",
|
247
|
+
)
|
248
|
+
)
|
249
|
+
|
250
|
+
for address in vote_data.nays:
|
251
|
+
vote_list.append(
|
252
|
+
"{}: {}".format(
|
253
|
+
delegate_info[address].display if address in delegate_info else address,
|
254
|
+
"[bold red]Nay[/bold red]",
|
255
|
+
)
|
256
|
+
)
|
257
|
+
|
258
|
+
return "\n".join(vote_list)
|
259
|
+
|
260
|
+
|
261
|
+
def format_call_data(call_data: dict) -> str:
|
262
|
+
# Extract the module and call details
|
263
|
+
module, call_details = next(iter(call_data.items()))
|
264
|
+
|
265
|
+
# Extract the call function name and arguments
|
266
|
+
call_info = call_details[0]
|
267
|
+
call_function, call_args = next(iter(call_info.items()))
|
268
|
+
|
269
|
+
# Extract the argument, handling tuple values
|
270
|
+
formatted_args = ", ".join(
|
271
|
+
str(arg[0]) if isinstance(arg, tuple) else str(arg)
|
272
|
+
for arg in call_args.values()
|
273
|
+
)
|
274
|
+
|
275
|
+
# Format the final output string
|
276
|
+
return f"{call_function}({formatted_args})"
|
277
|
+
|
278
|
+
|
279
|
+
def _validate_proposal_hash(proposal_hash: str) -> bool:
|
280
|
+
if proposal_hash[0:2] != "0x" or len(proposal_hash) != 66:
|
281
|
+
return False
|
282
|
+
else:
|
283
|
+
return True
|
284
|
+
|
285
|
+
|
286
|
+
async def _is_senate_member(subtensor: "SubtensorInterface", hotkey_ss58: str) -> bool:
|
287
|
+
"""
|
288
|
+
Checks if a given neuron (identified by its hotkey SS58 address) is a member of the Bittensor senate.
|
289
|
+
The senate is a key governance body within the Bittensor network, responsible for overseeing and
|
290
|
+
approving various network operations and proposals.
|
291
|
+
|
292
|
+
:param subtensor: SubtensorInterface object to use for the query
|
293
|
+
:param hotkey_ss58: The `SS58` address of the neuron's hotkey.
|
294
|
+
|
295
|
+
:return: `True` if the neuron is a senate member at the given block, `False` otherwise.
|
296
|
+
|
297
|
+
This function is crucial for understanding the governance dynamics of the Bittensor network and for
|
298
|
+
identifying the neurons that hold decision-making power within the network.
|
299
|
+
"""
|
300
|
+
|
301
|
+
senate_members = await _get_senate_members(subtensor)
|
302
|
+
|
303
|
+
if not hasattr(senate_members, "count"):
|
304
|
+
return False
|
305
|
+
|
306
|
+
return senate_members.count(hotkey_ss58) > 0
|
307
|
+
|
308
|
+
|
309
|
+
async def vote_senate_extrinsic(
|
310
|
+
subtensor: "SubtensorInterface",
|
311
|
+
wallet: Wallet,
|
312
|
+
proposal_hash: str,
|
313
|
+
proposal_idx: int,
|
314
|
+
vote: bool,
|
315
|
+
wait_for_inclusion: bool = False,
|
316
|
+
wait_for_finalization: bool = True,
|
317
|
+
prompt: bool = False,
|
318
|
+
) -> bool:
|
319
|
+
"""Votes ayes or nays on proposals.
|
320
|
+
|
321
|
+
:param subtensor: The SubtensorInterface object to use for the query
|
322
|
+
:param wallet: Bittensor wallet object, with coldkey and hotkey unlocked.
|
323
|
+
:param proposal_hash: The hash of the proposal for which voting data is requested.
|
324
|
+
:param proposal_idx: The index of the proposal to vote.
|
325
|
+
:param vote: Whether to vote aye or nay.
|
326
|
+
:param wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`, or returns
|
327
|
+
`False` if the extrinsic fails to enter the block within the timeout.
|
328
|
+
:param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning `True`,
|
329
|
+
or returns `False` if the extrinsic fails to be finalized within the timeout.
|
330
|
+
:param prompt: If `True`, the call waits for confirmation from the user before proceeding.
|
331
|
+
|
332
|
+
:return: Flag is `True` if extrinsic was finalized or included in the block. If we did not wait for
|
333
|
+
finalization/inclusion, the response is `True`.
|
334
|
+
"""
|
335
|
+
|
336
|
+
if prompt:
|
337
|
+
# Prompt user for confirmation.
|
338
|
+
if not Confirm.ask(f"Cast a vote of {vote}?"):
|
339
|
+
return False
|
340
|
+
|
341
|
+
with console.status(":satellite: Casting vote..", spinner="aesthetic"):
|
342
|
+
call = await subtensor.substrate.compose_call(
|
343
|
+
call_module="SubtensorModule",
|
344
|
+
call_function="vote",
|
345
|
+
call_params={
|
346
|
+
"hotkey": wallet.hotkey.ss58_address,
|
347
|
+
"proposal": proposal_hash,
|
348
|
+
"index": proposal_idx,
|
349
|
+
"approve": vote,
|
350
|
+
},
|
351
|
+
)
|
352
|
+
success, err_msg = await subtensor.sign_and_send_extrinsic(
|
353
|
+
call, wallet, wait_for_inclusion, wait_for_finalization
|
354
|
+
)
|
355
|
+
if not success:
|
356
|
+
err_console.print(f":cross_mark: [red]Failed[/red]: {err_msg}")
|
357
|
+
await asyncio.sleep(0.5)
|
358
|
+
return False
|
359
|
+
# Successful vote, final check for data
|
360
|
+
else:
|
361
|
+
if vote_data := await subtensor.get_vote_data(proposal_hash):
|
362
|
+
if (
|
363
|
+
vote_data.ayes.count(wallet.hotkey.ss58_address) > 0
|
364
|
+
or vote_data.nays.count(wallet.hotkey.ss58_address) > 0
|
365
|
+
):
|
366
|
+
console.print(":white_heavy_check_mark: [green]Vote cast.[/green]")
|
367
|
+
return True
|
368
|
+
else:
|
369
|
+
# hotkey not found in ayes/nays
|
370
|
+
err_console.print(
|
371
|
+
":cross_mark: [red]Unknown error. Couldn't find vote.[/red]"
|
372
|
+
)
|
373
|
+
return False
|
374
|
+
else:
|
375
|
+
return False
|
376
|
+
|
377
|
+
|
378
|
+
async def set_take_extrinsic(
|
379
|
+
subtensor: "SubtensorInterface",
|
380
|
+
wallet: Wallet,
|
381
|
+
delegate_ss58: str,
|
382
|
+
take: float = 0.0,
|
383
|
+
) -> bool:
|
384
|
+
"""
|
385
|
+
Set delegate hotkey take
|
386
|
+
|
387
|
+
:param subtensor: SubtensorInterface (initialized)
|
388
|
+
:param wallet: The wallet containing the hotkey to be nominated.
|
389
|
+
:param delegate_ss58: Hotkey
|
390
|
+
:param take: Delegate take on subnet ID
|
391
|
+
|
392
|
+
:return: `True` if the process is successful, `False` otherwise.
|
393
|
+
|
394
|
+
This function is a key part of the decentralized governance mechanism of Bittensor, allowing for the
|
395
|
+
dynamic selection and participation of validators in the network's consensus process.
|
396
|
+
"""
|
397
|
+
|
398
|
+
# Calculate u16 representation of the take
|
399
|
+
take_u16 = int(take * 0xFFFF)
|
400
|
+
|
401
|
+
print_verbose("Checking current take")
|
402
|
+
# Check if the new take is greater or lower than existing take or if existing is set
|
403
|
+
current_take = await get_current_take(subtensor, wallet)
|
404
|
+
current_take_u16 = int(float(current_take) * 0xFFFF)
|
405
|
+
|
406
|
+
if take_u16 == current_take_u16:
|
407
|
+
console.print("Nothing to do, take hasn't changed")
|
408
|
+
return True
|
409
|
+
|
410
|
+
if current_take_u16 < take_u16:
|
411
|
+
console.print(
|
412
|
+
f"Current take is [{COLOR_PALETTE['POOLS']['RATE']}]{current_take * 100.:.2f}%[/{COLOR_PALETTE['POOLS']['RATE']}]. Increasing to [{COLOR_PALETTE['POOLS']['RATE']}]{take * 100:.2f}%."
|
413
|
+
)
|
414
|
+
with console.status(
|
415
|
+
f":satellite: Sending decrease_take_extrinsic call on [white]{subtensor}[/white] ..."
|
416
|
+
):
|
417
|
+
call = await subtensor.substrate.compose_call(
|
418
|
+
call_module="SubtensorModule",
|
419
|
+
call_function="increase_take",
|
420
|
+
call_params={
|
421
|
+
"hotkey": delegate_ss58,
|
422
|
+
"take": take_u16,
|
423
|
+
},
|
424
|
+
)
|
425
|
+
success, err = await subtensor.sign_and_send_extrinsic(call, wallet)
|
426
|
+
|
427
|
+
else:
|
428
|
+
console.print(
|
429
|
+
f"Current take is [{COLOR_PALETTE['POOLS']['RATE']}]{current_take * 100.:.2f}%[/{COLOR_PALETTE['POOLS']['RATE']}]. Decreasing to [{COLOR_PALETTE['POOLS']['RATE']}]{take * 100:.2f}%."
|
430
|
+
)
|
431
|
+
with console.status(
|
432
|
+
f":satellite: Sending increase_take_extrinsic call on [white]{subtensor}[/white] ..."
|
433
|
+
):
|
434
|
+
call = await subtensor.substrate.compose_call(
|
435
|
+
call_module="SubtensorModule",
|
436
|
+
call_function="decrease_take",
|
437
|
+
call_params={
|
438
|
+
"hotkey": delegate_ss58,
|
439
|
+
"take": take_u16,
|
440
|
+
},
|
441
|
+
)
|
442
|
+
success, err = await subtensor.sign_and_send_extrinsic(call, wallet)
|
443
|
+
|
444
|
+
if not success:
|
445
|
+
err_console.print(err)
|
446
|
+
else:
|
447
|
+
console.print(
|
448
|
+
":white_heavy_check_mark: [dark_sea_green_3]Finalized[/dark_sea_green_3]"
|
449
|
+
)
|
450
|
+
return success
|
451
|
+
|
452
|
+
|
173
453
|
# commands
|
174
454
|
|
175
455
|
|
@@ -184,20 +464,19 @@ async def sudo_set_hyperparameter(
|
|
184
464
|
|
185
465
|
normalized_value: Union[str, bool]
|
186
466
|
if param_name in [
|
187
|
-
"
|
467
|
+
"network_registration_allowed",
|
188
468
|
"network_pow_registration_allowed",
|
189
469
|
"commit_reveal_weights_enabled",
|
190
470
|
"liquid_alpha_enabled",
|
191
471
|
]:
|
192
|
-
normalized_value = param_value.lower() in ["true", "
|
472
|
+
normalized_value = param_value.lower() in ["true", "1"]
|
193
473
|
else:
|
194
474
|
normalized_value = param_value
|
195
475
|
|
196
476
|
is_allowed_value, value = allowed_value(param_name, normalized_value)
|
197
477
|
if not is_allowed_value:
|
198
478
|
err_console.print(
|
199
|
-
f"Hyperparameter
|
200
|
-
f"Value is {normalized_value} but must be {value}"
|
479
|
+
f"Hyperparameter {param_name} value is not within bounds. Value is {normalized_value} but must be {value}"
|
201
480
|
)
|
202
481
|
return
|
203
482
|
|
@@ -217,13 +496,20 @@ async def get_hyperparameters(subtensor: "SubtensorInterface", netuid: int):
|
|
217
496
|
print_error(f"Subnet with netuid {netuid} does not exist.")
|
218
497
|
return False
|
219
498
|
subnet = await subtensor.get_subnet_hyperparameters(netuid)
|
499
|
+
subnet_info = await subtensor.subnet(netuid)
|
500
|
+
if subnet_info is None:
|
501
|
+
print_error(f"Subnet with netuid {netuid} does not exist.")
|
502
|
+
raise typer.Exit()
|
220
503
|
|
221
504
|
table = Table(
|
222
|
-
Column("[white]HYPERPARAMETER", style="
|
223
|
-
Column("[white]VALUE", style="
|
224
|
-
Column("[white]NORMALIZED", style="
|
225
|
-
title=f"[
|
226
|
-
f"{
|
505
|
+
Column("[white]HYPERPARAMETER", style=COLOR_PALETTE["SUDO"]["HYPERPARAMETER"]),
|
506
|
+
Column("[white]VALUE", style=COLOR_PALETTE["SUDO"]["VALUE"]),
|
507
|
+
Column("[white]NORMALIZED", style=COLOR_PALETTE["SUDO"]["NORMALIZED"]),
|
508
|
+
title=f"[{COLOR_PALETTE['GENERAL']['HEADER']}]\nSubnet Hyperparameters\n NETUID: "
|
509
|
+
f"[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid}"
|
510
|
+
f"{f' ({subnet_info.subnet_name})' if subnet_info.subnet_name is not None else ''}"
|
511
|
+
f"[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]"
|
512
|
+
f" - Network: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{subtensor.network}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]\n",
|
227
513
|
show_footer=True,
|
228
514
|
width=None,
|
229
515
|
pad_edge=False,
|
@@ -238,3 +524,220 @@ async def get_hyperparameters(subtensor: "SubtensorInterface", netuid: int):
|
|
238
524
|
|
239
525
|
console.print(table)
|
240
526
|
return True
|
527
|
+
|
528
|
+
|
529
|
+
async def get_senate(subtensor: "SubtensorInterface"):
|
530
|
+
"""View Bittensor's senate memebers"""
|
531
|
+
with console.status(
|
532
|
+
f":satellite: Syncing with chain: [white]{subtensor}[/white] ...",
|
533
|
+
spinner="aesthetic",
|
534
|
+
) as status:
|
535
|
+
print_verbose("Fetching senate members", status)
|
536
|
+
senate_members = await _get_senate_members(subtensor)
|
537
|
+
|
538
|
+
print_verbose("Fetching member details from Github and on-chain identities")
|
539
|
+
delegate_info: dict[
|
540
|
+
str, DelegatesDetails
|
541
|
+
] = await subtensor.get_delegate_identities()
|
542
|
+
|
543
|
+
table = Table(
|
544
|
+
Column(
|
545
|
+
"[bold white]NAME",
|
546
|
+
style="bright_cyan",
|
547
|
+
no_wrap=True,
|
548
|
+
),
|
549
|
+
Column(
|
550
|
+
"[bold white]ADDRESS",
|
551
|
+
style="bright_magenta",
|
552
|
+
no_wrap=True,
|
553
|
+
),
|
554
|
+
title=f"[underline dark_orange]Senate[/underline dark_orange]\n[dark_orange]Network: {subtensor.network}\n",
|
555
|
+
show_footer=True,
|
556
|
+
show_edge=False,
|
557
|
+
expand=False,
|
558
|
+
border_style="bright_black",
|
559
|
+
leading=True,
|
560
|
+
)
|
561
|
+
|
562
|
+
for ss58_address in senate_members:
|
563
|
+
table.add_row(
|
564
|
+
(
|
565
|
+
delegate_info[ss58_address].display
|
566
|
+
if ss58_address in delegate_info
|
567
|
+
else "~"
|
568
|
+
),
|
569
|
+
ss58_address,
|
570
|
+
)
|
571
|
+
|
572
|
+
return console.print(table)
|
573
|
+
|
574
|
+
|
575
|
+
async def proposals(subtensor: "SubtensorInterface"):
|
576
|
+
console.print(
|
577
|
+
":satellite: Syncing with chain: [white]{}[/white] ...".format(
|
578
|
+
subtensor.network
|
579
|
+
)
|
580
|
+
)
|
581
|
+
print_verbose("Fetching senate members & proposals")
|
582
|
+
block_hash = await subtensor.substrate.get_chain_head()
|
583
|
+
senate_members, all_proposals = await asyncio.gather(
|
584
|
+
_get_senate_members(subtensor, block_hash),
|
585
|
+
_get_proposals(subtensor, block_hash),
|
586
|
+
)
|
587
|
+
|
588
|
+
print_verbose("Fetching member information from Chain")
|
589
|
+
registered_delegate_info: dict[
|
590
|
+
str, DelegatesDetails
|
591
|
+
] = await subtensor.get_delegate_identities()
|
592
|
+
|
593
|
+
table = Table(
|
594
|
+
Column(
|
595
|
+
"[white]HASH",
|
596
|
+
style="light_goldenrod2",
|
597
|
+
no_wrap=True,
|
598
|
+
),
|
599
|
+
Column("[white]THRESHOLD", style="rgb(42,161,152)"),
|
600
|
+
Column("[white]AYES", style="green"),
|
601
|
+
Column("[white]NAYS", style="red"),
|
602
|
+
Column(
|
603
|
+
"[white]VOTES",
|
604
|
+
style="rgb(50,163,219)",
|
605
|
+
),
|
606
|
+
Column("[white]END", style="bright_cyan"),
|
607
|
+
Column("[white]CALLDATA", style="dark_sea_green"),
|
608
|
+
title=f"\n[dark_orange]Proposals\t\t\nActive Proposals: {len(all_proposals)}\t\tSenate Size: {len(senate_members)}\nNetwork: {subtensor.network}",
|
609
|
+
show_footer=True,
|
610
|
+
box=box.SIMPLE_HEAVY,
|
611
|
+
pad_edge=False,
|
612
|
+
width=None,
|
613
|
+
border_style="bright_black",
|
614
|
+
)
|
615
|
+
for hash_, (call_data, vote_data) in all_proposals.items():
|
616
|
+
table.add_row(
|
617
|
+
hash_,
|
618
|
+
str(vote_data.threshold),
|
619
|
+
str(len(vote_data.ayes)),
|
620
|
+
str(len(vote_data.nays)),
|
621
|
+
display_votes(vote_data, registered_delegate_info),
|
622
|
+
str(vote_data.end),
|
623
|
+
format_call_data(call_data),
|
624
|
+
)
|
625
|
+
return console.print(table)
|
626
|
+
|
627
|
+
|
628
|
+
async def senate_vote(
|
629
|
+
wallet: Wallet,
|
630
|
+
subtensor: "SubtensorInterface",
|
631
|
+
proposal_hash: str,
|
632
|
+
vote: bool,
|
633
|
+
prompt: bool,
|
634
|
+
) -> bool:
|
635
|
+
"""Vote in Bittensor's governance protocol proposals"""
|
636
|
+
|
637
|
+
if not proposal_hash:
|
638
|
+
err_console.print(
|
639
|
+
"Aborting: Proposal hash not specified. View all proposals with the `proposals` command."
|
640
|
+
)
|
641
|
+
return False
|
642
|
+
elif not _validate_proposal_hash(proposal_hash):
|
643
|
+
err_console.print(
|
644
|
+
"Aborting. Proposal hash is invalid. Proposal hashes should start with '0x' and be 32 bytes long"
|
645
|
+
)
|
646
|
+
return False
|
647
|
+
|
648
|
+
print_verbose(f"Fetching senate status of {wallet.hotkey_str}")
|
649
|
+
if not await _is_senate_member(subtensor, hotkey_ss58=wallet.hotkey.ss58_address):
|
650
|
+
err_console.print(
|
651
|
+
f"Aborting: Hotkey {wallet.hotkey.ss58_address} isn't a senate member."
|
652
|
+
)
|
653
|
+
return False
|
654
|
+
|
655
|
+
# Unlock the wallet.
|
656
|
+
try:
|
657
|
+
wallet.unlock_hotkey()
|
658
|
+
wallet.unlock_coldkey()
|
659
|
+
except KeyFileError:
|
660
|
+
return False
|
661
|
+
|
662
|
+
console.print(f"Fetching proposals in [dark_orange]network: {subtensor.network}")
|
663
|
+
vote_data = await subtensor.get_vote_data(proposal_hash, reuse_block=True)
|
664
|
+
if not vote_data:
|
665
|
+
err_console.print(":cross_mark: [red]Failed[/red]: Proposal not found.")
|
666
|
+
return False
|
667
|
+
|
668
|
+
success = await vote_senate_extrinsic(
|
669
|
+
subtensor=subtensor,
|
670
|
+
wallet=wallet,
|
671
|
+
proposal_hash=proposal_hash,
|
672
|
+
proposal_idx=vote_data.index,
|
673
|
+
vote=vote,
|
674
|
+
wait_for_inclusion=True,
|
675
|
+
wait_for_finalization=False,
|
676
|
+
prompt=prompt,
|
677
|
+
)
|
678
|
+
|
679
|
+
return success
|
680
|
+
|
681
|
+
|
682
|
+
async def get_current_take(subtensor: "SubtensorInterface", wallet: Wallet):
|
683
|
+
current_take = await subtensor.current_take(wallet.hotkey.ss58_address)
|
684
|
+
return current_take
|
685
|
+
|
686
|
+
|
687
|
+
async def display_current_take(subtensor: "SubtensorInterface", wallet: Wallet) -> None:
|
688
|
+
current_take = await get_current_take(subtensor, wallet)
|
689
|
+
console.print(
|
690
|
+
f"Current take is [{COLOR_PALETTE['POOLS']['RATE']}]{current_take * 100.:.2f}%"
|
691
|
+
)
|
692
|
+
|
693
|
+
|
694
|
+
async def set_take(
|
695
|
+
wallet: Wallet, subtensor: "SubtensorInterface", take: float
|
696
|
+
) -> bool:
|
697
|
+
"""Set delegate take."""
|
698
|
+
|
699
|
+
async def _do_set_take() -> bool:
|
700
|
+
if take > 0.18 or take < 0:
|
701
|
+
err_console.print("ERROR: Take value should not exceed 18% or be below 0%")
|
702
|
+
return False
|
703
|
+
|
704
|
+
block_hash = await subtensor.substrate.get_chain_head()
|
705
|
+
netuids_registered = await subtensor.get_netuids_for_hotkey(
|
706
|
+
wallet.hotkey.ss58_address, block_hash=block_hash
|
707
|
+
)
|
708
|
+
if not len(netuids_registered) > 0:
|
709
|
+
err_console.print(
|
710
|
+
f"Hotkey [{COLOR_PALETTE['GENERAL']['HOTKEY']}]{wallet.hotkey.ss58_address}[/{COLOR_PALETTE['GENERAL']['HOTKEY']}] is not registered to any subnet. Please register using [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]`btcli subnets register`[{COLOR_PALETTE['GENERAL']['SUBHEADING']}] and try again."
|
711
|
+
)
|
712
|
+
return False
|
713
|
+
|
714
|
+
result: bool = await set_take_extrinsic(
|
715
|
+
subtensor=subtensor,
|
716
|
+
wallet=wallet,
|
717
|
+
delegate_ss58=wallet.hotkey.ss58_address,
|
718
|
+
take=take,
|
719
|
+
)
|
720
|
+
|
721
|
+
if not result:
|
722
|
+
err_console.print("Could not set the take")
|
723
|
+
return False
|
724
|
+
else:
|
725
|
+
new_take = await get_current_take(subtensor, wallet)
|
726
|
+
console.print(
|
727
|
+
f"New take is [{COLOR_PALETTE['POOLS']['RATE']}]{new_take * 100.:.2f}%"
|
728
|
+
)
|
729
|
+
return True
|
730
|
+
|
731
|
+
console.print(
|
732
|
+
f"Setting take on [{COLOR_PALETTE['GENERAL']['LINKS']}]network: {subtensor.network}"
|
733
|
+
)
|
734
|
+
|
735
|
+
try:
|
736
|
+
wallet.unlock_hotkey()
|
737
|
+
wallet.unlock_coldkey()
|
738
|
+
except KeyFileError:
|
739
|
+
return False
|
740
|
+
|
741
|
+
result_ = await _do_set_take()
|
742
|
+
|
743
|
+
return result_
|