Virtual Canister

When I visited Dfinity HQ a few weeks ago, we had an interesting discussion with @Severin and @Kepler about user roles and permissions for canisters. We discussed the concept of a virtual canister, which is essentially a wrapper on top of an existing physical canister that exposes only certain functions.

Key points discussed:

•	The virtual canister should have its own ID and Candid IDL.
•	This virtual canister can have role-based permissions assigned to it.
•	Eventually, the virtual canister could have a time-to-live (TTL) expiration period, allowing for the creation of temporary canisters.

What do you think?

4 Likes

What’s your specific use case you were thinking?

Also what kind of roles/permissions?

1 Like

Thanks for asking @Gekctek

We can take the scenario I described in this article: Developing Enterprise Applications with the Internet Computer Using Java Agent.

In this ICP-based business process, we have three roles with different sets of permissions: Loan Applicant, Credit Validator, and multiple Loan Providers with competing loan offers.

If we create a virtual canister for a Loan Provider, for example, by only enabling functions to send loan quotes, the loan provider doesn’t have to see any other interfaces. With a virtual canister, we can also add or remove new providers without needing to deploy any new physical canisters. Each provider will have a unique canister ID and will not know the canister ID of the physical canister. Of course, each virtual canister can be called only with a unique Identity key.

Additionally, I would pass context with the virtual canister ID and a set of attributes from the virtual canister to the physical canister.

5 Likes

So this would have to be something at the protocol level? Like the virtual canisters would be indistinguishable from the physical ones? Or are you thinking more of an application level abstraction?

1 Like

Yes, they will have unique ID and Candid IDL. I am not sure about gas usage, but there can be wallet assigned to virtual canister as well. For reverse gas model.

1 Like

Interesting idea.
Are you thinking the virtual canisters would be clones of the physical one or they would all run their own wasms?

When I was trying to utilize multiple dynamic canister actors for my project it was a real pain to manage and expensive

I’m trying to think of some of the downsides
If it’s one physical canister then you would probably run into a shared storage limit easier

It might make sense if you’re able to split up a physical canister into multiple and migrate virtual ones to the new physical if needed without changing canister ids

1 Like

This is super interesting. I’ve been contemplating ‘sub-actors’ for a while, but this seems a bit different. I’ve been banging the drum for a while for derived canister ids for outgoing messages, but I hadn’t considered them as much for incoming messages.

Theoretically though, besides the cycle cost, what is the difference between a ‘virtual-canister’ and just spawning a new canister that you intend to delete when it is done with its tasks? Does it have access to the same data as the virtual canisters?

1 Like

I’m thinking about virtual canisters like subaccounts, with sub-principals that define them. The root canister would then be able to handle access control and intercept incoming calls before they reach their intended sub-principal (virtual canister) destination (access control).

Some other benefits off the top of my head:

  • Able to monitor canister metrics at the virtual canister or main canister level
  • Multiple Dapps in a canister (user canister installs from a Dapp store/marketplace)
  • Data encapsulation between virtual canisters (think per-app data on a user-owned canister)
  • If data/permission between virtual canisters is provided, communication between different virtual canisters is instant. No more waiting for chained inter-canister calls.
  • Simpler architecture - imagine storing all of OpenChat or HotOrNot on a single canister with hundreds of thousands or millions of user canisters. Reduces the need and overhead of creating a multi-subnet architecture until per-subnet storage becomes an issue.
  • Could spin out a virtual canister from it’s root canister, creating an independent actor later on (if desired)
  • Strong synergies with the actor model of the IC
  • Don’t need to spend 100B cycles to spin up a new, distinct canistier

This would also help the protocol scale. Right now 120k canisters/subnet is tough limit, and dapp success means millions of users. Virtual canisters could allow for millions of users to live within a single canister, each with their own sub-principal.

1 Like

At the moment, we’re exploring Wasm Component Model support on IC. Along other scenarios, this component model support would enable the following:

  1. A main canister component (Motoko or Rust) controlling all IC communication (messaging, System API calls, and Stable Memory access), i.e. controlling roles and permissions and implementing virtualization.

  2. A library component (any Wasm supported language, potentially including Java and Kotlin), managing its own heap memory only. Any communication with the library must pass through the main component.

Do you think folks this might be a step in the right direction for your use case? While the component model alone doesn’t provide virtual canister IDs, that could be an independent small feature (e.g., a canister can create/delete alias canister IDs).

5 Likes

This sounds perfect.

The “virtual” part can be handled via interface management. Basically like a sub account in your parameters. I’d love derived canister ids, but what you are describing sounds like the next best thing.

3 Likes

I think that we should have only one physical canister in this case. All virtual canisters would essentially be “skins” on top of this single physical canister. This approach can simplify lifecycle maintenance. For example, if we have 1,000 virtual canisters on one physical canister, we only need to update the physical canister.

Additionally, I suggest that only the owner of the physical canister should have the authority to create virtual canisters. However, if we include a keyword like “public,” it would enable anyone to create a virtual canister on top of the physical one.

1 Like

All virtual canisters should share the same data space. Additionally, I would add an option to filter based on a virtual canister attribute so that the virtual canister caller will get a subset of the data. For instance, in my example, each Loan Provider will see only their loan data, and the filter attribute can be Provider Id. Consequently, the physical canister function will have a where condition with this attribute.

This format clearly outlines the requirement for shared data space with the capability to filter data based on a specific attribute.

This is an excellent feature to have, making the application more componentizable. However, it’s not exactly the same use case. The role of a virtual canister is to make existing canisters more reusable and to restrict access to canister functions that the calling party should not see. We can also have different types of virtual canisters assigned to one physical canister, corresponding to different roles like Loan Provider or Credit Validator. These roles should not be able to see or execute functions dedicated to the other.

However, it’s not exactly the same use case.

I’m trying to decompose the “virtual canister” feature into smaller pieces. Breaking down the requirements:

we should have only one physical canister in this case

There will be one physical canister with two components: (1) main component, providing virtualization and controlling permissions (IC-aware language, like Rust or Motoko), and (2) library component, providing the functionality (any language compiling to Wasm).

The virtual canister should have its own ID and Candid IDL.

The number of canister IDs is limited per subnet. The virtual canister IDs seem like aliases of a single “physical” canister.

Different Candid IDLs probably also doable, but seems like a lot of work across different tools…

This virtual canister can have role-based permissions assigned to it.

The roles and permissions could be controlled by the main component.

Eventually, the virtual canister could have a time-to-live (TTL) expiration period

The canister ID alias might be created and deleted as needed by the main component.

All virtual canisters should share the same data space.

The main and library components will have their own memories, but they are shared across all virtual functions.

each Loan Provider will see only their loan data, and the filter attribute can be Provider Id

The main component controls access to the stable memory, so it can enforce that only a subset of data is returned from the stable memory.

Most of the requested functionality could likely be achieved with components and aliases. Seems our main point of contention is who controls permissions: the IC itself or the main component of a canister, right?

1 Like

Thanks @berestovskyy for breaking this down. Indeed, the main driver behind this is permission control. The main component owner should be able to grant and revoke permissions for each individual partner. But it’s also about simplicity: if we have a canister with 100 functions and we are interfacing with a partner using only 2 of them, we should be able to provide an IDL with such a limited scope and also restrict invocation.

I think this can be solved with defining multiple services per canister, which is rarely used. So maybe an alias with service definition and individual permission setting would work. Additionally, we should be able to make such changes using an API so we can automate provisioning.

1 Like

I think the closest thing parallel to this would be a database view, with its own permissions and filter conditions.

1 Like

“Don’t create entities without necessity.”

Virtual canisters for cycles optimization is a good idea, but it should be made as simple as possible, the interface to virtual canisters should be as similar as possible to the interface to regular canisters.

So, add two new types of canisters:

  • container canister, that contains virtual canisters
  • virtual canisters, contained in a container canister

Add two new operations:

  • create a container canister
  • create a virtual canister inside a container canister

Then the only difference of virtual canisters would be that they are no allocated resources on their own but use a part of resources of container canister. This is useful mainly to reduce canister creation cost (very expensive now).

Guys, you seem to attempt to create multiple responsibilities for virtual canisters, while there should be only one responsibility, to have lesser creation cost. (Have only a single responsibility for an object, is one of the main principles of software engineers, for the case if you don’t know.)

1 Like

Thank you, @qwertytrewq, for your response. You make a very valid point about the cost of canister creation. I agree we should keep virtual canister operations as simple as possible.
However, I would still like to have a narrowed-down version of the IDL interface, probably defining multiple services per canister.
Besides security, the reason is to simplify the implementation of client canister applications. Many canisters today have very complex interfaces with tens of functions. We should give partners a simplified IDL version, only with the functions they will need to implement.

Currently, we do not see many problems with this because the majority of ICP applications are monolithic, with embedded UI. However, with enterprise ICP adoption, we will see more demand for canister invocation from external applications. Partner developers will implement client code in languages like Java or C# based on the canister IDL.

Candid is candid…just only return the functions you have access to…this seems like a function of the old __secret_candid_endpoint that is dynamic based on the caller. That seems fairly simple to do based off a standard without any replica changes.

1 Like