mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-01-04 22:33:06 +00:00
add tps integration test
This commit is contained in:
parent
f96f4d9117
commit
c47693b9b0
@ -37,3 +37,6 @@ path = "../common"
|
||||
[dependencies.nssa]
|
||||
path = "../nssa"
|
||||
features = ["no_docker"]
|
||||
|
||||
[dependencies.key_protocol]
|
||||
path = "../key_protocol"
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
"genesis_id": 1,
|
||||
"is_genesis_random": true,
|
||||
"max_num_tx_in_block": 20,
|
||||
"mempool_max_size": 10000,
|
||||
"block_create_timeout_millis": 10000,
|
||||
"port": 3040,
|
||||
"initial_accounts": [
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
|
||||
use std::{path::PathBuf, time::Duration};
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use actix_web::dev::ServerHandle;
|
||||
use anyhow::Result;
|
||||
@ -38,6 +41,10 @@ use wallet::{
|
||||
helperfunctions::{fetch_config, fetch_persistent_accounts},
|
||||
};
|
||||
|
||||
use crate::tps_test_utils::TpsTestManager;
|
||||
|
||||
mod tps_test_utils;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(version)]
|
||||
struct Args {
|
||||
@ -81,6 +88,26 @@ pub async fn pre_test(
|
||||
))
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
async fn pre_tps_test(
|
||||
test: &TpsTestManager,
|
||||
) -> Result<(ServerHandle, JoinHandle<Result<()>>, TempDir)> {
|
||||
info!("Generating tps test config");
|
||||
let mut sequencer_config = test.generate_tps_test_config();
|
||||
info!("Done");
|
||||
|
||||
let temp_dir_sequencer = replace_home_dir_with_temp_dir_in_configs(&mut sequencer_config);
|
||||
|
||||
let (seq_http_server_handle, sequencer_loop_handle) =
|
||||
startup_sequencer(sequencer_config).await?;
|
||||
|
||||
Ok((
|
||||
seq_http_server_handle,
|
||||
sequencer_loop_handle,
|
||||
temp_dir_sequencer,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn replace_home_dir_with_temp_dir_in_configs(
|
||||
sequencer_config: &mut SequencerConfig,
|
||||
) -> TempDir {
|
||||
@ -112,6 +139,63 @@ pub async fn post_test(residual: (ServerHandle, JoinHandle<Result<()>>, TempDir)
|
||||
//So they are dropped and tempdirs will be dropped too,
|
||||
}
|
||||
|
||||
pub async fn tps_test() {
|
||||
let num_transactions = 300 * 5;
|
||||
let target_tps = 12;
|
||||
let tps_test = TpsTestManager::new(target_tps, num_transactions);
|
||||
let target_time = tps_test.target_time();
|
||||
info!("Target time: {:?} seconds", target_time.as_secs());
|
||||
let res = pre_tps_test(&tps_test).await.unwrap();
|
||||
|
||||
let wallet_config = fetch_config().await.unwrap();
|
||||
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
|
||||
|
||||
info!("TPS test begin");
|
||||
let txs = tps_test.build_public_txs();
|
||||
let now = Instant::now();
|
||||
|
||||
let mut tx_hashes = vec![];
|
||||
for (i, tx) in txs.into_iter().enumerate() {
|
||||
let tx_hash = seq_client.send_tx_public(tx).await.unwrap().tx_hash;
|
||||
info!("Sent tx {i}");
|
||||
tx_hashes.push(tx_hash);
|
||||
}
|
||||
|
||||
for (i, tx_hash) in tx_hashes.iter().enumerate() {
|
||||
loop {
|
||||
if now.elapsed().as_millis() > target_time.as_millis() {
|
||||
panic!("TPS test failed by timout");
|
||||
}
|
||||
|
||||
let tx_obj = seq_client
|
||||
.get_transaction_by_hash(tx_hash.clone())
|
||||
.await
|
||||
.inspect_err(|err| {
|
||||
warn!("Failed to get transaction by hash {tx_hash:#?} with error: {err:#?}")
|
||||
});
|
||||
|
||||
if let Ok(tx_obj) = tx_obj
|
||||
&& tx_obj.transaction.is_some()
|
||||
{
|
||||
info!("Found tx {i} with hash {tx_hash}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let time_elapsed = now.elapsed().as_secs();
|
||||
|
||||
info!("TPS test finished successfully");
|
||||
info!("Target TPS: {}", target_tps);
|
||||
info!(
|
||||
"Processed {} transactions in {}s",
|
||||
tx_hashes.len(),
|
||||
time_elapsed
|
||||
);
|
||||
info!("Target time: {:?}s", target_time.as_secs());
|
||||
|
||||
post_test(res).await;
|
||||
}
|
||||
|
||||
pub async fn test_success() {
|
||||
info!("test_success");
|
||||
let command = Command::Transfer(NativeTokenTransferProgramSubcommand::Public {
|
||||
@ -1624,6 +1708,9 @@ pub async fn main_tests_runner() -> Result<()> {
|
||||
} = args;
|
||||
|
||||
match test_name.as_str() {
|
||||
"tps_test" => {
|
||||
tps_test().await;
|
||||
}
|
||||
"test_success_token_program" => {
|
||||
test_cleanup_wrap!(home_dir, test_success_token_program);
|
||||
}
|
||||
|
||||
187
integration_tests/src/tps_test_utils.rs
Normal file
187
integration_tests/src/tps_test_utils.rs
Normal file
@ -0,0 +1,187 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder;
|
||||
use nssa::{
|
||||
Account, AccountId, Address, PrivacyPreservingTransaction, PrivateKey, PublicKey,
|
||||
PublicTransaction,
|
||||
privacy_preserving_transaction::{self as pptx, circuit},
|
||||
program::Program,
|
||||
public_transaction as putx,
|
||||
};
|
||||
use nssa_core::{
|
||||
MembershipProof, NullifierPublicKey, account::AccountWithMetadata,
|
||||
encryption::IncomingViewingPublicKey,
|
||||
};
|
||||
use sequencer_core::config::{AccountInitialData, CommitmentsInitialData, SequencerConfig};
|
||||
|
||||
pub(crate) struct TpsTestManager {
|
||||
public_keypairs: Vec<(PrivateKey, Address)>,
|
||||
target_tps: u64,
|
||||
}
|
||||
|
||||
impl TpsTestManager {
|
||||
/// Generates public account keypairs. These are used to populate the config and to generate valid
|
||||
/// public transactions for the tps test.
|
||||
pub(crate) fn new(target_tps: u64, number_transactions: usize) -> Self {
|
||||
let public_keypairs = (1..(number_transactions + 2))
|
||||
.map(|i| {
|
||||
let mut private_key_bytes = [0u8; 32];
|
||||
private_key_bytes[..8].copy_from_slice(&i.to_le_bytes());
|
||||
let private_key = PrivateKey::try_new(private_key_bytes).unwrap();
|
||||
let public_key = PublicKey::new_from_private_key(&private_key);
|
||||
let address = Address::from(&public_key);
|
||||
(private_key, address)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
Self {
|
||||
public_keypairs,
|
||||
target_tps,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn target_time(&self) -> Duration {
|
||||
let number_transactions = (self.public_keypairs.len() - 1) as u64;
|
||||
Duration::from_secs_f64(number_transactions as f64 / self.target_tps as f64)
|
||||
}
|
||||
|
||||
///
|
||||
/// Build a batch of public transactions to submit to the node.
|
||||
pub fn build_public_txs(&self) -> Vec<PublicTransaction> {
|
||||
// Create valid public transactions
|
||||
let program = Program::authenticated_transfer_program();
|
||||
let public_txs: Vec<PublicTransaction> = self
|
||||
.public_keypairs
|
||||
.windows(2)
|
||||
.map(|pair| {
|
||||
let amount: u128 = 1;
|
||||
let message = putx::Message::try_new(
|
||||
program.id(),
|
||||
[pair[0].1, pair[1].1].to_vec(),
|
||||
[0u128].to_vec(),
|
||||
amount,
|
||||
)
|
||||
.unwrap();
|
||||
let witness_set =
|
||||
nssa::public_transaction::WitnessSet::for_message(&message, &[&pair[0].0]);
|
||||
PublicTransaction::new(message, witness_set)
|
||||
})
|
||||
.collect();
|
||||
|
||||
public_txs
|
||||
}
|
||||
|
||||
/// Generates a sequencer configuration with initial balance in a number of public accounts.
|
||||
/// The transactions generated with the function `build_public_txs` will be valid in a node started
|
||||
/// with the config from this method.
|
||||
pub(crate) fn generate_tps_test_config(&self) -> SequencerConfig {
|
||||
// Create public public keypairs
|
||||
let initial_public_accounts = self
|
||||
.public_keypairs
|
||||
.iter()
|
||||
.map(|(_, addr)| AccountInitialData {
|
||||
addr: addr.to_string(),
|
||||
balance: 10,
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Generate an initial commitment to be used with the privacy preserving transaction
|
||||
// created with the `build_privacy_transaction` function.
|
||||
let sender_nsk = [1; 32];
|
||||
let sender_npk = NullifierPublicKey::from(&sender_nsk);
|
||||
let account = Account {
|
||||
balance: 100,
|
||||
nonce: 0xdeadbeef,
|
||||
program_owner: Program::authenticated_transfer_program().id(),
|
||||
data: vec![],
|
||||
};
|
||||
let initial_commitment = CommitmentsInitialData {
|
||||
npk: sender_npk,
|
||||
account,
|
||||
};
|
||||
|
||||
SequencerConfig {
|
||||
home: ".".into(),
|
||||
override_rust_log: None,
|
||||
genesis_id: 1,
|
||||
is_genesis_random: true,
|
||||
max_num_tx_in_block: 300,
|
||||
mempool_max_size: 10000,
|
||||
block_create_timeout_millis: 12000,
|
||||
port: 3040,
|
||||
initial_accounts: initial_public_accounts,
|
||||
initial_commitments: vec![initial_commitment],
|
||||
signing_key: [37; 32],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a single privacy transaction to use in stress tests. This involves generating a proof so
|
||||
/// it may take a while to run. In normal execution of the node this transaction will be accepted
|
||||
/// only once. Disabling the node's nullifier uniqueness check allows to submit this transaction
|
||||
/// multiple times with the purpose of testing the node's processing performance.
|
||||
#[allow(unused)]
|
||||
fn build_privacy_transaction() -> PrivacyPreservingTransaction {
|
||||
let program = Program::authenticated_transfer_program();
|
||||
let sender_nsk = [1; 32];
|
||||
let sender_isk = [99; 32];
|
||||
let sender_ipk = IncomingViewingPublicKey::from_scalar(sender_isk);
|
||||
let sender_npk = NullifierPublicKey::from(&sender_nsk);
|
||||
let sender_pre = AccountWithMetadata::new(
|
||||
Account {
|
||||
balance: 100,
|
||||
nonce: 0xdeadbeef,
|
||||
program_owner: program.id(),
|
||||
data: vec![],
|
||||
},
|
||||
true,
|
||||
AccountId::from(&sender_npk),
|
||||
);
|
||||
let recipient_nsk = [2; 32];
|
||||
let recipient_isk = [99; 32];
|
||||
let recipient_ipk = IncomingViewingPublicKey::from_scalar(recipient_isk);
|
||||
let recipient_npk = NullifierPublicKey::from(&recipient_nsk);
|
||||
let recipient_pre =
|
||||
AccountWithMetadata::new(Account::default(), false, AccountId::from(&recipient_npk));
|
||||
|
||||
let eph_holder_from = EphemeralKeyHolder::new(&sender_npk);
|
||||
let sender_ss = eph_holder_from.calculate_shared_secret_sender(&sender_ipk);
|
||||
let sender_epk = eph_holder_from.generate_ephemeral_public_key();
|
||||
|
||||
let eph_holder_to = EphemeralKeyHolder::new(&recipient_npk);
|
||||
let recipient_ss = eph_holder_to.calculate_shared_secret_sender(&recipient_ipk);
|
||||
let recipient_epk = eph_holder_from.generate_ephemeral_public_key();
|
||||
|
||||
let balance_to_move: u128 = 1;
|
||||
let proof: MembershipProof = (
|
||||
1,
|
||||
vec![[
|
||||
170, 10, 217, 228, 20, 35, 189, 177, 238, 235, 97, 129, 132, 89, 96, 247, 86, 91, 222,
|
||||
214, 38, 194, 216, 67, 56, 251, 208, 226, 0, 117, 149, 39,
|
||||
]],
|
||||
);
|
||||
let (output, proof) = circuit::execute_and_prove(
|
||||
&[sender_pre, recipient_pre],
|
||||
&Program::serialize_instruction(balance_to_move).unwrap(),
|
||||
&[1, 2],
|
||||
&[0xdeadbeef1, 0xdeadbeef2],
|
||||
&[
|
||||
(sender_npk.clone(), sender_ss),
|
||||
(recipient_npk.clone(), recipient_ss),
|
||||
],
|
||||
&[(sender_nsk, proof)],
|
||||
&program,
|
||||
)
|
||||
.unwrap();
|
||||
let message = pptx::message::Message::try_from_circuit_output(
|
||||
vec![],
|
||||
vec![],
|
||||
vec![
|
||||
(sender_npk, sender_ipk, sender_epk),
|
||||
(recipient_npk, recipient_ipk, recipient_epk),
|
||||
],
|
||||
output,
|
||||
)
|
||||
.unwrap();
|
||||
let witness_set = pptx::witness_set::WitnessSet::for_message(&message, proof, &[]);
|
||||
pptx::PrivacyPreservingTransaction::new(message, witness_set)
|
||||
}
|
||||
@ -28,6 +28,8 @@ pub struct SequencerConfig {
|
||||
pub is_genesis_random: bool,
|
||||
///Maximum number of transactions in block
|
||||
pub max_num_tx_in_block: usize,
|
||||
///Mempool maximum size
|
||||
pub mempool_max_size: usize,
|
||||
///Interval in which blocks produced
|
||||
pub block_create_timeout_millis: u64,
|
||||
///Port to listen
|
||||
|
||||
@ -103,7 +103,7 @@ impl SequencerCore {
|
||||
})?;
|
||||
|
||||
let mempool_size = self.mempool.len();
|
||||
if mempool_size >= self.sequencer_config.max_num_tx_in_block {
|
||||
if mempool_size >= self.sequencer_config.mempool_max_size {
|
||||
return Err(TransactionMalformationError::MempoolFullForRound);
|
||||
}
|
||||
|
||||
@ -218,6 +218,7 @@ mod tests {
|
||||
genesis_id: 1,
|
||||
is_genesis_random: false,
|
||||
max_num_tx_in_block: 10,
|
||||
mempool_max_size: 10000,
|
||||
block_create_timeout_millis: 1000,
|
||||
port: 8080,
|
||||
initial_accounts,
|
||||
@ -511,7 +512,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_push_tx_into_mempool_fails_mempool_full() {
|
||||
let config = SequencerConfig {
|
||||
max_num_tx_in_block: 1,
|
||||
mempool_max_size: 1,
|
||||
..setup_sequencer_config()
|
||||
};
|
||||
let mut sequencer = SequencerCore::start_from_config(config);
|
||||
|
||||
@ -357,6 +357,7 @@ mod tests {
|
||||
genesis_id: 1,
|
||||
is_genesis_random: false,
|
||||
max_num_tx_in_block: 10,
|
||||
mempool_max_size: 1000,
|
||||
block_create_timeout_millis: 1000,
|
||||
port: 8080,
|
||||
initial_accounts,
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
"genesis_id": 1,
|
||||
"is_genesis_random": true,
|
||||
"max_num_tx_in_block": 20,
|
||||
"mempool_max_size": 1000,
|
||||
"block_create_timeout_millis": 10000,
|
||||
"port": 3040,
|
||||
"initial_accounts": [
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user