/* * AquaVM Workflow Engine * * Copyright (C) 2024 Fluence DAO * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation version 3 of the * License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use air::ExecutionCidState; use air::UncatchableError; use air_interpreter_data::FoldSubTraceLore; use air_interpreter_data::ParResult; use air_interpreter_data::SubTraceDesc; use air_test_utils::prelude::*; use air_trace_handler::merger::ApResultError; use air_trace_handler::merger::CallResultError; use air_trace_handler::merger::CanonResultError; use air_trace_handler::merger::FoldResultError; use air_trace_handler::merger::MergeCtxType::Current; use air_trace_handler::merger::MergeCtxType::Previous; use air_trace_handler::merger::ResolvedFold; use air_trace_handler::merger::ResolvedSubTraceDescs; use air_trace_handler::KeeperError::*; use air_trace_handler::StateFSMError::*; use air_trace_handler::TraceHandlerError::KeeperError; use air_trace_handler::TraceHandlerError::MergeError; use air_trace_handler::TraceHandlerError::StateFSMError; use maplit::hashmap; #[tokio::test] async fn par_len_overflow() { let vm_peer_id_1 = "vm_peer_id_1"; let mut peer_vm_1 = create_avm(unit_call_service(), vm_peer_id_1).await; let script = format!( r#" (par (ap 42 some) (call "other" ("" "") [some] other) ) "# ); let error_left_pos_value = 1; let error_right_pos_value = PosType::MAX; let trace = vec![ executed_state::par(error_left_pos_value, error_right_pos_value), executed_state::request_sent_by(vm_peer_id_1), ]; let data = raw_data_from_trace(trace, <_>::default()); let result = call_vm!(peer_vm_1, <_>::default(), script, "", data); let expected_error = UncatchableError::TraceError { trace_error: StateFSMError(ParLenOverflow(ParResult::new( error_left_pos_value, error_right_pos_value, ))), instruction: "par".to_string(), }; assert!(check_error(&result, expected_error)); } #[tokio::test] async fn par_pos_overflow() { let vm_peer_id_1 = "vm_peer_id_1"; let mut peer_vm_1 = create_avm(unit_call_service(), vm_peer_id_1).await; let script = format!( r#" (par (ap 42 some) (call "other" ("" "") [some] other) ) "# ); let error_pos_value = PosType::MAX; let trace = vec![ executed_state::par(error_pos_value, error_pos_value), executed_state::request_sent_by(vm_peer_id_1), ]; let data = raw_data_from_trace(trace, <_>::default()); let result = call_vm!(peer_vm_1, <_>::default(), script, "", data); let expected_error = UncatchableError::TraceError { trace_error: StateFSMError(ParPosOverflow( ParResult::new(error_pos_value, error_pos_value), 1.into(), Previous, )), instruction: "par".to_string(), }; assert!(check_error(&result, expected_error)); } #[tokio::test] async fn par_len_underflow() { let vm_peer_id_1 = "vm_peer_id_1"; let mut peer_vm_1 = create_avm(unit_call_service(), vm_peer_id_1).await; let script = format!( r#" (par (ap 42 some) (call "other" ("" "") [some] other) ) "# ); let trace = vec![ executed_state::par(42, 1), executed_state::request_sent_by(vm_peer_id_1), ]; let data = raw_data_from_trace(trace, <_>::default()); let result = call_vm!(peer_vm_1, <_>::default(), script, "", data); let expected_error = UncatchableError::TraceError { trace_error: StateFSMError(ParLenUnderflow(ParResult::new(42, 1), 1, Current)), instruction: "par".to_string(), }; assert!(check_error(&result, expected_error)); } #[tokio::test] async fn set_subtrace_len_and_pos_failed() { let vm_peer_id_1 = "vm_peer_id_1"; let arg = json!([42, 43]); let mut peer_vm_1 = create_avm(set_variable_call_service(arg), vm_peer_id_1).await; let script = format!( r#" (par (call "vm_peer_id_1" ("" "") [] $s) (fold $s i (call "vm_peer_id_2" ("" "") [] a) (next i) ) ) "# ); let mut cid_state = ExecutionCidState::new(); let trace = vec![ executed_state::par(1, 2), 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), subtrace_desc(4, 0), )]), request_sent_by("vm_peer_id_1"), ]; let wrong_data = raw_data_from_trace(trace, cid_state); let result = call_vm!(peer_vm_1, <_>::default(), script, wrong_data, ""); let expected_error = UncatchableError::TraceError { trace_error: KeeperError(SetSubtraceLenAndPosFailed { requested_pos: 5.into(), requested_subtrace_len: 1, trace_len: 4, }), instruction: "fold $s i".to_string(), }; assert!(check_error(&result, expected_error)); } #[tokio::test] async fn no_element_at_position() { let vm_peer_id_1 = "vm_peer_id_1"; let mut peer_vm_1 = create_avm(unit_call_service(), vm_peer_id_1).await; let script = format!( r#" (par (call "vm_peer_id_1" ("" "") [] $s) (fold $s i (call "vm_peer_id_2" ("" "") [] a) (next i) ) ) "# ); let mut cid_state = ExecutionCidState::new(); let trace = vec![ executed_state::par(1, 2), 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), subtrace_desc(4, 0), )]), request_sent_by("vm_peer_id_1"), ]; let wrong_data = raw_data_from_trace(trace, cid_state); let result = call_vm!(peer_vm_1, <_>::default(), script, wrong_data, ""); let expected_error = UncatchableError::TraceError { trace_error: air_trace_handler::TraceHandlerError::KeeperError(NoElementAtPosition { position: 42.into(), trace_len: 4, }), instruction: "fold $s i".to_string(), }; assert!(check_error(&result, expected_error)); } #[tokio::test] async fn no_stream_state() { let vm_peer_id_1 = "vm_peer_id_1"; let arg = json!([42, 43]); let mut peer_vm_1 = create_avm(set_variable_call_service(arg), vm_peer_id_1).await; let script = format!( r#" (par (call "vm_peer_id_1" ("" "") [] $s) (fold $s i (call "vm_peer_id_2" ("" "") [] a) (next i) ) ) "# ); let mut tracker = ExecutionCidState::new(); 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, 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 subtrace_desc(4, 0), )]), wrong_state.clone(), ]; let wrong_data = raw_data_from_trace(trace, tracker); let result = call_vm!(peer_vm_1, <_>::default(), script, wrong_data, ""); let expected_error = UncatchableError::TraceError { trace_error: air_trace_handler::TraceHandlerError::KeeperError(NoStreamState { state: wrong_state }), instruction: "fold $s i".to_string(), }; assert!(check_error(&result, expected_error)); } #[tokio::test] async fn incompatible_executed_states() { let vm_peer_id = "vm_peer_id"; let mut peer_vm_1 = create_avm(echo_call_service(), vm_peer_id).await; let script = format!( r#" (seq (call "{vm_peer_id}" ("" "") [] scalar) (ap scalar $stream) ) "# ); let mut cid_tracker = ExecutionCidState::new(); let prev_trace = vec![ scalar_tracked!("", cid_tracker, peer = vm_peer_id), executed_state::ap(1), ]; let current_trace = vec![scalar!("", peer = vm_peer_id), scalar!("", peer = vm_peer_id)]; let prev_data = raw_data_from_trace(prev_trace, cid_tracker.clone().into()); let current_data = raw_data_from_trace(current_trace, cid_tracker.into()); let result = call_vm!(peer_vm_1, <_>::default(), &script, prev_data, current_data); let expected_error = UncatchableError::TraceError { trace_error: MergeError(air_trace_handler::merger::MergeError::IncompatibleExecutedStates( ExecutedState::Ap(ApResult::new(1.into())), scalar!("", peer = vm_peer_id), )), instruction: "ap scalar $stream".to_string(), }; assert!(check_error(&result, expected_error)); } #[tokio::test] async fn different_executed_state_expected() { let vm_peer_id_1 = "vm_peer_id_1"; let arg = json!([42, 43]); let mut peer_vm_1 = create_avm(set_variable_call_service(arg), vm_peer_id_1).await; let script = format!( r#" (seq (ap 42 some) (call "vm_peer_id_2" ("" "") [] $s) ) "# ); let wrong_state = executed_state::ap(42); let prev_trace = vec![wrong_state.clone()]; let prev_data = raw_data_from_trace(prev_trace, <_>::default()); let result = call_vm!(peer_vm_1, <_>::default(), &script, prev_data, ""); let expected_error = UncatchableError::TraceError { trace_error: MergeError(air_trace_handler::merger::MergeError::DifferentExecutedStateExpected( wrong_state, air_trace_handler::DataType::Previous, "call", )), instruction: String::from(r#"call "vm_peer_id_2" ("" "") [] $s"#), }; assert!(check_error(&result, expected_error)); } #[tokio::test] async fn invalid_dst_generations() { let vm_peer_id_1 = "vm_peer_id_1"; let mut peer_vm_1 = create_avm(unit_call_service(), vm_peer_id_1).await; let script = format!( r#" (ap "a" $s) "# ); let data_env = InterpreterDataEnvelope::from_execution_result( ExecutionTrace::from(vec![ExecutedState::Ap(ApResult { res_generations: vec![42.into(), 42.into()], })]), <_>::default(), <_>::default(), <_>::default(), semver::Version::new(1, 1, 1), ); let data = data_env.serialize().unwrap(); // let result = peer_vm_1.call(script, "", data, <_>::default()).await.unwrap(); let result = call_vm!(peer_vm_1, <_>::default(), &script, "", data); let expected_error = UncatchableError::TraceError { trace_error: MergeError(air_trace_handler::MergeError::IncorrectApResult( ApResultError::InvalidDstGenerations(ApResult { res_generations: vec![42.into(), 42.into()], }), )), instruction: String::from(r#"ap "a" $s"#), }; assert!(check_error(&result, expected_error)); } #[tokio::test] async fn incorrect_call_result() { let vm_peer_id_1 = "vm_peer_id_1"; let mut peer_vm_1 = create_avm(unit_call_service(), vm_peer_id_1).await; let script = format!( r#" (call "vm_peer_id_1" ("" "") [] v) "# ); let prev_call_result = failed!(42, "some", peer = vm_peer_id_1); let prev_trace = vec![prev_call_result.clone()]; let prev_data = raw_data_from_trace(prev_trace, <_>::default()); let curr_call_result = scalar!("some", peer = vm_peer_id_1); let curr_trace = vec![curr_call_result.clone()]; let curr_data = raw_data_from_trace(curr_trace, <_>::default()); let result = call_vm!(peer_vm_1, <_>::default(), &script, prev_data, curr_data); let mut cid_store = ExecutionCidState::new(); let curr_call_value_ref = ValueRef::Scalar(value_aggregate_cid( json!("some"), SecurityTetraplet::literal_tetraplet(vm_peer_id_1), vec![], &mut cid_store, )); let expected_error = UncatchableError::TraceError { trace_error: MergeError(air_trace_handler::MergeError::IncorrectCallResult( CallResultError::IncompatibleCallResults { prev_call: air_interpreter_data::CallResult::Failed(value_aggregate_cid( serde_json::to_value(CallServiceFailed::new(42, r#""some""#.to_owned().into())).unwrap(), SecurityTetraplet::literal_tetraplet(vm_peer_id_1), vec![], &mut cid_store, )), current_call: air_interpreter_data::CallResult::Executed(curr_call_value_ref), }, )), instruction: String::from(r#"call "vm_peer_id_1" ("" "") [] v"#), }; assert!(check_error(&result, expected_error)); } #[tokio::test] async fn canon_result_error() { let vm_peer_id_1 = "vm_peer_id_1"; let arg = json!([42, 43]); let mut peer_vm_1 = create_avm(set_variable_call_service(arg.clone()), vm_peer_id_1).await; let script = format!( r#" (canon "vm_peer_id_1" $stream #canon) "# ); let prev_tetraplet = json!({ "tetraplet": {"function_name": "s", "lens": "", "peer_pk": "vm_peer_id_1", "service_id": ""}, "values": [ { "result": 42, "tetraplet": {"function_name": "s", "lens": "", "peer_pk": "vm_peer_id_1", "service_id": ""}, }, ] }); let prev_trace = vec![executed_state::canon(prev_tetraplet)]; let prev_cid = extract_canon_result_cid(&prev_trace[0]); let prev_data = raw_data_from_trace(prev_trace, <_>::default()); let curr_tetraplet = json!({ "tetraplet": {"function_name": "s", "lens": "", "peer_pk": "vm_peer_id_1", "service_id": ""}, "values": [ { "result": 43, "tetraplet": {"function_name": "s", "lens": "", "peer_pk": "vm_peer_id_1", "service_id": ""}, }, ] }); let curr_trace = vec![executed_state::canon(curr_tetraplet)]; let curr_cid = extract_canon_result_cid(&curr_trace[0]); let curr_data = raw_data_from_trace(curr_trace, <_>::default()); let result = call_vm!(peer_vm_1, <_>::default(), &script, prev_data, curr_data); let expected_error = UncatchableError::TraceError { trace_error: MergeError(air_trace_handler::MergeError::IncorrectCanonResult( CanonResultError::IncompatibleState { prev_canon_result: air_interpreter_data::CanonResult::executed(prev_cid), current_canon_result: air_interpreter_data::CanonResult::executed(curr_cid), }, )), instruction: String::from(r#"canon "vm_peer_id_1" $stream #canon"#), }; assert!(check_error(&result, expected_error), "{:?}", result); } #[tokio::test] async fn several_records_with_same_pos() { let vm_peer_id_1 = "vm_peer_id_1"; let mut peer_vm_1 = create_avm(unit_call_service(), vm_peer_id_1).await; let script = format!( r#" (par (call "vm_peer_id_1" ("" "") [] $s) (fold $s i (call "vm_peer_id_2" ("" "") [] a) (next i) ) ) "# ); let mut cid_state = ExecutionCidState::new(); let value_pos = 1; let trace = vec![ executed_state::par(1, 2), 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)), ]), request_sent_by("vm_peer_id_1"), ]; let wrong_data = raw_data_from_trace(trace, cid_state); let result = call_vm!(peer_vm_1, <_>::default(), &script, wrong_data, ""); let fold_lore = FoldSubTraceLore { value_pos: value_pos.into(), subtraces_desc: vec![ SubTraceDesc { begin_pos: 3.into(), subtrace_len: 1, }, SubTraceDesc { begin_pos: 4.into(), subtrace_len: 0, }, ], }; let fold_result = FoldResult { lore: vec![fold_lore.clone(), fold_lore], }; let expected_error = UncatchableError::TraceError { trace_error: MergeError(air_trace_handler::merger::MergeError::IncorrectFoldResult( FoldResultError::SeveralRecordsWithSamePos(fold_result, value_pos.into()), )), instruction: String::from(String::from("fold $s i")), }; assert!(check_error(&result, expected_error)); } #[tokio::test] async fn values_not_equal() { let vm_peer_id_1 = "vm_peer_id_1"; let arg = json!([42, 43]); let mut peer_vm_1 = create_avm(set_variable_call_service(arg), vm_peer_id_1).await; let script = format!( r#" (call "vm_peer_id_1" ("" "") [] $s) "# ); let prev_value = json!(42); let prev_trace = vec![scalar!(prev_value.clone(), peer = vm_peer_id_1)]; let prev_data = raw_data_from_trace(prev_trace, <_>::default()); let curr_value = json!(43); let curr_trace = vec![scalar!(curr_value.clone(), peer = vm_peer_id_1)]; let curr_data = raw_data_from_trace(curr_trace, <_>::default()); let result = call_vm!(peer_vm_1, <_>::default(), &script, prev_data, curr_data); let mut cid_state = ExecutionCidState::new(); let prev_value = ValueRef::Scalar(value_aggregate_cid( prev_value, SecurityTetraplet::literal_tetraplet(vm_peer_id_1), vec![], &mut cid_state, )); let current_value = ValueRef::Scalar(value_aggregate_cid( curr_value, SecurityTetraplet::literal_tetraplet(vm_peer_id_1), vec![], &mut cid_state, )); let expected_error = UncatchableError::TraceError { trace_error: MergeError(air_trace_handler::merger::MergeError::IncorrectCallResult( CallResultError::ValuesNotEqual { prev_value, current_value, }, )), instruction: String::from(format!(r#"call "{vm_peer_id_1}" ("" "") [] $s"#)), }; assert!(check_error(&result, expected_error)); } #[tokio::test] async fn fold_pos_overflow() { let vm_peer_id_1 = "vm_peer_id_1"; let mut peer_vm_1 = create_avm(unit_call_service(), vm_peer_id_1).await; let script = format!( r#" (par (call "vm_peer_id_1" ("" "") [] $s) (fold $s i (call "vm_peer_id_2" ("" "") [] a) (next i) ) ) "# ); let mut cid_state = ExecutionCidState::new(); let value_pos = 1; let before_subtrace_pos = 3; let after_subtrace_pos = 4; let wrong_after_subtrace_len = TraceLen::MAX - 1; let trace = vec![ executed_state::par(1, 2), 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), subtrace_desc(after_subtrace_pos, wrong_after_subtrace_len), )]), request_sent_by("vm_peer_id_1"), ]; let wrong_data = raw_data_from_trace(trace, cid_state); let result = call_vm!(peer_vm_1, <_>::default(), &script, wrong_data, ""); let fold_lore = ResolvedSubTraceDescs::new( SubTraceDesc { begin_pos: before_subtrace_pos.into(), subtrace_len: wrong_after_subtrace_len + 1, }, SubTraceDesc { begin_pos: after_subtrace_pos.into(), subtrace_len: wrong_after_subtrace_len, }, ); let resolved_fold = ResolvedFold::new(hashmap!(value_pos.into() => fold_lore), wrong_after_subtrace_len + 1); let expected_error = UncatchableError::TraceError { trace_error: StateFSMError(FoldPosOverflow(resolved_fold, before_subtrace_pos.into(), Previous)), instruction: String::from(String::from("fold $s i")), }; assert!(check_error(&result, expected_error)); } #[tokio::test] async fn subtrace_len_overflow() { let vm_peer_id_1 = "vm_peer_id_1"; let mut peer_vm_1 = create_avm(unit_call_service(), vm_peer_id_1).await; let script = format!( r#" (par (call "vm_peer_id_1" ("" "") [] $s) (fold $s i (call "vm_peer_id_2" ("" "") [] a) (next i) ) ) "# ); let mut cid_state = ExecutionCidState::new(); let value_pos = 1; let wrong_subtrace_len = TraceLen::MAX; let trace = vec![ executed_state::par(1, 2), stream_tracked!(json!([42, 43]), 0, cid_state, peer = vm_peer_id_1), executed_state::fold(vec![executed_state::subtrace_lore( value_pos, subtrace_desc(3, 1), subtrace_desc(4, wrong_subtrace_len), )]), request_sent_by("vm_peer_id_1"), ]; let _wrong_data = raw_data_from_trace(trace, cid_state); let result = call_vm!(peer_vm_1, <_>::default(), script, _wrong_data, ""); let fold_lore = FoldSubTraceLore { value_pos: value_pos.into(), subtraces_desc: vec![ SubTraceDesc { begin_pos: 3.into(), subtrace_len: 1, }, SubTraceDesc { begin_pos: 4.into(), subtrace_len: wrong_subtrace_len, }, ], }; let fold_result = FoldResult { lore: vec![fold_lore] }; let expected_error = UncatchableError::TraceError { trace_error: MergeError(air_trace_handler::merger::MergeError::IncorrectFoldResult( FoldResultError::SubtraceLenOverflow { fold_result, count: 0 }, )), instruction: String::from(String::from("fold $s i")), }; assert!(check_error(&result, expected_error)); } #[tokio::test] async fn fold_incorrect_subtraces_count() { let vm_peer_id_1 = "vm_peer_id_1"; let mut peer_vm_1 = create_avm(unit_call_service(), vm_peer_id_1).await; let script = format!( r#" (par (call "vm_peer_id_1" ("" "") [] $s) (fold $s i (call "vm_peer_id_2" ("" "") [] a) (next i) ) ) "# ); let mut cid_state = ExecutionCidState::new(); let value_pos = 1; let wrong_subtrace_lore = vec![subtrace_desc(3, 1), subtrace_desc(4, 0), subtrace_desc(4, 0)]; let trace = vec![ executed_state::par(1, 2), stream_tracked!(json!([42, 43]), 0, cid_state, peer = vm_peer_id_1), executed_state::fold(vec![FoldSubTraceLore { value_pos: value_pos.into(), subtraces_desc: wrong_subtrace_lore.clone(), }]), request_sent_by("vm_peer_id_1"), ]; let _wrong_data = raw_data_from_trace(trace, cid_state); let result = call_vm!(peer_vm_1, <_>::default(), script, _wrong_data, ""); let expected_error = UncatchableError::TraceError { trace_error: MergeError(air_trace_handler::merger::MergeError::IncorrectFoldResult( FoldResultError::FoldIncorrectSubtracesCount(wrong_subtrace_lore.len()), )), instruction: String::from(String::from("fold $s i")), }; assert!(check_error(&result, expected_error)); }