/*
* 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::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.
#[tokio::test]
async 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::(alice_keypair, echo_call_service(), <_>::default()).await;
let mut bob_avm = create_avm_with_key::(bob_keypair, echo_call_service(), <_>::default()).await;
let mut mallory_avm =
create_avm_with_key::(mallory_keypair, echo_call_service(), <_>::default()).await;
let alice_res = alice_avm
.call(&air_script, "", "", test_run_params.clone())
.await
.unwrap();
let mallory_res = mallory_avm
.call(&air_script, "", alice_res.data, test_run_params.clone())
.await
.unwrap();
let mut mallory_env = env_from_result(&mallory_res);
let mut mallory_data = InterpreterData::try_from_slice(&mallory_env.inner_data).unwrap();
let mut mallory_raw_trace: Vec<_> = mallory_data.trace.to_vec();
mallory_raw_trace.swap(0, 1);
mallory_data.trace = ExecutionTrace::from(mallory_raw_trace);
mallory_env.inner_data = mallory_data.serialize().unwrap().into();
let mallory_data = mallory_env.serialize().unwrap();
let bob_res = bob_avm
.call(air_script, "", mallory_data, test_run_params)
.await
.unwrap();
assert_error_eq!(
&bob_res,
UncatchableError::InstructionParametersMismatch {
param: "call argument_hash",
expected_value: "bagaaihraryhzxrhasfve7jwovrl4rb4j45lljt5prmoci34y3i6qx7joxy2a".to_owned(),
stored_value: "bagaaihra7w4yil3eqnjimo4d3yp4kr2yra2o6svycab67oymtseafak4la6a".to_owned(),
}
);
}
#[tokio::test]
async 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::(alice_keypair, echo_call_service(), <_>::default()).await;
let mut bob_avm = create_avm_with_key::(bob_keypair, echo_call_service(), <_>::default()).await;
let mut mallory_avm =
create_avm_with_key::(mallory_keypair, echo_call_service(), <_>::default()).await;
let alice_res = alice_avm
.call(&air_script, "", "", test_run_params.clone())
.await
.unwrap();
let mallory_res = mallory_avm
.call(&air_script, "", alice_res.data, test_run_params.clone())
.await
.unwrap();
let mut mallory_env = env_from_result(&mallory_res);
let mut mallory_data = InterpreterData::try_from_slice(&mallory_env.inner_data).unwrap();
let mut mallory_raw_trace: Vec<_> = mallory_data.trace.to_vec();
mallory_raw_trace.swap(0, 1);
mallory_data.trace = ExecutionTrace::from(mallory_raw_trace);
mallory_env.inner_data = mallory_data.serialize().unwrap().into();
let mallory_data = mallory_env.serialize().unwrap();
let bob_res = bob_avm
.call(air_script, "", mallory_data, test_run_params)
.await
.unwrap();
let expected_value = format!(
concat!(
r#"SecurityTetraplet {{ peer_pk: "{alice_peer_id}","#,
r#" service_id: "service1", function_name: "func", lens: "" }}"#
),
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", lens: "" }}"#,
),
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,
}
);
}
#[tokio::test]
async 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::(alice_keypair, fallible_call_service_by_arg(43), <_>::default()).await;
let mut bob_avm = create_avm_with_key::(bob_keypair, echo_call_service(), <_>::default()).await;
let mut mallory_avm =
create_avm_with_key::(mallory_keypair, echo_call_service(), <_>::default()).await;
let alice_res = alice_avm
.call(&air_script, "", "", test_run_params.clone())
.await
.unwrap();
let mallory_res = mallory_avm
.call(&air_script, "", alice_res.data, test_run_params.clone())
.await
.unwrap();
let mut mallory_env = env_from_result(&mallory_res);
let mut mallory_data = InterpreterData::try_from_slice(&mallory_env.inner_data).unwrap();
let mut mallory_raw_trace: Vec<_> = mallory_data.trace.to_vec();
mallory_raw_trace.swap(0, 1);
mallory_data.trace = ExecutionTrace::from(mallory_raw_trace);
mallory_env.inner_data = mallory_data.serialize().unwrap().into();
let mallory_data = mallory_env.serialize().unwrap();
let bob_res = bob_avm
.call(air_script, "", mallory_data, test_run_params)
.await
.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: "bagaaihraryhzxrhasfve7jwovrl4rb4j45lljt5prmoci34y3i6qx7joxy2a".to_owned(),
stored_value: "bagaaihra7w4yil3eqnjimo4d3yp4kr2yra2o6svycab67oymtseafak4la6a".to_owned(),
}
);
}
#[tokio::test]
async 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::(alice_keypair, fallible_call_service("service1"), <_>::default()).await;
let mut bob_avm = create_avm_with_key::(bob_keypair, echo_call_service(), <_>::default()).await;
let mut mallory_avm =
create_avm_with_key::(mallory_keypair, echo_call_service(), <_>::default()).await;
let alice_res = alice_avm
.call(&air_script, "", "", test_run_params.clone())
.await
.unwrap();
let mallory_res = mallory_avm
.call(&air_script, "", alice_res.data, test_run_params.clone())
.await
.unwrap();
let mut mallory_env = env_from_result(&mallory_res);
let mut mallory_data = InterpreterData::try_from_slice(&mallory_env.inner_data).unwrap();
let mut mallory_raw_trace: Vec<_> = mallory_data.trace.to_vec();
mallory_raw_trace.swap(0, 1);
mallory_data.trace = ExecutionTrace::from(mallory_raw_trace);
mallory_env.inner_data = mallory_data.serialize().unwrap().into();
let mallory_data = mallory_env.serialize().unwrap();
let bob_res = bob_avm
.call(air_script, "", mallory_data, test_run_params)
.await
.unwrap();
let expected_value = format!(
concat!(
r#"SecurityTetraplet {{ peer_pk: "{alice_peer_id}","#,
r#" service_id: "service1", function_name: "func", lens: "" }}"#
),
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", lens: "" }}"#,
),
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,
}
);
}
#[tokio::test]
async 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::(alice_keypair, fallible_call_service("service1"), <_>::default()).await;
let mut bob_avm = create_avm_with_key::(bob_keypair, echo_call_service(), <_>::default()).await;
let mut mallory_avm =
create_avm_with_key::(mallory_keypair, echo_call_service(), <_>::default()).await;
let alice_res = alice_avm
.call(&air_script, "", "", test_run_params.clone())
.await
.unwrap();
let mallory_res = mallory_avm
.call(&air_script, "", alice_res.data, test_run_params.clone())
.await
.unwrap();
let mut mallory_env = env_from_result(&mallory_res);
let mut mallory_data = InterpreterData::try_from_slice(&mallory_env.inner_data).unwrap();
let mut mallory_raw_trace: Vec<_> = mallory_data.trace.to_vec();
mallory_raw_trace.swap(2, 3);
mallory_data.trace = ExecutionTrace::from(mallory_raw_trace);
mallory_env.inner_data = mallory_data.serialize().unwrap().into();
let mallory_data = mallory_env.serialize().unwrap();
let bob_res = bob_avm
.call(air_script, "", mallory_data, test_run_params)
.await
.unwrap();
let expected_value = format!(
concat!(
r#"SecurityTetraplet {{ peer_pk: "{alice_peer_id}","#,
r#" service_id: "", function_name: "", lens: "" }}"#
),
alice_peer_id = alice_peer_id,
);
let stored_value = format!(
concat!(
r#"SecurityTetraplet {{ peer_pk: "{mallory_peer_id}","#,
r#" service_id: "", function_name: "", lens: "" }}"#,
),
mallory_peer_id = mallory_peer_id,
);
assert_error_eq!(
&bob_res,
UncatchableError::InstructionParametersMismatch {
param: "canon tetraplet",
expected_value,
stored_value,
}
);
}