I recently got into a discussion with several other developers around canister intermediate state, and wanted to clarify how messages processing effects the internal state of canisters in the case that the messages being processed await inter-canister calls or perform other asynchronous behavior (i.e. HTTP requests).
See the example below and my questions.
Reframing this same scenario, but in the context of intermediate state and update APIs that make inter-canister (async) calls.
Canister A has two update APIs - lockFunds()
and withdrawFunds()
, both of which modify the state in canister variable funds
. withdrawFunds()
awaits the result of at least one inter-canister call before finishing.
For example, if withdrawFunds()
looks something like this:
public func withdrawFunds(withdrawAmount: Nat): async Nat {
funds -= withdrawAmount;
await transferFunds(); // makes ICP transfer via the Ledger canister
funds;
}
Scenario 1
Let’s say I make 10 calls to withdrawFunds()
at the exact same time, so that Canister A’s message queue has 10 calls to withdrawFunds()
.
Scenario 2
Now lets say I make 5 calls to lockFunds()
and 5 calls to withdrawFunds()
at the exact same time, so that Canister A’s message queue has randomly interleaved orderings of the 5 calls to lockFunds()
and 5 calls to withdrawFunds()
.
Scenario 3
Take the same situation as Scenario 2, except now lockFunds()
does not mutate funds
, it just references the funds
variable.
Questions for Scenarios 1-3:
-
As the update calls are processed (in batch?), is there any leakage of the
funds
variable between update calls due to batch message process interleaving? Does it matter if the funds decrement happens before vs. after theawait
state commit point in thewithdrawFunds()
function code? -
Are there any changes to question 1) above if
funds
is not a primitive, but an object sayMap<UserPrincipal, Funds>
and each of the incoming calls is operating on a different user principal’s funds?
Scenario 4
Now let’s say Canister A also has an update API appendText()
, that takes in a text and appends it to a string.
If I make 5 calls to withdrawFunds()
and 5 calls to appendToText()
, will the message execution of these calls be “smartly” interleaved knowing that these functions touch separate state, or will each message wait for the previous message to be processed before executing?