Skip to content

Commit

Permalink
test(cucumber): add concurrent wallet daemon call (#1140)
Browse files Browse the repository at this point in the history
Description
---

* Add concurrent wallet daemon call ability to replicate VN cli test
functionality
* Update concurrent feature with using the wallet daemon
* Concurrent feature is disabled due to lock bug while awaiting engine
update
  • Loading branch information
therealdannzor authored Sep 13, 2024
1 parent 5431162 commit b603394
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 89 deletions.
19 changes: 19 additions & 0 deletions integration_tests/src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::{
time::Duration,
};

use tari_dan_common_types::SubstateRequirement;
use tari_engine_types::substate::SubstateId;
use tokio::{io::AsyncWriteExt, task::JoinHandle};

Expand Down Expand Up @@ -136,3 +137,21 @@ pub fn get_address_from_output(world: &TariWorld, output_ref: String) -> &Substa
})
.unwrap_or_else(|| panic!("Output not found: {}", output_ref))
}

pub fn get_component_from_namespace(world: &TariWorld, fq_component_name: String) -> SubstateRequirement {
let (input_group, component_name) = fq_component_name.split_once('/').unwrap_or_else(|| {
panic!(
"Component name must be in the format '{{group}}/components/{{template_name}}', got {}",
fq_component_name
)
});

world
.outputs
.get(input_group)
.unwrap_or_else(|| panic!("No outputs found with name {}", input_group))
.iter()
.find(|(name, _)| **name == component_name)
.map(|(_, data)| data.clone())
.unwrap_or_else(|| panic!("No component named {}", component_name))
}
69 changes: 7 additions & 62 deletions integration_tests/src/validator_node_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use tari_validator_node_cli::{
};
use tari_validator_node_client::{types::SubmitTransactionResponse, ValidatorNodeClient};

use crate::{logging::get_base_dir_for_scenario, TariWorld};
use crate::{helpers::get_component_from_namespace, logging::get_base_dir_for_scenario, TariWorld};

fn get_key_manager(world: &mut TariWorld) -> KeyManager {
let path = get_cli_data_dir(world);
Expand Down Expand Up @@ -206,29 +206,12 @@ pub async fn concurrent_call_method(
method_call: String,
times: usize,
) -> Result<SubmitTransactionResponse, RejectReason> {
let vn_data_dir = get_cli_data_dir(world);
let (input_group, component_name) = fq_component_name.split_once('/').unwrap_or_else(|| {
panic!(
"Component name must be in the format '{{group}}/components/{{template_name}}', got {}",
fq_component_name
)
});

let vn_client = world.get_validator_node(&vn_name).get_client();

let mut component = world
.outputs
.get(input_group)
.unwrap_or_else(|| panic!("No outputs found with name {}", input_group))
.iter()
.find(|(name, _)| **name == component_name)
.map(|(_, data)| data.clone())
.unwrap_or_else(|| panic!("No component named {}", component_name));
let mut component = get_component_from_namespace(world, fq_component_name);
// For concurrent transactions we DO NOT specify the versions
component.version = None;

// call_method_inner(vn_client.clone(), vn_data_dir.clone(), component.clone(), method_call.clone()).await

let vn_data_dir = get_cli_data_dir(world);
let vn_client = world.get_validator_node(&vn_name).get_client();
let mut handles = Vec::new();
for _ in 0..times {
let handle = tokio::spawn(call_method_inner(
Expand Down Expand Up @@ -268,47 +251,9 @@ pub async fn call_method(
method_call: String,
) -> Result<SubmitTransactionResponse, RejectReason> {
let data_dir = get_cli_data_dir(world);
let (input_group, component_name) = fq_component_name.split_once('/').unwrap_or_else(|| {
panic!(
"Component name must be in the format '{{group}}/components/{{template_name}}', got {}",
fq_component_name
)
});
let component = world
.outputs
.get(input_group)
.unwrap_or_else(|| panic!("No outputs found with name {}", input_group))
.iter()
.find(|(name, _)| **name == component_name)
.map(|(_, data)| data.clone())
.unwrap_or_else(|| panic!("No component named {}", component_name));

let instruction = CliInstruction::CallMethod {
component_address: component.substate_id.clone(),
// TODO: actually parse the method call for arguments
method_name: method_call,
args: vec![],
};

println!("Inputs: {}", component);
let args = SubmitArgs {
instruction,
common: CommonSubmitArgs {
wait_for_result: true,
wait_for_result_timeout: Some(60),
inputs: vec![component],
version: None,
dump_outputs_into: None,
account_template_address: None,
dry_run: false,
},
};
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(failure) = resp.dry_run_result.as_ref().unwrap().finalize.full_reject() {
return Err(failure.clone());
}
let component = get_component_from_namespace(world, fq_component_name);
let vn_client = world.get_validator_node(&vn_name).get_client();
let resp = call_method_inner(vn_client, data_dir, component, method_call).await?;

// store the account component address and other substate ids for later reference
add_substate_ids(
Expand Down
60 changes: 56 additions & 4 deletions integration_tests/src/wallet_daemon_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

use std::{collections::HashMap, str::FromStr, time::Duration};

use anyhow::bail;
use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
use serde_json::json;
use tari_crypto::{
Expand Down Expand Up @@ -709,7 +710,7 @@ pub async fn call_component(
.call_method(source_component_address, &function_call, vec![])
.build_unsigned_transaction();

let resp = submit_unsigned_tx_and_wait_for_response(&mut client, tx, vec![], account).await;
let resp = submit_unsigned_tx_and_wait_for_response(client, tx, vec![], account).await;

add_substate_ids(
world,
Expand All @@ -727,6 +728,57 @@ pub async fn call_component(
resp
}

pub async fn concurrent_call_component(
world: &mut TariWorld,
account_name: String,
output_ref: String,
wallet_daemon_name: String,
function_call: String,
times: usize,
) -> anyhow::Result<()> {
let mut client = get_auth_wallet_daemon_client(world, &wallet_daemon_name).await;

let source_component_address = get_address_from_output(world, output_ref.clone())
.as_component_address()
.expect("Failed to get component address from output");

let account = get_account_from_name(&mut client, account_name).await;
let account_component_address = account
.address
.as_component_address()
.expect("Failed to get account component address");

let mut handles = Vec::new();
for _ in 0..times {
let acc = account.clone();
let clt = client.clone();
let tx = Transaction::builder()
.fee_transaction_pay_from_component(account_component_address, Amount(1))
.call_method(source_component_address, &function_call, vec![])
.build_unsigned_transaction();
let handle = tokio::spawn(submit_unsigned_tx_and_wait_for_response(clt, tx, vec![], acc));
handles.push(handle);
}

let mut last_resp = None;
for handle in handles {
let result = handle.await.map_err(|e| e.to_string());
if result.is_err() {
bail!("{}", result.as_ref().unwrap_err());
}
match result {
Ok(response) => last_resp = Some(response),
Err(e) => bail!("Failed to get response from handler: {}", e),
}
}

if last_resp.is_none() {
bail!("No responses from any of the wallet daemon concurrent calls");
}

Ok(())
}

pub async fn transfer(
world: &mut TariWorld,
account_name: String,
Expand Down Expand Up @@ -809,7 +861,7 @@ async fn get_account_from_name(client: &mut WalletDaemonClient, account_name: St
}

async fn submit_unsigned_tx_and_wait_for_response(
client: &mut WalletDaemonClient,
mut client: WalletDaemonClient,
tx: UnsignedTransaction,
inputs: Vec<SubstateRequirement>,
account: Account,
Expand All @@ -836,10 +888,10 @@ async fn submit_unsigned_tx_and_wait_for_response(
let resp = client
.wait_transaction_result(wait_req)
.await
.unwrap_or_else(|_| panic!("Waiting for the transaction when calling component failed"));
.map_err(|e| anyhow::Error::msg(e.to_string()))?;

if let Some(reason) = resp.result.as_ref().and_then(|finalize| finalize.reject()) {
panic!("Calling component result rejected: {}", reason);
bail!("Calling component result rejected: {}", reason);
}

Ok(resp)
Expand Down
23 changes: 23 additions & 0 deletions integration_tests/tests/cucumber.rs
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,29 @@ async fn call_wallet_daemon_method(
Ok(())
}

#[when(
expr = r#"I invoke on wallet daemon {word} on account {word} on component {word} the method call "{word}" concurrently {int} times"#
)]
async fn call_wallet_daemon_method_concurrently(
world: &mut TariWorld,
wallet_daemon_name: String,
account_name: String,
output_ref: String,
method_call: String,
times: usize,
) {
wallet_daemon_cli::concurrent_call_component(
world,
account_name,
output_ref,
wallet_daemon_name,
method_call,
times,
)
.await
.unwrap_or_else(|e| panic!("Concurrent wallet daemon call failed: {:?}", e));
}

#[when(
expr = "I invoke on all validator nodes on component {word} the method call \"{word}\" the result is \"{word}\""
)]
Expand Down
51 changes: 28 additions & 23 deletions integration_tests/tests/features/concurrency.feature.ignore
Original file line number Diff line number Diff line change
@@ -1,46 +1,51 @@
# Copyright 2024 The Tari Project
# SPDX-License-Identifier: BSD-3-Clause

@concurrency
@concurrency @doit
Feature: Concurrency

@serial
Scenario: Concurrent calls to the Counter template
Given fees are disabled

##### Setup
# Initialize a base node, wallet, miner and VN
Given fees are disabled
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 daemon WALLET_D
# Initialize a validator node
Given a validator node VN connected to base node BASE and wallet daemon WALLET_D

# 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
# Fund wallet to send VN registration tx
When miner MINER mines 10 new blocks
When wallet WALLET has at least 2000 T
When validator node VN sends a registration transaction to base wallet WALLET
When miner MINER mines 16 new blocks
Then the validator node VN is listed as registered

# VN registration
When validator node VAL_1 sends a registration transaction to base wallet WALLET
# Initialize indexer and connect wallet daemon
Given an indexer IDX connected to base node BASE
Given a wallet daemon WALLET_D connected to indexer IDX

# Register the "counter" template
When base wallet WALLET 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
When miner MINER mines 20 new blocks
Then VN has scanned to height 43

# A file-base CLI account must be created to sign future calls
When I use an account key named K1
# Create the sender account
When I create an account ACC via the wallet daemon WALLET_D with 10000 free coins

# Create a new Counter component
When I create a component COUNTER_1 of template "counter" on VAL_1 using "new"
When I print the cucumber world
##### Scenario
# The initial value of the counter must be 0
When I call function "new" on template "counter" using account ACC to pay fees via wallet daemon WALLET_D named "COUNTER"
When I invoke on wallet daemon WALLET_D on account ACC on component COUNTER/components/Counter the method call "value" the result is "0"

# Send multiple concurrent transactions to increase the counter
# TODO: when concurrency is fully working, call it with "2 times" or higher
When I invoke on VAL_1 on component COUNTER_1/components/Counter the method call "increase" concurrently 1 times
When I print the cucumber world
# Currently there is a lock bug where the subsequent transactions executed are being rejected, should be tested later after engine changes:
# Reject(FailedToLockInputs("Failed to Write lock substate component_459d...4443c:1 due to conflict with existing Write lock"))
When I invoke on wallet daemon WALLET_D on account ACC on component COUNTER/components/Counter the method call "increase" concurrently 2 times

# Check that the counter has been increased
# TODO: uncomment when concurrency is fully working
# When I invoke on VAL_1 on component TX1/components/Counter the method call "value" the result is "2"
# Note: this is currently not working together with the previous test case when times > 1, only the first transaction is being executed properly
When I invoke on wallet daemon WALLET_D on account ACC on component COUNTER/components/Counter the method call "value" the result is "2"

0 comments on commit b603394

Please sign in to comment.