The Asynchronous Programming Model
Background
The Leo asynchronous programming model enables users to update public on-chain data using a developer-friendly syntax. A function call to on-chain code is treated as an async function call which returns a Future
object. A Future
contains a description of a call to a function: it consists of the name of the function and of the values passed as arguments. The execution of the on-chain state change occurs after validators verify the proof associated with the transaction.
Managing Public State
On-chain data is stored in public mappings. Any logic that reads from or updates the state of a mapping must be contained within an async function
block as follows:
program first_mapping.aleo {
mapping accumulator: u8 => u64;
async function finalize_increment_state(){
let current_count: u64 = accumulator.get_or_use(0u8, 0u64); // Get current value, default 0
let new_count: u64 = current_count + 1u64;
accumulator.set(0u8, new_count);
}
}
However, users can only call transition
functions. In order for the Future generated by an async function
to be usable, it must be returned by a transition
function. Any transition
that calls on an async function
within its block must be annotated with the async
keyword and must explicitly return a Future
. Async transitions can return additional data types in a tuple, including Records, along with a Future
. Only one Future
can be returned. If multiple types are returned, the Future
must be the last type in the tuple.
program first_mapping.aleo {
mapping accumulator: u8 => u64;
async transition_increment() -> Future {
return finalize_increment_state();
}
async function finalize_increment_state(){
let current_count: u64 = accumulator.get_or_use(0u8, 0u64); // Get current value, default 0
let new_count: u64 = current_count + 1u64;
accumulator.set(0u8, new_count);
}
}
Calling async transitions from external programs
Leo enables developers to call external async transitions
from imported programs in an async transition
. A call to an async transition returns a Future
which must be passed as inputs to an async function. These Future
s must be composed inside of the async function
using the await
keyword, as shown in the example below.
import first_mapping.aleo;
program second_mapping.aleo {
mapping hashes: u8 => scalar;
async transition two_mappings(value: u8) -> Future {
let increment_future: Future = first_mapping.aleo/transition_increment();
return finalize_update_mapping(value, imported_future);
}
async function finalize_update_mapping(value: u8, imported_future: Future) {
imported_future.await();
let hash: scalar = BHP256::hash_to_scalar(value);
hashes.set(value, hash);
}
}
Managing public and private state in async transitions
Updating private state on Aleo utilizes off-chain proof generation to preserve the confidentiality of the user’s data and associated address. Therefore, Records cannot be created or consumed within the scope of async functions
. However, Records can be used inside of the scope of async transitions
. This is because transition and async transition functions are initially executed off-chain and are accompanied by proofs of correct execution which are subsequently verified by validators. Once the proof is verified, validators execute the code contained within a Future
, which is solely defined by code within an async function
.
Public State | Private State | |
---|---|---|
Function Type | async function | async transition or transition |
Data Storage | mapping | record |
Visibility | everyone | visible if you have the viewkey |