communex.cli.network
1import re 2from typing import Optional, cast 3 4import typer 5from rich.progress import track 6from typer import Context 7 8import communex.balance as c_balance 9from communex.cli._common import ( 10 CustomCtx, 11 make_custom_context, 12 print_table_from_plain_dict, 13 tranform_network_params, 14) 15from communex.client import CommuneClient 16from communex.compat.key import local_key_addresses 17from communex.misc import ( 18 IPFS_REGEX, 19 get_global_params, 20 local_keys_to_stakedbalance, 21) 22from communex.types import NetworkParams 23from communex.util import convert_cid_on_proposal 24 25network_app = typer.Typer(no_args_is_help=True) 26 27 28@network_app.command() 29def last_block(ctx: Context, hash: bool = False): 30 """ 31 Gets the last block 32 """ 33 context = make_custom_context(ctx) 34 client = context.com_client() 35 36 info = "number" if not hash else "hash" 37 38 block = client.get_block() 39 block_info = None 40 if block: 41 block_info = block["header"][info] 42 43 context.output(str(block_info)) 44 45 46@network_app.command() 47def params(ctx: Context): 48 """ 49 Gets global params 50 """ 51 context = make_custom_context(ctx) 52 client = context.com_client() 53 54 with context.progress_status("Getting global network params ..."): 55 global_params = get_global_params(client) 56 printable_params = tranform_network_params(global_params) 57 print_table_from_plain_dict( 58 printable_params, ["Global params", "Value"], context.console 59 ) 60 61 62@network_app.command() 63def list_proposals(ctx: Context, query_cid: bool = typer.Option(True)): 64 """ 65 Gets proposals 66 """ 67 context = make_custom_context(ctx) 68 client = context.com_client() 69 70 with context.progress_status("Getting proposals..."): 71 try: 72 proposals = client.query_map_proposals() 73 if query_cid: 74 proposals = convert_cid_on_proposal(proposals) 75 except IndexError: 76 context.info("No proposals found.") 77 return 78 79 for proposal_id, batch_proposal in proposals.items(): 80 status = batch_proposal["status"] 81 if isinstance(status, dict): 82 batch_proposal["status"] = [*status.keys()][0] 83 print_table_from_plain_dict( 84 batch_proposal, 85 [f"Proposal id: {proposal_id}", "Params"], 86 context.console, 87 ) 88 89 90@network_app.command() 91def propose_globally( 92 ctx: Context, 93 key: str, 94 cid: str, 95 max_name_length: int = typer.Option(None), 96 min_name_length: int = typer.Option(None), 97 max_allowed_subnets: int = typer.Option(None), 98 max_allowed_modules: int = typer.Option(None), 99 max_registrations_per_block: int = typer.Option(None), 100 max_allowed_weights: int = typer.Option(None), 101 max_burn: int = typer.Option(None), 102 min_burn: int = typer.Option(None), 103 floor_delegation_fee: int = typer.Option(None), 104 floor_founder_share: int = typer.Option(None), 105 min_weight_stake: int = typer.Option(None), 106 curator: str = typer.Option(None), 107 proposal_cost: int = typer.Option(None), 108 proposal_expiration: int = typer.Option(None), 109 general_subnet_application_cost: int = typer.Option(None), 110 kappa: int = typer.Option(None), 111 rho: int = typer.Option(None), 112 subnet_immunity_period: int = typer.Option(None), 113): 114 provided_params = locals().copy() 115 provided_params.pop("ctx") 116 provided_params.pop("key") 117 118 provided_params = { 119 key: value 120 for key, value in provided_params.items() 121 if value is not None 122 } 123 """ 124 Adds a global proposal to the network. 125 """ 126 context = make_custom_context(ctx) 127 client = context.com_client() 128 129 resolved_key = context.load_key(key, None) 130 131 provided_params = cast(NetworkParams, provided_params) 132 global_params = get_global_params(client) 133 global_params_config = global_params["governance_config"] 134 global_params["proposal_cost"] = global_params_config["proposal_cost"] # type: ignore 135 global_params["proposal_expiration"] = global_params_config[ # type: ignore 136 "proposal_expiration" 137 ] 138 global_params.pop("governance_config") # type: ignore 139 global_params.update(provided_params) 140 141 if not re.match(IPFS_REGEX, cid): 142 context.error(f"CID provided is invalid: {cid}") 143 typer.Exit(code=1) 144 with context.progress_status("Adding a proposal..."): 145 client.add_global_proposal(resolved_key, global_params, cid) 146 context.info("Proposal added.") 147 148 149def get_valid_voting_keys( 150 ctx: CustomCtx, 151 client: CommuneClient, 152 threshold: int = 25000000000, # 25 $COMAI 153) -> dict[str, int]: 154 local_keys = local_key_addresses(password_provider=ctx.password_manager) 155 keys_stake = local_keys_to_stakedbalance(client, local_keys) 156 keys_stake = { 157 key: stake for key, stake in keys_stake.items() if stake >= threshold 158 } 159 return keys_stake 160 161 162@network_app.command() 163def vote_proposal( 164 ctx: Context, 165 proposal_id: int, 166 key: Optional[str] = None, 167 agree: bool = typer.Option(True, "--disagree"), 168): 169 """ 170 Casts a vote on a specified proposal. Without specifying a key, all keys on disk will be used. 171 """ 172 context = make_custom_context(ctx) 173 client = context.com_client() 174 175 if key is None: 176 context.info("Voting with all keys on disk...") 177 delegators = client.get_voting_power_delegators() 178 keys_stake = get_valid_voting_keys(context, client) 179 keys_stake = { 180 key: stake 181 for key, stake in keys_stake.items() 182 if key not in delegators 183 } 184 else: 185 keys_stake = {key: None} 186 187 for voting_key in track(keys_stake.keys(), description="Voting..."): 188 keypair = context.load_key(voting_key, None) 189 try: 190 client.vote_on_proposal(keypair, proposal_id, agree) 191 except Exception as e: 192 print(f"Error while voting with key {key}: ", e) 193 print("Skipping...") 194 continue 195 196 197@network_app.command() 198def unvote_proposal(ctx: Context, key: str, proposal_id: int): 199 """ 200 Retracts a previously cast vote on a specified proposal. 201 """ 202 context = make_custom_context(ctx) 203 client = context.com_client() 204 205 resolved_key = context.load_key(key, None) 206 with context.progress_status(f"Unvoting on a proposal {proposal_id}..."): 207 client.unvote_on_proposal(resolved_key, proposal_id) 208 209 210@network_app.command() 211def add_custom_proposal(ctx: Context, key: str, cid: str): 212 """ 213 Adds a custom proposal. 214 """ 215 context = make_custom_context(ctx) 216 if not re.match(IPFS_REGEX, cid): 217 context.error(f"CID provided is invalid: {cid}") 218 exit(1) 219 else: 220 ipfs_prefix = "ipfs://" 221 cid = ipfs_prefix + cid 222 client = context.com_client() 223 # append the ipfs hash 224 ipfs_prefix = "ipfs://" 225 cid = ipfs_prefix + cid 226 227 resolved_key = context.load_key(key, None) 228 229 with context.progress_status("Adding a proposal..."): 230 client.add_custom_proposal(resolved_key, cid) 231 232 233@network_app.command() 234def set_root_weights(ctx: Context, key: str): 235 """ 236 Command for rootnet validators to set the weights on subnets. 237 """ 238 239 context = make_custom_context(ctx) 240 client = context.com_client() 241 rootnet_id = 0 242 243 # Ask set new weights ? 244 with context.progress_status("Getting subnets to vote on..."): 245 # dict[netuid, subnet_names] 246 subnet_names = client.query_map_subnet_names() 247 248 choices = [f"{uid}: {name}" for uid, name in subnet_names.items()] 249 250 # Prompt user to select subnets 251 selected_subnets = typer.prompt( 252 "Select subnets to set weights for (space-separated list of UIDs)", 253 prompt_suffix="\n" + "\n".join(choices) + "\nEnter UIDs: ", 254 ) 255 256 # Parse the input string into a list of integers 257 uids = [int(uid.strip()) for uid in selected_subnets.split()] 258 259 weights: list[int] = [] 260 for uid in uids: 261 weight = typer.prompt( 262 f"Enter weight for subnet {uid} ({subnet_names[uid]})", type=float 263 ) 264 weights.append(weight) 265 266 typer.echo("Selected subnets and weights:") 267 for uid, weight in zip(uids, weights): 268 typer.echo(f"Subnet {uid} ({subnet_names[uid]}): {weight}") 269 270 resolved_key = context.load_key(key, None) 271 272 client.vote(netuid=rootnet_id, uids=uids, weights=weights, key=resolved_key) 273 274 275@network_app.command() 276def registration_burn( 277 ctx: Context, 278 netuid: int, 279): 280 """ 281 Appraises the cost of registering a module on the Commune network. 282 """ 283 284 context = make_custom_context(ctx) 285 client = context.com_client() 286 287 burn = client.get_burn(netuid) 288 registration_cost = c_balance.from_nano(burn) 289 context.info( 290 f"The cost to register on a netuid: {netuid} is: {registration_cost} $COMAI" 291 )
network_app =
<typer.main.Typer object>
@network_app.command()
def
last_block(ctx: typer.models.Context, hash: bool = False):
29@network_app.command() 30def last_block(ctx: Context, hash: bool = False): 31 """ 32 Gets the last block 33 """ 34 context = make_custom_context(ctx) 35 client = context.com_client() 36 37 info = "number" if not hash else "hash" 38 39 block = client.get_block() 40 block_info = None 41 if block: 42 block_info = block["header"][info] 43 44 context.output(str(block_info))
Gets the last block
@network_app.command()
def
params(ctx: typer.models.Context):
47@network_app.command() 48def params(ctx: Context): 49 """ 50 Gets global params 51 """ 52 context = make_custom_context(ctx) 53 client = context.com_client() 54 55 with context.progress_status("Getting global network params ..."): 56 global_params = get_global_params(client) 57 printable_params = tranform_network_params(global_params) 58 print_table_from_plain_dict( 59 printable_params, ["Global params", "Value"], context.console 60 )
Gets global params
@network_app.command()
def
list_proposals( ctx: typer.models.Context, query_cid: bool = <typer.models.OptionInfo object>):
63@network_app.command() 64def list_proposals(ctx: Context, query_cid: bool = typer.Option(True)): 65 """ 66 Gets proposals 67 """ 68 context = make_custom_context(ctx) 69 client = context.com_client() 70 71 with context.progress_status("Getting proposals..."): 72 try: 73 proposals = client.query_map_proposals() 74 if query_cid: 75 proposals = convert_cid_on_proposal(proposals) 76 except IndexError: 77 context.info("No proposals found.") 78 return 79 80 for proposal_id, batch_proposal in proposals.items(): 81 status = batch_proposal["status"] 82 if isinstance(status, dict): 83 batch_proposal["status"] = [*status.keys()][0] 84 print_table_from_plain_dict( 85 batch_proposal, 86 [f"Proposal id: {proposal_id}", "Params"], 87 context.console, 88 )
Gets proposals
@network_app.command()
def
propose_globally( ctx: typer.models.Context, key: str, cid: str, max_name_length: int = <typer.models.OptionInfo object>, min_name_length: int = <typer.models.OptionInfo object>, max_allowed_subnets: int = <typer.models.OptionInfo object>, max_allowed_modules: int = <typer.models.OptionInfo object>, max_registrations_per_block: int = <typer.models.OptionInfo object>, max_allowed_weights: int = <typer.models.OptionInfo object>, max_burn: int = <typer.models.OptionInfo object>, min_burn: int = <typer.models.OptionInfo object>, floor_delegation_fee: int = <typer.models.OptionInfo object>, floor_founder_share: int = <typer.models.OptionInfo object>, min_weight_stake: int = <typer.models.OptionInfo object>, curator: str = <typer.models.OptionInfo object>, proposal_cost: int = <typer.models.OptionInfo object>, proposal_expiration: int = <typer.models.OptionInfo object>, general_subnet_application_cost: int = <typer.models.OptionInfo object>, kappa: int = <typer.models.OptionInfo object>, rho: int = <typer.models.OptionInfo object>, subnet_immunity_period: int = <typer.models.OptionInfo object>):
91@network_app.command() 92def propose_globally( 93 ctx: Context, 94 key: str, 95 cid: str, 96 max_name_length: int = typer.Option(None), 97 min_name_length: int = typer.Option(None), 98 max_allowed_subnets: int = typer.Option(None), 99 max_allowed_modules: int = typer.Option(None), 100 max_registrations_per_block: int = typer.Option(None), 101 max_allowed_weights: int = typer.Option(None), 102 max_burn: int = typer.Option(None), 103 min_burn: int = typer.Option(None), 104 floor_delegation_fee: int = typer.Option(None), 105 floor_founder_share: int = typer.Option(None), 106 min_weight_stake: int = typer.Option(None), 107 curator: str = typer.Option(None), 108 proposal_cost: int = typer.Option(None), 109 proposal_expiration: int = typer.Option(None), 110 general_subnet_application_cost: int = typer.Option(None), 111 kappa: int = typer.Option(None), 112 rho: int = typer.Option(None), 113 subnet_immunity_period: int = typer.Option(None), 114): 115 provided_params = locals().copy() 116 provided_params.pop("ctx") 117 provided_params.pop("key") 118 119 provided_params = { 120 key: value 121 for key, value in provided_params.items() 122 if value is not None 123 } 124 """ 125 Adds a global proposal to the network. 126 """ 127 context = make_custom_context(ctx) 128 client = context.com_client() 129 130 resolved_key = context.load_key(key, None) 131 132 provided_params = cast(NetworkParams, provided_params) 133 global_params = get_global_params(client) 134 global_params_config = global_params["governance_config"] 135 global_params["proposal_cost"] = global_params_config["proposal_cost"] # type: ignore 136 global_params["proposal_expiration"] = global_params_config[ # type: ignore 137 "proposal_expiration" 138 ] 139 global_params.pop("governance_config") # type: ignore 140 global_params.update(provided_params) 141 142 if not re.match(IPFS_REGEX, cid): 143 context.error(f"CID provided is invalid: {cid}") 144 typer.Exit(code=1) 145 with context.progress_status("Adding a proposal..."): 146 client.add_global_proposal(resolved_key, global_params, cid) 147 context.info("Proposal added.")
def
get_valid_voting_keys( ctx: communex.cli._common.CustomCtx, client: communex.client.CommuneClient, threshold: int = 25000000000) -> dict[str, int]:
150def get_valid_voting_keys( 151 ctx: CustomCtx, 152 client: CommuneClient, 153 threshold: int = 25000000000, # 25 $COMAI 154) -> dict[str, int]: 155 local_keys = local_key_addresses(password_provider=ctx.password_manager) 156 keys_stake = local_keys_to_stakedbalance(client, local_keys) 157 keys_stake = { 158 key: stake for key, stake in keys_stake.items() if stake >= threshold 159 } 160 return keys_stake
@network_app.command()
def
vote_proposal( ctx: typer.models.Context, proposal_id: int, key: Optional[str] = None, agree: bool = <typer.models.OptionInfo object>):
163@network_app.command() 164def vote_proposal( 165 ctx: Context, 166 proposal_id: int, 167 key: Optional[str] = None, 168 agree: bool = typer.Option(True, "--disagree"), 169): 170 """ 171 Casts a vote on a specified proposal. Without specifying a key, all keys on disk will be used. 172 """ 173 context = make_custom_context(ctx) 174 client = context.com_client() 175 176 if key is None: 177 context.info("Voting with all keys on disk...") 178 delegators = client.get_voting_power_delegators() 179 keys_stake = get_valid_voting_keys(context, client) 180 keys_stake = { 181 key: stake 182 for key, stake in keys_stake.items() 183 if key not in delegators 184 } 185 else: 186 keys_stake = {key: None} 187 188 for voting_key in track(keys_stake.keys(), description="Voting..."): 189 keypair = context.load_key(voting_key, None) 190 try: 191 client.vote_on_proposal(keypair, proposal_id, agree) 192 except Exception as e: 193 print(f"Error while voting with key {key}: ", e) 194 print("Skipping...") 195 continue
Casts a vote on a specified proposal. Without specifying a key, all keys on disk will be used.
@network_app.command()
def
unvote_proposal(ctx: typer.models.Context, key: str, proposal_id: int):
198@network_app.command() 199def unvote_proposal(ctx: Context, key: str, proposal_id: int): 200 """ 201 Retracts a previously cast vote on a specified proposal. 202 """ 203 context = make_custom_context(ctx) 204 client = context.com_client() 205 206 resolved_key = context.load_key(key, None) 207 with context.progress_status(f"Unvoting on a proposal {proposal_id}..."): 208 client.unvote_on_proposal(resolved_key, proposal_id)
Retracts a previously cast vote on a specified proposal.
@network_app.command()
def
add_custom_proposal(ctx: typer.models.Context, key: str, cid: str):
211@network_app.command() 212def add_custom_proposal(ctx: Context, key: str, cid: str): 213 """ 214 Adds a custom proposal. 215 """ 216 context = make_custom_context(ctx) 217 if not re.match(IPFS_REGEX, cid): 218 context.error(f"CID provided is invalid: {cid}") 219 exit(1) 220 else: 221 ipfs_prefix = "ipfs://" 222 cid = ipfs_prefix + cid 223 client = context.com_client() 224 # append the ipfs hash 225 ipfs_prefix = "ipfs://" 226 cid = ipfs_prefix + cid 227 228 resolved_key = context.load_key(key, None) 229 230 with context.progress_status("Adding a proposal..."): 231 client.add_custom_proposal(resolved_key, cid)
Adds a custom proposal.
@network_app.command()
def
set_root_weights(ctx: typer.models.Context, key: str):
234@network_app.command() 235def set_root_weights(ctx: Context, key: str): 236 """ 237 Command for rootnet validators to set the weights on subnets. 238 """ 239 240 context = make_custom_context(ctx) 241 client = context.com_client() 242 rootnet_id = 0 243 244 # Ask set new weights ? 245 with context.progress_status("Getting subnets to vote on..."): 246 # dict[netuid, subnet_names] 247 subnet_names = client.query_map_subnet_names() 248 249 choices = [f"{uid}: {name}" for uid, name in subnet_names.items()] 250 251 # Prompt user to select subnets 252 selected_subnets = typer.prompt( 253 "Select subnets to set weights for (space-separated list of UIDs)", 254 prompt_suffix="\n" + "\n".join(choices) + "\nEnter UIDs: ", 255 ) 256 257 # Parse the input string into a list of integers 258 uids = [int(uid.strip()) for uid in selected_subnets.split()] 259 260 weights: list[int] = [] 261 for uid in uids: 262 weight = typer.prompt( 263 f"Enter weight for subnet {uid} ({subnet_names[uid]})", type=float 264 ) 265 weights.append(weight) 266 267 typer.echo("Selected subnets and weights:") 268 for uid, weight in zip(uids, weights): 269 typer.echo(f"Subnet {uid} ({subnet_names[uid]}): {weight}") 270 271 resolved_key = context.load_key(key, None) 272 273 client.vote(netuid=rootnet_id, uids=uids, weights=weights, key=resolved_key)
Command for rootnet validators to set the weights on subnets.
@network_app.command()
def
registration_burn(ctx: typer.models.Context, netuid: int):
276@network_app.command() 277def registration_burn( 278 ctx: Context, 279 netuid: int, 280): 281 """ 282 Appraises the cost of registering a module on the Commune network. 283 """ 284 285 context = make_custom_context(ctx) 286 client = context.com_client() 287 288 burn = client.get_burn(netuid) 289 registration_cost = c_balance.from_nano(burn) 290 context.info( 291 f"The cost to register on a netuid: {netuid} is: {registration_cost} $COMAI" 292 )
Appraises the cost of registering a module on the Commune network.