There are parts that usually change in a proposal:
- A new
Action
variant. - A new proposal topic.
- A new
nnsFunction
or changes in one.
The change is not always just in one section. Many times you need to fix two sections simultaneously. For example, a new topic comes with a new nnsFunction
.
The Action
is a type in the Governance candid interface and it's used in the Proposal type:
type Action = variant {
RegisterKnownNeuron : KnownNeuron;
ManageNeuron : ManageNeuron;
ExecuteNnsFunction : ExecuteNnsFunction;
RewardNodeProvider : RewardNodeProvider;
OpenSnsTokenSwap : OpenSnsTokenSwap;
SetSnsTokenSwapOpenTimeWindow : SetSnsTokenSwapOpenTimeWindow;
SetDefaultFollowees : SetDefaultFollowees;
RewardNodeProviders : RewardNodeProviders;
ManageNetworkEconomics : NetworkEconomics;
ApproveGenesisKyc : ApproveGenesisKyc;
AddOrRemoveNodeProvider : AddOrRemoveNodeProvider;
Motion : Motion;
};
type Proposal = record {
url : text;
title : opt text;
action : opt Action;
summary : text;
};
Adding a new Action
variant breaks backwards compatibility.
This means that we need to upgrade the candid files and related, and synchronize the release with the Governance canister.
- Upgrade candid file.
- Add the i18n label in
en.governance.json
. - Upgrade the IC dependencies in nns-dapp canister.
The Internet Computer dependencies are in the Cargo.toml
of the backend
project. They are the ones that point to git = "https://github.com/dfinity/ic"
. For example:
ic-nns-governance = { git = "https://github.com/dfinity/ic", rev = "89129b8212791d7e05cab62ff08eece2888a86e0" }
Upgrading them means change the commit in rev = ...
.
Normally, upgrading the dependency of ic-nns-governance
is enough. But sometimes this library depends on the others and you need to upgrade the others.
Which commit?
Ideally, you should update to the upcoming commit that the NNS team is planning on releasing. You need to ask the NNS Team about their upcoming release.
Otherwise, you need to find the commit that added the new Action. Check the blame of the file in the IC repo or ask the NNS Team.
The topic is a property of the ProposalInfo
and it's of type integer.
type ProposalInfo = record {
id : opt NeuronId;
status : int32;
topic : int32;
failure_reason : opt GovernanceError;
ballots : vec record { nat64; Ballot };
proposal_timestamp_seconds : nat64;
reward_event_round : nat64;
deadline_timestamp_seconds : opt nat64;
failed_timestamp_seconds : nat64;
reject_cost_e8s : nat64;
latest_tally : opt Tally;
reward_status : int32;
decided_timestamp_seconds : nat64;
proposal : opt Proposal;
proposer : opt NeuronId;
executed_timestamp_seconds : nat64;
};
A new topic does not break backwards compatibility. Therefore, there is no need to synchronize releases.
Yet, a proposal of that topic won't be rendered properly until the changes are made and release.
Changes in nns-js:
- Add to topic entry in the governance enum.
- Add topic entry in the
Topic
for protobuf files. You can search forTOPIC_NEURON_MANAGEMENT
to better see where to add them.
Changes in nns-dapp:
- Add i18n labels in
en.governance.json
:topics
andtopics_description
. - Add i18n labels in
en.json
:follow_neurons.topic_XX_title
andfollow_neurons.topic_XX_description
The topic descriptions can be found in governance.proto
in IC repo.
One dependency for the topics is the Ledger App developed by Zondax.
When there is a new topic we need to open an issue in the Ledger ICP repo.
We need to specify the number of the new topic and the title that should be shown in the screen. The title should be the same as the label in en.governance.json
from the point above.
There are several steps to adding a new NNS function. They are described in detail below but at a high level they are:
- Verify that the intended change is for a new NNS function
- Understand the impact of a new NNS function
- Install a new governance canister
- Create a proposal with a payload for the new NNS function
- Verify that it doesn't render correctly yet
- Make the code changes
- Verify that it does render correctly
All the NNS functions are defined
here
under pub enum NnsFunction {
.
Check towards the bottom of the list of NNS functions to find the new type.
Note down its name and number. For example BitcoinSetConfig = 39
.
Note: If the above link is broken because the file has moved, here is an old version to track down what happened.
A new nnsFunction
does not break backwards compatibility. Therefore, there
is no need to synchronize releases.
Yet, a proposal with that nnsFunction
won't be rendered properly until the
changes are made and released.
Here we explain how to do it on dfx
network local
. It's possible to do the
same on testnet but that's not described here.
First you need to determine from which commit you want to install the governance
canister. This can be the commit that added the new proposal type or the latest
commit which provides built artifacts. The latter you can get by running
newest_sha_with_disk_image.sh
in the ic
repo.
git clone git@github.com:dfinity/ic.git
cd ic
git pull # If you didn't clone it just now
IC_COMMIT="$(./gitlab-ci/src/artifacts/newest_sha_with_disk_image.sh origin/master)"
echo $IC_COMMIT
The governance canister is installed by dfx
when you run dfx nns install
.
However, if there is a version of the canister in the dfx
cache, that version
is the one that will be used. So we first need to remove the canister from the
cache and then run dfx nns install
.
Note: If the last step below (dfx nns install
) gives an error because it
can't download some canister (for example lifeline.wasm.gz
), you can copy that
canister back from the backup directory into the cache directory and try
again, as long as it isn't the governance canister itself.
DFX_CACHE_DIR="$(dfx cache show)"
echo $DFX_CACHE_DIR
WASMS_BACKUP_DIR="$HOME/dfx-cache-wasms-backup-$(date +"%Y-%m-%d")"
echo $WASMS_BACKUP_DIR
mkdir -p "$WASMS_BACKUP_DIR"
mv $DFX_CACHE_DIR/wasms/* "$WASMS_BACKUP_DIR"
ls -l "$WASMS_BACKUP_DIR"
dfx start --clean # This will continue running so continue from another terminal
echo $IC_COMMIT
DFX_IC_COMMIT=$IC_COMMIT dfx nns install
You'll need to use ic-admin
to create the proposal. Because the payload
type is new, your current version of ic-admin
probably doesn't know about
it. So you should get a new version of ic-admin
:
Note: For Linux, use
https://download.dfinity.systems/ic/$IC_COMMIT/binaries/x86_64-linux/ic-admin.gz
instead of the Darwin URL.
curl "https://download.dfinity.systems/ic/$IC_COMMIT/openssl-static-binaries/x86_64-darwin/ic-admin.gz" | gunzip > ic-admin
chmod +x ./ic-admin
./ic-admin --help
Then you'll need the ic-admin
command to create the new proposal. You might
be able to figure out the command from ic-admin --help
but it's probably
easiest to ask someone on the NNS team or the person who asked you to add the
new proposal type. But for the --nns-url
flag you should be able to use the
result of
NNS_URL="http://localhost:$(dfx info replica-port)"
echo $NNS_URL
In order to be able to see the difference later, it's a good idea to see how the proposal renders without the required changes. You'll be able to see that it doesn't cause errors but also doesn't render the payload correctly.
To be able to deploy nns-dapp
locally, you need its canister ID in
.dfx/local/canister_ids.json
. If it isn't already, make sure that
.dfx/local/canister_ids.json
has at least the following:
{
"nns-dapp": {
"local": "qsgjb-riaaa-aaaaa-aaaga-cai"
}
}
If the file/directory doesn't exist at all, create it with that content.
Then deploy nns-dapp
:
- Build the Wasm. E.g.:
DFX_NETWORK=local ./build.sh
- Deploy the Wasm. E.g.:
dfx canister install nns-dapp --wasm nns-dapp.wasm.gz --upgrade-unchanged --mode reinstall -v --argument "$(cat nns-dapp-arg-local.did)"
Now you can visit http://qsgjb-riaaa-aaaaa-aaaga-cai.localhost:8080/ and check the proposal.
You'll need to make a 1-line change in the ic-js
repository to add the new NNS function to
the NnsFunction
enum
in packages/nns/src/enums/governance.enums.ts
. Use the name and number that
you noted down above in the section "Verify that the intended change is for a
new NNS function".
You'll need to change the following files:
For each dependency that has git = "https://github.com/dfinity/ic"
, change
the value of rev =
to the commit you decided to use above in the section
"Install a new governance canister".
If the proposal type also depends on types in other packages and/or repos, you may have to update/add those dependencies as well. To find out what exactly to add do the following:
- Find the file that has the type you need to depend on. For example for
SetConfigRequest
from the bitcoin canister, this is https://github.com/dfinity/bitcoin-canister/blob/master/interface/src/lib.rs - Then look for the
Cargo.toml
in the parent directory of the/src/
directory. - In the
Cargo.toml
file you should find the package name. For example for thisCargo.toml
file, it'sic-btc-interface
. - Use that name to add a line in
rs/backend/Cargo.toml
like this:
ic-btc-interface = { git = "https://github.com/dfinity/bitcoin-canister", rev="2c91aaae834dace5f1826ef41a910500d133d35e" }
- But replace
ic-btc-interface
with the package name you found in step 3, replace thegit =
value with the correct repo link, and replace therev =
value with a revision that contains the changes that you need. - Once the changes to
Cargo.toml
have been made, runcargo update
to generate the required changes inCargo.lock
.
Note: The updated dependencies might have breaking changes which need to be fixed when building nns-dapp later.
In en.governance.json
, you'll have to add entries to 2 different maps:
nns_functions
and
nns_functions_description
.
As the key in both maps, use the name that you noted down before and also used in the enum in the ic-js repo.
For nns_functions
use a label that's more or less the enum value name but
human readable. For example, for BitcoinSetConfig
use "Set Bitcoin Config"
.
For nns_functions_description
you might be able to find a good description
as a comment on the enum value definition.
If not, you'll just have to ask someone for a good description. This
description will be displayed when someone clicks on the (i) icon on the
proposal detail page.
You will need to know the fully qualified name of the proposal type you are adding. If you have the name of the type and the file it is defined in, you find the fully qualified name as follows:
- Find the package name as described in the
Cargo.toml
section above. - Take the directories under
/src/
on the path to the file. - If the filename is not
lib.rs
, take the filename as well. - Join everything from (1), (2), and (3) together with
::
and replace any hyphens with underscores.
For example for
BitcoinSetConfigProposal
,
you would get ic_nns_governance::governance::BitcoinSetConfigProposal
.
If you're lucky, the new type can be used as-is. But in some cases a transformation to the type is required to render it in a human readable way.
If no transformation is required:
- Add a new entry to
match nns_function
. Use the number you noted down above in the section "Verify that the intended change is for a new NNS function". And use the (non-fully qualified) name of the new proposal type. For example:
38 => identity::<UpdateElectedReplicaVersionsPayload>(payload_bytes),
- Add an entry at the bottom of the mod def section to define the type, pointing to the fully qualified type you looked up above. For example:
// NNS function 38 - UpdateElectedReplicaVersions
/// The payload of a proposal to update elected replica versions.
// https://gitlab.com/dfinity-lab/public/ic/-/blob/90d82ff6e51a66306f9ddba820fcad984f4d85a5/rs/registry/canister/src/mutations/do_update_elected_replica_versions.rs#L193
pub type UpdateElectedReplicaVersionsPayload =
registry_canister::mutations::do_update_elected_replica_versions::UpdateElectedReplicaVersionsPayload;
If a transformation is required:
- Add a new entry to
match nns_function
. Use the number you noted down above in the section "Verify that the intended change is for a new NNS function". And use the (non-fully qualified) name of the new proposal type, and then repeat it with theHumanReadable
suffix. For example:
39 => transform::<BitcoinSetConfigProposal, BitcoinSetConfigProposalHumanReadable>(payload_bytes),
- Add an entry at the bottom of the mod def section to define the type, pointing to the fully qualified type you looked up above. For example:
// NNS function 39 - BitcoinSetConfig
// https://github.com/dfinity/ic/blob/ae00aff1373e9f6db375ff7076250a20bbf3eea0/rs/nns/governance/src/governance.rs#L8930
pub type BitcoinSetConfigProposal = ic_nns_governance::governance::BitcoinSetConfigProposal;
- Define the corresponding
HumanReadable
type. For example:
#[derive(CandidType, Serialize, Deserialize)]
pub struct BitcoinSetConfigProposalHumanReadable {
pub network: ic_nns_governance::governance::BitcoinNetwork,
pub set_config_request: ic_btc_interface::SetConfigRequest,
}
- Implement
From<ProposalType>
forProposalTypeHumanReadable
. For example:
impl From<BitcoinSetConfigProposal> for BitcoinSetConfigProposalHumanReadable {
fn from(proposal: BitcoinSetConfigProposal) -> Self {
let set_config_request: ic_btc_interface::SetConfigRequest = candid::decode_one(&proposal.payload).unwrap();
BitcoinSetConfigProposalHumanReadable {
network: proposal.network,
set_config_request,
}
}
}
Follow the steps from "Verify that it doesn't render correctly yet" above to deploy the nns-dapp canister again and verify that now the proposal renders correctly.