Install NNS governance canister

Where can I find the wasm.gz file for the NNS governance canister, and what are the required initialization parameters to spin up that canister locally?

1 Like

Follow this format to get the latest wasm.gz file of any IC canister:

https://download.dfinity.systems/ic/$IC_VERSION/canisters/$CANISTER_NAME_IN_THE_LIST

With:
IC_VERSION as the latest release commit that can be found on the IC Data Dashboard releases
CANISTER_NAME_IN_THE_LIST as the canister name in this list.

In this case, the last IC version is bb76748d1d225c08d88037e99ca9a066f97de496 and the canister name is governance-canister.wasm.gz:

https://download.dfinity.systems/ic/bb76748d1d225c08d88037e99ca9a066f97de496/canisters/governance-canister.wasm.gz

2 Likes

For initialization parameters, maybe @jasonzhu or @daniel-wong can help?

Not sure about the exact use case, but perhaps you can use the dfx nns extension (documentation) if the goal is to run a governance canister on a local replica. It fetches all the NNS WASMs and install them on the local replica with reasonable init args.

1 Like

Iā€™m aware of this command, thatā€™s not the goal. Also I donā€™t use dfx here.

Alright, then it would be very helpful to specify the use case and the environment.

Without knowing more, if I take ā€œthe required initialization parametersā€ literally, the init args is a candid-serialized blob and it should be possible to build it using the init library of NNS Governance (init.rs) and then serialize it with candid.

Yeah I know can either retro-engineer the did file or source code on my spare time to understand what parameters are required or not but, given that there is absolutely zero documentation on the website, I was hoping for some guidance on what parameters are required and what are their expected values.

Anyway I guess those are the one I need to provide.

type Governance = record {
  default_followees : vec record { int32; Followees };
  making_sns_proposal : opt MakingSnsProposal;
  most_recent_monthly_node_provider_rewards : opt MostRecentMonthlyNodeProviderRewards;
  maturity_modulation_last_updated_at_timestamp_seconds : opt nat64;
  wait_for_quiet_threshold_seconds : nat64;
  metrics : opt GovernanceCachedMetrics;
  node_providers : vec NodeProvider;
  cached_daily_maturity_modulation_basis_points : opt int32;
  economics : opt NetworkEconomics;
  spawning_neurons : opt bool;
  latest_reward_event : opt RewardEvent;
  to_claim_transfers : vec NeuronStakeTransfer;
  short_voting_period_seconds : nat64;
  migrations : opt Migrations;
  proposals : vec record { nat64; ProposalData };
  in_flight_commands : vec record { nat64; NeuronInFlightCommand };
  neurons : vec record { nat64; Neuron };
  genesis_timestamp_seconds : nat64;
};

Reject text: Canister rrkah-fqaaa-aaaaa-aaaaq-cai trapped explicitly: Panicked at ā€˜Couldnā€™t initialize canister.: DecodeError { description: ā€œinvalid wire type: EndGroup (expected LengthDelimited)ā€, stack: [(ā€œGovernanceā€, ā€œeconomicsā€)] }ā€™, rs/nns/governance/canister/canister.rs:292:6

import {init} from '../declarations/governance.idl';

        const E8S_PER_ICP = 100_000_000n;
        const DEFAULT_TRANSFER_FEE = 10_000n;

        const economics: NetworkEconomics = {
            neuron_minimum_stake_e8s: E8S_PER_ICP,  // 1 ICP
            max_proposals_to_keep_per_topic: 100,
            neuron_management_fee_per_proposal_e8s: 1_000_000n, // 0.01 ICP
            reject_cost_e8s: E8S_PER_ICP, // 1 ICP
            transaction_fee_e8s: DEFAULT_TRANSFER_FEE,
            neuron_spawn_dissolve_delay_seconds: BigInt(24 * 60 * 60* 7), // 7 days
            minimum_icp_xdr_rate: 100n, // 1 XDR
            maximum_node_provider_rewards_e8s: BigInt(1_000_000 * 100_000_000,) // 1M ICP
        }

        const sourceArg: Governance = {
            default_followees: [],
            making_sns_proposal: [],
            most_recent_monthly_node_provider_rewards: [],
            maturity_modulation_last_updated_at_timestamp_seconds: [],
            wait_for_quiet_threshold_seconds: BigInt(60 * 60 * 24 * 4), // 4 days
            metrics: [],
            node_providers: [],
            cached_daily_maturity_modulation_basis_points: [],
            economics: [economics],
            spawning_neurons: [],
            latest_reward_event: [],
            to_claim_transfers: [],
            short_voting_period_seconds: BigInt(60 * 60 * 12), // 12 hours
            migrations: [],
            proposals: [],
            in_flight_commands: [],
            neurons: [],
            genesis_timestamp_seconds: 1n
        };

        const arg = IDL.encode(init({IDL}), [sourceArg]);

Bumped to last release git hash e790c6636115482db53ca3daa2f1900202ab04cf.

juno-satellite-1 | Reject code: 5
juno-satellite-1 | Reject text: Canister rrkah-fqaaa-aaaaa-aaaaq-cai trapped explicitly: Panicked at ā€˜Couldnā€™t initialize canister.: DecodeError { description: ā€œinvalid wire type: EndGroup (expected LengthDelimited)ā€, stack: [(ā€œGovernanceā€, ā€œeconomicsā€)] }ā€™, rs/nns/governance/canister/canister.rs:317:6

I donā€™t know the heck is happening when the payload is decoded here.

Is the provided governance did file (https://raw.githubusercontent.com/dfinity/ic/e790c6636115482db53ca3daa2f1900202ab04cf/rs/nns/governance/canister/governance.did) encoded correctly?

In the did file the service is declared as following:

service : (Governance) -> {

But the error says

stack: [("Governance", "economics")]

[Canister rrkah-fqaaa-aaaaa-aaaaq-cai] Error deserializing canister state in initialization: failed to decode Protobuf message: Governance.economics: invalid wire type: EndGroup (expected LengthDelimited).

Wait, you cannot initialize that freaking canister with Candid but, only protobuf?

6 hours spent, still not the right parameters. Love itā€¦

// Source: https://github.com/dfinity/ic/blob/e90838a1687f8e0869d85343aac2845d883f74ff/rs/nns/governance/src/governance.rs#L231
    const E8S_PER_ICP = 100_000_000n;
    const DEFAULT_TRANSFER_FEE = 10_000n;

    const neurons_fund_economics: NeuronsFundEconomics = {
      maximum_icp_xdr_rate: [
        {
          basis_points: [1_000_000n] // // 1:100
        }
      ],
      minimum_icp_xdr_rate: [
        {
          basis_points: [10_000n] // 1:1
        }
      ],
      neurons_fund_matched_funding_curve_coefficients: [
        {
          contribution_threshold_xdr: [
            {
              human_readable: ['75_000.0']
            }
          ],
          one_third_participation_milestone_xdr: [
            {
              human_readable: ['225_000.0']
            }
          ],
          full_participation_milestone_xdr: [
            {
              human_readable: ['375_000.0']
            }
          ]
        }
      ],
      max_theoretical_neurons_fund_participation_amount_xdr: [
        {
          human_readable: ['750_000.0']
        }
      ]
    };

    const economics: NetworkEconomics = {
      neuron_minimum_stake_e8s: E8S_PER_ICP, // 1 ICP
      max_proposals_to_keep_per_topic: 100,
      neuron_management_fee_per_proposal_e8s: 1_000_000n, // 0.01 ICP
      reject_cost_e8s: E8S_PER_ICP, // 1 ICP
      transaction_fee_e8s: DEFAULT_TRANSFER_FEE,
      neuron_spawn_dissolve_delay_seconds: BigInt(24 * 60 * 60 * 7), // 7 days
      minimum_icp_xdr_rate: 100n, // 1 XDR
      maximum_node_provider_rewards_e8s: BigInt(1_000_000 * 100_000_000), // 1M ICP,
      neurons_fund_economics: [neurons_fund_economics]
    };

    const sourceArg: Governance = {
      default_followees: [],
      making_sns_proposal: [],
      most_recent_monthly_node_provider_rewards: [],
      maturity_modulation_last_updated_at_timestamp_seconds: [],
      wait_for_quiet_threshold_seconds: BigInt(60 * 60 * 24 * 4), // 4 days
      metrics: [],
      node_providers: [],
      cached_daily_maturity_modulation_basis_points: [],
      economics: [economics],
      spawning_neurons: [false],
      latest_reward_event: [],
      to_claim_transfers: [],
      short_voting_period_seconds: BigInt(60 * 60 * 12), // 12 hours
      migrations: [],
      proposals: [],
      in_flight_commands: [],
      neurons: [],
      genesis_timestamp_seconds: 1n,
      neuron_management_voting_period_seconds: [],
      restore_aging_summary: [],
      topic_followee_index: [],
      xdr_conversion_rate: [
        {
          xdr_permyriad_per_icp: [10_000n],
          timestamp_seconds: [1n]
        }
      ]
    };

    // Type definitions generated by Candid are not clean enough.
    // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
    const arg = IDL.encode(init({IDL}), [sourceArg]);

=>

2024-05-10 11:53:25.462486500 UTC: [Canister rrkah-fqaaa-aaaaa-aaaaq-cai] Error deserializing canister state in initialization: failed to decode Protobuf message: Governance.economics: invalid wire type: EndGroup (expected LengthDelimited).
2024-05-10 11:53:25.462486500 UTC: [Canister rrkah-fqaaa-aaaaa-aaaaq-cai] Panicked at ā€˜Couldnā€™t initialize canister.: DecodeError { description: ā€œinvalid wire type: EndGroup (expected LengthDelimited)ā€, stack: [(ā€œGovernanceā€, ā€œeconomicsā€)] }ā€™, rs/nns/governance/canister/canister.rs:317:6
May 10 11:53:28.441 INFO s:s4n6d-5ipoi-2loaq-copwn-cgb2w-zzskv-wfsfm-3dnwk-rseqo-h56ts-7qe/n:lagdo-6ghtj-kxszl-7pa7f-wdf34-lault-uxqf2-qp4ae-wzow6-nf3lz-oae/ic_execution_environment/install Executing (canister_init) on canister rrkah-fqaaa-aaaaa-aaaaq-cai consumed 77203 instructions. 191841644797 instructions are left.
May 10 11:53:28.442 INFO s:s4n6d-5ipoi-2loaq-copwn-cgb2w-zzskv-wfsfm-3dnwk-rseqo-h56ts-7qe/n:lagdo-6ghtj-kxszl-7pa7f-wdf34-lault-uxqf2-qp4ae-wzow6-nf3lz-oae/ic_execution_environment/execution_environment Finished executing install_code message on canister CanisterId(rrkah-fqaaa-aaaaa-aaaaq-cai) after 1.7273262919999999 with error: Hypervisor(CanisterId(rrkah-fqaaa-aaaaa-aaaaq-cai), CalledTrap(ā€œPanicked at ā€˜Couldnā€™t initialize canister.: DecodeError { description: "invalid wire type: EndGroup (expected LengthDelimited)", stack: [("Governance", "economics")] }ā€™, rs/nns/governance/canister/canister.rs:317:6ā€)), instructions consumed 8158355203
May 10 11:53:28.478 INFO s:s4n6d-5ipoi-2loaq-copwn-cgb2w-zzskv-wfsfm-3dnwk-rseqo-h56ts-7qe/n:lagdo-6ghtj-kxszl-7pa7f-wdf34-lault-uxqf2-qp4ae-wzow6-nf3lz-oae/ic_execution_environment/execution_environment Start executing install_code message on canister CanisterId(jx5yt-yyaaa-aaaal-abzbq-cai)
Request ID: 13e7d96cee60518788403af8e532c61c44785aa42157b54590072815adcfc017
Reject code: 5
Reject text: Canister rrkah-fqaaa-aaaaa-aaaaq-cai trapped explicitly: Panicked at ā€˜Couldnā€™t initialize canister.: DecodeError { description: ā€œinvalid wire type: EndGroup (expected LengthDelimited)ā€, stack: [(ā€œGovernanceā€, ā€œeconomicsā€)] }ā€™, rs/nns/governance/canister/canister.rs:317:6

Given that there seem to be some expected tag order in the NNS canister I sorted the parameters, no luck neither.

const E8S_PER_ICP = 100_000_000n;
    const DEFAULT_TRANSFER_FEE = 10_000n;

    const neurons_fund_economics: NeuronsFundEconomics = {
      max_theoretical_neurons_fund_participation_amount_xdr: [
        {
          human_readable: ['750_000.0']
        }
      ],
      neurons_fund_matched_funding_curve_coefficients: [
        {
          contribution_threshold_xdr: [
            {
              human_readable: ['75_000.0']
            }
          ],
          one_third_participation_milestone_xdr: [
            {
              human_readable: ['225_000.0']
            }
          ],
          full_participation_milestone_xdr: [
            {
              human_readable: ['375_000.0']
            }
          ]
        }
      ],
      minimum_icp_xdr_rate: [
        {
          basis_points: [10_000n] // 1:1
        }
      ],
      maximum_icp_xdr_rate: [
        {
          basis_points: [1_000_000n] // // 1:100
        }
      ]
    };

    const economics: NetworkEconomics = {
      reject_cost_e8s: E8S_PER_ICP, // 1 ICP
      neuron_minimum_stake_e8s: E8S_PER_ICP, // 1 ICP
      neuron_management_fee_per_proposal_e8s: 1_000_000n, // 0.01 ICP
      minimum_icp_xdr_rate: 100n, // 1 XDR
      neuron_spawn_dissolve_delay_seconds: BigInt(24 * 60 * 60 * 7), // 7 days
      maximum_node_provider_rewards_e8s: BigInt(1_000_000 * 100_000_000), // 1M ICP,
      transaction_fee_e8s: DEFAULT_TRANSFER_FEE,
      max_proposals_to_keep_per_topic: 100,
      neurons_fund_economics: [neurons_fund_economics]
    };

    const sourceArg: Governance = {
      neurons: [],
      proposals: [],
      to_claim_transfers: [],
      wait_for_quiet_threshold_seconds: BigInt(60 * 60 * 24 * 4), // 4 days
      economics: [economics],
      latest_reward_event: [],
      in_flight_commands: [],
      genesis_timestamp_seconds: 1n,
      node_providers: [],
      default_followees: [],
      short_voting_period_seconds: BigInt(60 * 60 * 12), // 12 hours
      neuron_management_voting_period_seconds: [],
      metrics: [],
      most_recent_monthly_node_provider_rewards: [],
      cached_daily_maturity_modulation_basis_points: [],
      maturity_modulation_last_updated_at_timestamp_seconds: [],
      spawning_neurons: [false],
      making_sns_proposal: [],
      migrations: [],
      topic_followee_index: [],
      xdr_conversion_rate: [
        {
          timestamp_seconds: [1n],
          xdr_permyriad_per_icp: [10_000n]
        }
      ],
      restore_aging_summary: []
    };

Now I see, you are trying to pass the init args from another language, and thatā€™s why you cannot use the nns-init library.

Wait, you cannot initialize that freaking canister with Candid but, only protobuf?

After a closer look, yes. Apologies for what I said earlier. My impression came from the fact that we use candid::export_service! to generate governance.did from the canister code and in most cases it was accurate in terms of the consistency between the interface and what the canister actually expects. It turns out this is the biggest lie the governance.did has been telling - this is prost_message::decode which is protobuf wire format, and the ic-nns-init library I mentioned earlier is also encoding as protobuf here

Would you be able to use governance.proto to generate ts?

I was hoping for some guidance on what parameters are required and what are their expected values.

Your example above seems to provide enough for at least get it running.

There seems to be a relatively easy path towards sanity here, since those canister init methods (Governance and GTC) will not be used on mainnet anymore (and no post_upgrade arguments), we can change them without affecting mainnet, for better initialization for testing and development. On the other hand, you seem to be the first one running into this. Could you help provide some explanation on why you canā€™t use the dfx extension? This will help a lot in justifying the work, and help us prioritize. Thanks!

Thanks for the answer. So, did I understand it correctly that Protobuf is a must for installing the Governance canister?

Not that I know, I donā€™t think didc supports protobuf right?
Does the foundation provides another tool to converts Protobuf with JS/TS?

Update: There is @dfinity/nns-proto but not sure its definition is up-to-date and there seems to be no Governance.encode feature, Iā€™ve got check further.

I donā€™t think I should provide any motivation or context about my feature unless itā€™s acceptable to consider dfx as a gatekeeper for accessing or developing on the Internet Computer?

But if you want some spontaneous thoughts:

  • Protobuf is rarely used for any other canisters; therefore, there is a discrepancy in the Governance canister regarding the state of the art of development on the IC.
  • Having to use Protobuf makes it hard or impossible to install the Governance canister with any tool that uses JavaScript ā€“ for example, my custom feature, but I can also think at trying to incorporate that canister with PocketIC using PicJS.

It looks like in ic-js there are some scripts to generate js and ts scripts from proto so if I would manage to update the proto files and all dependencies, I might be able to generate something.

How it can be used afterwards thatā€™s another questionā€¦

It seems I was finally able to deploy the Governance canister locally it took only the all dayā€¦

So, if anyone is ever looking to do the same, please note that PROTOBUF is required - i.e., you cannot encode your arguments with Candid to spin up an NNS Governance canister.

I had to update the @dfinity/nns-proto library (PR +79,445 āˆ’8,353) and then rewrite a chunk of code in my feature (PR). I think we originally planned to abandon this library given that it is not used anymore as it was primarily meant for the hardware wallet which now also uses Candid. So, Iā€™ll speak with my colleagues to either release a new version, or Iā€™ll just fork it.

Besides that, letā€™s now continue with the next issue :weary:: Exchange rate canister required to deploy CMC

If anyone ever interested, thatā€™s what you need to build the protobuf parameters for a NNS Governance canister:

import type {Identity} from '@dfinity/agent';
import {
  Decimal,
  Governance,
  NetworkEconomics,
  Neuron,
  NeuronsFundEconomics,
  Percentage,
  PrincipalId
} from '@dfinity/nns-proto';
import {
  NeuronsFundMatchedFundingCurveCoefficients,
  XdrConversionRate
} from '@dfinity/nns-proto/dist/proto/governance_pb';
import {NeuronId} from '@dfinity/nns-proto/dist/proto/nns_common_pb';
import {neuronSubaccount} from '@dfinity/sns';
import {MAIN_IDENTITY_KEY} from '../../constants/constants';
import type {ModuleInstallParams} from '../../types/module';
import {NEURON_ID} from './governance.constants';

export const prepareGovernanceArgs = ({
  identities
}: Pick<ModuleInstallParams, 'identities'>): Governance => {
  const {[MAIN_IDENTITY_KEY]: identity} = identities;

  // Source: https://github.com/dfinity/ic/blob/e90838a1687f8e0869d85343aac2845d883f74ff/rs/nns/governance/src/governance.rs#L231
  const E8S_PER_ICP = 100_000_000n;
  const DEFAULT_TRANSFER_FEE = 10_000n;

  const fund = new NeuronsFundEconomics();

  const decimal = (value: string): Decimal => {
    const dev = new Decimal();
    dev.setHumanReadable(value);
    return dev;
  };

  fund.setMaxTheoreticalNeuronsFundParticipationAmountXdr(decimal('750_000.0'));

  const efficients = new NeuronsFundMatchedFundingCurveCoefficients();
  efficients.setContributionThresholdXdr(decimal('75_000.0'));
  efficients.setOneThirdParticipationMilestoneXdr(decimal('225_000.0'));
  efficients.setFullParticipationMilestoneXdr(decimal('375_000.0'));

  fund.setNeuronsFundMatchedFundingCurveCoefficients(efficients);

  const percentage = (value: number): Percentage => {
    const dev = new Percentage();
    dev.setBasisPoints(value);
    return dev;
  };

  fund.setMinimumIcpXdrRate(percentage(10_000)); // 1:1
  fund.setMaximumIcpXdrRate(percentage(1_000_000)); // 1:100

  const eco = new NetworkEconomics();
  eco.setRejectCostE8s(Number(E8S_PER_ICP)); // 1 ICP
  eco.setNeuronMinimumStakeE8s(Number(E8S_PER_ICP)); // 1 ICP
  eco.setNeuronManagementFeePerProposalE8s(1_000_000); // 0.01 ICP
  eco.setMinimumIcpXdrRate(100); // 1 XDR
  eco.setNeuronSpawnDissolveDelaySeconds(24 * 60 * 60 * 7); // 7 days
  eco.setMaximumNodeProviderRewardsE8s(1_000_000 * 100_000_000); // 1M ICP
  eco.setTransactionFeeE8s(Number(DEFAULT_TRANSFER_FEE));
  eco.setMaxProposalsToKeepPerTopic(100);
  eco.setNeuronsFundEconomics(fund);

  const xdr = new XdrConversionRate();
  xdr.setTimestampSeconds(1);
  xdr.setXdrPermyriadPerIcp(10_000);

  const neuron = prepareNeuron({identity});

  const gov = new Governance();
  gov.getNeuronsMap().set(neuron.getId()?.getId() ?? NEURON_ID, neuron);
  gov.setWaitForQuietThresholdSeconds(60 * 60 * 24 * 4); // 4 days
  gov.setEconomics(eco);
  gov.setGenesisTimestampSeconds(0);
  gov.setShortVotingPeriodSeconds(60 * 60 * 12); // 12 hours
  gov.setXdrConversionRate(xdr);

  return gov;
};

const prepareNeuron = ({identity}: {identity: Identity}): Neuron => {
  const id = new NeuronId();
  id.setId(NEURON_ID);

  const subAccount = neuronSubaccount({
    index: 0,
    controller: identity.getPrincipal()
  }) as Uint8Array;

  const principalId = new PrincipalId();
  principalId.setSerializedId(identity.getPrincipal().toUint8Array());

  const neuron = new Neuron();
  neuron.setId(id);
  neuron.setAccount(subAccount);
  neuron.setController(principalId);
  neuron.setHotKeysList([principalId]);
  neuron.setCachedNeuronStakeE8s(1_000_000 * 100_000_000); // 1M ICP
  neuron.setCreatedTimestampSeconds(0);
  neuron.setAgingSinceTimestampSeconds(0);
  neuron.setKycVerified(true);
  neuron.setMaturityE8sEquivalent(0);
  neuron.setNotForProfit(true);
  neuron.setDissolveDelaySeconds(24 * 60 * 60 * 365 * 8); // 8 * 365 days

  return neuron;
};
1 Like

we can not use nns to test and mock, because we can not get the admin or provide tokens

maybe I can start part of nns canisters ? like I can deploy icp ledger by myself, then I can init some ICP(test) to local identity. and then I can test my product