Make interpreter async (#130)

Co-authored-by: folex <0xdxdy@gmail.com>
Co-authored-by: Pavel Murygin <pavel.murygin@gmail.com>
This commit is contained in:
Mike Voronov 2021-10-04 10:58:00 +03:00 committed by GitHub
parent 442e284dff
commit 4a4fc0889b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
95 changed files with 2311 additions and 1828 deletions

View File

@ -1,3 +1,28 @@
## Version 0.15.0 (2021-09-30)
[PR 140](https://github.com/fluencelabs/aquavm/pull/130):
- the interpreter become async, now it's a pure function without any imports from a peer. Instead of calling import `call_service` from a peer, it now returns call results in the outcome structure, and receives their result in the `invoke` export.
- data structure now includes a new field to track last call request id to give peer more freedom.
- AVM server was completely refactored to support the new interpreter model and to expose a new trait storing data for a node.
[PR 144](https://github.com/fluencelabs/aquavm/pull/144)
The interpreter changed to be built with `unwind` panic handler and some other debug options were turned on.
[PR 139](https://github.com/fluencelabs/aquavm/pull/139)
Senders in `RequestSentBy` could be different now.
[PR 138](https://github.com/fluencelabs/aquavm/pull/138)
The computation algo for `subtrace_len` was completely refactored.
[PR 136](https://github.com/fluencelabs/aquavm/pull/136)
`serde` and `serde_json` crates were used without locking their version
[PR 133](https://github.com/fluencelabs/aquavm/pull/133)
fixed bug with applying json path to an empty stream
[PR 132](https://github.com/fluencelabs/aquavm/pull/132)
fix bug with json flattening
## Version 0.14.0 (2021-08-24)
[PR 74](https://github.com/fluencelabs/aquavm/pull/74):

87
Cargo.lock generated
View File

@ -13,7 +13,7 @@ dependencies = [
[[package]]
name = "air"
version = "0.14.1"
version = "0.15.0"
dependencies = [
"air-interpreter-data",
"air-interpreter-interface",
@ -41,7 +41,7 @@ dependencies = [
[[package]]
name = "air-interpreter"
version = "0.14.1"
version = "0.15.0"
dependencies = [
"air",
"log",
@ -68,6 +68,7 @@ dependencies = [
"fluence-it-types",
"marine-rs-sdk",
"serde",
"serde_json",
]
[[package]]
@ -100,6 +101,7 @@ name = "air-test-utils"
version = "0.2.1"
dependencies = [
"air",
"air-interpreter-interface",
"avm-server",
"marine-rs-sdk",
"serde_json",
@ -164,15 +166,22 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "avm-data-store"
version = "0.1.0"
[[package]]
name = "avm-server"
version = "0.10.1"
version = "0.11.0"
dependencies = [
"air-interpreter-interface",
"avm-data-store",
"eyre",
"fluence-faas",
"log",
"maplit",
"parking_lot 0.11.2",
"polyplets",
"serde",
"serde_json",
"thiserror",
@ -231,9 +240,9 @@ checksum = "cfa8873f51c92e232f9bac4065cddef41b714152812bfc5f7672ba16d6ef8cd9"
[[package]]
name = "bstr"
version = "0.2.16"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279"
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
dependencies = [
"lazy_static",
"memchr",
@ -243,9 +252,9 @@ dependencies = [
[[package]]
name = "bumpalo"
version = "3.7.0"
version = "3.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631"
checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538"
[[package]]
name = "byteorder"
@ -644,14 +653,24 @@ dependencies = [
[[package]]
name = "errno-dragonfly"
version = "0.1.1"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"gcc",
"cc",
"libc",
]
[[package]]
name = "eyre"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "221239d1d5ea86bf5d6f91c9d6bc3646ffe471b08ff9b0f91c44f115ac969d2b"
dependencies = [
"indenter",
"once_cell",
]
[[package]]
name = "fixedbitset"
version = "0.2.0"
@ -735,12 +754,6 @@ dependencies = [
"syn",
]
[[package]]
name = "gcc"
version = "0.3.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
[[package]]
name = "generational-arena"
version = "0.2.8"
@ -864,6 +877,12 @@ version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
[[package]]
name = "indenter"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
[[package]]
name = "indexmap"
version = "1.7.0"
@ -877,9 +896,9 @@ dependencies = [
[[package]]
name = "instant"
version = "0.1.10"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d"
checksum = "716d3d89f35ac6a34fd0eed635395f4c3b76fa889338a4632e5231a8684216bd"
dependencies = [
"cfg-if 1.0.0",
]
@ -1029,9 +1048,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.102"
version = "0.2.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2a5ac8f984bfcf3a823267e5fde638acc3325f6496633a5da6bb6eb2171e103"
checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6"
[[package]]
name = "lock_api"
@ -1115,18 +1134,18 @@ dependencies = [
[[package]]
name = "marine-macro"
version = "0.6.10"
version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd64d5febc6b2ed709a4461c510c1429dc6e4cfcbd6ca0d88463911630acd67b"
checksum = "679663e087698f1048f23fed9b51ed82f6fa75781d3747ce29ea2f3ad78a6534"
dependencies = [
"marine-macro-impl",
]
[[package]]
name = "marine-macro-impl"
version = "0.6.10"
version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c644127c4a9a3345434116f6887f8522c51bddde599b51d7d312aa8b2ecfaa54"
checksum = "0ba83fc29fec3b96374094a94396d3fe13c97468ffe196123b78555bdae1093e"
dependencies = [
"proc-macro2",
"quote",
@ -1172,9 +1191,9 @@ dependencies = [
[[package]]
name = "marine-rs-sdk"
version = "0.6.11"
version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9157bae63a4bbdd7a29984f6ded08f8ed72991b83ca3bdd59d2f889fa8b8ea02"
checksum = "8866fc6f24b92342f15d2816298d3de6377b685df245e38a36ddcde993c8f1d5"
dependencies = [
"marine-macro",
"marine-rs-sdk-main",
@ -1184,9 +1203,9 @@ dependencies = [
[[package]]
name = "marine-rs-sdk-main"
version = "0.6.10"
version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95c49b5893d8689589219e07cf67421cc415dc5f219ad0e9c197a9a050b5dd4f"
checksum = "4980a0c01121844419c0146e776d24e35fdf7cb2e90a33d19ecf52756e400196"
dependencies = [
"log",
"marine-macro",
@ -1225,9 +1244,9 @@ dependencies = [
[[package]]
name = "marine-timestamp-macro"
version = "0.6.10"
version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29ae1a5630bd9b652a77405750c3c37a5fdcfcdc679818bf7d970871ae28f7e6"
checksum = "5656745923b99d73f945e26cf191efa70e906c7f55b0d4c1fc176b4b8087e85b"
dependencies = [
"chrono",
"quote",
@ -1847,9 +1866,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "syn"
version = "1.0.76"
version = "1.0.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6f107db402c2c2055242dbf4d2af0e69197202e9faacbef9571bbe47f5a1b84"
checksum = "5239bc68e0fef57495900cfea4e8dc75596d9a319d7e16b1e0a440d24e6fe0a0"
dependencies = [
"proc-macro2",
"quote",
@ -1994,9 +2013,9 @@ checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
[[package]]
name = "unicode-width"
version = "0.1.8"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "unicode-xid"

View File

@ -3,6 +3,7 @@ members = [
"air",
"air-interpreter",
"crates/air-parser",
"crates/data-store",
"crates/polyplets",
"crates/interpreter-interface",
"crates/interpreter-data",

View File

@ -1,6 +1,6 @@
[package]
name = "air-interpreter"
version = "0.14.1"
version = "0.15.0"
authors = ["Fluence Labs"]
edition = "2018"
publish = false
@ -27,4 +27,6 @@ serde = { version = "1.0.118", features = [ "derive", "rc" ] }
serde_json = "1.0.61"
[features]
marine = ["air/marine"]
# indicates that this library should be compiled for the marine target,
# otherwise it will be compiled for the wasm bindgen target
marine = []

View File

@ -31,15 +31,25 @@ mod logger;
use air::execute_air;
use air::InterpreterOutcome;
use air::RunParameters;
use marine_rs_sdk::marine;
use marine_rs_sdk::module_manifest;
module_manifest!();
pub fn main() {
logger::init_logger(None);
}
#[marine]
pub fn invoke(init_peer_id: String, air: String, prev_data: Vec<u8>, data: Vec<u8>) -> InterpreterOutcome {
execute_air(init_peer_id, air, prev_data, data)
pub fn invoke(
air: String,
prev_data: Vec<u8>,
data: Vec<u8>,
params: RunParameters,
call_results: Vec<u8>,
) -> InterpreterOutcome {
execute_air(air, prev_data, data, params, call_results)
}
#[marine]

View File

@ -30,6 +30,7 @@ mod ast;
mod logger;
use air::execute_air;
use air::RunParameters;
use log::LevelFilter;
use wasm_bindgen::prelude::*;
@ -49,13 +50,22 @@ pub fn main() {
}
#[wasm_bindgen]
pub fn invoke(init_peer_id: String, air: String, prev_data: Vec<u8>, data: Vec<u8>, log_level: &str) -> String {
pub fn invoke(
air: String,
prev_data: Vec<u8>,
data: Vec<u8>,
params: Vec<u8>,
call_results: Vec<u8>,
log_level: &str,
) -> String {
use std::str::FromStr;
let log_level = log::LevelFilter::from_str(log_level).unwrap_or(DEFAULT_LOG_LEVEL);
log::set_max_level(log_level);
let outcome = execute_air(init_peer_id, air, prev_data, data);
let params: RunParameters = serde_json::from_slice(&params).expect("cannot parse RunParameters");
let outcome = execute_air(air, prev_data, data, params, call_results);
serde_json::to_string(&outcome).expect("Cannot parse InterpreterOutcome")
}

View File

@ -1,6 +1,6 @@
[package]
name = "air"
version = "0.14.1"
version = "0.15.0"
authors = ["Fluence Labs"]
edition = "2018"
publish = false
@ -54,8 +54,3 @@ harness = false
[[bench]]
name = "create_service_benchmark"
harness = false
[features]
# indicates that this library should be compiled for the wasm bindgen target
# otherwise it will be compiled to the Marine target
marine = []

81
air/README.md Normal file
View File

@ -0,0 +1,81 @@
# AIR
### Overview
This crates defines the core of the AIR interpreter intended to execute scripts to control execution flow in the Fluence network. From the high level point of view the interpreter could be considered as a state transition function that takes two states, merge them and then produce a new state.
### Interpreter interface
This interpreter has only one export function called `invoke` and no import functions. The export function has the following signature:
```rust
pub fn executed_air(
/// AIR script to execute.
air: String,
/// Previous data that should be equal to the last returned by the interpreter.
prev_data: Vec<u8>,
/// So-called current data that was sent with a particle from the interpreter on some other peer.
data: Vec<u8>,
/// Running parameters that includes different settings.
params: RunParameters,
/// Results of calling services.
call_results: Vec<u8>,
) -> InterpreterOutcome {...}
pub struct InterpreterOutcome {
/// A return code, where 0 means success.
pub ret_code: i32,
/// Contains error message if ret_code != 0.
pub error_message: String,
/// Contains so-called new data that should be preserved in an executor of this interpreter
/// regardless of ret_code value.
pub data: Vec<u8>,
/// Public keys of peers that should receive data.
pub next_peer_pks: Vec<String>,
/// Collected parameters of all met call instructions that could be executed on a current peer.
pub call_requests: Vec<u8>,
}
```
As it was already mentioned in the previous section, `invoke` takes two states (`prev_data` and `current_data`) and returns a new state (`new_data`). Additionally, it takes AIR script that should be executed, some run parameters (such as `init_peer_id` and `current_peer_id`), and `call_results`, results of services calling. As a result it provides the `IntepreterOutcome` structure described in the upper code snippet.
### Main properties
Let's consider the interpreter with respect to data first, because previous, current and resulted data are the most interesting parts of arguments and the outcome. Assuming `X` is a set of all possible values that data could have, we'll denote `executed_air` export function as `f: X * X -> X`. It could be seen that with respect to data `f` forms a magma.
Even more, `f` is an idempotent non-commutative monoid, because:
1. `f` is associative: `forall a, b, c from X: f(f(a,b), c) = f(a, f(b,c))`
2. `f` has a neutral element: `exists e, forall a from X: f(e, a) = f(a, e) = a`, where `e` is a data with an empty trace
3. `f` is a non-commutative function: `exists a, b from X: f(a, b) != f(b, a)`
4. `X` could be constructed from a four based elements that form the `ExecutedState` enum (that why this monoid is free)
5. `f` satisfies these idempotence properties:
1. `forall x from X: f(x, x) = x`
2. `forall a, b from X: f(a, b) = c, f(c, b) = c, f(c, a) = c`
### Interaction with the interpreter
The interpreter allows a peer (either a node or a browser) to call service asynchronously by collecting all arguments and other necessary stuff from each `call` instruction that could be called during the execution and return them in `InterpreterOutcome`. A host should then execute them at any time and call back the interpreter providing executed service results as the `call_results` argument.
A scheme of interacting with the interpreter should look as follows:
1. For each new `current_data` received from a network, a host should call the interpreter with corresponding `prev_data` and `current_data` and empty `call_results`. `prev_data` here is last `new_data` returned from the interpreter.
2. Having obtained a result of the interpreter, there could be non-empty `next_peer_ids` and non-empty `call_requests` in `InterpreterOutcome`:
1. re `next_peer_pks`: it's a peer duty to decide whether it should send particle after each interpreter call or after the whole particle execution, i.e. after completing all `call_requests`.
2. re `call_requests`: `call_requests` is a `HashMap<u32, CallRequestParams>` and it's important for host to keep that correspondence between `u32` and call `CallRequestParams`, because they should be used when results are passed back on step 3.
3. If `call_requests` was non-empty on step 2, a peer must call the interpreter again with supplied call results (`HashMap<u32, CallServiceResult>`) following next rules:
- current_data here shouldn't be supplied (actually, it could be supplied because of `f` is idempotent, but it's unnecessary and slow down a bit an interpreter execution)
- it's not necessary to supply `call_results` before handling the next particle, actually a peer could supply it in any moment
- a peer must preserve `new_data` after each execution step. It's important, because `f` is non-commutative and the interpreter save an additional info in `data` expecting to see the resulted data back as `prev_data` on the next launch.<br><br>
Then this flow should be repeated starting from point 2.
4. If `call_requests` was empty, the whole execution is completed, `new_data` must be preserved and particle send for all `new_peer_pks` as usual.
An example of interaction could be found in [tests](https://github.com/fluencelabs/aquavm/blob/async/crates/test-utils/src/test_runner.rs).

View File

@ -1,48 +0,0 @@
/*
* Copyright 2020 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#[cfg(feature = "marine")]
mod marine_target;
#[cfg(not(feature = "marine"))]
mod wasm_bindgen_target;
use serde::Deserialize;
use serde::Serialize;
pub const CALL_SERVICE_SUCCESS: i32 = 0;
#[marine_rs_sdk::marine]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CallServiceResult {
pub ret_code: i32,
pub result: String,
}
impl std::fmt::Display for CallServiceResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "ret_code: {}, result: '{}'", self.ret_code, self.result)
}
}
#[cfg(feature = "marine")]
pub(crate) use marine_target::call_service;
#[cfg(feature = "marine")]
pub(crate) use marine_target::get_current_peer_id;
#[cfg(not(feature = "marine"))]
pub(crate) use wasm_bindgen_target::call_service;
#[cfg(not(feature = "marine"))]
pub(crate) use wasm_bindgen_target::get_current_peer_id;

View File

@ -1,49 +0,0 @@
/*
* Copyright 2020 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use wasm_bindgen::__rt::std::env::VarError;
use wasm_bindgen::prelude::*;
pub(crate) fn call_service(
service_id: &str,
fn_name: &str,
args: &str,
security_tetraplets: &str,
) -> super::CallServiceResult {
let result = call_service_impl(service_id, fn_name, args, security_tetraplets);
log::trace!("result {}", result);
serde_json::from_str(&result).expect("Cannot parse CallServiceResult")
}
#[allow(clippy::unnecessary_wraps)]
pub(crate) fn get_current_peer_id() -> std::result::Result<String, VarError> {
Ok(get_current_peer_id_impl())
}
#[wasm_bindgen]
extern "C" {
#[allow(unused_attributes)]
#[link_name = "get_current_peer_id"]
fn get_current_peer_id_impl() -> String;
}
#[wasm_bindgen]
extern "C" {
#[allow(unused_attributes)]
#[link_name = "call_service"]
fn call_service_impl(service_id: &str, fn_name: &str, args: &str, security_tetraplets: &str) -> String;
}

View File

@ -15,9 +15,9 @@
*/
pub(crate) mod call_result_setter;
mod prev_result_handler;
mod resolved_call;
mod triplet;
mod utils;
use resolved_call::ResolvedCall;
@ -38,7 +38,7 @@ use std::rc::Rc;
impl<'i> super::ExecutableInstruction<'i> for Call<'i> {
fn execute(&self, exec_ctx: &mut ExecutionCtx<'i>, trace_ctx: &mut TraceHandler) -> ExecutionResult<()> {
log_instruction!(call, exec_ctx, trace_ctx);
exec_ctx.tracker.met_call();
exec_ctx.tracker.meet_call();
let resolved_call = joinable_call!(ResolvedCall::new(self, exec_ctx), exec_ctx).map_err(|e| {
set_last_error(self, exec_ctx, e.clone(), None);

View File

@ -45,7 +45,13 @@ pub(crate) fn set_local_result<'i>(
Ok(CallResult::executed_scalar(result_value))
}
CallOutputValue::Variable(AstVariable::Stream(name)) => {
let generation = set_stream_result(executed_result, Generation::Last, name.to_string(), exec_ctx)?;
// TODO: refactor this generation handling
let generation = match exec_ctx.streams.get(*name) {
Some(stream) => Generation::Nth(stream.borrow().generations_count() as u32 - 1),
None => Generation::Last,
};
let generation = set_stream_result(executed_result, generation, name.to_string(), exec_ctx)?;
Ok(CallResult::executed_stream(result_value, generation))
}
CallOutputValue::None => Ok(CallResult::executed_scalar(result_value)),
@ -70,13 +76,14 @@ pub(crate) fn set_result_from_value<'i>(
let _ = set_stream_result(result, generation, name.to_string(), exec_ctx)?;
}
// it isn't needed to check there that output and value matches because
// it's been already in trace handler
// it's been already checked in trace handler
_ => {}
};
Ok(())
}
#[macro_export]
macro_rules! shadowing_allowed(
($exec_ctx:ident, $entry:ident) => { {
// check that current execution_step flow is inside a fold block
@ -166,6 +173,6 @@ pub(crate) fn set_remote_call_result<'i>(
exec_ctx.next_peer_pks.push(peer_pk);
exec_ctx.subtree_complete = false;
let new_call_result = CallResult::RequestSentBy(exec_ctx.current_peer_id.clone());
let new_call_result = CallResult::sent_peer_id(exec_ctx.current_peer_id.clone());
trace_ctx.meet_call_end(new_call_result);
}

View File

@ -0,0 +1,153 @@
/*
* Copyright 2020 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use super::*;
use crate::exec_err;
use crate::execution_step::air::call::call_result_setter::set_result_from_value;
use crate::execution_step::trace_handler::TraceHandler;
use crate::execution_step::RSecurityTetraplet;
use air_interpreter_data::CallResult;
use air_interpreter_data::Sender;
use air_interpreter_interface::CallServiceResult;
use air_parser::ast::CallOutputValue;
/// This function looks at the existing call state, validates it,
/// and returns Ok(true) if the call should be executed further.
pub(super) fn handle_prev_state<'i>(
tetraplet: &RSecurityTetraplet,
output: &CallOutputValue<'i>,
prev_result: CallResult,
trace_pos: usize,
exec_ctx: &mut ExecutionCtx<'i>,
trace_ctx: &mut TraceHandler,
) -> ExecutionResult<bool> {
use CallResult::*;
let result = match &prev_result {
// this call was failed on one of the previous executions,
// here it's needed to bubble this special error up
CallServiceFailed(ret_code, err_msg) => {
exec_ctx.subtree_complete = false;
exec_err!(ExecutionError::LocalServiceError(*ret_code, err_msg.clone()))
}
RequestSentBy(Sender::PeerIdWithCallId { peer_id, call_id })
if peer_id.as_str() == exec_ctx.current_peer_id.as_str() =>
{
// call results are identified by call_id that is saved in data
match exec_ctx.call_results.remove(call_id) {
Some(call_result) => {
update_state_with_service_result(tetraplet, output, call_result, exec_ctx, trace_ctx)?;
return Ok(false);
}
// result hasn't been prepared yet
None => {
exec_ctx.subtree_complete = false;
Ok(false)
}
}
}
RequestSentBy(..) => {
// check whether current node can execute this call
let is_current_peer = tetraplet.borrow().triplet.peer_pk.as_str() == exec_ctx.current_peer_id.as_str();
if is_current_peer {
// if this peer could execute this call early return and
return Ok(true);
}
exec_ctx.subtree_complete = false;
Ok(false)
}
// this instruction's been already executed
Executed(value) => {
set_result_from_value(value.clone(), tetraplet.clone(), trace_pos, output, exec_ctx)?;
exec_ctx.subtree_complete = true;
Ok(false)
}
};
trace_ctx.meet_call_end(prev_result);
result
}
use super::call_result_setter::*;
use crate::execution_step::ResolvedCallResult;
use crate::JValue;
fn update_state_with_service_result<'i>(
tetraplet: &RSecurityTetraplet,
output: &CallOutputValue<'i>,
service_result: CallServiceResult,
exec_ctx: &mut ExecutionCtx<'i>,
trace_ctx: &mut TraceHandler,
) -> ExecutionResult<()> {
// check that service call succeeded
let service_result = handle_service_error(service_result, trace_ctx)?;
// try to get service result from call service result
let result = try_to_service_result(service_result, trace_ctx)?;
let trace_pos = trace_ctx.trace_pos();
let executed_result = ResolvedCallResult::new(result, tetraplet.clone(), trace_pos);
let new_call_result = set_local_result(executed_result, output, exec_ctx)?;
trace_ctx.meet_call_end(new_call_result);
Ok(())
}
fn handle_service_error(
service_result: CallServiceResult,
trace_ctx: &mut TraceHandler,
) -> ExecutionResult<CallServiceResult> {
use air_interpreter_interface::CALL_SERVICE_SUCCESS;
use CallResult::CallServiceFailed;
if service_result.ret_code == CALL_SERVICE_SUCCESS {
return Ok(service_result);
}
let error_message = Rc::new(service_result.result);
let error = ExecutionError::LocalServiceError(service_result.ret_code, error_message.clone());
let error = Rc::new(error);
trace_ctx.meet_call_end(CallServiceFailed(service_result.ret_code, error_message));
Err(error)
}
fn try_to_service_result(
service_result: CallServiceResult,
trace_ctx: &mut TraceHandler,
) -> ExecutionResult<Rc<JValue>> {
use CallResult::CallServiceFailed;
match serde_json::from_str(&service_result.result) {
Ok(result) => Ok(Rc::new(result)),
Err(e) => {
let error_msg = format!(
"call_service result '{0}' can't be serialized or deserialized with an error: {1}",
service_result.result, e
);
let error_msg = Rc::new(error_msg);
let error = CallServiceFailed(i32::MAX, error_msg.clone());
trace_ctx.meet_call_end(error);
Err(Rc::new(ExecutionError::LocalServiceError(i32::MAX, error_msg)))
}
}
}

View File

@ -17,10 +17,9 @@
#![allow(unused_unsafe)] // for wasm_bindgen target where calling FFI is safe
use super::call_result_setter::*;
use super::prev_result_handler::*;
use super::triplet::Triplet;
use super::utils::*;
use super::*;
use crate::execution_step::air::ResolvedCallResult;
use crate::execution_step::trace_handler::MergerCallResult;
use crate::execution_step::trace_handler::TraceHandler;
use crate::execution_step::RSecurityTetraplet;
@ -29,7 +28,9 @@ use crate::JValue;
use crate::SecurityTetraplet;
use air_interpreter_data::CallResult;
use air_parser::ast::{CallInstrArgValue, CallOutputValue};
use air_interpreter_interface::CallRequestParams;
use air_parser::ast::{AstVariable, CallInstrArgValue, CallOutputValue};
use polyplets::ResolvedTriplet;
use std::cell::RefCell;
use std::rc::Rc;
@ -56,6 +57,8 @@ impl<'i> ResolvedCall<'i> {
let tetraplet = SecurityTetraplet::from_triplet(triplet);
let tetraplet = Rc::new(RefCell::new(tetraplet));
check_output_name(&raw_call.output, exec_ctx)?;
Ok(Self {
tetraplet,
function_arg_paths: raw_call.args.clone(),
@ -71,12 +74,34 @@ impl<'i> ResolvedCall<'i> {
}
// call can be executed only on peers with such peer_id
let triplet = &self.tetraplet.borrow().triplet;
if triplet.peer_pk.as_str() != exec_ctx.current_peer_id.as_str() {
set_remote_call_result(triplet.peer_pk.clone(), exec_ctx, trace_ctx);
let tetraplet = &self.tetraplet.borrow().triplet;
if tetraplet.peer_pk.as_str() != exec_ctx.current_peer_id.as_str() {
set_remote_call_result(tetraplet.peer_pk.clone(), exec_ctx, trace_ctx);
return Ok(());
}
let request_params = self.prepare_request_params(exec_ctx, tetraplet)?;
let call_id = exec_ctx.next_call_request_id();
exec_ctx.call_requests.insert(call_id, request_params);
exec_ctx.subtree_complete = false;
trace_ctx.meet_call_end(CallResult::sent_peer_id_with_call_id(
exec_ctx.current_peer_id.clone(),
call_id,
));
Ok(())
}
pub(super) fn as_tetraplet(&self) -> RSecurityTetraplet {
self.tetraplet.clone()
}
fn prepare_request_params(
&self,
exec_ctx: &ExecutionCtx<'i>,
triplet: &ResolvedTriplet,
) -> ExecutionResult<CallRequestParams> {
let ResolvedArguments {
call_arguments,
tetraplets,
@ -84,40 +109,14 @@ impl<'i> ResolvedCall<'i> {
let serialized_tetraplets = serde_json::to_string(&tetraplets).expect("default serializer shouldn't fail");
let service_result = unsafe {
crate::build_targets::call_service(
&triplet.service_id,
&triplet.function_name,
&call_arguments,
&serialized_tetraplets,
)
};
exec_ctx.tracker.met_executed_call();
let request_params = CallRequestParams::new(
triplet.service_id.to_string(),
triplet.function_name.to_string(),
call_arguments,
serialized_tetraplets,
);
self.update_state_with_service_result(service_result, exec_ctx, trace_ctx)
}
fn update_state_with_service_result(
&self,
service_result: CallServiceResult,
exec_ctx: &mut ExecutionCtx<'i>,
trace_ctx: &mut TraceHandler,
) -> ExecutionResult<()> {
// check that service call succeeded
let call_service_result = handle_service_error(service_result, trace_ctx)?;
// try to get service result from call service result
let result = try_to_service_result(call_service_result, trace_ctx)?;
let trace_pos = trace_ctx.trace_pos();
let executed_result = ResolvedCallResult::new(result, self.tetraplet.clone(), trace_pos);
let new_call_result = set_local_result(executed_result, &self.output, exec_ctx)?;
trace_ctx.meet_call_end(new_call_result);
Ok(())
}
pub(super) fn as_tetraplet(&self) -> RSecurityTetraplet {
self.tetraplet.clone()
Ok(request_params)
}
/// Determine whether this call should be really called and adjust prev executed trace accordingly.
@ -166,47 +165,26 @@ impl<'i> ResolvedCall<'i> {
}
}
use crate::build_targets::CallServiceResult;
/// Check output type name for being already in execution context.
// TODO: this check should be moved on a parsing stage
fn check_output_name(output: &CallOutputValue<'_>, exec_ctx: &ExecutionCtx<'_>) -> ExecutionResult<()> {
use crate::execution_step::boxed_value::Scalar;
fn handle_service_error(
service_result: CallServiceResult,
trace_ctx: &mut TraceHandler,
) -> ExecutionResult<CallServiceResult> {
use crate::build_targets::CALL_SERVICE_SUCCESS;
use CallResult::CallServiceFailed;
let scalar_name = match output {
CallOutputValue::Variable(AstVariable::Scalar(name)) => *name,
_ => return Ok(()),
};
if service_result.ret_code == CALL_SERVICE_SUCCESS {
return Ok(service_result);
match exec_ctx.scalars.get(scalar_name) {
Some(Scalar::JValueRef(_)) => {
if exec_ctx.met_folds.is_empty() {
// shadowing is allowed only inside fold blocks
crate::exec_err!(ExecutionError::MultipleVariablesFound(scalar_name.to_string()))
} else {
Ok(())
}
let error_message = Rc::new(service_result.result);
let error = ExecutionError::LocalServiceError(service_result.ret_code, error_message.clone());
let error = Rc::new(error);
trace_ctx.meet_call_end(CallServiceFailed(service_result.ret_code, error_message));
Err(error)
}
fn try_to_service_result(
service_result: CallServiceResult,
trace_ctx: &mut TraceHandler,
) -> ExecutionResult<Rc<JValue>> {
use CallResult::CallServiceFailed;
match serde_json::from_str(&service_result.result) {
Ok(result) => Ok(Rc::new(result)),
Err(e) => {
let error_msg = format!(
"call_service result '{0}' can't be serialized or deserialized with an error: {1}",
service_result.result, e
);
let error_msg = Rc::new(error_msg);
let error = CallServiceFailed(i32::MAX, error_msg.clone());
trace_ctx.meet_call_end(error);
Err(Rc::new(ExecutionError::LocalServiceError(i32::MAX, error_msg)))
}
Some(_) => crate::exec_err!(ExecutionError::IterableShadowing(scalar_name.to_string())),
None => Ok(()),
}
}

View File

@ -1,67 +0,0 @@
/*
* Copyright 2020 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use super::*;
use crate::exec_err;
use crate::execution_step::trace_handler::TraceHandler;
use crate::execution_step::RSecurityTetraplet;
use crate::execution_step::air::call::call_result_setter::set_result_from_value;
use air_interpreter_data::CallResult;
use air_parser::ast::CallOutputValue;
/// This function looks at the existing call state, validates it,
/// and returns Ok(true) if the call should be executed further.
pub(super) fn handle_prev_state<'i>(
tetraplet: &RSecurityTetraplet,
output: &CallOutputValue<'i>,
prev_result: CallResult,
trace_pos: usize,
exec_ctx: &mut ExecutionCtx<'i>,
trace_ctx: &mut TraceHandler,
) -> ExecutionResult<bool> {
use CallResult::*;
let result = match &prev_result {
// this call was failed on one of the previous executions,
// here it's needed to bubble this special error up
CallServiceFailed(ret_code, err_msg) => {
exec_ctx.subtree_complete = false;
exec_err!(ExecutionError::LocalServiceError(*ret_code, err_msg.clone()))
}
RequestSentBy(..) => {
// check whether current node can execute this call
let is_current_peer = tetraplet.borrow().triplet.peer_pk.as_str() == exec_ctx.current_peer_id.as_str();
if is_current_peer {
// if this peer could execute this call early return and
return Ok(true);
}
exec_ctx.subtree_complete = false;
Ok(false)
}
// this instruction's been already executed
Executed(value) => {
set_result_from_value(value.clone(), tetraplet.clone(), trace_pos, output, exec_ctx)?;
exec_ctx.subtree_complete = true;
Ok(false)
}
};
trace_ctx.meet_call_end(prev_result);
result
}

View File

@ -32,7 +32,7 @@ pub(crate) struct FoldState<'i> {
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum IterableType {
Scalar,
Stream(usize),
Stream(u32),
}
impl<'i> FoldState<'i> {

View File

@ -64,7 +64,7 @@ macro_rules! execute {
/// Executes fold instruction, updates last error if needed, and call error_exit of TraceHandler.
macro_rules! execute_fold {
($self:expr, $instr:expr, $exec_ctx:ident, $trace_ctx:ident) => {{
$exec_ctx.tracker.met_fold_stream();
$exec_ctx.tracker.meet_fold_stream();
let fold_id = $exec_ctx.tracker.fold.seen_stream_count;
match $instr.execute($exec_ctx, $trace_ctx) {

View File

@ -26,6 +26,7 @@ use super::ResolvedCallResult;
use super::Stream;
use crate::JValue;
use air_interpreter_interface::CallResults;
use jsonpath_lib::JsonPathError;
use strum::IntoEnumIterator;
use strum_macros::EnumDiscriminants;
@ -125,6 +126,12 @@ pub(crate) enum ExecutionError {
/// could be applied to a stream, but result doesn't contain generation in a source position.
#[error("ap result {0:?} doesn't match corresponding instruction")]
ApResultNotCorrespondToInstr(MergerApResult),
/// Call results should be empty at the end of execution thanks to a execution invariant.
#[error(
"after finishing execution of supplied AIR, call results aren't empty: `{0:?}`, probably wrong call_id used"
)]
CallResultsNotEmpty(CallResults),
}
impl From<TraceHandlerError> for Rc<ExecutionError> {

View File

@ -20,6 +20,8 @@ use super::LastErrorWithTetraplet;
use crate::execution_step::boxed_value::Scalar;
use crate::execution_step::boxed_value::Stream;
use air_interpreter_interface::*;
use std::cell::RefCell;
use std::collections::HashMap;
use std::collections::VecDeque;
@ -66,10 +68,24 @@ pub(crate) struct ExecutionCtx<'i> {
/// Tracker of all met instructions.
pub tracker: InstrTracker,
/// Last call request id that was used as an id for call request in outcome.
pub last_call_request_id: u32,
/// Contains all executed results from a host side.
pub call_results: CallResults,
/// Tracks all functions that should be called from services.
pub call_requests: CallRequests,
}
impl<'i> ExecutionCtx<'i> {
pub(crate) fn new(current_peer_id: String, init_peer_id: String) -> Self {
pub(crate) fn new(
current_peer_id: String,
init_peer_id: String,
call_results: CallResults,
last_call_request_id: u32,
) -> Self {
let current_peer_id = Rc::new(current_peer_id);
Self {
@ -77,6 +93,8 @@ impl<'i> ExecutionCtx<'i> {
init_peer_id,
subtree_complete: true,
last_error_could_be_set: true,
last_call_request_id,
call_results,
..<_>::default()
}
}
@ -87,6 +105,11 @@ impl<'i> ExecutionCtx<'i> {
None => <_>::default(),
}
}
pub(crate) fn next_call_request_id(&mut self) -> u32 {
self.last_call_request_id += 1;
self.last_call_request_id
}
}
use std::fmt::Display;

View File

@ -20,53 +20,54 @@ pub(crate) struct InstrTracker {
pub(crate) ap: ApTracker,
pub(crate) call: CallTracker,
pub(crate) fold: FoldTracker,
pub(crate) match_count: usize,
pub(crate) mismatch_count: usize,
pub(crate) next_count: usize,
pub(crate) null_count: usize,
pub(crate) match_count: u32,
pub(crate) mismatch_count: u32,
pub(crate) next_count: u32,
pub(crate) null_count: u32,
pub(crate) par: ParTracker,
pub(crate) seq_count: usize,
pub(crate) xor_count: usize,
pub(crate) seq_count: u32,
pub(crate) xor_count: u32,
}
#[derive(Default)]
#[allow(dead_code)]
pub(crate) struct ApTracker {
pub(crate) seen_count: usize,
pub(crate) executed_count: usize,
pub(crate) seen_count: u32,
pub(crate) executed_count: u32,
}
#[derive(Default)]
#[allow(dead_code)]
pub(crate) struct CallTracker {
pub(crate) seen_count: usize,
pub(crate) executed_count: usize,
pub(crate) seen_count: u32,
pub(crate) executed_count: u32,
}
#[derive(Default)]
#[allow(dead_code)]
pub(crate) struct FoldTracker {
pub(crate) seen_scalar_count: usize,
pub(crate) seen_stream_count: usize,
pub(crate) seen_scalar_count: u32,
pub(crate) seen_stream_count: u32,
}
#[derive(Default)]
#[allow(dead_code)]
pub(crate) struct ParTracker {
pub(crate) seen_count: usize,
pub(crate) executed_count: usize,
pub(crate) seen_count: u32,
pub(crate) executed_count: u32,
}
impl InstrTracker {
pub(crate) fn met_call(&mut self) {
pub(crate) fn meet_call(&mut self) {
self.call.seen_count += 1;
}
pub(crate) fn met_executed_call(&mut self) {
#[allow(dead_code)]
pub(crate) fn meet_executed_call(&mut self) {
self.call.executed_count += 1;
}
pub(crate) fn met_fold_stream(&mut self) {
pub(crate) fn meet_fold_stream(&mut self) {
self.fold.seen_stream_count += 1;
}
}

View File

@ -18,7 +18,7 @@ mod context;
mod error_descriptor;
mod instructions_tracker;
pub(crate) use context::ExecutionCtx;
pub(crate) use context::*;
pub use error_descriptor::LastError;
pub(crate) use error_descriptor::LastErrorDescriptor;
pub(crate) use error_descriptor::LastErrorWithTetraplet;

View File

@ -116,7 +116,7 @@ impl TraceHandler {
}
impl TraceHandler {
pub(crate) fn meet_fold_start(&mut self, fold_id: usize) -> TraceHandlerResult<()> {
pub(crate) fn meet_fold_start(&mut self, fold_id: u32) -> TraceHandlerResult<()> {
let ingredients = try_merge_next_state_as_fold(&mut self.data_keeper)?;
let fold_fsm = FoldFSM::from_fold_start(ingredients, &mut self.data_keeper)?;
self.fsm_keeper.add_fold(fold_id, fold_fsm);
@ -124,42 +124,42 @@ impl TraceHandler {
Ok(())
}
pub(crate) fn meet_iteration_start(&mut self, fold_id: usize, value_pos: usize) -> TraceHandlerResult<()> {
pub(crate) fn meet_iteration_start(&mut self, fold_id: u32, value_pos: usize) -> TraceHandlerResult<()> {
let fold_fsm = self.fsm_keeper.fold_mut(fold_id)?;
fold_fsm.meet_iteration_start(value_pos, &mut self.data_keeper)?;
Ok(())
}
pub(crate) fn meet_iteration_end(&mut self, fold_id: usize) -> TraceHandlerResult<()> {
pub(crate) fn meet_iteration_end(&mut self, fold_id: u32) -> TraceHandlerResult<()> {
let fold_fsm = self.fsm_keeper.fold_mut(fold_id)?;
fold_fsm.meet_iteration_end(&mut self.data_keeper);
Ok(())
}
pub(crate) fn meet_back_iterator(&mut self, fold_id: usize) -> TraceHandlerResult<()> {
pub(crate) fn meet_back_iterator(&mut self, fold_id: u32) -> TraceHandlerResult<()> {
let fold_fsm = self.fsm_keeper.fold_mut(fold_id)?;
fold_fsm.meet_back_iterator(&mut self.data_keeper)?;
Ok(())
}
pub(crate) fn meet_generation_end(&mut self, fold_id: usize) -> TraceHandlerResult<()> {
pub(crate) fn meet_generation_end(&mut self, fold_id: u32) -> TraceHandlerResult<()> {
let fold_fsm = self.fsm_keeper.fold_mut(fold_id)?;
fold_fsm.meet_generation_end(&mut self.data_keeper);
Ok(())
}
pub(crate) fn meet_fold_end(&mut self, fold_id: usize) -> TraceHandlerResult<()> {
pub(crate) fn meet_fold_end(&mut self, fold_id: u32) -> TraceHandlerResult<()> {
let fold_fsm = self.fsm_keeper.extract_fold(fold_id)?;
fold_fsm.meet_fold_end(&mut self.data_keeper);
Ok(())
}
pub(crate) fn fold_end_with_error(&mut self, fold_id: usize) {
pub(crate) fn fold_end_with_error(&mut self, fold_id: u32) {
let fold_fsm = match self.fsm_keeper.extract_fold(fold_id) {
Ok(fold_fsm) => fold_fsm,
// just passing here is ok, because error could be produced while fold initialization

View File

@ -31,7 +31,7 @@ pub(crate) enum StateFSMError {
/// Errors occurred while trying to access or pop elements from queue,
/// which contains element of different type.
#[error("fold FSM for fold id {0} wasn't found")]
FoldFSMNotFound(usize),
FoldFSMNotFound(u32),
/// Errors occurred when ParResult.0 + ParResult.1 overflows.
#[error("overflow is occurred while calculating the entire len occupied by executed states corresponded to current par: '{0:?}'")]

View File

@ -24,7 +24,7 @@ use std::collections::HashMap;
#[derive(Debug, Default)]
pub(crate) struct FSMKeeper {
par_stack: Vec<ParFSM>,
fold_map: HashMap<usize, FoldFSM>,
fold_map: HashMap<u32, FoldFSM>,
}
impl FSMKeeper {
@ -32,7 +32,7 @@ impl FSMKeeper {
self.par_stack.push(par_fsm);
}
pub(crate) fn add_fold(&mut self, fold_id: usize, fold_fsm: FoldFSM) {
pub(crate) fn add_fold(&mut self, fold_id: u32, fold_fsm: FoldFSM) {
self.fold_map.insert(fold_id, fold_fsm);
}
@ -44,13 +44,13 @@ impl FSMKeeper {
self.par_stack.pop().ok_or(StateFSMError::ParQueueIsEmpty())
}
pub(crate) fn fold_mut(&mut self, fold_id: usize) -> FSMResult<&mut FoldFSM> {
pub(crate) fn fold_mut(&mut self, fold_id: u32) -> FSMResult<&mut FoldFSM> {
self.fold_map
.get_mut(&fold_id)
.ok_or(StateFSMError::FoldFSMNotFound(fold_id))
}
pub(crate) fn extract_fold(&mut self, fold_id: usize) -> FSMResult<FoldFSM> {
pub(crate) fn extract_fold(&mut self, fold_id: u32) -> FSMResult<FoldFSM> {
self.fold_map
.remove(&fold_id)
.ok_or(StateFSMError::FoldFSMNotFound(fold_id))

View File

@ -26,7 +26,6 @@
unreachable_patterns
)]
mod build_targets;
mod execution_step;
mod preparation_step;
@ -34,6 +33,7 @@ pub mod log_targets;
mod runner;
pub use air_interpreter_interface::InterpreterOutcome;
pub use air_interpreter_interface::RunParameters;
pub use air_interpreter_interface::INTERPRETER_SUCCESS;
pub use execution_step::execution_context::LastError;
pub use polyplets::ResolvedTriplet;

View File

@ -22,8 +22,6 @@ use strum_macros::EnumDiscriminants;
use strum_macros::EnumIter;
use thiserror::Error as ThisError;
use std::env::VarError;
/// Errors happened during the interpreter preparation_step step.
#[derive(Debug, EnumDiscriminants, ThisError)]
#[strum_discriminants(derive(EnumIter))]
@ -37,9 +35,9 @@ pub enum PreparationError {
Probably it's a data of an old version that couldn't be converted to '{}'", *DATA_FORMAT_VERSION)]
DataDeFailed(SerdeJsonError, Vec<u8>),
/// Error occurred while getting current peer id.
#[error("current peer id can't be obtained: {0:?}")]
CurrentPeerIdEnvError(VarError),
/// Error occurred on call results deserialization.
#[error("error occurred while deserialize call results: {1:?}:\n{0:?}")]
CallResultsDeFailed(SerdeJsonError, Vec<u8>),
}
impl PreparationError {

View File

@ -15,13 +15,12 @@
*/
use super::PreparationError;
use crate::build_targets::get_current_peer_id;
use crate::execution_step::ExecutionCtx;
use crate::execution_step::Stream;
use crate::execution_step::TraceHandler;
use crate::log_targets::RUN_PARAMS;
use air_interpreter_data::InterpreterData;
use air_interpreter_interface::RunParameters;
use air_parser::ast::Instruction;
type PreparationResult<T> = Result<T, PreparationError>;
@ -38,22 +37,15 @@ pub(crate) fn prepare<'i>(
prev_data: &[u8],
current_data: &[u8],
raw_air: &'i str,
init_peer_id: String,
call_results: &[u8],
run_parameters: RunParameters,
) -> PreparationResult<PreparationDescriptor<'static, 'i>> {
let prev_data = try_to_data(prev_data)?;
let current_data = try_to_data(current_data)?;
let air: Instruction<'i> = *air_parser::parse(raw_air).map_err(PreparationError::AIRParseError)?;
log::trace!(
target: RUN_PARAMS,
"air: {:?}\nprev_trace: {:?}\ncurrent_trace: {:?}",
air,
prev_data,
current_data
);
let exec_ctx = make_exec_ctx(init_peer_id, &prev_data)?;
let exec_ctx = make_exec_ctx(&prev_data, call_results, run_parameters)?;
let trace_handler = TraceHandler::from_data(prev_data, current_data);
let result = PreparationDescriptor {
@ -71,11 +63,25 @@ fn try_to_data(raw_data: &[u8]) -> PreparationResult<InterpreterData> {
InterpreterData::try_from_slice(raw_data).map_err(|err| DataDeFailed(err, raw_data.to_vec()))
}
fn make_exec_ctx(init_peer_id: String, prev_data: &InterpreterData) -> PreparationResult<ExecutionCtx<'static>> {
let current_peer_id = get_current_peer_id().map_err(PreparationError::CurrentPeerIdEnvError)?;
log::trace!(target: RUN_PARAMS, "current peer id {}", current_peer_id);
fn make_exec_ctx(
prev_data: &InterpreterData,
call_results: &[u8],
run_parameters: RunParameters,
) -> PreparationResult<ExecutionCtx<'static>> {
let RunParameters {
init_peer_id,
current_peer_id,
} = run_parameters;
let mut ctx = ExecutionCtx::new(current_peer_id, init_peer_id);
let call_results = serde_json::from_slice(call_results)
.map_err(|e| PreparationError::CallResultsDeFailed(e, call_results.to_vec()))?;
let mut ctx = ExecutionCtx::new(
current_peer_id,
init_peer_id,
call_results,
prev_data.last_call_request_id,
);
create_streams(&mut ctx, prev_data);
Ok(ctx)

View File

@ -16,36 +16,52 @@
mod outcome;
use crate::execution_step::Catchable;
use crate::execution_step::ExecutableInstruction;
use crate::execution_step::ExecutionCtx;
use crate::execution_step::ExecutionError;
use crate::execution_step::{Catchable, TraceHandler};
use crate::log_targets::RUN_PARAMS;
use crate::preparation_step::prepare;
use crate::preparation_step::PreparationDescriptor;
use air_interpreter_interface::InterpreterOutcome;
use air_interpreter_interface::RunParameters;
use std::rc::Rc;
pub fn execute_air(init_peer_id: String, air: String, prev_data: Vec<u8>, data: Vec<u8>) -> InterpreterOutcome {
use std::convert::identity;
log::trace!(
"air interpreter version is {}, init user id is {}",
env!("CARGO_PKG_VERSION"),
init_peer_id
);
execute_air_impl(init_peer_id, air, prev_data, data).unwrap_or_else(identity)
}
fn execute_air_impl(
init_peer_id: String,
pub fn execute_air(
air: String,
prev_data: Vec<u8>,
data: Vec<u8>,
params: RunParameters,
call_results: Vec<u8>,
) -> InterpreterOutcome {
use std::convert::identity;
log::trace!(
target: RUN_PARAMS,
"air interpreter version is {}, run parameters:\
init peer id {}\
current peer id {}",
env!("CARGO_PKG_VERSION"),
params.init_peer_id,
params.current_peer_id,
);
execute_air_impl(air, prev_data, data, params, call_results).unwrap_or_else(identity)
}
fn execute_air_impl(
air: String,
prev_data: Vec<u8>,
data: Vec<u8>,
params: RunParameters,
call_results: Vec<u8>,
) -> Result<InterpreterOutcome, InterpreterOutcome> {
let PreparationDescriptor {
mut exec_ctx,
mut trace_handler,
air,
} = match prepare(&prev_data, &data, air.as_str(), init_peer_id) {
} = match prepare(&prev_data, &data, air.as_str(), &call_results, params) {
Ok(desc) => desc,
// return the initial data in case of errors
Err(error) => return Err(outcome::from_preparation_error(prev_data, error)),
@ -54,14 +70,24 @@ fn execute_air_impl(
// match here is used instead of map_err, because the compiler can't determine that
// they are exclusive and would treat exec_ctx and trace_handler as moved
match air.execute(&mut exec_ctx, &mut trace_handler) {
Ok(_) => {}
Ok(_) => try_make_outcome(exec_ctx, trace_handler),
// return the old data in case of any trace errors
Err(e) if !e.is_catchable() => return Err(outcome::from_trace_error(prev_data, e)),
Err(e) if !e.is_catchable() => Err(outcome::from_trace_error(prev_data, e)),
// return new collected trace in case of errors
Err(e) => return Err(outcome::from_execution_error(exec_ctx, trace_handler, e)),
Err(e) => Err(outcome::from_execution_error(exec_ctx, trace_handler, e)),
}
}
fn try_make_outcome(
exec_ctx: ExecutionCtx<'_>,
trace_handler: TraceHandler,
) -> Result<InterpreterOutcome, InterpreterOutcome> {
if exec_ctx.call_results.is_empty() {
let outcome = outcome::from_success_result(exec_ctx, trace_handler);
return Ok(outcome);
}
let outcome = outcome::from_success_result(exec_ctx, trace_handler);
Ok(outcome)
let exec_error = Rc::new(ExecutionError::CallResultsNotEmpty(exec_ctx.call_results.clone()));
let outcome = outcome::from_execution_error(exec_ctx, trace_handler, exec_error);
Err(outcome)
}

View File

@ -24,6 +24,7 @@ use crate::INTERPRETER_SUCCESS;
use air_interpreter_data::InterpreterData;
use air_interpreter_data::StreamGenerations;
use air_interpreter_interface::CallRequests;
use std::cell::RefCell;
use std::collections::HashMap;
@ -36,16 +37,21 @@ const EXECUTION_ERRORS_START_ID: i32 = 1000;
/// set ret_code to INTERPRETER_SUCCESS.
pub(crate) fn from_success_result(exec_ctx: ExecutionCtx<'_>, trace_handler: TraceHandler) -> InterpreterOutcome {
let streams = extract_stream_generations(exec_ctx.streams);
let data = InterpreterData::from_execution_result(trace_handler.into_result_trace(), streams);
let data = InterpreterData::from_execution_result(
trace_handler.into_result_trace(),
streams,
exec_ctx.last_call_request_id,
);
let data = serde_json::to_vec(&data).expect("default serializer shouldn't fail");
let next_peer_pks = dedup(exec_ctx.next_peer_pks);
let call_requests = serde_json::to_vec(&exec_ctx.call_requests).expect("default serializer shouldn't fail");
InterpreterOutcome {
ret_code: INTERPRETER_SUCCESS,
error_message: String::new(),
data,
next_peer_pks,
call_requests,
}
}
@ -54,12 +60,14 @@ pub(crate) fn from_success_result(exec_ctx: ExecutionCtx<'_>, trace_handler: Tra
pub(crate) fn from_preparation_error(data: impl Into<Vec<u8>>, err: PreparationError) -> InterpreterOutcome {
let ret_code = err.to_error_code() as i32;
let data = data.into();
let call_requests = serde_json::to_vec(&CallRequests::new()).expect("default serializer shouldn't fail");
InterpreterOutcome {
ret_code,
error_message: format!("{}", err),
data,
next_peer_pks: vec![],
call_requests,
}
}
@ -69,12 +77,14 @@ pub(crate) fn from_trace_error(data: impl Into<Vec<u8>>, err: Rc<ExecutionError>
let ret_code = err.to_error_code() as i32;
let ret_code = EXECUTION_ERRORS_START_ID + ret_code;
let data = data.into();
let call_requests = serde_json::to_vec(&CallRequests::new()).expect("default serializer shouldn't fail");
InterpreterOutcome {
ret_code,
error_message: format!("{}", err),
data,
next_peer_pks: vec![],
call_requests,
}
}
@ -89,16 +99,21 @@ pub(crate) fn from_execution_error(
let ret_code = EXECUTION_ERRORS_START_ID + ret_code;
let streams = extract_stream_generations(exec_ctx.streams);
let data = InterpreterData::from_execution_result(trace_handler.into_result_trace(), streams);
let data = InterpreterData::from_execution_result(
trace_handler.into_result_trace(),
streams,
exec_ctx.last_call_request_id,
);
let data = serde_json::to_vec(&data).expect("default serializer shouldn't fail");
let next_peer_pks = dedup(exec_ctx.next_peer_pks);
let call_requests = serde_json::to_vec(&exec_ctx.call_requests).expect("default serializer shouldn't fail");
InterpreterOutcome {
ret_code,
error_message: format!("{}", err),
data,
next_peer_pks,
call_requests,
}
}

View File

@ -14,20 +14,16 @@
* limitations under the License.
*/
use air_test_utils::*;
use serde_json::json;
use air_test_utils::prelude::*;
#[test]
fn ap_with_scalars() {
let vm_1_peer_id = "vm_1_peer_id";
let test_value = "scalar_2";
let mut vm_1 = create_avm(
set_variable_call_service(json!({ "field": test_value }).to_string()),
vm_1_peer_id,
);
let mut vm_1 = create_avm(set_variable_call_service(json!({ "field": test_value })), vm_1_peer_id);
let vm_2_peer_id = "vm_2_peer_id";
let mut vm_2 = create_avm(echo_string_call_service(), vm_2_peer_id);
let mut vm_2 = create_avm(echo_call_service(), vm_2_peer_id);
let script = format!(
r#"
@ -158,10 +154,7 @@ fn ap_with_last_error() {
fn ap_with_dst_stream() {
let vm_1_peer_id = "vm_1_peer_id";
let test_value = "scalar_2";
let mut vm_1 = create_avm(
set_variable_call_service(json!({ "field": test_value }).to_string()),
vm_1_peer_id,
);
let mut vm_1 = create_avm(set_variable_call_service(json!({ "field": test_value })), vm_1_peer_id);
let vm_2_peer_id = "vm_2_peer_id";
let mut vm_2 = create_avm(echo_call_service(), vm_2_peer_id);

View File

@ -14,24 +14,14 @@
* limitations under the License.
*/
use air_test_utils::call_vm;
use air_test_utils::checked_call_vm;
use air_test_utils::create_avm;
use air_test_utils::echo_string_call_service;
use air_test_utils::executed_state;
use air_test_utils::set_variable_call_service;
use air_test_utils::trace_from_result;
use air_test_utils::unit_call_service;
use air_test_utils::CallServiceClosure;
use air_test_utils::IValue;
use air_test_utils::NEVec;
use air_test_utils::prelude::*;
// Check that %init_peer_id% alias works correctly (by comparing result with it and explicit peer id).
// Additionally, check that empty string for data does the same as empty call path.
#[test]
fn current_peer_id_call() {
let vm_peer_id = String::from("test_peer_id");
let mut vm = create_avm(unit_call_service(), vm_peer_id.clone());
let vm_peer_id = "test_peer_id";
let mut vm = create_avm(unit_call_service(), vm_peer_id);
let service_id = "local_service_id";
let function_name = "local_fn_name";
@ -42,13 +32,12 @@ fn current_peer_id_call() {
service_id, function_name
);
let result = checked_call_vm!(vm, &vm_peer_id, script, [], []);
let result = checked_call_vm!(vm, vm_peer_id, script, "", "");
let actual_trace = trace_from_result(&result);
let expected_state = executed_state::scalar_string("test");
let expected_trace = vec![executed_state::scalar_string("test")];
assert_eq!(actual_trace.len(), 1);
assert_eq!(actual_trace[0], expected_state);
assert_eq!(actual_trace, expected_trace);
assert!(result.next_peer_pks.is_empty());
let script = format!(
@ -69,7 +58,7 @@ fn current_peer_id_call() {
#[test]
fn remote_peer_id_call() {
let some_local_peer_id = String::from("some_local_peer_id");
let mut vm = create_avm(echo_string_call_service(), &some_local_peer_id);
let mut vm = create_avm(echo_call_service(), &some_local_peer_id);
let remote_peer_id = String::from("some_remote_peer_id");
let script = format!(
@ -91,7 +80,7 @@ fn remote_peer_id_call() {
#[test]
fn variables() {
let mut vm = create_avm(unit_call_service(), "remote_peer_id");
let mut set_variable_vm = create_avm(set_variable_call_service(r#""remote_peer_id""#), "set_variable");
let mut set_variable_vm = create_avm(set_variable_call_service(json!("remote_peer_id")), "set_variable");
let script = r#"
(seq
@ -127,43 +116,32 @@ fn duplicate_variables() {
// Check that string literals can be used as call parameters.
#[test]
fn string_parameters() {
let call_service: CallServiceClosure = Box::new(|args| -> Option<IValue> {
let arg = match &args.function_args[2] {
IValue::String(str) => str,
_ => unreachable!(),
};
let call_service: CallServiceClosure =
Box::new(|mut params| -> CallServiceResult { CallServiceResult::ok(params.arguments.remove(0)) });
Some(IValue::Record(
NEVec::new(vec![IValue::S32(0), IValue::String(arg.clone())]).unwrap(),
))
});
let vm_peer_id = "A";
let mut vm = create_avm(call_service, vm_peer_id);
let vm_peer_id = String::from("A");
let mut vm = create_avm(call_service, vm_peer_id.clone());
let set_variable_vm_peer_id = "set_variable";
let mut set_variable_vm = create_avm(set_variable_call_service(json!("arg3_value")), set_variable_vm_peer_id);
let set_variable_vm_peer_id = String::from("set_variable");
let mut set_variable_vm = create_avm(
set_variable_call_service(r#""arg3_value""#),
set_variable_vm_peer_id.clone(),
);
let service_id = String::from("some_service_id");
let function_name = String::from("local_fn_name");
let service_id = "some_service_id";
let function_name = "local_fn_name";
let script = format!(
r#"
(seq
(call "{}" ("{}" "{}") [] arg3)
(call "{}" ("{}" "{}") ["arg1" "arg2" arg3] result)
(call "{0}" ("{1}" "{2}") [] arg3)
(call "{3}" ("{1}" "{2}") ["arg1" "arg2" arg3] result)
)
"#,
set_variable_vm_peer_id, service_id, function_name, vm_peer_id, service_id, function_name
set_variable_vm_peer_id, service_id, function_name, vm_peer_id
);
let result = checked_call_vm!(set_variable_vm, "asd", &script, "", "");
let result = checked_call_vm!(vm, "asd", script, "", result.data);
let actual_trace = trace_from_result(&result);
let expected_state = executed_state::scalar_string_array(vec!["arg1", "arg2", "arg3_value"]);
let expected_state = executed_state::scalar_string("arg1");
assert_eq!(actual_trace.len(), 2);
assert_eq!(actual_trace[1], expected_state);

View File

@ -14,23 +14,15 @@
* limitations under the License.
*/
use air_test_utils::call_vm;
use air_test_utils::checked_call_vm;
use air_test_utils::create_avm;
use air_test_utils::echo_number_call_service;
use air_test_utils::echo_string_call_service;
use air_test_utils::executed_state;
use air_test_utils::set_variable_call_service;
use air_test_utils::trace_from_result;
use air_test_utils::AVMError;
use air_test_utils::InterpreterOutcome;
use serde_json::json;
use air_test_utils::prelude::*;
#[test]
fn lfold() {
let mut vm = create_avm(echo_number_call_service(), "A");
let mut set_variable_vm = create_avm(set_variable_call_service(r#"["1","2","3","4","5"]"#), "set_variable");
let mut vm = create_avm(echo_call_service(), "A");
let mut set_variable_vm = create_avm(
set_variable_call_service(json!(["1", "2", "3", "4", "5"])),
"set_variable",
);
let lfold = r#"
(seq
@ -53,15 +45,18 @@ fn lfold() {
assert_eq!(actual_trace[0], expected_state);
for i in 1..=5 {
let expected_state = executed_state::stream_number(i, 0);
let expected_state = executed_state::stream_string(format!("{}", i), 0);
assert_eq!(actual_trace[i], expected_state);
}
}
#[test]
fn rfold() {
let mut vm = create_avm(echo_number_call_service(), "A");
let mut set_variable_vm = create_avm(set_variable_call_service(r#"["1","2","3","4","5"]"#), "set_variable");
let mut vm = create_avm(echo_call_service(), "A");
let mut set_variable_vm = create_avm(
set_variable_call_service(json!(["1", "2", "3", "4", "5"])),
"set_variable",
);
let rfold = r#"
(seq
@ -84,15 +79,18 @@ fn rfold() {
assert_eq!(actual_trace[0], expected_state);
for i in 1..=5 {
let expected_state = executed_state::stream_number(6 - i, 0);
let expected_state = executed_state::stream_string(format!("{}", 6 - i), 0);
assert_eq!(actual_trace[i], expected_state);
}
}
#[test]
fn inner_fold() {
let mut vm = create_avm(echo_number_call_service(), "A");
let mut set_variable_vm = create_avm(set_variable_call_service(r#"["1","2","3","4","5"]"#), "set_variable");
let mut vm = create_avm(echo_call_service(), "A");
let mut set_variable_vm = create_avm(
set_variable_call_service(json!(["1", "2", "3", "4", "5"])),
"set_variable",
);
let script = r#"
(seq
@ -125,7 +123,7 @@ fn inner_fold() {
for i in 1..=5 {
for j in 1..=5 {
let expected_state = executed_state::stream_number(i, 0);
let expected_state = executed_state::stream_string(i.to_string(), 0);
assert_eq!(actual_trace[1 + 5 * (i - 1) + j], expected_state);
}
}
@ -133,7 +131,10 @@ fn inner_fold() {
#[test]
fn inner_fold_with_same_iterator() {
let mut vm = create_avm(set_variable_call_service(r#"["1","2","3","4","5"]"#), "set_variable");
let mut vm = create_avm(
set_variable_call_service(json!(["1", "2", "3", "4", "5"])),
"set_variable",
);
let script = r#"
(seq
@ -161,8 +162,8 @@ fn inner_fold_with_same_iterator() {
#[test]
fn empty_fold() {
let mut vm = create_avm(echo_number_call_service(), "A");
let mut set_variable_vm = create_avm(set_variable_call_service(r#"[]"#), "set_variable");
let mut vm = create_avm(echo_call_service(), "A");
let mut set_variable_vm = create_avm(set_variable_call_service(json!([])), "set_variable");
let empty_fold = r#"
(seq
@ -187,8 +188,8 @@ fn empty_fold() {
#[test]
fn empty_fold_json_path() {
let mut vm = create_avm(echo_number_call_service(), "A");
let mut set_variable_vm = create_avm(set_variable_call_service(r#"{ "messages": [] }"#), "set_variable");
let mut vm = create_avm(echo_call_service(), "A");
let mut set_variable_vm = create_avm(set_variable_call_service(json!({ "messages": [] })), "set_variable");
let empty_fold = r#"
(seq
@ -213,8 +214,8 @@ fn empty_fold_json_path() {
// Check that fold works with the join behaviour without hanging up.
#[test]
fn fold_with_join() {
let mut vm = create_avm(echo_number_call_service(), "A");
let mut set_variable_vm = create_avm(set_variable_call_service(r#"["1","2"]"#), "set_variable");
let mut vm = create_avm(echo_call_service(), "A");
let mut set_variable_vm = create_avm(set_variable_call_service(json!(["1", "2"])), "set_variable");
let fold_with_join = r#"
(seq
@ -239,13 +240,13 @@ fn fold_with_join() {
#[test]
fn json_path() {
let mut vm = create_avm(echo_number_call_service(), "A");
let mut vm = create_avm(echo_call_service(), "A");
let mut set_variable_vm = create_avm(
set_variable_call_service(r#"{ "array": ["1","2","3","4","5"] }"#),
set_variable_call_service(json!({ "array": ["1","2","3","4","5"] })),
"set_variable",
);
let lfold = r#"
let script = r#"
(seq
(call "set_variable" ("" "") [] iterable)
(fold iterable.$.array! i
@ -256,8 +257,8 @@ fn json_path() {
)
)"#;
let result = checked_call_vm!(set_variable_vm, "", lfold, "", "");
let result = checked_call_vm!(vm, "", lfold, "", result.data);
let result = checked_call_vm!(set_variable_vm, "", script, "", "");
let result = checked_call_vm!(vm, "", script, "", result.data);
let actual_trace = trace_from_result(&result);
let expected_state = executed_state::scalar(json!({ "array": ["1", "2", "3", "4", "5"] }));
@ -266,7 +267,7 @@ fn json_path() {
assert_eq!(actual_trace[0], expected_state);
for i in 1..=5 {
let expected_state = executed_state::stream_number(i, 0);
let expected_state = executed_state::stream_string(format!("{}", i), 0);
assert_eq!(actual_trace[i], expected_state);
}
}
@ -275,9 +276,9 @@ fn json_path() {
fn shadowing() {
use executed_state::*;
let mut set_variables_vm = create_avm(set_variable_call_service(r#"["1","2"]"#), "set_variable");
let mut vm_a = create_avm(echo_string_call_service(), "A");
let mut vm_b = create_avm(echo_string_call_service(), "B");
let mut set_variables_vm = create_avm(set_variable_call_service(json!(["1", "2"])), "set_variable");
let mut vm_a = create_avm(echo_call_service(), "A");
let mut vm_b = create_avm(echo_call_service(), "B");
let script = r#"
(seq
@ -338,10 +339,10 @@ fn shadowing() {
fn shadowing_scope() {
use executed_state::*;
fn execute_script(script: String) -> Result<InterpreterOutcome, AVMError> {
let mut set_variables_vm = create_avm(set_variable_call_service(r#"["1","2"]"#), "set_variable");
let mut vm_a = create_avm(echo_string_call_service(), "A");
let mut vm_b = create_avm(echo_string_call_service(), "B");
fn execute_script(script: String) -> Result<RawAVMOutcome, String> {
let mut set_variables_vm = create_avm(set_variable_call_service(json!(["1", "2"])), "set_variable");
let mut vm_a = create_avm(echo_call_service(), "A");
let mut vm_b = create_avm(echo_call_service(), "B");
let result = checked_call_vm!(set_variables_vm, "", script.clone(), "", "");
let result = checked_call_vm!(vm_a, "", script.clone(), "", result.data);
@ -349,7 +350,7 @@ fn shadowing_scope() {
let result = checked_call_vm!(vm_a, "", script.clone(), "", result.data);
let result = checked_call_vm!(vm_b, "", script.clone(), "", result.data);
vm_a.call_with_prev_data("", script, "", result.data)
vm_a.call(script, "", result.data, "")
}
let variable_shadowing_script = r#"

View File

@ -14,15 +14,15 @@
* limitations under the License.
*/
use air_test_utils::*;
use air_test_utils::prelude::*;
#[test]
fn match_equal() {
let set_variable_peer_id = "set_variable_peer_id";
let mut set_variable_vm = create_avm(echo_string_call_service(), set_variable_peer_id);
let mut set_variable_vm = create_avm(echo_call_service(), set_variable_peer_id);
let local_peer_id = "local_peer_id";
let mut vm = create_avm(echo_string_call_service(), local_peer_id);
let mut vm = create_avm(echo_call_service(), local_peer_id);
let script = format!(
r#"
@ -54,10 +54,10 @@ fn match_equal() {
#[test]
fn match_not_equal() {
let set_variable_peer_id = "set_variable_peer_id";
let mut set_variable_vm = create_avm(echo_string_call_service(), set_variable_peer_id);
let mut set_variable_vm = create_avm(echo_call_service(), set_variable_peer_id);
let local_peer_id = "local_peer_id";
let mut vm = create_avm(echo_string_call_service(), local_peer_id);
let mut vm = create_avm(echo_call_service(), local_peer_id);
let script = format!(
r#"
@ -89,10 +89,10 @@ fn match_not_equal() {
#[test]
fn match_with_string() {
let set_variable_peer_id = "set_variable_peer_id";
let mut set_variable_vm = create_avm(echo_string_call_service(), set_variable_peer_id);
let mut set_variable_vm = create_avm(echo_call_service(), set_variable_peer_id);
let local_peer_id = "local_peer_id";
let mut vm = create_avm(echo_string_call_service(), local_peer_id);
let mut vm = create_avm(echo_call_service(), local_peer_id);
let script = format!(
r#"
@ -121,10 +121,10 @@ fn match_with_string() {
#[test]
fn match_with_init_peer_id() {
let set_variable_peer_id = "set_variable_peer_id";
let mut set_variable_vm = create_avm(echo_string_call_service(), set_variable_peer_id);
let mut set_variable_vm = create_avm(echo_call_service(), set_variable_peer_id);
let local_peer_id = "local_peer_id";
let mut vm = create_avm(echo_string_call_service(), local_peer_id);
let mut vm = create_avm(echo_call_service(), local_peer_id);
let script = format!(
r#"
@ -153,7 +153,7 @@ fn match_with_init_peer_id() {
#[test]
fn match_with_equal_numbers() {
let local_peer_id = "local_peer_id";
let mut vm = create_avm(echo_string_call_service(), local_peer_id);
let mut vm = create_avm(echo_call_service(), local_peer_id);
let script = "
(xor
@ -170,10 +170,10 @@ fn match_with_equal_numbers() {
#[test]
fn match_without_xor() {
let set_variable_peer_id = "set_variable_peer_id";
let mut set_variable_vm = create_avm(echo_string_call_service(), set_variable_peer_id);
let mut set_variable_vm = create_avm(echo_call_service(), set_variable_peer_id);
let local_peer_id = "local_peer_id";
let mut vm = create_avm(echo_string_call_service(), local_peer_id);
let mut vm = create_avm(echo_call_service(), local_peer_id);
let script = format!(
r#"
@ -201,13 +201,8 @@ fn match_without_xor() {
#[test]
fn match_with_two_xors() {
use air_test_utils::set_variable_call_service;
let local_peer_id = "local_peer_id";
let mut vm = create_avm(
set_variable_call_service(serde_json::json!(false).to_string()),
local_peer_id,
);
let mut vm = create_avm(set_variable_call_service(serde_json::json!(false)), local_peer_id);
let local_peer_id_2 = "local_peer_id_2";

View File

@ -14,20 +14,15 @@
* limitations under the License.
*/
use air_test_utils::call_vm;
use air_test_utils::checked_call_vm;
use air_test_utils::create_avm;
use air_test_utils::echo_string_call_service;
use air_test_utils::executed_state;
use air_test_utils::trace_from_result;
use air_test_utils::prelude::*;
#[test]
fn mismatch_equal() {
let set_variable_peer_id = "set_variable_peer_id";
let mut set_variable_vm = create_avm(echo_string_call_service(), set_variable_peer_id);
let mut set_variable_vm = create_avm(echo_call_service(), set_variable_peer_id);
let local_peer_id = "local_peer_id";
let mut vm = create_avm(echo_string_call_service(), local_peer_id);
let mut vm = create_avm(echo_call_service(), local_peer_id);
let script = format!(
r#"
@ -59,10 +54,10 @@ fn mismatch_equal() {
#[test]
fn mismatch_not_equal() {
let set_variable_peer_id = "set_variable_peer_id";
let mut set_variable_vm = create_avm(echo_string_call_service(), set_variable_peer_id);
let mut set_variable_vm = create_avm(echo_call_service(), set_variable_peer_id);
let local_peer_id = "local_peer_id";
let mut vm = create_avm(echo_string_call_service(), local_peer_id);
let mut vm = create_avm(echo_call_service(), local_peer_id);
let script = format!(
r#"
@ -94,10 +89,10 @@ fn mismatch_not_equal() {
#[test]
fn mismatch_with_string() {
let set_variable_peer_id = "set_variable_peer_id";
let mut set_variable_vm = create_avm(echo_string_call_service(), set_variable_peer_id);
let mut set_variable_vm = create_avm(echo_call_service(), set_variable_peer_id);
let local_peer_id = "local_peer_id";
let mut vm = create_avm(echo_string_call_service(), local_peer_id);
let mut vm = create_avm(echo_call_service(), local_peer_id);
let script = format!(
r#"
@ -126,10 +121,10 @@ fn mismatch_with_string() {
#[test]
fn mismatch_without_xor() {
let set_variable_peer_id = "set_variable_peer_id";
let mut set_variable_vm = create_avm(echo_string_call_service(), set_variable_peer_id);
let mut set_variable_vm = create_avm(echo_call_service(), set_variable_peer_id);
let local_peer_id = "local_peer_id";
let mut vm = create_avm(echo_string_call_service(), local_peer_id);
let mut vm = create_avm(echo_call_service(), local_peer_id);
let script = format!(
r#"
@ -157,13 +152,8 @@ fn mismatch_without_xor() {
#[test]
fn mismatch_with_two_xors() {
use air_test_utils::set_variable_call_service;
let local_peer_id = "local_peer_id";
let mut vm = create_avm(
set_variable_call_service(serde_json::json!(false).to_string()),
local_peer_id,
);
let mut vm = create_avm(set_variable_call_service(serde_json::json!(false)), local_peer_id);
let local_peer_id_2 = "local_peer_id_2";

View File

@ -14,9 +14,7 @@
* limitations under the License.
*/
use air_test_utils::checked_call_vm;
use air_test_utils::create_avm;
use air_test_utils::unit_call_service;
use air_test_utils::prelude::*;
#[test]
fn par_remote_remote() {

View File

@ -14,11 +14,7 @@
* limitations under the License.
*/
use air_test_utils::checked_call_vm;
use air_test_utils::create_avm;
use air_test_utils::executed_state;
use air_test_utils::raw_data_from_trace;
use air_test_utils::unit_call_service;
use air_test_utils::prelude::*;
#[test]
fn seq_remote_remote() {

View File

@ -14,17 +14,12 @@
* limitations under the License.
*/
use air_test_utils::checked_call_vm;
use air_test_utils::create_avm;
use air_test_utils::echo_string_call_service;
use air_test_utils::executed_state;
use air_test_utils::fallible_call_service;
use air_test_utils::trace_from_result;
use air_test_utils::prelude::*;
#[test]
fn xor() {
let local_peer_id = "local_peer_id";
let fallible_service_id = String::from("service_id_1");
let fallible_service_id = "service_id_1";
let mut vm = create_avm(fallible_call_service(fallible_service_id), local_peer_id);
let script = format!(
@ -39,10 +34,10 @@ fn xor() {
let result = checked_call_vm!(vm, "asd", script, "", "");
let actual_trace = trace_from_result(&result);
let expected_call_result = executed_state::scalar_string("res");
let expected_call_result = executed_state::scalar_string("test");
assert_eq!(actual_trace.len(), 2);
assert_eq!(actual_trace[0], executed_state::service_failed(1, "error"));
assert_eq!(actual_trace[0], executed_state::service_failed(1, r#""error""#));
assert_eq!(actual_trace[1], expected_call_result);
let script = format!(
@ -64,7 +59,7 @@ fn xor() {
#[test]
fn xor_var_not_found() {
let local_peer_id = "local_peer_id";
let mut vm = create_avm(echo_string_call_service(), local_peer_id);
let mut vm = create_avm(echo_call_service(), local_peer_id);
let script = format!(
r#"
@ -88,10 +83,10 @@ fn xor_var_not_found() {
#[test]
fn xor_multiple_variables_found() {
let set_variables_peer_id = "set_variables_peer_id";
let mut set_variables_vm = create_avm(echo_string_call_service(), set_variables_peer_id);
let mut set_variables_vm = create_avm(echo_call_service(), set_variables_peer_id);
let local_peer_id = "local_peer_id";
let mut vm = create_avm(echo_string_call_service(), local_peer_id);
let mut vm = create_avm(echo_call_service(), local_peer_id);
let some_string = String::from("some_string");
let expected_string = String::from("expected_string");
@ -111,12 +106,12 @@ fn xor_multiple_variables_found() {
let result = checked_call_vm!(vm, "asd", script, "", result.data);
let actual_trace = trace_from_result(&result);
let some_string_call_result = executed_state::scalar_string(some_string);
let expected_string_call_result = executed_state::scalar_string(expected_string);
let expected_trace = vec![
executed_state::scalar_string(some_string),
executed_state::scalar_string(expected_string),
];
assert_eq!(actual_trace.len(), 2);
assert_eq!(actual_trace[0], some_string_call_result);
assert_eq!(actual_trace[1], expected_string_call_result);
assert_eq!(actual_trace, expected_trace);
}
#[test]
@ -151,16 +146,16 @@ fn xor_par() {
let result = checked_call_vm!(vm, "asd", &script, "", "");
let actual_trace = trace_from_result(&result);
let scalar_result = String::from("res");
let scalar_result = String::from("test");
let expected_trace = vec![
par(3, 3),
par(1, 1),
service_failed(1, "error"),
service_failed(1, "error"),
service_failed(1, r#""error""#),
service_failed(1, r#""error""#),
par(1, 1),
service_failed(1, "error"),
service_failed(1, "error"),
service_failed(1, r#""error""#),
service_failed(1, r#""error""#),
scalar_string(&scalar_result),
scalar_string(&scalar_result),
];
@ -175,12 +170,10 @@ fn xor_par() {
#[test]
fn last_error_with_xor() {
use air_test_utils::echo_string_call_service;
let faillible_peer_id = "failible_peer_id";
let mut faillible_vm = create_avm(fallible_call_service("service_id_1"), faillible_peer_id);
let local_peer_id = "local_peer_id";
let mut vm = create_avm(echo_string_call_service(), local_peer_id);
let mut vm = create_avm(echo_call_service(), local_peer_id);
let script = format!(
r#"
@ -195,7 +188,8 @@ fn last_error_with_xor() {
let result = checked_call_vm!(vm, "asd", script, "", result.data);
let actual_trace = trace_from_result(&result);
let expected_state = executed_state::scalar_string("Local service error, ret_code is 1, error message is 'error'");
let expected_state =
executed_state::scalar_string(r#"Local service error, ret_code is 1, error message is '"error"'"#);
assert_eq!(actual_trace[1], expected_state);
}

View File

@ -14,17 +14,7 @@
* limitations under the License.
*/
use air_test_utils::checked_call_vm;
use air_test_utils::create_avm;
use air_test_utils::executed_state;
use air_test_utils::set_variables_call_service;
use air_test_utils::trace_from_result;
use air_test_utils::unit_call_service;
use air_test_utils::CallServiceClosure;
use air_test_utils::IValue;
use air_test_utils::NEVec;
use serde_json::json;
use air_test_utils::prelude::*;
#[test]
fn seq_par_call() {
@ -111,33 +101,26 @@ fn create_service() {
let blueprint = json!({ "name": "blueprint", "dependencies": [module]});
let variables_mapping = maplit::hashmap!(
String::from("module_bytes") => module_bytes.to_string(),
String::from("module_config") => module_config.to_string(),
String::from("blueprint") => blueprint.to_string(),
"module_bytes".to_string() => module_bytes.clone(),
"module_config".to_string() => module_config.clone(),
"blueprint".to_string() => blueprint.clone(),
);
let mut set_variables_vm = create_avm(set_variables_call_service(variables_mapping), "set_variables");
let add_module_response = String::from("add_module response");
let add_blueprint_response = String::from("add_blueprint response");
let create_response = String::from("create response");
let add_module_response = "add_module response";
let add_blueprint_response = "add_blueprint response";
let create_response = "create response";
let call_service: CallServiceClosure = Box::new(move |args| -> Option<IValue> {
let builtin_service = match &args.function_args[0] {
IValue::String(str) => str,
_ => unreachable!(),
let call_service: CallServiceClosure = Box::new(move |params| -> CallServiceResult {
let response = match params.service_id.as_str() {
"add_module" => add_module_response,
"add_blueprint" => add_blueprint_response,
"create" => create_response,
_ => "unknown response",
};
let response = match builtin_service.as_str() {
"add_module" => add_module_response.clone(),
"add_blueprint" => add_blueprint_response.clone(),
"create" => create_response.clone(),
_ => String::from("unknown response"),
};
Some(IValue::Record(
NEVec::new(vec![IValue::S32(0), IValue::String(format!("\"{}\"", response))]).unwrap(),
))
CallServiceResult::ok(json!(response))
});
let mut vm = create_avm(call_service, "A");

View File

@ -14,8 +14,7 @@
* limitations under the License.
*/
use air_test_utils::*;
use serde_json::json;
use air_test_utils::prelude::*;
#[test]
fn ap_with_fold() {
@ -29,7 +28,7 @@ fn ap_with_fold() {
("a".into(), vec),
];
let set_variable_id = "set_variable_peer_id";
let mut set_variable_vm = create_avm(set_variable_call_service(json!(elems).to_string()), set_variable_id);
let mut set_variable_vm = create_avm(set_variable_call_service(json!(elems)), set_variable_id);
let local_vm_peer_id = "local_peer_id";
let mut local_vm = create_avm(unit_call_service(), local_vm_peer_id);

View File

@ -14,18 +14,7 @@
* limitations under the License.
*/
use air_test_utils::checked_call_vm;
use air_test_utils::create_avm;
use air_test_utils::echo_number_call_service;
use air_test_utils::executed_state::*;
use air_test_utils::raw_data_from_trace;
use air_test_utils::trace_from_result;
use air_test_utils::unit_call_service;
use air_test_utils::CallServiceClosure;
use air_test_utils::IValue;
use air_test_utils::NEVec;
use serde_json::json;
use air_test_utils::prelude::*;
#[test]
fn executed_trace_seq_par_call() {
@ -178,26 +167,18 @@ fn executed_trace_create_service() {
let module_bytes = json!([1, 2]);
let blueprint = json!({ "name": "blueprint", "dependencies": [module]});
let add_module_response = String::from("add_module response");
let add_blueprint_response = String::from("add_blueprint response");
let create_response = String::from("create response");
let add_module_response = "add_module response";
let add_blueprint_response = "add_blueprint response";
let create_response = "create response";
let call_service: CallServiceClosure = Box::new(move |args| -> Option<IValue> {
let builtin_service = match &args.function_args[0] {
IValue::String(str) => str,
_ => unreachable!(),
let call_service: CallServiceClosure = Box::new(move |params| -> CallServiceResult {
let response = match params.service_id.as_str() {
"add_module" => add_module_response,
"add_blueprint" => add_blueprint_response,
"create" => create_response,
_ => "unknown response",
};
let response = match builtin_service.as_str() {
"add_module" => add_module_response.clone(),
"add_blueprint" => add_blueprint_response.clone(),
"create" => create_response.clone(),
_ => String::from("unknown response"),
};
Some(IValue::Record(
NEVec::new(vec![IValue::S32(0), IValue::String(format!("\"{}\"", response))]).unwrap(),
))
CallServiceResult::ok(json!(response))
});
let mut vm = create_avm(call_service, "A");
@ -228,20 +209,12 @@ fn executed_trace_create_service() {
#[test]
fn executed_trace_par_seq_fold_call() {
let return_numbers_call_service: CallServiceClosure = Box::new(|_| -> Option<IValue> {
Some(IValue::Record(
NEVec::new(vec![
IValue::S32(0),
IValue::String(String::from(
"[\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"10\"]",
)),
])
.unwrap(),
))
let return_numbers_call_service: CallServiceClosure = Box::new(|_| -> CallServiceResult {
CallServiceResult::ok(json!(["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]))
});
let mut vm1 = create_avm(return_numbers_call_service, "some_peer_id_1");
let mut vm2 = create_avm(echo_number_call_service(), "some_peer_id_2");
let mut vm2 = create_avm(echo_call_service(), "some_peer_id_2");
let mut vm3 = create_avm(unit_call_service(), "some_peer_id_3");
let script = String::from(
@ -277,25 +250,25 @@ fn executed_trace_par_seq_fold_call() {
par(21, 1),
scalar_string_array(vec!["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]),
par(1, 18),
stream_number(1, generation),
stream_string(1.to_string(), generation),
par(1, 16),
stream_number(2, generation),
stream_string(2.to_string(), generation),
par(1, 14),
stream_number(3, generation),
stream_string(3.to_string(), generation),
par(1, 12),
stream_number(4, generation),
stream_string(4.to_string(), generation),
par(1, 10),
stream_number(5, generation),
stream_string(5.to_string(), generation),
par(1, 8),
stream_number(6, generation),
stream_string(6.to_string(), generation),
par(1, 6),
stream_number(7, generation),
stream_string(7.to_string(), generation),
par(1, 4),
stream_number(8, generation),
stream_string(8.to_string(), generation),
par(1, 2),
stream_number(9, generation),
stream_string(9.to_string(), generation),
par(1, 0),
stream_number(10, generation),
stream_string(10.to_string(), generation),
scalar_string("test"),
];
@ -305,20 +278,12 @@ fn executed_trace_par_seq_fold_call() {
#[test]
fn executed_trace_par_seq_fold_in_cycle_call() {
let return_numbers_call_service: CallServiceClosure = Box::new(|_| -> Option<IValue> {
Some(IValue::Record(
NEVec::new(vec![
IValue::S32(0),
IValue::String(String::from(
"[\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"10\"]",
)),
])
.unwrap(),
))
let return_numbers_call_service: CallServiceClosure = Box::new(|_| -> CallServiceResult {
CallServiceResult::ok(json!(["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]))
});
let mut vm1 = create_avm(return_numbers_call_service, "some_peer_id_1");
let mut vm2 = create_avm(echo_number_call_service(), "some_peer_id_2");
let mut vm2 = create_avm(echo_call_service(), "some_peer_id_2");
let mut vm3 = create_avm(unit_call_service(), "some_peer_id_3");
let script = r#"
@ -349,25 +314,25 @@ fn executed_trace_par_seq_fold_in_cycle_call() {
par(21, 1),
scalar_string_array(vec!["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]),
par(1, 18),
stream_number(1, generation),
stream_string(1.to_string(), generation),
par(1, 16),
stream_number(2, generation),
stream_string(2.to_string(), generation),
par(1, 14),
stream_number(3, generation),
stream_string(3.to_string(), generation),
par(1, 12),
stream_number(4, generation),
stream_string(4.to_string(), generation),
par(1, 10),
stream_number(5, generation),
stream_string(5.to_string(), generation),
par(1, 8),
stream_number(6, generation),
stream_string(6.to_string(), generation),
par(1, 6),
stream_number(7, generation),
stream_string(7.to_string(), generation),
par(1, 4),
stream_number(8, generation),
stream_string(8.to_string(), generation),
par(1, 2),
stream_number(9, generation),
stream_string(9.to_string(), generation),
par(1, 0),
stream_number(10, generation),
stream_string(10.to_string(), generation),
scalar_string("test"),
];
@ -379,10 +344,10 @@ fn executed_trace_par_seq_fold_in_cycle_call() {
#[test]
fn executed_trace_seq_par_seq_seq() {
let peer_id_1 = String::from("12D3KooWHk9BjDQBUqnavciRPhAYFvqKBe4ZiPPvde7vDaqgn5er");
let peer_id_2 = String::from("12D3KooWAzJcYitiZrerycVB4Wryrx22CFKdDGx7c4u31PFdfTbR");
let mut vm1 = create_avm(unit_call_service(), peer_id_1.clone());
let mut vm2 = create_avm(unit_call_service(), peer_id_2.clone());
let peer_id_1 = "12D3KooWHk9BjDQBUqnavciRPhAYFvqKBe4ZiPPvde7vDaqgn5er";
let peer_id_2 = "12D3KooWAzJcYitiZrerycVB4Wryrx22CFKdDGx7c4u31PFdfTbR";
let mut vm1 = create_avm(unit_call_service(), peer_id_1);
let mut vm2 = create_avm(unit_call_service(), peer_id_2);
let script = format!(
r#"
(seq
@ -402,11 +367,11 @@ fn executed_trace_seq_par_seq_seq() {
peer_id_1, peer_id_2, peer_id_2, peer_id_1, peer_id_2
);
let result = checked_call_vm!(vm2, "asd", script.clone(), "", "");
assert_eq!(result.next_peer_pks, vec![peer_id_1.clone()]);
let result = checked_call_vm!(vm2, "asd", &script, "", "");
assert_eq!(result.next_peer_pks, vec![peer_id_1.to_string()]);
let result = checked_call_vm!(vm1, "asd", script.clone(), "", result.data);
assert_eq!(result.next_peer_pks, vec![peer_id_2.clone()]);
let result = checked_call_vm!(vm1, "asd", &script, "", result.data);
assert_eq!(result.next_peer_pks, vec![peer_id_2.to_string()]);
let result = checked_call_vm!(vm2, "asd", script, "", result.data);

View File

@ -14,19 +14,12 @@
* limitations under the License.
*/
use air_test_utils::checked_call_vm;
use air_test_utils::create_avm;
use air_test_utils::CallServiceClosure;
use air_test_utils::IValue;
use air_test_utils::NEVec;
use air_test_utils::AVM;
use air_test_utils::prelude::*;
use std::cell::RefCell;
use std::collections::HashSet;
use std::rc::Rc;
type JValue = serde_json::Value;
fn parse_peers() -> Vec<String> {
use csv::ReaderBuilder;
@ -62,44 +55,31 @@ fn client_host_function(
let relay_id = JValue::String(relay_id);
let to_ret_value = Box::new(
move |service_name: &str, function_name: &str, arguments: Vec<&str>| -> JValue {
match (service_name, function_name, arguments.as_slice()) {
("", "load", &["relayId"]) => relay_id.clone(),
("", "load", &["knownPeers"]) => known_peers.clone(),
("", "load", &["clientId"]) => client_id.clone(),
move |service_name: &str, function_name: &str, arguments: Vec<String>| -> JValue {
if service_name != "" || function_name != "load" || arguments.len() != 1 {
return JValue::Null;
}
match arguments[0].as_str() {
"relayId" => relay_id.clone(),
"knownPeers" => known_peers.clone(),
"clientId" => client_id.clone(),
_ => JValue::Null,
}
},
);
let all_info_inner = all_info.clone();
let host_function: CallServiceClosure = Box::new(move |args| -> Option<IValue> {
let service_name = match &args.function_args[0] {
IValue::String(str) => str,
_ => unreachable!(),
};
let function_name = match &args.function_args[1] {
IValue::String(str) => str,
_ => unreachable!(),
};
let function_args = match &args.function_args[2] {
IValue::String(str) => str,
_ => unreachable!(),
};
let ret_value = match serde_json::from_str(function_args) {
Ok(args) => to_ret_value(service_name.as_str(), function_name.as_str(), args),
let host_function: CallServiceClosure = Box::new(move |params| -> CallServiceResult {
let ret_value = match serde_json::from_value(JValue::Array(params.arguments.clone())) {
Ok(args) => to_ret_value(params.service_id.as_str(), params.function_name.as_str(), args),
Err(_) => {
*all_info_inner.borrow_mut() = function_args.clone();
*all_info_inner.borrow_mut() = JValue::Array(params.arguments).to_string();
JValue::Null
}
};
Some(IValue::Record(
NEVec::new(vec![IValue::S32(0), IValue::String(ret_value.to_string())]).unwrap(),
))
CallServiceResult::ok(ret_value)
});
(host_function, all_info)
@ -133,29 +113,12 @@ fn peer_host_function(
},
);
Box::new(move |args| -> Option<IValue> {
let service_name = match &args.function_args[0] {
IValue::String(str) => str,
_ => unreachable!(),
};
Box::new(move |params| -> CallServiceResult {
let args: Vec<String> = serde_json::from_value(JValue::Array(params.arguments)).unwrap();
let t_args = args.iter().map(|s| s.as_str()).collect::<Vec<_>>();
let ret_value = to_ret_value(params.service_id.as_str(), params.function_name.as_str(), t_args);
let function_name = match &args.function_args[1] {
IValue::String(str) => str,
_ => unreachable!(),
};
let function_args = match &args.function_args[2] {
IValue::String(str) => str,
_ => unreachable!(),
};
let args: Vec<&str> = serde_json::from_str(function_args).unwrap();
let ret_value = to_ret_value(service_name.as_str(), function_name.as_str(), args);
Some(IValue::Record(
NEVec::new(vec![IValue::S32(0), IValue::String(ret_value.to_string())]).unwrap(),
))
CallServiceResult::ok(ret_value)
})
}
@ -176,7 +139,7 @@ fn create_peer_host_function(peer_id: String, known_peer_ids: Vec<String>) -> Ca
}
struct AVMState {
vm: AVM,
vm: TestRunner,
peer_id: String,
prev_result: Vec<u8>,
}
@ -186,8 +149,8 @@ fn dashboard() {
let script = include_str!("./scripts/dashboard.clj");
let known_peer_ids = parse_peers();
let client_id = String::from("client_id");
let relay_id = String::from("relay_id");
let client_id = "client_id".to_string();
let relay_id = "relay_id".to_string();
let (host_function, all_info) = client_host_function(known_peer_ids.clone(), client_id.clone(), relay_id.clone());
@ -214,20 +177,14 @@ fn dashboard() {
.collect::<Vec<_>>();
// -> client 1
let client_1_result = checked_call_vm!(client, client_id.clone(), script.clone(), "", "");
let client_1_result = checked_call_vm!(client, &client_id, script, "", "");
let next_peer_pks = into_hashset(client_1_result.next_peer_pks);
let mut all_peer_pks = into_hashset(known_peer_ids.clone());
all_peer_pks.insert(relay_id.clone());
assert_eq!(next_peer_pks, all_peer_pks);
// client 1 -> relay 1
let relay_1_result = checked_call_vm!(
relay,
client_id.clone(),
script.clone(),
client_1_result.data.clone(),
""
);
let relay_1_result = checked_call_vm!(relay, &client_id, script, client_1_result.data.clone(), "");
let next_peer_pks = into_hashset(relay_1_result.next_peer_pks.clone());
all_peer_pks.remove(&relay_id);
all_peer_pks.insert(client_id.clone());
@ -236,8 +193,8 @@ fn dashboard() {
// relay 1 -> client 2
let client_2_result = checked_call_vm!(
client,
client_id.clone(),
script.clone(),
&client_id,
script,
client_1_result.data.clone(),
relay_1_result.data.clone()
);
@ -258,7 +215,7 @@ fn dashboard() {
let known_peer_result = checked_call_vm!(
avm.vm,
client_id.clone(),
script.clone(),
script,
prev_result,
client_1_result.data.clone()
);
@ -269,7 +226,7 @@ fn dashboard() {
relay_2_result = checked_call_vm!(
relay,
client_id.clone(),
script.clone(),
script,
relay_2_result.data.clone(),
avm.prev_result.clone()
);
@ -278,7 +235,7 @@ fn dashboard() {
client_3_result = checked_call_vm!(
client,
client_id.clone(),
script.clone(),
script,
client_3_result.data.clone(),
relay_2_result.data.clone()
);
@ -293,7 +250,7 @@ fn dashboard() {
}
all_peer_pks.remove(&client_id);
all_peer_pks.insert(relay_id.clone());
all_peer_pks.insert(relay_id.to_string());
let mut relay_3_result = relay_2_result.clone();
let mut client_4_result = client_3_result.clone();
@ -301,13 +258,7 @@ fn dashboard() {
// peers 2 -> relay 3 -> client 4
for avm in known_peers.iter_mut() {
let prev_result = std::mem::replace(&mut avm.prev_result, vec![]);
let known_peer_result = checked_call_vm!(
avm.vm,
client_id.clone(),
script.clone(),
prev_result,
relay_1_result.data.clone()
);
let known_peer_result = checked_call_vm!(avm.vm, &client_id, script, prev_result, relay_1_result.data.clone());
all_peer_pks.remove(&avm.peer_id);
let next_peer_pks = into_hashset(known_peer_result.next_peer_pks.clone());
assert_eq!(next_peer_pks, all_peer_pks);
@ -318,8 +269,8 @@ fn dashboard() {
relay_3_result = checked_call_vm!(
relay,
client_id.clone(),
script.clone(),
&client_id,
script,
relay_3_result.data.clone(),
avm.prev_result.clone()
);
@ -328,8 +279,8 @@ fn dashboard() {
// client -> peers -> relay -> client
client_4_result = checked_call_vm!(
client,
client_id.clone(),
script.clone(),
&client_id,
script,
client_4_result.data.clone(),
relay_3_result.data.clone()
);
@ -355,16 +306,15 @@ fn dashboard() {
let prev_data = known_peers[j].prev_result.clone();
let data = known_peers[i].prev_result.clone();
let known_peer_i_j_result =
checked_call_vm!(known_peers[j].vm, client_id.clone(), script.clone(), prev_data, data);
let known_peer_i_j_result = checked_call_vm!(known_peers[j].vm, &client_id, script, prev_data, data);
assert_eq!(known_peer_i_j_result.next_peer_pks, vec![relay_id.clone()]);
known_peers[j].prev_result = known_peer_i_j_result.data;
relay_4_result = checked_call_vm!(
relay,
client_id.clone(),
script.clone(),
&client_id,
script,
relay_4_result.data.clone(),
known_peers[j].prev_result.clone()
);
@ -373,8 +323,8 @@ fn dashboard() {
// client -> peers -> relay -> client
client_5_result = checked_call_vm!(
client,
client_id.clone(),
script.clone(),
&client_id,
script,
client_5_result.data.clone(),
relay_4_result.data.clone()
);

View File

@ -14,14 +14,9 @@
* limitations under the License.
*/
// use pretty_assertions::assert_eq;
use serde_json::json;
use air_test_utils::*;
use air_test_utils::prelude::*;
use std::collections::HashMap;
type JValue = serde_json::Value;
#[test]
fn data_merge() {
use executed_state::*;
@ -30,10 +25,7 @@ fn data_merge() {
let vm_1_id = "A";
let vm_2_id = "B";
let mut set_variable = create_avm(
set_variable_call_service(json!([vm_1_id, vm_2_id]).to_string()),
set_variable_id,
);
let mut set_variable = create_avm(set_variable_call_service(json!([vm_1_id, vm_2_id])), set_variable_id);
let mut vm1 = create_avm(return_string_call_service(vm_1_id), vm_1_id);
let mut vm2 = create_avm(return_string_call_service(vm_2_id), vm_2_id);
@ -63,7 +55,6 @@ fn data_merge() {
)
"#;
// little hack here with init_peer_id to execute the first call from both VMs
let result_0 = checked_call_vm!(set_variable, "", script, "", "");
let result_1 = checked_call_vm!(vm1, "", script, "", result_0.data.clone());
let result_2 = checked_call_vm!(vm2, "", script, "", result_0.data);
@ -148,33 +139,15 @@ fn data_merge() {
#[test]
fn acc_merge() {
let neighborhood_call_service: CallServiceClosure = Box::new(|args| -> Option<IValue> {
let args_count = match &args.function_args[1] {
IValue::String(str) => str,
_ => unreachable!(),
};
let neighborhood_call_service: CallServiceClosure = Box::new(|params| -> CallServiceResult {
let args_count = (params.function_name.as_bytes()[0] - b'0') as usize;
let args: Vec<Vec<JValue>> = serde_json::from_value(JValue::Array(params.arguments)).expect("valid json");
assert_eq!(args[0].len(), args_count);
let args_count = (args_count.as_bytes()[0] - b'0') as usize;
let args_json = match &args.function_args[2] {
IValue::String(str) => str,
_ => unreachable!(),
};
let args: Vec<JValue> = serde_json::from_str(args_json).expect("valid json");
let args = match &args[0] {
JValue::Array(args) => args,
_ => unreachable!(),
};
assert_eq!(args.len(), args_count);
Some(IValue::Record(
NEVec::new(vec![IValue::S32(0), IValue::String(json!(args).to_string())]).unwrap(),
))
CallServiceResult::ok(json!(args))
});
let mut vm1 = create_avm(set_variable_call_service(r#""peer_id""#), "A");
let mut vm1 = create_avm(set_variable_call_service(json!("peer_id")), "A");
let mut vm2 = create_avm(neighborhood_call_service, "B");
let script = String::from(
@ -210,8 +183,8 @@ fn fold_merge() {
let local_vm_id = "local_vm";
let variables = maplit::hashmap! {
"stream_1".to_string() => json!(["peer_0", "peer_1", "peer_2", "peer_3"]).to_string(),
"stream_2".to_string() => json!(["peer_4", "peer_5", "peer_6"]).to_string(),
"stream_1".to_string() => json!(["peer_0", "peer_1", "peer_2", "peer_3"]),
"stream_2".to_string() => json!(["peer_4", "peer_5", "peer_6"]),
};
let mut set_variable_vm = create_avm(set_variables_call_service(variables), set_variable_vm_id);
@ -226,7 +199,7 @@ fn fold_merge() {
let mut local_vms_results = Vec::with_capacity(7);
for vm_id in 0..7 {
let peer_id = format!("peer_{}", vm_id);
let mut vm = create_avm(echo_string_call_service(), peer_id);
let mut vm = create_avm(echo_call_service(), peer_id);
let result = checked_call_vm!(
vm,
"",
@ -239,7 +212,7 @@ fn fold_merge() {
local_vms_results.push(result);
}
let mut local_vm = create_avm(echo_string_call_service(), local_vm_id);
let mut local_vm = create_avm(echo_call_service(), local_vm_id);
let result_1 = checked_call_vm!(local_vm, "", &script, "", local_vms_results[0].data.clone());
let result_2 = checked_call_vm!(
local_vm,

View File

@ -14,9 +14,7 @@
* limitations under the License.
*/
use air_test_utils::*;
use serde_json::json;
use air_test_utils::prelude::*;
#[test]
fn empty_array() {

View File

@ -14,15 +14,7 @@
* limitations under the License.
*/
use air_test_utils::call_vm;
use air_test_utils::checked_call_vm;
use air_test_utils::create_avm;
use air_test_utils::set_variable_call_service;
use air_test_utils::CallServiceClosure;
use air_test_utils::IValue;
use air_test_utils::NEVec;
use serde_json::json;
use air_test_utils::prelude::*;
use std::cell::RefCell;
use std::rc::Rc;
@ -38,32 +30,17 @@ struct ClosureCallArgs {
}
fn create_check_service_closure(closure_call_args: ClosureCallArgs) -> CallServiceClosure {
Box::new(move |args| -> Option<IValue> {
Box::new(move |params| -> CallServiceResult {
use std::ops::Deref;
let service_id = match &args.function_args[0] {
IValue::String(str) => str,
_ => unreachable!(),
};
*closure_call_args.service_id_var.deref().borrow_mut() = service_id.clone();
*closure_call_args.service_id_var.deref().borrow_mut() = params.service_id.clone();
*closure_call_args.function_name_var.deref().borrow_mut() = params.function_name.clone();
let function_name = match &args.function_args[1] {
IValue::String(str) => str,
_ => unreachable!(),
};
*closure_call_args.function_name_var.deref().borrow_mut() = function_name.clone();
let call_args = match &args.function_args[2] {
IValue::String(str) => str,
_ => unreachable!(),
};
let call_args: Vec<i32> = serde_json::from_str(call_args).expect("json deserialization shouldn't fail");
let call_args: Vec<i32> =
serde_json::from_value(JValue::Array(params.arguments)).expect("json deserialization shouldn't fail");
*closure_call_args.args_var.deref().borrow_mut() = call_args;
Some(IValue::Record(
NEVec::new(vec![IValue::S32(0), IValue::String(r#""""#.to_string())]).unwrap(),
))
CallServiceResult::ok(json!(""))
})
}
@ -74,7 +51,6 @@ fn flattening_scalar_arrays() {
{"peer_id" : "local_peer_id", "service_id": "local_service_id", "function_name": "local_function_name", "args": [2, 3]},
]});
let scalar_array = serde_json::to_string(&scalar_array).expect("the default serializer shouldn't fail");
let set_variable_peer_id = "set_variable";
let mut set_variable_vm = create_avm(set_variable_call_service(scalar_array), set_variable_peer_id);
@ -119,7 +95,6 @@ fn flattening_streams() {
{"peer_id" : "local_peer_id", "service_id": "local_service_id", "function_name": "local_function_name", "args": [0, 1]}
);
let stream_value = serde_json::to_string(&stream_value).expect("the default serializer shouldn't fail");
let set_variable_peer_id = "set_variable";
let mut set_variable_vm = create_avm(set_variable_call_service(stream_value), set_variable_peer_id);
@ -169,7 +144,6 @@ fn flattening_empty_values() {
{"args": []}
);
let stream_value = serde_json::to_string(&stream_value).expect("the default serializer shouldn't fail");
let set_variable_peer_id = "set_variable";
let mut set_variable_vm = create_avm(set_variable_call_service(stream_value), set_variable_peer_id);
@ -201,7 +175,6 @@ fn test_handling_non_flattening_values() {
{"peer_id" : "local_peer_id", "service_id": "local_service_id", "function_name": "local_function_name", "args": [0, 1]}
);
let stream_value = serde_json::to_string(&stream_value).expect("the default serializer shouldn't fail");
let set_variable_peer_id = "set_variable";
let mut set_variable_vm = create_avm(set_variable_call_service(stream_value), set_variable_peer_id);

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
use air_test_utils::*;
use air_test_utils::prelude::*;
#[test]
// test for github.com/fluencelabs/aquavm/issues/137

View File

@ -14,30 +14,14 @@
* limitations under the License.
*/
use air_test_utils::checked_call_vm;
use air_test_utils::create_avm;
use air_test_utils::executed_state;
use air_test_utils::trace_from_result;
use air_test_utils::unit_call_service;
use air_test_utils::CallServiceClosure;
use air_test_utils::IValue;
use air_test_utils::NEVec;
use serde_json::json;
use air_test_utils::prelude::*;
#[test]
fn join_chat() {
use std::collections::HashSet;
let members_call_service1: CallServiceClosure = Box::new(|_| -> Option<IValue> {
Some(IValue::Record(
NEVec::new(vec![
IValue::S32(0),
IValue::String(String::from(r#"[["A", "Relay1"], ["B", "Relay2"]]"#)),
])
.unwrap(),
))
});
let members_call_service1: CallServiceClosure =
Box::new(|_| -> CallServiceResult { CallServiceResult::ok(json!([["A", "Relay1"], ["B", "Relay2"]])) });
let mut relay_1 = create_avm(unit_call_service(), "Relay1");
let mut relay_2 = create_avm(unit_call_service(), "Relay2");
@ -182,11 +166,8 @@ fn join_chat() {
#[test]
fn join() {
let members_call_service1: CallServiceClosure = Box::new(|_| -> Option<IValue> {
Some(IValue::Record(
NEVec::new(vec![IValue::S32(0), IValue::String(String::from(r#"[["A"], ["B"]]"#))]).unwrap(),
))
});
let members_call_service1: CallServiceClosure =
Box::new(|_| -> CallServiceResult { CallServiceResult::ok(json!([["A"], ["B"]])) });
let mut relay_1 = create_avm(unit_call_service(), "Relay1");
let mut remote = create_avm(members_call_service1, "Remote");
@ -235,11 +216,8 @@ fn join() {
#[test]
fn init_peer_id() {
let members_call_service1: CallServiceClosure = Box::new(|_| -> Option<IValue> {
Some(IValue::Record(
NEVec::new(vec![IValue::S32(0), IValue::String(String::from(r#"[["A"], ["B"]]"#))]).unwrap(),
))
});
let members_call_service1: CallServiceClosure =
Box::new(|_| -> CallServiceResult { CallServiceResult::ok(json!([["A"], ["B"]])) });
let initiator_peer_id = String::from("initiator");

View File

@ -14,14 +14,7 @@
* limitations under the License.
*/
use air_test_utils::checked_call_vm;
use air_test_utils::create_avm;
use air_test_utils::set_variables_call_service;
use air_test_utils::trace_from_result;
use air_test_utils::unit_call_service;
use air_test_utils::{call_vm, echo_call_service};
use serde_json::json;
use air_test_utils::prelude::*;
#[test]
fn dont_wait_on_json_path() {
@ -31,11 +24,11 @@ fn dont_wait_on_json_path() {
"ret_code": 0,
});
let msg = String::from(r#""some message""#);
let msg = json!("some message");
let variables = maplit::hashmap!(
"msg".to_string() => msg,
"status".to_string() => status.to_string(),
"status".to_string() => status,
);
let set_variables_call_service = set_variables_call_service(variables);
@ -148,8 +141,8 @@ fn dont_wait_on_json_path_on_scalars() {
});
let variables = maplit::hashmap!(
"array".to_string() => array.to_string(),
"object".to_string() => object.to_string(),
"array".to_string() => array,
"object".to_string() => object,
);
let set_variables_call_service = set_variables_call_service(variables);

View File

@ -14,18 +14,15 @@
* limitations under the License.
*/
use air_test_utils::call_vm;
use air_test_utils::checked_call_vm;
use air_test_utils::create_avm;
use air_test_utils::echo_string_call_service;
use air_test_utils::prelude::*;
#[test]
fn json_path_not_allowed_for_non_objects_and_arrays() {
let set_variable_peer_id = "set_variable";
let mut set_variable_vm = create_avm(echo_string_call_service(), set_variable_peer_id);
let mut set_variable_vm = create_avm(echo_call_service(), set_variable_peer_id);
let local_peer_id = "local_peer_id";
let mut local_vm = create_avm(echo_string_call_service(), local_peer_id);
let mut local_vm = create_avm(echo_call_service(), local_peer_id);
let script = format!(
r#"

View File

@ -16,13 +16,7 @@
use air::LastError;
use air::SecurityTetraplet;
use air_test_utils::checked_call_vm;
use air_test_utils::create_avm;
use air_test_utils::fallible_call_service;
use air_test_utils::unit_call_service;
use air_test_utils::CallServiceClosure;
use air_test_utils::IValue;
use air_test_utils::NEVec;
use air_test_utils::prelude::*;
use std::cell::RefCell;
use std::rc::Rc;
@ -33,29 +27,15 @@ fn create_check_service_closure(
args_to_check: ArgToCheck<LastError>,
tetraplets_to_check: ArgToCheck<Vec<Vec<SecurityTetraplet>>>,
) -> CallServiceClosure {
Box::new(move |args| -> Option<IValue> {
let call_args = match &args.function_args[2] {
IValue::String(str) => str,
_ => unreachable!(),
};
Box::new(move |params| -> CallServiceResult {
let mut call_args: Vec<LastError> =
serde_json::from_str(call_args).expect("json deserialization shouldn't fail");
let tetraplets = match &args.function_args[3] {
IValue::String(str) => str,
_ => unreachable!(),
};
let de_tetraplets: Vec<Vec<SecurityTetraplet>> =
serde_json::from_str(tetraplets).expect("json deserialization shouldn't fail");
serde_json::from_value(JValue::Array(params.arguments)).expect("json deserialization shouldn't fail");
let result = json!(params.tetraplets);
*args_to_check.borrow_mut() = Some(call_args.remove(0));
*tetraplets_to_check.borrow_mut() = Some(de_tetraplets);
*tetraplets_to_check.borrow_mut() = Some(params.tetraplets);
Some(IValue::Record(
NEVec::new(vec![IValue::S32(0), IValue::String(tetraplets.clone())]).unwrap(),
))
CallServiceResult::ok(result)
})
}
@ -93,7 +73,7 @@ fn last_error_tetraplets() {
assert_eq!(
actual_value.msg,
r#"Local service error, ret_code is 1, error message is 'error'"#
r#"Local service error, ret_code is 1, error message is '"error"'"#
);
let triplet = (*tetraplets.borrow()).as_ref().unwrap()[0][0].triplet.clone();

View File

@ -14,17 +14,15 @@
* limitations under the License.
*/
use air_test_utils::*;
use serde_json::json;
use air_test_utils::prelude::*;
#[test]
fn network_explore() {
let relay_id = "relay_id";
let client_id = "client_id";
let set_variables_state = maplit::hashmap!(
"relay".to_string() => json!(relay_id).to_string(),
"client".to_string() => json!(client_id).to_string(),
"relay".to_string() => json!(relay_id),
"client".to_string() => json!(client_id),
);
let client_call_service = set_variables_call_service(set_variables_state);
@ -34,20 +32,16 @@ fn network_explore() {
let client_2_id = "client_2_id";
let client_3_id = "client_3_id";
let relay_call_service =
air_test_utils::set_variable_call_service(json!([client_1_id, client_2_id, client_3_id, relay_id]).to_string());
let relay_call_service = set_variable_call_service(json!([client_1_id, client_2_id, client_3_id, relay_id]));
let mut relay = create_avm(relay_call_service, relay_id);
let client_1_call_service =
air_test_utils::set_variable_call_service(json!([client_1_id, client_3_id, relay_id, client_2_id]).to_string());
let client_1_call_service = set_variable_call_service(json!([client_1_id, client_3_id, relay_id, client_2_id]));
let mut client_1 = create_avm(client_1_call_service, client_1_id);
let client_2_call_service =
air_test_utils::set_variable_call_service(json!([relay_id, client_3_id, client_1_id, client_2_id]).to_string());
let client_2_call_service = set_variable_call_service(json!([relay_id, client_3_id, client_1_id, client_2_id]));
let mut client_2 = create_avm(client_2_call_service, client_2_id);
let client_3_call_service =
air_test_utils::set_variable_call_service(json!([relay_id, client_3_id, client_1_id, client_2_id]).to_string());
let client_3_call_service = set_variable_call_service(json!([relay_id, client_3_id, client_1_id, client_2_id]));
let mut client_3 = create_avm(client_3_call_service, client_3_id);
let script = include_str!("./scripts/network_explore.clj");

View File

@ -16,12 +16,7 @@
use air::ResolvedTriplet;
use air::SecurityTetraplet;
use air_test_utils::checked_call_vm;
use air_test_utils::create_avm;
use air_test_utils::executed_state;
use air_test_utils::CallServiceClosure;
use air_test_utils::IValue;
use air_test_utils::NEVec;
use air_test_utils::prelude::*;
use std::cell::RefCell;
use std::rc::Rc;
@ -32,19 +27,11 @@ fn arg_host_function() -> (CallServiceClosure, Rc<RefCell<ArgTetraplets>>) {
let arg_tetraplets = Rc::new(RefCell::new(ArgTetraplets::new()));
let arg_tetraplets_inner = arg_tetraplets.clone();
let host_function: CallServiceClosure = Box::new(move |args| -> Option<IValue> {
let tetraplets = match &args.function_args[3] {
IValue::String(str) => str,
_ => unreachable!(),
};
let host_function: CallServiceClosure = Box::new(move |params| -> CallServiceResult {
let result = json!(params.tetraplets);
*arg_tetraplets_inner.borrow_mut() = params.tetraplets;
let de_tetraplets: ArgTetraplets =
serde_json::from_str(tetraplets).expect("json deserialization shouldn't fail");
*arg_tetraplets_inner.borrow_mut() = de_tetraplets;
Some(IValue::Record(
NEVec::new(vec![IValue::S32(0), IValue::String(tetraplets.clone())]).unwrap(),
))
CallServiceResult::ok(result)
});
(host_function, arg_tetraplets)
@ -52,16 +39,8 @@ fn arg_host_function() -> (CallServiceClosure, Rc<RefCell<ArgTetraplets>>) {
#[test]
fn simple_fold() {
let return_numbers_call_service: CallServiceClosure = Box::new(|_| -> Option<IValue> {
Some(IValue::Record(
NEVec::new(vec![
IValue::S32(0),
IValue::String(String::from(
"[\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"10\"]",
)),
])
.unwrap(),
))
let return_numbers_call_service: CallServiceClosure = Box::new(|_| -> CallServiceResult {
CallServiceResult::ok(json!(["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]))
});
let set_variable_vm_peer_id = String::from("some_peer_id_1");
@ -127,16 +106,8 @@ fn simple_fold() {
#[test]
fn fold_json_path() {
let return_numbers_call_service: CallServiceClosure = Box::new(|_| -> Option<IValue> {
Some(IValue::Record(
NEVec::new(vec![
IValue::S32(0),
IValue::String(String::from(
"{\"arg\": [\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"10\"]}",
)),
])
.unwrap(),
))
let return_numbers_call_service: CallServiceClosure = Box::new(|_| -> CallServiceResult {
CallServiceResult::ok(json!({"args": ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]}))
});
let set_variable_vm_peer_id = String::from("some_peer_id_1");
@ -152,7 +123,7 @@ fn fold_json_path() {
r#"
(seq
(call "{}" ("{}" "{}") [] IterableResultPeer1)
(fold IterableResultPeer1.$.arg i
(fold IterableResultPeer1.$.args i
(par
(call "{}" ("local_service_id" "local_fn_name") [i "some_text_literal"] $acc)
(next i)
@ -173,7 +144,7 @@ fn fold_json_path() {
};
let first_arg_tetraplet = SecurityTetraplet {
triplet: first_arg_triplet,
json_path: String::from("$.arg"),
json_path: String::from("$.args"),
};
let second_arg_triplet = ResolvedTriplet {
@ -248,44 +219,23 @@ fn tetraplet_with_wasm_modules() {
let services_inner = services.clone();
const ADMIN_PEER_PK: &str = "12D3KooWEXNUbCXooUwHrHBbrmjsrpHXoEphPwbjQXEGyzbqKnE1";
let host_func: CallServiceClosure = Box::new(move |args| -> Option<IValue> {
let args = &args.function_args;
let service_id = match &args[0] {
IValue::String(str) => str,
_ => unreachable!(),
};
let function_name = match &args[1] {
IValue::String(str) => str,
_ => unreachable!(),
};
let service_args = match &args[2] {
IValue::String(str) => str,
_ => unreachable!(),
};
let tetraplets = match &args[3] {
IValue::String(str) => str,
_ => unreachable!(),
};
let tetraplets: Vec<Vec<SDKTetraplet>> = serde_json::from_str(tetraplets).unwrap();
let host_func: CallServiceClosure = Box::new(move |params| -> CallServiceResult {
let tetraplets = serde_json::to_vec(&params.tetraplets).expect("default serializer shouldn't fail");
let tetraplets: Vec<Vec<SDKTetraplet>> =
serde_json::from_slice(&tetraplets).expect("default deserializer shouldn't fail");
let mut call_parameters = CallParameters::default();
call_parameters.init_peer_id = ADMIN_PEER_PK.to_string();
call_parameters.tetraplets = tetraplets;
let service_args = serde_json::from_str(service_args).unwrap();
let mut service = services_inner.borrow_mut();
let service: &mut AppService = service.get_mut(service_id.as_str()).unwrap();
let service: &mut AppService = service.get_mut(params.service_id.as_str()).unwrap();
let result = service.call(function_name, service_args, call_parameters).unwrap();
let result = service
.call(params.function_name, JValue::Array(params.arguments), call_parameters)
.unwrap();
Some(IValue::Record(
NEVec::new(vec![IValue::S32(0), IValue::String(result.to_string())]).unwrap(),
))
CallServiceResult::ok(result)
});
let local_peer_id = "local_peer_id";

View File

@ -14,29 +14,19 @@
* limitations under the License.
*/
use air_test_utils::*;
use serde_json::json;
use serde_json::Value as JValue;
use air_test_utils::prelude::*;
#[test]
fn empty_stream() {
fn arg_type_check_closure() -> CallServiceClosure {
Box::new(move |args| -> Option<IValue> {
let call_args = match &args.function_args[2] {
IValue::String(str) => str,
_ => unreachable!(),
};
Box::new(move |params| -> CallServiceResult {
let actual_call_args: Vec<Vec<JValue>> =
serde_json::from_str(call_args).expect("json deserialization shouldn't fail");
serde_json::from_value(JValue::Array(params.arguments)).expect("json deserialization shouldn't fail");
let expected_call_args: Vec<Vec<JValue>> = vec![vec![]];
assert_eq!(actual_call_args, expected_call_args);
Some(IValue::Record(
NEVec::new(vec![IValue::S32(0), IValue::String(r#""""#.to_string())]).unwrap(),
))
CallServiceResult::ok(json!(""))
})
}
@ -60,9 +50,9 @@ fn stream_merging_v0() {
let executor_id = "stream_executor";
let mut initiator = create_avm(unit_call_service(), initiator_id);
let mut setter_1 = create_avm(set_variable_call_service(json!("1").to_string()), setter_1_id);
let mut setter_2 = create_avm(set_variable_call_service(json!("2").to_string()), setter_2_id);
let mut setter_3 = create_avm(set_variable_call_service(json!("3").to_string()), setter_3_id);
let mut setter_1 = create_avm(set_variable_call_service(json!("1")), setter_1_id);
let mut setter_2 = create_avm(set_variable_call_service(json!("2")), setter_2_id);
let mut setter_3 = create_avm(set_variable_call_service(json!("3")), setter_3_id);
let mut executor = create_avm(unit_call_service(), executor_id);
let script = format!(
@ -200,9 +190,9 @@ fn stream_merging_v1() {
let executor_id = "stream_executor";
let mut initiator = create_avm(unit_call_service(), initiator_id);
let mut setter_1 = create_avm(set_variable_call_service(json!("1").to_string()), setter_1_id);
let mut setter_2 = create_avm(set_variable_call_service(json!("2").to_string()), setter_2_id);
let mut setter_3 = create_avm(set_variable_call_service(json!("3").to_string()), setter_3_id);
let mut setter_1 = create_avm(set_variable_call_service(json!("1")), setter_1_id);
let mut setter_2 = create_avm(set_variable_call_service(json!("2")), setter_2_id);
let mut setter_3 = create_avm(set_variable_call_service(json!("3")), setter_3_id);
let mut executor = create_avm(unit_call_service(), executor_id);
let script = format!(
@ -340,9 +330,9 @@ fn stream_merging_v2() {
let executor_id = "stream_executor";
let mut initiator = create_avm(unit_call_service(), initiator_id);
let mut setter_1 = create_avm(set_variable_call_service(json!("1").to_string()), setter_1_id);
let mut setter_2 = create_avm(set_variable_call_service(json!("2").to_string()), setter_2_id);
let mut setter_3 = create_avm(set_variable_call_service(json!("3").to_string()), setter_3_id);
let mut setter_1 = create_avm(set_variable_call_service(json!("1")), setter_1_id);
let mut setter_2 = create_avm(set_variable_call_service(json!("2")), setter_2_id);
let mut setter_3 = create_avm(set_variable_call_service(json!("3")), setter_3_id);
let mut executor = create_avm(unit_call_service(), executor_id);
let script = format!(

View File

@ -14,10 +14,7 @@
* limitations under the License.
*/
use air_test_utils::*;
use pretty_assertions::assert_eq;
use serde_json::json;
use air_test_utils::prelude::*;
#[test]
fn par_early_exit() {
@ -27,8 +24,8 @@ fn par_early_exit() {
let setter_3_id = "setter_3";
let mut init = create_avm(unit_call_service(), init_peer_id);
let mut setter_1 = create_avm(set_variable_call_service(json!("1").to_string()), setter_1_id);
let mut setter_2 = create_avm(set_variable_call_service(json!("2").to_string()), setter_2_id);
let mut setter_1 = create_avm(set_variable_call_service(json!("1")), setter_1_id);
let mut setter_2 = create_avm(set_variable_call_service(json!("2")), setter_2_id);
let mut setter_3 = create_avm(fallible_call_service("error"), setter_3_id);
let script = format!(
@ -53,11 +50,11 @@ fn par_early_exit() {
executed_state::request_sent_by(init_peer_id),
executed_state::request_sent_by(init_peer_id),
executed_state::request_sent_by(init_peer_id),
executed_state::stream_string("res", 0),
executed_state::service_failed(1, "error"),
executed_state::stream_string("res", 0),
executed_state::service_failed(1, "error"),
executed_state::service_failed(1, "error"),
executed_state::stream_string("test", 0),
executed_state::service_failed(1, r#""error""#),
executed_state::stream_string("test", 0),
executed_state::service_failed(1, r#""error""#),
executed_state::service_failed(1, r#""error""#),
executed_state::request_sent_by(setter_3_id),
];
assert_eq!(actual_trace_1, expected_trace);
@ -97,11 +94,11 @@ fn par_early_exit() {
executed_state::stream_string("1", 1),
executed_state::stream_string("2", 2),
executed_state::stream_string("1", 1),
executed_state::stream_string("res", 0),
executed_state::service_failed(1, "error"),
executed_state::stream_string("res", 0),
executed_state::service_failed(1, "error"),
executed_state::service_failed(1, "error"),
executed_state::stream_string("test", 0),
executed_state::service_failed(1, r#""error""#),
executed_state::stream_string("test", 0),
executed_state::service_failed(1, r#""error""#),
executed_state::service_failed(1, r#""error""#),
executed_state::request_sent_by("setter_3"),
];
assert_eq!(actual_trace_2, expected_trace);
@ -117,11 +114,11 @@ fn par_early_exit() {
executed_state::stream_string("1", 0),
executed_state::stream_string("2", 0),
executed_state::stream_string("1", 0),
executed_state::stream_string("res", 0),
executed_state::service_failed(1, "error"),
executed_state::stream_string("res", 0),
executed_state::service_failed(1, "error"),
executed_state::service_failed(1, "error"),
executed_state::stream_string("test", 0),
executed_state::service_failed(1, r#""error""#),
executed_state::stream_string("test", 0),
executed_state::service_failed(1, r#""error""#),
executed_state::service_failed(1, r#""error""#),
executed_state::scalar_string("test"),
];
assert_eq!(actual_trace_3, expected_trace);
@ -137,8 +134,8 @@ fn par_early_exit() {
executed_state::request_sent_by(init_peer_id),
executed_state::request_sent_by(init_peer_id),
executed_state::stream_string("non_exist_value", 0),
executed_state::stream_string("res", 0),
executed_state::service_failed(1, "error"),
executed_state::stream_string("test", 0),
executed_state::service_failed(1, r#""error""#),
executed_state::request_sent_by(setter_3_id),
];
let setter_3_malicious_data = raw_data_from_trace(setter_3_malicious_trace);
@ -151,7 +148,7 @@ fn par_early_exit() {
}
#[test]
fn fold_early_exit__() {
fn fold_early_exit() {
let variables_setter_id = "set_variable_id";
let stream_setter_id = "stream_setter_id";
let fold_executor_id = "fold_executor_id";
@ -160,14 +157,14 @@ fn fold_early_exit__() {
let last_peer_checker_id = "last_peer_checker_id";
let variables = maplit::hashmap!(
"stream_1".to_string() => json!(["a1", "a2"]).to_string(),
"stream_2".to_string() => json!(["b1", "b2"]).to_string(),
"stream_3".to_string() => json!(["c1", "c2"]).to_string(),
"stream_4".to_string() => json!(["d1", "d2"]).to_string(),
"stream_1".to_string() => json!(["a1", "a2"]),
"stream_2".to_string() => json!(["b1", "b2"]),
"stream_3".to_string() => json!(["c1", "c2"]),
"stream_4".to_string() => json!(["d1", "d2"]),
);
let mut variables_setter = create_avm(set_variables_call_service(variables), variables_setter_id);
let mut stream_setter = create_avm(echo_string_call_service(), stream_setter_id);
let mut stream_setter = create_avm(echo_call_service(), stream_setter_id);
let mut fold_executor = create_avm(unit_call_service(), fold_executor_id);
let mut error_trigger = create_avm(fallible_call_service("error"), error_trigger_id);
let mut last_error_receiver = create_avm(unit_call_service(), last_error_receiver_id);
@ -233,7 +230,7 @@ fn fold_early_exit__() {
]),
executed_state::scalar_string(test_value),
executed_state::scalar_string(test_value),
executed_state::service_failed(1, "error"),
executed_state::service_failed(1, r#""error""#),
executed_state::scalar_string(test_value),
executed_state::scalar_string(test_value),
];
@ -251,14 +248,14 @@ fn fold_par_early_exit() {
let last_peer_checker_id = "last_peer_checker_id";
let variables = maplit::hashmap!(
"stream_1".to_string() => json!(["a1", "a2"]).to_string(),
"stream_2".to_string() => json!(["b1", "b2"]).to_string(),
"stream_3".to_string() => json!(["c1", "c2"]).to_string(),
"stream_4".to_string() => json!(["d1", "d2"]).to_string(),
"stream_1".to_string() => json!(["a1", "a2"]),
"stream_2".to_string() => json!(["b1", "b2"]),
"stream_3".to_string() => json!(["c1", "c2"]),
"stream_4".to_string() => json!(["d1", "d2"]),
);
let mut variables_setter = create_avm(set_variables_call_service(variables), variables_setter_id);
let mut stream_setter = create_avm(echo_string_call_service(), stream_setter_id);
let mut stream_setter = create_avm(echo_call_service(), stream_setter_id);
let mut fold_executor = create_avm(unit_call_service(), fold_executor_id);
let mut error_trigger = create_avm(fallible_call_service("error"), error_trigger_id);
let mut last_error_receiver = create_avm(unit_call_service(), last_error_receiver_id);
@ -331,7 +328,7 @@ fn fold_par_early_exit() {
executed_state::scalar_string(test_value),
executed_state::par(1, 0),
executed_state::scalar_string(test_value),
executed_state::service_failed(1, "error"),
executed_state::service_failed(1, r#""error""#),
executed_state::par(15, 0),
executed_state::par(13, 1),
executed_state::fold(vec![

View File

@ -10,16 +10,3 @@ module.exports = `"${base64string}`""
$data | Out-File "./src/wasm.js"
$__wbg_callserviceimpl = Get-Content wasm/air_interpreter_client_bg.js | Select-String -Pattern __wbg_callserviceimpl\w+
$__wbg_getcurrentpeeridimpl = Get-Content wasm/air_interpreter_client_bg.js | Select-String -Pattern __wbg_getcurrentpeeridimpl_\w+
$__wbg_callserviceimpl = $__wbg_callserviceimpl.matches[0].value
$__wbg_getcurrentpeeridimpl = $__wbg_getcurrentpeeridimpl.matches[0].value
$data = "// auto-generated
export const __wbg_callserviceimpl = '${__wbg_callserviceimpl}';
export const __wbg_getcurrentpeeridimpl = '${__wbg_getcurrentpeeridimpl}';"
$data | Out-File "./src/importObject.ts"

View File

@ -22,13 +22,3 @@ cat << EOF > ./src/wasm.js
module.exports = "$BASE64";
EOF
callserviceimpl=$(cat wasm/air_interpreter_client_bg.js | grep -o '__wbg_callserviceimpl_\w*')
getcurrentpeeridimpl=$(cat wasm/air_interpreter_client_bg.js | grep -o '__wbg_getcurrentpeeridimpl_\w*')
cat << EOF > ./src/importObject.ts
// auto-generated
export const __wbg_callserviceimpl = "$callserviceimpl";
export const __wbg_getcurrentpeeridimpl = "$getcurrentpeeridimpl";
EOF

View File

@ -4413,9 +4413,9 @@
}
},
"typescript": {
"version": "3.9.9",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.9.tgz",
"integrity": "sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==",
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz",
"integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==",
"dev": true
},
"universalify": {

View File

@ -23,6 +23,6 @@
"@types/node": "^14.0.0",
"jest": "^27.2.4",
"ts-jest": "^27.0.5",
"typescript": "^3.9.5"
"typescript": "^4.0.0"
}
}

View File

@ -1,28 +1,21 @@
import { AirInterpreter, ParticleHandler } from '..';
import { AirInterpreter } from '..';
const vmPeerId = '12D3KooWNzutuy8WHXDKFqFsATvCR6j9cj2FijYbnd47geRKaQZS';
const createTestIntepreter = async (handler: ParticleHandler) => {
return AirInterpreter.create(handler, vmPeerId, 'trace', (level, message) => {
const createTestInterpreter = async () => {
return AirInterpreter.create('off', (level, message) => {
console.log(`level: ${level}, message=${message}`);
});
};
const testInvoke = (interpreter, script, prevData, data): string => {
prevData = Buffer.from(prevData);
data = Buffer.from(data);
return interpreter.invoke(vmPeerId, script, prevData, data);
const b = (s: string) => {
return Buffer.from(s);
};
describe('Tests', () => {
it('should work', async () => {
// arrange
const i = await createTestIntepreter(() => {
return {
ret_code: 0,
result: '{}',
};
});
const i = await createTestInterpreter();
const s = `(seq
(par
@ -33,9 +26,11 @@ describe('Tests', () => {
)`;
// act
const res = testInvoke(i, s, [], []);
const params = { initPeerId: vmPeerId, currentPeerId: vmPeerId };
const res = i.invoke(s, b(''), b(''), params, []);
// assert
console.log(res);
expect(res).not.toBeUndefined();
});
});

View File

@ -15,19 +15,37 @@
*/
import { toByteArray } from 'base64-js';
import { return_current_peer_id, return_call_service_result, getStringFromWasm0, free, invoke, ast } from './wrapper';
import { getStringFromWasm0, invoke } from './wrapper';
import wasmBs64 from './wasm';
import { __wbg_callserviceimpl, __wbg_getcurrentpeeridimpl } from './importObject';
export type LogLevel = 'info' | 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'off';
export type LogFunction = (level: LogLevel, message: string) => void;
export interface CallServiceResult {
ret_code: number;
retCode: number;
result: string;
}
export interface CallRequest {
serviceId: string;
functionName: string;
arguments: any[];
tetraplets: SecurityTetraplet[][];
}
export type CallRequestsArray = Array<[key: number, callRequest: CallRequest]>;
export type CallResultsArray = Array<[key: number, callServiceResult: CallServiceResult]>;
export interface InterpreterResult {
retCode: number;
errorMessage: string;
data: Uint8Array;
nextPeerPks: Array<string>;
callRequests: CallRequestsArray;
}
export interface ResolvedTriplet {
peer_pk: string;
service_id: string;
@ -38,13 +56,6 @@ export interface SecurityTetraplet extends ResolvedTriplet {
json_path: string;
}
export type ParticleHandler = (
serviceId: string,
fnName: string,
args: any[],
tetraplets: SecurityTetraplet[][],
) => CallServiceResult;
type Exports = any;
type Instance = any;
type ExportValue = any;
@ -54,13 +65,6 @@ type LogImport = {
};
type ImportObject = {
'./air_interpreter_client_bg.js': {
// fn call_service_impl(service_id: String, fn_name: String, args: String, security_tetraplets: String) -> String;
// prettier-ignore
[__wbg_callserviceimpl]: (arg0: any, arg1: any, arg2: any, arg3: any, arg4: any, arg5: any, arg6: any, arg7: any, arg8: any, ) => void;
[__wbg_getcurrentpeeridimpl]: (arg0: any) => void;
__wbindgen_throw: (arg: any) => void;
};
host: LogImport;
};
@ -143,54 +147,8 @@ function log_import(cfg: HostImportsConfig, logFunction: LogFunction): LogImport
}
/// Returns import object that describes host functions called by AIR interpreter
function newImportObject(
particleHandler: ParticleHandler,
cfg: HostImportsConfig,
peerId: string,
logFunction: LogFunction,
): ImportObject {
function newImportObject(cfg: HostImportsConfig, logFunction: LogFunction): ImportObject {
return {
// __wbg_callserviceimpl_c0ca292e3c8c0c97 this is a function generated by bindgen. Could be changed.
// If so, an error with a new name will be occurred after wasm initialization.
'./air_interpreter_client_bg.js': {
// prettier-ignore
[__wbg_callserviceimpl]: (arg0: any, arg1: any, arg2: any, arg3: any, arg4: any, arg5: any, arg6: any, arg7: any, arg8: any) => {
let wasm = cfg.exports;
let serviceId = getStringFromWasm0(wasm, arg1, arg2);
let fnName = getStringFromWasm0(wasm, arg3, arg4);
let args = getStringFromWasm0(wasm, arg5, arg6);
let tetraplets = getStringFromWasm0(wasm, arg7, arg8);
let argsObject;
let tetrapletsObject: SecurityTetraplet[][];
let serviceResult;
try {
argsObject = JSON.parse(args);
if (!Array.isArray(argsObject)) {
throw new Error('args is not an array');
}
tetrapletsObject = JSON.parse(tetraplets);
serviceResult = particleHandler(serviceId, fnName, argsObject, tetrapletsObject);
} catch (err) {
logFunction('error', 'Cannot parse arguments: ' + JSON.stringify(err));
serviceResult = {
result: JSON.stringify('Cannot parse arguments: ' + JSON.stringify(err)),
ret_code: 1,
};
}
let resultStr = JSON.stringify(serviceResult);
return_call_service_result(wasm, resultStr, arg0);
},
[__wbg_getcurrentpeeridimpl]: (arg0: any) => {
let wasm = cfg.exports;
return_current_peer_id(wasm, peerId, arg0);
},
__wbindgen_throw: (arg: any) => {
throw new Error(`wbindgen throws: ${JSON.stringify(arg)}`);
},
},
host: log_import(cfg, logFunction),
};
}
@ -203,14 +161,9 @@ export class AirInterpreter {
this.wasmWrapper = wasmWrapper;
}
static async create(
particleHandler: ParticleHandler,
peerId: string,
logLevel: LogLevel,
logFunction: LogFunction,
) {
static async create(logLevel: LogLevel, logFunction: LogFunction) {
const cfg = new HostImportsConfig((cfg) => {
return newImportObject(particleHandler, cfg, peerId, logFunction);
return newImportObject(cfg, logFunction);
});
const instance = await interpreterInstance(cfg, logFunction);
@ -219,11 +172,90 @@ export class AirInterpreter {
return res;
}
invoke(init_peer_id: string, script: string, prev_data: Uint8Array, data: Uint8Array): string {
return invoke(this.wasmWrapper.exports, init_peer_id, script, prev_data, data, this.logLevel);
invoke(
air: string,
prevData: Uint8Array,
data: Uint8Array,
params: { initPeerId: string; currentPeerId: string },
callResults: CallResultsArray,
): InterpreterResult {
const callResultsToPass: any = {};
for (let [k, v] of callResults) {
callResultsToPass[k] = {
ret_code: v.retCode,
result: v.result,
};
}
parseAir(script: string): string {
return ast(this.wasmWrapper.exports, script);
const paramsToPass = Buffer.from(
JSON.stringify({
init_peer_id: params.initPeerId,
current_peer_id: params.currentPeerId,
}),
);
const rawResult = invoke(
// force new line
this.wasmWrapper.exports,
air,
prevData,
data,
paramsToPass,
Buffer.from(JSON.stringify(callResultsToPass)),
this.logLevel,
);
let result: any;
try {
result = JSON.parse(rawResult);
} catch (ex) {}
const callRequestsStr = new TextDecoder().decode(Buffer.from(result.call_requests));
let parsedCallRequests;
try {
if (callRequestsStr.length === 0) {
parsedCallRequests = {};
} else {
parsedCallRequests = JSON.parse(callRequestsStr);
}
} catch (e) {
throw "Couldn't parse call requests: " + e + '. Original string is: ' + callRequestsStr;
}
let resultCallRequests: Array<[key: number, callRequest: CallRequest]> = [];
for (const k in parsedCallRequests) {
const v = parsedCallRequests[k];
let arguments_;
let tetraplets;
try {
arguments_ = JSON.parse(v.arguments);
} catch (e) {
throw "Couldn't parse arguments: " + e + '. Original string is: ' + arguments_;
}
try {
tetraplets = JSON.parse(v.tetraplets);
} catch (e) {
throw "Couldn't parse tetraplets: " + e + '. Original string is: ' + tetraplets;
}
resultCallRequests.push([
k as any,
{
serviceId: v.service_id,
functionName: v.function_name,
arguments: arguments_,
tetraplets: tetraplets,
},
]);
}
return {
retCode: result.ret_code,
errorMessage: result.error_message,
data: result.data,
nextPeerPks: result.next_peer_pks,
callRequests: resultCallRequests,
};
}
}

View File

@ -20,7 +20,9 @@
*
*/
function main(wasm) {
/**
*/
export function main(wasm) {
wasm.main();
}
@ -75,6 +77,7 @@ function passStringToWasm0(wasm, arg, malloc, realloc) {
if (code > 0x7f) break;
mem[ptr + offset] = code;
}
if (offset !== len) {
if (offset !== 0) {
arg = arg.slice(offset);
@ -87,7 +90,13 @@ function passStringToWasm0(wasm, arg, malloc, realloc) {
}
WASM_VECTOR_LEN = offset;
return ptr;
}
function passArray8ToWasm0(wasm, arg, malloc) {
const ptr = malloc(arg.length * 1);
getUint8Memory0(wasm).set(arg, ptr / 1);
WASM_VECTOR_LEN = arg.length;
return ptr;
}
@ -103,39 +112,36 @@ const lTextDecoder = typeof TextDecoder === 'undefined' ? module.require('util')
let cachedTextDecoder = new lTextDecoder('utf-8', { ignoreBOM: true, fatal: true });
cachedTextDecoder.decode();
export function getStringFromWasm0(wasm, ptr, len) {
return cachedTextDecoder.decode(getUint8Memory0(wasm).subarray(ptr, ptr + len));
}
function passArray8ToWasm0(wasm, arg, malloc) {
const ptr = malloc(arg.length * 1);
getUint8Memory0(wasm).set(arg, ptr / 1);
WASM_VECTOR_LEN = arg.length;
return ptr;
}
/**
* @param {any} wasm
* @param {string} init_peer_id
* @param {} wasm wrapper
* @param {string} air
* @param {string} prev_data
* @param {string} data
* @param {Uint8Array} prev_data
* @param {Uint8Array} data
* @param {Uint8Array} params
* @param {Uint8Array} call_results
* @param {string} log_level
* @returns {string}
*/
export function invoke(wasm, init_peer_id, air, prev_data, data, log_level) {
export function invoke(wasm, air, prev_data, data, params, call_results, log_level) {
try {
var ptr0 = passStringToWasm0(wasm, init_peer_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var ptr0 = passStringToWasm0(wasm, air, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len0 = WASM_VECTOR_LEN;
var ptr1 = passStringToWasm0(wasm, air, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var ptr1 = passArray8ToWasm0(wasm, prev_data, wasm.__wbindgen_malloc);
var len1 = WASM_VECTOR_LEN;
var ptr2 = passArray8ToWasm0(wasm, prev_data, wasm.__wbindgen_malloc);
var ptr2 = passArray8ToWasm0(wasm, data, wasm.__wbindgen_malloc);
var len2 = WASM_VECTOR_LEN;
var ptr3 = passArray8ToWasm0(wasm, data, wasm.__wbindgen_malloc);
var ptr3 = passArray8ToWasm0(wasm, params, wasm.__wbindgen_malloc);
var len3 = WASM_VECTOR_LEN;
var ptr4 = passStringToWasm0(wasm, log_level, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var ptr4 = passArray8ToWasm0(wasm, call_results, wasm.__wbindgen_malloc);
var len4 = WASM_VECTOR_LEN;
wasm.invoke(8, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4);
var ptr5 = passStringToWasm0(wasm, log_level, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len5 = WASM_VECTOR_LEN;
wasm.invoke(8, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4, ptr5, len5);
var r0 = getInt32Memory0(wasm)[8 / 4 + 0];
var r1 = getInt32Memory0(wasm)[8 / 4 + 1];
return getStringFromWasm0(wasm, r0, r1);
@ -143,34 +149,3 @@ export function invoke(wasm, init_peer_id, air, prev_data, data, log_level) {
wasm.__wbindgen_free(r0, r1);
}
}
export function ast(wasm, script) {
try {
var ptr0 = passStringToWasm0(wasm, script, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len0 = WASM_VECTOR_LEN;
wasm.ast(8, ptr0, len0);
var r0 = getInt32Memory0(wasm)[8 / 4 + 0];
var r1 = getInt32Memory0(wasm)[8 / 4 + 1];
return getStringFromWasm0(wasm, r0, r1);
} finally {
wasm.__wbindgen_free(r0, r1);
}
}
export function return_current_peer_id(wasm, peerId, arg0) {
var ptr0 = passStringToWasm0(wasm, peerId, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len0 = WASM_VECTOR_LEN;
getInt32Memory0(wasm)[arg0 / 4 + 1] = len0;
getInt32Memory0(wasm)[arg0 / 4 + 0] = ptr0;
}
export function return_call_service_result(wasm, ret, arg0) {
var ptr1 = passStringToWasm0(wasm, ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len1 = WASM_VECTOR_LEN;
getInt32Memory0(wasm)[arg0 / 4 + 1] = len1;
getInt32Memory0(wasm)[arg0 / 4 + 0] = ptr1;
}
export function free(wasm, ptr, len) {
wasm.__wbindgen_free(ptr, len);
}

View File

@ -1,7 +1,7 @@
[package]
name = "avm-server"
description = "Fluence AIR VM"
version = "0.10.1"
version = "0.11.0"
authors = ["Fluence Labs"]
edition = "2018"
license = "Apache-2.0"
@ -13,14 +13,13 @@ path = "src/lib.rs"
[dependencies]
fluence-faas = "0.9.0"
air-interpreter-interface = { version = "0.6.0", path = "../../crates/interpreter-interface" }
avm-data-store = { version = "0.1.0", path = "../../crates/data-store" }
polyplets = { version = "0.1.2", path = "../../crates/polyplets" }
thiserror = "1.0.24"
eyre = "0.6.5"
thiserror = "1.0.29"
maplit = "1.0.2"
serde_json = "1.0.61"
serde = "1.0.118"
log = "0.4.14"
parking_lot = "0.11.1"
[features]
# enable raw AVM API intended for testing
raw-avm-api = []

View File

@ -14,293 +14,87 @@
* limitations under the License.
*/
use crate::call_service::CallServiceArgs;
use super::avm_runner::AVMRunner;
use super::AVMDataStore;
use super::AVMError;
use super::AVMOutcome;
use super::CallResults;
use crate::config::AVMConfig;
use crate::data_store::{create_vault_effect, particle_vault_dir, prev_data_file};
use crate::errors::AVMError::CleanupParticleError;
use crate::AVMError;
use crate::InterpreterOutcome;
use crate::{CallServiceClosure, IType, Result};
use crate::AVMResult;
use fluence_faas::FluenceFaaS;
use fluence_faas::HostImportDescriptor;
use fluence_faas::IValue;
use fluence_faas::{FaaSConfig, HostExportedFunc, ModuleDescriptor};
use parking_lot::Mutex;
use std::ops::{Deref, DerefMut};
use std::path::PathBuf;
use std::sync::Arc;
const CALL_SERVICE_NAME: &str = "call_service";
const CURRENT_PEER_ID_ENV_NAME: &str = "CURRENT_PEER_ID";
use std::ops::Deref;
use std::ops::DerefMut;
/// A newtype needed to mark it as `unsafe impl Send`
struct SendSafeFaaS(FluenceFaaS);
struct SendSafeRunner(AVMRunner);
/// Mark runtime as Send, so libp2p on the node (use-site) is happy
unsafe impl Send for SendSafeFaaS {}
unsafe impl Send for SendSafeRunner {}
impl Deref for SendSafeFaaS {
type Target = FluenceFaaS;
impl Deref for SendSafeRunner {
type Target = AVMRunner;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for SendSafeFaaS {
impl DerefMut for SendSafeRunner {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
/// Information about the particle that is being executed by the interpreter at the moment
#[derive(Clone, Debug, Default)]
pub struct ParticleParameters {
pub init_user_id: String,
pub particle_id: String,
pub struct AVM<E> {
runner: SendSafeRunner,
data_store: AVMDataStore<E>,
}
pub struct AVM {
faas: SendSafeFaaS,
particle_data_store: PathBuf,
vault_dir: PathBuf,
/// file name of the AIR interpreter .wasm
wasm_filename: String,
/// information about the particle that is being executed at the moment
current_particle: Arc<Mutex<ParticleParameters>>,
}
impl AVM {
impl<E> AVM<E> {
/// Create AVM with provided config.
pub fn new(config: AVMConfig) -> Result<Self> {
use AVMError::{CreateVaultDirError, InvalidDataStorePath};
pub fn new(config: AVMConfig<E>) -> AVMResult<Self, E> {
let AVMConfig {
air_wasm_path,
current_peer_id,
logging_mask,
mut data_store,
} = config;
let current_particle: Arc<Mutex<ParticleParameters>> = <_>::default();
let particle_data_store = config.particle_data_store;
let vault_dir = config.vault_dir;
let call_service = call_service_descriptor(
current_particle.clone(),
config.call_service,
vault_dir.clone(),
);
let (wasm_dir, wasm_filename) = split_dirname(config.air_wasm_path)?;
data_store.initialize()?;
let faas_config = make_faas_config(
wasm_dir,
&wasm_filename,
call_service,
config.current_peer_id,
config.logging_mask,
);
let faas = FluenceFaaS::with_raw_config(faas_config)?;
std::fs::create_dir_all(&particle_data_store)
.map_err(|e| InvalidDataStorePath(e, particle_data_store.clone()))?;
std::fs::create_dir_all(&vault_dir)
.map_err(|e| CreateVaultDirError(e, vault_dir.clone()))?;
let avm = Self {
faas: SendSafeFaaS(faas),
particle_data_store,
vault_dir,
wasm_filename,
current_particle,
};
let runner = AVMRunner::new(air_wasm_path, current_peer_id, logging_mask)
.map_err(AVMError::RunnerError)?;
let runner = SendSafeRunner(runner);
let avm = Self { runner, data_store };
Ok(avm)
}
pub fn call(
&mut self,
init_user_id: impl Into<String>,
air: impl Into<String>,
data: impl Into<Vec<u8>>,
particle_id: impl Into<String>,
) -> Result<InterpreterOutcome> {
use AVMError::PersistDataError;
let particle_id = particle_id.into();
init_user_id: impl Into<String>,
particle_id: &str,
call_results: CallResults,
) -> AVMResult<AVMOutcome, E> {
let init_user_id = init_user_id.into();
let prev_data = self.data_store.read_data(particle_id)?;
let prev_data_path = prev_data_file(&self.particle_data_store, &particle_id);
// TODO: check for errors related to invalid file content (such as invalid UTF8 string)
let prev_data = std::fs::read_to_string(&prev_data_path)
.unwrap_or_default()
.into_bytes();
let args = prepare_args(prev_data, data, init_user_id.clone(), air);
// Update ParticleParams with the new values so subsequent calls to `call_service` can use them
self.update_current_particle(particle_id, init_user_id);
let result =
self.faas
.call_with_ivalues(&self.wasm_filename, "invoke", &args, <_>::default())?;
let outcome =
InterpreterOutcome::from_ivalues(result).map_err(AVMError::InterpreterResultDeError)?;
let outcome = self
.runner
.call(air, prev_data, data, init_user_id, call_results)
.map_err(AVMError::RunnerError)?;
// persist resulted data
std::fs::write(&prev_data_path, &outcome.data)
.map_err(|e| PersistDataError(e, prev_data_path))?;
self.data_store.store_data(&outcome.data, particle_id)?;
let outcome = AVMOutcome::from_raw_outcome(outcome)?;
Ok(outcome)
}
/// Remove particle directories and files:
/// - prev data file
/// - particle file vault directory
pub fn cleanup_particle(&self, particle_id: &str) -> Result<()> {
let prev_data = prev_data_file(&self.particle_data_store, particle_id);
std::fs::remove_file(&prev_data).map_err(|err| CleanupParticleError(err, prev_data))?;
let vault_dir = particle_vault_dir(&self.vault_dir, particle_id);
std::fs::remove_dir_all(&vault_dir).map_err(|err| CleanupParticleError(err, vault_dir))?;
/// Cleanup data that become obsolete.
pub fn cleanup_data(&mut self, particle_id: &str) -> AVMResult<(), E> {
self.data_store.cleanup_data(particle_id)?;
Ok(())
}
fn update_current_particle(&self, particle_id: String, init_user_id: String) {
let mut params = self.current_particle.lock();
params.particle_id = particle_id;
params.init_user_id = init_user_id;
}
}
fn prepare_args(
prev_data: impl Into<Vec<u8>>,
data: impl Into<Vec<u8>>,
init_user_id: impl Into<String>,
air: impl Into<String>,
) -> Vec<IValue> {
vec![
IValue::String(init_user_id.into()),
IValue::String(air.into()),
IValue::ByteArray(prev_data.into()),
IValue::ByteArray(data.into()),
]
}
fn call_service_descriptor(
params: Arc<Mutex<ParticleParameters>>,
call_service: CallServiceClosure,
vault_dir: PathBuf,
) -> HostImportDescriptor {
let call_service_closure: HostExportedFunc = Box::new(move |_, ivalues: Vec<IValue>| {
let params = {
let lock = params.lock();
lock.deref().clone()
};
let create_vault = create_vault_effect(&vault_dir, &params.particle_id);
let args = CallServiceArgs {
particle_parameters: params,
function_args: ivalues,
create_vault,
};
call_service(args)
});
HostImportDescriptor {
host_exported_func: call_service_closure,
argument_types: vec![IType::String, IType::String, IType::String, IType::String],
output_type: Some(IType::Record(0)),
error_handler: None,
}
}
/// Splits given path into its directory and file name
///
/// # Example
/// For path `/path/to/air_interpreter_server.wasm` result will be `Ok(PathBuf(/path/to), "air_interpreter_server.wasm")`
fn split_dirname(path: PathBuf) -> Result<(PathBuf, String)> {
use AVMError::InvalidAIRPath;
let metadata = path.metadata().map_err(|err| InvalidAIRPath {
invalid_path: path.clone(),
reason: "failed to get file's metadata (doesn't exist or invalid permissions)",
io_error: Some(err),
})?;
if !metadata.is_file() {
return Err(InvalidAIRPath {
invalid_path: path,
reason: "is not a file",
io_error: None,
});
}
let file_name = path
.file_name()
.expect("checked to be a file, file name must be defined");
let file_name = file_name.to_string_lossy().into_owned();
let mut path = path;
// drop file name from path
path.pop();
Ok((path, file_name))
}
fn make_faas_config(
air_wasm_dir: PathBuf,
air_wasm_file: &str,
call_service: HostImportDescriptor,
current_peer_id: String,
logging_mask: i32,
) -> FaaSConfig {
use fluence_faas::FaaSModuleConfig;
use maplit::hashmap;
let host_imports = hashmap! {
String::from(CALL_SERVICE_NAME) => call_service
};
let mut air_module_config = FaaSModuleConfig {
mem_pages_count: None,
logger_enabled: true,
host_imports,
wasi: None,
logging_mask,
};
let envs = hashmap! {
CURRENT_PEER_ID_ENV_NAME.as_bytes().to_vec() => current_peer_id.into_bytes(),
};
air_module_config.extend_wasi_envs(envs);
FaaSConfig {
modules_dir: Some(air_wasm_dir),
modules_config: vec![ModuleDescriptor {
file_name: String::from(air_wasm_file),
import_name: String::from(air_wasm_file),
config: air_module_config,
}],
default_modules_config: None,
}
}
// This API is intended for testing purposes
#[cfg(feature = "raw-avm-api")]
impl AVM {
pub fn call_with_prev_data(
&mut self,
init_user_id: impl Into<String>,
air: impl Into<String>,
prev_data: impl Into<Vec<u8>>,
data: impl Into<Vec<u8>>,
) -> Result<InterpreterOutcome> {
let args = prepare_args(prev_data, data, init_user_id, air);
let result =
self.faas
.call_with_ivalues(&self.wasm_filename, "invoke", &args, <_>::default())?;
let outcome =
InterpreterOutcome::from_ivalues(result).map_err(AVMError::InterpreterResultDeError)?;
Ok(outcome)
}
}

View File

@ -14,29 +14,19 @@
* limitations under the License.
*/
use crate::CallServiceClosure;
use super::AVMDataStore;
use std::path::PathBuf;
/// Describes behaviour of the AVM.
pub struct AVMConfig {
pub struct AVMConfig<E> {
/// Path to a AIR interpreter Wasm file.
pub air_wasm_path: PathBuf,
/// Descriptor of a closure that will be invoked on call_service call from the AIR interpreter.
pub call_service: CallServiceClosure,
/// Current peer id.
pub current_peer_id: String,
/// Path to a folder contains prev data.
/// AVM uses it to store data obtained after interpreter execution_step, and load it as a prev_data by particle_id.
pub particle_data_store: PathBuf,
/// Path to a directory to store shared directories called Particle File Vault.
/// These directories are shared between services called in the span of a same particle execution.
pub vault_dir: PathBuf,
/// Mask used to filter logs, for details see `log_utf8_string` in fluence-faas.
pub logging_mask: i32,
pub data_store: AVMDataStore<E>,
}

View File

@ -1,39 +0,0 @@
/*
* Copyright 2021 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use crate::call_service::Effect;
use crate::errors::AVMError::CreateVaultDirError;
use std::path::{Path, PathBuf};
pub fn create_vault_effect(vault_dir: &Path, particle_id: &str) -> Effect<PathBuf> {
let particle_vault_dir = particle_vault_dir(vault_dir, particle_id);
let closure = move || {
std::fs::create_dir_all(&particle_vault_dir)
.map_err(|err| CreateVaultDirError(err, particle_vault_dir.clone()))?;
Ok(particle_vault_dir.clone())
};
Box::new(closure)
}
pub fn particle_vault_dir(vault_dir: &Path, particle_id: &str) -> PathBuf {
vault_dir.join(particle_id)
}
pub fn prev_data_file(particle_data_store: &Path, particle_id: &str) -> PathBuf {
particle_data_store.join(particle_id)
}

View File

@ -14,39 +14,38 @@
* limitations under the License.
*/
use crate::interface::ErrorAVMOutcome;
use fluence_faas::FaaSError;
use fluence_faas::IValue;
use serde_json::Error as SerdeError;
use thiserror::Error as ThisError;
use std::io::Error as IOError;
use std::path::PathBuf;
#[derive(Debug, ThisError)]
pub enum AVMError {
/// FaaS errors.
pub enum AVMError<E> {
/// This error contains interpreter outcome in case when execution failed on the interpreter
/// side. A host should match on this error type explicitly to save provided data.
#[error("interpreter failed with: {0:?}")]
InterpreterFailed(ErrorAVMOutcome),
/// This errors are encountered from an AVM runner.
#[error(transparent)]
RunnerError(RunnerError),
/// This errors are encountered from a data store object.
#[error(transparent)]
DataStoreError(#[from] E),
}
#[derive(Debug, ThisError)]
pub enum RunnerError {
/// This errors are encountered from FaaS.
#[error(transparent)]
FaaSError(#[from] FaaSError),
/// AIR interpreter result deserialization errors.
#[error("{0}")]
InterpreterResultDeError(String),
/// I/O errors while persisting resulted data.
#[error("an error occurred while saving prev data {0:?} by {1:?} path")]
PersistDataError(#[source] IOError, PathBuf),
/// Errors related to particle_data_store path from supplied config.
#[error("an error occurred while creating data storage {0:?} by {1:?} path")]
InvalidDataStorePath(#[source] IOError, PathBuf),
/// Failed to create Particle File Vault directory (thrown inside Effect)
#[error("error creating Particle File Vault {1:?}: {0:?}")]
CreateVaultDirError(#[source] IOError, PathBuf),
/// Failed to remove particle directories (called by node after particle's ttl is expired)
#[error("error cleaning up particle directory {1:?}: {0:?}")]
CleanupParticleError(#[source] IOError, PathBuf),
/// Specified path to AIR interpreter .wasm file was invalid
#[error("path to AIR interpreter .wasm ({invalid_path:?}) is invalid: {reason}; IO Error: {io_error:?}")]
InvalidAIRPath {
@ -54,10 +53,54 @@ pub enum AVMError {
io_error: Option<IOError>,
reason: &'static str,
},
/// AIR interpreter result deserialization errors.
#[error("{0}")]
InterpreterResultDeError(String),
/// FaaS call returns Vec<IValue> to support multi-value in a future,
/// but actually now it could return empty vec or a vec with one value.
/// This error is encountered when it returns vec with not a one value.
#[error("result `{0:?}` returned from FaaS should contain only one element")]
IncorrectInterpreterResult(Vec<IValue>),
/// This errors are encountered from an call results/params se/de.
#[error(transparent)]
CallSeDeErrors(#[from] CallSeDeErrors),
}
impl From<std::convert::Infallible> for AVMError {
fn from(_: std::convert::Infallible) -> Self {
unreachable!()
}
#[derive(Debug, ThisError)]
#[allow(clippy::enum_variant_names)]
pub enum CallSeDeErrors {
/// Errors encountered while trying to serialize call results.
#[error("error occurred while call results `{call_results:?}` deserialization: {se_error}")]
CallResultsSeFailed {
call_results: air_interpreter_interface::CallResults,
se_error: SerdeError,
},
/// This error is encountered when deserialization pof call requests failed for some reason.
#[error("'{raw_call_request:?}' can't been serialized with error '{error}'")]
CallRequestsDeError {
raw_call_request: Vec<u8>,
error: SerdeError,
},
/// Errors encountered while trying to deserialize arguments from call parameters returned
/// by the interpreter. In the corresponding struct such arguments are Vec<JValue> serialized
/// to a string.
#[error("error occurred while deserialization of arguments from call params `{call_params:?}`: {de_error}")]
CallParamsArgsDeFailed {
call_params: air_interpreter_interface::CallRequestParams,
de_error: SerdeError,
},
/// Errors encountered while trying to deserialize tetraplets from call parameters returned
/// by the interpreter. In the corresponding struct such tetraplets are
/// Vec<Vec<SecurityTetraplet>> serialized to a string.
#[error("error occurred while deserialization of tetraplets from call params `{call_params:?}`: {de_error}")]
CallParamsTetrapletsDeFailed {
call_params: air_interpreter_interface::CallRequestParams,
de_error: SerdeError,
},
}

View File

@ -0,0 +1,109 @@
/*
* Copyright 2021 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use super::JValue;
use crate::errors::CallSeDeErrors;
use crate::RunnerResult;
use crate::SecurityTetraplet;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
pub type CallRequests = HashMap<u32, CallRequestParams>;
/// Contains arguments of a call instruction and all other necessary information
/// required for calling a service.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CallRequestParams {
/// Id of a service that should be called.
pub service_id: String,
/// Name of a function from service identified by service_id that should be called.
pub function_name: String,
/// Arguments that should be passed to the function.
pub arguments: Vec<JValue>,
/// Tetraplets that should be passed to the service.
pub tetraplets: Vec<Vec<SecurityTetraplet>>,
}
impl CallRequestParams {
pub fn new(
service_id: String,
function_name: String,
arguments: Vec<JValue>,
tetraplets: Vec<Vec<SecurityTetraplet>>,
) -> Self {
Self {
service_id,
function_name,
arguments,
tetraplets,
}
}
pub(crate) fn from_raw(
call_params: air_interpreter_interface::CallRequestParams,
) -> RunnerResult<Self> {
let arguments: Vec<JValue> =
serde_json::from_str(&call_params.arguments).map_err(|de_error| {
CallSeDeErrors::CallParamsArgsDeFailed {
call_params: call_params.clone(),
de_error,
}
})?;
let tetraplets: Vec<Vec<SecurityTetraplet>> = serde_json::from_str(&call_params.tetraplets)
.map_err(|de_error| CallSeDeErrors::CallParamsTetrapletsDeFailed {
call_params: call_params.clone(),
de_error,
})?;
let call_params = Self {
service_id: call_params.service_id,
function_name: call_params.function_name,
arguments,
tetraplets,
};
Ok(call_params)
}
}
pub(crate) fn from_raw_call_requests(raw_call_params: Vec<u8>) -> RunnerResult<CallRequests> {
let call_requests: air_interpreter_interface::CallRequests =
match serde_json::from_slice(&raw_call_params) {
Ok(requests) => requests,
Err(error) => {
return Err(CallSeDeErrors::CallRequestsDeError {
raw_call_request: raw_call_params,
error,
})
.map_err(Into::into)
}
};
call_requests
.into_iter()
.map(|(call_id, call_params)| -> RunnerResult<_> {
let call_params = CallRequestParams::from_raw(call_params)?;
Ok((call_id, call_params))
})
.collect::<Result<_, _>>()
}

View File

@ -0,0 +1,74 @@
/*
* Copyright 2021 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use super::JValue;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
pub type CallResults = HashMap<u32, CallServiceResult>;
pub const CALL_SERVICE_SUCCESS: i32 = 0;
/// Represents an executed host function result.
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct CallServiceResult {
/// A error code service or builtin returned, where CALL_SERVICE_SUCCESS represents success.
pub ret_code: i32,
/// Resulted JValue returned by a service string.
pub result: JValue,
}
impl CallServiceResult {
pub fn ok(result: JValue) -> Self {
Self {
ret_code: CALL_SERVICE_SUCCESS,
result,
}
}
pub fn err(err_code: i32, result: JValue) -> Self {
Self {
ret_code: err_code,
result,
}
}
pub(crate) fn into_raw(self) -> air_interpreter_interface::CallServiceResult {
let CallServiceResult { ret_code, result } = self;
air_interpreter_interface::CallServiceResult {
ret_code,
result: result.to_string(),
}
}
}
pub(crate) fn into_raw_result(call_results: CallResults) -> air_interpreter_interface::CallResults {
call_results
.into_iter()
.map(|(call_id, call_result)| (call_id, call_result.into_raw()))
.collect::<_>()
}
use std::fmt;
impl fmt::Display for CallServiceResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ret_code: {}, result: '{}'", self.ret_code, self.result)
}
}

View File

@ -0,0 +1,26 @@
/*
* Copyright 2021 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
mod call_request_parameters;
mod call_service_result;
mod outcome;
pub mod raw_outcome;
type JValue = serde_json::Value;
pub use call_request_parameters::*;
pub use call_service_result::*;
pub use outcome::*;

View File

@ -0,0 +1,88 @@
/*
* Copyright 2021 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use super::CallRequests;
use crate::avm_runner::RawAVMOutcome;
use crate::AVMError;
use crate::AVMResult;
use serde::Deserialize;
use serde::Serialize;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AVMOutcome {
/// Contains script data that should be preserved in an executor of this interpreter
/// regardless of ret_code value.
pub data: Vec<u8>,
/// Collected parameters of all met call instructions that could be executed on a current peer.
pub call_requests: CallRequests,
/// Public keys of peers that should receive data.
pub next_peer_pks: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ErrorAVMOutcome {
pub error_code: i32,
pub error_message: String,
pub outcome: AVMOutcome,
}
impl AVMOutcome {
pub(self) fn new(
data: Vec<u8>,
call_requests: CallRequests,
next_peer_pks: Vec<String>,
) -> Self {
Self {
data,
call_requests,
next_peer_pks,
}
}
pub(crate) fn from_raw_outcome<E>(raw_outcome: RawAVMOutcome) -> AVMResult<Self, E> {
use air_interpreter_interface::INTERPRETER_SUCCESS;
let RawAVMOutcome {
ret_code,
error_message,
data,
call_requests,
next_peer_pks,
} = raw_outcome;
let avm_outcome = AVMOutcome::new(data, call_requests, next_peer_pks);
if ret_code == INTERPRETER_SUCCESS {
return Ok(avm_outcome);
}
let error_outcome = ErrorAVMOutcome::new(ret_code, error_message, avm_outcome);
Err(AVMError::InterpreterFailed(error_outcome))
}
}
impl ErrorAVMOutcome {
pub(self) fn new(error_code: i32, error_msg: String, outcome: AVMOutcome) -> Self {
Self {
error_code,
error_message: error_msg,
outcome,
}
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright 2021 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use super::CallRequests;
use crate::RunnerResult;
use air_interpreter_interface::InterpreterOutcome;
use serde::Deserialize;
use serde::Serialize;
/// This struct is very similar to AVMOutcome, but keeps error_code and error_msg for test purposes.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RawAVMOutcome {
pub ret_code: i32,
pub error_message: String,
pub data: Vec<u8>,
pub call_requests: CallRequests,
pub next_peer_pks: Vec<String>,
}
impl RawAVMOutcome {
pub(crate) fn from_interpreter_outcome(outcome: InterpreterOutcome) -> RunnerResult<Self> {
let InterpreterOutcome {
ret_code,
error_message,
data,
call_requests,
next_peer_pks,
} = outcome;
let call_requests = crate::interface::from_raw_call_requests(call_requests)?;
let raw_avm_outcome = Self {
ret_code,
error_message,
data,
call_requests,
next_peer_pks,
};
Ok(raw_avm_outcome)
}
}

View File

@ -25,17 +25,20 @@
)]
mod avm;
mod call_service;
mod config;
mod data_store;
mod errors;
mod interface;
mod runner;
pub use avm::ParticleParameters;
pub use avm::AVM;
pub use call_service::CallServiceArgs;
pub use call_service::CallServiceClosure;
pub use config::AVMConfig;
pub use errors::AVMError;
pub use interface::*;
pub mod avm_runner {
pub use crate::interface::raw_outcome::RawAVMOutcome;
pub use crate::runner::AVMRunner;
}
// Re-exports
pub use fluence_faas::ne_vec;
@ -46,7 +49,13 @@ pub use fluence_faas::HostImportError;
pub use fluence_faas::IType;
pub use fluence_faas::IValue;
pub use air_interpreter_interface::InterpreterOutcome;
pub use air_interpreter_interface::INTERPRETER_SUCCESS;
pub use polyplets::SecurityTetraplet;
pub(crate) type Result<T> = std::result::Result<T, AVMError>;
pub use avm_data_store::DataStore;
pub type AVMDataStore<E> = Box<dyn DataStore<E> + Send + Sync + 'static>;
pub type AVMResult<T, E> = std::result::Result<T, AVMError<E>>;
pub(crate) use errors::RunnerError;
pub(crate) type RunnerResult<T> = std::result::Result<T, RunnerError>;

180
avm/server/src/runner.rs Normal file
View File

@ -0,0 +1,180 @@
/*
* Copyright 2021 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use super::CallResults;
use crate::raw_outcome::RawAVMOutcome;
use crate::RunnerError;
use crate::RunnerResult;
use air_interpreter_interface::InterpreterOutcome;
use fluence_faas::FaaSConfig;
use fluence_faas::FluenceFaaS;
use fluence_faas::IValue;
use fluence_faas::ModuleDescriptor;
use std::path::PathBuf;
pub struct AVMRunner {
faas: FluenceFaaS,
current_peer_id: String,
/// file name of the AIR interpreter .wasm
wasm_filename: String,
}
impl AVMRunner {
/// Create AVM with provided config.
pub fn new(
air_wasm_path: PathBuf,
current_peer_id: impl Into<String>,
logging_mask: i32,
) -> RunnerResult<Self> {
let (wasm_dir, wasm_filename) = split_dirname(air_wasm_path)?;
let faas_config = make_faas_config(wasm_dir, &wasm_filename, logging_mask);
let faas = FluenceFaaS::with_raw_config(faas_config)?;
let current_peer_id = current_peer_id.into();
let avm = Self {
faas,
current_peer_id,
wasm_filename,
};
Ok(avm)
}
pub fn call(
&mut self,
air: impl Into<String>,
prev_data: impl Into<Vec<u8>>,
data: impl Into<Vec<u8>>,
init_user_id: impl Into<String>,
call_results: CallResults,
) -> RunnerResult<RawAVMOutcome> {
let init_user_id = init_user_id.into();
let args = prepare_args(
air,
prev_data,
data,
init_user_id,
self.current_peer_id.clone(),
call_results,
);
let result =
self.faas
.call_with_ivalues(&self.wasm_filename, "invoke", &args, <_>::default())?;
let result = try_as_one_value_vec(result)?;
let outcome = InterpreterOutcome::from_ivalue(result)
.map_err(RunnerError::InterpreterResultDeError)?;
let outcome = RawAVMOutcome::from_interpreter_outcome(outcome)?;
Ok(outcome)
}
}
fn prepare_args(
air: impl Into<String>,
prev_data: impl Into<Vec<u8>>,
data: impl Into<Vec<u8>>,
init_peer_id: impl Into<String>,
current_peer_id: String,
call_results: CallResults,
) -> Vec<IValue> {
use fluence_faas::ne_vec::NEVec;
let run_parameters = vec![
IValue::String(init_peer_id.into()),
IValue::String(current_peer_id),
];
let run_parameters = NEVec::new(run_parameters).unwrap();
let call_results = crate::interface::into_raw_result(call_results);
let call_results =
serde_json::to_vec(&call_results).expect("the default serializer shouldn't fail");
vec![
IValue::String(air.into()),
IValue::ByteArray(prev_data.into()),
IValue::ByteArray(data.into()),
IValue::Record(run_parameters),
IValue::ByteArray(call_results),
]
}
/// Splits given path into its directory and file name
///
/// # Example
/// For path `/path/to/air_interpreter_server.wasm` result will be `Ok(PathBuf(/path/to), "air_interpreter_server.wasm")`
fn split_dirname(path: PathBuf) -> RunnerResult<(PathBuf, String)> {
use RunnerError::InvalidAIRPath;
let metadata = path.metadata().map_err(|err| InvalidAIRPath {
invalid_path: path.clone(),
reason: "failed to get file's metadata (doesn't exist or invalid permissions)",
io_error: Some(err),
})?;
if !metadata.is_file() {
return Err(InvalidAIRPath {
invalid_path: path,
reason: "is not a file",
io_error: None,
});
}
let file_name = path
.file_name()
.expect("checked to be a file, file name must be defined");
let file_name = file_name.to_string_lossy().into_owned();
let mut path = path;
// drop file name from path
path.pop();
Ok((path, file_name))
}
fn make_faas_config(air_wasm_dir: PathBuf, air_wasm_file: &str, logging_mask: i32) -> FaaSConfig {
let air_module_config = fluence_faas::FaaSModuleConfig {
mem_pages_count: None,
logger_enabled: true,
host_imports: <_>::default(),
wasi: None,
logging_mask,
};
FaaSConfig {
modules_dir: Some(air_wasm_dir),
modules_config: vec![ModuleDescriptor {
file_name: String::from(air_wasm_file),
import_name: String::from(air_wasm_file),
config: air_module_config,
}],
default_modules_config: None,
}
}
fn try_as_one_value_vec(mut ivalues: Vec<IValue>) -> RunnerResult<IValue> {
use RunnerError::IncorrectInterpreterResult;
if ivalues.len() != 1 {
return Err(IncorrectInterpreterResult(ivalues));
}
Ok(ivalues.remove(0))
}

View File

@ -63,7 +63,7 @@ Instr: Box<Instruction<'input>> = {
let instruction = Rc::new(*i);
let fold = FoldStream { stream_name: stream, iterator, instruction };
let span = Span { left, right };
validator.met_fold_stream(&fold, span);
validator.meet_fold_stream(&fold, span);
Box::new(Instruction::FoldStream(fold))
},

View File

@ -1,5 +1,5 @@
// auto-generated: "lalrpop 0.19.6"
// sha3: ec6297654486ff6653229497083c43cbdeadeb5545b73675ba8f2f9665737ec
// sha3: bcd19a478f6a51d6d816bf63f4b9bd31c8eb721de974cf77626c92ea437d66
use crate::parser::ast::*;
use crate::parser::air_parser::make_flattened_error;
use crate::parser::air_parser::make_stream_iterable_error;
@ -3368,7 +3368,7 @@ fn __action8<
let instruction = Rc::new(*i);
let fold = FoldStream { stream_name: stream, iterator, instruction };
let span = Span { left, right };
validator.met_fold_stream(&fold, span);
validator.meet_fold_stream(&fold, span);
Box::new(Instruction::FoldStream(fold))
}

View File

@ -80,7 +80,7 @@ impl<'i> VariableValidator<'i> {
self.met_iterator_definition(fold.iterator, span);
}
pub(super) fn met_fold_stream(&mut self, fold: &FoldStream<'i>, span: Span) {
pub(super) fn meet_fold_stream(&mut self, fold: &FoldStream<'i>, span: Span) {
self.met_variable(&AstVariable::Stream(fold.stream_name), span);
self.met_iterator_definition(fold.iterator, span);
}

View File

@ -0,0 +1,10 @@
[package]
name = "avm-data-store"
version = "0.1.0"
authors = ["Fluence Labs"]
edition = "2018"
license = "Apache-2.0"
[lib]
name = "avm_data_store"
path = "src/lib.rs"

View File

@ -14,15 +14,13 @@
* limitations under the License.
*/
use crate::{AVMError, IValue, ParticleParameters};
/// This trait should be used to persist prev_data between successive calls of an interpreter o.
pub trait DataStore<E> {
fn initialize(&mut self) -> Result<(), E>;
use std::path::PathBuf;
fn store_data(&mut self, data: &[u8], key: &str) -> Result<(), E>;
pub type Effect<T> = Box<dyn Fn() -> Result<T, AVMError> + 'static>;
pub struct CallServiceArgs {
pub particle_parameters: ParticleParameters,
pub function_args: Vec<IValue>,
pub create_vault: Effect<PathBuf>,
fn read_data(&mut self, key: &str) -> Result<Vec<u8>, E>;
fn cleanup_data(&mut self, key: &str) -> Result<(), E>;
}
pub type CallServiceClosure = Box<dyn Fn(CallServiceArgs) -> Option<IValue> + 'static>;

View File

@ -0,0 +1,18 @@
## Version 0.2.1
[PR 130](https://github.com/fluencelabs/aquavm/pull/130):
- added a new field to track the latest exposed to a peer number of a call request
- `RequestSentBy` enum variant of `CallResult` contains a `Sender` enum to support call request scheme (this `Sender` will se/de into string, so this change won't require a hard fork)
## Version 0.2.0
[PR 74](https://github.com/fluencelabs/aquavm/pull/74) (hard fork):
- added a new state for the `ap` instruction
- added a new state for the `fold` instruction
- added a new field to track data version
- added a new field to track the maximum number of generation of each stream
- changed the serialization scheme of the `par` and `call` instructions in order to make it shorter in se view
## Version 0.1.0
The initial version of data with states for the `par` and `call` instruction was introduced.

View File

@ -18,6 +18,7 @@ mod impls;
mod se_de;
use se_de::par_serializer;
use se_de::sender_serializer;
use serde::Deserialize;
use serde::Serialize;
use serde_json::Value as JValue;
@ -30,12 +31,19 @@ pub struct ParResult {
pub right_size: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Sender {
PeerId(Rc<String>),
PeerIdWithCallId { peer_id: Rc<String>, call_id: u32 },
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[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(Rc<String>),
RequestSentBy(Sender),
/// A corresponding call's been already executed with such value as a result.
Executed(Value),

View File

@ -33,8 +33,12 @@ impl ParResult {
}
impl CallResult {
pub fn sent(sender: impl Into<String>) -> CallResult {
CallResult::RequestSentBy(Rc::new(sender.into()))
pub fn sent_peer_id(peer_id: Rc<String>) -> CallResult {
CallResult::RequestSentBy(Sender::PeerId(peer_id))
}
pub fn sent_peer_id_with_call_id(peer_id: Rc<String>, call_id: u32) -> CallResult {
CallResult::RequestSentBy(Sender::PeerIdWithCallId { peer_id, call_id })
}
pub fn executed_scalar(value: Rc<JValue>) -> CallResult {
@ -92,7 +96,7 @@ impl std::fmt::Display for ExecutedState {
left_size: left_subtree_size,
right_size: right_subtree_size,
}) => write!(f, "par({}, {})", left_subtree_size, right_subtree_size),
Call(RequestSentBy(peer_id)) => write!(f, r#"request_sent_by("{}")"#, peer_id),
Call(RequestSentBy(sender)) => write!(f, r"{}", sender),
Call(Executed(value)) => {
write!(f, "executed({})", value)
}
@ -131,3 +135,14 @@ impl std::fmt::Display for Value {
}
}
}
impl std::fmt::Display for Sender {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Sender::PeerId(peer_id) => write!(f, "request_sent_by({})", peer_id),
Sender::PeerIdWithCallId { peer_id, call_id } => {
write!(f, "request_sent_by({}: {})", peer_id, call_id)
}
}
}
}

View File

@ -39,8 +39,8 @@ pub mod par_serializer {
where
D: Deserializer<'de>,
{
struct VecVisitor;
impl<'de> Visitor<'de> for VecVisitor {
struct ParVisitor;
impl<'de> Visitor<'de> for ParVisitor {
type Value = ParResult;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
@ -63,6 +63,61 @@ pub mod par_serializer {
}
}
deserializer.deserialize_seq(VecVisitor {})
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

@ -39,6 +39,11 @@ pub struct InterpreterData {
/// Version of this data format.
pub version: semver::Version,
/// Last exposed to a peer call request id. All next call request ids will be bigger than this.
#[serde(default)]
#[serde(rename = "lcid")]
pub last_call_request_id: u32,
}
impl InterpreterData {
@ -47,14 +52,20 @@ impl InterpreterData {
trace: <_>::default(),
streams: <_>::default(),
version: DATA_FORMAT_VERSION.deref().clone(),
last_call_request_id: 0,
}
}
pub fn from_execution_result(trace: ExecutionTrace, streams: StreamGenerations) -> Self {
pub fn from_execution_result(
trace: ExecutionTrace,
streams: StreamGenerations,
last_call_request_id: u32,
) -> Self {
Self {
trace,
streams,
version: DATA_FORMAT_VERSION.deref().clone(),
last_call_request_id,
}
}
@ -75,3 +86,37 @@ impl Default for InterpreterData {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde::Deserialize;
use serde::Serialize;
#[test]
fn compatible_with_0_2_0_version() {
#[derive(Serialize, Deserialize)]
struct InterpreterData0_2_0 {
pub trace: ExecutionTrace,
pub streams: StreamGenerations,
pub version: semver::Version,
}
// test 0.2.0 to 0.2.1 conversion
let data_0_2_0 = InterpreterData0_2_0 {
trace: ExecutionTrace::default(),
streams: StreamGenerations::default(),
version: semver::Version::new(0, 2, 0),
};
let data_0_2_0_se = serde_json::to_vec(&data_0_2_0).unwrap();
let data_0_2_1 = serde_json::from_slice::<InterpreterData>(&data_0_2_0_se);
assert!(data_0_2_1.is_ok());
// test 0.2.1 to 0.2.1 conversion
let data_0_2_1 = InterpreterData::default();
let data_0_2_1_se = serde_json::to_vec(&data_0_2_1).unwrap();
let data_0_2_0 = serde_json::from_slice::<InterpreterData0_2_0>(&data_0_2_1_se);
assert!(data_0_2_0.is_ok());
}
}

View File

@ -24,5 +24,5 @@ use once_cell::sync::Lazy;
use std::str::FromStr;
pub static DATA_FORMAT_VERSION: Lazy<semver::Version> = Lazy::new(|| {
semver::Version::from_str("0.2.0").expect("invalid data format version specified")
semver::Version::from_str("0.2.1").expect("invalid data format version specified")
});

View File

@ -19,3 +19,4 @@ marine-rs-sdk = "0.6.11"
fluence-it-types = "0.3.0"
serde = "1.0.118"
serde_json = "1.0.56"

View File

@ -0,0 +1,56 @@
/*
* Copyright 2021 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use marine_rs_sdk::marine;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
pub type CallRequests = HashMap<u32, CallRequestParams>;
/// Contains arguments of a call instruction and all other necessary information
/// required for calling a service.
#[marine]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CallRequestParams {
/// Id of a service that should be called.
pub service_id: String,
/// Name of a function from service identified by service_id that should be called.
pub function_name: String,
/// Serialized to JSON string Vec<JValue> of arguments that should be passed to a service.
pub arguments: String,
/// Serialized to JSON string Vec<Vec<SecurityTetraplet>> that should be passed to a service.
pub tetraplets: String,
}
impl CallRequestParams {
pub fn new(
service_id: String,
function_name: String,
arguments: String,
tetraplets: String,
) -> Self {
Self {
service_id,
function_name,
arguments,
tetraplets,
}
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2021 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use serde::Deserialize;
use serde::Serialize;
use serde_json::Value as JValue;
use std::collections::HashMap;
pub type CallResults = HashMap<u32, CallServiceResult>;
pub const CALL_SERVICE_SUCCESS: i32 = 0;
/// Represents an executed host function result.
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct CallServiceResult {
/// A error code service or builtin returned, where CALL_SERVICE_SUCCESS represents success.
pub ret_code: i32,
/// Resulted JValue serialized to a string. It's impossible to wrap it with the marine macro,
/// inasmuch as it's a enum uses HashMap inside.
pub result: String,
}
impl CallServiceResult {
pub fn ok(result: &JValue) -> Self {
Self {
ret_code: CALL_SERVICE_SUCCESS,
result: result.to_string(),
}
}
pub fn err(err_code: i32, result: &JValue) -> Self {
Self {
ret_code: err_code,
result: result.to_string(),
}
}
}
use std::fmt;
impl fmt::Display for CallServiceResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ret_code: {}, result: '{}'", self.ret_code, self.result)
}
}

View File

@ -0,0 +1,143 @@
/*
* Copyright 2021 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use marine_rs_sdk::marine;
use fluence_it_types::IValue;
use serde::Deserialize;
use serde::Serialize;
pub const INTERPRETER_SUCCESS: i32 = 0;
/// Describes a result returned at the end of the interpreter execution_step.
#[marine]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct InterpreterOutcome {
/// A return code, where INTERPRETER_SUCCESS means success.
pub ret_code: i32,
/// Contains error message if ret_code != INTERPRETER_SUCCESS.
pub error_message: String,
/// Contains script data that should be preserved in an executor of this interpreter
/// regardless of ret_code value.
pub data: Vec<u8>,
/// Public keys of peers that should receive data.
pub next_peer_pks: Vec<String>,
/// Collected parameters of all met call instructions that could be executed on a current peer.
pub call_requests: Vec<u8>,
}
impl InterpreterOutcome {
pub fn from_ivalue(ivalue: IValue) -> Result<Self, String> {
const OUTCOME_FIELDS_COUNT: usize = 5;
let mut record_values = try_as_record(ivalue)?.into_vec();
if record_values.len() != OUTCOME_FIELDS_COUNT {
return Err(format!(
"expected InterpreterOutcome struct with {} fields, got {:?}",
OUTCOME_FIELDS_COUNT, record_values
));
}
let call_requests = try_as_byte_vec(record_values.pop().unwrap(), "call_requests")?;
let next_peer_pks = try_as_string_vec(record_values.pop().unwrap(), "next_peer_pks")?;
let data = try_as_byte_vec(record_values.pop().unwrap(), "data")?;
let error_message = try_as_string(record_values.pop().unwrap(), "error_message")?;
let ret_code = try_as_i32(record_values.pop().unwrap(), "ret_code")?;
let outcome = Self {
ret_code,
error_message,
data,
next_peer_pks,
call_requests,
};
Ok(outcome)
}
}
use fluence_it_types::ne_vec::NEVec;
fn try_as_record(ivalue: IValue) -> Result<NEVec<IValue>, String> {
match ivalue {
IValue::Record(record_values) => Ok(record_values),
v => {
return Err(format!(
"expected record for InterpreterOutcome, got {:?}",
v
))
}
}
}
fn try_as_i32(ivalue: IValue, field_name: &str) -> Result<i32, String> {
match ivalue {
IValue::S32(value) => Ok(value),
v => return Err(format!("expected an i32 for {}, got {:?}", field_name, v)),
}
}
fn try_as_string(ivalue: IValue, field_name: &str) -> Result<String, String> {
match ivalue {
IValue::String(value) => Ok(value),
v => return Err(format!("expected a string for {}, got {:?}", field_name, v)),
}
}
fn try_as_byte_vec(ivalue: IValue, field_name: &str) -> Result<Vec<u8>, String> {
let byte_vec = match ivalue {
IValue::Array(array) => {
let array: Result<Vec<_>, _> = array
.into_iter()
.map(|v| match v {
IValue::U8(byte) => Ok(byte),
v => Err(format!("expected a byte, got {:?}", v)),
})
.collect();
array?
}
IValue::ByteArray(array) => array,
v => {
return Err(format!(
"expected a Vec<u8> for {}, got {:?}",
field_name, v
))
}
};
Ok(byte_vec)
}
fn try_as_string_vec(ivalue: IValue, field_name: &str) -> Result<Vec<String>, String> {
match ivalue {
IValue::Array(ar_values) => {
let array = ar_values
.into_iter()
.map(|v| match v {
IValue::String(str) => Ok(str),
v => Err(format!("expected string for next_peer_pks, got {:?}", v)),
})
.collect::<Result<Vec<String>, _>>()?;
Ok(array)
}
v => Err(format!("expected an array for {}, got {:?}", field_name, v)),
}
}

View File

@ -14,101 +14,12 @@
* limitations under the License.
*/
use marine_rs_sdk::marine;
mod call_request_parameters;
mod call_service_result;
mod interpreter_outcome;
mod run_parameters;
use fluence_it_types::IValue;
use serde::Deserialize;
use serde::Serialize;
pub const INTERPRETER_SUCCESS: i32 = 0;
/// Describes a result returned at the end of the interpreter execution_step.
#[marine]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct InterpreterOutcome {
/// A return code, where INTERPRETER_SUCCESS means success.
pub ret_code: i32,
/// Contains error message if ret_code != INTERPRETER_SUCCESS.
pub error_message: String,
/// Contains script data that should be preserved in an executor of this interpreter
/// regardless of ret_code value.
pub data: Vec<u8>,
/// Public keys of peers that should receive data.
pub next_peer_pks: Vec<String>,
}
impl InterpreterOutcome {
pub fn from_ivalues(mut ivalues: Vec<IValue>) -> Result<Self, String> {
const OUTCOME_FIELDS_COUNT: usize = 4;
let record_values = match ivalues.remove(0) {
IValue::Record(record_values) => record_values,
v => {
return Err(format!(
"expected record for InterpreterOutcome, got {:?}",
v
))
}
};
let mut record_values = record_values.into_vec();
if record_values.len() != OUTCOME_FIELDS_COUNT {
return Err(format!(
"expected InterpreterOutcome struct with {} fields, got {:?}",
OUTCOME_FIELDS_COUNT, record_values
));
}
let ret_code = match record_values.remove(0) {
IValue::S32(ret_code) => ret_code,
v => return Err(format!("expected i32 for ret_code, got {:?}", v)),
};
let error_message = match record_values.remove(0) {
IValue::String(str) => str,
v => return Err(format!("expected string for data, got {:?}", v)),
};
let data = match record_values.remove(0) {
IValue::Array(array) => {
let array: Result<Vec<_>, _> = array
.into_iter()
.map(|v| match v {
IValue::U8(byte) => Ok(byte),
v => Err(format!("expected a byte, got {:?}", v)),
})
.collect();
array?
}
IValue::ByteArray(array) => array,
v => return Err(format!("expected Vec<u8> for data, got {:?}", v)),
};
let next_peer_pks = match record_values.remove(0) {
IValue::Array(ar_values) => {
let array = ar_values
.into_iter()
.map(|v| match v {
IValue::String(str) => Ok(str),
v => Err(format!("expected string for next_peer_pks, got {:?}", v)),
})
.collect::<Result<Vec<String>, _>>()?;
Ok(array)
}
v => Err(format!("expected array for next_peer_pks, got {:?}", v)),
}?;
let outcome = Self {
ret_code,
error_message,
data,
next_peer_pks,
};
Ok(outcome)
}
}
pub use call_request_parameters::*;
pub use call_service_result::*;
pub use interpreter_outcome::*;
pub use run_parameters::*;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2020 Fluence Labs Limited
* Copyright 2021 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,23 +14,17 @@
* limitations under the License.
*/
use super::CallServiceResult;
use marine_rs_sdk::marine;
use marine_rs_sdk::module_manifest;
use std::env::VarError;
const CURRENT_PEER_ID_ENV_NAME: &str = "CURRENT_PEER_ID";
module_manifest!();
pub(crate) fn get_current_peer_id() -> std::result::Result<String, VarError> {
std::env::var(CURRENT_PEER_ID_ENV_NAME)
}
use serde::Deserialize;
use serde::Serialize;
/// Parameters that a host side should pass to an interpreter and that necessary for execution.
#[marine]
#[link(wasm_import_module = "host")]
extern "C" {
pub(crate) fn call_service(service_id: &str, fn_name: &str, args: &str, tetraplets: &str) -> CallServiceResult;
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct RunParameters {
/// Peer id of a peer that start this particle.
pub init_peer_id: String,
/// Peer id of a current peer.
pub current_peer_id: String,
}

View File

@ -30,6 +30,7 @@ use marine_rs_sdk::marine;
fn main() {}
#[marine]
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct CallServiceResult {
pub ret_code: i32,
pub result: String,

View File

@ -12,7 +12,8 @@ path = "src/lib.rs"
[dependencies]
marine-rs-sdk = "0.6.11"
avm-server = { path = "../../avm/server", features = ["raw-avm-api"] }
avm-server = { path = "../../avm/server" }
air = { path = "../../air" }
air-interpreter-interface = { path = "../../crates/interpreter-interface" }
serde_json = "1.0.61"

View File

@ -15,139 +15,56 @@
*/
use super::*;
use serde_json::json;
use std::collections::HashMap;
pub fn unit_call_service() -> CallServiceClosure {
Box::new(|_| -> Option<IValue> {
Some(IValue::Record(
NEVec::new(vec![
IValue::S32(0),
IValue::String(String::from("\"test\"")),
])
.unwrap(),
))
})
Box::new(|_| -> CallServiceResult { CallServiceResult::ok(json!("test")) })
}
pub fn echo_call_service() -> CallServiceClosure {
Box::new(|args| -> Option<IValue> {
let arg = match &args.function_args[2] {
IValue::String(str) => str,
_ => unreachable!(),
};
let arg: Vec<serde_json::Value> = serde_json::from_str(arg).unwrap();
Some(IValue::Record(
NEVec::new(vec![IValue::S32(0), IValue::String(arg[0].to_string())]).unwrap(),
))
Box::new(|mut params| -> CallServiceResult {
CallServiceResult::ok(params.arguments.remove(0))
})
}
pub fn echo_string_call_service() -> CallServiceClosure {
Box::new(|args| -> Option<IValue> {
let arg = match &args.function_args[2] {
IValue::String(str) => str,
_ => unreachable!(),
};
let arg: Vec<String> = serde_json::from_str(arg).unwrap();
let arg = serde_json::to_string(&arg[0]).unwrap();
Some(IValue::Record(
NEVec::new(vec![IValue::S32(0), IValue::String(arg)]).unwrap(),
))
})
pub fn set_variable_call_service(json: JValue) -> CallServiceClosure {
Box::new(move |_| -> CallServiceResult { CallServiceResult::ok(json.clone()) })
}
pub fn echo_number_call_service() -> CallServiceClosure {
Box::new(|args| -> Option<IValue> {
let arg = match &args.function_args[2] {
IValue::String(str) => str,
_ => unreachable!(),
pub fn set_variables_call_service(
variables_mapping: HashMap<String, JValue>,
) -> CallServiceClosure {
Box::new(move |mut params| -> CallServiceResult {
let var_name = match params.arguments.pop() {
Some(JValue::String(name)) => name,
_ => "default".to_string(),
};
let arg: Vec<String> = serde_json::from_str(arg).unwrap();
Some(IValue::Record(
NEVec::new(vec![IValue::S32(0), IValue::String(arg[0].clone())]).unwrap(),
))
})
}
pub fn set_variable_call_service(json: impl Into<String>) -> CallServiceClosure {
let json = json.into();
Box::new(move |_| -> Option<IValue> {
Some(IValue::Record(
NEVec::new(vec![IValue::S32(0), IValue::String(json.clone())]).unwrap(),
))
})
}
pub fn set_variables_call_service(ret_mapping: HashMap<String, String>) -> CallServiceClosure {
Box::new(move |args| -> Option<IValue> {
let arg_name = match &args.function_args[2] {
IValue::String(json_str) => {
let json = serde_json::from_str(json_str).expect("a valid json");
match json {
JValue::Array(array) => match array.first() {
Some(JValue::String(str)) => str.to_string(),
_ => String::from("default"),
},
_ => String::from("default"),
}
}
_ => String::from("default"),
};
let result = ret_mapping
.get(&arg_name)
.cloned()
.unwrap_or_else(|| String::from(r#""test""#));
Some(IValue::Record(
NEVec::new(vec![IValue::S32(0), IValue::String(result)]).unwrap(),
))
variables_mapping.get(&var_name).map_or_else(
|| CallServiceResult::ok(json!("test")),
|var| CallServiceResult::ok(var.clone()),
)
})
}
pub fn return_string_call_service(ret_str: impl Into<String>) -> CallServiceClosure {
let ret_str = ret_str.into();
Box::new(move |_| -> Option<IValue> {
Some(IValue::Record(
NEVec::new(vec![
IValue::S32(0),
IValue::String(format!(r#""{}""#, ret_str)),
])
.unwrap(),
))
})
Box::new(move |_| -> CallServiceResult { CallServiceResult::ok(json!(ret_str)) })
}
pub fn fallible_call_service(fallible_service_id: impl Into<String>) -> CallServiceClosure {
let fallible_service_id = fallible_service_id.into();
Box::new(move |args| -> Option<IValue> {
let builtin_service = match &args.function_args[0] {
IValue::String(str) => str,
_ => unreachable!(),
};
Box::new(move |params| -> CallServiceResult {
// return a error for service with such id
if builtin_service == &fallible_service_id {
Some(IValue::Record(
NEVec::new(vec![IValue::S32(1), IValue::String(String::from("error"))]).unwrap(),
))
if params.service_id == fallible_service_id {
CallServiceResult::err(1, json!("error"))
} else {
// return success for services with other ids
Some(IValue::Record(
NEVec::new(vec![
IValue::S32(0),
IValue::String(String::from(r#""res""#)),
])
.unwrap(),
))
// return success for services with other service id
CallServiceResult::ok(json!("test"))
}
})
}

View File

@ -19,6 +19,7 @@ use super::CallResult;
use super::ExecutedState;
use super::JValue;
use super::ParResult;
use super::Sender;
use super::Value;
use crate::FoldLore;
use crate::FoldResult;
@ -78,7 +79,9 @@ pub fn stream_string_array(result: Vec<impl Into<String>>, generation: u32) -> E
}
pub fn request_sent_by(sender: impl Into<String>) -> ExecutedState {
ExecutedState::Call(CallResult::RequestSentBy(Rc::new(sender.into())))
ExecutedState::Call(CallResult::RequestSentBy(Sender::PeerId(Rc::new(
sender.into(),
))))
}
pub fn par(left: usize, right: usize) -> ExecutedState {

View File

@ -25,45 +25,34 @@
unreachable_patterns
)]
mod call_services;
pub mod call_services;
pub mod executed_state;
pub use avm_server::ne_vec::NEVec;
pub use avm_server::AVMConfig;
pub use avm_server::AVMError;
pub use avm_server::CallServiceClosure;
pub use avm_server::IType;
pub use avm_server::IValue;
pub use avm_server::InterpreterOutcome;
pub use avm_server::ParticleParameters;
pub use avm_server::AVM;
pub use call_services::*;
pub mod test_runner;
pub use air::interpreter_data::*;
pub use avm_server::raw_outcome::*;
pub use avm_server::*;
use std::path::PathBuf;
pub mod prelude {
pub use super::*;
pub use call_services::*;
pub use executed_state::*;
pub use test_runner::*;
pub(self) type JValue = serde_json::Value;
pub use air::interpreter_data::*;
pub use avm_server::*;
pub fn create_avm(call_service: CallServiceClosure, current_peer_id: impl Into<String>) -> AVM {
let tmp_dir = std::env::temp_dir();
let config = AVMConfig {
air_wasm_path: PathBuf::from("../target/wasm32-wasi/debug/air_interpreter_server.wasm"),
call_service,
current_peer_id: current_peer_id.into(),
vault_dir: tmp_dir.join("vault"),
particle_data_store: tmp_dir,
logging_mask: i32::MAX,
};
AVM::new(config).expect("vm should be created")
pub use serde_json::json;
}
pub type CallServiceClosure = Box<dyn Fn(CallRequestParams) -> CallServiceResult + 'static>;
pub type JValue = serde_json::Value;
#[macro_export]
macro_rules! checked_call_vm {
($vm:expr, $init_peer_id:expr, $script:expr, $prev_data:expr, $data:expr) => {{
match $vm.call_with_prev_data($init_peer_id, $script, $prev_data, $data) {
match $vm.call($script, $prev_data, $data, $init_peer_id) {
Ok(v) if v.ret_code != 0 => {
panic!("VM returns a error: {} {}", v.ret_code, v.error_message)
}
@ -76,24 +65,24 @@ macro_rules! checked_call_vm {
#[macro_export]
macro_rules! call_vm {
($vm:expr, $init_peer_id:expr, $script:expr, $prev_data:expr, $data:expr) => {
match $vm.call_with_prev_data($init_peer_id, $script, $prev_data, $data) {
match $vm.call($script, $prev_data, $data, $init_peer_id) {
Ok(v) => v,
Err(err) => panic!("VM call failed: {}", err),
}
};
}
pub fn trace_from_result(result: &InterpreterOutcome) -> ExecutionTrace {
pub fn trace_from_result(result: &RawAVMOutcome) -> ExecutionTrace {
let data = data_from_result(result);
data.trace
}
pub fn data_from_result(result: &InterpreterOutcome) -> InterpreterData {
pub fn data_from_result(result: &RawAVMOutcome) -> InterpreterData {
serde_json::from_slice(&result.data).expect("default serializer shouldn't fail")
}
pub fn raw_data_from_trace(trace: ExecutionTrace) -> Vec<u8> {
let data = InterpreterData::from_execution_result(trace, <_>::default());
let data = InterpreterData::from_execution_result(trace, <_>::default(), 0);
serde_json::to_vec(&data).expect("default serializer shouldn't fail")
}
@ -108,7 +97,7 @@ macro_rules! assert_next_pks {
};
}
pub fn print_trace(result: &InterpreterOutcome, trace_name: &str) {
pub fn print_trace(result: &RawAVMOutcome, trace_name: &str) {
let trace = trace_from_result(result);
println!("trace {} (states_count: {}): [", trace_name, trace.len());

View File

@ -0,0 +1,93 @@
/*
* Copyright 2020 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use super::CallServiceClosure;
use avm_server::avm_runner::*;
use std::collections::HashMap;
use std::collections::HashSet;
use std::path::PathBuf;
pub struct TestRunner {
runner: AVMRunner,
call_service: CallServiceClosure,
}
impl TestRunner {
pub fn call(
&mut self,
air: impl Into<String>,
prev_data: impl Into<Vec<u8>>,
data: impl Into<Vec<u8>>,
init_user_id: impl Into<String>,
) -> Result<RawAVMOutcome, String> {
let air = air.into();
let mut prev_data = prev_data.into();
let mut data = data.into();
let init_user_id = init_user_id.into();
let mut call_results = HashMap::new();
let mut next_peer_pks = HashSet::new();
loop {
let mut outcome = self
.runner
.call(
air.clone(),
prev_data,
data,
init_user_id.clone(),
call_results,
)
.map_err(|e| e.to_string())?;
next_peer_pks.extend(outcome.next_peer_pks);
if outcome.call_requests.is_empty() {
outcome.next_peer_pks = next_peer_pks.into_iter().collect::<Vec<_>>();
return Ok(outcome);
}
call_results = outcome
.call_requests
.into_iter()
.map(|(id, call_parameters)| {
let service_result = (self.call_service)(call_parameters);
(id, service_result)
})
.collect::<HashMap<_, _>>();
prev_data = outcome.data;
data = vec![];
}
}
}
pub fn create_avm(
call_service: CallServiceClosure,
current_peer_id: impl Into<String>,
) -> TestRunner {
let air_wasm_path = PathBuf::from("../target/wasm32-wasi/debug/air_interpreter_server.wasm");
let current_peer_id = current_peer_id.into();
let logging_mask = i32::MAX;
let runner =
AVMRunner::new(air_wasm_path, current_peer_id, logging_mask).expect("vm should be created");
TestRunner {
runner,
call_service,
}
}