diff --git a/CHANGELOG.md b/CHANGELOG.md index e5865ab5..58e98535 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,31 @@ +## 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): +[PR 74](https://github.com/fluencelabs/aquavm/pull/74): - introduced a new CRDT-like data format for streams: - call results contains different values for streams and scalars - introduced a new state for fold whose iterables are streams diff --git a/Cargo.lock b/Cargo.lock index a80a8f2e..b200dcb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index d2322ccf..67d6c57a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "air", "air-interpreter", "crates/air-parser", + "crates/data-store", "crates/polyplets", "crates/interpreter-interface", "crates/interpreter-data", diff --git a/air-interpreter/Cargo.toml b/air-interpreter/Cargo.toml index 3d6cc50f..f14b1660 100644 --- a/air-interpreter/Cargo.toml +++ b/air-interpreter/Cargo.toml @@ -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 = [] diff --git a/air-interpreter/src/marine.rs b/air-interpreter/src/marine.rs index 7ab8fcec..e5fc1f10 100644 --- a/air-interpreter/src/marine.rs +++ b/air-interpreter/src/marine.rs @@ -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, data: Vec) -> InterpreterOutcome { - execute_air(init_peer_id, air, prev_data, data) +pub fn invoke( + air: String, + prev_data: Vec, + data: Vec, + params: RunParameters, + call_results: Vec, +) -> InterpreterOutcome { + execute_air(air, prev_data, data, params, call_results) } #[marine] diff --git a/air-interpreter/src/wasm_bindgen.rs b/air-interpreter/src/wasm_bindgen.rs index 42171d86..37d0a738 100644 --- a/air-interpreter/src/wasm_bindgen.rs +++ b/air-interpreter/src/wasm_bindgen.rs @@ -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, data: Vec, log_level: &str) -> String { +pub fn invoke( + air: String, + prev_data: Vec, + data: Vec, + params: Vec, + call_results: Vec, + 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(¶ms).expect("cannot parse RunParameters"); + + let outcome = execute_air(air, prev_data, data, params, call_results); serde_json::to_string(&outcome).expect("Cannot parse InterpreterOutcome") } diff --git a/air/Cargo.toml b/air/Cargo.toml index 06e005ac..4ae91c54 100644 --- a/air/Cargo.toml +++ b/air/Cargo.toml @@ -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 = [] diff --git a/air/README.md b/air/README.md new file mode 100644 index 00000000..532c3c43 --- /dev/null +++ b/air/README.md @@ -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, + + /// So-called current data that was sent with a particle from the interpreter on some other peer. + data: Vec, + + /// Running parameters that includes different settings. + params: RunParameters, + + /// Results of calling services. + call_results: Vec, +) -> 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, + + /// Public keys of peers that should receive data. + pub next_peer_pks: Vec, + + /// Collected parameters of all met call instructions that could be executed on a current peer. + pub call_requests: Vec, +} +``` + +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` 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`) 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.

+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). diff --git a/air/src/build_targets/mod.rs b/air/src/build_targets/mod.rs deleted file mode 100644 index 17a091ca..00000000 --- a/air/src/build_targets/mod.rs +++ /dev/null @@ -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; diff --git a/air/src/build_targets/wasm_bindgen_target.rs b/air/src/build_targets/wasm_bindgen_target.rs deleted file mode 100644 index f28b65b5..00000000 --- a/air/src/build_targets/wasm_bindgen_target.rs +++ /dev/null @@ -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 { - 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; -} diff --git a/air/src/execution_step/air/call.rs b/air/src/execution_step/air/call.rs index 4e8b8a10..b077e572 100644 --- a/air/src/execution_step/air/call.rs +++ b/air/src/execution_step/air/call.rs @@ -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); diff --git a/air/src/execution_step/air/call/call_result_setter.rs b/air/src/execution_step/air/call/call_result_setter.rs index 37480b66..38232141 100644 --- a/air/src/execution_step/air/call/call_result_setter.rs +++ b/air/src/execution_step/air/call/call_result_setter.rs @@ -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); } diff --git a/air/src/execution_step/air/call/prev_result_handler.rs b/air/src/execution_step/air/call/prev_result_handler.rs new file mode 100644 index 00000000..2392dff0 --- /dev/null +++ b/air/src/execution_step/air/call/prev_result_handler.rs @@ -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 { + 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 { + 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> { + 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))) + } + } +} diff --git a/air/src/execution_step/air/call/resolved_call.rs b/air/src/execution_step/air/call/resolved_call.rs index 11efd6fb..38ec0bb1 100644 --- a/air/src/execution_step/air/call/resolved_call.rs +++ b/air/src/execution_step/air/call/resolved_call.rs @@ -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 { 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 { - 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); - } - - 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> { - 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))) + 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(()) + } } + Some(_) => crate::exec_err!(ExecutionError::IterableShadowing(scalar_name.to_string())), + None => Ok(()), } } diff --git a/air/src/execution_step/air/call/utils.rs b/air/src/execution_step/air/call/utils.rs deleted file mode 100644 index 56ab05f5..00000000 --- a/air/src/execution_step/air/call/utils.rs +++ /dev/null @@ -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 { - 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 -} diff --git a/air/src/execution_step/air/fold/fold_state.rs b/air/src/execution_step/air/fold/fold_state.rs index a0d9ff37..b049b532 100644 --- a/air/src/execution_step/air/fold/fold_state.rs +++ b/air/src/execution_step/air/fold/fold_state.rs @@ -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> { diff --git a/air/src/execution_step/air/mod.rs b/air/src/execution_step/air/mod.rs index 020b6327..d23bf6b5 100644 --- a/air/src/execution_step/air/mod.rs +++ b/air/src/execution_step/air/mod.rs @@ -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) { diff --git a/air/src/execution_step/errors.rs b/air/src/execution_step/errors.rs index d2c3e04b..f9046841 100644 --- a/air/src/execution_step/errors.rs +++ b/air/src/execution_step/errors.rs @@ -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 for Rc { diff --git a/air/src/execution_step/execution_context/context.rs b/air/src/execution_step/execution_context/context.rs index 2b5a967d..497c2185 100644 --- a/air/src/execution_step/execution_context/context.rs +++ b/air/src/execution_step/execution_context/context.rs @@ -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; diff --git a/air/src/execution_step/execution_context/instructions_tracker.rs b/air/src/execution_step/execution_context/instructions_tracker.rs index 77b27ed3..14684180 100644 --- a/air/src/execution_step/execution_context/instructions_tracker.rs +++ b/air/src/execution_step/execution_context/instructions_tracker.rs @@ -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; } } diff --git a/air/src/execution_step/execution_context/mod.rs b/air/src/execution_step/execution_context/mod.rs index ca699245..c1dca9ab 100644 --- a/air/src/execution_step/execution_context/mod.rs +++ b/air/src/execution_step/execution_context/mod.rs @@ -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; diff --git a/air/src/execution_step/trace_handler/handler.rs b/air/src/execution_step/trace_handler/handler.rs index d20aaaf2..236cb5ee 100644 --- a/air/src/execution_step/trace_handler/handler.rs +++ b/air/src/execution_step/trace_handler/handler.rs @@ -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 diff --git a/air/src/execution_step/trace_handler/state_automata/errors.rs b/air/src/execution_step/trace_handler/state_automata/errors.rs index 42b68394..4ca41c6b 100644 --- a/air/src/execution_step/trace_handler/state_automata/errors.rs +++ b/air/src/execution_step/trace_handler/state_automata/errors.rs @@ -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:?}'")] diff --git a/air/src/execution_step/trace_handler/state_automata/fsm_queue.rs b/air/src/execution_step/trace_handler/state_automata/fsm_queue.rs index 9a27f504..49f2b96a 100644 --- a/air/src/execution_step/trace_handler/state_automata/fsm_queue.rs +++ b/air/src/execution_step/trace_handler/state_automata/fsm_queue.rs @@ -24,7 +24,7 @@ use std::collections::HashMap; #[derive(Debug, Default)] pub(crate) struct FSMKeeper { par_stack: Vec, - fold_map: HashMap, + fold_map: HashMap, } 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 { + pub(crate) fn extract_fold(&mut self, fold_id: u32) -> FSMResult { self.fold_map .remove(&fold_id) .ok_or(StateFSMError::FoldFSMNotFound(fold_id)) diff --git a/air/src/lib.rs b/air/src/lib.rs index 1f96a6ae..a3b3b1a8 100644 --- a/air/src/lib.rs +++ b/air/src/lib.rs @@ -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; diff --git a/air/src/preparation_step/errors.rs b/air/src/preparation_step/errors.rs index 2478785c..e7c641b8 100644 --- a/air/src/preparation_step/errors.rs +++ b/air/src/preparation_step/errors.rs @@ -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), - /// 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), } impl PreparationError { diff --git a/air/src/preparation_step/preparation.rs b/air/src/preparation_step/preparation.rs index b12b1d4a..b3ba70fe 100644 --- a/air/src/preparation_step/preparation.rs +++ b/air/src/preparation_step/preparation.rs @@ -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 = Result; @@ -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> { 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::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> { - 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> { + 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) diff --git a/air/src/runner.rs b/air/src/runner.rs index 81d05a42..78eb94ec 100644 --- a/air/src/runner.rs +++ b/air/src/runner.rs @@ -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, data: Vec) -> 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, data: Vec, + params: RunParameters, + call_results: Vec, +) -> 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, + data: Vec, + params: RunParameters, + call_results: Vec, ) -> Result { 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 { + 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) } diff --git a/air/src/runner/outcome.rs b/air/src/runner/outcome.rs index 7988f819..8d594b61 100644 --- a/air/src/runner/outcome.rs +++ b/air/src/runner/outcome.rs @@ -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>, 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>, err: Rc 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, } } diff --git a/air/tests/test_module/instructions/ap.rs b/air/tests/test_module/instructions/ap.rs index df5b08da..2ed28932 100644 --- a/air/tests/test_module/instructions/ap.rs +++ b/air/tests/test_module/instructions/ap.rs @@ -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); diff --git a/air/tests/test_module/instructions/call.rs b/air/tests/test_module/instructions/call.rs index dc1d555c..58b1111e 100644 --- a/air/tests/test_module/instructions/call.rs +++ b/air/tests/test_module/instructions/call.rs @@ -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 { - 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); diff --git a/air/tests/test_module/instructions/fold.rs b/air/tests/test_module/instructions/fold.rs index e23c83c5..0c5e88c6 100644 --- a/air/tests/test_module/instructions/fold.rs +++ b/air/tests/test_module/instructions/fold.rs @@ -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 { - 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 { + 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#" diff --git a/air/tests/test_module/instructions/match_.rs b/air/tests/test_module/instructions/match_.rs index 5e40afc0..51567a6e 100644 --- a/air/tests/test_module/instructions/match_.rs +++ b/air/tests/test_module/instructions/match_.rs @@ -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"; diff --git a/air/tests/test_module/instructions/mismatch.rs b/air/tests/test_module/instructions/mismatch.rs index fdb8dcdb..f59631fe 100644 --- a/air/tests/test_module/instructions/mismatch.rs +++ b/air/tests/test_module/instructions/mismatch.rs @@ -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"; diff --git a/air/tests/test_module/instructions/par.rs b/air/tests/test_module/instructions/par.rs index 4b979b00..4adf1961 100644 --- a/air/tests/test_module/instructions/par.rs +++ b/air/tests/test_module/instructions/par.rs @@ -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() { diff --git a/air/tests/test_module/instructions/seq.rs b/air/tests/test_module/instructions/seq.rs index 7f1c573a..416e4e23 100644 --- a/air/tests/test_module/instructions/seq.rs +++ b/air/tests/test_module/instructions/seq.rs @@ -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() { diff --git a/air/tests/test_module/instructions/xor.rs b/air/tests/test_module/instructions/xor.rs index 97017629..92608da8 100644 --- a/air/tests/test_module/instructions/xor.rs +++ b/air/tests/test_module/instructions/xor.rs @@ -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); } diff --git a/air/tests/test_module/integration/air_basic.rs b/air/tests/test_module/integration/air_basic.rs index 2514e7c3..f3b38db3 100644 --- a/air/tests/test_module/integration/air_basic.rs +++ b/air/tests/test_module/integration/air_basic.rs @@ -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 { - 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"); diff --git a/air/tests/test_module/integration/ap_with_fold.rs b/air/tests/test_module/integration/ap_with_fold.rs index 1f43b8b7..9d64de6a 100644 --- a/air/tests/test_module/integration/ap_with_fold.rs +++ b/air/tests/test_module/integration/ap_with_fold.rs @@ -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); diff --git a/air/tests/test_module/integration/call_evidence_basic.rs b/air/tests/test_module/integration/call_evidence_basic.rs index 08ead514..f8d8fb21 100644 --- a/air/tests/test_module/integration/call_evidence_basic.rs +++ b/air/tests/test_module/integration/call_evidence_basic.rs @@ -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 { - 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 { - 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 { - 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); diff --git a/air/tests/test_module/integration/dashboard.rs b/air/tests/test_module/integration/dashboard.rs index c6fb5819..17e93f20 100644 --- a/air/tests/test_module/integration/dashboard.rs +++ b/air/tests/test_module/integration/dashboard.rs @@ -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 { 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| -> 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 { - 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 { - let service_name = match &args.function_args[0] { - IValue::String(str) => str, - _ => unreachable!(), - }; + Box::new(move |params| -> CallServiceResult { + let args: Vec = serde_json::from_value(JValue::Array(params.arguments)).unwrap(); + let t_args = args.iter().map(|s| s.as_str()).collect::>(); + 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) -> Ca } struct AVMState { - vm: AVM, + vm: TestRunner, peer_id: String, prev_result: Vec, } @@ -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::>(); // -> 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() ); diff --git a/air/tests/test_module/integration/data_merge.rs b/air/tests/test_module/integration/data_merge.rs index f15c3a61..fdba4af5 100644 --- a/air/tests/test_module/integration/data_merge.rs +++ b/air/tests/test_module/integration/data_merge.rs @@ -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 { - 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> = 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 = 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, diff --git a/air/tests/test_module/integration/empty_array.rs b/air/tests/test_module/integration/empty_array.rs index a3894ae3..8d6d2a6c 100644 --- a/air/tests/test_module/integration/empty_array.rs +++ b/air/tests/test_module/integration/empty_array.rs @@ -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() { diff --git a/air/tests/test_module/integration/flattening.rs b/air/tests/test_module/integration/flattening.rs index f46b83e2..f2945c9e 100644 --- a/air/tests/test_module/integration/flattening.rs +++ b/air/tests/test_module/integration/flattening.rs @@ -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 { + 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 = serde_json::from_str(call_args).expect("json deserialization shouldn't fail"); + let call_args: Vec = + 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); diff --git a/air/tests/test_module/integration/issue_137.rs b/air/tests/test_module/integration/issue_137.rs index 5f5597df..37d8b53f 100644 --- a/air/tests/test_module/integration/issue_137.rs +++ b/air/tests/test_module/integration/issue_137.rs @@ -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 diff --git a/air/tests/test_module/integration/join.rs b/air/tests/test_module/integration/join.rs index 5154f175..1c947668 100644 --- a/air/tests/test_module/integration/join.rs +++ b/air/tests/test_module/integration/join.rs @@ -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 { - 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 { - 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 { - 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"); diff --git a/air/tests/test_module/integration/join_behaviour.rs b/air/tests/test_module/integration/join_behaviour.rs index 94608c28..256d9741 100644 --- a/air/tests/test_module/integration/join_behaviour.rs +++ b/air/tests/test_module/integration/join_behaviour.rs @@ -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); diff --git a/air/tests/test_module/integration/json_path.rs b/air/tests/test_module/integration/json_path.rs index 94b33ce8..923cd4f2 100644 --- a/air/tests/test_module/integration/json_path.rs +++ b/air/tests/test_module/integration/json_path.rs @@ -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#" diff --git a/air/tests/test_module/integration/last_error.rs b/air/tests/test_module/integration/last_error.rs index 2861364a..425a02e4 100644 --- a/air/tests/test_module/integration/last_error.rs +++ b/air/tests/test_module/integration/last_error.rs @@ -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, tetraplets_to_check: ArgToCheck>>, ) -> CallServiceClosure { - Box::new(move |args| -> Option { - let call_args = match &args.function_args[2] { - IValue::String(str) => str, - _ => unreachable!(), - }; - + Box::new(move |params| -> CallServiceResult { let mut call_args: Vec = - 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> = - 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(); diff --git a/air/tests/test_module/integration/network_explore.rs b/air/tests/test_module/integration/network_explore.rs index 9369c48f..bebd849e 100644 --- a/air/tests/test_module/integration/network_explore.rs +++ b/air/tests/test_module/integration/network_explore.rs @@ -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"); diff --git a/air/tests/test_module/integration/security_tetraplets.rs b/air/tests/test_module/integration/security_tetraplets.rs index cacca7f9..f637cfc1 100644 --- a/air/tests/test_module/integration/security_tetraplets.rs +++ b/air/tests/test_module/integration/security_tetraplets.rs @@ -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>) { 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 { - 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>) { #[test] fn simple_fold() { - let return_numbers_call_service: CallServiceClosure = Box::new(|_| -> Option { - 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 { - 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 { - 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> = serde_json::from_str(tetraplets).unwrap(); + let host_func: CallServiceClosure = Box::new(move |params| -> CallServiceResult { + let tetraplets = serde_json::to_vec(¶ms.tetraplets).expect("default serializer shouldn't fail"); + let tetraplets: Vec> = + 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"; diff --git a/air/tests/test_module/integration/streams.rs b/air/tests/test_module/integration/streams.rs index 676a828b..08dcd0f4 100644 --- a/air/tests/test_module/integration/streams.rs +++ b/air/tests/test_module/integration/streams.rs @@ -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 { - let call_args = match &args.function_args[2] { - IValue::String(str) => str, - _ => unreachable!(), - }; - + Box::new(move |params| -> CallServiceResult { let actual_call_args: Vec> = - 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![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!( diff --git a/air/tests/test_module/integration/streams_early_exit.rs b/air/tests/test_module/integration/streams_early_exit.rs index 706a8761..1daa1d4d 100644 --- a/air/tests/test_module/integration/streams_early_exit.rs +++ b/air/tests/test_module/integration/streams_early_exit.rs @@ -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![ diff --git a/avm/client/build_wasm.ps1 b/avm/client/build_wasm.ps1 index ac77e0d6..3d1ee20d 100644 --- a/avm/client/build_wasm.ps1 +++ b/avm/client/build_wasm.ps1 @@ -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" diff --git a/avm/client/build_wasm.sh b/avm/client/build_wasm.sh index aefd65eb..a5911097 100755 --- a/avm/client/build_wasm.sh +++ b/avm/client/build_wasm.sh @@ -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 diff --git a/avm/client/package-lock.json b/avm/client/package-lock.json index 1563f042..eaf236fd 100644 --- a/avm/client/package-lock.json +++ b/avm/client/package-lock.json @@ -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": { diff --git a/avm/client/package.json b/avm/client/package.json index 519f888a..6124fecc 100644 --- a/avm/client/package.json +++ b/avm/client/package.json @@ -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" } } diff --git a/avm/client/src/__test__/test.spec.ts b/avm/client/src/__test__/test.spec.ts index e728957f..40751f7d 100644 --- a/avm/client/src/__test__/test.spec.ts +++ b/avm/client/src/__test__/test.spec.ts @@ -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(); }); }); diff --git a/avm/client/src/index.ts b/avm/client/src/index.ts index 697fe54b..af0d1d13 100644 --- a/avm/client/src/index.ts +++ b/avm/client/src/index.ts @@ -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; + 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, + }; } } diff --git a/avm/client/src/wrapper.ts b/avm/client/src/wrapper.ts index c09a62cf..7031078f 100644 --- a/avm/client/src/wrapper.ts +++ b/avm/client/src/wrapper.ts @@ -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); -} diff --git a/avm/server/Cargo.toml b/avm/server/Cargo.toml index 45441eed..aba00d8f 100644 --- a/avm/server/Cargo.toml +++ b/avm/server/Cargo.toml @@ -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 = [] diff --git a/avm/server/src/avm.rs b/avm/server/src/avm.rs index 5bbe94e5..7c3b6912 100644 --- a/avm/server/src/avm.rs +++ b/avm/server/src/avm.rs @@ -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 { + runner: SendSafeRunner, + data_store: AVMDataStore, } -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>, -} - -impl AVM { +impl AVM { /// Create AVM with provided config. - pub fn new(config: AVMConfig) -> Result { - use AVMError::{CreateVaultDirError, InvalidDataStorePath}; + pub fn new(config: AVMConfig) -> AVMResult { + let AVMConfig { + air_wasm_path, + current_peer_id, + logging_mask, + mut data_store, + } = config; - let current_particle: Arc> = <_>::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, air: impl Into, data: impl Into>, - particle_id: impl Into, - ) -> Result { - use AVMError::PersistDataError; - - let particle_id = particle_id.into(); + init_user_id: impl Into, + particle_id: &str, + call_results: CallResults, + ) -> AVMResult { 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>, - data: impl Into>, - init_user_id: impl Into, - air: impl Into, -) -> Vec { - 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>, - call_service: CallServiceClosure, - vault_dir: PathBuf, -) -> HostImportDescriptor { - let call_service_closure: HostExportedFunc = Box::new(move |_, ivalues: Vec| { - let params = { - let lock = params.lock(); - lock.deref().clone() - }; - - let create_vault = create_vault_effect(&vault_dir, ¶ms.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, - air: impl Into, - prev_data: impl Into>, - data: impl Into>, - ) -> Result { - 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) - } } diff --git a/avm/server/src/config.rs b/avm/server/src/config.rs index fcce77dc..ce7c3bcc 100644 --- a/avm/server/src/config.rs +++ b/avm/server/src/config.rs @@ -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 { /// 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, } diff --git a/avm/server/src/data_store.rs b/avm/server/src/data_store.rs deleted file mode 100644 index 3901c502..00000000 --- a/avm/server/src/data_store.rs +++ /dev/null @@ -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 { - 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) -} diff --git a/avm/server/src/errors.rs b/avm/server/src/errors.rs index 02aa191e..c6766f5c 100644 --- a/avm/server/src/errors.rs +++ b/avm/server/src/errors.rs @@ -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 { + /// 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, reason: &'static str, }, + + /// AIR interpreter result deserialization errors. + #[error("{0}")] + InterpreterResultDeError(String), + + /// FaaS call returns Vec 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), + + /// This errors are encountered from an call results/params se/de. + #[error(transparent)] + CallSeDeErrors(#[from] CallSeDeErrors), } -impl From 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, + error: SerdeError, + }, + + /// Errors encountered while trying to deserialize arguments from call parameters returned + /// by the interpreter. In the corresponding struct such arguments are Vec 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> 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, + }, } diff --git a/avm/server/src/interface/call_request_parameters.rs b/avm/server/src/interface/call_request_parameters.rs new file mode 100644 index 00000000..250bfeb1 --- /dev/null +++ b/avm/server/src/interface/call_request_parameters.rs @@ -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; + +/// 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, + + /// Tetraplets that should be passed to the service. + pub tetraplets: Vec>, +} + +impl CallRequestParams { + pub fn new( + service_id: String, + function_name: String, + arguments: Vec, + tetraplets: Vec>, + ) -> Self { + Self { + service_id, + function_name, + arguments, + tetraplets, + } + } + + pub(crate) fn from_raw( + call_params: air_interpreter_interface::CallRequestParams, + ) -> RunnerResult { + let arguments: Vec = + serde_json::from_str(&call_params.arguments).map_err(|de_error| { + CallSeDeErrors::CallParamsArgsDeFailed { + call_params: call_params.clone(), + de_error, + } + })?; + + let tetraplets: Vec> = 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) -> RunnerResult { + 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::>() +} diff --git a/avm/server/src/interface/call_service_result.rs b/avm/server/src/interface/call_service_result.rs new file mode 100644 index 00000000..01af6917 --- /dev/null +++ b/avm/server/src/interface/call_service_result.rs @@ -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; +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) + } +} diff --git a/avm/server/src/interface/mod.rs b/avm/server/src/interface/mod.rs new file mode 100644 index 00000000..0750c721 --- /dev/null +++ b/avm/server/src/interface/mod.rs @@ -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::*; diff --git a/avm/server/src/interface/outcome.rs b/avm/server/src/interface/outcome.rs new file mode 100644 index 00000000..8b608164 --- /dev/null +++ b/avm/server/src/interface/outcome.rs @@ -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, + + /// 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, +} + +#[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, + call_requests: CallRequests, + next_peer_pks: Vec, + ) -> Self { + Self { + data, + call_requests, + next_peer_pks, + } + } + + pub(crate) fn from_raw_outcome(raw_outcome: RawAVMOutcome) -> AVMResult { + 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, + } + } +} diff --git a/avm/server/src/interface/raw_outcome.rs b/avm/server/src/interface/raw_outcome.rs new file mode 100644 index 00000000..bdafdb65 --- /dev/null +++ b/avm/server/src/interface/raw_outcome.rs @@ -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, + pub call_requests: CallRequests, + pub next_peer_pks: Vec, +} + +impl RawAVMOutcome { + pub(crate) fn from_interpreter_outcome(outcome: InterpreterOutcome) -> RunnerResult { + 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) + } +} diff --git a/avm/server/src/lib.rs b/avm/server/src/lib.rs index 93a2bad8..915728d3 100644 --- a/avm/server/src/lib.rs +++ b/avm/server/src/lib.rs @@ -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 = std::result::Result; +pub use avm_data_store::DataStore; + +pub type AVMDataStore = Box + Send + Sync + 'static>; + +pub type AVMResult = std::result::Result>; + +pub(crate) use errors::RunnerError; +pub(crate) type RunnerResult = std::result::Result; diff --git a/avm/server/src/runner.rs b/avm/server/src/runner.rs new file mode 100644 index 00000000..1d0172cc --- /dev/null +++ b/avm/server/src/runner.rs @@ -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, + logging_mask: i32, + ) -> RunnerResult { + 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, + prev_data: impl Into>, + data: impl Into>, + init_user_id: impl Into, + call_results: CallResults, + ) -> RunnerResult { + 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, + prev_data: impl Into>, + data: impl Into>, + init_peer_id: impl Into, + current_peer_id: String, + call_results: CallResults, +) -> Vec { + 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) -> RunnerResult { + use RunnerError::IncorrectInterpreterResult; + + if ivalues.len() != 1 { + return Err(IncorrectInterpreterResult(ivalues)); + } + + Ok(ivalues.remove(0)) +} diff --git a/crates/air-parser/src/parser/air.lalrpop b/crates/air-parser/src/parser/air.lalrpop index 0fb09423..fa36d8e9 100644 --- a/crates/air-parser/src/parser/air.lalrpop +++ b/crates/air-parser/src/parser/air.lalrpop @@ -63,7 +63,7 @@ Instr: Box> = { 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)) }, diff --git a/crates/air-parser/src/parser/air.rs b/crates/air-parser/src/parser/air.rs index f60bc206..44527ee4 100644 --- a/crates/air-parser/src/parser/air.rs +++ b/crates/air-parser/src/parser/air.rs @@ -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)) } diff --git a/crates/air-parser/src/parser/validator.rs b/crates/air-parser/src/parser/validator.rs index ed598172..5863532e 100644 --- a/crates/air-parser/src/parser/validator.rs +++ b/crates/air-parser/src/parser/validator.rs @@ -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); } diff --git a/crates/data-store/Cargo.toml b/crates/data-store/Cargo.toml new file mode 100644 index 00000000..c08528ed --- /dev/null +++ b/crates/data-store/Cargo.toml @@ -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" diff --git a/avm/server/src/call_service.rs b/crates/data-store/src/lib.rs similarity index 61% rename from avm/server/src/call_service.rs rename to crates/data-store/src/lib.rs index e5e61b37..b92fa59e 100644 --- a/avm/server/src/call_service.rs +++ b/crates/data-store/src/lib.rs @@ -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 { + fn initialize(&mut self) -> Result<(), E>; -use std::path::PathBuf; + fn store_data(&mut self, data: &[u8], key: &str) -> Result<(), E>; -pub type Effect = Box Result + 'static>; -pub struct CallServiceArgs { - pub particle_parameters: ParticleParameters, - pub function_args: Vec, - pub create_vault: Effect, + fn read_data(&mut self, key: &str) -> Result, E>; + + fn cleanup_data(&mut self, key: &str) -> Result<(), E>; } - -pub type CallServiceClosure = Box Option + 'static>; diff --git a/crates/interpreter-data/CHANGELOG.md b/crates/interpreter-data/CHANGELOG.md new file mode 100644 index 00000000..79e7f05f --- /dev/null +++ b/crates/interpreter-data/CHANGELOG.md @@ -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. diff --git a/crates/interpreter-data/src/executed_state.rs b/crates/interpreter-data/src/executed_state.rs index d8d4a41d..6015ae4a 100644 --- a/crates/interpreter-data/src/executed_state.rs +++ b/crates/interpreter-data/src/executed_state.rs @@ -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), + PeerIdWithCallId { peer_id: Rc, 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), + RequestSentBy(Sender), /// A corresponding call's been already executed with such value as a result. Executed(Value), diff --git a/crates/interpreter-data/src/executed_state/impls.rs b/crates/interpreter-data/src/executed_state/impls.rs index 5a8c9862..ae1b042f 100644 --- a/crates/interpreter-data/src/executed_state/impls.rs +++ b/crates/interpreter-data/src/executed_state/impls.rs @@ -33,8 +33,12 @@ impl ParResult { } impl CallResult { - pub fn sent(sender: impl Into) -> CallResult { - CallResult::RequestSentBy(Rc::new(sender.into())) + pub fn sent_peer_id(peer_id: Rc) -> CallResult { + CallResult::RequestSentBy(Sender::PeerId(peer_id)) + } + + pub fn sent_peer_id_with_call_id(peer_id: Rc, call_id: u32) -> CallResult { + CallResult::RequestSentBy(Sender::PeerIdWithCallId { peer_id, call_id }) } pub fn executed_scalar(value: Rc) -> 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) + } + } + } +} diff --git a/crates/interpreter-data/src/executed_state/se_de.rs b/crates/interpreter-data/src/executed_state/se_de.rs index 5fe95d67..a2bbdfb0 100644 --- a/crates/interpreter-data/src/executed_state/se_de.rs +++ b/crates/interpreter-data/src/executed_state/se_de.rs @@ -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(value: &Sender, serializer: S) -> Result + 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 + 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(self, raw_sender: &str) -> Result { + 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::().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 {}) } } diff --git a/crates/interpreter-data/src/interpreter_data.rs b/crates/interpreter-data/src/interpreter_data.rs index 39d78dd4..c8a34bf4 100644 --- a/crates/interpreter-data/src/interpreter_data.rs +++ b/crates/interpreter-data/src/interpreter_data.rs @@ -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::(&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::(&data_0_2_1_se); + assert!(data_0_2_0.is_ok()); + } +} diff --git a/crates/interpreter-data/src/lib.rs b/crates/interpreter-data/src/lib.rs index 44fd7e0e..6de65a69 100644 --- a/crates/interpreter-data/src/lib.rs +++ b/crates/interpreter-data/src/lib.rs @@ -24,5 +24,5 @@ use once_cell::sync::Lazy; use std::str::FromStr; pub static DATA_FORMAT_VERSION: Lazy = 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") }); diff --git a/crates/interpreter-interface/Cargo.toml b/crates/interpreter-interface/Cargo.toml index c59885f7..e3b8cc35 100644 --- a/crates/interpreter-interface/Cargo.toml +++ b/crates/interpreter-interface/Cargo.toml @@ -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" diff --git a/crates/interpreter-interface/src/call_request_parameters.rs b/crates/interpreter-interface/src/call_request_parameters.rs new file mode 100644 index 00000000..86a9f17b --- /dev/null +++ b/crates/interpreter-interface/src/call_request_parameters.rs @@ -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; + +/// 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 of arguments that should be passed to a service. + pub arguments: String, + + /// Serialized to JSON string Vec> 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, + } + } +} diff --git a/crates/interpreter-interface/src/call_service_result.rs b/crates/interpreter-interface/src/call_service_result.rs new file mode 100644 index 00000000..65bdbc4d --- /dev/null +++ b/crates/interpreter-interface/src/call_service_result.rs @@ -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; +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) + } +} diff --git a/crates/interpreter-interface/src/interpreter_outcome.rs b/crates/interpreter-interface/src/interpreter_outcome.rs new file mode 100644 index 00000000..727a4b14 --- /dev/null +++ b/crates/interpreter-interface/src/interpreter_outcome.rs @@ -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, + + /// Public keys of peers that should receive data. + pub next_peer_pks: Vec, + + /// Collected parameters of all met call instructions that could be executed on a current peer. + pub call_requests: Vec, +} + +impl InterpreterOutcome { + pub fn from_ivalue(ivalue: IValue) -> Result { + 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, 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 { + 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 { + 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, String> { + let byte_vec = match ivalue { + IValue::Array(array) => { + let array: Result, _> = 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 for {}, got {:?}", + field_name, v + )) + } + }; + + Ok(byte_vec) +} + +fn try_as_string_vec(ivalue: IValue, field_name: &str) -> Result, 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::, _>>()?; + + Ok(array) + } + v => Err(format!("expected an array for {}, got {:?}", field_name, v)), + } +} diff --git a/crates/interpreter-interface/src/lib.rs b/crates/interpreter-interface/src/lib.rs index 948054bf..f63d9883 100644 --- a/crates/interpreter-interface/src/lib.rs +++ b/crates/interpreter-interface/src/lib.rs @@ -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, - - /// Public keys of peers that should receive data. - pub next_peer_pks: Vec, -} - -impl InterpreterOutcome { - pub fn from_ivalues(mut ivalues: Vec) -> Result { - 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, _> = 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 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::, _>>()?; - - 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::*; diff --git a/air/src/build_targets/marine_target.rs b/crates/interpreter-interface/src/run_parameters.rs similarity index 54% rename from air/src/build_targets/marine_target.rs rename to crates/interpreter-interface/src/run_parameters.rs index f202a3aa..f1b46e76 100644 --- a/air/src/build_targets/marine_target.rs +++ b/crates/interpreter-interface/src/run_parameters.rs @@ -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 { - 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, } diff --git a/crates/test-module/src/main.rs b/crates/test-module/src/main.rs index b4d0b2e7..954495f0 100644 --- a/crates/test-module/src/main.rs +++ b/crates/test-module/src/main.rs @@ -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, diff --git a/crates/test-utils/Cargo.toml b/crates/test-utils/Cargo.toml index f4f2104d..5f1b2734 100644 --- a/crates/test-utils/Cargo.toml +++ b/crates/test-utils/Cargo.toml @@ -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" diff --git a/crates/test-utils/src/call_services.rs b/crates/test-utils/src/call_services.rs index 93f451b4..b73e8d74 100644 --- a/crates/test-utils/src/call_services.rs +++ b/crates/test-utils/src/call_services.rs @@ -15,139 +15,56 @@ */ use super::*; + +use serde_json::json; use std::collections::HashMap; pub fn unit_call_service() -> CallServiceClosure { - Box::new(|_| -> Option { - 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 { - let arg = match &args.function_args[2] { - IValue::String(str) => str, - _ => unreachable!(), - }; - - let arg: Vec = 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 { - let arg = match &args.function_args[2] { - IValue::String(str) => str, - _ => unreachable!(), - }; - - let arg: Vec = 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 { - let arg = match &args.function_args[2] { - IValue::String(str) => str, - _ => unreachable!(), +pub fn set_variables_call_service( + variables_mapping: HashMap, +) -> 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 = 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) -> CallServiceClosure { - let json = json.into(); - Box::new(move |_| -> Option { - Some(IValue::Record( - NEVec::new(vec![IValue::S32(0), IValue::String(json.clone())]).unwrap(), - )) - }) -} - -pub fn set_variables_call_service(ret_mapping: HashMap) -> CallServiceClosure { - Box::new(move |args| -> Option { - 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) -> CallServiceClosure { let ret_str = ret_str.into(); - Box::new(move |_| -> Option { - 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) -> CallServiceClosure { let fallible_service_id = fallible_service_id.into(); - Box::new(move |args| -> Option { - 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")) } }) } diff --git a/crates/test-utils/src/executed_state.rs b/crates/test-utils/src/executed_state.rs index 2687b4b2..2f44d1b2 100644 --- a/crates/test-utils/src/executed_state.rs +++ b/crates/test-utils/src/executed_state.rs @@ -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>, generation: u32) -> E } pub fn request_sent_by(sender: impl Into) -> 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 { diff --git a/crates/test-utils/src/lib.rs b/crates/test-utils/src/lib.rs index 0ceb6ce0..cd557797 100644 --- a/crates/test-utils/src/lib.rs +++ b/crates/test-utils/src/lib.rs @@ -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) -> 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 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 { - 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()); diff --git a/crates/test-utils/src/test_runner.rs b/crates/test-utils/src/test_runner.rs new file mode 100644 index 00000000..5c2fcec3 --- /dev/null +++ b/crates/test-utils/src/test_runner.rs @@ -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, + prev_data: impl Into>, + data: impl Into>, + init_user_id: impl Into, + ) -> Result { + 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::>(); + 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::>(); + + prev_data = outcome.data; + data = vec![]; + } + } +} + +pub fn create_avm( + call_service: CallServiceClosure, + current_peer_id: impl Into, +) -> 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, + } +}