Call overview
Introduction
To interact with a canister's methods, there are two primary types of calls that can be used: update calls and query calls.
Canisters implement methods that can be called via update or query calls. A query method can be called as both an update and a query, whereas update methods can be called only as an update.
Update calls can make modifications to a canister's state, while query calls cannot.
See the reference on ingress messages for a more technical discussion of this topic.
Update calls
Update calls are executed on all nodes of a subnet since the result must go through consensus. This makes them slower compared to query calls. They are submitted asynchronously and answered synchronously.
Example update call
- Motoko
- Rust
- TypeScript
- Python
actor Counter {
stable var counter = 0;
public func inc() : async () {
counter += 1;
};
};
use candid::types::number::Nat;
use std::cell::RefCell;
thread_local! {
static COUNTER: RefCell<Nat> = RefCell::new(Nat::from(0_u32));
}
#[ic_cdk_macros::update]
fn inc() {
COUNTER.with(|counter| *counter.borrow_mut() += 1_u32);
}
import { IDL, update } from 'azle';
export default class {
counter: bigint = 0n;
@update([], IDL.Nat64)
inc(): bigint {
this.counter += 1n;
return this.counter;
}
}
from kybra import nat64, query, update
counter: nat64 = 0
@query
def count() -> nat64:
return counter
@update
def inc() -> nat64:
global counter
counter += 1
return counter
Kybra canisters must be deployed from a Python virtual environment. Learn more in the Kybra docs.
Query calls
Query calls, also referred to as non-replicated queries, are executed on a single node and return a synchronous response. Since they execute on a single node, they do not go through consensus and can be much faster than update calls.
The downside of query calls is that the response is not trusted since it's coming from a single node. An update call or a certified query (see below) should be used for security-critical calls.
Example query call
- Motoko
- Rust
- TypeScript
- Python
actor {
public func greet(name : Text) : async Text {
return "Hello, " # name # "!";
};
};
#[ic_cdk::query]
fn greet(name: String) -> String {
format!("Hello, {}!", name)
}
import { IDL, query } from 'azle';
export default class {
@query([], IDL.Text)
greet(name){
return "Hello, {name}";
}
}
from kybra import query
@query
def greet(name: str) -> str:
return f"Hello, {name}!"
Kybra canisters must be deployed from a Python virtual environment. Learn more in the Kybra docs.
Comparison of update calls vs query calls
Update calls | Query Calls |
---|---|
Slow (1-2s) | Fast (200-400ms) |
Can modify state | Can't modify state |
Goes through consensus | Does not go through consensus |
Synchronous response | Synchronous response |
Executed on all nodes of a subnet | Executed on a single node |
ICP message executions
Calls to canisters can be initiated by end users or other canisters. A call is processed by the canister in one or more message executions. A message execution is a set of consecutive instructions that ICP executes on behalf of the canister.
Typically, a call is processed within a single message execution unless there are calls to other canisters involved, in which case the call will be split across several message executions.
Learn more about the properties of ICP message executions.
Other types of calls
Composite queries
Composite queries are query calls that can call other queries (on the same subnet). They can only be invoked by end users, and not by other canisters.
Certified queries
Certified queries use certified variables to prove the authenticity of a piece of data that comes along with the query's response. Certified variables can be set via an update call and can then be read via a query call.
Replicated queries
Replicated queries, also referred to as the "query-as-update" execution model, are query calls executed as updates. The query still discards the state changes, but execution happens on all subnet nodes and the results go through consensus.
Inter-canister calls
Inter-canister calls are used to make calls between different canisters. An inter-canister call involves two messages: a request from the caller to the callee along with a response that the callee will return to the caller.
Authorized calls
An authorized call on ICP refers to a call to a canister that can only be made by a principal (a user or another canister) that has the necessary permissions to perform the call. For example, only a controller of a canister can call the canister's install_code
function.
Authorized calls that return a value of ()
indicate that the function was successful, but may not take an argument or return a meaningful value. If an authorized call is unsuccessful, it will return an error.
Making calls with IDLs
On the protocol level, calls on ICP use blobs to describe arguments and results passed to and returned from canisters. It's typically easier to use an Interface description language (IDL) to define a canister's interfaces that can be called by end users or other canisters.
While any IDL can be used for this purpose, most canisters on ICP use Candid, a specialized IDL that is developed for ICP and makes use of some of the unique features and properties of ICP.
Errors related to calling canisters
Common errors related to calling canisters include: