SNS: How do we adapt a script that's been depoying to a replica to now submit an SNS proposal instead?

Currently we are in the process of testing our repo for moving to the SNS way of doing things. We have an upgrade script here

We need help to adapt this part specifically:

To provide some more context, we have 2 types of canisters. We have top level canisters that need to directly be upgraded. And then we have these child canisters that are upgraded by the top level canisters on a call to a specific endpoint on the top level canister. There’s also access control that needs to be added to the top level canister so that only an authorized caller is allowed to call the upgrade on the top level canister.

Basically what we’re trying to figure out is

  • how do we submit an SNS proposal to the SNS governance canister to upgrade a canister and also pass an argument along? What would the payload look like?
  • How do we make the governance canister call an endpoint on the top level canister passing along a payload?

Difficulties that we are currently facing testing this with the sns-testing repo is that the upgrade_dapp script expects to be run from the testing repo but our upgrade script calls dfx which expects to be run from the project directory.

Thoughts?

@lara @mraszyk @Severin

how do we submit an SNS proposal to the SNS governance canister to upgrade a canister and also pass an argument along?

You can use quill sns make-upgrade-canister-proposal for that.

What would the payload look like?

I suppose you refer to a post-upgrade argument: that can be passed in its textual Candid form using the flag --canister-upgrade-arg or in its binary form stored in a file specified via the flag --canister-upgrade-arg-path.

How do we make the governance canister call an endpoint on the top level canister passing along a payload?

You can find instructions on that here (Home | Internet Computer) and in the Test canister section of the sns-testing README (GitHub - dfinity/sns-testing: Testing SNS in local testing environment). You can think of the test canister from the sns-testing as a placeholder for one of your top-level canisters.

The payload is embedded in the SNS proposal created by quill sns make-proposal and specified either in its textual Candid form using the flag --proposal or in its binary form stored in a file specified via the flag --proposal-path. Note that in both cases we need to pass a Candid value for the entire SNS proposal incl. title, summary, and action (AddGenericNervousSystemFunction to register the functions or ExecuteGenericNervousSystemFunction to execute them).

the upgrade_dapp script expects to be run from the testing repo but our upgrade script calls dfx which expects to be run from the project directory

You can try out the branch mraszyk/change-script-dir from this PR and the Docker container martin2718/sns-testing-multidir that I built from that branch.

Then upgrade_dapp.sh can also be invoked from the /dapp directory:

# empty 2nd argument of upgrade_dapp.sh means that `dfx build` is executed to build the canister's WASM
/dapp# ~/upgrade_dapp.sh dapp_backend "" "(opt record {sns_governance = opt principal\"si2b5-pyaaa-aaaaa-aaaja-cai\"; greeting = opt \"Hoi\";})"

# it is also possible to pass a binary argument stored in a file (can also be produced by Candid encoding a Rust struct into a file)
/dapp# ~/bin/didc encode "(opt record {sns_governance = opt principal\"si2b5-pyaaa-aaaaa-aaaja-cai\"; greeting = opt \"Hoi\";})" | xxd -r -p > a.bin
/dapp# ~/upgrade_dapp.sh dapp_backend "" ./a.bin

Here is a concrete example of using the generic functions on the test canister from the /dapp directory:

# for the generic functions, we need to set up `IC_URL` for `quill`
/dapp# export IC_URL="http://localhost:8080"

# and also the developer SNS neuron ID (only works on an sns-testing dev branch)
/dapp# DEVELOPER_NEURON_ID=$(~/developer_neuron_id.sh)

# note that `--pem-file` must be an absolute path
/dapp# ~/bin/quill sns  \
   --canister-ids-file "~/sns_canister_ids.json"  \
   --pem-file "/home/sns/.config/dfx/identity/default/identity.pem"  \
   make-proposal --proposal "(record { title=\"Register generic functions for test canister.\"; url=\"https://example.com/\"; summary=\"This proposals registers generic functions for test canister.\"; action=opt variant {AddGenericNervousSystemFunction = record {id=2000:nat64; name=\"execute\"; description=\"set parameters of test canisters\"; function_type=opt variant {GenericNervousSystemFunction=record{validator_canister_id=opt principal\"$(dfx canister id dapp_backend)\"; target_canister_id=opt principal\"$(dfx canister id dapp_backend)\"; validator_method_name=opt\"validate\"; target_method_name=opt\"execute\"}}}}})" $DEVELOPER_NEURON_ID > msg.json
/dapp# ~/bin/quill --insecure-local-dev-mode send --yes msg.json

# the payload (here of type `String`) passed to the function of the test canister is Candid encoded into a blob
/dapp# ~/bin/quill sns  \
   --canister-ids-file "~/sns_canister_ids.json"  \
   --pem-file "/home/sns/.config/dfx/identity/default/identity.pem"  \
   make-proposal --proposal "(record { title=\"Execute generic functions for test canister.\"; url=\"https://example.com/\"; summary=\"This proposal executes generic functions for test canister.\"; action=opt variant {ExecuteGenericNervousSystemFunction = record {function_id=2000:nat64; payload=$(didc encode --format blob "(\"Hoi\")")}}})" $DEVELOPER_NEURON_ID > msg.json
/dapp# ~/bin/quill --insecure-local-dev-mode send --yes msg.json
3 Likes

Hi @mraszyk

I’m running into this when I run the following:

quill sns make-upgrade-canister-proposal \
--target-canister-id $(dfx canister id configuration) \
--wasm-path ./target/wasm32-unknown-unknown/release/configuration.wasm.gz \
--canister-upgrade-arg "(record {})" \
8474081407935031357

I’m quite certain I copied the neuron ID correctly. Here:

Thoughts on what I’m doing wrong?

Thoughts on what I’m doing wrong?

The neuron ID must be hex encoded:

ARGS:
    <PROPOSER_NEURON_ID>    The id of the neuron making the proposal. A **hex encoded** string

A suitable representation can be derived by listing all SNS neurons:

dfx canister --network "${NETWORK}" call sns_governance list_neurons "(record {of_principal = null; limit = 0})"

finding the ID represented as a Candid-encoded blob and then convert it to hex (tail -c +21 strips the Candid prefix before the encoding of the actual blob):

didc encode '(blob "\c7\d6\12|\e8\93\83\b2\b7w\86C~\9a\e9\95\98\cc\0f\16\1a\a7T\bc\ecRr\ba\c0T\aa\01")' | tail -c +21

@mraszyk

What’s the easiest way to get the neuron information running from the project folder? So, that I can make it part of my upgrade script?

I wouldn’t be able to call this:

dfx canister --network "${NETWORK}" call sns_governance list_neurons

from my own upgrade script inside my project folder. Thoughts?

@mraszyk
I have also tried running the Mac instructions to be able to just run these commands locally but stuck on the first step of starting the replica and getting this:

Thoughts?

I’m not sure if you have all the required dynamic library dependencies on your nixos? Could you please try ldd ./bin/dfx and check if a dynamic library is missing?

@mraszyk This is what I get

Thoughts?

I’m not sure why you cannot run dfx on your system then. I’d also check if the binary is marked executable, who’s the owner etc. But it’s difficult to tell from the screenshots (and I couldn’t reproduce the issue myself).

@mraszyk
I checked permissions. This is what I see

I will try the docker way of things in that case. But I’m stuck trying to figure this out.

Thoughts on the above?

On the branch mraszyk/change-script-dir (or the corresponding docker image martin2718/sns-testing-multidir), you can invoke the script developer_neuron_id.sh from anywhere and it will print to stdout the SNS neuron id for the default DFX identity.

1 Like

Hi @saikatdas0790,

First here is a link to the open-chat sns governance canister: Canister: OpenChat Governance - ICP Dashboard. click on the manage_neuron method and look at the payload configurations to see the structure of the payload. Put a checkmark in the command option, then click on the variant dropdown and select MakeProposal. Put a checkmark where it says action, then click on the new variant dropdown within the action box and you can view the proposal action types and their payloads configurations.

The proposal action for upgrading an sns controlled canister is: UpgradeSnsControlledCanister.
In the UpgradeSnsControlledCanister variant value you can specify the new_canister_wasm, the canister install mode (represented as an int32. check here for the int32 values that represent the modes. ) (if you put a None value for the mode it will be the upgrade mode), the canister_id that you want to upgrade, and the canister_upgrade_arg.

This one requires a first proposal to set up the governance canister’s ability to call the specific endpoint, and then further proposals can execute that ability and make the governance canister perform the call to the specific endpoint on the top-level canister.

The first proposal action type is AddGenericNervousSystemFunction. In the AddGenericNervousSystemFunction value you specify an id that identifies this proposal-that-calls-the-specific-endpoint (id must be >= 1000), a name (something like: ‘Upgrade Child Canisters’), a description (something like: ‘This proposal type makes the sns governance canister call the endpoint on the top level canister to start upgrading the child canisters.’), and a function_type variant select the GenericNervousSystemFunction variant type and there you can put the values: target_canister_id is the canister_id of your top-level-canister, and target_method_name is the method name which you want to call on the top-level-canister. validator_canister_id and validator_method_name are a canister and method accept the same argument as target_method_name but returns a Result<String, String>. When making a later proposal (of type ExecuteGenericNervousSystemFunction reference below) to call the target_method_name with an argument, the governance canister will first call this validator function that you specify with the argument and will validate that the argument is valid (return an Ok(String) if the argument is valid or an Err(String) if the argument is invalid) before calling the target_method_name with the argument. The validator method is there as a double check sort of thing so that when making proposals to call target_method_name with an argument, the governance canister double checks that the argument is valid for the target_method_name by calling the validator function with the same argument.

Once that proposal passes and the governance canister’s ability to call the top-level-canister’s method is set up, then further proposals can execute this function and make the governance canister perform the call to the top-level-canister. This is done by the proposal action type: ExecuteGenericNervousSystemFunction. In the ExecuteGenericNervousSystemFunction variant value, the function_id is the id that you put previously that identifies the proposal-that-calls-the-specific-endpoint (id >= 1000), and the payload is the payload that the governance canister will call the top-level-canister with. When making a proposal of this type, the governance canister will call the validator method (reference above) with the payload to double check that payload is valid for the top-level-canister method and if it is invalid the governance canister will not register the proposal.

In this picture you copied the NNS neuron id. The neuron id for making proposals for an sns must be an SNS-neuron of that specific SNS.

3 Likes