Is it possible to Dynamically install code to FRONTEND canisters?

I’m trying to dynamically install canister code to UI canisters. First of all, is there a mechanism for doing such a thing? I’m attempting to do so using the following function

ic.install_code({
            arg : Blob, 
            wasm_module : Blob,
            mode = #upgrade;
            canister_id : Principal;
        });

If this is the proper function to use, what value must I insert as the arg value when the canister that I’m installing the code to is a frontend/UI canister?

2 Likes

@peterparker , might you know the answer to this one? you were spot on with my last post regarding the install_code() method.

do you mean replace the code of a [certified asset canister](Do you mean install_code to a certified asset canister (sdk/src/canisters/frontend at master · dfinity/sdk · GitHub)?) provided and deployed with dfx?

not an expert, notably because I wrote and use my own custom asset canister in Juno, but I would say yes, you can install_code as you can install_code in any canister on the IC as long as you are the controller

that said, assuming you want to preserve the canister features, install_code replace the all code so if you want to replace its code, then you will have to use the wasm of the certified canister you have compiled but maybe your goal is to replace the wasm code with something totally different?

regarding args, as far as I can see from this piece of code sdk/src/canisters/frontend/ic-certified-assets/src/lib.rs at b4a337f8ce25042894983fe47b2e3241b35cd453 · dfinity/sdk · GitHub, I would say it needs no args - i.e. empty. @Severin can confirm or unconfirm this.

I believe the UI canister I’m referring to corresponds to the ic-frontend-canister file. I’m talking about the canister that you get as a result of deploying the frontend code of a project that was created using dfx new. Does that make sense?

Thanks for clearing that. In that case we were indeed speaking about the same canister.

So i would do the following?


ic.install_code({ 
            wasm_module : Blob,
            mode = #upgrade;
            canister_id : Principal;
        });

Just omit the arg property?

I think arg is mandatory so I meant an empty blob like arg = Blob.fromArray([]) or something but would wait for Severin feedback.

1 Like

I’m pretty sure i want to preserve the canister features. I’m not familiar with what goes on when the frontend canister code is compiled and deployed, but I’ll describe the context to you so that you can derive the goal.

Context: im creating an app that is designed to have many instances deployed to the IC. The app has a frontend canister and backend canister. So each instance of the app that is deployed to the IC will have its own frontend canister (and consequently, it’s own URL) and it’s own backend canister.

I need a way to deliver upgrades to these many different instances of the app that are deployed to the IC. For that, I’ll be using the install_code method to programmatically deliver the upgrades to these many different instances. When calling install_code on the frontend canisters, i need to do so in a manner that does not change the canister ids that are specified within the canister_ids.json file. The reason being is that each instance of the app will have canister Id’s that are unique to that particular instance. Those canister id’s are specified in the canister_ids.json file, therefore, that file needs to be unchanged, while the rest of the code is still delivered to the frontend canister. Will i be able to achieve this using the install_code method to deliver the wasm modules to the UI canister’s? Or will the install_code cause the canister ids specified in the canister_ids.json file to be overwritten?

install_code replaces the wasm code of a canister without changing it’s id.

e.g. if I install a wasm-v0.0.2 in a canister ID 123 that runs wasm-v0.0.1, after install code the ID is still 123 and the code is wasm-v0.0.2

so in that sense, yes installing code does not impact the canister ids.

now, if I understand what you are trying to achieve, I think there are two distinct upgrades for your target goal

  1. upgrade the wasm of your frontend asset canister to stay up-to-date with the last enhancement and fix of the certified asset cansiter provided through dfx

  2. update the app (JS, HTML, etc) that exists in memory of your various canister instances to propagate the last enhancement and fix that your are developing

The 1. can be achieved with install_code by providing a newer version of the wasm and the 2., I think, can be achieved by replicating the upload process achieved by dfx deploy.

I don’t think 2. can be achieved by install_code out-of-the-box with that particular canister because the assets of your app aren’t part of the wasm that is uploaded.

Does that help and answer your question a bit?

1 Like

Personal note: for that type of architecture with multiple instances of the same apps, don’t know your project but, spontaneously, I would personaly go for a solution where I would develop my own canister that does both backend and frontend - i.e. not two canisters but one.

Time consuming to develop but worth it at the end. In Papyrs I got that two canisters approach and retrospectively I think it was an incorrect decision. In Juno I went for one canister - satellite - that does everything.

Of course it depends of the project, scalability etc. but for mine, never been so happy that way.

Just my two spontaneous cents for what is worth :grin:.

2 Likes

This does help, however i have 1 clarifying question:

If i make the updates to the JSX and HTML code of my frontend, and then call dfx build on the now-updated frontend code, I’ll get a .wasm file as a result. Are you saying that that .wasm file (if installed to the other instances of frontend canisters using the install_code method) Would not result in the other frontend canisters receiving the updated JSX and HTML? But it would result in them receiving the other updates you describe in the 1.?

Exactly. The wasm file does not contain the JSX and HTML. These outcome are uploaded afterwards.

dfx deploy a frontend canister does two things my understanding:

  1. upgrade wasm code
  2. upload the JS and HTML

Makes sense?

1 Like

Yes that makes sense. It’s unfortunate news for me because now i have no idea how to programmatically propagate updates of the JSX/HTML code to the frontend canister instances. Is there any documentation regarding the process by which the JSX/HTML code is uploaded to the canisters? That’d be a helpful starting point in this new researching arc that i must now embark on :sob:

Not really documentation, but the entry point is ic-asset::sync. If you want to run this standalone (without dfx deploying wasm), you’d use icx-asset sync.

The reason for using this install-and-only-after-upload approach is that if we baked all frontend code into the wasm, we’d hit size limits instantly - install_code accepts gzipped input, but still only ~2MB. Most frontends are larger than that, so we had to choose a different approach.

I see three ways forward for you: 1) Either write your own asset canister that bakes assets into it, the same way the NNS frontend dapp does it (and you’d be limited to the 2MB total asset size). 2) Or you can write your own syncing logic in a canister so that your canisters can automatically push/pull new frontend versions. 3) Or you manually use ic-asset::sync from your own computer, which would require access to all of your service’s canisters

1 Like

This option sounds like the best option, but what’s stopping this option from encountering the same 2MB issue? I’m assuming some chunking would have to be written into the logic then, right? Option 1 prohibitively limited and option 3 wouldn’t be possible since i wouldn’t be the canister controller of the other canisters that are due to receive the updates.

Now, what i need to do is understand the syncing logic and then implement it. Is there any reason why i wouldn’t be able to implement the syncing logic in Motoko? I don’t know rust at all. If necessary, I’ll bite the bullet and learn it, but would rather avoid adding such a task to my To-do list.

The easiest solution is probably to have all your frontend canister within your dfx.json (that’s possible right @severin) and create a bash script that does following pseudo-code:

for each canister ids of type asset declared in your dfx.json
    dfx deploy the_canister_name --network ic etc.
end

not the prettiest, not the most sustanable, not the cheapest, not the more secure - i.e. what if one update fails and the other not -, not the more engineered things but, can do the job

The syncing/chunking logic isn’t that bad. Here’s the canister interface and the code where data gets processed.

I don’t see any reason why it would be harder in Motoko than in Rust. In the end it’s just chunking blobs and making inter canister calls.

You’ll probably have to learn to read Rust if you want to copy the syncing logic, but that’s not too bad. If you have any questions on specific implementation details feel free to ask here.

2 Likes

@Severin , I noticed that the commit_batch method (along with several others) isn’t an asynchronous method. I mean to make it so that a motoko canister can call the commit_batch method of an assets canister in order to perform the upgrade for the JSX/HTML code within the asset canister. The fact that the commit_batch method isn’t an async method implies that this method cannot be called from another canister, correct?

Every function in any canister interface is an async method. Here’s how it gets called in the code that uploads assets via dfx.

If you’re referring to this function declaration, then you should not think of it as ‘not async’, but instead as an update function because it has #[candid_method(update)], which makes it callable from the outside, no matter if it is async fn or fn

1 Like

Thats perfect. Thanks a bunch!

1 Like