diff --git a/Cargo.lock b/Cargo.lock index 0da2f4949..4d7833696 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3766,6 +3766,7 @@ dependencies = [ "base64 0.21.5", "config", "cucumber", + "futures 0.3.29", "httpmock", "indexmap 1.9.3", "log", diff --git a/integration_tests/Cargo.toml b/integration_tests/Cargo.toml index 3d87ad929..e9b47283d 100644 --- a/integration_tests/Cargo.toml +++ b/integration_tests/Cargo.toml @@ -49,6 +49,7 @@ cucumber = { version = "0.18.0", features = [ "libtest", "output-junit", ] } +futures = "0.3.29" httpmock = "0.6.8" indexmap = "1.9.1" log = { version = "0.4.8", features = ["std"] } diff --git a/integration_tests/src/validator_node_cli.rs b/integration_tests/src/validator_node_cli.rs index 2d0032c0a..e6dad0d42 100644 --- a/integration_tests/src/validator_node_cli.rs +++ b/integration_tests/src/validator_node_cli.rs @@ -4,6 +4,7 @@ use std::{collections::HashMap, path::PathBuf, str::FromStr}; use tari_engine_types::{ + commit_result::RejectReason, instruction::Instruction, substate::{SubstateAddress, SubstateDiff}, }; @@ -215,7 +216,7 @@ pub async fn call_method( fq_component_name: String, outputs_name: String, method_call: String, -) -> SubmitTransactionResponse { +) -> Result { let data_dir = get_cli_data_dir(world); let (input_group, component_name) = fq_component_name.split_once('/').unwrap_or_else(|| { panic!( @@ -256,8 +257,8 @@ pub async fn call_method( let mut client = world.get_validator_node(&vn_name).get_client(); let resp = handle_submit(args, data_dir, &mut client).await.unwrap(); - if let Some(ref failure) = resp.dry_run_result.as_ref().unwrap().finalize.reject() { - panic!("Transaction failed: {:?}", failure); + if let Some(failure) = resp.dry_run_result.as_ref().unwrap().finalize.reject() { + return Err(failure.clone()); } // store the account component address and other substate addresses for later reference add_substate_addresses( @@ -265,7 +266,7 @@ pub async fn call_method( outputs_name, resp.dry_run_result.as_ref().unwrap().finalize.result.accept().unwrap(), ); - resp + Ok(resp) } pub async fn submit_manifest( diff --git a/integration_tests/tests/cucumber.rs b/integration_tests/tests/cucumber.rs index 80b624fcb..9e64abcff 100644 --- a/integration_tests/tests/cucumber.rs +++ b/integration_tests/tests/cucumber.rs @@ -207,13 +207,34 @@ async fn call_component_method( method_call: String, output_name: String, ) { - let resp = validator_node_cli::call_method(world, vn_name, component_name, output_name, method_call).await; + let resp = validator_node_cli::call_method(world, vn_name, component_name, output_name, method_call) + .await + .unwrap(); assert_eq!(resp.dry_run_result.unwrap().decision, QuorumDecision::Accept); // give it some time between transactions // tokio::time::sleep(Duration::from_secs(4)).await; } +#[when( + expr = r#"I invoke on {word} on component {word} the method call "{word}" named "{word}" the result is error {string}"# +)] +async fn call_component_method_must_error( + world: &mut TariWorld, + vn_name: String, + component_name: String, + method_call: String, + output_name: String, + error_msg: String, +) { + let res = validator_node_cli::call_method(world, vn_name, component_name, output_name, method_call).await; + if let Err(reject) = res { + assert!(reject.to_string().contains(&error_msg)); + } else { + panic!("Expected an error but the call was successful"); + } +} + #[when(expr = r#"I invoke on all validator nodes on component {word} the method call "{word}" named "{word}""#)] async fn call_component_method_on_all_vns( world: &mut TariWorld, @@ -230,7 +251,8 @@ async fn call_component_method_on_all_vns( output_name.clone(), method_call.clone(), ) - .await; + .await + .unwrap(); assert_eq!(resp.dry_run_result.unwrap().decision, QuorumDecision::Accept); } // give it some time between transactions @@ -246,7 +268,9 @@ async fn call_component_method_and_check_result( expected_result: String, ) { let resp = - validator_node_cli::call_method(world, vn_name, component_name, "dummy_outputs".to_string(), method_call).await; + validator_node_cli::call_method(world, vn_name, component_name, "dummy_outputs".to_string(), method_call) + .await + .unwrap(); let finalize_result = resp.dry_run_result.unwrap(); assert_eq!(finalize_result.decision, QuorumDecision::Accept); @@ -283,7 +307,8 @@ async fn call_component_method_on_all_vns_and_check_result( "dummy_outputs".to_string(), method_call.clone(), ) - .await; + .await + .unwrap(); let finalize_result = resp.dry_run_result.unwrap(); assert_eq!(finalize_result.decision, QuorumDecision::Accept); diff --git a/integration_tests/tests/features/substates.feature b/integration_tests/tests/features/substates.feature new file mode 100644 index 000000000..72b6141a8 --- /dev/null +++ b/integration_tests/tests/features/substates.feature @@ -0,0 +1,48 @@ +# Copyright 2022 The Tari Project +# SPDX-License-Identifier: BSD-3-Clause + +Feature: Substates + + @serial + Scenario: Transactions with DOWN local substates are rejected + Given fees are disabled + # Initialize a base node, wallet, miner and VN + Given a base node BASE + Given a wallet WALLET connected to base node BASE + Given a miner MINER connected to base node BASE and wallet WALLET + + # Initialize a VN + Given a validator node VAL_1 connected to base node BASE and wallet WALLET + + # The wallet must have some funds before the VN sends transactions + When miner MINER mines 6 new blocks + When wallet WALLET has at least 20 T + + # VN registration + When validator node VAL_1 sends a registration transaction + + # Register the "counter" template + When validator node VAL_1 registers the template "counter" + When miner MINER mines 13 new blocks + Then VAL_1 has scanned to height 16 + Then the validator node VAL_1 is listed as registered + Then the template "counter" is listed as registered by the validator node VAL_1 + + # A file-base CLI account must be created to sign future calls + When I use an account key named K1 + + # Create a new Counter component + When I create a component COUNTER_1 of template "counter" on VAL_1 using "new" + + # Increase the counter an check the value + When I invoke on VAL_1 on component COUNTER_1/components/Counter the method call "increase" named "TX1" + When I invoke on VAL_1 on component TX1/components/Counter the method call "value" the result is "1" + + # We should get an error if we se as inputs the same component version thas has already been downed from previous transactions + # We can achieve this by reusing inputs from COUNTER_1 instead of the most recent TX1 + When I invoke on VAL_1 on component COUNTER_1/components/Counter the method call "increase" named "TX2" the result is error "Shard was rejected" + + # Check that the counter has NOT been increased by the previous erroneous transaction + When I invoke on VAL_1 on component TX1/components/Counter the method call "value" the result is "1" + +