We use the http request function to handle user events because in the current version of the TEA Party, the front-end sends the back-end http requests.
libp2p_back_message
Tea project uses a modified version of rust-based lib P2P protocol between nodes communication.
Interaction with OrbitDB
Query OrbitDb example: load_message_list
Please take a look at the function pub fn load_message_list(req: &LoadMessageRequest) -> anyhow::Result<Vec<u8>> in message.rs file
Focus on these lines:
let dbname = db_name(req.tapp_id, &req.channel);
let get_message_data = orbitdb::GetMessageRequest {
tapp_id: req.tapp_id,
dbname,
sender: match req.address.is_empty() {
true => "".to_string(),
false => req.address.to_string(),
},
utc: block - 2,
};
let res = orbitdb::OrbitBbsResponse::decode(
untyped::default()
.call(
tea_codec::ORBITDB_CAPABILITY_ID,
"bbs_GetMessage",
encode_protobuf(get_message_data)?,
)
.map_err(|e| anyhow::anyhow!("{}", e))?
.as_slice(),
)?;
First, generate the dbname which will be used later in the parameter get_message_data of the future provider call bbs_GetMessage.
These lines are the typical way to call a provider. You can find such patterns everywhere in the TEA Project.
pub fn send_txn(
action_name: &str,
uuid: &str,
req_bytes: Vec<u8>,
txn_bytes: Vec<u8>,
txn_target: &str,
) -> anyhow::Result<()> {
let ori_uuid = str::replace(&uuid, "txn_", "");
let action_key = uuid_cb_key(&ori_uuid, "action_name");
let req_key = uuid_cb_key(&ori_uuid, "action_req");
help::set_mem_cache(&action_key, bincode::serialize(&action_name)?)?;
help::set_mem_cache(&req_key, req_bytes.clone())?;
info!(
"start to send txn request for {} with uuid [{}]",
&action_name, &uuid
);
p2p_send_txn(txn_bytes, uuid.to_string(), txn_target.to_string())?;
info!("finish to send txn request...");
Ok(())
}
If we keep following the call stack we'll eventually find more interesting details but we have to stop here. Otherwise, this article would become very long.
The remaining logic would be described as follows:
Check the layer one, find the currently active state machine replicas, and their p2p addresses
After the first txn P2P messages are sent out, record the time from the GPS atomic clock.
Query example: query_balance
This function checks the user balance they've topped up to their TEA Party app account. "query_balance" => api::query_balance(&serde_json::from_slice(&req.payload)?),
Similar to handle_adapter_http_request, we still have handle_adapter_request which is an upper level handler. That's because all http requests are actually captured by the first. Adapter is the sole component that a hosting CML can contact the outside world.
The use libp2p_back_message to handle libP2P messages. In our Tea party sample code, the only usage of this function is to receive response message to its own memory cache help::set_mem_cache(&body.uuid, content)?;.
The memory cache is used to temporarily store the response/error message from the . When the sends a query for the result of any command, the hosting CML's back end actor will check this temporary store to get recently received results and get back to the .
The main function call is the provider call. tea_codec::ORBITDB_CAPABILITY_ID is the ID of the OrbitDB . the bbs_GetMessage is the API name to call. The parameter needs to encode_protobuf so that the provider can decode it later. The response of this provider function call is a bytes buffer, so we have to issue orbitdb::OrbitBbsResponse::decode to a regular res data structure.
The rest of the code is easy to understand. The data response from the OrbitDB provider goes to the message_item list. This list is returned to the caller. Finally, it shows in the UI in the browser.
Usually there are two kinds of requests that need to be sent to the to handle. They're either or .
The function post_message sends a txn (we sometimes call it sending ) to the . The following code sends the txn:
In this function call, "post_message" is the name of the API that can handle. uuid is the nonce that the back-end actor uses to check the execution result. txn_bytes is the body of txn.
Randomly select 2 (or more if you think necessary) s. Send the txn in P2P message to them.
Use this time stamp in the message in the Ts field. Note, we only need the first txn's sent time, ignore the 2nd txn sent time.
Send out the message to those two s (function pub fn send_followup_via_p2p(fu: Followup, uuid: String)).
You can follow how the finds the nodes and sends out using the p2p_send_to_receive_actor function:
The a_nodes is the internal name for . target_conn_id is the address that libp2p can use to find the destination nodes.
You may have noticed that no matter if it's or , the caller will not get the response immediately (even for that are not supposed to have to wait in the . That's because all communication between nodes are asyncronous. However, you can always query the result using the uuid when you generate the request.
Please note, the front-end has no way to know when the result will be ready. It's common that the front-end needs to query several times to get the result. You can find the sample of how to query the result in the code. In bbs.js, the function is const sync_request = async (method, param, message_cb, sp_method='query_result', sp_uuid=null).