Background
In order to properly understand the proposal below, we need to first agree on some terminology.
As the drawing above shows, a canister smart contract can expose two types of functions, update functions and query functions. A canister can be called by external users or by other canisters.
Further, calls can either be replicated or non-replicated. A replicated call means that the subnet blockchain came to an agreement to process the call and all honest nodes will perform it. Non-replicated calls only execute against a single node in the subnet.
When an update function executes successfully, the state changes that it makes are persisted and the state changes made by a query function are discarded. Hence, an update function can only be called as a replicated call while it is possible to call a query function as both replicated and non-replicated calls.
Now we can define the following types of calls:
- Ingress. When a user calls an update function. The user submits an ingress message and there is an agreement on the subnet blockchain to process the message.
- User query. When a user calls a query function as a non-replicated call. It is also possible for the user to call the function as a replicated call, in this case the user must submit an ingress message to call the function.
- Update call: When a canister calls an update function. There is an agreement on the subnet blockchain(s) to process the message.
- Non-replicated query: This is when the calling canister is executing a user query in the non-replicated fashion and it calls a query function on another canister. This feature is not currently supported and generally is referred to as the inter-canister query calls feature.
- Replicated query: When a canister is executing an update call (and hence executing a replicated call), it can call a query function on another canister that is also executed as a replicated call.
Proposal
Canister smart contracts need to expose various bits of state to other canisters and to external users. Examples of this state are set of controllers; wasm module hash, etc. Some of this state should only be exposed to their controllers (private state) and some of this state can be exposed to everyone (public state). Currently, not all necessary information is exposed and the exposed information is not exposed in a standardised way. This makes the developer experience unnecessarily complicated.
The two distinct mechanisms available today are:
- Reading from the System state tree. Currently this is only available to external users and this can only be used to expose the public state.
- Calling the IC method canister_status. This can only be called by controllers and only available in replicated calls (Ingress from users or update calls from canisters).
There are a number of scenarios that the above mechanisms do not address:
- Canisters cannot access the public information that another canister is exposing.
- The information available to controllers is only available in replicated calls. A user who is a controller has to use ingress messages which are relatively slow (compared to non-replicated queries) and also more expensive in terms of cycles charged to the called canister.
The end goal would be that all the public state that canisters want to expose is available to all principals (external users and canisters) and all private state is similarly exposed to all controllers (external users and canisters) in the most efficient manner.
The proposed solution is to add a new IC method: read_state. The input to this method will be the state tree as before and the response will be the hashtree and an optional certificate. This method will be callable by canisters as well as external users. It will be available as a query function. This will have the following properties:
- Users can call it efficiently in a non-replicated call using User Queries. In this case, the response will include a certificate which can be used by the caller to validate the response.
- Canisters will be able to call it as a replicated query. The response is guaranteed to be genuine so no certificate will be necessary.
- In the future, if we add support for inter-canister queries, then canisters will also be able to call it as a non-replicated call. In this case, the certificate will again be included.
Once the above method is added, the two existing methods, in particular the read_state http interface and the IC method canister_status, can both be deprecated.
Contributors
A number of people (potentially unknowingly) contributed to this proposal. The list should include at least: @chenyan, @bogdanwarinschi, @bjoern, @nomeata.