Update canister controlled by SNS canister

I need to update the code in one of the canisters controlled by my SNS backend.

I have the management library setup, i just need to know how i include the wasm_module, arg and sender canister version…


    public func updateManagerCanisterWasm() : async () {
      let IC : Management.Management = actor (Environment.Default);
      let updateResult = await (
        IC.install_code(
          {
            mode = #upgrade(null);
            canister_id = Principal.fromText("ljxqq-4iaaa-aaaal-qjd4a-cai");
            wasm_module = wasm_module;
            arg = Blob;
            sender_canister_version = ?Nat64;
          }),
      );
    };

The canister file is defined as an actor class and I’m not sure how I get it to be a wasm module for upgrade since I’ve only ever just created a new actor class as a canister and not upgraded one.

Any help appreciated.
Thanks,
James

Looking in here:

https://github.com/Web3NL/motoko-book/blob/main/src/common-internet-computer-canisters/_mo/ic-management/wasm.mo

Seems as if I need to just convert my actor class to a [Nat8], is there a tool to do this anywhere?

1 Like

Can you send the wasm as an argument to updateManagerCanisterWasm, then handle the formatting locally? If so this thread may help you through:

So the wasm isn’t created since the canister isn’t defined in dfx.json, it’s created dynamically by the backend.

I did have a look through the solution in that thread a few times, I’m surprise this would be the way, obviously it’s a fudge, surely there is a proper way to handle this core part of the workflow, updating dynamically created canisters?

I’m currently thinking the way to go is to create a new project for building dynamic canisters, add the actor class I want, build it using dfx.json then use David’s solution mentioned here.

1 Like

If someone could confirm this is the way to go that would be great.

I don’t want to lose all the managers selections in the canister with guess on how to update it.

Hi @jamesbeadle, let me try to help.

This is the ID of the target canister you want to upgrade, right? ljxqq-4iaaa-aaaal-qjd4a-cai

I can see that it’s controlled by bboqb-jiaaa-aaaal-qb6ea-cai (OpenFPL Dapp Canister) that in turn is controlled by the SNS (which can be seen from the fact that its controller is the OpenFPL SNS Root canister).

To upgrade ljxqq-4iaaa-aaaal-qjd4a-cai, you should do the following:

  1. Obtain a new WASM that you would like to upgrade your target canister to. This can be done by compiling the Motoko source code into a WASM module. To that end, either use dfx build (which requires a project file defined under dfx.json) or run moc directly, e.g.: $(dfx cache show)/moc main.mo
  2. Since your target canister isn’t directly controlled by the DAO, you would need to take one of the following approaches:
    • A. Make the DAO directly control the target canister by adding SNS Root (gyito-zyaaa-aaaaq-aacpq-cai) as its controller. This most likely requires implementing some new (but rather simple) functionality for the current controller of your target, namely bboqb-jiaaa-aaaal-qb6ea-cai, making it call the API for changing canister’s settings (just once would be enough). This can be done by dispatching a timer via IC-CDK.
    • B. Extend the functionality of the current controller (bboqb-jiaaa-aaaal-qb6ea-cai) to enable it to upgrade your target canister. This seems like much more involved depending on design details.

I would recommend taking approach A.

Hi,

Thanks for getting back to me, I’m fine with approach A, however building the canister with dfx cache show results in an error when referencing the mo:base libraries

What happens if your wasm is more than 2MB in size gziped? Is that supported via SNS upgrades?

1 Like

Not sure if this is helpful, but I had to use this to upgrade via an SNS:

export PROPOSAL_NEURON_ID="ca0c08391b8977845642e14932a763b531ad2d0b90b2cb93645140ac685c7467"
export IC_URL="http://localhost:8000"
export WASM_PATH="/Users/afat/Dropbox/development/origyn/gitlab/snsgovapp/.dfx/local/canisters/sns_gov_app2/sns_gov_app2.wasm"
echo $WASM_PATH
./bin/quill sns make-upgrade-canister-proposal --target-canister-id "a3shf-5eaaa-aaaaa-qaafa-cai" --wasm-path $WASM_PATH $PROPOSAL_NEURON_ID --canister-ids-file sns_canister_ids.json --pem-file ~/.config/dfx/identity/default/identity.pem  > message.json 

./bin/quill --insecure-local-dev-mode send message.json

Now if you have an SNS canister itself that is upgrading you have a couple different paths. If it it is an actor class you can use the system upgrade stuff in motoko:

Not obviously this only works if you have the actor referenced in your “parent” canister and you have upgraded that canister with the new code using the quill stuff above. And also you need to know the list of canisters…but if you have all of that you can set up a timer to loop through your list and upgrade one a round until they are all upgraded.

The second would be to do an upgrade via the management canister from the controlling canister.
Motoko code that also supports larger than 2MB wasm: GitHub - ORIGYN-SA/large_canister_deployer_internal: Install wasm code to a canister larger than 2 mb by chunking it and deploying it via a canister in the same subnet

Ok so I am able to get a list of canisters from my main backend and my manager canister I would like to upgrade does have a reference to the backend canister.

Is is an actor class:

If you see from the project solution the actor class is referenced by the parent class as it uses it to create the canister. You can see the manager canister being created on line 1413 here:

If ManagerComposite is your parent and MangementCanister is your child then it should be as easy as looping through your

for(thisItem in uniqueManagerCanisterIds.vals()){
 let oldManagement = actor(Principal.toText())
 let newManagement = await (system ManagerCanister._ManagerCanister)(#upgrade oldManagement)();
}

That should upgrade all of them provided the list is complete.

Note: You MAY want to stop them all first and then start them after the upgrade depending on what kind of processes you have running on those canisters and if they make untrusted calls to other canisters or not. I think the general best practice is to stop them.

1 Like

Legend I’m going to try this now, I’ll stop them thanks

So I’m going to run this via proposal which will be triggered on the postUpgrade call of my backend:


    private func postUpgradeCallback() : async (){
      let managerCanisterIds = seasonManager.getManagerCanisterIds();

      let IC : Management.Management = actor (Environment.Default);
      for(canisterId in Iter.fromArray(managerCanisterIds)){
        
        await IC.stop_canister({ canister_id = Principal.fromText(canisterId); }); 
        
        let oldManagement = actor (canisterId) : actor {};
        let _ = await (system ManagerCanister._ManagerCanister)(#upgrade oldManagement)();
        
        await IC.start_canister({ canister_id = Principal.fromText(canisterId); }); 
      };
      await seasonManager.snapshotFantasyTeams();
      
}

Finger crossed all the managers are still there after!

Well…I’d certainly test it locally first. :joy:

1 Like

What’s that?

JK, will do.

Ok it all seems to work locally, upgrades the dynamically created canisters without losing anything:

It looks like you’re going to be spot on sir. I should be able to get everything back up now.