# Program interaction
## 1 part (Communication between programs)
In Gear, the `handle` and `handle_reply` are the basic means for programs to send and receive messages to and from other programs in the system.
When a program receives a message, the `handle` function is called, which can then perform some action based on the contents of the message and optionally send a reply message back to the original sender. If a reply message is sent by the receiver, then the `handle_reply` entrypoint is called on the original sender.
Let's consider examples of message sending and handling replies to them.
1. An example of a simple message sending, as already studied in previous lessons, looks as follows:
```rust
#![no_std]
use gstd::{msg, ActorId, MessageId};
static mut DESTINATION: ActorId = ActorId::zero();
#[no_mangle]
extern "C" fn init() {
let destination = msg::load().expect("Failed to load the message");
unsafe { DESTINATION = destination; }
}
#[no_mangle]
extern "C" fn handle() {
msg::send(unsafe { DESTINATION }, [], 0).expect("Failed to send message");
}
```
Let's now consider an example where a program sends a message and receives a reply via `handle_reply` entrypoint.
The program sends a message to another program and saves the id of the message it sent. If the receiver program sends a reply to the sender using the `msg::reply` function, then the `handle_reply` function is called on the original sender, which can identify which message it is receiving a reply for using the `msg::reply_to` function.
```rust
#![no_std]
use gstd::{msg, ActorId, MessageId};
static mut DESTINATION: ActorId = ActorId::zero();
static mut MESSAGE_SENT: MessageId = MessageId::zero();
#[no_mangle]
extern "C" fn init() {
let destination = msg::load().expect("Failed to load the msg");
unsafe { DESTINATION = destination; }
}
#[no_mangle]
extern "C" fn handle() {
let message_sent = msg::send(unsafe { DESTINATION }, [], 0)
.expect("Failed to send message");
unsafe { MESSAGE_SENT = message_sent; }
}
#[no_mangle]
extern "C" fn handle_reply() {
let replied_to = msg::reply_to().expect("Failed to query reply_to data");
if replied_to == unsafe { MESSAGE_SENT } {
let _result = msg::load_bytes().expect("Failed to load bytes");
// DO SOMETHING WITH _result
}
}
```
2. A program can also send a message, receive a reply, and return the reply into `handle` entrypoint with a new message.
```rust
#![no_std]
use gstd::{exec, msg, ActorId, MessageId};
// Storing SELF only for gas optimization: can be queried every time.
static mut DESTINATION: ActorId = ActorId::zero();
static mut MESSAGE_SENT: MessageId = MessageId::zero();
#[no_mangle]
extern "C" fn init() {
let destination = msg::load().expect("Failed to get destination id from payload");
unsafe { DESTINATION = destination; }
}
#[no_mangle]
extern "C" fn handle() {
if msg::source() == exec::program_id() {
let _result = msg::load_bytes().expect("Failed to load bytes");
// DO SOMETHING WITH _result
} else {
let message_sent = msg::send(unsafe { DESTINATION }, [], 0)
.expect("Failed to send message");
unsafe { MESSAGE_SENT = message_sent; }
}
}
#[no_mangle]
extern "C" fn handle_reply() {
let replied_to = msg::reply_to().expect("Failed to query reply_to data");
if replied_to == unsafe { MESSAGE_SENT } {
let result = msg::load_bytes().expect("Failed to load bytes");
msg::send_bytes(exec::program_id(), result, 0)
.expect("Failed to send message");
}
}
```
3. Another way to handle message passing in a Gear program is to use the `exec::wait()` and `exec::wake()` functions:
- `exec::wait()`: pause the current message handling. It completes the current message handle execution with a special result and puts this message into the waiting queue. The message can then be awakened using the corresponding wake function at a later time.
:::info
:bulb: When a message goes into the waiting state using the `exec::wait()` function, all the changes made by the program before the wait call are saved. These changes are stored in the program's persistent storage, so they are not lost when the program is paused.
:::
:::info
:bulb: When a message is waiting, all gas that hasn't been spent yet is attributed to that message in the waiting queue. If the gas runs out while the message is waiting, it will not be awakened, and the program can be stuck in an intermediate state.
:::
So, if you write it as follows:
```rust
#[no_mangle]
extern "C" fn handle() {
msg::send(unsafe { DESTINATION }, [], 0).expect("Failed to send message");
exec::wait();
}
```
all the gas will be used for the waiting queue, and the message will be sent with zero gas.
Therefore, Gear also provides the ability to enter a waiting state for a certain number of blocks using the `wait_for` and `wait_up_to` functions:
- `wait_for(duration)`: The message waits for the specified number of blocks, and after that time, it wakes up itself if it hasn't been woken up before. If the message doesn't have enough gas to pay for the specified number of blocks, then the function panics.
- `wait_up_to(duration)`: The message waits for the number of blocks that it has enough gas to pay for, and this number of blocks does not exceed the specified `duration`.
- `exec::wake()`: Resume the execution of a message that was previously paused using the wait function. When the wake function is called with a valid `message_id`, it will take the message out of the waiting queue and put it back into the processing queue. The `message_id` must be a valid ID of a message that is currently waiting in the queue. Otherwise, the function will return an error.
:::info
:bulb: It is important to note that message execution starts from the very beginning. The message comes to the `handle` entrypoint and executes all the logic from the beginning.
:::
Here's an example that demonstrates the use of the `wait` and `wake` functions:
```rust
#![no_std]
use gstd::{exec, msg, ProgramId, MessageId};
static mut DESTINATION: ProgramId = ProgramId::zero();
static mut ORIGINAL_MESSAGE: MessageId = MessageId::zero();
static mut MESSAGE_SENT: MessageId = MessageId::zero();
static mut REPLY: Option<Vec<u8>> = None;
#[no_mangle]
extern "C" fn init() {
let destination = msg::load().expect("Failed to get destination id from payload");
unsafe { DESTINATION = destination; }
}
#[no_mangle]
extern "C" fn handle() {
if let Some(_result) = unsafe { REPLY} {
// DO SOMETHING WITH _result
} else {
let message_sent = msg::send(unsafe { DESTINATION }, [], 0).expect("Failed to send message");
unsafe { MESSAGE_SENT = message_sent; }
unsafe { ORIGINAL_MESSAGE = msg::id(); }
exec::wait(10);
}
}
#[no_mangle]
extern "C" fn handle_reply() {
let replied_to = msg::reply_to().expect("Failed to query reply_to data");
if replied_to == unsafe { MESSAGE_SENT } {
unsafe { REPLY = Some(msg::load_bytes().expect("Failed to load bytes")); }
exec::wake(unsafe { ORIGINAL_MESSAGE }).expect("Failed to wake message");
}
}
```
In this example, the initial value of `REPLY` is set to None, so the program sends a message to another program and waits for a reply for a maximum 10 blocks by calling `exec::wait(10)`. The program execution is paused until a message is received or until 10 blocks have passed.
When a reply message is received, the `handle_reply` function is called. It loads the reply message and saves it to the REPLY variable. The `exec::wake()` function is then called with the message ID to wake up the handle function and continue its execution.
The message starts executing from the very beginning. The program checks if there is a reply, and if there is one, it means that the message was already sent and the reply has been received.
Gear provides a macro called [#[gstd::async_main]](https://docs.gear.rs/gstd/attr.async_main.html) which allows you to write the same functionality in a more convenient way. Instead of using the `exec::wait()` and `exec::wake()` functions, you can write your code using `async/await` syntax and rely on the macro to take care of the message loop and handling of replies:
```rust
#[gstd::async_main]
async fn main() {
let receiver: ActorId = msg::load().expect("Unable to decode `ActorId`");
let reply = msg::send_for_reply(receiver, [], 0)
.expect("Unable to send `Hello` message")
.await
.expect("Unable to decode the reply");
// decode the received reply
let decoded_reply = String::decode(&mut &reply[..]).expect("Unable to decode the reply");
if decoded_reply == "Hello" {
// Do something after receiving the reply
}
}
```
With this macro, the code for handling messages and replies is generated automatically. Here's what the generated code looks like:
```rust
#[no_mangle]
extern "C" fn handle() {
gstd::message_loop(async {
let receiver: ActorId = msg::load().expect("Unable to decode `ActorId`");
let reply = msg::send_for_reply(receiver, "Hello", 0)
.expect("Unable to send `Hello` message")
.await
.expect("Unable to decode the reply");
// decode the received reply
let decoded_reply = String::decode(&mut &reply[..]).expect("Unable to decode the reply");
if decoded_reply == "World" {
// Do something after receiving the reply
}
});
}
#[no_mangle]
extern "C" fn handle_reply() {
gstd::record_reply();
();
}
```
When using the [#\[gstd::async_main\]](https://docs.gear.rs/gstd/fn.message_loop.html) macro, it may seem like the main() function becomes the entry point of the program, but in reality, it is still the
`handle()` and `handle_reply()` functions that serve as the entry point. So, if this macro is used, there is no need to define the `handle()` and `handle_reply()` functions as they are automatically generated by the macro.
The `handle()` function generated by the #\[gstd::async_main] macro uses the [`message_loop()`] function, which creates an infinite loop that receives and processes messages. On the other hand, the `handle_reply()` function generated by the macro contains calls [`record_reply()`](https://docs.gear.rs/gstd/fn.record_reply.html), which records the reply message and wakes up the waiting message, allowing the main loop to continue processing messages.
## 2 Part (Reply provision)
The communication between smart contracts in the Gear protocol is based on the actor model. This implies that each contract is viewed as an actor that is capable of receiving designated messages and responding to them.
<center>
<img src="https://i.imgur.com/aa9FglT.png" width=440>
</center>
Being aware of the delivery and processing status of messages is very important for a program. If a message sent by a program fails to be processed, it is crucial for the program to receive notification of this failure.
Let's explore scenarios where a message sent by a program may not be processed:
1. When program A sends a message to program B, there is a possibility that the message may not be received by program B if program A did not attach sufficient gas to the message.
2. When program B processes the message, the processing may also be interrupted if the gas runs out or if an unexpected panic occurs.
3. After program B finishes processing the message and sends a response, there is still a possibility that program A may not receive the response if the gas runs out before the response is delivered.
To ensure that program A is aware of these scenarios, the Gear protocol provides a functionality that reserves gas for notification of message failures:
```rust
let msg_id = msg::send(program_id, "", 0).expect("Error during message sending");
exec::create_provision(msg_id, REPLY_PROVISION).expect("Failed to create a reply provision");
```
Thus, when a program sends a message, it uses the `create_provision(msg_id, amount)` function to specify the amount of gas to be spent on notifying the program of the message execution status. This is crucial especially when a state change in program B affects the further execution of program A.
## 3 Part
In decentralized finance (DeFi), smart contracts often interact with one another to provide a wide range of financial services.
Let's say we want to create a decentralized exchange contract where users can trade tokens, and the exchange contract needs to know the current token price from an external source. In this scenario, we can use the Actor Model to represent the different entities involved in the exchange as actors:
- The first actor is the `Token-Exchange` Actor, which is responsible for managing the exchange's state and handling transactions.
- The second actor is the `Token` Actor, which represents the token that is being traded and is responsible for keeping track of user balances.
- The third actor is the `Oracle` Actor, which holds the current token price.
The process of purchasing tokens through the token exchange contract involves the following steps:
1. The buyer sends a message to the `Token-Exchange` Actor with the details of the token he want to buy and the amount he is willing to pay.
2. The `Token-Exchange` Actor receives the message and sends a message to the `Oracle` Actor, requesting the current price of the token.
3. The `Oracle` Actor receives the message and sends a message back to the `Token-Exchange` Actor with the current price of the token.
4. The `Token-Exchange` Actor checks if the current price of the token is within the buyer's budget.
5. If the current price of the token is within buyer's budget, the `Token-Exchange` Actor sends a message to the `Token` Actor with the details of the transaction.
6. Upon receiving the message, the `Token` Actor reduces the amount of tokens held by the `Token-Exchange` contract. Then, it sends a response message indicating that the token selling has been completed.
7. The `Token-Exchange` Actor then sends a message to the buyer, confirming that the transaction has been successful.

In the context of token purchasing through the Token-Exchange contract, we can see that there are multiple interactions between different actors. The buyer sends a message to the Token-Exchange Actor, which then communicates with the Oracle Actor to obtain the current token price. Once the price is confirmed, the Token-Exchange Actor then sends a message to the Token Actor to initiate the transfer of tokens to the buyer.
All of these interactions together form a larger business transaction. This business transaction is divided into small sub-transactions that take place between different actors in a coordinated manner. Each sub-transaction is necessary for the successful completion of the larger business transaction, and any failure at any point in the process can cause the entire transaction to fail.
Therefore, it is important to ensure that each sub-transaction is executed correctly and that each actor performs its role as expected. This ensures the successful completion of the entire business transaction, ultimately resulting in the transfer of tokens from the Token-Exchange contract to the buyer's account.
## 4 Part
As described earlier, the interaction between the Exchange Actor, Token Actor, and Oracle Actor represents a single business transaction that involves several sub-transactions. However, in a distributed system, failures can occur at any time, which can cause some of these sub-transactions to fail, leading to inconsistencies in the system. To address this problem, a possible solution is to use the Saga pattern.
There are two main approaches to implementing sagas: saga choreography and saga orchestration (also known as saga coordination).
Saga choreography is a pattern for coordinating distributed transactions between multiple services in a decentralized manner. Each service involved in the transaction is responsible for executing a specific part of the overall transaction. Each service listens for messages indicating the status of the overall transaction and updates its state accordingly. If one of the services encounters an error or fails to complete its part of the transaction, it sends a compensating message to undo the previous actions and restore the system to a consistent state.
In the context of smart contracts, the saga choreography pattern is not very suitable. Unlike traditional services, smart contracts on a blockchain network cannot listen for events or messages. Instead, smart contracts are triggered by transactions on the blockchain and execute their code automatically in response to those transactions. Since the saga choreography pattern relies heavily on events and messages to coordinate sub-transactions, it cannot be easily implemented in a smart contract environment.
Instead, the saga coordinator pattern is more suitable for smart contracts. In this pattern, the saga coordinator is responsible for coordinating the sub-transactions that make up the larger business transaction. The coordinator keeps track of the state of each sub-transaction and ensures that they are executed in the correct order. Once all sub-transactions have been successfully completed, the coordinator finalizes the business transaction. In a token sale scenario, the Exchange actor may act as the coordinator to manage the flow of the transaction.
One thing to note is that the Saga pattern ensures eventual consistency. Suppose a scenario where the token actor may successfully transfer tokens to a buyer and updates balances in its state, while the exchanger actor may not have updated that the token sale was completed. This can result in a state inconsistency between the actors involved in the transaction.
To address this issue, eventual consistency is employed to ensure that the data will eventually become consistent. Eventual consistency allows for a temporary inconsistency between actors, as long as the system eventually reaches a consistent state. In the context of a token sale, eventual consistency would ensure that the exchanger actor eventually updates its state to reflect the completion of the sale.
However, transactions on the blockchain can fail due to various reasons, such as insufficient gas or incorrect data. It is useful to model the Saga pattern as a state machine because it allows for clear visualization of the different states and transitions that a Saga can go through during its execution. The possible state for the token exchange process:
- `Initial` - the initial state of the transaction;
- `RequestSentToOracle` - the coordinator sent a message to the oracle contract and went to the waiting state.
- `PriceRequest`: The initial state of the transaction. The transaction is initiated by the user, who sends a request to the Exchanger contract. The smart contract verifies that the user has sufficient tokens, sends a request to the oracle for the current exchange rate and enters the `PriceRequest` state;
- `TokenTransfer`: The oracle responds with the exchange rate, Exchanger contract sends a message to the Token contract enters the `TokenTransfer` state.
In the context of our example, let's consider the transaction process and possible failures.
1. When the coordinator receives a request for a new transaction, it begins the process of handling the message. This involves:
- storing information about the new transaction;
- sending a message to the oracle contract;
- setting the status of transaction to `PriceRequest`
However, if an error occurs during this process, the new transaction is not saved, and the actor's state is rolled back to the state it was in before the transaction was received. Once the message is processed successfully, the actor's state is updated with information about the new transaction and the message to the oracle contract is sent.
2. The Exchanger sent a message to the Oracle contract, but the following errors may occur during the execution of this local transaction:
- Gas may run out when the Oracle contract loads the message or during its processing;
- An error may occur during message processing due to logic execution;
- The Oracle contract may successfully process the message and send a reply, but the reply may fail to reach its destination due to running out of gas.
If any of the mentioned errors occur, it is the responsibility of the Exchanger contract (which acts as the coordinator) to handle them and take appropriate actions.
3. After receiving the reply, Exchanger proceeds to execute the next step of the transaction. Similarly to the previous step, gas may run out during the processing of the message, and logic-related errors may occur.
Exchanger contract verifies if the price of the token purchase matches the buyer's budget. If the price is higher than the amount specified by the buyer, the transaction is cancelled, and the funds are returned.
:::danger
It is crucial to note that if the Exchanger contract runs out of gas while processing the reply from the Oracle contract, the transaction status will remain in the `PriceRequest` state. As a result, the transaction will appear as if the Exchanger contract has not received a response from the Oracle. Therefore, it is important for the Echanger to monitor all possible transaction failure scenarios.
:::
4. The message is sent to the Token contract, and the possible errors here are similar to those that occurred when the message was sent to the Oracle contract.
5. After receiving the reply from Token, the Exchanger completes the transaction. If the tokens were successfully transferred, the Exchanger sets the transaction status to success. However, if the token transfer failed, the Exchanger refunds the buyer and sets the status to failure.
:::danger
It's important to note that gas may run out after the Exchanger receives the message. In such cases, there could be an inconsistency in the program states. For instance, the Token contract will have transferred the tokens, but the Exchanger contract will not know the outcome of the transaction, which could be either successful or unsuccessful.
:::
Therefore, the Exchanger contract should resend the message to the Token contract to determine the outcome of the transaction.
When the Exchanger resends the message to the Token contract, it's essential to ensure that the transaction is not executed more than once, as this can result in inconsistencies in the program states, such as duplicated transfers of tokens. Here, we come to the concept of `idempotency`.
:::info
:bulb:`Idempotency` is a concept in computer science that refers to the property of certain operations that can be applied multiple times without changing the result beyond the initial application. In out context, idempotency means that if the same message is sent multiple times, the result will be the same as if it was sent only once.
:::