feat(interpreter-data)!: New data format for calls (#501)

BREAKING CHANGE:
1. Call values in the trace have CID references to structures that have call arguments' hash and CID references to values and tetraplets.
2. If call value is unused, it is serialized with `Unused` variant, and CID references are not stored.

Previous data scheme was (Scalar as an example, other cases are similar):

```
Scalar(CID<JValue>) ---<value_store>----> JValue
```

New data scheme is much more sophisticated:

```
Scalar(CID<ServiceResultAggregate>) ---+
                                       |
  +----<service_result_store>----------+
  |
  +-------> ServiceResultAggregate:
               value_cid ------------<value_store>----> JValue
               tetraplet_cid --------<tetraplet_store>----> SecurityTetraplet
               argument_hash: String
```
`Stream` variant is similar, however, `Unused` is different: it has value CID only, but the value is not stored into the `value_store`:

```
Unused(Rc<CID<JValue>>) ---> X
```

Co-authored-by: Mike Voronov <michail.vms@gmail.com>
This commit is contained in:
Ivan Boldyrev
2023-03-21 19:12:04 +07:00
committed by GitHub
parent 631abd4ec4
commit d5028942e4
56 changed files with 2965 additions and 1302 deletions

View File

@ -23,7 +23,6 @@ use crate::TracePos;
use air_interpreter_cid::CID;
use polyplets::SecurityTetraplet;
use se_de::par_serializer;
use se_de::sender_serializer;
use serde::Deserialize;
use serde::Serialize;
@ -36,7 +35,7 @@ pub struct ParResult {
pub right_size: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Sender {
PeerId(Rc<String>),
PeerIdWithCallId { peer_id: Rc<String>, call_id: u32 },
@ -46,26 +45,79 @@ pub enum Sender {
#[serde(rename_all = "snake_case")]
pub enum CallResult {
/// Request was sent to a target node by node with such public key and it shouldn't be called again.
#[serde(with = "sender_serializer")]
#[serde(rename = "sent_by")]
RequestSentBy(Sender),
/// A corresponding call's been already executed with such value as a result.
Executed(ValueRef),
/// call_service ended with a service error.
#[serde(rename = "failed")]
CallServiceFailed(i32, Rc<String>),
/// The call returned a service error.
///
/// The `JValue` has to be a two element array `[i32, String]`.
Failed(Rc<CID<ServiceResultAggregate>>),
}
/*
* The current value structure is:
*
* ```
* Scalar(CID<ServiceResultAggregate>) ---+
* |
* +----<service_result_store>------+
* |
* +-------> ServiceResultAggregate:
* value_cid ------------<value_store>----> JValue
* tetraplet_cid --------<tetraplet_store>----> SecurityTetraplet
* argument_hash: String
* ```
*
* `Stream` variant is similar, however, `Unused` is different: it has value CID only, but the value
* is not stored into the `value_store`:
*
* ```
* Unused(Rc<CID<JValue>>) ---> X
* ```
*/
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ValueRef {
/// The call value is stored to a scalar variable.
Scalar(Rc<CID<ServiceResultAggregate>>),
/// The call value is stored to a stream variable.
Stream {
cid: Rc<CID<ServiceResultAggregate>>,
generation: u32,
},
/// The call value is not stored.
Unused(Rc<CID<JValue>>),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CallServiceFailed {
pub ret_code: i32,
/// This field contains a JSON-serialized value, not a plain error message.
pub message: Rc<String>,
}
impl CallServiceFailed {
pub fn new(ret_code: i32, message: Rc<String>) -> Self {
Self { ret_code, message }
}
pub fn to_value(&self) -> JValue {
serde_json::to_value(self).expect("default serializer shouldn't fail")
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ValueRef {
Scalar(Rc<CID<JValue>>),
Stream {
cid: Rc<CID<JValue>>,
generation: u32,
},
/// A proof of service result execution result.
pub struct ServiceResultAggregate {
pub value_cid: Rc<CID<JValue>>,
/// Hash of the call arguments.
pub argument_hash: Rc<str>,
/// The tetraplet of the call result.
pub tetraplet_cid: Rc<CID<SecurityTetraplet>>,
}
/// Let's consider an example of trace that could be produces by the following fold:

View File

@ -15,7 +15,6 @@
*/
use super::*;
use crate::JValue;
impl ParResult {
pub fn new(left_size: u32, right_size: u32) -> Self {
@ -42,20 +41,30 @@ impl CallResult {
CallResult::RequestSentBy(Sender::PeerIdWithCallId { peer_id, call_id })
}
pub fn executed_scalar(cid: Rc<CID<JValue>>) -> CallResult {
let value = ValueRef::Scalar(cid);
CallResult::Executed(value)
pub fn executed_service_result(value_ref: ValueRef) -> Self {
Self::Executed(value_ref)
}
pub fn executed_stream(cid: Rc<CID<JValue>>, generation: u32) -> CallResult {
let value = ValueRef::Stream { cid, generation };
CallResult::Executed(value)
pub fn executed_scalar(service_result_agg_cid: Rc<CID<ServiceResultAggregate>>) -> Self {
Self::executed_service_result(ValueRef::Scalar(service_result_agg_cid))
}
pub fn failed(ret_code: i32, error_msg: impl Into<String>) -> CallResult {
CallResult::CallServiceFailed(ret_code, Rc::new(error_msg.into()))
pub fn executed_stream(
service_result_agg_cid: Rc<CID<ServiceResultAggregate>>,
generation: u32,
) -> CallResult {
Self::executed_service_result(ValueRef::Stream {
cid: service_result_agg_cid,
generation,
})
}
pub fn executed_unused(value_cid: Rc<CID<JValue>>) -> CallResult {
Self::executed_service_result(ValueRef::Unused(value_cid))
}
pub fn failed(service_result_agg_cid: Rc<CID<ServiceResultAggregate>>) -> CallResult {
CallResult::Failed(service_result_agg_cid)
}
}
@ -107,11 +116,11 @@ impl std::fmt::Display for ExecutedState {
right_size: right_subgraph_size,
}) => write!(f, "par({left_subgraph_size}, {right_subgraph_size})"),
Call(RequestSentBy(sender)) => write!(f, r"{sender}"),
Call(Executed(value)) => {
write!(f, "executed({value})")
Call(Executed(value_ref)) => {
write!(f, "executed({value_ref:?})")
}
Call(CallServiceFailed(ret_code, err_msg)) => {
write!(f, r#"call_service_failed({ret_code}, "{err_msg}")"#)
Call(Failed(failed_cid)) => {
write!(f, "failed({failed_cid:?})")
}
Fold(FoldResult { lore }) => {
writeln!(f, "fold(",)?;
@ -145,6 +154,7 @@ impl std::fmt::Display for ValueRef {
ValueRef::Stream { cid, generation } => {
write!(f, "stream: {cid:?} generation: {generation}")
}
ValueRef::Unused(cid) => write!(f, "unused: {cid:?}"),
}
}
}

View File

@ -66,57 +66,3 @@ pub mod par_serializer {
deserializer.deserialize_seq(ParVisitor {})
}
}
pub mod sender_serializer {
use super::*;
pub fn serialize<S>(value: &Sender, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match value {
Sender::PeerId(peer_id) => serializer.serialize_str(peer_id.as_str()),
Sender::PeerIdWithCallId { peer_id, call_id } => {
let result = format!("{peer_id}: {call_id}");
serializer.serialize_str(&result)
}
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Sender, D::Error>
where
D: Deserializer<'de>,
{
struct SenderVisitor;
impl<'de> Visitor<'de> for SenderVisitor {
type Value = Sender;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("call sender")
}
fn visit_str<E: serde::de::Error>(self, raw_sender: &str) -> Result<Self::Value, E> {
let sender = match raw_sender.find(": ") {
None => Sender::PeerId(Rc::new(raw_sender.to_string())),
Some(pos) => {
let peer_id = raw_sender[..pos].to_string();
let call_id = &raw_sender[pos + 2..];
let call_id = call_id.parse::<u32>().map_err(|e| {
serde::de::Error::custom(format!(
"failed to parse call_id of a sender {call_id}: {e}"
))
})?;
Sender::PeerIdWithCallId {
peer_id: Rc::new(peer_id),
call_id,
}
}
};
Ok(sender)
}
}
deserializer.deserialize_str(SenderVisitor {})
}
}

View File

@ -20,6 +20,7 @@ use crate::cid_store::CidStore;
use crate::CanonCidAggregate;
use crate::ExecutionTrace;
use crate::JValue;
use crate::ServiceResultAggregate;
use air_utils::measure;
use polyplets::SecurityTetraplet;
@ -130,16 +131,19 @@ impl Versions {
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CidInfo {
/// Map CID to value
/// Map CID to value.
pub value_store: CidStore<JValue>,
/// Map CID to a tetraplet
/// Map CID to a tetraplet.
pub tetraplet_store: CidStore<SecurityTetraplet>,
/// Map CID to a canon value
/// Map CID to a canon value.
pub canon_store: CidStore<CanonCidAggregate>,
/// Map CID to a service result aggregate.
pub service_result_store: CidStore<ServiceResultAggregate>,
}
#[cfg(test)]