feat(aquavm-air)!: signature checking (#607)

This commit is contained in:
Ivan Boldyrev 2023-10-13 23:19:02 +04:00 committed by GitHub
parent b6c4eaaddc
commit 8a07613027
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
136 changed files with 7177 additions and 5398 deletions

View File

@ -86,6 +86,12 @@ jobs:
for san in address leak; do
RUSTFLAGS="$RUSTFLAGS -Z sanitizer=$san" cargo test --features test_with_native_code --target x86_64-unknown-linux-gnu
done
- name: Run signature tests with test_with_native_code
env:
RUST_TEST_THREADS: 1
run: |
# Temporary solution until signatures works: it depends on other PRs
cargo test --features test_with_native_code,gen_signatures,check_signatures --target x86_64-unknown-linux-gnu features::signatures
- name: Check native aquavm-air-cli
run: cargo check --package aquavm-air-cli --no-default-features

4
.gitmodules vendored Normal file
View File

@ -0,0 +1,4 @@
[submodule "benches/performance_metering"]
path = benches/performance_metering
url = git@github.com:fluencelabs/aquavm-benchmark-data.git
branch = feat/VM-221-signature-checking

93
Cargo.lock generated
View File

@ -100,6 +100,7 @@ dependencies = [
"multihash 0.18.1",
"serde",
"serde_json",
"sha2 0.10.7",
]
[[package]]
@ -110,6 +111,7 @@ dependencies = [
"air-interpreter-signatures",
"air-utils",
"aquavm-air-parser",
"fluence-keypair",
"newtype_derive",
"num-traits",
"once_cell",
@ -117,6 +119,7 @@ dependencies = [
"semver 1.0.18",
"serde",
"serde_json",
"thiserror",
"tracing",
]
@ -135,10 +138,11 @@ name = "air-interpreter-signatures"
version = "0.1.1"
dependencies = [
"air-interpreter-cid",
"borsh 0.10.3",
"borsh-derive 0.10.3",
"bs58 0.5.0",
"fluence-keypair",
"serde",
"serde_json",
]
[[package]]
@ -194,6 +198,7 @@ dependencies = [
name = "air-testing-framework"
version = "0.6.1"
dependencies = [
"air-interpreter-signatures",
"air-test-utils",
"aquavm-air-parser",
"itertools",
@ -201,6 +206,7 @@ dependencies = [
"nom",
"nom_locate",
"pretty_assertions 1.4.0",
"regex",
"serde_json",
"strum",
]
@ -333,9 +339,11 @@ dependencies = [
"air-trace-handler",
"air-utils",
"aquavm-air-parser",
"borsh 0.10.3",
"concat-idents",
"criterion 0.3.6",
"csv",
"ed25519-dalek",
"env_logger 0.7.1",
"fluence-app-service",
"fluence-keypair",
@ -806,7 +814,17 @@ version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15bf3650200d8bffa99015595e10f1fbd17de07abbc25bb067da79e769939bfa"
dependencies = [
"borsh-derive",
"borsh-derive 0.9.3",
"hashbrown 0.11.2",
]
[[package]]
name = "borsh"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b"
dependencies = [
"borsh-derive 0.10.3",
"hashbrown 0.11.2",
]
@ -816,8 +834,21 @@ version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6441c552f230375d18e3cc377677914d2ca2b0d36e52129fe15450a2dce46775"
dependencies = [
"borsh-derive-internal",
"borsh-schema-derive-internal",
"borsh-derive-internal 0.9.3",
"borsh-schema-derive-internal 0.9.3",
"proc-macro-crate 0.1.5",
"proc-macro2",
"syn 1.0.109",
]
[[package]]
name = "borsh-derive"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0754613691538d51f329cce9af41d7b7ca150bc973056f1156611489475f54f7"
dependencies = [
"borsh-derive-internal 0.10.3",
"borsh-schema-derive-internal 0.10.3",
"proc-macro-crate 0.1.5",
"proc-macro2",
"syn 1.0.109",
@ -834,6 +865,17 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "borsh-derive-internal"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "borsh-schema-derive-internal"
version = "0.9.3"
@ -845,6 +887,17 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "borsh-schema-derive-internal"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "bs58"
version = "0.4.0"
@ -3332,7 +3385,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "885db39b08518fa700b73fa2214e8adbbfba316ba82dd510f50519173eadaf73"
dependencies = [
"borsh",
"borsh 0.9.3",
"schemars",
"semver 1.0.18",
"serde",
@ -3344,7 +3397,7 @@ version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d258582a1878e6db67400b0504a5099db85718d22c2e07f747fe1706ae7150"
dependencies = [
"borsh",
"borsh 0.9.3",
"serde",
]
@ -3354,7 +3407,7 @@ version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d924011380de759c3dc6fdbcda37a19a5c061f56dab69d28a34ecee765e23e4"
dependencies = [
"borsh",
"borsh 0.9.3",
"serde",
]
@ -3385,7 +3438,7 @@ checksum = "1e75673d69fd7365508f3d32483669fe45b03bfb34e4d9363e90adae9dfb416c"
dependencies = [
"arrayref",
"blake2",
"borsh",
"borsh 0.9.3",
"bs58 0.4.0",
"c2-chacha",
"curve25519-dalek",
@ -3411,7 +3464,7 @@ checksum = "7754612b47737d277fb818e9fdbb1406e90f9e57151c55c3584d714421976cb6"
dependencies = [
"arrayref",
"blake2",
"borsh",
"borsh 0.9.3",
"bs58 0.4.0",
"c2-chacha",
"curve25519-dalek",
@ -3434,7 +3487,7 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1335ffce1476da6516dcd22b26cece1a495fc725c0e8fec1879073752ac068d"
dependencies = [
"borsh",
"borsh 0.9.3",
"lazy_static",
"log",
"near-chain-configs",
@ -3469,7 +3522,7 @@ version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ad1a9a1640539c81f065425c31bffcfbf6b31ef1aeaade59ce905f5df6ac860"
dependencies = [
"borsh",
"borsh 0.9.3",
"byteorder",
"bytesize",
"chrono",
@ -3498,7 +3551,7 @@ version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97670b302dce15f09bba50f24c67aa08130fd01528cc61d4415892401e88e974"
dependencies = [
"borsh",
"borsh 0.9.3",
"byteorder",
"bytesize",
"cfg-if 1.0.0",
@ -3529,7 +3582,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91d508f0fc340f6461e4e256417685720d3c4c00bb5a939b105160e49137caba"
dependencies = [
"base64 0.11.0",
"borsh",
"borsh 0.9.3",
"bs58 0.4.0",
"derive_more",
"near-account-id 0.14.0",
@ -3546,7 +3599,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7929e19d862221949734c4a0063a8f55e7069de3a2ebc2d4f4c13497a5e953cb"
dependencies = [
"base64 0.13.1",
"borsh",
"borsh 0.9.3",
"bs58 0.4.0",
"derive_more",
"near-account-id 0.15.0",
@ -3623,7 +3676,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15eb3de2defe3626260cc209a6cdb985c6b27b0bd4619fad97dcfae002c3c5bd"
dependencies = [
"base64 0.13.1",
"borsh",
"borsh 0.9.3",
"bs58 0.4.0",
"near-abi",
"near-crypto 0.14.0",
@ -3663,7 +3716,7 @@ version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0da466a30f0446639cbd788c30865086fac3e8dcb07a79e51d2b0775ed4261e"
dependencies = [
"borsh",
"borsh 0.9.3",
"near-account-id 0.14.0",
"near-rpc-error-macro 0.14.0",
"serde",
@ -3675,7 +3728,7 @@ version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5591c9c8afa83a040cb5c3f29bc52b2efae2c32d4bcaee1bba723738da1a5cf6"
dependencies = [
"borsh",
"borsh 0.9.3",
"near-account-id 0.15.0",
"near-rpc-error-macro 0.15.0",
"serde",
@ -3689,7 +3742,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81b534828419bacbf1f7b11ef7b00420f248c548c485d3f0cfda8bb6931152f2"
dependencies = [
"base64 0.13.1",
"borsh",
"borsh 0.9.3",
"bs58 0.4.0",
"byteorder",
"near-account-id 0.14.0",
@ -6370,7 +6423,7 @@ dependencies = [
"async-process",
"async-trait",
"base64 0.13.1",
"borsh",
"borsh 0.9.3",
"bs58 0.4.0",
"chrono",
"dirs 3.0.2",
@ -6441,7 +6494,7 @@ version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e61de68ede9ffdd69c01664f65a178c5188b73f78faa21f0936016a888ff7c"
dependencies = [
"borsh",
"borsh 0.9.3",
"byteorder",
"crunchy",
"lazy_static",

View File

@ -37,3 +37,6 @@ tracing-subscriber = { version = "0.3.17", default-features = false, features =
# indicates that this library should be compiled for the marine target,
# otherwise it will be compiled for the wasm bindgen target
marine = []
gen_signatures = ["aquavm-air/gen_signatures"]
check_signatures = ["aquavm-air/check_signatures"]

View File

@ -51,14 +51,20 @@ air-testing-framework = { path = "../crates/testing-framework" }
fluence-app-service = "0.29.0"
marine-rs-sdk = { version = "0.10.0", features = ["logger"] }
borsh = "0.10.3"
# the feature just silence a warning in the criterion 0.3.x.
criterion = { version = "0.3.3", features = ["html_reports"] }
csv = "1.1.5"
once_cell = "1.4.1"
ed25519-dalek = "1.0.1"
env_logger = "0.7.1"
once_cell = "1.4.1"
pretty_assertions = "0.6.1"
serde_json = "1.0.61"
[features]
check_signatures = []
gen_signatures = []
[[bench]]
name = "call_benchmark"
harness = false

View File

@ -117,6 +117,17 @@ pub enum UncatchableError {
/// CanonStreamMap related errors.
#[error(transparent)]
CanonStreamMapError(#[from] CanonStreamMapError),
/// Argument hash or tetraplet mismatch in a call/canon merged from current_data with an evaluated value.
#[error("{param} doesn't match expected parameters: expected {expected_value}, got {stored_value} ")]
InstructionParametersMismatch {
param: &'static str,
expected_value: String,
stored_value: String,
},
#[error("failed to sign data: {0}")]
SigningError(#[from] fluence_keypair::error::SigningError),
}
impl ToErrorCode for UncatchableError {

View File

@ -147,15 +147,28 @@ impl ExecutionCidState {
.ok_or_else(|| UncatchableError::ValueForCidNotFound("service result aggregate", cid.clone().into()))
}
pub(crate) fn resolve_service_value(
pub(crate) fn resolve_service_info(
&self,
service_result_agg_cid: &CID<ServiceResultCidAggregate>,
) -> Result<Rc<JValue>, UncatchableError> {
) -> Result<ResolvedServiceInfo, UncatchableError> {
let service_result_aggregate = self.get_service_result_agg_by_cid(service_result_agg_cid)?;
self.get_value_by_cid(&service_result_aggregate.value_cid)
let value = self.get_value_by_cid(&service_result_aggregate.value_cid)?;
let tetraplet = self.get_tetraplet_by_cid(&service_result_aggregate.tetraplet_cid)?;
Ok(ResolvedServiceInfo {
value,
tetraplet,
service_result_aggregate,
})
}
}
pub(crate) struct ResolvedServiceInfo {
pub(crate) value: Rc<JValue>,
pub(crate) tetraplet: RcSecurityTetraplet,
pub(crate) service_result_aggregate: Rc<ServiceResultCidAggregate>,
}
impl From<ExecutionCidState> for CidInfo {
fn from(value: ExecutionCidState) -> Self {
Self {

View File

@ -31,13 +31,12 @@ use air_interpreter_data::CanonResultCidAggregate;
use air_interpreter_data::CidInfo;
use air_interpreter_data::ServiceResultCidAggregate;
use air_interpreter_interface::*;
use air_interpreter_signatures::PeerCidTracker;
use air_interpreter_signatures::SignatureStore;
use air_interpreter_signatures::SignatureTracker;
use std::rc::Rc;
/// Contains all necessary state needed to execute AIR script.
#[derive(Default)]
pub(crate) struct ExecutionCtx<'i> {
/// Contains all scalars.
pub(crate) scalars: Scalars<'i>,
@ -89,11 +88,10 @@ pub(crate) struct ExecutionCtx<'i> {
/// It contains peers' signatures for verification.
pub(crate) signature_store: SignatureStore,
/// Local signatures tracker.
/// Current peer's CID tracker.
///
/// It gathers peers' CIDs (call results and canon results) stored in the trace either for signing (current peer's
/// CIDs) or sign verification (other peers).
pub(crate) signature_tracker: SignatureTracker,
/// It gathers current peer's CIDs (call results and canon results) for further signing.
pub(crate) peer_cid_tracker: PeerCidTracker,
}
impl<'i> ExecutionCtx<'i> {
@ -101,15 +99,15 @@ impl<'i> ExecutionCtx<'i> {
prev_ingredients: ExecCtxIngredients,
current_ingredients: ExecCtxIngredients,
call_results: CallResults,
signature_store: SignatureStore,
run_parameters: &RunParameters,
) -> Self {
let run_parameters = RcRunParameters::from_run_parameters(run_parameters);
let streams = Streams::new();
let cid_state = ExecutionCidState::from_cid_info(prev_ingredients.cid_info, current_ingredients.cid_info);
// TODO we might keep both stores and merge them only with signature info collected into SignatureTracker
let signature_store =
SignatureStore::merge(prev_ingredients.signature_store, current_ingredients.signature_store);
let peer_cid_tracker = PeerCidTracker::new(run_parameters.current_peer_id.clone());
Self {
run_parameters,
@ -117,9 +115,16 @@ impl<'i> ExecutionCtx<'i> {
last_call_request_id: prev_ingredients.last_call_request_id,
call_results,
streams,
stream_maps: <_>::default(),
cid_state,
signature_store,
..<_>::default()
peer_cid_tracker,
scalars: <_>::default(),
next_peer_pks: <_>::default(),
last_error_descriptor: <_>::default(),
error_descriptor: <_>::default(),
tracker: <_>::default(),
call_requests: <_>::default(),
}
}
@ -136,12 +141,12 @@ impl<'i> ExecutionCtx<'i> {
self.last_call_request_id
}
pub(crate) fn record_call_cid(&mut self, peer_id: impl Into<Box<str>>, cid: &CID<ServiceResultCidAggregate>) {
self.signature_tracker.register(peer_id, cid);
pub(crate) fn record_call_cid(&mut self, peer_id: &str, cid: &CID<ServiceResultCidAggregate>) {
self.peer_cid_tracker.register(peer_id, cid);
}
pub(crate) fn record_canon_cid(&mut self, peer_id: impl Into<Box<str>>, cid: &CID<CanonResultCidAggregate>) {
self.signature_tracker.register(peer_id, cid);
pub(crate) fn record_canon_cid(&mut self, peer_id: &str, cid: &CID<CanonResultCidAggregate>) {
self.peer_cid_tracker.register(peer_id, cid);
}
}
@ -205,7 +210,6 @@ impl ExecutionCtx<'_> {
pub(crate) struct ExecCtxIngredients {
pub(crate) last_call_request_id: u32,
pub(crate) cid_info: CidInfo,
pub(crate) signature_store: SignatureStore,
}
use serde::Deserialize;
@ -218,6 +222,8 @@ use std::fmt::Formatter;
pub(crate) struct RcRunParameters {
pub(crate) init_peer_id: Rc<str>,
pub(crate) current_peer_id: Rc<String>,
// TODO use [u8]
pub(crate) salt: Rc<str>,
pub(crate) timestamp: u64,
pub(crate) ttl: u32,
}
@ -227,23 +233,13 @@ impl RcRunParameters {
Self {
init_peer_id: run_parameters.init_peer_id.as_str().into(),
current_peer_id: Rc::new(run_parameters.current_peer_id.clone()),
salt: run_parameters.particle_id.as_str().into(),
timestamp: run_parameters.timestamp,
ttl: run_parameters.ttl,
}
}
}
impl Default for RcRunParameters {
fn default() -> Self {
Self {
init_peer_id: "".into(),
current_peer_id: Default::default(),
timestamp: Default::default(),
ttl: Default::default(),
}
}
}
impl<'i> Display for ExecutionCtx<'i> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
writeln!(f, "scalars:")?;

View File

@ -24,6 +24,7 @@ mod streams_variables;
pub use instruction_error::*;
pub use cid_state::ExecutionCidState;
pub(crate) use cid_state::ResolvedServiceInfo;
pub(crate) use context::*;
pub(crate) use scalar_variables::*;
pub(crate) use stream_maps_variables::*;

View File

@ -18,6 +18,7 @@ pub(crate) mod call_result_setter;
mod prev_result_handler;
mod resolved_call;
pub(crate) mod triplet;
mod verifier;
use resolved_call::ResolvedCall;

View File

@ -38,7 +38,7 @@ pub(crate) fn populate_context_from_peer_service_result<'i>(
) -> ExecutionResult<CallResult> {
match output {
CallOutputValue::Scalar(scalar) => {
let peer_id: Box<str> = tetraplet.peer_pk.as_str().into();
let peer_id = tetraplet.peer_pk.clone();
let service_result_agg_cid =
exec_ctx
.cid_state
@ -46,11 +46,11 @@ pub(crate) fn populate_context_from_peer_service_result<'i>(
let executed_result = ValueAggregate::from_service_result(executed_result, service_result_agg_cid.clone());
exec_ctx.scalars.set_scalar_value(scalar.name, executed_result)?;
exec_ctx.record_call_cid(peer_id, &service_result_agg_cid);
exec_ctx.record_call_cid(&peer_id, &service_result_agg_cid);
Ok(CallResult::executed_scalar(service_result_agg_cid))
}
CallOutputValue::Stream(stream) => {
let peer_id: Box<str> = tetraplet.peer_pk.as_str().into();
let peer_id = tetraplet.peer_pk.clone();
let service_result_agg_cid =
exec_ctx
.cid_state
@ -61,7 +61,7 @@ pub(crate) fn populate_context_from_peer_service_result<'i>(
let value_descriptor =
StreamValueDescriptor::new(executed_result, stream.name, Generation::New, stream.position);
exec_ctx.streams.add_stream_value(value_descriptor)?;
exec_ctx.record_call_cid(&*peer_id, &service_result_agg_cid);
exec_ctx.record_call_cid(&peer_id, &service_result_agg_cid);
Ok(CallResult::executed_stream_stub(service_result_agg_cid))
}
CallOutputValue::None => {
@ -76,6 +76,7 @@ pub(crate) fn populate_context_from_peer_service_result<'i>(
pub(crate) fn populate_context_from_data<'i>(
value: ValueRef,
argument_hash: &str,
tetraplet: RcSecurityTetraplet,
trace_pos: TracePos,
value_source: ValueSource,
@ -84,13 +85,37 @@ pub(crate) fn populate_context_from_data<'i>(
) -> ExecutionResult<()> {
match (output, value) {
(CallOutputValue::Scalar(scalar), ValueRef::Scalar(cid)) => {
let value = exec_ctx.cid_state.resolve_service_value(&cid)?;
let ResolvedServiceInfo {
value,
tetraplet: current_tetraplet,
service_result_aggregate,
} = exec_ctx.cid_state.resolve_service_info(&cid)?;
verifier::verify_call(
argument_hash,
&tetraplet,
&service_result_aggregate.argument_hash,
&current_tetraplet,
)?;
let result = ServiceResultAggregate::new(value, tetraplet, trace_pos);
let result = ValueAggregate::from_service_result(result, cid);
exec_ctx.scalars.set_scalar_value(scalar.name, result)?;
}
(CallOutputValue::Stream(stream), ValueRef::Stream { cid, generation }) => {
let value = exec_ctx.cid_state.resolve_service_value(&cid)?;
let ResolvedServiceInfo {
value,
tetraplet: current_tetraplet,
service_result_aggregate,
} = exec_ctx.cid_state.resolve_service_info(&cid)?;
verifier::verify_call(
argument_hash,
&tetraplet,
&service_result_aggregate.argument_hash,
&current_tetraplet,
)?;
let result = ServiceResultAggregate::new(value, tetraplet, trace_pos);
let result = ValueAggregate::from_service_result(result, cid);
let generation = Generation::from_data(value_source, generation);

View File

@ -15,6 +15,7 @@
*/
use super::*;
use crate::execution_step::execution_context::ResolvedServiceInfo;
use crate::execution_step::instructions::call::call_result_setter::populate_context_from_data;
use crate::execution_step::CatchableError;
use crate::execution_step::RcSecurityTetraplet;
@ -50,15 +51,27 @@ pub(super) fn handle_prev_state<'i>(
// this call was failed on one of the previous executions,
// here it's needed to bubble this special error up
Failed(ref failed_cid) => {
let err_value = exec_ctx
let ResolvedServiceInfo {
value: err_value,
tetraplet: current_tetraplet,
service_result_aggregate,
} = exec_ctx
.cid_state
.resolve_service_value(failed_cid)
.resolve_service_info(failed_cid)
.map_err(UncatchableError::from)?;
verifier::verify_call(
argument_hash.as_ref().unwrap(),
tetraplet,
&service_result_aggregate.argument_hash,
&current_tetraplet,
)?;
let call_service_failed: CallServiceFailed =
serde_json::from_value((*err_value).clone()).map_err(UncatchableError::MalformedCallServiceFailed)?;
exec_ctx.make_subgraph_incomplete();
exec_ctx.record_call_cid(&*tetraplet.peer_pk, failed_cid);
exec_ctx.record_call_cid(&tetraplet.peer_pk, failed_cid);
trace_ctx.meet_call_end(met_result.result);
let err_msg = call_service_failed.message;
@ -103,6 +116,7 @@ pub(super) fn handle_prev_state<'i>(
populate_context_from_data(
value.clone(),
argument_hash.as_ref().unwrap(),
tetraplet.clone(),
met_result.trace_pos,
met_result.source,
@ -112,7 +126,7 @@ pub(super) fn handle_prev_state<'i>(
match &value {
ValueRef::Scalar(ref cid) | ValueRef::Stream { ref cid, .. } => {
exec_ctx.record_call_cid(&*tetraplet.peer_pk, cid);
exec_ctx.record_call_cid(&tetraplet.peer_pk, cid);
}
ValueRef::Unused(_) => {}
}
@ -178,13 +192,13 @@ fn handle_service_error(
let failed_value = CallServiceFailed::new(service_result.ret_code, error_message).to_value();
let peer_id: Box<str> = tetraplet.peer_pk.as_str().into();
let peer_id = tetraplet.peer_pk.clone();
let service_result_agg_cid =
exec_ctx
.cid_state
.track_service_result(failed_value.into(), tetraplet, argument_hash)?;
exec_ctx.record_call_cid(peer_id, &service_result_agg_cid);
exec_ctx.record_call_cid(&peer_id, &service_result_agg_cid);
trace_ctx.meet_call_end(Failed(service_result_agg_cid));
Err(error.into())

View File

@ -0,0 +1,43 @@
/*
* Copyright 2023 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::UncatchableError;
use polyplets::SecurityTetraplet;
/// Check that computed call parameters match the parameters from current data.
pub(crate) fn verify_call(
expected_argument_hash: &str,
expected_tetraplet: &SecurityTetraplet,
stored_argument_hash: &str,
stored_tetraplet: &SecurityTetraplet,
) -> Result<(), UncatchableError> {
if expected_argument_hash != stored_argument_hash {
return Err(UncatchableError::InstructionParametersMismatch {
param: "call argument_hash",
expected_value: expected_argument_hash.to_owned(),
stored_value: stored_argument_hash.to_owned(),
});
}
if expected_tetraplet != stored_tetraplet {
return Err(UncatchableError::InstructionParametersMismatch {
param: "call tetraplet",
expected_value: format!("{expected_tetraplet:?}"),
stored_value: format!("{stored_tetraplet:?}"),
});
}
Ok(())
}

View File

@ -47,6 +47,7 @@ impl<'i> super::ExecutableInstruction<'i> for ast::Canon<'i> {
let create_canon_producer = create_canon_stream_producer(self.stream.name, self.stream.position);
match canon_result {
MergerCanonResult::CanonResult(canon_result) => handle_seen_canon(
&self.peer_id,
epilog,
&create_canon_producer,
canon_result,

View File

@ -47,6 +47,7 @@ impl<'i> super::ExecutableInstruction<'i> for ast::CanonMap<'i> {
let create_canon_producer = create_canon_stream_producer(self.stream_map.name, self.stream_map.position);
match canon_result {
MergerCanonResult::CanonResult(canon_result) => handle_seen_canon(
&self.peer_id,
epilog,
&create_canon_producer,
canon_result,

View File

@ -50,6 +50,7 @@ impl<'i> super::ExecutableInstruction<'i> for ast::CanonStreamMapScalar<'i> {
let create_canon_producer = create_canon_stream_producer(self.stream_map.name, self.stream_map.position);
match canon_result {
MergerCanonResult::CanonResult(canon_result) => handle_seen_canon(
&self.peer_id,
epilog,
&create_canon_producer,
canon_result,

View File

@ -25,6 +25,7 @@ use air_interpreter_cid::CID;
use air_interpreter_data::CanonResult;
use air_interpreter_data::CanonResultCidAggregate;
use air_parser::ast::ResolvableToPeerIdVariable;
use polyplets::SecurityTetraplet;
use std::rc::Rc;
@ -34,6 +35,7 @@ pub(crate) type CanonEpilogClosure<'closure> = dyn Fn(CanonStream, Rc<CID<CanonR
pub(crate) type CreateCanonStreamClosure<'closure> = dyn Fn(&mut ExecutionCtx<'_>, String) -> CanonStream + 'closure;
pub(crate) fn handle_seen_canon(
peer_id_var: &ResolvableToPeerIdVariable<'_>,
epilog: &CanonEpilogClosure<'_>,
create_canon_stream: &CreateCanonStreamClosure<'_>,
canon_result: CanonResult,
@ -45,7 +47,9 @@ pub(crate) fn handle_seen_canon(
CanonResult::RequestSentBy(..) => {
handle_canon_request_sent_by(epilog, create_canon_stream, peer_id, canon_result, exec_ctx, trace_ctx)
}
CanonResult::Executed(canon_result_cid) => handle_canon_executed(epilog, canon_result_cid, exec_ctx, trace_ctx),
CanonResult::Executed(canon_result_cid) => {
handle_canon_executed(peer_id_var, epilog, canon_result_cid, exec_ctx, trace_ctx)
}
}
}
@ -72,21 +76,29 @@ pub(crate) fn handle_canon_request_sent_by(
}
pub(crate) fn handle_canon_executed(
peer_id_var: &ResolvableToPeerIdVariable<'_>,
epilog: &CanonEpilogClosure<'_>,
canon_result_cid: Rc<CID<CanonResultCidAggregate>>,
exec_ctx: &mut ExecutionCtx<'_>,
trace_ctx: &mut TraceHandler,
) -> ExecutionResult<()> {
let canon_result_agg = exec_ctx.cid_state.get_canon_result_by_cid(&canon_result_cid)?;
let tetraplet = exec_ctx.cid_state.get_tetraplet_by_cid(&canon_result_agg.tetraplet)?;
let peer_id = crate::execution_step::instructions::resolve_peer_id_to_string(peer_id_var, exec_ctx)?;
let expected_tetraplet = SecurityTetraplet::new(peer_id, "", "", "");
exec_ctx.record_canon_cid(&*tetraplet.peer_pk, &canon_result_cid);
let canon_result_agg = exec_ctx.cid_state.get_canon_result_by_cid(&canon_result_cid)?;
let tetraplet_cid = canon_result_agg.tetraplet.clone();
let tetraplet = exec_ctx.cid_state.get_tetraplet_by_cid(&tetraplet_cid)?;
verify_canon(&expected_tetraplet, &tetraplet)?;
let value_cids = canon_result_agg.values.clone();
let values = value_cids
.iter()
.map(|canon_value_cid| exec_ctx.cid_state.get_canon_value_by_cid(canon_value_cid))
.collect::<Result<Vec<_>, _>>()?;
populate_seen_cid_context(exec_ctx, &tetraplet.peer_pk, &canon_result_cid);
let canon_stream = CanonStream::new(values, tetraplet);
epilog(canon_stream, canon_result_cid, exec_ctx, trace_ctx)
@ -124,11 +136,19 @@ fn create_canon_stream_for_first_time(
trace_ctx: &mut TraceHandler,
) -> ExecutionResult<()> {
let canon_stream = create_canon_stream(exec_ctx, peer_id);
let canon_result_cid = populate_cid_context(exec_ctx, &canon_stream)?;
let canon_result_cid = populate_unseen_cid_context(exec_ctx, &canon_stream)?;
epilog(canon_stream, canon_result_cid, exec_ctx, trace_ctx)
}
fn populate_cid_context(
fn populate_seen_cid_context(
exec_ctx: &mut ExecutionCtx<'_>,
peer_id: &str,
canon_result_cid: &Rc<CID<CanonResultCidAggregate>>,
) {
exec_ctx.record_canon_cid(peer_id, canon_result_cid)
}
fn populate_unseen_cid_context(
exec_ctx: &mut ExecutionCtx<'_>,
canon_stream: &CanonStream,
) -> ExecutionResult<Rc<CID<CanonResultCidAggregate>>> {
@ -151,6 +171,20 @@ fn populate_cid_context(
.track_value(canon_result)
.map_err(UncatchableError::from)?;
exec_ctx.record_canon_cid(&*tetraplet.peer_pk, &canon_result_cid);
exec_ctx.record_canon_cid(&tetraplet.peer_pk, &canon_result_cid);
Ok(canon_result_cid)
}
pub(crate) fn verify_canon(
expected_tetraplet: &SecurityTetraplet,
stored_tetraplet: &SecurityTetraplet,
) -> Result<(), UncatchableError> {
if expected_tetraplet != stored_tetraplet {
return Err(UncatchableError::InstructionParametersMismatch {
param: "canon tetraplet",
expected_value: format!("{expected_tetraplet:?}"),
stored_value: format!("{stored_tetraplet:?}"),
});
}
Ok(())
}

View File

@ -133,8 +133,8 @@ fn compactify_streams(exec_ctx: &mut ExecutionCtx<'_>, trace_ctx: &mut TraceHand
fn sign_result(exec_ctx: &mut ExecutionCtx<'_>, keypair: &KeyPair) -> Result<(), InterpreterOutcome> {
let current_signature = exec_ctx
.signature_tracker
.into_signature(&exec_ctx.run_parameters.current_peer_id, keypair)
.peer_cid_tracker
.gen_signature(&exec_ctx.run_parameters.salt, keypair)
.map_err(signing_error_into_outcome)?;
let current_pubkey = keypair.public();

View File

@ -30,7 +30,9 @@ mod execution_step;
mod farewell_step;
mod preparation_step;
mod runner;
mod signing_step;
mod utils;
mod verification_step;
pub use air_interpreter_interface::InterpreterOutcome;
pub use air_interpreter_interface::RunParameters;

View File

@ -16,6 +16,8 @@
use crate::ToErrorCode;
use air_interpreter_data::data_version;
use air_interpreter_data::verification::DataVerifierError;
use air_interpreter_data::CidStoreVerificationError;
use air_interpreter_data::Versions;
use serde_json::Error as SerdeJsonError;
@ -84,6 +86,14 @@ pub enum PreparationError {
#[from]
error: fluence_keypair::error::DecodingError,
},
/// Failed to verify CidStore contents of the current data.
#[error(transparent)]
CidStoreVerificationError(#[from] CidStoreVerificationError),
/// Failed to check peers' signatures.
#[error(transparent)]
DataSignatureCheckError(#[from] DataVerifierError),
}
impl ToErrorCode for PreparationError {

View File

@ -22,5 +22,7 @@ pub use errors::PreparationError;
pub use interpreter_versions::interpreter_version;
pub use interpreter_versions::min_supported_version;
pub(crate) use preparation::parse_data;
pub(crate) use preparation::prepare;
pub(crate) use preparation::ParsedDataPair;
pub(crate) use preparation::PreparationDescriptor;

View File

@ -21,6 +21,7 @@ use crate::execution_step::TraceHandler;
use air_interpreter_data::InterpreterData;
use air_interpreter_interface::RunParameters;
use air_interpreter_signatures::SignatureStore;
use air_parser::ast::Instruction;
use fluence_keypair::KeyFormat;
use fluence_keypair::KeyPair;
@ -37,35 +38,54 @@ pub(crate) struct PreparationDescriptor<'ctx, 'i> {
pub(crate) keypair: KeyPair,
}
/// Parse and prepare supplied data and AIR script.
pub(crate) struct ParsedDataPair {
pub(crate) prev_data: InterpreterData,
pub(crate) current_data: InterpreterData,
}
/// Parse data and check its version.
#[tracing::instrument(skip_all)]
pub(crate) fn prepare<'i>(
prev_data: &[u8],
current_data: &[u8],
raw_air: &'i str,
call_results: &[u8],
run_parameters: RunParameters,
) -> PreparationResult<PreparationDescriptor<'static, 'i>> {
pub(crate) fn parse_data(prev_data: &[u8], current_data: &[u8]) -> PreparationResult<ParsedDataPair> {
let prev_data = try_to_data(prev_data)?;
let current_data = try_to_data(current_data)?;
check_version_compatibility(&current_data)?;
Ok(ParsedDataPair {
prev_data,
current_data,
})
}
/// Parse and prepare supplied data and AIR script.
#[tracing::instrument(skip_all)]
pub(crate) fn prepare<'i>(
prev_data: InterpreterData,
current_data: InterpreterData,
raw_air: &'i str,
call_results: &[u8],
run_parameters: RunParameters,
signature_store: SignatureStore,
) -> PreparationResult<PreparationDescriptor<'static, 'i>> {
let air: Instruction<'i> = *air_parser::parse(raw_air).map_err(PreparationError::AIRParseError)?;
let prev_ingredients = ExecCtxIngredients {
last_call_request_id: prev_data.last_call_request_id,
cid_info: prev_data.cid_info,
signature_store: prev_data.signatures,
};
let current_ingredients = ExecCtxIngredients {
last_call_request_id: current_data.last_call_request_id,
cid_info: current_data.cid_info,
signature_store: current_data.signatures,
};
let exec_ctx = make_exec_ctx(prev_ingredients, current_ingredients, call_results, &run_parameters)?;
let exec_ctx = make_exec_ctx(
prev_ingredients,
current_ingredients,
call_results,
signature_store,
&run_parameters,
)?;
let trace_handler = TraceHandler::from_trace(prev_data.trace, current_data.trace);
let key_format = KeyFormat::try_from(run_parameters.key_format)?;
@ -81,7 +101,7 @@ pub(crate) fn prepare<'i>(
Ok(result)
}
fn try_to_data(raw_data: &[u8]) -> PreparationResult<InterpreterData> {
pub(crate) fn try_to_data(raw_data: &[u8]) -> PreparationResult<InterpreterData> {
// treat empty slice as an empty data,
// it allows abstracting from an internal format for an empty data
if raw_data.is_empty() {
@ -103,16 +123,23 @@ fn make_exec_ctx(
prev_ingredients: ExecCtxIngredients,
current_ingredients: ExecCtxIngredients,
call_results: &[u8],
signature_store: SignatureStore,
run_parameters: &RunParameters,
) -> PreparationResult<ExecutionCtx<'static>> {
let call_results = serde_json::from_slice(call_results)
.map_err(|e| PreparationError::call_results_de_failed(call_results.to_vec(), e))?;
let ctx = ExecutionCtx::new(prev_ingredients, current_ingredients, call_results, run_parameters);
let ctx = ExecutionCtx::new(
prev_ingredients,
current_ingredients,
call_results,
signature_store,
run_parameters,
);
Ok(ctx)
}
fn check_version_compatibility(data: &InterpreterData) -> PreparationResult<()> {
pub(crate) fn check_version_compatibility(data: &InterpreterData) -> PreparationResult<()> {
if &data.versions.interpreter_version < super::min_supported_version() {
return Err(PreparationError::UnsupportedInterpreterVersion {
actual_version: data.versions.interpreter_version.clone(),

View File

@ -16,8 +16,12 @@
use crate::execution_step::ExecutableInstruction;
use crate::farewell_step as farewell;
use crate::preparation_step::parse_data;
use crate::preparation_step::prepare;
use crate::preparation_step::ParsedDataPair;
use crate::preparation_step::PreparationDescriptor;
use crate::signing_step::sign_produced_cids;
use crate::verification_step::verify;
use air_interpreter_interface::InterpreterOutcome;
use air_interpreter_interface::RunParameters;
@ -50,20 +54,45 @@ pub fn execute_air(
#[allow(clippy::result_large_err)]
fn execute_air_impl(
air: String,
prev_data: Vec<u8>,
data: Vec<u8>,
raw_prev_data: Vec<u8>,
raw_current_data: Vec<u8>,
params: RunParameters,
call_results: Vec<u8>,
) -> Result<InterpreterOutcome, InterpreterOutcome> {
let ParsedDataPair {
prev_data,
current_data,
} = match parse_data(&raw_prev_data, &raw_current_data) {
Ok(parsed_datas) => parsed_datas,
// return the prev data in case of errors
Err(error) => return Err(farewell::from_uncatchable_error(raw_prev_data, error)),
};
// TODO currently we use particle ID, but it should be changed to signature,
// as partical ID can be equally replayed
let salt = params.particle_id.clone();
let signature_store = match verify(&prev_data, &current_data, &salt) {
Ok(signature_store) => signature_store,
// return the prev data in case of errors
Err(error) => return Err(farewell::from_uncatchable_error(raw_prev_data, error)),
};
let PreparationDescriptor {
mut exec_ctx,
mut trace_handler,
air,
keypair,
} = match prepare(&prev_data, &data, air.as_str(), &call_results, params) {
} = match prepare(
prev_data,
current_data,
air.as_str(),
&call_results,
params,
signature_store,
) {
Ok(descriptor) => descriptor,
// return the prev data in case of errors
Err(error) => return Err(farewell::from_uncatchable_error(prev_data, error)),
Err(error) => return Err(farewell::from_uncatchable_error(raw_prev_data, error)),
};
// match here is used instead of map_err, because the compiler can't determine that
@ -73,6 +102,17 @@ fn execute_air_impl(
tracing::Level::INFO,
"execute",
);
match sign_produced_cids(
&mut exec_ctx.peer_cid_tracker,
&mut exec_ctx.signature_store,
&salt,
&keypair,
) {
Ok(()) => {}
Err(error) => return Err(farewell::from_uncatchable_error(raw_prev_data, error)),
}
measure!(
match exec_result {
Ok(_) => farewell::from_success_result(exec_ctx, trace_handler, &keypair),
@ -81,7 +121,7 @@ fn execute_air_impl(
Err(farewell::from_execution_error(exec_ctx, trace_handler, error, &keypair))
}
// return the prev data in case of any trace errors
Err(error) => Err(farewell::from_uncatchable_error(prev_data, error)),
Err(error) => Err(farewell::from_uncatchable_error(raw_prev_data, error)),
},
tracing::Level::INFO,
"farewell",

48
air/src/signing_step.rs Normal file
View File

@ -0,0 +1,48 @@
/*
* Copyright 2023 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::ExecutionError;
use air_interpreter_signatures::{PeerCidTracker, SignatureStore};
#[cfg(feature = "gen_signatures")]
#[tracing::instrument(skip_all)]
pub(crate) fn sign_produced_cids(
signature_tracker: &mut PeerCidTracker,
signature_store: &mut SignatureStore,
salt: &str,
keypair: &fluence_keypair::KeyPair,
) -> Result<(), ExecutionError> {
use crate::UncatchableError;
let signature = signature_tracker
.gen_signature(salt, keypair)
.map_err(UncatchableError::SigningError)?;
let public_key = keypair.public().into();
signature_store.put(public_key, signature);
Ok(())
}
#[cfg(not(feature = "gen_signatures"))]
#[tracing::instrument(skip_all)]
pub(crate) fn sign_produced_cids(
_signature_tracker: &mut PeerCidTracker,
_signature_store: &mut SignatureStore,
_salt: &str,
_keypair: &fluence_keypair::KeyPair,
) -> Result<(), ExecutionError> {
Ok(())
}

View File

@ -0,0 +1,51 @@
/*
* Copyright 2023 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 air_interpreter_data::InterpreterData;
use air_interpreter_signatures::SignatureStore;
// TODO replace with VerificationError
use crate::PreparationError;
#[cfg(feature = "check_signatures")]
#[tracing::instrument(skip_all)]
pub(crate) fn verify(
prev_data: &InterpreterData,
current_data: &InterpreterData,
salt: &str,
) -> Result<SignatureStore, PreparationError> {
use air_interpreter_data::verification;
current_data.cid_info.verify()?;
let prev_data_verifier = verification::DataVerifier::new(prev_data, salt)?;
let current_data_verifier = verification::DataVerifier::new(current_data, salt)?;
// prev_data is always correct, check only current_data
current_data_verifier.verify()?;
let signature_store = prev_data_verifier.merge(current_data_verifier)?;
Ok(signature_store)
}
#[cfg(not(feature = "check_signatures"))]
#[tracing::instrument(skip_all)]
pub(crate) fn verify(
_prev_data: &InterpreterData,
_current_data: &InterpreterData,
_salt: &str,
) -> Result<SignatureStore, PreparationError> {
Ok(<_>::default())
}

View File

@ -19,34 +19,37 @@ use air::UncatchableError::ValueForCidNotFound;
use air_interpreter_cid::CID;
use air_interpreter_data::{CidStore, CidTracker};
use air_test_framework::AirScriptExecutor;
use air_test_utils::key_utils::at;
use air_test_utils::prelude::*;
use pretty_assertions::assert_eq;
#[test]
fn test_canon_ok() {
let init_peer_id = "init_peer_id";
let init_peer_name = "init_peer_id";
let script = format!(
r#"(seq
(seq
(ap 42 $stream)
(call "{init_peer_id}" ("serv" "func") [] $stream)) ; ok = "to canon"
(canon "{init_peer_id}" $stream #canon)
(call "{init_peer_name}" ("serv" "func") [] $stream)) ; ok = "to canon"
(canon "{init_peer_name}" $stream #canon)
)"#
);
let executor =
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_id), &script).unwrap();
let result = executor.execute_one(init_peer_id).unwrap();
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_name), &script).unwrap();
let result = executor.execute_one(init_peer_name).unwrap();
let data = data_from_result(&result);
let mut cid_state = ExecutionCidState::new();
let init_peer_id = at(init_peer_name);
let stream_exec_state = stream_tracked!(
"to canon",
1,
cid_state,
peer = init_peer_id,
peer_name = init_peer_name,
service = "serv..0",
function = "func"
);
@ -98,33 +101,36 @@ fn test_canon_ok() {
#[test]
fn test_canon_ok_multi() {
let init_peer_id = "init_peer_id";
let other_peer_id = "other_peer_id";
let init_peer_name = "init_peer_id";
let other_peer_name = "other_peer_id";
let script = format!(
r#"(seq
(seq
(call "{init_peer_id}" ("serv" "func") [] $stream) ; ok = "to canon"
(call "{other_peer_id}" ("other_serv" "other_func") [] $stream) ; ok = "other"
(call "{init_peer_name}" ("serv" "func") [] $stream) ; ok = "to canon"
(call "{other_peer_name}" ("other_serv" "other_func") [] $stream) ; ok = "other"
)
(canon "{init_peer_id}" $stream #canon)
(canon "{init_peer_name}" $stream #canon)
)"#
);
let executor =
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_id), &script).unwrap();
let _result1 = executor.execute_one(init_peer_id).unwrap();
let _result2 = executor.execute_one(other_peer_id).unwrap();
let result3 = executor.execute_one(init_peer_id).unwrap();
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_name), &script).unwrap();
let _result1 = executor.execute_one(init_peer_name).unwrap();
let _result2 = executor.execute_one(other_peer_name).unwrap();
let result3 = executor.execute_one(init_peer_name).unwrap();
let data = data_from_result(&result3);
let init_peer_id = at(init_peer_name);
let other_peer_id = at(other_peer_name);
let mut cid_state = ExecutionCidState::new();
let stream_state_1 = stream_tracked!(
"to canon",
0,
cid_state,
peer = init_peer_id,
peer_name = init_peer_name,
service = "serv..0",
function = "func"
);
@ -134,7 +140,7 @@ fn test_canon_ok_multi() {
"other",
1,
cid_state,
peer = other_peer_id,
peer_name = other_peer_name,
service = "other_serv..1",
function = "other_func"
);
@ -310,7 +316,7 @@ fn test_canon_tetraplet_not_found() {
"function_name": "func",
"json_path": "",
"peer_pk": "peer_1",
"service_id": "serv",
"service_id": "serv..0",
},
}]
}),
@ -318,7 +324,7 @@ fn test_canon_tetraplet_not_found() {
),
];
let missing_cid = "bagaaieracu6twiik6az3cosyzlplrscon3ek6rnu3lkjnflibphqkw6kcdiq";
let missing_cid = "bagaaierawgvzxeomczgjfgaf7jhbap27kqihlzm4i4na42uoi36lgzfrzwdq";
let tetraplet_store: CidStore<_> = cid_state.tetraplet_tracker.into();
assert!(tetraplet_store.get(&CID::<_>::new(missing_cid)).is_some());
@ -332,7 +338,7 @@ fn test_canon_tetraplet_not_found() {
let result = call_vm!(vm, <_>::default(), air_script, vec![], cur_data);
let expected_error = ValueForCidNotFound("tetraplet", String::from(missing_cid));
assert!(check_error(&result, expected_error));
assert!(check_error(&result, expected_error), "{}", result.error_message);
}
#[test]

View File

@ -69,34 +69,36 @@ fn test_correct_cid() {
#[test]
fn test_scalar_cid() {
let vm_peer_id = "vm_peer_id";
let vm_peer_name = "vm_peer_id";
let annotated_air_script = format!(
r#"
(seq
(call "{vm_peer_id}" ("service" "call1") [] x) ; ok="hi"
(call "{vm_peer_id}" ("service" "call2") [] y) ; ok="ipld"
(call "{vm_peer_name}" ("service" "call1") [] x) ; ok="hi"
(call "{vm_peer_name}" ("service" "call2") [] y) ; ok="ipld"
)"#
);
let executor =
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(vm_peer_id), &annotated_air_script)
let executor = AirScriptExecutor::from_annotated(
TestRunParameters::from_init_peer_id(vm_peer_name),
&annotated_air_script,
)
.unwrap();
let result = executor.execute_one(vm_peer_id).unwrap();
let result = executor.execute_one(vm_peer_name).unwrap();
let data = data_from_result(&result);
let mut cid_state = ExecutionCidState::new();
let expected_trace = vec![
scalar_tracked!(
"hi",
cid_state,
peer = vm_peer_id,
peer_name = vm_peer_name,
service = "service..0",
function = "call1"
),
scalar_tracked!(
"ipld",
cid_state,
peer = vm_peer_id,
peer_name = vm_peer_name,
service = "service..1",
function = "call2"
),
@ -114,20 +116,22 @@ fn test_scalar_cid() {
#[test]
fn test_stream_cid() {
let vm_peer_id = "vm_peer_id";
let vm_peer_name = "vm_peer_id";
let annotated_air_script = format!(
r#"
(seq
(call "{vm_peer_id}" ("service" "call1") [] $x) ; ok="hi"
(call "{vm_peer_id}" ("service" "call2") [] $x) ; ok="ipld"
(call "{vm_peer_name}" ("service" "call1") [] $x) ; ok="hi"
(call "{vm_peer_name}" ("service" "call2") [] $x) ; ok="ipld"
)"#
);
let executor =
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(vm_peer_id), &annotated_air_script)
let executor = AirScriptExecutor::from_annotated(
TestRunParameters::from_init_peer_id(vm_peer_name),
&annotated_air_script,
)
.unwrap();
let result = executor.execute_one(vm_peer_id).unwrap();
let result = executor.execute_one(vm_peer_name).unwrap();
let data = data_from_result(&result);
let mut cid_state = ExecutionCidState::new();
let expected_trace = vec![
@ -135,7 +139,7 @@ fn test_stream_cid() {
"hi",
0,
cid_state,
peer = vm_peer_id,
peer_name = vm_peer_name,
service = "service..0",
function = "call1"
),
@ -143,7 +147,7 @@ fn test_stream_cid() {
"ipld",
1,
cid_state,
peer = vm_peer_id,
peer_name = vm_peer_name,
service = "service..1",
function = "call2"
),

View File

@ -54,7 +54,7 @@ fn fail_with_rebubble_error() {
scalar_tracked!(
expected_error_json.clone(),
cid_tracker,
peer = peer_id,
peer_name = peer_id,
service = "m..0",
function = "f1",
args = [expected_error_json]
@ -62,7 +62,7 @@ fn fail_with_rebubble_error() {
scalar_tracked!(
no_error_object(),
cid_tracker,
peer = peer_id,
peer_name = peer_id,
service = "m..1",
function = "f2",
args = [no_error_object()]
@ -120,7 +120,7 @@ fn rebubble_error_from_xor_right_branch() {
scalar_tracked!(
inner_expected_error_json.clone(),
cid_tracker,
peer = peer_id,
peer_name = peer_id,
service = "m..0",
function = "f1",
args = [inner_expected_error_json]
@ -128,7 +128,7 @@ fn rebubble_error_from_xor_right_branch() {
scalar_tracked!(
outer_expected_error_json.clone(),
cid_tracker,
peer = peer_id,
peer_name = peer_id,
service = "m..1",
function = "f2",
args = [outer_expected_error_json]
@ -136,12 +136,14 @@ fn rebubble_error_from_xor_right_branch() {
scalar_tracked!(
no_error_object(),
cid_tracker,
peer = peer_id,
peer_name = peer_id,
service = "m..2",
function = "f3",
args = [no_error_object()]
),
];
assert_eq!(actual_trace, expected_trace,);
print_trace(&result.last().unwrap(), "");
assert_eq!(actual_trace, expected_trace);
}

View File

@ -517,7 +517,7 @@ fn undefined_last_error_errcode() {
let expected_trace = ExecutionTrace::from(vec![scalar_tracked!(
errcode_lambda_output.clone(),
cid_state,
peer = local_peer_id,
peer_name = local_peer_id,
service = "test..0",
function = "error_code",
args = vec![errcode_lambda_output]
@ -545,7 +545,7 @@ fn undefined_last_error_msg_errcode() {
let expected_trace = ExecutionTrace::from(vec![scalar_tracked!(
message_lambda_output.clone(),
cid_state,
peer = local_peer_id,
peer_name = local_peer_id,
service = "test..0",
function = "message",
args = vec![message_lambda_output]

View File

@ -30,16 +30,16 @@ fn length_functor_for_array_scalar() {
)
"#;
let init_peer_id = "init_peer_id";
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_id), script)
let init_peer_name = "init_peer_id";
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_name), script)
.expect("invalid test AIR script");
let result = executor.execute_one(init_peer_id).unwrap();
let result = executor.execute_one(init_peer_name).unwrap();
let actual_trace = trace_from_result(&result);
let expected_trace = vec![
scalar!(json!([1, 1, 1]), peer = init_peer_id, service = "..0"),
unused!(3, peer = init_peer_id, args = vec![3], service = "..1"),
scalar!(json!([1, 1, 1]), peer_name = init_peer_name, service = "..0"),
unused!(3, peer_name = init_peer_name, args = vec![3], service = "..1"),
];
assert_eq!(actual_trace, expected_trace);
}
@ -56,11 +56,11 @@ fn length_functor_for_non_array_scalar() {
"#
);
let init_peer_id = "init_peer_id";
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_id), &script)
let init_peer_name = "init_peer_id";
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_name), &script)
.expect("invalid test AIR script");
let result = executor.execute_one(init_peer_id).unwrap();
let result = executor.execute_one(init_peer_name).unwrap();
check_error(
&result,
CatchableError::LengthFunctorAppliedToNotArray(json!(result_jvalue)),
@ -81,27 +81,29 @@ fn length_functor_for_stream() {
)
"#;
let init_peer_id = "init_peer_id";
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_id), script)
let init_peer_name = "init_peer_id";
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_name), script)
.expect("invalid test AIR script");
let result = executor.execute_one(init_peer_id).unwrap();
let result = executor.execute_one(init_peer_name).unwrap();
let actual_trace = trace_from_result(&result);
let init_peer_id = executor.resolve_name(init_peer_name).to_string();
let expected_trace = vec![
executed_state::ap(0),
executed_state::ap(0),
executed_state::canon(json!({
"tetraplet": {"function_name": "", "json_path": "", "peer_pk": "init_peer_id", "service_id": ""},
"tetraplet": {"function_name": "", "json_path": "", "peer_pk": init_peer_id, "service_id": ""},
"values": [
{
"result": 1,
"tetraplet": {"function_name": "", "json_path": "", "peer_pk": "init_peer_id", "service_id": ""},
"tetraplet": {"function_name": "", "json_path": "", "peer_pk": init_peer_id, "service_id": ""},
"trace_pos": 0,
},
{
"result": 1,
"tetraplet": {"function_name": "", "json_path": "", "peer_pk": "init_peer_id", "service_id": ""},
"tetraplet": {"function_name": "", "json_path": "", "peer_pk": init_peer_id, "service_id": ""},
"trace_pos": 1,
},
]
@ -122,16 +124,18 @@ fn length_functor_for_empty_stream() {
)
"#;
let init_peer_id = "init_peer_id";
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_id), script)
let init_peer_name = "init_peer_id";
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_name), script)
.expect("invalid test AIR script");
let result = executor.execute_one(init_peer_id).unwrap();
let result = executor.execute_one(init_peer_name).unwrap();
let actual_trace = trace_from_result(&result);
let init_peer_id = executor.resolve_name(init_peer_name).to_string();
let expected_trace = vec![
executed_state::canon(
json!({"tetraplet": {"function_name": "", "json_path": "", "peer_pk": "init_peer_id", "service_id": ""},
json!({"tetraplet": {"function_name": "", "json_path": "", "peer_pk": init_peer_id, "service_id": ""},
"values": []} ),
),
unused!(0, peer = init_peer_id, service = "..0", args = vec![0]),
@ -153,20 +157,22 @@ fn length_functor_for_canon_stream() {
)
"#;
let init_peer_id = "init_peer_id";
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_id), script)
let init_peer_name = "init_peer_id";
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_name), script)
.expect("invalid test AIR script");
let result = executor.execute_one(init_peer_id).unwrap();
let result = executor.execute_one(init_peer_name).unwrap();
let actual_trace = trace_from_result(&result);
let init_peer_id = executor.resolve_name(init_peer_name).to_string();
let expected_trace = vec![
executed_state::ap(0),
executed_state::ap(0),
executed_state::canon(
json!({"tetraplet": {"function_name": "", "json_path": "", "peer_pk": "init_peer_id", "service_id": ""},
"values": [{"result": 1, "tetraplet": {"function_name": "", "json_path": "", "peer_pk": "init_peer_id", "service_id": ""}, "trace_pos": 0},
{"result": 1, "tetraplet": {"function_name": "", "json_path": "", "peer_pk": "init_peer_id", "service_id": ""}, "trace_pos": 1}
json!({"tetraplet": {"function_name": "", "json_path": "", "peer_pk": init_peer_id, "service_id": ""},
"values": [{"result": 1, "tetraplet": {"function_name": "", "json_path": "", "peer_pk": init_peer_id, "service_id": ""}, "trace_pos": 0},
{"result": 1, "tetraplet": {"function_name": "", "json_path": "", "peer_pk": init_peer_id, "service_id": ""}, "trace_pos": 1}
]} ),
),
unused!(2, peer = init_peer_id, service = "..0", args = vec![2]),
@ -185,16 +191,18 @@ fn length_functor_for_empty_canon_stream() {
)
"#;
let init_peer_id = "init_peer_id";
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_id), script)
let init_peer_name = "init_peer_id";
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_name), script)
.expect("invalid test AIR script");
let result = executor.execute_one(init_peer_id).unwrap();
let result = executor.execute_one(init_peer_name).unwrap();
let actual_trace = trace_from_result(&result);
let init_peer_id = executor.resolve_name(init_peer_name).to_string();
let expected_trace = vec![
executed_state::canon(
json!({"tetraplet": {"function_name": "", "json_path": "", "peer_pk": "init_peer_id", "service_id": ""}, "values": []} ),
json!({"tetraplet": {"function_name": "", "json_path": "", "peer_pk": init_peer_id, "service_id": ""}, "values": []} ),
),
unused!(0, peer = init_peer_id, service = "..0", args = vec![0]),
];

View File

@ -14,441 +14,12 @@
* limitations under the License.
*/
use air_interpreter_signatures::SignatureTracker;
use air_test_framework::ephemeral::PeerId;
use air_test_framework::AirScriptExecutor;
use air_test_utils::key_utils::derive_dummy_keypair;
use air_test_utils::prelude::*;
use air_test_utils::test_runner::TestRunParameters;
#[cfg(feature = "check_signatures")]
mod attacks;
#[cfg(feature = "check_signatures")]
mod corruption;
#[test]
fn test_signature_empty() {
let script = "(null)";
let init_peer_id = "init_peer_id";
let (keypair, _) = derive_dummy_keypair(init_peer_id);
mod runtime;
let exec = <AirScriptExecutor>::new(
TestRunParameters::from_init_peer_id(init_peer_id),
vec![],
vec![PeerId::from(init_peer_id)].into_iter(),
script,
)
.unwrap();
let res = exec.execute_one(init_peer_id).unwrap();
assert_eq!(res.ret_code, 0, "{:?}", res);
let expected_signature: air_interpreter_signatures::Signature = keypair.sign(b"[]").unwrap().into();
let data = data_from_result(&res);
let signature = data.signatures.get(&keypair.public().into());
assert_eq!(signature, Some(&expected_signature), "{:?}", data.signatures);
}
#[test]
fn test_signature_call_var() {
let init_peer_id = "init_peer_id";
let (keypair, _) = derive_dummy_keypair(init_peer_id);
let air_script = format!(
r#"
(call "{init_peer_id}" ("" "") [] var) ; ok = "ok"
"#
);
let exec =
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_id), &air_script).unwrap();
let res = exec.execution_iter(init_peer_id).unwrap().last().unwrap();
assert_eq!(res.ret_code, 0, "{:?}", res);
let data = data_from_result(&res);
let expected_call_state = scalar!("ok", peer = init_peer_id, service = "..0");
let expected_cid = extract_service_result_cid(&expected_call_state);
let mut expected_tracker = SignatureTracker::new();
expected_tracker.register(init_peer_id, &expected_cid);
let expected_signature = expected_tracker.into_signature(init_peer_id, &keypair).unwrap();
let signature = data.signatures.get(&keypair.public().into());
assert_eq!(signature, Some(&expected_signature), "{:?}", data.signatures);
}
#[test]
fn test_signature_call_stream() {
let init_peer_id = "init_peer_id";
let air_script = format!(
r#"
(call "{init_peer_id}" ("" "") [] $var) ; ok = "ok"
"#
);
let exec =
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_id), &air_script).unwrap();
let res = exec.execution_iter(init_peer_id).unwrap().last().unwrap();
assert_eq!(res.ret_code, 0, "{:?}", res);
let data = data_from_result(&res);
let expected_call_state = stream!("ok", 0, peer = init_peer_id, service = "..0");
let expected_cid = extract_service_result_cid(&expected_call_state);
let (keypair, _) = derive_dummy_keypair(init_peer_id);
let mut expected_tracker = SignatureTracker::new();
expected_tracker.register(init_peer_id, &expected_cid);
let expected_signature = expected_tracker.into_signature(init_peer_id, &keypair).unwrap();
let signature = data.signatures.get(&keypair.public().into());
assert_eq!(signature, Some(&expected_signature), "{:?}", data.signatures);
}
#[test]
fn test_signature_call_ununsed() {
let init_peer_id = "init_peer_id";
let air_script = format!(
r#"
(call "{init_peer_id}" ("" "") []) ; ok = "ok"
"#
);
let exec =
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_id), &air_script).unwrap();
let res = exec.execution_iter(init_peer_id).unwrap().last().unwrap();
assert_eq!(res.ret_code, 0, "{:?}", res);
let data = data_from_result(&res);
let (keypair, _) = derive_dummy_keypair(init_peer_id);
let mut expected_tracker = SignatureTracker::new();
let expected_signature = expected_tracker.into_signature(init_peer_id, &keypair).unwrap();
let signature = data.signatures.get(&keypair.public().into());
assert_eq!(signature, Some(&expected_signature), "{:?}", data.signatures);
}
#[test]
fn test_signature_call_merged() {
let init_peer_id = "init_peer_id";
let other_peer_id = "other_peer_id";
let air_script = format!(
r#"
(seq
(call "{init_peer_id}" ("" "") [] x) ; ok = "res0"
(seq
(call "{other_peer_id}" ("" "") [] y) ; ok = "res1"
(call "{init_peer_id}" ("" "") [] z) ; ok = "res2"
))
"#
);
let exec =
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_id), &air_script).unwrap();
let _ = exec.execute_one(init_peer_id).unwrap();
let _ = exec.execute_one(other_peer_id).unwrap();
let res2 = exec.execute_one(init_peer_id).unwrap();
let data2 = data_from_result(&res2);
let expected_call_state0 = scalar!("res0", peer = init_peer_id, service = "..0");
let expected_cid0 = extract_service_result_cid(&expected_call_state0);
let expected_call_state2 = scalar!("res2", peer = init_peer_id, service = "..2");
let expected_cid2 = extract_service_result_cid(&expected_call_state2);
let (keypair, _) = derive_dummy_keypair(init_peer_id);
let mut expected_tracker = SignatureTracker::new();
expected_tracker.register(init_peer_id, &expected_cid0);
expected_tracker.register(init_peer_id, &expected_cid2);
let expected_signature = expected_tracker.into_signature(init_peer_id, &keypair).unwrap();
let signature = data2.signatures.get(&keypair.public().into());
assert_eq!(signature, Some(&expected_signature), "{:?}", data2.signatures);
}
#[test]
fn test_signature_call_double() {
// Test that if some CID appears twice in the call result, it is accounted twice.
let init_peer_id = "init_peer_id";
let air_script = format!(
r#"
(seq
(seq (ap 1 $s) (ap 2 $s))
(fold $s i
(seq
(call "{init_peer_id}" ("" "") [] var) ; ok = "ok"
(next i))))
"#
);
let exec =
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_id), &air_script).unwrap();
let res = exec.execution_iter(init_peer_id).unwrap().last().unwrap();
assert_eq!(res.ret_code, 0, "{:?}", res);
let data = data_from_result(&res);
let expected_call_state = scalar!("ok", peer = init_peer_id, service = "..0");
let expected_cid = extract_service_result_cid(&expected_call_state);
let (keypair, _) = derive_dummy_keypair(init_peer_id);
let mut unexpected_tracker = SignatureTracker::new();
unexpected_tracker.register(init_peer_id, &expected_cid);
let unexpected_signature = unexpected_tracker.into_signature(init_peer_id, &keypair).unwrap();
let mut expected_tracker = SignatureTracker::new();
expected_tracker.register(init_peer_id, &expected_cid);
expected_tracker.register(init_peer_id, &expected_cid);
let expected_signature = expected_tracker.into_signature(init_peer_id, &keypair).unwrap();
assert_ne!(expected_signature, unexpected_signature, "test is incorrect");
let signature = data.signatures.get(&keypair.public().into());
assert_eq!(signature, Some(&expected_signature), "{:?}", data.signatures);
}
#[test]
fn test_signature_canon_basic() {
let init_peer_id = "init_peer_id";
let (keypair, _) = derive_dummy_keypair(init_peer_id);
let air_script = format!(
r#"
(seq
(call "{init_peer_id}" ("serv" "func") [] items) ; ok = [1, 2, 3]
(seq
(fold items i
(seq
(ap i $stream)
(next i)))
(canon "{init_peer_id}" $stream #canon)))
"#
);
let exec =
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_id), &air_script).unwrap();
let last_result = exec.execution_iter(init_peer_id).unwrap().last().unwrap();
let last_data = data_from_result(&last_result);
let expected_call_result = scalar!(
json!([1, 2, 3]),
peer = init_peer_id,
service = "serv..0",
function = "func"
);
let expected_call_result_cid = extract_service_result_cid(&expected_call_result);
let expected_canon_state = canon(json!({
"tetraplet": {"peer_pk": init_peer_id, "service_id": "", "function_name": "", "json_path": ""},
"values": [{
"result": 1,
"tetraplet": {
"peer_pk": init_peer_id,
"service_id": "serv..0",
"function_name": "func",
"json_path": ".$.[0]",
},
"provenance": Provenance::service_result(expected_call_result_cid.clone()),
}, {
"result": 2,
"tetraplet": {
"peer_pk": init_peer_id,
"service_id": "serv..0",
"function_name": "func",
"json_path": ".$.[1]",
},
"provenance": Provenance::service_result(expected_call_result_cid.clone()),
}, {
"result": 3,
"tetraplet": {
"peer_pk": init_peer_id,
"service_id": "serv..0",
"function_name": "func",
"json_path": ".$.[2]",
},
"provenance": Provenance::service_result(expected_call_result_cid.clone()),
}]
}));
let expected_canon_cid = extract_canon_result_cid(&expected_canon_state);
let mut expected_tracker = SignatureTracker::new();
expected_tracker.register(init_peer_id, &expected_canon_cid);
expected_tracker.register(init_peer_id.to_owned(), &expected_call_result_cid);
let expected_signature = expected_tracker.into_signature(init_peer_id, &keypair).unwrap();
let signature = last_data.signatures.get(&keypair.public().into());
assert_eq!(signature, Some(&expected_signature), "{:?}", last_data);
}
#[test]
fn test_signature_canon_merge() {
let init_peer_id = "init_peer_id";
let other_peer_id = "other_peer_id";
let (keypair, _) = derive_dummy_keypair(init_peer_id);
let air_script = format!(
r#"
(seq
(seq
(call "{init_peer_id}" ("serv" "func") [] items) ; ok = [1, 2, 3]
(seq
(fold items i
(seq
(ap i $stream)
(next i)))
(canon "{init_peer_id}" $stream #canon)))
(seq
(call "{other_peer_id}" ("" "") []) ; ok = "ok"
(call "{init_peer_id}" ("" "") []))) ; ok = "ok"
"#
);
let exec =
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_id), &air_script).unwrap();
exec.execute_all(init_peer_id);
exec.execute_one(other_peer_id);
let last_result = exec.execution_iter(init_peer_id).unwrap().last().unwrap();
let last_data = data_from_result(&last_result);
let expected_call_result = scalar!(
json!([1, 2, 3]),
peer = init_peer_id,
service = "serv..0",
function = "func"
);
let expected_call_result_cid = extract_service_result_cid(&expected_call_result);
let expected_canon_state = canon(json!({
"tetraplet": {"peer_pk": init_peer_id, "service_id": "", "function_name": "", "json_path": ""},
"values": [{
"result": 1,
"tetraplet": {
"peer_pk": init_peer_id,
"service_id": "serv..0",
"function_name": "func",
"json_path": ".$.[0]",
},
"provenance": Provenance::service_result(expected_call_result_cid.clone()),
}, {
"result": 2,
"tetraplet": {
"peer_pk": init_peer_id,
"service_id": "serv..0",
"function_name": "func",
"json_path": ".$.[1]",
},
"provenance": Provenance::service_result(expected_call_result_cid.clone()),
}, {
"result": 3,
"tetraplet": {
"peer_pk": init_peer_id,
"service_id": "serv..0",
"function_name": "func",
"json_path": ".$.[2]",
},
"provenance": Provenance::service_result(expected_call_result_cid.clone()),
}]
}));
let expected_canon_cid = extract_canon_result_cid(&expected_canon_state);
let mut expected_tracker = SignatureTracker::new();
expected_tracker.register(init_peer_id, &expected_canon_cid);
expected_tracker.register(init_peer_id, &expected_call_result_cid);
let expected_signature = expected_tracker.into_signature(init_peer_id, &keypair).unwrap();
let signature = last_data.signatures.get(&keypair.public().into());
assert_eq!(signature, Some(&expected_signature), "{:?}", last_data);
}
#[test]
fn test_signature_canon_result() {
// this test checks that call result in canon doesn't lead to repeadted accounting of the call result
let init_peer_id = "init_peer_id";
let (keypair, _) = derive_dummy_keypair(init_peer_id);
let air_script = format!(
r#"
(seq
(seq
(call "{init_peer_id}" ("serv" "func") [] items) ; ok = [1, 2, 3]
(fold items i
(seq
(ap i $stream)
(next i))))
(seq
(call "{init_peer_id}" ("serv" "func2") [] $stream) ; ok = 42
(canon "{init_peer_id}" $stream #canon)))
"#
);
let exec =
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_id), &air_script).unwrap();
let last_result = exec.execution_iter(init_peer_id).unwrap().last().unwrap();
let last_data = data_from_result(&last_result);
let expected_call_result1 = scalar!(
json!([1, 2, 3]),
peer = init_peer_id,
service = "serv..0",
function = "func"
);
let expected_call_result_cid1 = extract_service_result_cid(&expected_call_result1);
let expected_call_result2 = stream!(
json!(42),
1,
peer = init_peer_id,
service = "serv..1",
function = "func2"
);
let expected_call_result_cid2 = extract_service_result_cid(&expected_call_result2);
let expected_canon_state = canon(json!({
"tetraplet": {"peer_pk": init_peer_id, "service_id": "", "function_name": "", "json_path": ""},
"values": [{
"result": 1,
"tetraplet": {
"peer_pk": init_peer_id,
"service_id": "serv..0",
"function_name": "func",
"json_path": ".$.[0]",
},
"provenance": Provenance::service_result(expected_call_result_cid1.clone()),
}, {
"result": 2,
"tetraplet": {
"peer_pk": init_peer_id,
"service_id": "serv..0",
"function_name": "func",
"json_path": ".$.[1]",
},
"provenance": Provenance::service_result(expected_call_result_cid1.clone()),
}, {
"result": 3,
"tetraplet": {
"peer_pk": init_peer_id,
"service_id": "serv..0",
"function_name": "func",
"json_path": ".$.[2]",
},
"provenance": Provenance::service_result(expected_call_result_cid1.clone()),
}, {
"result": 42,
"tetraplet": {
"peer_pk": init_peer_id,
"service_id": "serv..1",
"function_name": "func2",
"json_path": "",
},
"provenance": Provenance::service_result(expected_call_result_cid2.clone()),
}]
}));
let expected_canon_cid = extract_canon_result_cid(&expected_canon_state);
let mut expected_tracker = SignatureTracker::new();
expected_tracker.register(init_peer_id, &expected_call_result_cid1);
expected_tracker.register(init_peer_id, &expected_call_result_cid2);
expected_tracker.register(init_peer_id, &expected_canon_cid);
let expected_signature = expected_tracker.into_signature(init_peer_id, &keypair).unwrap();
let signature = last_data.signatures.get(&keypair.public().into());
assert_eq!(signature, Some(&expected_signature), "{:?}", last_data);
}
#[cfg(feature = "gen_signatures")]
mod signing;

View File

@ -0,0 +1,457 @@
/*
* Copyright 2023 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 air::{ExecutionCidState, PreparationError};
use air_interpreter_signatures::{PeerCidTracker, PublicKey, SignatureStore};
use air_test_utils::key_utils::derive_dummy_keypair;
use air_test_utils::prelude::*;
use semver::Version;
/// This testing modules assert AquaVM resistance to various attacks.
///
/// CID store manipulations are checked in the `corruption` module.
#[test]
fn test_attack_injection_current_peer_scalar() {
// injecting a value that arrives to peer who does the next step
let (alice_keypair, alice_peer_id) = derive_dummy_keypair("alice_peer");
let (mallory_keypair, mallory_peer_id) = derive_dummy_keypair("mallory_peer");
let air_script = format!(
r#"
(seq
(seq
(call "{alice_peer_id}" ("" "") [] x)
(call "{mallory_peer_id}" ("" "") [] y))
(call "{alice_peer_id}" ("" "") [] z))
"#
);
let mut alice_cid_state = ExecutionCidState::new();
let mut alice_signature_tracker = PeerCidTracker::new(alice_peer_id.clone());
let mut alice_signature_store = SignatureStore::new();
let alice_call_1 = scalar_tracked!("good result", &mut alice_cid_state, peer = &alice_peer_id);
alice_signature_tracker.register(&*alice_peer_id, &extract_service_result_cid(&alice_call_1));
let alice_trace = vec![alice_call_1.clone()];
let alice_signature = alice_signature_tracker.gen_signature("", &alice_keypair).unwrap();
alice_signature_store.put(alice_keypair.public().into(), alice_signature);
let mut mallory_cid_state = alice_cid_state.clone();
let mut mallory_signature_tracker = PeerCidTracker::new(mallory_peer_id.clone());
let mut mallory_signature_store = alice_signature_store.clone();
let mallory_call_2 = scalar_tracked!("valid result", &mut mallory_cid_state, peer = &mallory_peer_id);
let fake_call_3 = scalar_tracked!("fake result", &mut mallory_cid_state, peer = &alice_peer_id);
mallory_signature_tracker.register(&*mallory_peer_id, &extract_service_result_cid(&mallory_call_2));
let mallory_trace = vec![alice_call_1, mallory_call_2, fake_call_3];
let mallory_signature = mallory_signature_tracker.gen_signature("", &mallory_keypair).unwrap();
mallory_signature_store.put(mallory_keypair.public().into(), mallory_signature);
let alice_data = InterpreterData::from_execution_result(
alice_trace.into(),
alice_cid_state.into(),
alice_signature_store,
2,
Version::new(1, 1, 1),
);
let mallory_data = InterpreterData::from_execution_result(
mallory_trace.into(),
mallory_cid_state.into(),
mallory_signature_store,
2,
Version::new(1, 1, 1),
);
let mut alice_avm = create_avm_with_key::<NativeAirRunner>(alice_keypair, unit_call_service());
let test_run_params = TestRunParameters::from_init_peer_id(alice_peer_id);
let prev_data = serde_json::to_vec(&alice_data).unwrap();
let cur_data = serde_json::to_vec(&mallory_data).unwrap();
let res = alice_avm
.call(&air_script, prev_data, cur_data, test_run_params)
.unwrap();
assert_ne!(res.ret_code, 0);
}
#[test]
fn test_attack_injection_current_peer_stream() {
// injecting a value that arrives to peer who does the next step
let alice_peer_id = "alice_peer";
let mallory_peer_id = "mallory_peer";
let (alice_keypair, alice_peer_id) = derive_dummy_keypair(alice_peer_id);
let (mallory_keypair, mallory_peer_id) = derive_dummy_keypair(mallory_peer_id);
let alice_pk: PublicKey = alice_keypair.public().into();
let mallory_pk: PublicKey = mallory_keypair.public().into();
let air_script = format!(
r#"
(seq
(seq
(call "{alice_peer_id}" ("" "") [] x)
(call "{mallory_peer_id}" ("" "") [] y))
(call "{alice_peer_id}" ("" "") [] $z))
"#
);
let mut alice_cid_state = ExecutionCidState::default();
let alice_call_1 = scalar_tracked!("good result", &mut alice_cid_state, peer = &alice_peer_id);
let mut alice_signature_tracker = PeerCidTracker::new(alice_peer_id.clone());
alice_signature_tracker.register(&*alice_peer_id, &extract_service_result_cid(&alice_call_1));
let mut alice_signature_store = SignatureStore::new();
let alice_signature = alice_signature_tracker.gen_signature("", &alice_keypair).unwrap();
alice_signature_store.put(alice_pk, alice_signature);
let alice_trace = vec![alice_call_1.clone()];
let mut mallory_cid_state = alice_cid_state.clone();
let mallory_call_2 = scalar_tracked!("valid result", &mut mallory_cid_state, peer = &mallory_peer_id);
let fake_call_3 = stream_tracked!("fake result", 0, &mut mallory_cid_state, peer = &alice_peer_id);
let mut mallory_signature_tracker = PeerCidTracker::new(mallory_peer_id.clone());
mallory_signature_tracker.register(&*mallory_peer_id, &extract_service_result_cid(&mallory_call_2));
let mut mallory_signature_store = SignatureStore::new();
let mallory_signature = mallory_signature_tracker.gen_signature("", &mallory_keypair).unwrap();
mallory_signature_store.put(mallory_pk, mallory_signature);
let mallory_trace = vec![alice_call_1, mallory_call_2, fake_call_3];
let alice_data = InterpreterData::from_execution_result(
alice_trace.into(),
alice_cid_state.into(),
alice_signature_store,
2,
Version::new(1, 1, 1),
);
let mallory_data = InterpreterData::from_execution_result(
mallory_trace.into(),
mallory_cid_state.into(),
mallory_signature_store,
2,
Version::new(1, 1, 1),
);
let mut alice_avm = create_avm_with_key::<NativeAirRunner>(alice_keypair, unit_call_service());
let test_run_params = TestRunParameters::from_init_peer_id(alice_peer_id);
let prev_data = serde_json::to_vec(&alice_data).unwrap();
let cur_data = serde_json::to_vec(&mallory_data).unwrap();
let res = alice_avm
.call(&air_script, prev_data, cur_data, test_run_params)
.unwrap();
assert_ne!(res.ret_code, 0, "{}", res.error_message);
}
#[test]
fn test_attack_injection_current_injection_unused() {
// injecting a value that arrives to peer who does the next step
let alice_peer_id = "alice_peer";
let mallory_peer_id = "mallory_peer";
let (alice_keypair, alice_peer_id) = derive_dummy_keypair(alice_peer_id);
let (mallory_keypair, mallory_peer_id) = derive_dummy_keypair(mallory_peer_id);
let alice_pk: PublicKey = alice_keypair.public().into();
let mallory_pk: PublicKey = mallory_keypair.public().into();
let air_script = format!(
r#"
(seq
(seq
(call "{alice_peer_id}" ("" "") [] x)
(call "{mallory_peer_id}" ("" "") [] y))
(call "{alice_peer_id}" ("" "") []))
"#
);
let mut alice_cid_state = ExecutionCidState::default();
let alice_call_1 = scalar_tracked!("good result", &mut alice_cid_state, peer = &alice_peer_id);
let alice_trace = vec![alice_call_1.clone()];
let mut mallory_cid_state = alice_cid_state.clone();
let mallory_call_2 = scalar_tracked!("valid result", &mut mallory_cid_state, peer = &mallory_peer_id);
let fake_call_3 = unused!("fake result", peer = &alice_peer_id);
let mallory_trace = vec![alice_call_1, mallory_call_2, fake_call_3];
let mut alice_signature_store = SignatureStore::new();
let mut alice_cid_tracker = PeerCidTracker::new(alice_peer_id.clone());
alice_cid_tracker.register(&alice_peer_id, &extract_service_result_cid(&mallory_trace[0]));
let alice_signature = alice_cid_tracker.gen_signature("", &alice_keypair).unwrap();
alice_signature_store.put(alice_pk, alice_signature);
let mallory_signature_store = alice_signature_store.clone();
let mut mallory_cid_tracker = PeerCidTracker::new(mallory_peer_id.clone());
mallory_cid_tracker.register(&mallory_peer_id, &extract_service_result_cid(&mallory_trace[1]));
let mallory_signature = mallory_cid_tracker.gen_signature("", &mallory_keypair).unwrap();
alice_signature_store.put(mallory_pk, mallory_signature);
let alice_data = InterpreterData::from_execution_result(
alice_trace.into(),
alice_cid_state.into(),
alice_signature_store,
2,
Version::new(1, 1, 1),
);
let mallory_data = InterpreterData::from_execution_result(
mallory_trace.into(),
mallory_cid_state.into(),
mallory_signature_store,
2,
Version::new(1, 1, 1),
);
let mut alice_avm = create_avm_with_key::<NativeAirRunner>(alice_keypair, unit_call_service());
let test_run_params = TestRunParameters::from_init_peer_id(alice_peer_id);
let prev_data = serde_json::to_vec(&alice_data).unwrap();
let cur_data = serde_json::to_vec(&mallory_data).unwrap();
let res = alice_avm
.call(&air_script, prev_data, cur_data, test_run_params)
.unwrap();
assert_ne!(res.ret_code, 0, "{}", res.error_message);
}
#[test]
fn test_attack_injection_other_peer_scalar() {
// injecting a value that arrives to peer who does the next step
let alice_peer_id = "alice_peer";
let bob_peer_id = "bob_peer";
let mallory_peer_id = "mallory_peer";
let (alice_keypair, alice_peer_id) = derive_dummy_keypair(alice_peer_id);
let (bob_keypair, bob_peer_id) = derive_dummy_keypair(bob_peer_id);
let (mallory_keypair, mallory_peer_id) = derive_dummy_keypair(mallory_peer_id);
let alice_pk: PublicKey = alice_keypair.public().into();
let mallory_pk: PublicKey = mallory_keypair.public().into();
let air_script = format!(
r#"
(seq
(seq
(call "{alice_peer_id}" ("" "") [] x)
(call "{mallory_peer_id}" ("" "") [] y))
(call "{bob_peer_id}" ("" "") [] z))
"#
);
let mut mallory_cid_state = ExecutionCidState::default();
let alice_call_1 = scalar_tracked!("good result", &mut mallory_cid_state, peer = &alice_peer_id);
let mallory_call_2 = scalar_tracked!("valid result", &mut mallory_cid_state, peer = &mallory_peer_id);
let fake_call_3 = scalar_tracked!("fake result", &mut mallory_cid_state, peer = &alice_peer_id);
let mallory_trace = vec![alice_call_1, mallory_call_2, fake_call_3];
let mut signature_store = SignatureStore::new();
let mut alice_cid_tracker = PeerCidTracker::new(alice_peer_id.clone());
alice_cid_tracker.register(&alice_peer_id, &extract_service_result_cid(&mallory_trace[0]));
let alice_signature = alice_cid_tracker.gen_signature("", &alice_keypair).unwrap();
signature_store.put(alice_pk, alice_signature);
let mut mallory_cid_tracker = PeerCidTracker::new(mallory_peer_id.clone());
mallory_cid_tracker.register(&mallory_peer_id, &extract_service_result_cid(&mallory_trace[1]));
let mallory_signature = mallory_cid_tracker.gen_signature("", &mallory_keypair).unwrap();
signature_store.put(mallory_pk, mallory_signature);
let mallory_data = InterpreterData::from_execution_result(
mallory_trace.into(),
mallory_cid_state.into(),
signature_store,
2,
Version::new(1, 1, 1),
);
let mut bob_avm = create_avm_with_key::<NativeAirRunner>(bob_keypair, unit_call_service());
let test_run_params = TestRunParameters::from_init_peer_id(alice_peer_id);
let prev_data = "";
let cur_data = serde_json::to_vec(&mallory_data).unwrap();
let res = bob_avm.call(&air_script, prev_data, cur_data, test_run_params).unwrap();
assert_ne!(res.ret_code, 0);
}
#[test]
fn test_attack_injection_other_peer_stream() {
// injecting a value that arrives to peer who does the next step
let alice_peer_id = "alice_peer";
let bob_peer_id = "bob_peer";
let mallory_peer_id = "mallory_peer";
let (alice_keypair, alice_peer_id) = derive_dummy_keypair(alice_peer_id);
let (bob_keypair, bob_peer_id) = derive_dummy_keypair(bob_peer_id);
let (mallory_keypair, mallory_peer_id) = derive_dummy_keypair(mallory_peer_id);
let alice_pk: PublicKey = alice_keypair.public().into();
let mallory_pk: PublicKey = mallory_keypair.public().into();
let air_script = format!(
r#"
(seq
(seq
(call "{alice_peer_id}" ("" "") [] x)
(call "{mallory_peer_id}" ("" "") [] y))
(call "{bob_peer_id}" ("" "") [] $z))
"#
);
let mut mallory_cid_state = ExecutionCidState::default();
let alice_call_1 = scalar_tracked!("good result", &mut mallory_cid_state, peer = &alice_peer_id);
let mallory_call_2 = scalar_tracked!("valid result", &mut mallory_cid_state, peer = &mallory_peer_id);
let fake_call_3 = stream_tracked!("fake result", 0, &mut mallory_cid_state, peer = &alice_peer_id);
let mut signature_store = SignatureStore::new();
let mut alice_signature_tracker = PeerCidTracker::new(alice_peer_id.clone());
alice_signature_tracker.register(&*alice_peer_id, &extract_service_result_cid(&alice_call_1));
let alice_signature = alice_signature_tracker.gen_signature("", &alice_keypair).unwrap();
signature_store.put(alice_pk, alice_signature);
let mut mallory_signature_tracker = PeerCidTracker::new(mallory_peer_id.clone());
mallory_signature_tracker.register(&*mallory_peer_id, &extract_service_result_cid(&mallory_call_2));
let mallory_signature = mallory_signature_tracker.gen_signature("", &mallory_keypair).unwrap();
signature_store.put(mallory_pk, mallory_signature);
let mallory_trace = vec![alice_call_1, mallory_call_2, fake_call_3];
let mallory_data = InterpreterData::from_execution_result(
mallory_trace.into(),
mallory_cid_state.into(),
signature_store,
2,
Version::new(1, 1, 1),
);
let mut bob_avm = create_avm_with_key::<NativeAirRunner>(bob_keypair, unit_call_service());
let test_run_params = TestRunParameters::from_init_peer_id(alice_peer_id);
let prev_data = "";
let cur_data = serde_json::to_vec(&mallory_data).unwrap();
let res = bob_avm.call(&air_script, prev_data, cur_data, test_run_params).unwrap();
assert_ne!(res.ret_code, 0, "{}", res.error_message);
}
#[test]
fn test_attack_injection_other_peer_unused() {
// injecting a value that arrives to peer who does the next step
let alice_peer_id = "alice_peer";
let bob_peer_id = "bob_peer";
let mallory_peer_id = "mallory_peer";
let (alice_keypair, alice_peer_id) = derive_dummy_keypair(alice_peer_id);
let (bob_keypair, bob_peer_id) = derive_dummy_keypair(bob_peer_id);
let (mallory_keypair, mallory_peer_id) = derive_dummy_keypair(mallory_peer_id);
let alice_pk: PublicKey = alice_keypair.public().into();
let mallory_pk: PublicKey = mallory_keypair.public().into();
let air_script = format!(
r#"
(seq
(seq
(call "{alice_peer_id}" ("" "") [] x)
(call "{mallory_peer_id}" ("" "") [] y))
(call "{bob_peer_id}" ("" "") []))
"#
);
let mut mallory_cid_state = ExecutionCidState::default();
let alice_call_1 = scalar_tracked!("good result", &mut mallory_cid_state, peer = &alice_peer_id);
let mallory_call_2 = scalar_tracked!("valid result", &mut mallory_cid_state, peer = &mallory_peer_id);
let fake_call_3 = unused!("fake result", peer = &alice_peer_id);
let mut signature_store = SignatureStore::new();
let mut alice_signature_tracker = PeerCidTracker::new(alice_peer_id.clone());
alice_signature_tracker.register(&*alice_peer_id, &extract_service_result_cid(&alice_call_1));
let alice_signature = alice_signature_tracker.gen_signature("", &alice_keypair).unwrap();
signature_store.put(alice_pk, alice_signature);
let mut mallory_signature_tracker = PeerCidTracker::new(mallory_peer_id.clone());
mallory_signature_tracker.register(&*mallory_peer_id, &extract_service_result_cid(&mallory_call_2));
let mallory_signature = mallory_signature_tracker.gen_signature("", &mallory_keypair).unwrap();
signature_store.put(mallory_pk, mallory_signature);
let mallory_trace = vec![alice_call_1, mallory_call_2, fake_call_3];
let mallory_data = InterpreterData::from_execution_result(
mallory_trace.into(),
mallory_cid_state.into(),
signature_store,
2,
Version::new(1, 1, 1),
);
let mut bob_avm = create_avm_with_key::<NativeAirRunner>(bob_keypair, unit_call_service());
let test_run_params = TestRunParameters::from_init_peer_id(alice_peer_id);
let prev_data = "";
let cur_data = serde_json::to_vec(&mallory_data).unwrap();
let res = bob_avm.call(&air_script, prev_data, cur_data, test_run_params).unwrap();
// please not that such injection is not caught
assert_eq!(res.ret_code, 0, "{}", res.error_message);
}
#[test]
fn test_attack_replay() {
let alice_name = "alice_peer_id";
let bob_name = "bob_peer_id";
let (alice_keypair, alice_peer_id) = derive_dummy_keypair(alice_name);
let (bob_keypair, _) = derive_dummy_keypair(bob_name);
let air_script = format!(
r#"(seq
(call "{alice_peer_id}" ("" "") [] y)
(call "bob" ("" "") [] z))"#
);
let mut alice_avm = create_avm_with_key::<NativeAirRunner>(alice_keypair.clone(), unit_call_service());
let mut bob_avm = create_avm_with_key::<NativeAirRunner>(bob_keypair.clone(), unit_call_service());
let run_params1 = TestRunParameters::from_init_peer_id(&alice_peer_id).with_particle_id("first_particle");
let run_params2 = run_params1.clone();
let res1 = alice_avm.call(&air_script, "", "", run_params1.clone()).unwrap();
let res2 = alice_avm.call(&air_script, "", "", run_params2).unwrap();
assert_eq!(res1.ret_code, 0, "test validity check failed: {}", res1.error_message);
assert_eq!(res1, res2, "test validity check failed");
let res_bob = bob_avm.call(&air_script, "", res1.data.clone(), run_params1).unwrap();
assert_eq!(
res_bob.ret_code, 0,
"test validity check failed: {}",
res_bob.error_message
);
let mallory_run_params = TestRunParameters::from_init_peer_id(&alice_peer_id).with_particle_id("second_particle");
let res_replay = bob_avm.call(&air_script, "", res1.data, mallory_run_params).unwrap();
let dalek_error = ed25519_dalek::ed25519::Error::from_source("Verification equation was not satisfied");
let nested_error = fluence_keypair::error::VerificationError::Ed25519(
dalek_error,
// will break if signed data format changes
"3eSuF5uvjQvmvSC6vu5Kmb8bJcswXhNUcqsSG9USEad1oNgnpAcBNm2maM4Tyk3BsLYnwdwNEj4KiJ4pqence7XF".to_owned(),
"6m3zmtymxDL56KBpNgKqc7QiGRuWuxr82bG2q7dF5xCD".to_owned(),
);
let cids: Vec<Box<str>> = vec!["bagaaieraazcwm4lxybe4pwlisvcgpv4mii63nxouogvf4ihkmz762mnhea7a".into()];
let expected = PreparationError::DataSignatureCheckError(verification::DataVerifierError::SignatureMismatch {
error: nested_error.into(),
cids,
peer_id: alice_peer_id,
});
assert_error_eq!(&res_replay, expected);
}

View File

@ -0,0 +1,536 @@
/*
* Copyright 2023 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.
*/
/// This test module asserts CID store verification functionality:
/// values forged in the CID stores.
use air::ExecutionCidState;
use air::PreparationError;
use air_interpreter_signatures::PeerCidTracker;
use air_interpreter_signatures::PublicKey;
use air_interpreter_signatures::SignatureStore;
use air_test_utils::key_utils::derive_dummy_keypair;
use air_test_utils::prelude::*;
use pretty_assertions::assert_eq;
use semver::Version;
#[test]
fn test_attack_replace_value() {
// Bob gets a trace where call result value is edited by Mallory.
let alice_peer_id = "alice";
let bob_peer_id = "bob";
let mallory_peer_id = "mallory";
let (alice_keypair, alice_peer_id) = derive_dummy_keypair(alice_peer_id);
let (mallory_keypair, mallory_peer_id) = derive_dummy_keypair(mallory_peer_id);
let alice_pk: PublicKey = alice_keypair.public().into();
let mallory_pk: PublicKey = mallory_keypair.public().into();
let air_script = format!(
r#"
(seq
(seq
(call "{alice_peer_id}" ("" "") [] x)
(call "{mallory_peer_id}" ("" "") [] y))
(call "{bob_peer_id}" ("" "") [] z))
"#
);
let mut mallory_cid_state = ExecutionCidState::new();
let mallory_trace = vec![
scalar_tracked!("alice", &mut mallory_cid_state, peer = &alice_peer_id),
scalar_tracked!("mallory", &mut mallory_cid_state, peer = &mallory_peer_id),
];
let mut mallory_cid_info = serde_json::to_value::<CidInfo>(mallory_cid_state.into()).unwrap();
let mut cnt = 0;
for (_cid, val) in mallory_cid_info["value_store"].as_object_mut().unwrap().iter_mut() {
if *val == "alice" {
*val = "evil".into();
cnt += 1;
}
}
assert_eq!(cnt, 1, "test validity failed");
let mut signature_store = SignatureStore::new();
let mut alice_cid_tracker = PeerCidTracker::new(alice_peer_id.clone());
alice_cid_tracker.register(&alice_peer_id, &extract_service_result_cid(&mallory_trace[0]));
let alice_signature = alice_cid_tracker.gen_signature("", &alice_keypair).unwrap();
signature_store.put(alice_pk, alice_signature);
let mut mallory_cid_tracker = PeerCidTracker::new(mallory_peer_id.clone());
mallory_cid_tracker.register(&mallory_peer_id, &extract_service_result_cid(&mallory_trace[1]));
let mallory_signature = mallory_cid_tracker.gen_signature("", &mallory_keypair).unwrap();
signature_store.put(mallory_pk, mallory_signature);
let mallory_data = InterpreterData::from_execution_result(
mallory_trace.into(),
serde_json::from_value(mallory_cid_info).unwrap(),
signature_store,
0,
Version::new(1, 1, 1),
);
let mut bob_avm = create_avm(unit_call_service(), bob_peer_id);
let test_run_params = TestRunParameters::from_init_peer_id(alice_peer_id);
let prev_data = "";
let cur_data = serde_json::to_vec(&mallory_data).unwrap();
let res = bob_avm.call(&air_script, prev_data, cur_data, test_run_params).unwrap();
assert_error_eq!(
&res,
PreparationError::CidStoreVerificationError(CidStoreVerificationError::MismatchError {
type_name: "serde_json::value::Value",
cid_repr: "bagaaierabjifaczkgq2745dsq57lelki2r5cfduunmfzsgvxiavi2ahwwmwq".to_owned(),
})
);
}
#[test]
fn test_attack_replace_tetraplet() {
// Bob gets a trace where call result tetraplet is edited by Mallory.
let alice_peer_id = "alice";
let bob_peer_id = "bob";
let mallory_peer_id = "mallory";
let (alice_keypair, alice_peer_id) = derive_dummy_keypair(alice_peer_id);
let (mallory_keypair, mallory_peer_id) = derive_dummy_keypair(mallory_peer_id);
let alice_pk: PublicKey = alice_keypair.public().into();
let mallory_pk: PublicKey = mallory_keypair.public().into();
let air_script = format!(
r#"
(seq
(seq
(call "{alice_peer_id}" ("" "") [] x)
(call "{mallory_peer_id}" ("" "") [] y))
(call "{bob_peer_id}" ("" "") [] z))
"#
);
let mut mallory_cid_state = ExecutionCidState::new();
let mallory_trace = vec![
scalar_tracked!("alice", &mut mallory_cid_state, peer = &alice_peer_id),
scalar_tracked!("mallory", &mut mallory_cid_state, peer = &mallory_peer_id),
];
let mut mallory_cid_info = serde_json::to_value::<CidInfo>(mallory_cid_state.into()).unwrap();
let mut cnt = 0;
for (_cid, tetraplet_val) in mallory_cid_info["tetraplet_store"].as_object_mut().unwrap().iter_mut() {
if tetraplet_val["peer_pk"] == alice_peer_id {
tetraplet_val["service_id"] = json!("evil");
cnt += 1;
}
}
assert_eq!(cnt, 1, "test validity failed");
let mut signature_store = SignatureStore::new();
let mut alice_cid_tracker = PeerCidTracker::new(alice_peer_id.clone());
alice_cid_tracker.register(&alice_peer_id, &extract_service_result_cid(&mallory_trace[0]));
let alice_signature = alice_cid_tracker.gen_signature("", &alice_keypair).unwrap();
signature_store.put(alice_pk, alice_signature);
let mut mallory_cid_tracker = PeerCidTracker::new(mallory_peer_id.clone());
mallory_cid_tracker.register(&mallory_peer_id, &extract_service_result_cid(&mallory_trace[1]));
let mallory_signature = mallory_cid_tracker.gen_signature("", &mallory_keypair).unwrap();
signature_store.put(mallory_pk, mallory_signature);
let mallory_data = InterpreterData::from_execution_result(
mallory_trace.into(),
serde_json::from_value(mallory_cid_info).unwrap(),
signature_store,
0,
Version::new(1, 1, 1),
);
let mut bob_avm = create_avm(unit_call_service(), bob_peer_id);
let test_run_params = TestRunParameters::from_init_peer_id(alice_peer_id);
let prev_data = "";
let cur_data = serde_json::to_vec(&mallory_data).unwrap();
let res = bob_avm.call(&air_script, prev_data, cur_data, test_run_params).unwrap();
assert_error_eq!(
&res,
PreparationError::CidStoreVerificationError(CidStoreVerificationError::MismatchError {
type_name: "marine_call_parameters::SecurityTetraplet",
cid_repr: "bagaaierapisclqfeq36psuo6uxiazvcash32pndayqlwxrqchii2ykxerfba".to_owned(),
})
);
}
#[test]
fn test_attack_replace_call_result() {
// Bob gets a trace where call result is edited by Mallory.
let alice_peer_id = "alice";
let bob_peer_id = "bob";
let mallory_peer_id = "mallory";
let (alice_keypair, alice_peer_id) = derive_dummy_keypair(alice_peer_id);
let (mallory_keypair, mallory_peer_id) = derive_dummy_keypair(mallory_peer_id);
let alice_pk: PublicKey = alice_keypair.public().into();
let mallory_pk: PublicKey = mallory_keypair.public().into();
let air_script = format!(
r#"
(seq
(seq
(call "{alice_peer_id}" ("" "") [] x)
(call "{mallory_peer_id}" ("" "") [] y))
(call "{bob_peer_id}" ("" "") [] z))
"#
);
let mut mallory_cid_state = ExecutionCidState::new();
let alice_trace_1 = scalar_tracked!("alice", &mut mallory_cid_state, peer = &alice_peer_id);
let alice_trace_1_cid = (*extract_service_result_cid(&alice_trace_1)).clone().into_inner();
let mallory_trace = vec![
alice_trace_1,
scalar_tracked!("mallory", &mut mallory_cid_state, peer = &mallory_peer_id),
];
let mut mallory_cid_info = serde_json::to_value::<CidInfo>(mallory_cid_state.into()).unwrap();
let mut cnt = 0;
for (cid, service_cid_val) in mallory_cid_info["service_result_store"]
.as_object_mut()
.unwrap()
.iter_mut()
{
if *cid == alice_trace_1_cid {
service_cid_val["argument_hash"] = "42".into();
cnt += 1;
}
}
assert_eq!(cnt, 1, "test validity failed");
let mut signature_store = SignatureStore::new();
let mut alice_cid_tracker = PeerCidTracker::new(alice_peer_id.clone());
alice_cid_tracker.register(&alice_peer_id, &extract_service_result_cid(&mallory_trace[0]));
let alice_signature = alice_cid_tracker.gen_signature("", &alice_keypair).unwrap();
signature_store.put(alice_pk, alice_signature);
let mut mallory_cid_tracker = PeerCidTracker::new(mallory_peer_id.clone());
mallory_cid_tracker.register(&mallory_peer_id, &extract_service_result_cid(&mallory_trace[1]));
let mallory_signature = mallory_cid_tracker.gen_signature("", &mallory_keypair).unwrap();
signature_store.put(mallory_pk, mallory_signature);
let mallory_data = InterpreterData::from_execution_result(
mallory_trace.into(),
serde_json::from_value(mallory_cid_info).unwrap(),
signature_store,
0,
Version::new(1, 1, 1),
);
let mut bob_avm = create_avm(unit_call_service(), bob_peer_id);
let test_run_params = TestRunParameters::from_init_peer_id(alice_peer_id);
let prev_data = "";
let cur_data = serde_json::to_vec(&mallory_data).unwrap();
let res = bob_avm.call(&air_script, prev_data, cur_data, test_run_params).unwrap();
assert_error_eq!(
&res,
PreparationError::CidStoreVerificationError(CidStoreVerificationError::MismatchError {
type_name: "air_interpreter_data::executed_state::ServiceResultCidAggregate",
cid_repr: "bagaaierarbji6ebokx3pantdp6xg2l57bhdj7pmlydwe2wnbd6fdkatg7xka".to_owned(),
})
);
}
#[test]
fn test_attack_replace_canon_value() {
// Bob gets a trace where canon value is edited by Mallory.
let alice_peer_id = "alice";
let bob_peer_id = "bob";
let mallory_peer_id = "mallory";
let (alice_keypair, alice_peer_id) = derive_dummy_keypair(alice_peer_id);
let (mallory_keypair, mallory_peer_id) = derive_dummy_keypair(mallory_peer_id);
let alice_pk: PublicKey = alice_keypair.public().into();
let mallory_pk: PublicKey = mallory_keypair.public().into();
let air_script = format!(
r#"
(seq
(seq
(ap 1 $s)
(canon "{alice_peer_id}" $s #c))
(seq
(call "{mallory_peer_id}" ("" "") [] x)
(call "{bob_peer_id}" ("" "") [])))
"#
);
let mut mallory_cid_state = ExecutionCidState::new();
let alice_canon_cid = canon_tracked(
json!({
"tetraplet": {"peer_pk": &alice_peer_id, "service_id": "", "function_name": "", "json_path": ""},
"values": [{
"tetraplet": {"peer_pk": &alice_peer_id, "service_id": "", "function_name": "", "json_path": ""},
"result": 1,
"provenance": Provenance::literal(),
}]
}),
&mut mallory_cid_state,
);
let mallory_call_result_state = scalar_tracked!("mallory", &mut mallory_cid_state, peer = &mallory_peer_id);
let mallory_call_result_cid = extract_service_result_cid(&mallory_call_result_state);
let mallory_trace = vec![ap(0), ap(0), alice_canon_cid, mallory_call_result_state];
let mut mallory_cid_info = serde_json::to_value::<CidInfo>(mallory_cid_state.into()).unwrap();
let mut cnt = 0;
for (_cid, canon_element) in mallory_cid_info["canon_element_store"]
.as_object_mut()
.unwrap()
.iter_mut()
{
canon_element["provenance"] = json!(Provenance::service_result(mallory_call_result_cid.clone()));
cnt += 1;
}
assert_eq!(cnt, 1, "test validity failed");
let mut signature_store = SignatureStore::new();
let mut alice_cid_tracker = PeerCidTracker::new(alice_peer_id.clone());
alice_cid_tracker.register(&alice_peer_id, &extract_canon_result_cid(&mallory_trace[2]));
let alice_signature = alice_cid_tracker.gen_signature("", &alice_keypair).unwrap();
signature_store.put(alice_pk, alice_signature);
let mut mallory_cid_tracker = PeerCidTracker::new(mallory_peer_id.clone());
mallory_cid_tracker.register(&mallory_peer_id, &extract_service_result_cid(&mallory_trace[3]));
let mallory_signature = mallory_cid_tracker.gen_signature("", &mallory_keypair).unwrap();
signature_store.put(mallory_pk, mallory_signature);
let mallory_data = InterpreterData::from_execution_result(
mallory_trace.into(),
serde_json::from_value(mallory_cid_info).unwrap(),
signature_store,
0,
Version::new(1, 1, 1),
);
let mut bob_avm = create_avm(unit_call_service(), bob_peer_id);
let test_run_params = TestRunParameters::from_init_peer_id(alice_peer_id);
let prev_data = "";
let cur_data = serde_json::to_vec(&mallory_data).unwrap();
let res = bob_avm.call(&air_script, prev_data, cur_data, test_run_params).unwrap();
assert_error_eq!(
&res,
PreparationError::CidStoreVerificationError(CidStoreVerificationError::MismatchError {
type_name: "air_interpreter_data::executed_state::CanonCidAggregate",
cid_repr: "bagaaierayrb7yu6tvdofr3d7tvuzx7fb3uve27rqty4ckzy7ox66oicuhjjq".to_owned(),
})
);
}
#[test]
fn test_attack_replace_canon_result_values() {
// Bob gets a trace where canon result is edited by Mallory.
let alice_peer_id = "alice";
let bob_peer_id = "bob";
let mallory_peer_id = "mallory";
let (alice_keypair, alice_peer_id) = derive_dummy_keypair(alice_peer_id);
let (mallory_keypair, mallory_peer_id) = derive_dummy_keypair(mallory_peer_id);
let alice_pk: PublicKey = alice_keypair.public().into();
let mallory_pk: PublicKey = mallory_keypair.public().into();
let air_script = format!(
r#"
(seq
(seq
(seq
(ap 1 $s)
(ap 2 $s))
(canon "{alice_peer_id}" $s #c))
(seq
(call "{mallory_peer_id}" ("" "") [] x)
(call "{bob_peer_id}" ("" "") [])))
"#
);
let mut mallory_cid_state = ExecutionCidState::new();
let alice_canon_cid = canon_tracked(
json!({
"tetraplet": {"peer_pk": alice_peer_id, "service_id": "", "function_name": "", "json_path": ""},
"values": [{
"tetraplet": {"peer_pk": alice_peer_id, "service_id": "", "function_name": "", "json_path": ""},
"result": 1,
"provenance": Provenance::literal(),
}, {
"tetraplet": {"peer_pk": alice_peer_id, "service_id": "", "function_name": "", "json_path": ""},
"result": 2,
"provenance": Provenance::literal(),
}]
}),
&mut mallory_cid_state,
);
let mallory_trace = vec![
ap(0),
ap(0),
alice_canon_cid,
scalar_tracked!("mallory", &mut mallory_cid_state, peer = &mallory_peer_id),
];
let mut mallory_cid_info = serde_json::to_value::<CidInfo>(mallory_cid_state.into()).unwrap();
let mut cnt = 0;
for (_cid, canon_result) in mallory_cid_info["canon_result_store"]
.as_object_mut()
.unwrap()
.iter_mut()
{
canon_result["values"].as_array_mut().unwrap().pop();
cnt += 1;
}
assert_eq!(cnt, 1, "test validity failed");
let mut signature_store = SignatureStore::new();
let mut alice_cid_tracker = PeerCidTracker::new(alice_peer_id.clone());
alice_cid_tracker.register(&alice_peer_id, &extract_canon_result_cid(&mallory_trace[2]));
let alice_signature = alice_cid_tracker.gen_signature("", &alice_keypair).unwrap();
signature_store.put(alice_pk, alice_signature);
let mut mallory_cid_tracker = PeerCidTracker::new(mallory_peer_id.clone());
mallory_cid_tracker.register(&mallory_peer_id, &extract_service_result_cid(&mallory_trace[3]));
let mallory_signature = mallory_cid_tracker.gen_signature("", &mallory_keypair).unwrap();
signature_store.put(mallory_pk, mallory_signature);
let mallory_data = InterpreterData::from_execution_result(
mallory_trace.into(),
serde_json::from_value(mallory_cid_info).unwrap(),
signature_store,
0,
Version::new(1, 1, 1),
);
let mut bob_avm = create_avm(unit_call_service(), bob_peer_id);
let test_run_params = TestRunParameters::from_init_peer_id(alice_peer_id);
let prev_data = "";
let cur_data = serde_json::to_vec(&mallory_data).unwrap();
let res = bob_avm.call(&air_script, prev_data, cur_data, test_run_params).unwrap();
assert_error_eq!(
&res,
PreparationError::CidStoreVerificationError(CidStoreVerificationError::MismatchError {
type_name: "air_interpreter_data::executed_state::CanonResultCidAggregate",
cid_repr: "bagaaieratezrhuyz2eprlmiidxywv6ir2tmswlxycad37noykg3p5oxhs5tq".to_owned(),
})
);
}
#[test]
fn test_attack_replace_canon_result_tetraplet() {
// Bob gets a trace where canon result is edited by Mallory.
let alice_peer_id = "alice";
let bob_peer_id = "bob";
let mallory_peer_id = "mallory";
let (alice_keypair, alice_peer_id) = derive_dummy_keypair(alice_peer_id);
let (mallory_keypair, mallory_peer_id) = derive_dummy_keypair(mallory_peer_id);
let alice_pk: PublicKey = alice_keypair.public().into();
let mallory_pk: PublicKey = mallory_keypair.public().into();
let air_script = format!(
r#"
(seq
(seq
(seq
(ap 1 $s)
(ap 2 $s))
(canon "{alice_peer_id}" $s #c))
(seq
(call "{mallory_peer_id}" ("" "") [] x)
(call "{bob_peer_id}" ("" "") [])))
"#
);
let mut mallory_cid_state = ExecutionCidState::new();
let alice_canon_cid = canon_tracked(
json!({
"tetraplet": {"peer_pk": alice_peer_id, "service_id": "", "function_name": "", "json_path": ""},
"values": [{
"tetraplet": {"peer_pk": alice_peer_id, "service_id": "", "function_name": "", "json_path": ""},
"result": 1,
"provenance": Provenance::literal(),
}, {
"tetraplet": {"peer_pk": alice_peer_id, "service_id": "", "function_name": "", "json_path": ""},
"result": 2,
"provenance": Provenance::literal(),
}]
}),
&mut mallory_cid_state,
);
let mallory_trace = vec![
ap(0),
ap(0),
alice_canon_cid,
scalar_tracked!("mallory", &mut mallory_cid_state, peer = &mallory_peer_id),
];
let mut mallory_cid_info = serde_json::to_value::<CidInfo>(mallory_cid_state.into()).unwrap();
let mut cnt = 0;
let mut fake_cid = None;
for (tetraplet_cid, tetraplet) in mallory_cid_info["tetraplet_store"].as_object().unwrap() {
if tetraplet["peer_pk"] == mallory_peer_id {
fake_cid = Some(tetraplet_cid.clone());
}
}
assert!(fake_cid.is_some(), "test is invalid");
for (_cid, canon_result) in mallory_cid_info["canon_result_store"].as_object_mut().unwrap() {
canon_result["tetraplet"] = json!(fake_cid.clone().unwrap());
cnt += 1;
}
assert_eq!(cnt, 1, "test validity failed");
let mut signature_store = SignatureStore::new();
let mut alice_cid_tracker = PeerCidTracker::new(alice_peer_id.clone());
alice_cid_tracker.register(&alice_peer_id, &extract_canon_result_cid(&mallory_trace[2]));
let alice_signature = alice_cid_tracker.gen_signature("", &alice_keypair).unwrap();
signature_store.put(alice_pk, alice_signature);
let mut mallory_cid_tracker = PeerCidTracker::new(mallory_peer_id.clone());
mallory_cid_tracker.register(&mallory_peer_id, &extract_service_result_cid(&mallory_trace[3]));
let mallory_signature = mallory_cid_tracker.gen_signature("", &mallory_keypair).unwrap();
signature_store.put(mallory_pk, mallory_signature);
let mallory_data = InterpreterData::from_execution_result(
mallory_trace.into(),
serde_json::from_value(mallory_cid_info).unwrap(),
signature_store,
0,
Version::new(1, 1, 1),
);
let mut bob_avm = create_avm(unit_call_service(), bob_peer_id);
let test_run_params = TestRunParameters::from_init_peer_id(alice_peer_id);
let prev_data = "";
let cur_data = serde_json::to_vec(&mallory_data).unwrap();
let res = bob_avm.call(&air_script, prev_data, cur_data, test_run_params).unwrap();
assert_error_eq!(
&res,
PreparationError::CidStoreVerificationError(CidStoreVerificationError::MismatchError {
type_name: "air_interpreter_data::executed_state::CanonResultCidAggregate",
cid_repr: "bagaaieratezrhuyz2eprlmiidxywv6ir2tmswlxycad37noykg3p5oxhs5tq".to_owned(),
})
);
}

View File

@ -0,0 +1,334 @@
/*
* Copyright 2023 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 air::UncatchableError;
use air_test_utils::key_utils::derive_dummy_keypair;
use air_test_utils::prelude::*;
/// This test module asserts various runtime safety checks, for example,
/// that actual calls' tetraplets are compared to stored one.
#[test]
fn test_runtime_executed_call_argument_hash() {
// Mallory gets a trace where there are two calls that differ only by argument_hash.
// Can it swap them successfully?
let alice_name = "alice";
let bob_name = "bob";
let mallory_name = "mallory";
let (alice_keypair, alice_peer_id) = derive_dummy_keypair(alice_name);
let (bob_keypair, bob_peer_id) = derive_dummy_keypair(bob_name);
let (mallory_keypair, mallory_peer_id) = derive_dummy_keypair(mallory_name);
let test_run_params = TestRunParameters::from_init_peer_id(&alice_peer_id);
let air_script = format!(
r#"
(seq
(seq
(call "{alice_peer_id}" ("service" "func") [42] x)
(call "{alice_peer_id}" ("service" "func") [43] y))
(seq
(call "{mallory_peer_id}" ("" "") [42] z)
(call "{bob_peer_id}" ("service" "secret") [x y z] w)))
"#
);
let mut alice_avm = create_avm_with_key::<NativeAirRunner>(alice_keypair, echo_call_service());
let mut bob_avm = create_avm_with_key::<NativeAirRunner>(bob_keypair, echo_call_service());
let mut mallory_avm = create_avm_with_key::<NativeAirRunner>(mallory_keypair, echo_call_service());
let alice_res = alice_avm.call(&air_script, "", "", test_run_params.clone()).unwrap();
let mallory_res = mallory_avm
.call(&air_script, "", alice_res.data, test_run_params.clone())
.unwrap();
let mut mallory_data = data_from_result(&mallory_res);
let mut mallory_raw_trace: Vec<_> = mallory_data.trace.iter().cloned().collect();
mallory_raw_trace.swap(0, 1);
mallory_data.trace = ExecutionTrace::from(mallory_raw_trace);
let mallory_data = serde_json::to_vec(&mallory_data).unwrap();
let bob_res = bob_avm.call(air_script, "", mallory_data, test_run_params).unwrap();
assert_error_eq!(
&bob_res,
UncatchableError::InstructionParametersMismatch {
param: "call argument_hash",
expected_value: "bagaaieraxbvr5ii3fajw7zjcjoor4maxw7x3ndkpvyfzbreubyga7cntsb5a".to_owned(),
stored_value: "bagaaieralq23ubl3bxyggvynq44g6p5233fla7hrr2jrspeamml4zu2iapvq".to_owned(),
}
);
}
#[test]
fn test_runtime_executed_call_tetraplet() {
// Mallory gets a trace where there are two calls that differ only by argument_hash.
// Can it swap them successfully?
let alice_name = "alice";
let bob_name = "bob";
let mallory_name = "mallory";
let (alice_keypair, alice_peer_id) = derive_dummy_keypair(alice_name);
let (bob_keypair, bob_peer_id) = derive_dummy_keypair(bob_name);
let (mallory_keypair, mallory_peer_id) = derive_dummy_keypair(mallory_name);
let test_run_params = TestRunParameters::from_init_peer_id(&alice_peer_id);
let air_script = format!(
r#"
(seq
(seq
(call "{alice_peer_id}" ("service1" "func") [42] x)
(call "{alice_peer_id}" ("service2" "func") [42] y))
(seq
(call "{mallory_peer_id}" ("" "") [42] z)
(call "{bob_peer_id}" ("service" "secret") [x y z] w)))
"#
);
let mut alice_avm = create_avm_with_key::<NativeAirRunner>(alice_keypair, echo_call_service());
let mut bob_avm = create_avm_with_key::<NativeAirRunner>(bob_keypair, echo_call_service());
let mut mallory_avm = create_avm_with_key::<NativeAirRunner>(mallory_keypair, echo_call_service());
let alice_res = alice_avm.call(&air_script, "", "", test_run_params.clone()).unwrap();
let mallory_res = mallory_avm
.call(&air_script, "", alice_res.data, test_run_params.clone())
.unwrap();
let mut mallory_data = data_from_result(&mallory_res);
let mut mallory_raw_trace: Vec<_> = mallory_data.trace.iter().cloned().collect();
mallory_raw_trace.swap(0, 1);
mallory_data.trace = ExecutionTrace::from(mallory_raw_trace);
let mallory_data = serde_json::to_vec(&mallory_data).unwrap();
let bob_res = bob_avm.call(air_script, "", mallory_data, test_run_params).unwrap();
let expected_value = format!(
concat!(
r#"SecurityTetraplet {{ peer_pk: "{alice_peer_id}","#,
r#" service_id: "service1", function_name: "func", json_path: "" }}"#
),
alice_peer_id = alice_peer_id,
);
let stored_value = format!(
concat!(
r#"SecurityTetraplet {{ peer_pk: "{alice_peer_id}","#,
r#" service_id: "service2", function_name: "func", json_path: "" }}"#,
),
alice_peer_id = alice_peer_id,
);
assert_error_eq!(
&bob_res,
UncatchableError::InstructionParametersMismatch {
param: "call tetraplet",
// please note that order is important here: if values are swapped, then the error is
// handled by Executed branch, not Failed branch
expected_value,
stored_value,
}
);
}
#[test]
fn test_runtime_executed_failed_argument_hash() {
// Mallory gets a trace where there are two calls that differ only by argument_hash.
// Can it swap them successfully?
let alice_name = "alice";
let bob_name = "bob";
let mallory_name = "mallory";
let (alice_keypair, alice_peer_id) = derive_dummy_keypair(alice_name);
let (bob_keypair, bob_peer_id) = derive_dummy_keypair(bob_name);
let (mallory_keypair, mallory_peer_id) = derive_dummy_keypair(mallory_name);
let test_run_params = TestRunParameters::from_init_peer_id(&alice_peer_id);
let air_script = format!(
r#"
(seq
(seq
(xor
(call "{alice_peer_id}" ("service" "func") [42] x)
(null))
(call "{alice_peer_id}" ("service" "func") [43] y))
(seq
(call "{mallory_peer_id}" ("" "") [42] z)
(call "{bob_peer_id}" ("service" "secret") [x y z] w)))
"#
);
let mut alice_avm = create_avm_with_key::<NativeAirRunner>(alice_keypair, fallible_call_service_by_arg(43));
let mut bob_avm = create_avm_with_key::<NativeAirRunner>(bob_keypair, echo_call_service());
let mut mallory_avm = create_avm_with_key::<NativeAirRunner>(mallory_keypair, echo_call_service());
let alice_res = alice_avm.call(&air_script, "", "", test_run_params.clone()).unwrap();
let mallory_res = mallory_avm
.call(&air_script, "", alice_res.data, test_run_params.clone())
.unwrap();
let mut mallory_data = data_from_result(&mallory_res);
let mut mallory_raw_trace: Vec<_> = mallory_data.trace.iter().cloned().collect();
mallory_raw_trace.swap(0, 1);
mallory_data.trace = ExecutionTrace::from(mallory_raw_trace);
let mallory_data = serde_json::to_vec(&mallory_data).unwrap();
let bob_res = bob_avm.call(air_script, "", mallory_data, test_run_params).unwrap();
assert_error_eq!(
&bob_res,
UncatchableError::InstructionParametersMismatch {
param: "call argument_hash",
// please note that order is important here: if values are swapped, then the error is
// handled by Executed branch, not Failed branch
expected_value: "bagaaieraxbvr5ii3fajw7zjcjoor4maxw7x3ndkpvyfzbreubyga7cntsb5a".to_owned(),
stored_value: "bagaaieralq23ubl3bxyggvynq44g6p5233fla7hrr2jrspeamml4zu2iapvq".to_owned(),
}
);
}
#[test]
fn test_runtime_failed_call_tetraplet() {
// Mallory gets a trace where there are two calls that differ only by argument_hash.
// Can it swap them successfully?
let alice_name = "alice";
let bob_name = "bob";
let mallory_name = "mallory";
let (alice_keypair, alice_peer_id) = derive_dummy_keypair(alice_name);
let (bob_keypair, bob_peer_id) = derive_dummy_keypair(bob_name);
let (mallory_keypair, mallory_peer_id) = derive_dummy_keypair(mallory_name);
let test_run_params = TestRunParameters::from_init_peer_id(&alice_peer_id);
let air_script = format!(
r#"
(seq
(seq
(xor
(call "{alice_peer_id}" ("service1" "func") [42] x)
(null))
(call "{alice_peer_id}" ("service2" "func") [42] y))
(seq
(call "{mallory_peer_id}" ("" "") [42] z)
(call "{bob_peer_id}" ("service" "secret") [x y z] w)))
"#
);
let mut alice_avm = create_avm_with_key::<NativeAirRunner>(alice_keypair, fallible_call_service("service1"));
let mut bob_avm = create_avm_with_key::<NativeAirRunner>(bob_keypair, echo_call_service());
let mut mallory_avm = create_avm_with_key::<NativeAirRunner>(mallory_keypair, echo_call_service());
let alice_res = alice_avm.call(&air_script, "", "", test_run_params.clone()).unwrap();
let mallory_res = mallory_avm
.call(&air_script, "", alice_res.data, test_run_params.clone())
.unwrap();
let mut mallory_data = data_from_result(&mallory_res);
let mut mallory_raw_trace: Vec<_> = mallory_data.trace.iter().cloned().collect();
mallory_raw_trace.swap(0, 1);
mallory_data.trace = ExecutionTrace::from(mallory_raw_trace);
let mallory_data = serde_json::to_vec(&mallory_data).unwrap();
let bob_res = bob_avm.call(air_script, "", mallory_data, test_run_params).unwrap();
let expected_value = format!(
concat!(
r#"SecurityTetraplet {{ peer_pk: "{alice_peer_id}","#,
r#" service_id: "service1", function_name: "func", json_path: "" }}"#
),
alice_peer_id = alice_peer_id,
);
let stored_value = format!(
concat!(
r#"SecurityTetraplet {{ peer_pk: "{alice_peer_id}","#,
r#" service_id: "service2", function_name: "func", json_path: "" }}"#,
),
alice_peer_id = alice_peer_id,
);
assert_error_eq!(
&bob_res,
UncatchableError::InstructionParametersMismatch {
param: "call tetraplet",
// please note that order is important here: if values are swapped, then the error is
// handled by Executed branch, not Failed branch
expected_value,
stored_value,
}
);
}
#[test]
fn test_runtime_canon_tetraplet() {
let alice_name = "alice";
let bob_name = "bob";
let mallory_name = "mallory";
let (alice_keypair, alice_peer_id) = derive_dummy_keypair(alice_name);
let (bob_keypair, bob_peer_id) = derive_dummy_keypair(bob_name);
let (mallory_keypair, mallory_peer_id) = derive_dummy_keypair(mallory_name);
let test_run_params = TestRunParameters::from_init_peer_id(&alice_peer_id);
let air_script = format!(
r#"
(seq
(seq
(ap 42 $x)
(ap 43 $x))
(seq
(seq
(canon "{alice_peer_id}" $x #xa)
(canon "{mallory_peer_id}" $x #xm))
(call "{bob_peer_id}" ("" "") [#xa #xm] z)))
"#
);
let mut alice_avm = create_avm_with_key::<NativeAirRunner>(alice_keypair, fallible_call_service("service1"));
let mut bob_avm = create_avm_with_key::<NativeAirRunner>(bob_keypair, echo_call_service());
let mut mallory_avm = create_avm_with_key::<NativeAirRunner>(mallory_keypair, echo_call_service());
let alice_res = alice_avm.call(&air_script, "", "", test_run_params.clone()).unwrap();
let mallory_res = mallory_avm
.call(&air_script, "", alice_res.data, test_run_params.clone())
.unwrap();
let mut mallory_data = data_from_result(&mallory_res);
let mut mallory_raw_trace: Vec<_> = mallory_data.trace.iter().cloned().collect();
mallory_raw_trace.swap(2, 3);
mallory_data.trace = ExecutionTrace::from(mallory_raw_trace);
let mallory_data = serde_json::to_vec(&mallory_data).unwrap();
let bob_res = bob_avm.call(air_script, "", mallory_data, test_run_params).unwrap();
let expected_value = format!(
concat!(
r#"SecurityTetraplet {{ peer_pk: "{alice_peer_id}","#,
r#" service_id: "", function_name: "", json_path: "" }}"#
),
alice_peer_id = alice_peer_id,
);
let stored_value = format!(
concat!(
r#"SecurityTetraplet {{ peer_pk: "{mallory_peer_id}","#,
r#" service_id: "", function_name: "", json_path: "" }}"#,
),
mallory_peer_id = mallory_peer_id,
);
assert_error_eq!(
&bob_res,
UncatchableError::InstructionParametersMismatch {
param: "canon tetraplet",
expected_value,
stored_value,
}
);
}

View File

@ -0,0 +1,455 @@
/*
* Copyright 2023 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 air_interpreter_signatures::PeerCidTracker;
use air_test_framework::{ephemeral::PeerId, AirScriptExecutor};
use air_test_utils::key_utils::derive_dummy_keypair;
use air_test_utils::prelude::*;
use air_test_utils::test_runner::TestRunParameters;
#[test]
fn test_signature_empty() {
let script = "(null)";
let init_peer_name = "init_peer_id";
let (keypair, _) = derive_dummy_keypair(init_peer_name);
let exec = <AirScriptExecutor>::new(
TestRunParameters::from_init_peer_id(init_peer_name),
vec![],
vec![PeerId::from(init_peer_name)].into_iter(),
script,
)
.unwrap();
let res = exec.execute_one(init_peer_name).unwrap();
assert_eq!(res.ret_code, 0, "{:?}", res);
let data = borsh::to_vec(&(vec![""; 0], "")).unwrap();
let expected_signature: air_interpreter_signatures::Signature = keypair.sign(&data).unwrap().into();
let data = data_from_result(&res);
let signature = data.signatures.get(&keypair.public().into());
assert_eq!(signature, Some(&expected_signature), "{:?}", data.signatures);
}
#[test]
fn test_signature_call_var() {
let init_peer_name = "init_peer_id";
let (keypair, init_peer_id) = derive_dummy_keypair(init_peer_name);
let air_script = format!(
r#"
(call "{init_peer_name}" ("" "") [] var) ; ok = "ok"
"#
);
let exec =
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_name), &air_script).unwrap();
let res = exec.execution_iter(init_peer_id.as_str()).unwrap().last().unwrap();
assert_eq!(res.ret_code, 0, "{:?}", res);
let data = data_from_result(&res);
let expected_call_state = scalar!("ok", peer = &init_peer_id, service = "..0");
let expected_cid = extract_service_result_cid(&expected_call_state);
let mut expected_tracker = PeerCidTracker::new(init_peer_id.clone());
expected_tracker.register(&init_peer_id, &expected_cid);
let expected_signature = expected_tracker.gen_signature("", &keypair).unwrap();
let signature = data.signatures.get(&keypair.public().into());
assert_eq!(signature, Some(&expected_signature), "{:?}", data.signatures);
}
#[test]
fn test_signature_call_stream() {
let init_peer_name = "init_peer_id";
let air_script = format!(
r#"
(call "{init_peer_name}" ("" "") [] $var) ; ok = "ok"
"#
);
let exec =
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_name), &air_script).unwrap();
let res = exec.execution_iter(init_peer_name).unwrap().last().unwrap();
assert_eq!(res.ret_code, 0, "{:?}", res);
let data = data_from_result(&res);
let (keypair, init_peer_id) = derive_dummy_keypair(init_peer_name);
let expected_call_state = stream!("ok", 0, peer = &init_peer_id, service = "..0");
let expected_cid = extract_service_result_cid(&expected_call_state);
let mut expected_tracker = PeerCidTracker::new(init_peer_id.clone());
expected_tracker.register(&init_peer_id, &expected_cid);
let expected_signature = expected_tracker.gen_signature("", &keypair).unwrap();
let signature = data.signatures.get(&keypair.public().into());
assert_eq!(signature, Some(&expected_signature), "{:?}", data.signatures);
}
#[test]
fn test_signature_call_unused() {
let init_peer_name = "init_peer_id";
let air_script = format!(
r#"
(call "{init_peer_name}" ("" "") []) ; ok = "ok"
"#
);
let exec =
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_name), &air_script).unwrap();
let res = exec.execution_iter(init_peer_name).unwrap().last().unwrap();
assert_eq!(res.ret_code, 0, "{:?}", res);
let data = data_from_result(&res);
let (keypair, init_peer_id) = derive_dummy_keypair(init_peer_name);
let expected_tracker = PeerCidTracker::new(init_peer_id.to_owned());
let expected_signature = expected_tracker.gen_signature("", &keypair).unwrap();
let signature = data.signatures.get(&keypair.public().into());
assert_eq!(signature, Some(&expected_signature), "{:?}", data.signatures);
}
#[test]
fn test_signature_call_merged() {
let init_peer_name = "init_peer_id";
let other_peer_name = "other_peer_id";
let air_script = format!(
r#"
(seq
(call "{init_peer_name}" ("" "") [] x) ; ok = "res0"
(seq
(call "{other_peer_name}" ("" "") [] y) ; ok = "res1"
(call "{init_peer_name}" ("" "") [] z) ; ok = "res2"
))
"#
);
let exec =
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_name), &air_script).unwrap();
let _ = exec.execute_one(init_peer_name).unwrap();
let _ = exec.execute_one(other_peer_name).unwrap();
let res2 = exec.execute_one(init_peer_name).unwrap();
let data2 = data_from_result(&res2);
let expected_call_state0 = scalar!("res0", peer_name = init_peer_name, service = "..0");
let expected_cid0 = extract_service_result_cid(&expected_call_state0);
let expected_call_state2 = scalar!("res2", peer_name = init_peer_name, service = "..2");
let expected_cid2 = extract_service_result_cid(&expected_call_state2);
let (keypair, _) = derive_dummy_keypair(init_peer_name);
let mut expected_tracker = PeerCidTracker::new(init_peer_name.to_owned());
expected_tracker.register(init_peer_name, &expected_cid0);
expected_tracker.register(init_peer_name, &expected_cid2);
let expected_signature = expected_tracker.gen_signature("", &keypair).unwrap();
let signature = data2.signatures.get(&keypair.public().into());
assert_eq!(signature, Some(&expected_signature), "{:?}", data2.signatures);
}
#[test]
fn test_signature_call_twice() {
// Test that if some CID appears twice in the call result, it is accounted twice.
let init_peer_name = "init_peer_id";
let (keypair, init_peer_id) = derive_dummy_keypair(init_peer_name);
let air_script = format!(
r#"
(seq
(seq (ap 1 $s) (ap 2 $s))
(fold $s i
(seq
(call "{init_peer_name}" ("" "") [] var) ; ok = "ok"
(next i))))
"#
);
let exec =
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_name), &air_script).unwrap();
let res = exec.execution_iter(init_peer_id.as_str()).unwrap().last().unwrap();
assert_eq!(res.ret_code, 0, "{:?}", res);
let data = data_from_result(&res);
let expected_call_state = scalar!("ok", peer = &init_peer_id, service = "..0");
let expected_cid = extract_service_result_cid(&expected_call_state);
let mut unexpected_tracker = PeerCidTracker::new(init_peer_id.to_owned());
unexpected_tracker.register(&init_peer_id, &expected_cid);
let unexpected_signature = unexpected_tracker.gen_signature("", &keypair).unwrap();
let mut expected_tracker = PeerCidTracker::new(init_peer_id.to_owned());
expected_tracker.register(&init_peer_id, &expected_cid);
expected_tracker.register(&init_peer_id, &expected_cid);
let expected_signature = expected_tracker.gen_signature("", &keypair).unwrap();
assert_ne!(expected_signature, unexpected_signature, "test is incorrect");
let signature = data.signatures.get(&keypair.public().into());
assert_eq!(signature, Some(&expected_signature), "{:?}", data.signatures);
}
#[test]
fn test_signature_canon_basic() {
let init_peer_name = "init_peer_id";
let (keypair, init_peer_id) = derive_dummy_keypair(init_peer_name);
let air_script = format!(
r#"
(seq
(call "{init_peer_name}" ("serv" "func") [] items) ; ok = [1, 2, 3]
(seq
(fold items i
(seq
(ap i $stream)
(next i)))
(canon "{init_peer_name}" $stream #canon)))
"#
);
let exec =
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_name), &air_script).unwrap();
let last_result = exec.execution_iter(init_peer_name).unwrap().last().unwrap();
let last_data = data_from_result(&last_result);
let expected_call_result = scalar!(
json!([1, 2, 3]),
peer = &init_peer_id,
service = "serv..0",
function = "func"
);
let expected_call_result_cid = extract_service_result_cid(&expected_call_result);
let expected_canon_state = canon(json!({
"tetraplet": {"peer_pk": init_peer_id, "service_id": "", "function_name": "", "json_path": ""},
"values": [{
"result": 1,
"tetraplet": {
"peer_pk": init_peer_id,
"service_id": "serv..0",
"function_name": "func",
"json_path": ".$.[0]",
},
"provenance": Provenance::service_result(expected_call_result_cid.clone()),
}, {
"result": 2,
"tetraplet": {
"peer_pk": init_peer_id,
"service_id": "serv..0",
"function_name": "func",
"json_path": ".$.[1]",
},
"provenance": Provenance::service_result(expected_call_result_cid.clone()),
}, {
"result": 3,
"tetraplet": {
"peer_pk": init_peer_id,
"service_id": "serv..0",
"function_name": "func",
"json_path": ".$.[2]",
},
"provenance": Provenance::service_result(expected_call_result_cid.clone()),
}]
}));
let expected_canon_cid = extract_canon_result_cid(&expected_canon_state);
let mut expected_tracker = PeerCidTracker::new(init_peer_name.to_owned());
expected_tracker.register(init_peer_name, &expected_canon_cid);
expected_tracker.register(init_peer_name, &expected_call_result_cid);
let expected_signature = expected_tracker.gen_signature("", &keypair).unwrap();
let signature = last_data.signatures.get(&keypair.public().into());
assert_eq!(signature, Some(&expected_signature), "{:?}", last_data);
}
#[test]
fn test_signature_canon_merge() {
let init_peer_name = "init_peer_id";
let other_peer_name = "other_peer_id";
let (keypair, init_peer_id) = derive_dummy_keypair(init_peer_name);
let air_script = format!(
r#"
(seq
(seq
(call "{init_peer_name}" ("serv" "func") [] items) ; ok = [1, 2, 3]
(seq
(fold items i
(seq
(ap i $stream)
(next i)))
(canon "{init_peer_name}" $stream #canon)))
(seq
(call "{other_peer_name}" ("" "") []) ; ok = "ok"
(call "{init_peer_name}" ("" "") []))) ; ok = "ok"
"#
);
let exec =
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_name), &air_script).unwrap();
exec.execute_all(init_peer_name);
exec.execute_one(other_peer_name);
let last_result = exec.execution_iter(init_peer_name).unwrap().last().unwrap();
let last_data = data_from_result(&last_result);
let expected_call_result = scalar!(
json!([1, 2, 3]),
peer = &init_peer_id,
service = "serv..0",
function = "func"
);
let expected_call_result_cid = extract_service_result_cid(&expected_call_result);
let expected_canon_state = canon(json!({
"tetraplet": {"peer_pk": init_peer_id, "service_id": "", "function_name": "", "json_path": ""},
"values": [{
"result": 1,
"tetraplet": {
"peer_pk": init_peer_id,
"service_id": "serv..0",
"function_name": "func",
"json_path": ".$.[0]",
},
"provenance": Provenance::service_result(expected_call_result_cid.clone()),
}, {
"result": 2,
"tetraplet": {
"peer_pk": init_peer_id,
"service_id": "serv..0",
"function_name": "func",
"json_path": ".$.[1]",
},
"provenance": Provenance::service_result(expected_call_result_cid.clone()),
}, {
"result": 3,
"tetraplet": {
"peer_pk": init_peer_id,
"service_id": "serv..0",
"function_name": "func",
"json_path": ".$.[2]",
},
"provenance": Provenance::service_result(expected_call_result_cid.clone()),
}]
}));
let expected_canon_cid = extract_canon_result_cid(&expected_canon_state);
let mut expected_tracker = PeerCidTracker::new(init_peer_name.to_owned());
expected_tracker.register(init_peer_name, &expected_canon_cid);
expected_tracker.register(init_peer_name, &expected_call_result_cid);
let expected_signature = expected_tracker.gen_signature("", &keypair).unwrap();
let signature = last_data.signatures.get(&keypair.public().into());
assert_eq!(signature, Some(&expected_signature), "{:?}", last_data);
}
#[test]
fn test_signature_canon_result() {
// this test checks that call result in canon doesn't lead to repeadted accounting of the call result
let init_peer_name = "init_peer_id";
let (keypair, init_peer_id) = derive_dummy_keypair(init_peer_name);
let air_script = format!(
r#"
(seq
(seq
(call "{init_peer_name}" ("serv" "func") [] items) ; ok = [1, 2, 3]
(fold items i
(seq
(ap i $stream)
(next i))))
(seq
(call "{init_peer_name}" ("serv" "func2") [] $stream) ; ok = 42
(canon "{init_peer_name}" $stream #canon)))
"#
);
let exec =
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_name), &air_script).unwrap();
let last_result = exec.execution_iter(init_peer_name).unwrap().last().unwrap();
let last_data = data_from_result(&last_result);
let expected_call_result1 = scalar!(
json!([1, 2, 3]),
peer = &init_peer_id,
service = "serv..0",
function = "func"
);
let expected_call_result_cid1 = extract_service_result_cid(&expected_call_result1);
let expected_call_result2 = stream!(
json!(42),
1,
peer = &init_peer_id,
service = "serv..1",
function = "func2"
);
let expected_call_result_cid2 = extract_service_result_cid(&expected_call_result2);
let expected_canon_state = canon(json!({
"tetraplet": {"peer_pk": init_peer_id, "service_id": "", "function_name": "", "json_path": ""},
"values": [{
"result": 1,
"tetraplet": {
"peer_pk": init_peer_id,
"service_id": "serv..0",
"function_name": "func",
"json_path": ".$.[0]",
},
"provenance": Provenance::service_result(expected_call_result_cid1.clone()),
}, {
"result": 2,
"tetraplet": {
"peer_pk": init_peer_id,
"service_id": "serv..0",
"function_name": "func",
"json_path": ".$.[1]",
},
"provenance": Provenance::service_result(expected_call_result_cid1.clone()),
}, {
"result": 3,
"tetraplet": {
"peer_pk": init_peer_id,
"service_id": "serv..0",
"function_name": "func",
"json_path": ".$.[2]",
},
"provenance": Provenance::service_result(expected_call_result_cid1.clone()),
}, {
"result": 42,
"tetraplet": {
"peer_pk": init_peer_id,
"service_id": "serv..1",
"function_name": "func2",
"json_path": "",
},
"provenance": Provenance::service_result(expected_call_result_cid2.clone()),
}]
}));
let expected_canon_cid = extract_canon_result_cid(&expected_canon_state);
let mut expected_tracker = PeerCidTracker::new(init_peer_name.to_owned());
expected_tracker.register(init_peer_name, &expected_call_result_cid1);
expected_tracker.register(init_peer_name, &expected_call_result_cid2);
expected_tracker.register(init_peer_name, &expected_canon_cid);
let expected_signature = expected_tracker.gen_signature("", &keypair).unwrap();
let signature = last_data.signatures.get(&keypair.public().into());
assert_eq!(signature, Some(&expected_signature), "{:?}", last_data);
}

View File

@ -21,19 +21,19 @@ use air_test_utils::*;
#[test]
fn global_streams_are_compactified() {
let peer_id = "peer_id";
let peer_name = "peer_id";
let service_result = "service_result";
let script = format!(
r#"
(seq
(ap 1 $stream)
(call "{peer_id}" ("" "") [] $stream) ; ok = "{service_result}"
(call "{peer_name}" ("" "") [] $stream) ; ok = "{service_result}"
)
"#
);
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(peer_id), &script).unwrap();
let result = executor.execute_all(peer_id).unwrap();
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(peer_name), &script).unwrap();
let result = executor.execute_all(peer_name).unwrap();
let actual_trace = trace_from_result(result.last().unwrap());
let mut cid_state = ExecutionCidState::new();
@ -43,7 +43,7 @@ fn global_streams_are_compactified() {
service_result,
1,
cid_state,
peer = peer_id,
peer_name = peer_name,
service = "..0",
function = ""
),
@ -54,22 +54,22 @@ fn global_streams_are_compactified() {
#[test]
fn global_stream_maps_are_compactified() {
let peer_id = "peer_id";
let peer_name = "peer_id";
let service_result = "service_result";
let script = format!(
r#"
(seq
(ap (1 1) %stream_map)
(seq
(call "{peer_id}" ("" "") [] $stream) ; ok = "{service_result}"
(call "{peer_name}" ("" "") [] $stream) ; ok = "{service_result}"
(ap (1 1) %stream_map)
)
)
"#
);
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(peer_id), &script).unwrap();
let result = executor.execute_all(peer_id).unwrap();
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(peer_name), &script).unwrap();
let result = executor.execute_all(peer_name).unwrap();
let actual_trace = trace_from_result(result.last().unwrap());
let mut cid_state = ExecutionCidState::new();
@ -79,7 +79,7 @@ fn global_stream_maps_are_compactified() {
service_result,
0,
cid_state,
peer = peer_id,
peer_name = peer_name,
service = "..0",
function = ""
),
@ -91,21 +91,21 @@ fn global_stream_maps_are_compactified() {
#[test]
fn local_streams_are_compactified() {
let peer_id = "peer_id";
let peer_name = "peer_id";
let service_result = "service_result";
let script = format!(
r#"
(new $stream
(seq
(ap 1 $stream)
(call "{peer_id}" ("" "") [] $stream) ; ok = "{service_result}"
(call "{peer_name}" ("" "") [] $stream) ; ok = "{service_result}"
)
)
"#
);
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(peer_id), &script).unwrap();
let result = executor.execute_all(peer_id).unwrap();
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(peer_name), &script).unwrap();
let result = executor.execute_all(peer_name).unwrap();
let actual_trace = trace_from_result(result.last().unwrap());
let mut cid_state = ExecutionCidState::new();
@ -115,7 +115,7 @@ fn local_streams_are_compactified() {
service_result,
1,
cid_state,
peer = peer_id,
peer_name = peer_name,
service = "..0",
function = ""
),
@ -126,7 +126,7 @@ fn local_streams_are_compactified() {
#[test]
fn local_stream_maps_are_compactified() {
let peer_id = "peer_id";
let peer_name = "peer_id";
let service_result = "service_result";
let script = format!(
r#"
@ -134,7 +134,7 @@ fn local_stream_maps_are_compactified() {
(seq
(ap (1 1) %stream_map)
(seq
(call "{peer_id}" ("" "") [] $stream) ; ok = "{service_result}"
(call "{peer_name}" ("" "") [] $stream) ; ok = "{service_result}"
(ap (1 1) %stream_map)
)
)
@ -142,8 +142,8 @@ fn local_stream_maps_are_compactified() {
"#
);
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(peer_id), &script).unwrap();
let result = executor.execute_all(peer_id).unwrap();
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(peer_name), &script).unwrap();
let result = executor.execute_all(peer_name).unwrap();
let actual_trace = trace_from_result(result.last().unwrap());
let mut cid_state = ExecutionCidState::new();
@ -153,7 +153,7 @@ fn local_stream_maps_are_compactified() {
service_result,
0,
cid_state,
peer = peer_id,
peer_name = peer_name,
service = "..0",
function = ""
),

View File

@ -15,14 +15,14 @@
*/
use air_test_framework::AirScriptExecutor;
use air_test_utils::prelude::*;
use air_test_utils::{key_utils::at, prelude::*};
#[test]
fn merging_fold_iterations_extensively() {
let script = r#"
(seq
(seq
(call "client" ("get" "data") [] permutations) ; ok = [["p1",[[["p1",1],["p2",2],["p3",3]],[["p1",4],["p3",5],["p2",6]]]],["p2",[[["p2",7],["p1",8],["p3",9]],[["p2",10],["p3",11],["p1",12]]]],["p3",[[["p3",13],["p1",14],["p2",15]],[["p3",16],["p2",17],["p1",18]]]]]
(call "client" ("get" "data") [] permutations) ; ok = [[@"p1",[[[@"p1",1],[@"p2",2],[@"p3",3]],[[@"p1",4],[@"p3",5],[@"p2",6]]]],[@"p2",[[[@"p2",7],[@"p1",8],[@"p3",9]],[[@"p2",10],[@"p3",11],[@"p1",12]]]],[@"p3",[[[@"p3",13],[@"p1",14],[@"p2",15]],[[@"p3",16],[@"p2",17],[@"p1",18]]]]]
(seq
(fold permutations pair
(seq
@ -103,7 +103,7 @@ fn merging_fold_iterations_extensively() {
queue.push_back(peer.clone());
}
if peer == "relay" {
if peer == at("relay") {
relay_outcomes.push(outcome);
}
}
@ -131,7 +131,7 @@ fn merging_fold_iterations_extensively_2() {
let script = r#"
(seq
(seq
(call "client" ("get" "data") [] permutations) ; ok = [["p1",[[["p1",1],["p2",2],["p3",3]],[["p1",4],["p3",5],["p2",6]]]],["p2",[[["p2",7],["p1",8],["p3",9]],[["p2",10],["p3",11],["p1",12]]]],["p3",[[["p3",13],["p1",14],["p2",15]],[["p3",16],["p2",17],["p1",18]]]]]
(call "client" ("get" "data") [] permutations) ; ok = [[@"p1",[[[@"p1",1],[@"p2",2],[@"p3",3]],[[@"p1",4],[@"p3",5],[@"p2",6]]]],[@"p2",[[[@"p2",7],[@"p1",8],[@"p3",9]],[[@"p2",10],[@"p3",11],[@"p1",12]]]],[@"p3",[[[@"p3",13],[@"p1",14],[@"p2",15]],[[@"p3",16],[@"p2",17],[@"p1",18]]]]]
(seq
(seq
(fold permutations pair
@ -243,7 +243,7 @@ fn merging_fold_iterations_extensively_2() {
}
}
if peer == "p1" {
if peer == at("p1") {
p1_outcomes.push(outcome);
}
}

View File

@ -15,11 +15,11 @@
*/
use air_test_framework::AirScriptExecutor;
use air_test_utils::prelude::*;
use air_test_utils::{key_utils::at, prelude::*};
#[test]
fn call_result() {
let init_peer_id = "B";
let init_peer_name = "B";
let air_script = r#"
(seq
@ -27,9 +27,9 @@ fn call_result() {
(canon "B" $s #c))
"#;
let runner =
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_id), air_script).unwrap();
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_name), air_script).unwrap();
let result = runner.execute_one(init_peer_id).unwrap();
let result = runner.execute_one(init_peer_name).unwrap();
assert_eq!(result.ret_code, 0, "{:?}", result.error_message);
let data = data_from_result(&result);
@ -37,12 +37,14 @@ fn call_result() {
let val = scalar!(
"some_data",
peer = init_peer_id,
peer_name = init_peer_name,
service = "service..0",
function = "func"
);
let val_cid = extract_service_result_cid(&val);
let init_peer_id = at(init_peer_name);
let expected_state = canon(json!({
"tetraplet": {
"peer_pk": init_peer_id,
@ -67,7 +69,7 @@ fn call_result() {
#[test]
fn call_result_iteration() {
let init_peer_id = "A";
let init_peer_name = "A";
let air_script = r#"
(seq
@ -80,17 +82,19 @@ fn call_result_iteration() {
(canon "A" $s #c))
"#;
let runner =
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_id), air_script).unwrap();
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_name), air_script).unwrap();
let result = runner.execute_one(init_peer_id).unwrap();
let result = runner.execute_one(init_peer_name).unwrap();
assert_eq!(result.ret_code, 0, "{:?}", result.error_message);
let data = data_from_result(&result);
let last_state = data.trace.last().unwrap();
let init_peer_id = at(init_peer_name);
let val = scalar!(
json!([10, 11, 12]),
peer = init_peer_id,
peer_name = init_peer_name,
service = "service..0",
function = "func"
);
@ -138,7 +142,7 @@ fn call_result_iteration() {
#[test]
fn literal() {
let init_peer_id = "B";
let init_peer_name = "B";
let air_script = r#"
(seq
@ -146,14 +150,16 @@ fn literal() {
(canon "B" $s #c))
"#;
let runner =
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_id), air_script).unwrap();
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_name), air_script).unwrap();
let result = runner.execute_one(init_peer_id).unwrap();
let result = runner.execute_one(init_peer_name).unwrap();
assert_eq!(result.ret_code, 0, "{:?}", result.error_message);
let data = data_from_result(&result);
let last_state = data.trace.last().unwrap();
let init_peer_id = at(init_peer_name);
let expected_state = canon(json!({
"tetraplet": {
"peer_pk": init_peer_id,
@ -178,7 +184,7 @@ fn literal() {
#[test]
fn canon_in_canon() {
let init_peer_id = "B";
let init_peer_name = "B";
let air_script = r#"
(seq
@ -190,15 +196,17 @@ fn canon_in_canon() {
(canon "B" $s #d)))
"#;
let runner =
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_id), air_script).unwrap();
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_name), air_script).unwrap();
let result = runner.execute_one(init_peer_id).unwrap();
let result = runner.execute_one(init_peer_name).unwrap();
assert_eq!(result.ret_code, 0, "{:?}", result.error_message);
let trace = trace_from_result(&result);
let last_state = trace.last().unwrap();
let val = scalar!(1, peer = init_peer_id, service = "service..0", function = "func");
let init_peer_id = at(init_peer_name);
let val = scalar!(1, peer_name = init_peer_name, service = "service..0", function = "func");
let val_cid = extract_service_result_cid(&val);
let value_tetraplet = json!({
"peer_pk": init_peer_id,
@ -250,7 +258,7 @@ fn canon_in_canon() {
#[test]
fn lambda_result_iteration() {
let init_peer_id = "A";
let init_peer_name = "A";
let air_script = r#"
(seq
@ -266,17 +274,19 @@ fn lambda_result_iteration() {
(canon "A" $s #c))
"#;
let runner =
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_id), air_script).unwrap();
AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(init_peer_name), air_script).unwrap();
let result = runner.execute_one(init_peer_id).unwrap();
let result = runner.execute_one(init_peer_name).unwrap();
assert_eq!(result.ret_code, 0, "{:?}", result.error_message);
let data = data_from_result(&result);
let last_state = data.trace.last().unwrap();
let init_peer_id = at(init_peer_name);
let val = scalar!(
json!([{"field": [10, 11, 12]}]),
peer = init_peer_id,
peer_name = init_peer_name,
service = "service..0",
function = "func"
);

View File

@ -14,6 +14,7 @@
* limitations under the License.
*/
use air_test_utils::key_utils::at;
use air_test_utils::prelude::*;
use polyplets::SecurityTetraplet;
use pretty_assertions::assert_eq;
@ -98,7 +99,7 @@ fn fold_with_inner_call() {
#[test]
fn fold_stream_with_inner_call() {
let init_peer_id = "init_peer_id";
let init_peer_name = "init_peer_id";
let air_script = r#"
(seq
(seq
@ -112,20 +113,22 @@ fn fold_stream_with_inner_call() {
(next i))))
"#;
let executor = air_test_framework::AirScriptExecutor::from_annotated(
TestRunParameters::from_init_peer_id(init_peer_id),
TestRunParameters::from_init_peer_id(init_peer_name),
&air_script,
)
.unwrap();
let result = executor.execute_one(init_peer_id).unwrap();
let result = executor.execute_one(init_peer_name).unwrap();
assert_eq!(result.ret_code, 0, "{}", result.error_message);
let data = data_from_result(&result);
let init_peer_id = at(init_peer_name);
let expected_trace = vec![
stream!(
json!([[{"peer_pk": init_peer_id, "service_id": "..0", "function_name": "", "json_path": ""}]]),
0,
peer = init_peer_id,
peer = &init_peer_id,
service = "..2",
args = [42]
),
@ -142,7 +145,7 @@ fn fold_stream_with_inner_call() {
#[test]
fn fold_canon_with_inner_call() {
let init_peer_id = "init_peer_id";
let init_peer_name = "init_peer_id";
let air_script = r#"
(seq
(seq
@ -158,20 +161,22 @@ fn fold_canon_with_inner_call() {
(next x)))))
"#;
let executor = air_test_framework::AirScriptExecutor::from_annotated(
TestRunParameters::from_init_peer_id(init_peer_id),
TestRunParameters::from_init_peer_id(init_peer_name),
&air_script,
)
.unwrap();
let result = executor.execute_one(init_peer_id).unwrap();
let result = executor.execute_one(init_peer_name).unwrap();
assert_eq!(result.ret_code, 0, "{}", result.error_message);
let data = data_from_result(&result);
let init_peer_id = at(init_peer_name);
let expected_trace = vec![
stream!(
json!([[{"peer_pk": init_peer_id, "service_id": "..0", "function_name": "", "json_path": ""}]]),
0,
peer = init_peer_id,
peer = &init_peer_id,
service = "..2",
args = [42]
),

View File

@ -17,7 +17,9 @@
use air::no_error_object;
use air::ExecutionCidState;
use air_test_framework::AirScriptExecutor;
use air_test_utils::key_utils::at;
use air_test_utils::prelude::*;
use pretty_assertions::assert_eq;
use std::cell::RefCell;
use std::rc::Rc;
@ -643,25 +645,27 @@ fn ap_stream_map_with_undefined_last_error() {
#[test]
fn ap_canon_stream_map_with_string_key_accessor_lambda() {
let vm_1_peer_id = "vm_1_peer_id";
let vm_1_peer_name = "vm_1_peer_id";
let vm_1_peer_id = at(vm_1_peer_name);
let script = format!(
r#"
(seq
(seq
(ap ("key" "value1") %map)
(canon "{vm_1_peer_id}" %map #%canon_map)
(canon "{vm_1_peer_name}" %map #%canon_map)
)
(seq
(ap #%canon_map.$.key scalar)
(call "{vm_1_peer_id}" ("m" "f") [scalar] scalar1) ; behaviour = echo
(call "{vm_1_peer_name}" ("m" "f") [scalar] scalar1) ; behaviour = echo
)
)
"#
);
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(vm_1_peer_id), &script)
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(vm_1_peer_name), &script)
.expect("invalid test AIR script");
let result = executor.execute_all(vm_1_peer_id).unwrap();
let result = executor.execute_all(vm_1_peer_name).unwrap();
let actual_trace = trace_from_result(&result.last().unwrap());
let mut cid_tracker: ExecutionCidState = ExecutionCidState::new();
@ -692,30 +696,32 @@ fn ap_canon_stream_map_with_string_key_accessor_lambda() {
),
];
assert_eq!(actual_trace, expected_trace,);
assert_eq!(&*actual_trace, expected_trace,);
}
#[test]
fn ap_canon_stream_map_with_numeric_key_accessor_lambda() {
let vm_1_peer_id = "vm_1_peer_id";
let vm_1_peer_name = "vm_1_peer_id";
let vm_1_peer_id = at(vm_1_peer_name);
let script = format!(
r#"
(seq
(seq
(ap (42 "value1") %map)
(canon "{vm_1_peer_id}" %map #%canon_map)
(canon "{vm_1_peer_name}" %map #%canon_map)
)
(seq
(ap #%canon_map.$.[42] scalar)
(call "{vm_1_peer_id}" ("m" "f") [scalar] scalar1) ; behaviour = echo
(call "{vm_1_peer_name}" ("m" "f") [scalar] scalar1) ; behaviour = echo
)
)
"#
);
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(vm_1_peer_id), &script)
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(vm_1_peer_name), &script)
.expect("invalid test AIR script");
let result = executor.execute_all(vm_1_peer_id).unwrap();
let result = executor.execute_all(vm_1_peer_name).unwrap();
let actual_trace = trace_from_result(&result.last().unwrap());
let mut cid_tracker: ExecutionCidState = ExecutionCidState::new();
@ -745,7 +751,7 @@ fn ap_canon_stream_map_with_numeric_key_accessor_lambda() {
args = [call_arg]
),
];
assert_eq!(actual_trace, expected_trace,);
assert_eq!(&*actual_trace, expected_trace,);
}
#[test]

View File

@ -17,7 +17,9 @@
use air::ExecutionCidState;
use air::UncatchableError;
use air_test_framework::AirScriptExecutor;
use air_test_utils::key_utils::at;
use air_test_utils::prelude::*;
use pretty_assertions::assert_eq;
// 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.
@ -199,7 +201,9 @@ fn string_parameters() {
#[test]
fn call_canon_stream_map_arg() {
let vm_1_peer_id = "vm_1_peer_id";
let vm_1_peer_name = "vm_1_peer_id";
let vm_1_peer_id = at(vm_1_peer_name);
let script = format!(
r#"
(seq
@ -208,16 +212,16 @@ fn call_canon_stream_map_arg() {
(ap (-42 "value2") %map)
)
(seq
(canon "{vm_1_peer_id}" %map #%canon_map)
(call "{vm_1_peer_id}" ("m" "f") [#%canon_map] scalar) ; behaviour = echo
(canon "{vm_1_peer_name}" %map #%canon_map)
(call "{vm_1_peer_name}" ("m" "f") [#%canon_map] scalar) ; behaviour = echo
)
)
"#
);
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(vm_1_peer_id), &script)
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(vm_1_peer_name), &script)
.expect("invalid test AIR script");
let result = executor.execute_all(vm_1_peer_id).unwrap();
let result = executor.execute_all(vm_1_peer_name).unwrap();
let actual_trace = trace_from_result(&result.last().unwrap());
let mut cid_tracker: ExecutionCidState = ExecutionCidState::new();
@ -260,35 +264,32 @@ fn call_canon_stream_map_arg() {
),
];
assert_eq!(
actual_trace, expected_trace,
"{:#?}\n {:#?}",
actual_trace, expected_trace
);
assert_eq!(&*actual_trace, expected_trace,);
}
// WIP add negative
#[test]
fn call_peer_id_from_canon_stream_map() {
let vm_1_peer_id = "vm_1_peer_id";
let vm_1_peer_name = "vm_1_peer_id";
let vm_1_peer_id = at(vm_1_peer_name);
let script = format!(
r#"
(seq
(seq
(ap ("peerid" "{vm_1_peer_id}") %map)
(ap ("peerid" @"{vm_1_peer_name}") %map)
(ap (-42 "value2") %map)
)
(seq
(canon "{vm_1_peer_id}" %map #%canon_map)
(canon "{vm_1_peer_name}" %map #%canon_map)
(call #%canon_map.$.peerid.[0] ("m" "f") [#%canon_map] scalar) ; behaviour = echo
)
)
"#
);
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(vm_1_peer_id), &script)
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(vm_1_peer_name), &script)
.expect("invalid test AIR script");
let result = executor.execute_all(vm_1_peer_id).unwrap();
let result = executor.execute_all(vm_1_peer_name).unwrap();
let actual_trace = trace_from_result(&result.last().unwrap());
let mut cid_tracker: ExecutionCidState = ExecutionCidState::new();
@ -330,11 +331,7 @@ fn call_peer_id_from_canon_stream_map() {
),
];
assert_eq!(
actual_trace, expected_trace,
"{:#?}\n {:#?}",
actual_trace, expected_trace
);
assert_eq!(&*actual_trace, expected_trace,);
}
#[test]

View File

@ -16,6 +16,7 @@
use air::ExecutionCidState;
use air_test_framework::AirScriptExecutor;
use air_test_utils::key_utils::at;
use air_test_utils::prelude::*;
use pretty_assertions::assert_eq;
@ -695,29 +696,31 @@ fn test_merge_executed() {
#[test]
fn canon_stream_map() {
let vm_peer_id_1 = "vm_peer_id_1";
let vm_peer_id_1_name = "vm_peer_id_1";
let vm_peer_id_1_id = at(vm_peer_id_1_name);
let script = format!(
r#"
(seq
(ap (42 "value2") %map)
(seq
(ap ("key" "value1") %map)
(canon "{vm_peer_id_1}" %map #%canon_map)
(canon "{vm_peer_id_1_name}" %map #%canon_map)
)
)
"#
);
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(vm_peer_id_1), &script)
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(vm_peer_id_1_name), &script)
.expect("invalid test AIR script");
let result = executor.execute_all(vm_peer_id_1).unwrap();
let result = executor.execute_all(vm_peer_id_1_name).unwrap();
let actual_trace = trace_from_result(&result.last().unwrap());
let actual_data = data_from_result(&result.last().unwrap());
let mut cid_state: ExecutionCidState = ExecutionCidState::new();
let map_value1 = json!({"key": 42, "value": "value2"});
let map_value2 = json!({"key": "key", "value": "value1"});
let tetraplet = json!({"function_name": "", "json_path": "", "peer_pk": "vm_peer_id_1", "service_id": ""});
let tetraplet = json!({"function_name": "", "json_path": "", "peer_pk": vm_peer_id_1_id, "service_id": ""});
let states_vec = vec![
executed_state::ap(0),
@ -741,11 +744,7 @@ fn canon_stream_map() {
let expected_trace = ExecutionTrace::from(states_vec.clone());
assert_eq!(
actual_trace, expected_trace,
"left {:#?} \n right {:#?}",
actual_trace, expected_trace
);
assert_eq!(actual_data.trace, expected_trace, "{:#?}", actual_data.cid_info,);
}
#[test]
@ -1126,11 +1125,7 @@ fn canon_map_non_existing_index_tetraplet_check() {
let expected_trace = ExecutionTrace::from(states_vec.clone());
let expected_tetraplet = RefCell::new(vec![vec![SecurityTetraplet::new(vm_peer_id_1, "", "", ".$.key")]]);
assert_eq!(
actual_trace, expected_trace,
"left {:#?} \n right {:#?}",
actual_trace, expected_trace
);
assert_eq!(actual_trace, expected_trace,);
assert_eq!(tetraplet_checker.as_ref(), &expected_tetraplet);
}
@ -1221,11 +1216,7 @@ fn canon_map_non_existing_index_and_element_tetraplet_check() {
".$.[43].[2].some",
)]]);
assert_eq!(
actual_trace, expected_trace,
"left {:#?} \n right {:#?}",
actual_trace, expected_trace
);
assert_eq!(actual_trace, expected_trace,);
assert_eq!(tetraplet_checker.as_ref(), &expected_tetraplet);
}
@ -1264,13 +1255,13 @@ fn canon_map_2_scalar_tetraplet_check() {
let result = checked_call_vm!(vm_1, <_>::default(), &script, "", "");
let actual_trace = trace_from_result(&result);
let actual_data = data_from_result(&result);
let mut cid_state: ExecutionCidState = ExecutionCidState::new();
let map_value1 = json!({"42": "value1", "key": "value1"});
let call_result = map_value1.clone();
let tetraplet = json!({"function_name": "", "json_path": "", "peer_pk": "vm_peer_id_1", "service_id": ""});
let tetraplet = json!({"function_name": "", "json_path": "", "peer_pk": vm_peer_id_1, "service_id": ""});
let states_vec = vec![
executed_state::ap(0),
@ -1302,11 +1293,7 @@ fn canon_map_2_scalar_tetraplet_check() {
assert_eq!(tetraplet_checker.as_ref(), &expected_tetraplet);
assert_eq!(
actual_trace, expected_trace,
"left {:#?} \n right {:#?}",
actual_trace, expected_trace
);
assert_eq!(actual_data.trace, expected_trace, "{:#?}", actual_data.cid_info,);
}
#[test]
@ -1343,13 +1330,13 @@ fn canon_map_2_scalar_with_lens_tetraplet_check() {
);
let result = checked_call_vm!(vm_1, <_>::default(), &script, "", "");
let actual_trace = trace_from_result(&result);
let actual_data = data_from_result(&result);
let mut cid_state: ExecutionCidState = ExecutionCidState::new();
let map_value1 = json!({"42": "value1", "key": "value1"});
let call_result = json!("value1");
let tetraplet = json!({"function_name": "", "json_path": "", "peer_pk": "vm_peer_id_1", "service_id": ""});
let tetraplet = json!({"function_name": "", "json_path": "", "peer_pk": vm_peer_id_1, "service_id": ""});
let states_vec = vec![
executed_state::ap(0),
@ -1381,11 +1368,7 @@ fn canon_map_2_scalar_with_lens_tetraplet_check() {
assert_eq!(tetraplet_checker.as_ref(), &expected_tetraplet);
assert_eq!(
actual_trace, expected_trace,
"left {:#?} \n right {:#?}",
actual_trace, expected_trace
);
assert_eq!(actual_data.trace, expected_trace, "{:#?}", actual_data.cid_info,);
}
#[test]

View File

@ -19,6 +19,7 @@ use air::PreparationError;
use air::ToErrorCode;
use air_interpreter_data::ExecutionTrace;
use air_test_framework::AirScriptExecutor;
use air_test_utils::key_utils::at;
use air_test_utils::prelude::*;
use pretty_assertions::assert_eq;
@ -936,7 +937,9 @@ fn fold_stream_map() {
#[test]
fn fold_canon_stream_map() {
let vm_1_peer_id = "vm_1_peer_id";
let vm_1_peer_name = "vm_1_peer_id";
let vm_1_peer_id = at(vm_1_peer_name);
let script = format!(
r#"
(seq
@ -945,10 +948,10 @@ fn fold_canon_stream_map() {
(ap (-42 "value2") %map)
)
(seq
(canon "{vm_1_peer_id}" %map #%canon_map)
(canon "{vm_1_peer_name}" %map #%canon_map)
(fold #%canon_map iter
(seq
(call "{vm_1_peer_id}" ("m" "f") [iter] scalar) ; behaviour = echo
(call "{vm_1_peer_name}" ("m" "f") [iter] scalar) ; behaviour = echo
(next iter)
)
)
@ -957,9 +960,9 @@ fn fold_canon_stream_map() {
"#
);
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(vm_1_peer_id), &script)
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(vm_1_peer_name), &script)
.expect("invalid test AIR script");
let result = executor.execute_all(vm_1_peer_id).unwrap();
let result = executor.execute_all(vm_1_peer_name).unwrap();
let actual_trace = trace_from_result(&result.last().unwrap());
@ -991,7 +994,7 @@ fn fold_canon_stream_map() {
scalar_tracked!(
map_value_1.clone(),
cid_tracker,
peer = vm_1_peer_id,
peer = &vm_1_peer_id,
service = "m..0",
function = "f",
args = [map_value_1]
@ -1006,7 +1009,7 @@ fn fold_canon_stream_map() {
),
];
assert_eq!(actual_trace, expected_trace,);
assert_eq!(&*actual_trace, expected_trace,);
}
/// This test checks that fold over map and fold over canon map both produce
@ -1015,7 +1018,8 @@ fn fold_canon_stream_map() {
/// increments service name index for each call used.
#[test]
fn fold_map_and_canon_map_orders_are_same() {
let vm_1_peer_id = "vm_1_peer_id";
let vm_1_peer_name = "vm_1_peer_id";
let vm_1_peer_id = at(vm_1_peer_name);
let script = format!(
r#"
@ -1032,17 +1036,17 @@ fn fold_map_and_canon_map_orders_are_same() {
)
(seq
(seq
(canon "{vm_1_peer_id}" %map #%canon_map)
(canon "{vm_1_peer_name}" %map #%canon_map)
(fold #%canon_map iter
(seq
(call "{vm_1_peer_id}" ("m" "f") [iter] scalar) ; behaviour = echo
(call "{vm_1_peer_name}" ("m" "f") [iter] scalar) ; behaviour = echo
(next iter)
)
)
)
(fold %map iter
(seq
(call "{vm_1_peer_id}" ("m" "f") [iter] scalar1) ; behaviour = echo
(call "{vm_1_peer_name}" ("m" "f") [iter] scalar1) ; behaviour = echo
(next iter)
)
)
@ -1051,9 +1055,9 @@ fn fold_map_and_canon_map_orders_are_same() {
"#
);
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(vm_1_peer_id), &script)
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(vm_1_peer_name), &script)
.expect("invalid test AIR script");
let result = executor.execute_all(vm_1_peer_id).unwrap();
let result = executor.execute_all(vm_1_peer_name).unwrap();
let actual_trace = trace_from_result(&result.last().unwrap());
@ -1099,7 +1103,7 @@ fn fold_map_and_canon_map_orders_are_same() {
scalar_tracked!(
map_value_1.clone(),
cid_tracker,
peer = vm_1_peer_id,
peer = &vm_1_peer_id,
service = "m..0",
function = "f",
args = [map_value_1.clone()]
@ -1107,7 +1111,7 @@ fn fold_map_and_canon_map_orders_are_same() {
scalar_tracked!(
map_value_2.clone(),
cid_tracker,
peer = vm_1_peer_id,
peer = &vm_1_peer_id,
service = "m..0",
function = "f",
args = [map_value_2.clone()]
@ -1115,7 +1119,7 @@ fn fold_map_and_canon_map_orders_are_same() {
scalar_tracked!(
map_value_3.clone(),
cid_tracker,
peer = vm_1_peer_id,
peer = &vm_1_peer_id,
service = "m..0",
function = "f",
args = [map_value_3.clone()]
@ -1123,7 +1127,7 @@ fn fold_map_and_canon_map_orders_are_same() {
scalar_tracked!(
map_value_4.clone(),
cid_tracker,
peer = vm_1_peer_id,
peer = &vm_1_peer_id,
service = "m..0",
function = "f",
args = [map_value_4.clone()]
@ -1137,7 +1141,7 @@ fn fold_map_and_canon_map_orders_are_same() {
scalar_tracked!(
map_value_1.clone(),
cid_tracker,
peer = vm_1_peer_id,
peer = &vm_1_peer_id,
service = "m..1",
function = "f",
args = [map_value_1]
@ -1145,7 +1149,7 @@ fn fold_map_and_canon_map_orders_are_same() {
scalar_tracked!(
map_value_2.clone(),
cid_tracker,
peer = vm_1_peer_id,
peer = &vm_1_peer_id,
service = "m..1",
function = "f",
args = [map_value_2]
@ -1153,7 +1157,7 @@ fn fold_map_and_canon_map_orders_are_same() {
scalar_tracked!(
map_value_3.clone(),
cid_tracker,
peer = vm_1_peer_id,
peer = &vm_1_peer_id,
service = "m..1",
function = "f",
args = [map_value_3]
@ -1161,12 +1165,12 @@ fn fold_map_and_canon_map_orders_are_same() {
scalar_tracked!(
map_value_4.clone(),
cid_tracker,
peer = vm_1_peer_id,
peer = &vm_1_peer_id,
service = "m..1",
function = "f",
args = [map_value_4]
),
];
assert_eq!(actual_trace, expected_trace,);
assert_eq!(&*actual_trace, expected_trace);
}

View File

@ -413,7 +413,7 @@ fn match_with_undefined_last_error_errcode() {
let expected_trace = ExecutionTrace::from(vec![scalar_tracked!(
errcode_lambda_output.clone(),
cid_state,
peer = local_peer_id,
peer_name = local_peer_id,
service = "test..0",
function = "error_code",
args = vec![errcode_lambda_output]
@ -444,7 +444,7 @@ fn match_with_undefined_last_error_message() {
let expected_trace = ExecutionTrace::from(vec![scalar_tracked!(
message_lambda_output.clone(),
cid_state,
peer = local_peer_id,
peer_name = local_peer_id,
service = "test..0",
function = "message",
args = vec![message_lambda_output]

View File

@ -19,23 +19,23 @@ use air_test_utils::prelude::*;
#[test]
// https://github.com/fluencelabs/aquavm/issues/178
fn par_ap_behaviour() {
let client_id = "client_id";
let relay_id = "relay_id";
let variable_setter_id = "variable_setter_id";
let client_name = "client_id";
let relay_name = "relay_id";
let variable_setter_name = "variable_setter_id";
// ap doesn't affect the subgraph_complete flag
let script = format!(
r#"
(par
(call "{variable_setter_id}" ("peer" "timeout") [] join_it) ; behaviour=unit
(call "{variable_setter_name}" ("peer" "timeout") [] join_it) ; behaviour=unit
(seq
(par
(call "{relay_id}" ("peer" "timeout") [join_it] $result) ; behaviour=unit
(call "{relay_name}" ("peer" "timeout") [join_it] $result) ; behaviour=unit
(ap "fast_result" $result)
)
(seq
(canon "{client_id}" $result #result)
(call "{client_id}" ("op" "return") [#result.$[0]]) ; behaviour=unit
(canon "{client_name}" $result #result)
(call "{client_name}" ("op" "return") [#result.$[0]]) ; behaviour=unit
)
)
)
@ -43,15 +43,21 @@ fn par_ap_behaviour() {
);
let engine =
air_test_framework::AirScriptExecutor::from_annotated(TestRunParameters::new("client_id", 0, 1, ""), &script)
air_test_framework::AirScriptExecutor::from_annotated(TestRunParameters::new(client_name, 0, 1, ""), &script)
.expect("invalid test executor config");
let client_result_1 = engine.execute_one(client_id).unwrap();
assert_next_pks!(&client_result_1.next_peer_pks, [relay_id, variable_setter_id]);
let relay_id = engine.resolve_name(relay_name).to_string();
let variable_setter_id = engine.resolve_name(variable_setter_name).to_string();
let setter_result = engine.execute_one(variable_setter_id).unwrap();
let client_result_1 = engine.execute_one(client_name).unwrap();
assert_next_pks!(
&client_result_1.next_peer_pks,
[relay_id.as_str(), variable_setter_id.as_str()]
);
let setter_result = engine.execute_one(variable_setter_name).unwrap();
assert!(setter_result.next_peer_pks.is_empty());
let relay_result = engine.execute_one(relay_id).unwrap();
let relay_result = engine.execute_one(relay_name).unwrap();
assert!(relay_result.next_peer_pks.is_empty());
}

View File

@ -23,7 +23,7 @@ use pretty_assertions::assert_eq;
// test for github.com/fluencelabs/aquavm/issues/211
// On the versions < 0.20.1 it just crashes
fn issue_211() {
let peer_1_id = "peer_1_id";
let peer_1_name = "peer_1_id";
let script = format!(
r#"
@ -68,23 +68,25 @@ fn issue_211() {
"#
);
let run_params = TestRunParameters::from_init_peer_id(peer_1_id);
let run_params = TestRunParameters::from_init_peer_id(peer_1_name);
let engine = air_test_framework::AirScriptExecutor::from_annotated(run_params, &script)
.expect("invalid test executor config");
let peer_1_id = engine.resolve_name(peer_1_name).to_string();
let peer_1_id = peer_1_id.as_str();
let result = engine.execute_one(peer_1_id).unwrap();
let result = engine.execute_one(peer_1_name).unwrap();
let scalar_2 = scalar!(
json!([1, 2, 3]),
peer = peer_1_id,
peer_name = peer_1_name,
service = "getdatasrv..1",
function = "nodes"
);
let cid_2 = extract_service_result_cid(&scalar_2);
let expected_trace = ExecutionTrace::from(vec![
scalar!(2, peer = peer_1_id, service = "getdatasrv..0", function = "idx"),
scalar!(2, peer_name = peer_1_name, service = "getdatasrv..0", function = "idx"),
scalar_2,
executed_state::par(6, 0),
executed_state::par(1, 4),
@ -94,55 +96,55 @@ fn issue_211() {
executed_state::par(1, 0),
executed_state::ap(0),
executed_state::canon(json!({
"tetraplet": {"function_name": "", "json_path": "", "peer_pk": "peer_1_id", "service_id": ""},
"tetraplet": {"function_name": "", "json_path": "", "peer_pk": peer_1_id, "service_id": ""},
"values": [
{
"result": 1,
"tetraplet": {"function_name": "nodes", "json_path": ".$.[0]", "peer_pk": "peer_1_id", "service_id": "getdatasrv..1"},
"tetraplet": {"function_name": "nodes", "json_path": ".$.[0]", "peer_pk": peer_1_id, "service_id": "getdatasrv..1"},
"provenance": Provenance::service_result(cid_2.clone()),
},
{
"result": 2,
"tetraplet": {"function_name": "nodes", "json_path": ".$.[1]", "peer_pk": "peer_1_id", "service_id": "getdatasrv..1"},
"tetraplet": {"function_name": "nodes", "json_path": ".$.[1]", "peer_pk": peer_1_id, "service_id": "getdatasrv..1"},
"provenance": Provenance::service_result(cid_2.clone()),
},
{
"result": 3,
"tetraplet": {"function_name": "nodes", "json_path": ".$.[2]", "peer_pk": "peer_1_id", "service_id": "getdatasrv..1"},
"tetraplet": {"function_name": "nodes", "json_path": ".$.[2]", "peer_pk": peer_1_id, "service_id": "getdatasrv..1"},
"provenance": Provenance::service_result(cid_2.clone()),
},
]
})),
unused!(
"expected result",
peer = peer_1_id,
peer_name = peer_1_name,
service = "op..2",
function = "noop",
args = vec![json!(3), json!([1, 2, 3])]
),
executed_state::canon(json!({
"tetraplet": {"function_name": "", "json_path": "", "peer_pk": "peer_1_id", "service_id": ""},
"tetraplet": {"function_name": "", "json_path": "", "peer_pk": peer_1_id, "service_id": ""},
"values": [
{
"result": 1,
"tetraplet": {"function_name": "nodes", "json_path": ".$.[0]", "peer_pk": "peer_1_id", "service_id": "getdatasrv..1"},
"tetraplet": {"function_name": "nodes", "json_path": ".$.[0]", "peer_pk": peer_1_id, "service_id": "getdatasrv..1"},
"provenance": Provenance::service_result(cid_2.clone()),
},
{
"result": 2,
"tetraplet": {"function_name": "nodes", "json_path": ".$.[1]", "peer_pk": "peer_1_id", "service_id": "getdatasrv..1"},
"tetraplet": {"function_name": "nodes", "json_path": ".$.[1]", "peer_pk": peer_1_id, "service_id": "getdatasrv..1"},
"provenance": Provenance::service_result(cid_2.clone()),
},
{
"result": 3,
"tetraplet": {"function_name": "nodes", "json_path": ".$.[2]", "peer_pk": "peer_1_id", "service_id": "getdatasrv..1"},
"tetraplet": {"function_name": "nodes", "json_path": ".$.[2]", "peer_pk": peer_1_id, "service_id": "getdatasrv..1"},
"provenance": Provenance::service_result(cid_2),
},
]
})),
scalar!(
"expected result",
peer = peer_1_id,
peer_name = peer_1_name,
service = "op..3",
function = "identity",
args = vec![json!([1, 2, 3])]

View File

@ -16,17 +16,18 @@
use air_interpreter_data::ExecutionTrace;
use air_test_framework::AirScriptExecutor;
use air_test_utils::key_utils::at;
use air_test_utils::prelude::*;
use pretty_assertions::assert_eq;
#[test]
// test for github.com/fluencelabs/aquavm/issues/221
fn issue_221() {
let peer_1_id = "peer_1_id";
let peer_2_id = "peer_2_id";
let join_1_id = "join_1_id";
let join_2_id = "join_2_id";
let set_variable_id = "set_variable_id";
let peer_1_name = "peer_1_id";
let peer_2_name = "peer_2_id";
let join_1_name = "join_1_id";
let join_2_name = "join_2_id";
let set_variable_name = "set_variable_id";
let peer_1_value = "peer_1_value";
let peer_2_value = "peer_2_value";
@ -37,11 +38,11 @@ fn issue_221() {
(seq
(seq
;; let's peers be an array of two values [peer_1_id, peer_2_id]
(call "{set_variable_id}" ("" "") [] peers) ; ok = ["{peer_1_id}", "{peer_2_id}"]
(call "{set_variable_name}" ("" "") [] peers) ; ok = [@"{peer_1_name}", @"{peer_2_name}"]
(fold peers peer
(par
(seq
(call peer ("" "") [peer] value) ; map = {{"{peer_1_id}": "{peer_1_value}", "{peer_2_id}": "{peer_2_value}"}}
(call peer ("" "") [peer] value) ; map = {{@"{peer_1_name}": "{peer_1_value}", @"{peer_2_name}": "{peer_2_value}"}}
;; it's crucial to reproduce this bug to add value to stream
;; with help of ap instruction
(ap value $stream)
@ -58,14 +59,14 @@ fn issue_221() {
;; appropriate way and state for (1) is returned
(par
(par
(call "{join_1_id}" ("" "") [iterator]) ; behaviour = echo
(call "{join_2_id}" ("" "") [iterator]) ; behaviour = echo
(call "{join_1_name}" ("" "") [iterator]) ; behaviour = echo
(call "{join_2_name}" ("" "") [iterator]) ; behaviour = echo
)
(next iterator)
)
)
)
(call "some_peer_id" ("" "") []) ;; (1)
(call "some_peer_name" ("" "") []) ;; (1)
)
"#
);
@ -73,25 +74,43 @@ fn issue_221() {
let executor = <AirScriptExecutor>::new(
TestRunParameters::from_init_peer_id("set_variable_id"),
vec![],
vec![peer_1_id, peer_2_id].into_iter().map(Into::into),
vec![peer_1_name, peer_2_name].into_iter().map(Into::into),
&script,
)
.expect("Invalid annotated AIR script");
let _result = executor.execute_one(set_variable_id).unwrap();
let _peer_1_result = executor.execute_one(peer_1_id).unwrap();
let _peer_2_result = executor.execute_one(peer_2_id).unwrap();
let peer_1_id = at(peer_1_name);
let peer_2_id = at(peer_2_name);
let join_1_id = at(join_1_name);
let _join_1_result = executor.execute_one(join_1_id).unwrap();
let join_1_result = executor.execute_one(join_1_id).unwrap(); // before 0.20.9 it fails here
let _result = executor.execute_one(set_variable_name).unwrap();
let _peer_1_result = executor.execute_one(peer_1_name).unwrap();
let _peer_2_result = executor.execute_one(peer_2_name).unwrap();
let _join_1_result = executor.execute_one(join_1_name).unwrap();
let join_1_result = executor.execute_one(join_1_name).unwrap(); // before 0.20.9 it fails here
let actual_trace = trace_from_result(&join_1_result);
let expected_trace = ExecutionTrace::from(vec![
scalar!(json!([peer_1_id, peer_2_id]), peer = set_variable_id, service = "..0"),
scalar!(
json!([peer_1_id, peer_2_id]),
peer_name = set_variable_name,
service = "..0"
),
executed_state::par(2, 3),
scalar!(peer_1_value, peer = peer_1_id, service = "..1", args = vec![peer_1_id]),
scalar!(
peer_1_value,
peer_name = peer_1_name,
service = "..1",
args = vec![peer_1_id.as_str()]
),
executed_state::ap(0),
executed_state::par(2, 0),
scalar!(peer_2_value, peer = peer_2_id, service = "..1", args = vec![peer_2_id]),
scalar!(
peer_2_value,
peer_name = peer_2_name,
service = "..1",
args = vec![peer_2_id.as_str()]
),
executed_state::ap(1),
executed_state::fold(vec![
executed_state::subtrace_lore(3, SubTraceDesc::new(8.into(), 4), SubTraceDesc::new(12.into(), 0)),
@ -101,7 +120,7 @@ fn issue_221() {
executed_state::par(1, 1),
unused!(
peer_1_value,
peer = join_1_id,
peer_name = join_1_name,
service = "..2",
args = vec![peer_1_value]
),
@ -110,7 +129,7 @@ fn issue_221() {
executed_state::par(1, 1),
unused!(
peer_2_value,
peer = join_1_id,
peer_name = join_1_name,
service = "..2",
args = vec![peer_2_value]
),

View File

@ -22,9 +22,9 @@ fn issue_356() {
let script = r#"
(seq
(seq
(call "relay" ("kad" "neighborhood") ["relay"] neighs_top) ; ok = ["p1"]
(call "relay" ("kad" "neighborhood") ["relay"] neighs_top) ; ok = [@"p1"]
(seq
(call "p1" ("kad" "neighborhood") ["p1"] neighs_inner) ; ok =["p1"]
(call "p1" ("kad" "neighborhood") ["p1"] neighs_inner) ; ok =[@"p1"]
(par
(call "relay" ("peer" "identify") ["relay"] $external_addresses) ; behaviour = echo
(call "p1" ("peer" "identify") ["p1"] $external_addresses) ; behaviour = echo

View File

@ -145,7 +145,7 @@ fn set_subtrace_len_and_pos_failed() {
let mut cid_state = ExecutionCidState::new();
let trace = vec![
executed_state::par(1, 2),
stream_tracked!(json!([42, 43]), 0, cid_state),
stream_tracked!(json!([42, 43]), 0, cid_state, peer = vm_peer_id_1),
executed_state::fold(vec![executed_state::subtrace_lore(
1,
subtrace_desc(5, 1),
@ -184,7 +184,7 @@ fn no_element_at_position() {
let mut cid_state = ExecutionCidState::new();
let trace = vec![
executed_state::par(1, 2),
stream_tracked!(json!([42, 43]), 0, cid_state),
stream_tracked!(json!([42, 43]), 0, cid_state, peer = vm_peer_id_1),
executed_state::fold(vec![executed_state::subtrace_lore(
42,
subtrace_desc(3, 1),
@ -224,7 +224,7 @@ fn no_stream_state() {
let wrong_state = request_sent_by("vm_peer_id_1");
let trace = vec![
executed_state::par(1, 2),
stream_tracked!(json!([42, 43]), 0, &mut tracker),
stream_tracked!(json!([42, 43]), 0, &mut tracker, peer = vm_peer_id_1),
executed_state::fold(vec![executed_state::subtrace_lore(
3,
subtrace_desc(3, 1), // try to change the number of elems to 3
@ -444,7 +444,7 @@ fn several_records_with_same_pos() {
let value_pos = 1;
let trace = vec![
executed_state::par(1, 2),
stream_tracked!(json!([42, 43]), 0, cid_state),
stream_tracked!(json!([42, 43]), 0, &mut cid_state, peer = vm_peer_id_1),
fold(vec![
subtrace_lore(value_pos, subtrace_desc(3, 1), subtrace_desc(4, 0)),
subtrace_lore(value_pos, subtrace_desc(3, 1), subtrace_desc(4, 0)),
@ -543,7 +543,7 @@ fn fold_pos_overflow() {
let wrong_after_subtrace_len = TraceLen::MAX - 1;
let trace = vec![
executed_state::par(1, 2),
stream_tracked!(json!([42, 43]), 0, cid_state),
stream_tracked!(json!([42, 43]), 0, cid_state, peer = vm_peer_id_1),
fold(vec![subtrace_lore(
value_pos,
subtrace_desc(before_subtrace_pos, 1),

View File

@ -143,7 +143,7 @@ fn malformed_call_service_failed() {
let tetraplet_cid = cid_state.tetraplet_tracker.track_value(tetraplet).unwrap();
let service_result_agg = ServiceResultCidAggregate {
value_cid,
argument_hash: "0000000000000".into(),
argument_hash: "bagaaieraj5j43immfovaya2uxnpzupwl4xwrfk2nryi3vbz4f4irmeqcxfcq".into(),
tetraplet_cid,
};
let service_result_agg_cid = cid_state
@ -159,7 +159,7 @@ fn malformed_call_service_failed() {
let result = vm.call(&air, vec![], data, TestRunParameters::default()).unwrap();
let expected_serde_error = serde_json::from_value::<CallServiceFailed>(value).unwrap_err();
let expected_error = MalformedCallServiceFailed(expected_serde_error);
assert!(check_error(&result, expected_error));
assert_error_eq!(&result, expected_error);
}
#[test]

File diff suppressed because it is too large Load Diff

View File

@ -409,6 +409,469 @@ Machine c1f3ea5950db0a10b44da931c25774d64ab25084f47d504f72f311e694550ff1:
execute: 29.00µs
new: 38.00µs
Machine d77ebe8481884bc3b2778c8083f1bf459e548e929edd87041beb14f6b868d35f:
<<<<<<< HEAD
Platform: macOS-13.4.1-arm64-arm-64bit
Timestamp: 2023-07-23 15:42:03.477661+00:00
AquaVM version: 0.42.0
Benches:
Features: check_signatures,gen_signatures
big_values_data (9.29ms; 59.062 MiB, 59.062 MiB): Loading a trace with huge values
air::runner::execute_air: 9.29ms
preparation_step::preparation::parse_data: 4.90ms
from_slice: 4.85ms
preparation_step::preparation::prepare: 176.00µs
air::preparation_step::preparation::make_exec_ctx: 19.00µs
air_parser::parser::air_parser::parse: 32.00µs
runner::execute: 12.00µs
runner::farewell: 3.86ms
from_success_result: 3.84ms
populate_outcome_from_contexts: 3.80ms
signing_step::sign_produced_cids: 110.00µs
verification_step::verify: 83.00µs
verify: 10.00µs
dashboard (6.71ms; 52.750 MiB, 52.750 MiB): big dashboard test
air::runner::execute_air: 6.71ms
preparation_step::preparation::parse_data: 1.00ms
from_slice: 947.00µs
preparation_step::preparation::prepare: 327.00µs
air::preparation_step::preparation::make_exec_ctx: 36.00µs
air_parser::parser::air_parser::parse: 170.00µs
runner::execute: 1.70ms
to_string(tetraplets): 369.00µs
runner::farewell: 485.00µs
from_success_result: 465.00µs
populate_outcome_from_contexts: 417.00µs
signing_step::sign_produced_cids: 105.00µs
verification_step::verify: 2.93ms
verify: 197.00µs
long_data (3.88ms; 54.000 MiB, 54.000 MiB): Long data trace
air::runner::execute_air: 3.88ms
preparation_step::preparation::parse_data: 1.76ms
from_slice: 1.72ms
preparation_step::preparation::prepare: 175.00µs
air::preparation_step::preparation::make_exec_ctx: 19.00µs
air_parser::parser::air_parser::parse: 30.00µs
runner::execute: 11.00µs
runner::farewell: 1.06ms
from_success_result: 1.04ms
populate_outcome_from_contexts: 1.00ms
signing_step::sign_produced_cids: 108.00µs
verification_step::verify: 612.00µs
verify: 10.00µs
multiple-cids10 (2.63ms; 52.625 MiB, 52.625 MiB): verifying multiple CIDs for single peer
air::runner::execute_air: 2.63ms
preparation_step::preparation::parse_data: 418.00µs
from_slice: 364.00µs
preparation_step::preparation::prepare: 216.00µs
air::preparation_step::preparation::make_exec_ctx: 33.00µs
air_parser::parser::air_parser::parse: 63.00µs
runner::execute: 452.00µs
to_string(tetraplets): 10.00µs
runner::farewell: 342.00µs
from_success_result: 322.00µs
populate_outcome_from_contexts: 285.00µs
signing_step::sign_produced_cids: 103.00µs
verification_step::verify: 944.00µs
verify: 227.00µs
multiple-cids50 (24.68ms; 59.375 MiB, 59.375 MiB): verifying multiple CIDs for single peer
air::runner::execute_air: 24.68ms
preparation_step::preparation::parse_data: 4.87ms
from_slice: 4.81ms
preparation_step::preparation::prepare: 444.00µs
air::preparation_step::preparation::make_exec_ctx: 255.00µs
air_parser::parser::air_parser::parse: 67.00µs
runner::execute: 8.86ms
to_string(tetraplets): 11.00µs
runner::farewell: 3.40ms
from_success_result: 3.38ms
populate_outcome_from_contexts: 3.34ms
signing_step::sign_produced_cids: 104.00µs
verification_step::verify: 6.86ms
verify: 4.68ms
multiple-peers14 (36.77ms; 59.688 MiB, 59.688 MiB): verifying many CIDs for many peers
air::runner::execute_air: 36.77ms
preparation_step::preparation::parse_data: 5.62ms
from_slice: 5.56ms
preparation_step::preparation::prepare: 462.00µs
air::preparation_step::preparation::make_exec_ctx: 273.00µs
air_parser::parser::air_parser::parse: 70.00µs
runner::execute: 15.29ms
to_string(tetraplets): 11.00µs
runner::farewell: 3.80ms
from_success_result: 3.77ms
populate_outcome_from_contexts: 3.74ms
signing_step::sign_produced_cids: 105.00µs
verification_step::verify: 11.32ms
verify: 5.12ms
multiple-peers25 (188.40ms; 86.000 MiB, 86.000 MiB): verifying many CIDs for many peers
air::runner::execute_air: 188.40ms
preparation_step::preparation::parse_data: 30.22ms
from_slice: 30.15ms
preparation_step::preparation::prepare: 2.17ms
air::preparation_step::preparation::make_exec_ctx: 1.97ms
air_parser::parser::air_parser::parse: 79.00µs
runner::execute: 87.63ms
to_string(tetraplets): 22.00µs
runner::farewell: 20.97ms
from_success_result: 20.94ms
populate_outcome_from_contexts: 20.88ms
signing_step::sign_produced_cids: 115.00µs
verification_step::verify: 46.95ms
verify: 29.99ms
multiple-peers5 (17.65ms; 52.625 MiB, 52.625 MiB): verifying many CIDs for many peers
air::runner::execute_air: 17.65ms
preparation_step::preparation::parse_data: 590.00µs
from_slice: 540.00µs
preparation_step::preparation::prepare: 229.00µs
air::preparation_step::preparation::make_exec_ctx: 35.00µs
air_parser::parser::air_parser::parse: 67.00µs
runner::execute: 13.61ms
execute: 10.57ms
execute: 2.47ms
prepare_request_params: 44.00µs
to_string(tetraplets): 13.00µs
new: 1.83ms
runner::farewell: 483.00µs
from_success_result: 463.00µs
populate_outcome_from_contexts: 429.00µs
to_vec(call_results): 12.00µs
to_vec(data): 209.00µs
signing_step::sign_produced_cids: 109.00µs
verification_step::verify: 2.48ms
verify: 289.00µs
multiple-sigs10 (13.63ms; 52.875 MiB, 52.875 MiB): signing multiple CIDs
air::runner::execute_air: 13.63ms
preparation_step::preparation::parse_data: 653.00µs
from_slice: 602.00µs
preparation_step::preparation::prepare: 224.00µs
air::preparation_step::preparation::make_exec_ctx: 37.00µs
air_parser::parser::air_parser::parse: 65.00µs
runner::execute: 10.85ms
call::execute: 7.94ms
execute: 1.77ms
new: 1.35ms
canon::execute: 496.00µs
runner::farewell: 564.00µs
from_success_result: 544.00µs
populate_outcome_from_contexts: 485.00µs
to_vec(call_results): 12.00µs
to_vec(data): 227.00µs
signing_step::sign_produced_cids: 155.00µs
verification_step::verify: 1.02ms
verify: 238.00µs
multiple-sigs200 (906.10ms; 211.625 MiB, 211.625 MiB): signing multiple CIDs
air::runner::execute_air: 906.10ms
preparation_step::preparation::parse_data: 166.70ms
from_slice: 166.67ms
preparation_step::preparation::prepare: 16.93ms
air::preparation_step::preparation::make_exec_ctx: 16.71ms
air_parser::parser::air_parser::parse: 80.00µs
runner::execute: 381.00ms
runner::farewell: 165.40ms
from_success_result: 165.40ms
populate_outcome_from_contexts: 153.70ms
signing_step::sign_produced_cids: 22.05ms
verification_step::verify: 153.40ms
verify: 84.07ms
multiple-sigs50 (49.64ms; 62.625 MiB, 62.625 MiB): signing multiple CIDs
air::runner::execute_air: 49.64ms
preparation_step::preparation::parse_data: 10.52ms
from_slice: 10.46ms
preparation_step::preparation::prepare: 615.00µs
air::preparation_step::preparation::make_exec_ctx: 428.00µs
air_parser::parser::air_parser::parse: 68.00µs
runner::execute: 21.67ms
runner::farewell: 6.97ms
from_success_result: 6.95ms
populate_outcome_from_contexts: 6.57ms
signing_step::sign_produced_cids: 1.23ms
verification_step::verify: 8.51ms
verify: 4.56ms
network-explore (3.12ms; 52.500 MiB, 52.500 MiB): 5 peers of network are discovered
air::runner::execute_air: 3.12ms
preparation_step::preparation::parse_data: 478.00µs
from_slice: 423.00µs
preparation_step::preparation::prepare: 233.00µs
air::preparation_step::preparation::make_exec_ctx: 27.00µs
air_parser::parser::air_parser::parse: 86.00µs
runner::execute: 174.00µs
to_string(tetraplets): 10.00µs
runner::farewell: 265.00µs
from_success_result: 245.00µs
populate_outcome_from_contexts: 208.00µs
signing_step::sign_produced_cids: 105.00µs
verification_step::verify: 1.71ms
verify: 72.00µs
network_explore (5.23ms; 52.500 MiB, 52.500 MiB): 5 peers of network are discovered
air::runner::execute_air: 5.23ms
preparation_step::preparation::parse_data: 480.00µs
from_slice: 429.00µs
preparation_step::preparation::prepare: 236.00µs
air::preparation_step::preparation::make_exec_ctx: 25.00µs
air_parser::parser::air_parser::parse: 84.00µs
runner::execute: 2.19ms
execute: 1.67ms
execute: 408.00µs
prepare_request_params: 48.00µs
to_string(tetraplets): 17.00µs
new: 300.00µs
runner::farewell: 326.00µs
from_success_result: 306.00µs
populate_outcome_from_contexts: 270.00µs
to_vec(call_results): 12.00µs
to_vec(data): 97.00µs
signing_step::sign_produced_cids: 112.00µs
verification_step::verify: 1.73ms
verify: 76.00µs
null (725.00µs; 52.500 MiB, 52.500 MiB): Empty data and null script
air::runner::execute_air: 725.00µs
preparation_step::preparation::parse_data: 20.00µs
preparation_step::preparation::prepare: 193.00µs
air::preparation_step::preparation::make_exec_ctx: 24.00µs
air_parser::parser::air_parser::parse: 39.00µs
runner::execute: 11.00µs
runner::farewell: 189.00µs
from_success_result: 170.00µs
populate_outcome_from_contexts: 133.00µs
signing_step::sign_produced_cids: 112.00µs
verification_step::verify: 38.00µs
verify: 12.00µs
parser_10000_100 (22.72ms; 57.812 MiB, 57.812 MiB): Running very long AIR script with lot of variables and assignments
air::runner::execute_air: 22.72ms
preparation_step::preparation::parse_data: 20.00µs
preparation_step::preparation::prepare: 21.25ms
air::preparation_step::preparation::make_exec_ctx: 23.00µs
air_parser::parser::air_parser::parse: 21.11ms
runner::execute: 47.00µs
runner::farewell: 192.00µs
from_success_result: 173.00µs
populate_outcome_from_contexts: 137.00µs
signing_step::sign_produced_cids: 110.00µs
verification_step::verify: 39.00µs
verify: 11.00µs
Machine e536f8eaae8c978493a773ba566ae3393e2e6240d6ea8e05b5ca1b8f77e9c441:
Platform: Linux-5.15.0-76-generic-x86_64-with-glibc2.29
Timestamp: 2023-07-14 15:53:40.260043+00:00
AquaVM version: 0.41.0
Benches:
Features: check_signatures,gen_signatures
big_values_data (13.28ms; 59.062 MiB, 59.062 MiB): Loading a trace with huge values
air::runner::execute_air: 13.28ms
preparation_step::preparation::parse_data: 7.70ms
from_slice: 7.64ms
preparation_step::preparation::prepare: 226.00µs
air::preparation_step::preparation::make_exec_ctx: 16.00µs
air_parser::parser::air_parser::parse: 21.00µs
runner::execute: 10.00µs
runner::farewell: 4.97ms
from_success_result: 4.94ms
populate_outcome_from_contexts: 4.90ms
to_vec(call_results): 15.00µs
to_vec(data): 3.93ms
signing_step::sign_produced_cids: 154.00µs
verification_step::verify: 90.00µs
verify: 12.00µs
dashboard (36.27ms; 52.750 MiB, 52.750 MiB): big dashboard test
air::runner::execute_air: 36.27ms
preparation_step::preparation::parse_data: 1.34ms
from_slice: 1.28ms
preparation_step::preparation::prepare: 350.00µs
air::preparation_step::preparation::make_exec_ctx: 42.00µs
air_parser::parser::air_parser::parse: 130.00µs
runner::execute: 29.00ms
execute: 23.27ms
execute: 6.94ms
prepare_request_params: 1.86ms
to_string(tetraplets): 573.00µs
new: 3.79ms
runner::farewell: 746.00µs
from_success_result: 723.00µs
populate_outcome_from_contexts: 678.00µs
to_vec(call_results): 31.00µs
to_vec(data): 379.00µs
signing_step::sign_produced_cids: 159.00µs
verification_step::verify: 4.55ms
verify: 273.00µs
long_data (5.23ms; 54.000 MiB, 54.000 MiB): Long data trace
air::runner::execute_air: 5.23ms
preparation_step::preparation::parse_data: 2.57ms
from_slice: 2.53ms
preparation_step::preparation::prepare: 223.00µs
air::preparation_step::preparation::make_exec_ctx: 16.00µs
air_parser::parser::air_parser::parse: 20.00µs
runner::execute: 10.00µs
runner::farewell: 1.27ms
from_success_result: 1.25ms
populate_outcome_from_contexts: 1.21ms
to_vec(call_results): 14.00µs
to_vec(data): 658.00µs
signing_step::sign_produced_cids: 156.00µs
verification_step::verify: 858.00µs
verify: 12.00µs
multiple-cids10 (15.18ms; 52.625 MiB, 52.625 MiB): verifying multiple CIDs for single peer
air::runner::execute_air: 15.18ms
preparation_step::preparation::parse_data: 486.00µs
from_slice: 432.00µs
preparation_step::preparation::prepare: 256.00µs
air::preparation_step::preparation::make_exec_ctx: 34.00µs
air_parser::parser::air_parser::parse: 46.00µs
runner::execute: 12.19ms
execute: 9.42ms
execute: 2.23ms
prepare_request_params: 48.00µs
to_string(tetraplets): 14.00µs
new: 1.49ms
runner::farewell: 546.00µs
from_success_result: 524.00µs
populate_outcome_from_contexts: 492.00µs
to_vec(call_results): 14.00µs
to_vec(data): 229.00µs
signing_step::sign_produced_cids: 157.00µs
verification_step::verify: 1.41ms
verify: 314.00µs
multiple-cids50 (323.00ms; 59.375 MiB, 59.375 MiB): verifying multiple CIDs for single peer
air::runner::execute_air: 323.00ms
preparation_step::preparation::parse_data: 6.79ms
from_slice: 6.72ms
preparation_step::preparation::prepare: 595.00µs
air::preparation_step::preparation::make_exec_ctx: 362.00µs
air_parser::parser::air_parser::parse: 49.00µs
runner::execute: 299.20ms
execute: 232.19ms
execute: 53.08ms
prepare_request_params: 54.00µs
to_string(tetraplets): 16.00µs
new: 36.44ms
runner::farewell: 6.07ms
from_success_result: 6.05ms
populate_outcome_from_contexts: 6.01ms
to_vec(call_results): 16.00µs
to_vec(data): 4.77ms
signing_step::sign_produced_cids: 180.00µs
verification_step::verify: 10.09ms
verify: 6.97ms
multiple-peers14 (383.60ms; 59.750 MiB, 59.812 MiB): verifying many CIDs for many peers
air::runner::execute_air: 383.60ms
preparation_step::preparation::parse_data: 7.75ms
from_slice: 7.68ms
preparation_step::preparation::prepare: 607.00µs
air::preparation_step::preparation::make_exec_ctx: 374.00µs
air_parser::parser::air_parser::parse: 52.00µs
runner::execute: 351.40ms
execute: 274.07ms
execute: 65.03ms
prepare_request_params: 52.00µs
to_string(tetraplets): 15.00µs
new: 46.23ms
runner::farewell: 6.55ms
from_success_result: 6.53ms
populate_outcome_from_contexts: 6.49ms
to_vec(call_results): 16.00µs
to_vec(data): 5.13ms
signing_step::sign_produced_cids: 170.00µs
verification_step::verify: 17.10ms
verify: 7.55ms
multiple-peers25 (2.15s; 85.750 MiB, 86.562 MiB): verifying many CIDs for many peers
air::runner::execute_air: 2.15s
preparation_step::preparation::parse_data: 40.88ms
from_slice: 40.76ms
preparation_step::preparation::prepare: 3.70ms
air::preparation_step::preparation::make_exec_ctx: 3.40ms
air_parser::parser::air_parser::parse: 58.00µs
runner::execute: 2.00s
execute: 1.56s
execute: 378.34ms
prepare_request_params: 73.00µs
to_string(tetraplets): 16.00µs
new: 259.16ms
runner::farewell: 34.76ms
from_success_result: 34.72ms
populate_outcome_from_contexts: 34.63ms
to_vec(call_results): 18.00µs
to_vec(data): 27.11ms
signing_step::sign_produced_cids: 190.00µs
verification_step::verify: 70.22ms
verify: 44.09ms
multiple-sigs200 (6.04s; 214.375 MiB, 214.375 MiB): signing multiple CIDs
air::runner::execute_air: 6.04s
preparation_step::preparation::parse_data: 219.80ms
from_slice: 219.70ms
preparation_step::preparation::prepare: 25.42ms
air::preparation_step::preparation::make_exec_ctx: 25.09ms
air_parser::parser::air_parser::parse: 58.00µs
runner::execute: 5.30s
call::execute: 3.83s
execute: 941.01ms
new: 604.96ms
canon::execute: 308.30ms
runner::farewell: 253.30ms
from_success_result: 253.30ms
populate_outcome_from_contexts: 233.40ms
to_vec(call_results): 16.00µs
to_vec(data): 145.70ms
signing_step::sign_produced_cids: 30.84ms
verification_step::verify: 214.00ms
verify: 119.20ms
multiple-sigs50 (369.10ms; 62.562 MiB, 62.562 MiB): signing multiple CIDs
air::runner::execute_air: 369.10ms
preparation_step::preparation::parse_data: 13.94ms
from_slice: 13.87ms
preparation_step::preparation::prepare: 914.00µs
air::preparation_step::preparation::make_exec_ctx: 678.00µs
air_parser::parser::air_parser::parse: 52.00µs
runner::execute: 328.70ms
call::execute: 238.69ms
execute: 56.21ms
new: 38.04ms
canon::execute: 18.24ms
runner::farewell: 11.01ms
from_success_result: 11.01ms
populate_outcome_from_contexts: 10.57ms
to_vec(call_results): 14.00µs
to_vec(data): 7.77ms
signing_step::sign_produced_cids: 1.77ms
verification_step::verify: 12.49ms
verify: 6.96ms
network-explore (6.83ms; 52.500 MiB, 52.500 MiB): 5 peers of network are discovered
air::runner::execute_air: 6.83ms
preparation_step::preparation::parse_data: 603.00µs
from_slice: 545.00µs
preparation_step::preparation::prepare: 274.00µs
air::preparation_step::preparation::make_exec_ctx: 26.00µs
air_parser::parser::air_parser::parse: 68.00µs
runner::execute: 2.55ms
execute: 1.98ms
execute: 490.00µs
prepare_request_params: 52.00µs
to_string(tetraplets): 16.00µs
new: 339.00µs
runner::farewell: 429.00µs
from_success_result: 406.00µs
populate_outcome_from_contexts: 372.00µs
to_vec(call_results): 14.00µs
to_vec(data): 140.00µs
signing_step::sign_produced_cids: 157.00µs
verification_step::verify: 2.67ms
verify: 81.00µs
parser_10000_100 (30.02ms; 57.812 MiB, 57.812 MiB): Running very long AIR script with lot of variables and assignments
air::runner::execute_air: 30.02ms
preparation_step::preparation::parse_data: 19.00µs
preparation_step::preparation::prepare: 28.02ms
air::preparation_step::preparation::make_exec_ctx: 18.00µs
air_parser::parser::air_parser::parse: 27.78ms
runner::execute: 163.00µs
execute: 115.00µs
execute: 29.00µs
new: 21.00µs
runner::farewell: 328.00µs
from_success_result: 304.00µs
populate_outcome_from_contexts: 270.00µs
to_vec(call_results): 15.00µs
to_vec(data): 44.00µs
signing_step::sign_produced_cids: 161.00µs
verification_step::verify: 40.00µs
verify: 13.00µs
=======
Platform: macOS-14.0-arm64-arm-64bit
Timestamp: 2023-10-04 14:50:18.122679+00:00
AquaVM version: 0.48.0

View File

@ -1,25 +0,0 @@
# Preparing data for the performance_metering
One may need to regenerate data on changing data format. Here is the instruction for data regeneration.
## `parser_10000_100`
Data is empty, no regeneration required.
## `dashboard` and `network_explore`
Run `junk/gen_test_data`. No WASM binary required. It will generate prev and current data as JSON files
with prefixes `dashboard` and `explore`.
## `big_data` and `long_data`
In the `junk/cidify` directory, run
``` sh
cargo run -- ./anomaly_big.json simple-calls-info.json > ../../air/benches/data/anomaly_big.json
cargo run -- ./anomaly_long.json simple-calls-info.json > ../../air/benches/data/anomaly_long.json
cp ../../air/benches/data/anomaly_big.json ../../benches/performance_metering/big_values_data/prev_data.json
cp ../../air/benches/data/anomaly_long.json ../../benches/performance_metering/long_data/cur_data.json
```
You may need update the `cidify` tool if you change data format again.

View File

@ -1 +0,0 @@
4nWRt5MyWTEjBJEYaKokky9SS7T5Vms9v2VfiFwAwNaW1E9FVWAFV8sKxMy7yLCkm5zKhkV6cRKwWAGTcVMvvEac

View File

@ -1,3 +0,0 @@
{
"comment": "Loading a trace with huge values"
}

View File

@ -1 +0,0 @@
4nWRt5MyWTEjBJEYaKokky9SS7T5Vms9v2VfiFwAwNaW1E9FVWAFV8sKxMy7yLCkm5zKhkV6cRKwWAGTcVMvvEac

View File

@ -1,3 +0,0 @@
{
"comment": "big dashboard test"
}

View File

@ -1 +0,0 @@
4nWRt5MyWTEjBJEYaKokky9SS7T5Vms9v2VfiFwAwNaW1E9FVWAFV8sKxMy7yLCkm5zKhkV6cRKwWAGTcVMvvEac

View File

@ -1,3 +0,0 @@
{
"comment": "Long data trace"
}

View File

@ -1 +0,0 @@
(null)

View File

@ -1 +0,0 @@
36gwouRjGormaTzTsyzx7cbuLbsTMk31VoZZ7LA2DaLNHQBqWND4ThNfJUUCnbBhMvRLCJrfr9zW68QURik7zPnT

View File

@ -1 +0,0 @@
{"comment":"verifying multiple CIDs for single peer"}

View File

@ -1 +0,0 @@
(seq (seq (call "12D3KooWPeUMWXdtEveZonecW8hhFdt4wfT4AyYpp4yrFGti7w5m" ("..0" "init") [] data) (fold data x (seq (fold data y (seq (call "12D3KooWHNgtEsSgAQukzihG36ErkNc9f58mjUeB8JcCRKNr1jQu" ("serv..1" "args") [x y] $stream) (next y))) (next x)))) (call "12D3KooWPeUMWXdtEveZonecW8hhFdt4wfT4AyYpp4yrFGti7w5m" ("" "complete") []))

View File

@ -1 +0,0 @@
36gwouRjGormaTzTsyzx7cbuLbsTMk31VoZZ7LA2DaLNHQBqWND4ThNfJUUCnbBhMvRLCJrfr9zW68QURik7zPnT

View File

@ -1 +0,0 @@
{"comment":"verifying many CIDs for many peers"}

View File

@ -1 +0,0 @@
(seq (seq (call "12D3KooWPeUMWXdtEveZonecW8hhFdt4wfT4AyYpp4yrFGti7w5m" ("..0" "init") [] data) (fold data p (seq (fold data x (seq (fold data y (seq (call p ("serv..1" "args") [p x y] $stream) (next y))) (next x))) (next p)))) (seq (call "12D3KooWHNgtEsSgAQukzihG36ErkNc9f58mjUeB8JcCRKNr1jQu" ("..2" "collect") []) (call "12D3KooWPeUMWXdtEveZonecW8hhFdt4wfT4AyYpp4yrFGti7w5m" ("" "complete") [])))

View File

@ -1 +0,0 @@
4nWRt5MyWTEjBJEYaKokky9SS7T5Vms9v2VfiFwAwNaW1E9FVWAFV8sKxMy7yLCkm5zKhkV6cRKwWAGTcVMvvEac

View File

@ -1,3 +0,0 @@
{
"comment": "5 peers of network are discovered"
}

View File

@ -1 +0,0 @@
4nWRt5MyWTEjBJEYaKokky9SS7T5Vms9v2VfiFwAwNaW1E9FVWAFV8sKxMy7yLCkm5zKhkV6cRKwWAGTcVMvvEac

View File

@ -1,3 +0,0 @@
{
"comment": "Running very long AIR script with lot of variables and assignments"
}

File diff suppressed because one or more lines are too long

View File

@ -15,3 +15,4 @@ cid = { version = "0.10.1", default-features = false, features = ["std"] }
multihash = { version = "0.18.1", default-features = false, features = ["multihash-impl", "std", "sha2"] }
serde = { version = "1.0.164", features = ["derive"] }
serde_json = "1.0.95"
sha2 = "0.10.7"

View File

@ -30,6 +30,7 @@ use serde::Deserialize;
use serde::Serialize;
use std::fmt;
use std::io::BufWriter;
use std::marker::PhantomData;
#[derive(Serialize, Deserialize)]
@ -51,6 +52,7 @@ impl<T: ?Sized> Clone for CID<T> {
Self(self.0.clone(), self.1)
}
}
impl<T: ?Sized> fmt::Debug for CID<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("CID").field(&self.0).finish()
@ -121,7 +123,26 @@ impl std::error::Error for CidCalculationError {
}
/// Calculate a CID of JSON-serialized value.
pub fn value_to_json_cid<Val: Serialize>(value: &Val) -> Result<CID<Val>, CidCalculationError> {
let data = serde_json::to_vec(value)?;
Ok(json_data_cid(&data))
// TODO we might refactor this to `SerializationFormat` trait
// that both transform data to binary/text form (be it JSON, CBOR or something else)
// and produces CID too
pub fn value_to_json_cid<Val: Serialize + ?Sized>(
value: &Val,
) -> Result<CID<Val>, CidCalculationError> {
use cid::Cid;
use multihash::{Code, MultihashDigest};
use sha2::Digest;
let mut hasher = sha2::Sha256::new();
serde_json::to_writer(BufWriter::with_capacity(8 * 1024, &mut hasher), value)?;
let hash = hasher.finalize();
let digest = Code::Sha2_256
.wrap(&hash)
.expect("can't happend: incorrect hash length");
// seems to be better than RAW_CODEC = 0x55
const JSON_CODEC: u64 = 0x0200;
let cid = Cid::new_v1(JSON_CODEC, digest);
Ok(CID::new(cid.to_string()))
}

View File

@ -21,6 +21,7 @@ air-interpreter-cid = { version = "0.3.0", path = "../interpreter-cid" }
air-interpreter-signatures = { version = "0.1.1", path = "../interpreter-signatures" }
polyplets = { version = "0.5.1", path = "../polyplets" }
fluence-keypair = { version = "0.10.1", default-features = false }
serde = {version = "1.0.164", features = ["derive", "rc"]}
serde_json = "1.0.95"
semver = { version = "1.0.17", features = ["serde"] }
@ -28,3 +29,4 @@ once_cell = "1.17.1"
tracing = "0.1.37"
newtype_derive = "0.1.6"
num-traits = "0.2.15"
thiserror = "1.0.40"

View File

@ -0,0 +1,111 @@
/*
* Copyright 2023 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::CidStore;
use crate::CidStoreVerificationError;
use crate::CanonCidAggregate;
use crate::CanonResultCidAggregate;
use crate::JValue;
use crate::ServiceResultCidAggregate;
use polyplets::SecurityTetraplet;
use serde::Deserialize;
use serde::Serialize;
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CidInfo {
/// Map CID to value.
pub value_store: CidStore<JValue>,
/// Map CID to a tetraplet.
pub tetraplet_store: CidStore<SecurityTetraplet>,
/// Map CID to a canon element value.
pub canon_element_store: CidStore<CanonCidAggregate>,
/// Map CID to a canon result.
pub canon_result_store: CidStore<CanonResultCidAggregate>,
/// Map CID to a service result aggregate.
pub service_result_store: CidStore<ServiceResultCidAggregate>,
}
impl CidInfo {
#[tracing::instrument(skip_all)]
pub fn verify(&self) -> Result<(), CidStoreVerificationError> {
self.verify_value_store()?;
self.verify_tetraplet_store()?;
self.verify_canon_result_store()?;
self.verify_service_result_store()?;
Ok(())
}
fn verify_value_store(&self) -> Result<(), CidStoreVerificationError> {
self.value_store.verify()
}
fn verify_tetraplet_store(&self) -> Result<(), CidStoreVerificationError> {
self.tetraplet_store.verify()
}
fn verify_service_result_store(&self) -> Result<(), CidStoreVerificationError> {
self.service_result_store.verify()?;
for (serv_cid, serv_result) in self.service_result_store.iter() {
self.tetraplet_store
.check_reference(serv_cid, &serv_result.tetraplet_cid)?;
self.value_store
.check_reference(serv_cid, &serv_result.value_cid)?;
}
Ok(())
}
fn verify_canon_result_store(&self) -> Result<(), CidStoreVerificationError> {
self.canon_element_store.verify()?;
self.canon_result_store.verify()?;
for (canon_cid, canon_result) in self.canon_result_store.iter() {
for val in &canon_result.values {
self.canon_element_store.check_reference(canon_cid, val)?;
}
self.tetraplet_store
.check_reference(canon_cid, &canon_result.tetraplet)?;
}
for (element_cid, canon_element) in self.canon_element_store.iter() {
self.tetraplet_store
.check_reference(element_cid, &canon_element.tetraplet)?;
self.value_store
.check_reference(element_cid, &canon_element.value)?;
match &canon_element.provenance {
crate::Provenance::Literal => {}
crate::Provenance::ServiceResult { cid } => {
self.service_result_store
.check_reference(element_cid, cid)?;
}
crate::Provenance::Canon { cid } => {
self.canon_result_store.check_reference(element_cid, cid)?;
}
}
}
Ok(())
}
}

View File

@ -21,6 +21,7 @@ use air_interpreter_cid::CidCalculationError;
use air_interpreter_cid::CID;
use serde::Deserialize;
use serde::Serialize;
use thiserror::Error as ThisError;
use std::{collections::HashMap, rc::Rc};
@ -45,6 +46,60 @@ impl<Val> CidStore<Val> {
pub fn len(&self) -> usize {
self.0.len()
}
pub fn iter(&self) -> impl Iterator<Item = (&Rc<CID<Val>>, &Rc<Val>)> {
self.0.iter()
}
pub fn check_reference<Src>(
&self,
_source_cid: &CID<Src>,
target_cid: &Rc<CID<Val>>,
) -> Result<(), CidStoreVerificationError> {
self.0
.get(target_cid)
.ok_or_else(|| CidStoreVerificationError::MissingReference {
source_type_name: std::any::type_name::<Src>(),
target_type_name: std::any::type_name::<Val>(),
target_cid_repr: (**target_cid).clone().into_inner(),
})?;
Ok(())
}
}
impl<Val: Serialize> CidStore<Val> {
pub fn verify(&self) -> Result<(), CidStoreVerificationError> {
for (cid, value) in &self.0 {
let expected_cid = value_to_json_cid::<Val>(value)?;
if expected_cid != **cid {
return Err(CidStoreVerificationError::MismatchError {
type_name: std::any::type_name::<Val>(),
cid_repr: (**cid).clone().into_inner(),
});
}
}
Ok(())
}
}
#[derive(ThisError, Debug)]
pub enum CidStoreVerificationError {
#[error("Failed to recalculate CID during the verification: {0}")]
CidCalculationError(#[from] CidCalculationError),
#[error("Value mismatch in the {type_name:?} store for CID {cid_repr:?}")]
MismatchError {
// nb: type_name is std::any::type_name() result that may be inconsistent between the Rust compiler versions
type_name: &'static str,
cid_repr: String,
},
#[error("Reference CID {target_cid_repr:?} from type {source_type_name:?} to {target_type_name:?} was not found")]
MissingReference {
source_type_name: &'static str,
target_type_name: &'static str,
target_cid_repr: String,
},
}
impl<Val> Default for CidStore<Val> {

View File

@ -61,15 +61,11 @@ impl CallResult {
CallResult::Failed(service_result_agg_cid)
}
pub fn get_cid(&self) -> Option<Rc<CID<ServiceResultCidAggregate>>> {
pub fn get_cid(&self) -> Option<&Rc<CID<ServiceResultCidAggregate>>> {
match self {
CallResult::RequestSentBy(_) => None,
CallResult::Executed(executed) => match executed {
ValueRef::Scalar(cid) => Some(cid.clone()),
ValueRef::Stream { cid, .. } => Some(cid.clone()),
ValueRef::Unused(_) => None,
},
CallResult::Failed(cid) => Some(cid.clone()),
CallResult::Executed(executed) => executed.get_cid(),
CallResult::Failed(cid) => Some(cid),
}
}
}
@ -214,6 +210,16 @@ impl std::fmt::Display for ExecutedState {
}
}
impl ValueRef {
pub(crate) fn get_cid(&self) -> Option<&Rc<CID<ServiceResultCidAggregate>>> {
match self {
ValueRef::Scalar(cid) => Some(cid),
ValueRef::Stream { cid, .. } => Some(cid),
ValueRef::Unused(_) => None,
}
}
}
impl std::fmt::Display for ValueRef {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {

View File

@ -14,16 +14,14 @@
* limitations under the License.
*/
use crate::cid_store::CidStore;
use crate::CanonCidAggregate;
use crate::CanonResultCidAggregate;
pub(crate) mod errors;
pub mod verification;
use crate::CidInfo;
use crate::ExecutionTrace;
use crate::JValue;
use crate::ServiceResultCidAggregate;
use air_interpreter_signatures::SignatureStore;
use air_utils::measure;
use polyplets::SecurityTetraplet;
use serde::Deserialize;
use serde::Serialize;
@ -121,21 +119,3 @@ impl Versions {
}
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CidInfo {
/// Map CID to value.
pub value_store: CidStore<JValue>,
/// Map CID to a tetraplet.
pub tetraplet_store: CidStore<SecurityTetraplet>,
/// Map CID to a canon element value.
pub canon_element_store: CidStore<CanonCidAggregate>,
/// Map CID to a canon result.
pub canon_result_store: CidStore<CanonResultCidAggregate>,
/// Map CID to a service result aggregate.
pub service_result_store: CidStore<ServiceResultCidAggregate>,
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2023 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 thiserror::Error as ThisError;
#[derive(Debug, ThisError)]
pub enum DataVerifierError {
#[error(transparent)]
MalformedKey(fluence_keypair::error::DecodingError),
#[error(transparent)]
MalformedSignature(fluence_keypair::error::DecodingError),
#[error("peer_id doens't match any available public key: {0:?}")]
PeerIdNotFound(String),
#[error("signature mismatch for {peer_id:?}: {error:?}, values: CIDS: {cids:?}")]
SignatureMismatch {
error: Box<fluence_keypair::error::VerificationError>,
cids: Vec<Box<str>>,
peer_id: String,
},
#[error(
"inconsistent CID multisets on merge for peer {peer_id:?}, prev: {larger_cids:?}, current: {smaller_cids:?}"
)]
MergeMismatch {
peer_id: String,
larger_cids: Vec<Box<str>>,
smaller_cids: Vec<Box<str>>,
},
}

View File

@ -0,0 +1,258 @@
/*
* Copyright 2023 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.
*/
pub use super::errors::DataVerifierError;
use crate::CanonResult;
use crate::CidInfo;
use crate::ExecutedState;
use crate::ExecutionTrace;
use crate::InterpreterData;
use air_interpreter_cid::CID;
use air_interpreter_signatures::PublicKey;
use air_interpreter_signatures::Signature;
use air_interpreter_signatures::SignatureStore;
use std::collections::HashMap;
use std::rc::Rc;
const CANNOT_HAPPEN_IN_VERIFIED_CID_STORE: &str = "cannot happen in a checked CID store";
/// An util for verificating particular data's signatures.
pub struct DataVerifier<'data> {
// a map from peer_id to peer's info (public key, signature, CIDS)
grouped_cids: HashMap<Box<str>, PeerInfo<'data>>,
salt: &'data str,
}
impl<'data> DataVerifier<'data> {
// it can be further optimized if only required parts are passed
// SignatureStore is not used elsewhere
pub fn new(data: &'data InterpreterData, salt: &'data str) -> Result<Self, DataVerifierError> {
// it contains signature too; if we try to add a value to a peer w/o signature, it is an immediate error
let mut grouped_cids: HashMap<Box<str>, PeerInfo<'data>> = data
.signatures
.iter()
.map(|(public_key, signature)| {
(
public_key.to_peer_id().to_string().into(),
PeerInfo::new(public_key, signature),
)
})
.collect();
// fill PeerInfo's `cids` field, checking for peer IDs without a key
collect_peers_cids_from_trace(&data.trace, &data.cid_info, &mut grouped_cids)?;
// sort cids for canonicalization
for peer_info in grouped_cids.values_mut() {
peer_info.cids.sort_unstable();
}
Ok(Self { grouped_cids, salt })
}
/// Verify each peers' signatures.
pub fn verify(&self) -> Result<(), DataVerifierError> {
for peer_info in self.grouped_cids.values() {
peer_info
.public_key
.verify(&peer_info.cids, self.salt, peer_info.signature)
.map_err(|error| DataVerifierError::SignatureMismatch {
error: error.into(),
cids: peer_info.cids.clone(),
peer_id: peer_info.public_key.to_peer_id().to_string(),
})?;
}
Ok(())
}
/// For each peer, merge previous and current CID multisets by determining the largest set.
///
/// This code uses an invariant: peer's multiset of produced CIDs is always a superset of
/// previous invocation's multiset:
///
/// A_0 ⊆ A_1 ⊆ ... ⊆ A_n.
///
/// So, the largest multiset is selected as the result of merging, the invariant is checked,
/// and a error is returned if it is violated.
///
/// If the multisets are of same size, they have to be equal.
// TODO enforce merging only verified sets
// The result is same regardless argument order, so "prevous/current" terminology
// is not used deliberately.
pub fn merge(mut self, other: Self) -> Result<SignatureStore, DataVerifierError> {
use std::collections::hash_map::Entry::*;
for (other_peer_pk, mut other_info) in other.grouped_cids {
let our_info = self.grouped_cids.entry(other_peer_pk);
match our_info {
Occupied(mut our_info_ent) => {
debug_assert_eq!(other_info.public_key, our_info_ent.get().public_key);
if our_info_ent.get().cids.len() < other_info.cids.len() {
// the merged map contains the largest set for each peer_id
//
// this code assumes that a peer only adds CIDs to its set, so CID multisets
// are growing-only; but it is additionally checked below
// so, we get a largest set as merged one
std::mem::swap(our_info_ent.get_mut(), &mut other_info);
}
// nb: if length are equal, sets should be equal, and any of them
// should be used; if they are not equal, check_cid_multiset_consistency
// will detect it.
let larger_info = our_info_ent.get();
let smaller_info = &other_info;
check_cid_multiset_invariant(larger_info, smaller_info)?;
}
Vacant(ent) => {
ent.insert(other_info);
}
}
}
let mut store = SignatureStore::new();
for peer_info in self.grouped_cids.into_values() {
store.put(peer_info.public_key.clone(), peer_info.signature.clone())
}
Ok(store)
}
}
fn collect_peers_cids_from_trace<'data>(
trace: &'data ExecutionTrace,
cid_info: &'data CidInfo,
grouped_cids: &mut HashMap<Box<str>, PeerInfo<'data>>,
) -> Result<(), DataVerifierError> {
for elt in trace {
match elt {
ExecutedState::Call(ref call) => {
let cid = call.get_cid();
if let Some(cid) = cid {
// TODO refactor
let service_result = cid_info
.service_result_store
.get(cid)
.expect(CANNOT_HAPPEN_IN_VERIFIED_CID_STORE);
let tetraplet = cid_info
.tetraplet_store
.get(&service_result.tetraplet_cid)
.expect(CANNOT_HAPPEN_IN_VERIFIED_CID_STORE);
let peer_pk = tetraplet.peer_pk.as_str();
try_push_cid(grouped_cids, peer_pk, cid)?;
}
}
ExecutedState::Canon(CanonResult::Executed(ref cid)) => {
// TODO refactor
let canon_result = cid_info
.canon_result_store
.get(cid)
.expect(CANNOT_HAPPEN_IN_VERIFIED_CID_STORE);
let tetraplet = cid_info
.tetraplet_store
.get(&canon_result.tetraplet)
.expect(CANNOT_HAPPEN_IN_VERIFIED_CID_STORE);
let peer_pk = tetraplet.peer_pk.as_str();
try_push_cid(grouped_cids, peer_pk, cid)?;
}
_ => {}
};
}
Ok(())
}
fn try_push_cid<T>(
grouped_cids: &mut HashMap<Box<str>, PeerInfo<'_>>,
peer_pk: &str,
cid: &Rc<CID<T>>,
) -> Result<(), DataVerifierError> {
match grouped_cids.get_mut(peer_pk) {
Some(peer_info) => {
peer_info.cids.push((**cid).clone().into_inner().into());
Ok(())
}
None => Err(DataVerifierError::PeerIdNotFound(peer_pk.into())),
}
}
/// Safety check for malicious peer that returns inconsistent CID multisets,
/// i.e. non-increasing multisets.
fn check_cid_multiset_invariant(
larger_pair: &PeerInfo<'_>,
smaller_pair: &PeerInfo<'_>,
) -> Result<(), DataVerifierError> {
let larger_cids = &larger_pair.cids;
let smaller_cids = &smaller_pair.cids;
let larger_count_map = to_count_map(larger_cids);
let smaller_count_map = to_count_map(smaller_cids);
if is_multisubset(larger_count_map, smaller_count_map) {
Ok(())
} else {
let peer_id = smaller_pair.public_key.to_peer_id().to_string();
Err(DataVerifierError::MergeMismatch {
peer_id,
larger_cids: larger_cids.clone(),
smaller_cids: smaller_cids.clone(),
})
}
}
fn to_count_map(cids: &Vec<Box<str>>) -> HashMap<&str, usize> {
let mut count_map = HashMap::<_, usize>::new();
for cid in cids {
// the counter can't overflow, the memory will overflow first
*count_map.entry(&**cid).or_default() += 1;
}
count_map
}
fn is_multisubset(
larger_count_set: HashMap<&str, usize>,
smaller_count_set: HashMap<&str, usize>,
) -> bool {
for (cid, &smaller_count) in &smaller_count_set {
debug_assert!(smaller_count > 0);
let larger_count = larger_count_set.get(cid).cloned().unwrap_or_default();
if larger_count < smaller_count {
return false;
}
}
true
}
struct PeerInfo<'data> {
/// A peer's public key.
public_key: &'data PublicKey,
/// A peer's signature.
signature: &'data Signature,
/// Sorted vector of CIDs that belong to the peer.
cids: Vec<Box<str>>,
}
impl<'data> PeerInfo<'data> {
fn new(public_key: &'data PublicKey, signature: &'data Signature) -> Self {
Self {
public_key,
signature,
cids: vec![],
}
}
}

View File

@ -26,6 +26,7 @@
unreachable_patterns
)]
mod cid_info;
mod cid_store;
mod executed_state;
mod generation_idx;
@ -33,6 +34,7 @@ mod interpreter_data;
mod trace;
mod trace_pos;
pub use cid_info::*;
pub use cid_store::*;
pub use executed_state::*;
pub use generation_idx::*;

View File

@ -88,3 +88,13 @@ impl PartialEq<Vec<ExecutedState>> for ExecutionTrace {
&self.0 == other
}
}
impl<'trace> IntoIterator for &'trace ExecutionTrace {
type Item = &'trace ExecutedState;
type IntoIter = <&'trace Vec<ExecutedState> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}

View File

@ -14,7 +14,7 @@ categories = ["wasm"]
air-interpreter-cid = { version = "0.3.0", path = "../interpreter-cid" }
fluence-keypair = { version = "0.10.1", default-features = false }
serde = { version = "1.0.164", features = ["derive"] }
serde_json = "1.0.96"
bs58 = "0.5.0"
borsh = "0.10.3"
borsh-derive = "0.10.3"
serde = { version = "1.0.164", features = ["derive"] }

View File

@ -27,14 +27,17 @@
)]
mod sede;
mod stores;
mod trackers;
use air_interpreter_cid::CID;
use fluence_keypair::error::SigningError;
use fluence_keypair::KeyPair;
pub use crate::stores::*;
pub use crate::trackers::*;
pub use fluence_keypair::KeyPair;
use borsh::BorshSerialize;
use serde::{Deserialize, Serialize};
use std::borrow::Borrow;
use std::collections::HashMap;
use std::hash::Hash;
use std::ops::Deref;
@ -52,16 +55,15 @@ pub struct PublicKey(
);
impl PublicKey {
pub fn verify<T: Serialize + ?Sized>(
pub fn verify<T: BorshSerialize + ?Sized>(
&self,
value: &T,
salt: &str,
signature: &fluence_keypair::Signature,
) -> Result<(), fluence_keypair::error::VerificationError> {
let pk = &**self;
let serialized_value =
serde_json::to_vec(value).expect("default serialization shouldn't fail");
let serialized_value = SaltedData::new(&value, salt).serialize();
pk.verify(&serialized_value, signature)
}
}
@ -126,75 +128,18 @@ impl From<Signature> for fluence_keypair::Signature {
}
}
#[derive(Debug, Default)]
pub struct SignatureTracker {
// from peer id to CID strings
peer_to_cids: HashMap<Box<str>, Vec<Box<str>>>,
}
#[derive(borsh_derive::BorshSerialize)]
pub(crate) struct SaltedData<'ctx, Data: BorshSerialize>(&'ctx Data, &'ctx str);
impl SignatureTracker {
pub fn new() -> Self {
Default::default()
impl<'ctx, Data: BorshSerialize> SaltedData<'ctx, Data> {
pub(crate) fn new(data: &'ctx Data, salt: &'ctx str) -> Self {
Self(data, salt)
}
pub fn register<T>(&mut self, peer_id: impl Into<Box<str>>, cid: &CID<T>) {
self.peer_to_cids
.entry(peer_id.into())
.or_default()
.push(cid.clone().into_inner().into());
}
pub fn into_signature(
&mut self,
peer_id: &str,
signer: &KeyPair,
) -> Result<Signature, SigningError> {
let mut cids = self.peer_to_cids.get(peer_id).cloned().unwrap_or_default();
cids.sort_unstable();
pub(crate) fn serialize(&self) -> Vec<u8> {
// TODO make pluggable serialization
// TODO it will be useful for CID too
// TODO please note that using serde::Serializer is not enough
let serialized_cids =
serde_json::to_string(&cids).expect("default serialization shouldn't fail");
signer.sign(serialized_cids.as_bytes()).map(Signature::new)
}
}
/// A dictionary-like structure that stores peer public keys and their particle data signatures.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SignatureStore<Key: Hash + Eq = PublicKey, Sign = Signature>(HashMap<Key, Sign>);
impl<Key: Hash + Eq, Sign> SignatureStore<Key, Sign> {
pub fn new() -> Self {
Default::default()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn get<Q>(&self, peer_pk: &Q) -> Option<&Sign>
where
Key: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
self.0.get(peer_pk)
}
pub fn put(&mut self, peer_pk: Key, signature: Sign) {
self.0.insert(peer_pk, signature);
}
pub fn merge(prev: Self, _current: Self) -> Self {
// TODO STUB
prev
}
}
impl<Key: Hash + Eq, Sign> Default for SignatureStore<Key, Sign> {
fn default() -> Self {
Self(Default::default())
borsh::to_vec(&self).expect("borsh serializer shouldn't fail")
}
}

View File

@ -0,0 +1,60 @@
/*
* Copyright 2023 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::PublicKey;
use crate::Signature;
use serde::{Deserialize, Serialize};
use std::borrow::Borrow;
use std::collections::HashMap;
use std::hash::Hash;
/// A dictionary-like structure that stores peer public keys and their particle data signatures.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SignatureStore<Key: Hash + Eq = PublicKey, Sign = Signature>(HashMap<Key, Sign>);
impl<Key: Hash + Eq, Sign> SignatureStore<Key, Sign> {
pub fn new() -> Self {
Default::default()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn get<Q>(&self, peer_pk: &Q) -> Option<&Sign>
where
Key: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
self.0.get(peer_pk)
}
pub fn put(&mut self, peer_pk: Key, signature: Sign) {
self.0.insert(peer_pk, signature);
}
pub fn iter(&self) -> <&HashMap<Key, Sign> as IntoIterator>::IntoIter {
self.0.iter()
}
}
impl<Key: Hash + Eq, Sign> Default for SignatureStore<Key, Sign> {
fn default() -> Self {
Self(Default::default())
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright 2023 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::SaltedData;
use air_interpreter_cid::CID;
use fluence_keypair::error::SigningError;
use fluence_keypair::KeyPair;
use std::rc::Rc;
/// The tracker that collect current peer's CIDs only.
#[derive(Debug)]
pub struct PeerCidTracker {
current_peer_id: Rc<String>,
cids: Vec<Box<str>>,
}
impl PeerCidTracker {
pub fn new(current_peer_id: impl Into<Rc<String>>) -> Self {
Self {
current_peer_id: current_peer_id.into(),
cids: vec![],
}
}
pub fn register<T>(&mut self, peer: &str, cid: &CID<T>) {
if peer == *self.current_peer_id {
self.cids.push(cid.clone().into_inner().into())
}
}
pub fn gen_signature(
&self,
salt: &str,
keypair: &KeyPair,
) -> Result<crate::Signature, SigningError> {
sign_cids(self.cids.clone(), salt, keypair)
}
}
fn sign_cids(
mut cids: Vec<Box<str>>,
salt: &str,
keypair: &KeyPair,
) -> Result<crate::Signature, SigningError> {
cids.sort_unstable();
let serialized_cids = SaltedData::new(&cids, salt).serialize();
keypair.sign(&serialized_cids).map(crate::Signature::new)
}

View File

@ -89,6 +89,20 @@ pub fn fallible_call_service(fallible_service_id: impl Into<String>) -> CallServ
})
}
pub fn fallible_call_service_by_arg(arg: impl Into<JValue>) -> CallServiceClosure {
let arg = arg.into();
Box::new(move |params| -> CallServiceResult {
// return a error for service with specific arg
if params.arguments.get(0) == Some(&arg) {
CallServiceResult::err(1, json!("failed result from fallible_call_service_by_arg"))
} else {
// return success for services with other arg
CallServiceResult::ok(json!("success result from fallible_call_service_by_arg"))
}
})
}
pub type ArgTetraplets = Vec<Vec<SecurityTetraplet>>;
pub fn tetraplet_host_function(

View File

@ -24,6 +24,7 @@ use super::ParResult;
use super::Sender;
use super::TracePos;
use super::ValueRef;
use crate::key_utils::at;
use crate::FoldLore;
use crate::FoldResult;
use crate::FoldSubTraceLore;
@ -311,6 +312,11 @@ impl ExecutedCallBuilder {
self
}
pub fn peer_name(mut self, peer_name: impl AsRef<str>) -> Self {
self.tetraplet.peer_pk = at(peer_name.as_ref());
self
}
pub fn service(mut self, service_id: impl Into<String>) -> Self {
self.tetraplet.service_id = service_id.into();
self

View File

@ -38,3 +38,7 @@ pub fn derive_dummy_keypair(seed: &str) -> (KeyPair, String) {
let peer_id = keypair.public().to_peer_id().to_string();
(keypair, peer_id)
}
pub fn at(peer_name: &str) -> String {
derive_dummy_keypair(peer_name).1
}

View File

@ -201,7 +201,7 @@ fn print_canon_values(data: &InterpreterData, canon_result: &CanonResult) {
#[macro_export]
macro_rules! rc {
($expr:expr) => {
std::rc::Rc::new($expr)
::std::rc::Rc::new($expr)
};
}
@ -215,3 +215,24 @@ pub fn is_interpreter_succeded(result: &RawAVMOutcome) -> bool {
pub fn check_error(result: &RawAVMOutcome, error: impl ToErrorCode + ToString) -> bool {
result.ret_code == error.to_error_code() && result.error_message == error.to_string()
}
#[macro_export]
macro_rules! assert_error_eq {
($result:expr, $error:expr $(,)?) => {{
let result: &::air_test_utils::RawAVMOutcome = $result;
let error = $error;
::std::assert_eq!(
(result.ret_code, &result.error_message),
(::air::ToErrorCode::to_error_code(&error), &::std::string::ToString::to_string(&error))
);
}};
($result:expr, $error:expr, $($arg:tt)+) => {{
let result: &::air_test_utils::RawAVMOutcome = $result;
let error = $error;
::std::assert_eq!(
(result.ret_code, &result.error_message),
(::air::ToErrorCode::to_error_code(&error), &::std::string::ToString::to_string(&error)),
$($arg)+
);
}};
}

View File

@ -73,4 +73,8 @@ impl AirRunner for NativeAirRunner {
Ok(outcome)
}
fn get_current_peer_id(&self) -> &str {
&self.current_peer_id
}
}

View File

@ -49,12 +49,14 @@ pub trait AirRunner {
key_pair: &KeyPair,
particle_id: String,
) -> Result<RawAVMOutcome, Box<dyn std::error::Error>>;
fn get_current_peer_id(&self) -> &str;
}
pub struct TestRunner<R = DefaultAirRunner> {
pub runner: R,
call_service: CallServiceClosure,
keypair: KeyPair,
pub keypair: KeyPair,
}
#[derive(Debug, Default, Clone)]
@ -179,6 +181,20 @@ pub fn create_custom_avm<R: AirRunner>(
}
}
pub fn create_avm_with_key<R: AirRunner>(
keypair: KeyPair,
call_service: CallServiceClosure,
) -> TestRunner<R> {
let current_peer_id = keypair.public().to_peer_id().to_string();
let runner = R::new(current_peer_id);
TestRunner {
runner,
call_service,
keypair,
}
}
impl TestRunParameters {
pub fn new(
init_peer_id: impl Into<String>,
@ -215,6 +231,11 @@ impl TestRunParameters {
..<_>::default()
}
}
pub fn with_particle_id(mut self, particle_id: impl Into<String>) -> Self {
self.particle_id = particle_id.into();
self
}
}
#[cfg(test)]

View File

@ -92,6 +92,10 @@ impl AirRunner for WasmAirRunner {
particle_id,
)?)
}
fn get_current_peer_id(&self) -> &str {
&self.current_peer_id
}
}
/// WASM runner that runs release build form benchmarking.
@ -147,4 +151,8 @@ impl AirRunner for ReleaseWasmAirRunner {
particle_id,
)?)
}
fn get_current_peer_id(&self) -> &str {
&self.current_peer_id
}
}

View File

@ -22,6 +22,8 @@ strum = { version="0.24.1", features=["derive"] }
nom = "7.1.3"
nom_locate = "4.1.0"
serde_json = "1.0.95"
regex = "1.8.4"
air-interpreter-signatures = { version = "0.1.1", path = "../air-lib/interpreter-signatures" }
[dev-dependencies]
maplit = "1.0.2"

View File

@ -22,20 +22,29 @@ use crate::{
services::{services_to_call_service_closure, MarineServiceHandle, NetworkServices},
};
use air_interpreter_signatures::KeyPair;
use air_test_utils::{
test_runner::{create_custom_avm, AirRunner, DefaultAirRunner, TestRunParameters, TestRunner},
key_utils::derive_dummy_keypair,
test_runner::{
create_avm_with_key, AirRunner, DefaultAirRunner, TestRunParameters, TestRunner,
},
RawAVMOutcome,
};
use std::{borrow::Borrow, cell::RefCell, collections::HashMap, hash::Hash, rc::Rc};
use std::{borrow::Borrow, cell::RefCell, collections::HashMap, hash::Hash, ops::Deref, rc::Rc};
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct PeerId(Rc<str>);
impl PeerId {
pub fn new<'any>(peer_id: impl Into<&'any str>) -> Self {
Self(peer_id.into().into())
}
pub fn from_keypair(keypair: &KeyPair) -> Self {
Self::new(keypair.public().to_peer_id().to_string().as_str())
}
}
impl From<String> for PeerId {
fn from(source: String) -> Self {
@ -49,12 +58,26 @@ impl From<&str> for PeerId {
}
}
impl From<&PeerId> for PeerId {
fn from(value: &PeerId) -> Self {
value.clone()
}
}
impl Borrow<str> for PeerId {
fn borrow(&self) -> &str {
&self.0
}
}
impl Deref for PeerId {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub type Data = Vec<u8>;
pub struct Peer<R> {
@ -63,14 +86,19 @@ pub struct Peer<R> {
}
impl<R: AirRunner> Peer<R> {
pub fn new(peer_id: impl Into<PeerId>, services: Rc<[MarineServiceHandle]>) -> Self {
let peer_id = Into::into(peer_id);
pub fn new(keypair: KeyPair, services: Rc<[MarineServiceHandle]>) -> Self {
let call_service = services_to_call_service_closure(services);
let runner = create_custom_avm(call_service, &*peer_id.0);
let runner = create_avm_with_key::<R>(keypair, call_service);
let peer_id = runner.runner.get_current_peer_id().into();
Self { peer_id, runner }
}
pub fn get_peer_id(&self) -> &PeerId {
&self.peer_id
}
pub(crate) fn invoke(
&mut self,
air: impl Into<String>,
@ -85,6 +113,10 @@ impl<R: AirRunner> Peer<R> {
}
res
}
pub fn get_keypair(&self) -> &KeyPair {
&self.runner.keypair
}
}
impl<R: AirRunner> std::fmt::Debug for Peer<R> {
@ -98,7 +130,12 @@ impl<R: AirRunner> std::fmt::Debug for Peer<R> {
pub struct Network<R = DefaultAirRunner> {
peers: RefCell<HashMap<PeerId, Rc<RefCell<PeerEnv<R>>>>>,
// Default peer services.
services: Rc<NetworkServices>,
// Resolves human-readable peer names to real peer IDs.
resolver: RefCell<HashMap<PeerId, PeerId>>,
}
// it is implemented only for the default runner for compatibility reasons
@ -112,15 +149,16 @@ impl Network<DefaultAirRunner> {
impl<R: AirRunner> Network<R> {
pub fn new(
peers: impl Iterator<Item = impl Into<PeerId>>,
named_peers: impl Iterator<Item = impl Into<PeerId>>,
common_services: Vec<MarineServiceHandle>,
) -> Rc<Self> {
let network = Rc::new(Self {
peers: Default::default(),
services: NetworkServices::new(common_services).into(),
resolver: Default::default(),
});
for peer_id in peers {
network.ensure_peer(peer_id);
for peer_name in named_peers {
network.ensure_named_peer(peer_name);
}
network
}
@ -145,15 +183,23 @@ impl<R: AirRunner> Network<R> {
self.insert_peer_env_entry(peer_id, peer_env);
}
pub fn ensure_peer(self: &Rc<Self>, peer_id: impl Into<PeerId>) {
let peer_id = peer_id.into();
let exists = {
let peers_ref = self.peers.borrow();
peers_ref.contains_key(&peer_id)
};
if !exists {
let peer = Peer::new(peer_id, self.services.get_services());
pub fn ensure_named_peer(self: &Rc<Self>, name: impl Into<PeerId>) -> PeerId {
use std::collections::hash_map::Entry;
let name = name.into();
match self.resolver.borrow_mut().entry(name.clone()) {
Entry::Occupied(entry) => entry.get().clone(),
Entry::Vacant(empty) => {
let (keypair, _) = derive_dummy_keypair(&name);
let peer = Peer::new(keypair, self.services.get_services());
let peer_id = peer.get_peer_id().clone();
self.add_peer(peer);
empty.insert(peer_id.clone());
peer_id
}
}
}
@ -165,77 +211,100 @@ impl<R: AirRunner> Network<R> {
}
fn insert_peer_env_entry(&self, peer_id: PeerId, peer_env: PeerEnv<R>) {
use std::collections::hash_map::Entry;
let mut peers_ref = self.peers.borrow_mut();
let peer_env = Rc::new(peer_env.into());
// It will be simplified with entry_insert stabilization
// https://github.com/rust-lang/rust/issues/65225
match peers_ref.entry(peer_id) {
std::collections::hash_map::Entry::Occupied(ent) => {
Entry::Occupied(ent) => {
let cell = ent.into_mut();
*cell = peer_env;
cell
}
std::collections::hash_map::Entry::Vacant(ent) => ent.insert(peer_env),
Entry::Vacant(ent) => ent.insert(peer_env),
};
}
pub fn set_peer_failed<Id>(&mut self, peer_id: &Id, failed: bool)
// TODO named peer
pub fn set_peer_failed<Id>(&mut self, name: &Id, failed: bool)
where
PeerId: Borrow<Id>,
PeerId: Borrow<Id> + for<'a> From<&'a Id>,
Id: Hash + Eq + ?Sized,
{
let peer_id = self.resolve_peer(name);
let mut peers_ref = self.peers.borrow_mut();
peers_ref
.get_mut(peer_id)
.get_mut::<PeerId>(&peer_id)
.expect("unknown peer")
.as_ref()
.borrow_mut()
.set_failed(failed);
}
pub fn fail_peer_for<Id>(&mut self, source_peer_id: &Id, target_peer_id: impl Into<PeerId>)
// TODO named peer
pub fn fail_peer_for<Id1, Id2>(&mut self, source_peer_name: &Id1, target_peer_name: &Id2)
where
PeerId: Borrow<Id>,
Id: Hash + Eq + ?Sized,
{
let mut peers_ref = self.peers.borrow_mut();
peers_ref
.get_mut(source_peer_id)
.expect("unknown peer")
.as_ref()
.borrow_mut()
.get_neighborhood_mut()
.set_target_unreachable(target_peer_id);
}
pub fn unfail_peer_for<Id1, Id2>(&mut self, source_peer_id: &Id1, target_peer_id: &Id2)
where
PeerId: Borrow<Id1>,
PeerId: Borrow<Id1> + for<'a> From<&'a Id1>,
Id1: Hash + Eq + ?Sized,
PeerId: Borrow<Id2>,
PeerId: Borrow<Id2> + for<'a> From<&'a Id2>,
Id2: Hash + Eq + ?Sized,
{
let source_peer_id = self.resolve_peer(source_peer_name);
let target_peer_id = self.resolve_peer(target_peer_name);
let mut peers_ref = self.peers.borrow_mut();
peers_ref
.get_mut(source_peer_id)
.get_mut::<PeerId>(&source_peer_id)
.expect("unknown peer")
.as_ref()
.borrow_mut()
.get_neighborhood_mut()
.unset_target_unreachable(target_peer_id);
.set_target_unreachable(&target_peer_id);
}
// TODO named peer
pub fn unfail_peer_for<Id1, Id2>(&mut self, source_peer_name: &Id1, target_peer_name: &Id2)
where
PeerId: Borrow<Id1> + for<'a> From<&'a Id1>,
Id1: Hash + Eq + ?Sized,
PeerId: Borrow<Id2> + for<'a> From<&'a Id2>,
Id2: Hash + Eq + ?Sized,
{
let source_peer_id = self.resolve_peer(source_peer_name);
let target_peer_id = self.resolve_peer(target_peer_name);
let mut peers_ref = self.peers.borrow_mut();
peers_ref
.get_mut(&source_peer_id)
.expect("unknown peer")
.as_ref()
.borrow_mut()
.get_neighborhood_mut()
.unset_target_unreachable(&target_peer_id);
}
// TODO there is some kind of unsymmetry between these methods and the fail/unfail:
// the latters panic on unknown peer; perhaps, it's OK
// TODO named peer
pub fn get_peer_env<Id>(&self, peer_id: &Id) -> Option<Rc<RefCell<PeerEnv<R>>>>
where
PeerId: Borrow<Id>,
PeerId: Borrow<Id> + for<'a> From<&'a Id>,
Id: Hash + Eq + ?Sized,
{
let peers_ref = self.peers.borrow();
peers_ref.get(peer_id).cloned()
}
pub fn get_named_peer_env<Id>(&self, peer_name: &Id) -> Option<Rc<RefCell<PeerEnv<R>>>>
where
PeerId: Borrow<Id> + for<'a> From<&'a Id>,
Id: Hash + Eq + ?Sized,
{
let peer_id: PeerId = self.resolve_peer(peer_name);
self.get_peer_env::<PeerId>(&peer_id)
}
pub(crate) fn get_services(&self) -> Rc<NetworkServices> {
self.services.clone()
}
@ -244,4 +313,16 @@ impl<R: AirRunner> Network<R> {
let peers_ref = self.peers.borrow();
peers_ref.keys().cloned().collect::<Vec<_>>().into_iter()
}
// TODO it sounds cool, but actually, name and PeerId should be
// distinct and have distinct API for working with a peer by name: &str
// and by PeerId
pub fn resolve_peer<Id>(&self, name: &Id) -> PeerId
where
PeerId: Borrow<Id> + for<'a> From<&'a Id>,
Id: Hash + Eq + ?Sized,
{
let resolver = self.resolver.borrow();
resolver.get(name).cloned().unwrap_or_else(|| (name).into())
}
}

Some files were not shown because too many files have changed in this diff Show More