Skip to main content

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 Futures 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 StatePrivate State
Function Typeasync functionasync transition or transition
Data Storagemappingrecord
Visibilityeveryonevisible if you have the viewkey