Edit on GitHub

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.