Overview
Introduction
To interact with a <Tooltip text="A smart contract on ICP."
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.
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 and answered asynchronously.
Example update call
- Motoko
- Rust
- TypeScript
- Python
actor countCharacters {
public func test(text : Text) : async Bool {
let size = Text.size(text);
return size % 2 == 0;
};
};
#[ic_cdk_macros::update]
fn increment() {
COUNTER.with(|counter| *counter.borrow_mut() += 1);
}
setMessage: update([text], Void, (newMessage) => {
message = newMessage; // This change will be persisted
})
@update
def set_message(new_message: str) -> void:
global message
message = new_message
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 Echo {
public query func say(phrase : Text) : async Text {
return phrase;
};
};
#[ic_cdk::query]
fn greet(name: String) -> String {
format!("Hello, {}!", name)
}
getMessage: query([], text, () => {
return message;
}),
@query
def get_message() -> str:
return message
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 |
Asynchronous 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.
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.