Skip to content

Commit d21c21a

Browse files
committed
feat(proxy): add list and reject commands (#742)
- _parse_proxy_storage + list_proxies: query Proxy.Proxies, display table or JSON - reject_announcement: submit Proxy.reject_announcement with DB integration - CLI: register proxy list and proxy reject commands - Adapt proxy_remove to upstream all_ parameter - Tests: 7 parse_proxy, list/reject function, CLI handler - E2E: test_proxy_list
1 parent 34235e8 commit d21c21a

File tree

4 files changed

+1156
-2
lines changed

4 files changed

+1156
-2
lines changed

bittensor_cli/cli.py

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1243,6 +1243,12 @@ def __init__(self):
12431243
"execute",
12441244
rich_help_panel=HELP_PANELS["PROXY"]["MGMT"],
12451245
)(self.proxy_execute_announced)
1246+
self.proxy_app.command("list", rich_help_panel=HELP_PANELS["PROXY"]["MGMT"])(
1247+
self.proxy_list
1248+
)
1249+
self.proxy_app.command("reject", rich_help_panel=HELP_PANELS["PROXY"]["MGMT"])(
1250+
self.proxy_reject
1251+
)
12461252

12471253
# Sub command aliases
12481254
# Wallet
@@ -9892,6 +9898,7 @@ def proxy_remove(
98929898
delegate = is_valid_ss58_address_param(delegate)
98939899

98949900
self.verbosity_handler(quiet, verbose, json_output, prompt)
9901+
98959902
wallet = self.wallet_ask(
98969903
wallet_name=wallet_name,
98979904
wallet_path=wallet_path,
@@ -10226,6 +10233,281 @@ def proxy_execute_announced(
1022610233
with ProxyAnnouncements.get_db() as (conn, cursor):
1022710234
ProxyAnnouncements.mark_as_executed(conn, cursor, got_call_from_db)
1022810235

10236+
def proxy_list(
10237+
self,
10238+
address: Annotated[
10239+
Optional[str],
10240+
typer.Option(
10241+
callback=is_valid_ss58_address_param,
10242+
help="The SS58 address to list proxies for. If not provided, uses the wallet's coldkey.",
10243+
),
10244+
] = None,
10245+
network: Optional[list[str]] = Options.network,
10246+
wallet_name: str = Options.wallet_name,
10247+
wallet_path: str = Options.wallet_path,
10248+
wallet_hotkey: str = Options.wallet_hotkey,
10249+
quiet: bool = Options.quiet,
10250+
verbose: bool = Options.verbose,
10251+
json_output: bool = Options.json_output,
10252+
):
10253+
"""
10254+
Lists all proxies for an account.
10255+
10256+
Queries the chain to display all proxy delegates configured for the specified address,
10257+
including their proxy types and delay settings.
10258+
10259+
[bold]Common Examples:[/bold]
10260+
1. List proxies for your wallet
10261+
[green]$[/green] btcli proxy list
10262+
10263+
2. List proxies for a specific address
10264+
[green]$[/green] btcli proxy list --address 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
10265+
10266+
"""
10267+
self.verbosity_handler(quiet, verbose, json_output, prompt=False)
10268+
10269+
# If no address provided, use wallet's coldkey
10270+
if address is None:
10271+
wallet = self.wallet_ask(
10272+
wallet_name=wallet_name,
10273+
wallet_path=wallet_path,
10274+
wallet_hotkey=wallet_hotkey,
10275+
ask_for=[WO.NAME, WO.PATH],
10276+
validate=WV.WALLET,
10277+
)
10278+
address = wallet.coldkeypub.ss58_address
10279+
10280+
logger.debug(f"args:\naddress: {address}\nnetwork: {network}\n")
10281+
10282+
return self._run_command(
10283+
proxy_commands.list_proxies(
10284+
subtensor=self.initialize_chain(network),
10285+
address=address,
10286+
json_output=json_output,
10287+
)
10288+
)
10289+
10290+
def proxy_reject(
10291+
self,
10292+
delegate: Annotated[
10293+
Optional[str],
10294+
typer.Option(
10295+
callback=is_valid_ss58_address_param,
10296+
help="The SS58 address of the delegate (proxy) who made the announcement.",
10297+
),
10298+
] = None,
10299+
call_hash: Annotated[
10300+
Optional[str],
10301+
typer.Option(
10302+
help="The hash of the announced call to reject",
10303+
),
10304+
] = None,
10305+
network: Optional[list[str]] = Options.network,
10306+
wallet_name: str = Options.wallet_name,
10307+
wallet_path: str = Options.wallet_path,
10308+
wallet_hotkey: str = Options.wallet_hotkey,
10309+
prompt: bool = Options.prompt,
10310+
decline: bool = Options.decline,
10311+
wait_for_inclusion: bool = Options.wait_for_inclusion,
10312+
wait_for_finalization: bool = Options.wait_for_finalization,
10313+
period: int = Options.period,
10314+
quiet: bool = Options.quiet,
10315+
verbose: bool = Options.verbose,
10316+
json_output: bool = Options.json_output,
10317+
):
10318+
"""
10319+
Rejects an announced proxy call.
10320+
10321+
Removes a previously announced call from the pending announcements, preventing it
10322+
from being executed. This must be called by the real account (the account that
10323+
granted the proxy permissions).
10324+
10325+
[bold]Common Examples:[/bold]
10326+
1. Reject an announced call
10327+
[green]$[/green] btcli proxy reject --delegate 5GDel... --call-hash 0x1234...
10328+
10329+
"""
10330+
self.verbosity_handler(quiet, verbose, json_output, prompt, decline)
10331+
10332+
logger.debug(
10333+
"args:\n"
10334+
f"delegate: {delegate}\n"
10335+
f"call_hash: {call_hash}\n"
10336+
f"network: {network}\n"
10337+
f"wait_for_finalization: {wait_for_finalization}\n"
10338+
f"wait_for_inclusion: {wait_for_inclusion}\n"
10339+
f"era: {period}\n"
10340+
)
10341+
10342+
wallet = self.wallet_ask(
10343+
wallet_name=wallet_name,
10344+
wallet_path=wallet_path,
10345+
wallet_hotkey=wallet_hotkey,
10346+
ask_for=[WO.NAME, WO.PATH],
10347+
validate=WV.WALLET,
10348+
)
10349+
10350+
if not delegate:
10351+
if prompt:
10352+
delegate = Prompt.ask(
10353+
"Enter the SS58 address of the delegate (proxy) who made the announcement"
10354+
)
10355+
if not is_valid_ss58_address(delegate):
10356+
print_error(f"Invalid SS58 address: {delegate}")
10357+
return
10358+
else:
10359+
if json_output:
10360+
json_console.print_json(
10361+
data={
10362+
"success": False,
10363+
"message": "--delegate is required. Provide the SS58 address of the proxy that made the announcement.",
10364+
"extrinsic_identifier": None,
10365+
}
10366+
)
10367+
else:
10368+
print_error(
10369+
"--delegate is required. Provide the SS58 address of the proxy that made the announcement."
10370+
)
10371+
return
10372+
10373+
# Try to find the announcement in the local DB
10374+
# DB stores address = the real account (the wallet calling reject)
10375+
real_address = wallet.coldkeypub.ss58_address
10376+
got_call_from_db: Optional[int] = None
10377+
with ProxyAnnouncements.get_db() as (conn, cursor):
10378+
announcements = ProxyAnnouncements.read_rows(conn, cursor)
10379+
10380+
if not call_hash:
10381+
potential_call_matches = []
10382+
for row in announcements:
10383+
(
10384+
id_,
10385+
address,
10386+
epoch_time,
10387+
block_,
10388+
call_hash_,
10389+
call_hex_,
10390+
call_serialized,
10391+
executed_int,
10392+
) = row
10393+
executed = bool(executed_int)
10394+
if address == real_address and executed is False:
10395+
potential_call_matches.append(row)
10396+
10397+
if len(potential_call_matches) == 0:
10398+
if not prompt:
10399+
if json_output:
10400+
json_console.print_json(
10401+
data={
10402+
"success": False,
10403+
"message": "No pending announcements found in the local address book. Please provide --call-hash explicitly.",
10404+
"extrinsic_identifier": None,
10405+
}
10406+
)
10407+
else:
10408+
print_error(
10409+
"No pending announcements found in the local address book. "
10410+
"Please provide --call-hash explicitly."
10411+
)
10412+
return
10413+
call_hash = Prompt.ask(
10414+
"Enter the call hash of the announcement to reject"
10415+
)
10416+
elif len(potential_call_matches) == 1:
10417+
call_hash = potential_call_matches[0][4]
10418+
got_call_from_db = potential_call_matches[0][0]
10419+
if not json_output:
10420+
console.print(f"Found announcement with call hash: {call_hash}")
10421+
else:
10422+
if not prompt:
10423+
if json_output:
10424+
json_console.print_json(
10425+
data={
10426+
"success": False,
10427+
"message": "Multiple pending announcements found. Please provide --call-hash explicitly.",
10428+
"extrinsic_identifier": None,
10429+
}
10430+
)
10431+
else:
10432+
print_error(
10433+
"Multiple pending announcements found. "
10434+
f"Please run without {arg__('--no-prompt')} to select one, or provide --call-hash explicitly."
10435+
)
10436+
return
10437+
else:
10438+
console.print(
10439+
f"Found {len(potential_call_matches)} pending announcements. "
10440+
f"Please select the one to reject:"
10441+
)
10442+
for row in potential_call_matches:
10443+
(
10444+
id_,
10445+
address,
10446+
epoch_time,
10447+
block_,
10448+
call_hash_,
10449+
call_hex_,
10450+
call_serialized,
10451+
executed_int,
10452+
) = row
10453+
console.print(
10454+
f"Time: {datetime.datetime.fromtimestamp(epoch_time)}\n"
10455+
f"Call Hash: {call_hash_}\nCall:\n"
10456+
)
10457+
console.print_json(call_serialized)
10458+
if confirm_action(
10459+
"Is this the announcement to reject?",
10460+
decline=decline,
10461+
quiet=quiet,
10462+
):
10463+
call_hash = call_hash_
10464+
got_call_from_db = id_
10465+
break
10466+
if call_hash is None:
10467+
print_error("No announcement selected.")
10468+
return
10469+
else:
10470+
# call_hash provided, try to find it in DB
10471+
for row in announcements:
10472+
(
10473+
id_,
10474+
address,
10475+
epoch_time,
10476+
block_,
10477+
call_hash_,
10478+
call_hex_,
10479+
call_serialized,
10480+
executed_int,
10481+
) = row
10482+
executed = bool(executed_int)
10483+
if (
10484+
(call_hash_ == call_hash or f"0x{call_hash_}" == call_hash)
10485+
and address == real_address
10486+
and executed is False
10487+
):
10488+
got_call_from_db = id_
10489+
break
10490+
10491+
success = self._run_command(
10492+
proxy_commands.reject_announcement(
10493+
subtensor=self.initialize_chain(network),
10494+
wallet=wallet,
10495+
delegate=delegate,
10496+
call_hash=call_hash,
10497+
prompt=prompt,
10498+
decline=decline,
10499+
quiet=quiet,
10500+
wait_for_inclusion=wait_for_inclusion,
10501+
wait_for_finalization=wait_for_finalization,
10502+
period=period,
10503+
json_output=json_output,
10504+
)
10505+
)
10506+
10507+
if success and got_call_from_db is not None:
10508+
with ProxyAnnouncements.get_db() as (conn, cursor):
10509+
ProxyAnnouncements.mark_as_executed(conn, cursor, got_call_from_db)
10510+
1022910511
@staticmethod
1023010512
def convert(
1023110513
from_rao: Optional[str] = typer.Option(

0 commit comments

Comments
 (0)