1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
//! Copyright (c) 2022 MASSA LABS <info@massa.net>

//! # General description
//!
//! This crate implements a consensual/deterministic pool of asynchronous messages (`AsyncPool`) within the context of autonomous smart contracts.
//!
//! `AsyncPool` is used in conjunction with `FinalLedger` within the `FinalState`, but also as a speculative copy for speculative execution.
//!
//! ## Goal
//!
//! Allow a smart contract to send a message to trigger another smart contract's handler asynchronously.
//!
//! Note that all the "coins" mentioned here are SCE coins.
//!
//! ## Message format
//!
//! ```json
//! {
//!     "sender": "xxxx",  // address that sent the message and spent fee + coins on emission
//!     "slot": {"period": 123455, "thread": 11},  // slot at which the message was emitted
//!     "emission_index": 212,  // index of the message emitted in this slot
//!     "destination": "xxxx",  // target address
//!     "function": "handle_message",  // name of the function to call in the target SC
//!     "validity_start": {"period": 123456, "thread": 12},  // the message can be handled starting from the validity_start slot (included)
//!     "validity_end": {"period": 123457, "thread": 16},  // the message can be handled until the validity_end slot (excluded)
//!     "max_gas": 12334,  // max gas available when the handler is called
//!     "coins": "1111.11",  // amount of coins to transfer to the destination address when calling its handler
//!     "function_params": { ... any object ... }  // parameters to call the function
//! }
//! ```
//!
//! ## How to send a message during bytecode execution
//!
//! * messages are sent using an ABI: `send_message(target_address, function, validity_start, validity_end, max_gas, fee, coins, function_params) -> Result<(), ABIReturnError>`.
//! * when called, this ABI does this:
//!   * it consumes `compute_gas_cost_of_message_storage(context.current_slot, validity_end_slot)` of gas in the current execution. This allows making the message emission more gas-consuming when it requires storing the message in queue for longer
//!   * it consumes `fee + coins` coins from the sender
//!   * it generates an `AsyncMessage` and stores it in an asynchronous pool
//!
//! Note that `fee + coins` coins are burned when sending the message.
//!
//! ## How is the `AsyncPool` handled
//! ```md
//! * In the AsyncPool, Messages are kept sorted by `priority = AsyncMessageId(rev(Ratio(msg.fee, max(msg.max_gas,1))), rev(msg.slot), rev(msg.emission_index))`
//!
//! * when an AsyncMessage is added to the AsyncPool:
//!   * if the AsyncPool length has exceeded config.max_async_pool_length:
//!     * remove the lowest-priority message and reimburse "coins" to the message sender
//!
//! * At every slot S :
//!   * expired messages are deleted, and "coins" are credited back to the message sender
//!   * messages that are valid at slot S (in terms of validity_start, validity end) are popped in highest-to-lowest priority order until they accumulate max_async_gas_per_slot. For each selected message M in decreasing priority order:
//!     * make sure that M.target_address exists and has a method called M.target_handler with the right signature, otherwise fail the execution
//!     * credit target_address with M.coins
//!     * run the target handler function with M.payload as parameter and the context:
//!       * max_gas = M.max_gas
//!       * fee = M.fee
//!       * slot = S
//!       * call_stack = [M.target_address, M.sender_address]
//!   * on any failure, cancel all the effects of execution and credit M.coins back to the sender
//!   * if there is a block at slot S, the execution of the block happens here
//!
//! ## How to receive a message (inside the smart contract)
//!
//! * define a public exported handler function taking 1 parameter
//! * this function will be called when a message is processed with the right `destination` and `handler`
//! ```
//!
//! # Architecture
//!
//! ## message.rs
//! Defines `AsyncMessage` that represents an asynchronous message.
//!
//! ## pool.rs
//! Defines the `AsyncPool` that manipulates a list of `AsyncMessages` sorted by priority.
//!
//! ## changes.rs
//! Represents and manipulates changes (message additions/deletions) in the `AsyncPool`.
//!
//! ## bootstrap.rs
//! Provides serializable structures and tools for bootstrapping the asynchronous pool.
//!
//! ## Test exports
//!
//! When the crate feature `test-exports` is enabled, tooling useful for test-exports purposes is exported.
//! See `test_exports/mod.rs` for details.

mod changes;
mod config;
mod mapping_grpc;
mod message;
mod pool;

pub use changes::{AsyncPoolChanges, AsyncPoolChangesDeserializer, AsyncPoolChangesSerializer};
pub use config::AsyncPoolConfig;
pub use message::{
    AsyncMessage, AsyncMessageDeserializer, AsyncMessageId, AsyncMessageIdDeserializer,
    AsyncMessageIdSerializer, AsyncMessageInfo, AsyncMessageSerializer, AsyncMessageTrigger,
    AsyncMessageTriggerSerializer, AsyncMessageUpdate,
};
pub use pool::{AsyncPool, AsyncPoolDeserializer, AsyncPoolSerializer};

#[cfg(test)]
mod tests;

#[cfg(feature = "test-exports")]
pub mod test_exports;