From 127d59bf9ecc0001a4eacc91506f70013eb101a8 Mon Sep 17 00:00:00 2001 From: Angel Petrov <146711006+Angel-Petrov@users.noreply.github.com> Date: Thu, 18 Jan 2024 15:50:37 +0200 Subject: [PATCH 01/13] Replace dotenv with dotenvy --- Cargo.toml | 2 +- src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 227b2a5..b1efa64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ futures = "0.3" clap = { version = "4.2.7", features = ["derive"] } color-eyre = "0.6.2" config = "0.13.3" -dotenv = "0.15.0" +dotenvy = "0.15.7" serde = "1.0.163" serde_derive = "1.0.163" serde_json = { version = "1.0.96", features = ["preserve_order"] } diff --git a/src/main.rs b/src/main.rs index 9ef2672..56e7d06 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ extern crate log; use clap::Parser; use color_eyre::eyre::Result; -use dotenv::dotenv; +use dotenvy::dotenv; use gatling::{ actions::shoot::shoot, cli::{Cli, Command}, From c85f6a9ced89e818aa595b7a973ac1ed76a2bc65 Mon Sep 17 00:00:00 2001 From: Angel Petrov <146711006+Angel-Petrov@users.noreply.github.com> Date: Thu, 18 Jan 2024 15:54:58 +0200 Subject: [PATCH 02/13] Remove statrs dep This dependancy has not been mantained for over a year and is only used to calculate a mean --- Cargo.toml | 1 - src/metrics.rs | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b1efa64..2db5f75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,4 +33,3 @@ rand = { version = "0.8.5", features = ["rand_chacha"] } lazy_static = "1.4.0" colored = "2.0.4" sysinfo = "0.29.8" -statrs = "0.16.0" diff --git a/src/metrics.rs b/src/metrics.rs index 2a04fdf..c1507cf 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -4,7 +4,6 @@ use color_eyre::Result; use serde_json::{json, Value}; use starknet::providers::{jsonrpc::HttpTransport, JsonRpcClient, Provider}; -use statrs::statistics::Statistics; use std::{fmt, sync::Arc}; pub const BLOCK_TIME: u64 = 6; @@ -143,7 +142,7 @@ fn average_tps(num_tx_per_block: &[u64]) -> f64 { } fn average_tpb(num_tx_per_block: &[u64]) -> f64 { - num_tx_per_block.iter().map(|x| *x as f64).mean() + num_tx_per_block.iter().sum::() as f64 / num_tx_per_block.len() as f64 } pub fn compute_all_metrics(num_tx_per_block: Vec) -> Vec { From cb4c1f31eccf44ba64f000d845bc61bc7a66d902 Mon Sep 17 00:00:00 2001 From: Angel Petrov <146711006+Angel-Petrov@users.noreply.github.com> Date: Thu, 18 Jan 2024 16:03:20 +0200 Subject: [PATCH 03/13] Minor refactors --- src/actions/shoot.rs | 2 +- src/cli/mod.rs | 2 +- src/metrics.rs | 83 +++++++++++++++++++++++--------------------- src/utils.rs | 26 +++++++++----- 4 files changed, 63 insertions(+), 50 deletions(-) diff --git a/src/actions/shoot.rs b/src/actions/shoot.rs index 91b9c3e..b6d17b5 100644 --- a/src/actions/shoot.rs +++ b/src/actions/shoot.rs @@ -207,7 +207,7 @@ impl GatlingShooter { std::fs::create_dir_all(&self.config.report.reports_dir)?; let writer = std::fs::File::create(report_path)?; - serde_json::to_writer(writer, &report.to_json()?)?; + serde_json::to_writer(writer, &report.to_json())?; } Ok(()) diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 4b549d3..04d2f5e 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -3,7 +3,7 @@ // Imports use clap::{Args, Parser, Subcommand}; -const VERSION_STRING: &str = concat!(env!("CARGO_PKG_VERSION")); +const VERSION_STRING: &str = env!("CARGO_PKG_VERSION"); /// Main CLI struct #[derive(Parser, Debug)] diff --git a/src/metrics.rs b/src/metrics.rs index c1507cf..f2c6a36 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -1,10 +1,10 @@ -use crate::utils::{get_num_tx_per_block, SYSINFO}; +use crate::utils::{get_num_tx_per_block, SysInfo, SYSINFO}; use color_eyre::Result; use serde_json::{json, Value}; use starknet::providers::{jsonrpc::HttpTransport, JsonRpcClient, Provider}; -use std::{fmt, sync::Arc}; +use std::{fmt, ops::Deref, sync::Arc}; pub const BLOCK_TIME: u64 = 6; @@ -50,12 +50,9 @@ impl BenchmarkReport { pub async fn from_block_range<'a>( starknet_rpc: Arc>, name: String, - start_block: u64, - end_block: u64, + mut start_block: u64, + mut end_block: u64, ) -> Result { - let mut start_block = start_block; - let mut end_block = end_block; - // Whenever possible, skip the first and last blocks from the metrics // to make sure all the blocks used for calculating metrics are full if end_block - start_block > 2 { @@ -84,53 +81,59 @@ impl BenchmarkReport { Ok(BenchmarkReport { name, metrics }) } - pub fn to_json(&self) -> Result { - let sysinfo_string = format!( - "CPU Count: {}\n\ - CPU Model: {}\n\ - CPU Speed (MHz): {}\n\ - Total Memory: {} GB\n\ - Platform: {}\n\ - Release: {}\n\ - Architecture: {}", - SYSINFO.cpu_count, - SYSINFO.cpu_frequency, - SYSINFO.cpu_brand, - SYSINFO.memory / (1024 * 1024 * 1024), - SYSINFO.os_name, - SYSINFO.kernel_version, - SYSINFO.arch - ); + pub fn to_json(&self) -> Value { + let SysInfo { + os_name, + kernel_version, + arch, + cpu_count, + cpu_frequency, + cpu_brand, + memory, + } = SYSINFO.deref(); - let mut report = vec![]; - - for metric in self.metrics.iter() { - report.push(json!({ - "name": metric.name, - "unit": metric.unit, - "value": metric.value, - "extra": sysinfo_string - })); - } + let gigabyte_memory = memory / (1024 * 1024 * 1024); - let report_json = serde_json::to_value(report)?; + let sysinfo_string = format!( + "CPU Count: {cpu_count}\n\ + CPU Model: {cpu_brand}\n\ + CPU Speed (MHz): {cpu_frequency}\n\ + Total Memory: {gigabyte_memory} GB\n\ + Platform: {os_name}\n\ + Release: {kernel_version}\n\ + Architecture: {arch}", + ); - Ok(report_json) + self.metrics + .iter() + .map(|metric| { + json!({ + "name": metric.name, + "unit": metric.unit, + "value": metric.value, + "extra": sysinfo_string + }) + }) + .collect::() } } impl fmt::Display for MetricResult { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}: {} {}", self.name, self.value, self.unit) + let Self { name, value, unit } = self; + + write!(f, "{name}: {value} {unit}") } } impl fmt::Display for BenchmarkReport { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "Benchmark Report: {}", self.name)?; + let Self { name, metrics } = self; + + writeln!(f, "Benchmark Report: {name}")?; - for metric in &self.metrics { - writeln!(f, "{}", metric)?; + for metric in metrics { + writeln!(f, "{metric}")?; } Ok(()) diff --git a/src/utils.rs b/src/utils.rs index 5b30b27..51904e3 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -92,16 +92,26 @@ impl Default for SysInfo { impl fmt::Display for SysInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + os_name, + kernel_version, + arch, + cpu_count, + cpu_frequency, + cpu_brand, + memory, + } = self; + + let cpu_ghz_freq = *cpu_frequency as f64 / 1000.0; + let gigabyte_memory = memory / (1024 * 1024 * 1024); + writeln!( f, - "System Information:\nSystem : {} Kernel Version {}\nArch : {}\nCPU : {} {:.2}GHz {} cores\nMemory : {} GB", - self.os_name, - self.kernel_version, - self.arch, - self.cpu_brand, - format!("{:.2} GHz", self.cpu_frequency as f64 / 1000.0), - self.cpu_count, - self.memory / (1024 * 1024 * 1024) + "System Information:\n\ + System : {os_name} Kernel Version {kernel_version}\n\ + Arch : {arch}\n\ + CPU : {cpu_brand} {cpu_ghz_freq:.2} GHz {cpu_count} cores\n\ + Memory : {gigabyte_memory} GB" ) } } From 53509270ca3d7fb735b6e0d9c6cb9513eb5010e6 Mon Sep 17 00:00:00 2001 From: Angel Petrov Date: Wed, 24 Jan 2024 11:16:31 +0200 Subject: [PATCH 04/13] Remove `num_cpus` dep --- Cargo.toml | 1 - src/actions/shoot.rs | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2db5f75..06ba7ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,6 @@ opt-level = 3 # Starknet dependencies starknet = "0.6.0" -num_cpus = "1.0" env_logger = "0.10.0" log = "0.4.17" tokio = { version = "1", features = ["full"] } diff --git a/src/actions/shoot.rs b/src/actions/shoot.rs index b6d17b5..c571bb3 100644 --- a/src/actions/shoot.rs +++ b/src/actions/shoot.rs @@ -51,8 +51,7 @@ pub async fn shoot(config: GatlingConfig) -> Result { // Trigger the setup phase. shooter.setup(&mut gatling_report).await?; - let threads = std::cmp::min(num_cpus::get(), config.run.concurrency as usize); - info!("Using {} threads", threads); + info!("Using {} threads", config.run.concurrency); // Run the benchmarks. shooter.run(&mut gatling_report).await?; From c2f17a8ce2e58d26a7249161d6496e6725351418 Mon Sep 17 00:00:00 2001 From: Angel Petrov Date: Thu, 25 Jan 2024 15:18:21 +0200 Subject: [PATCH 05/13] Goose reimpl of shoot, rough first draft --- Cargo.toml | 2 + src/actions/goose.rs | 237 +++++++++++++++++++++++++++++++++++++++++++ src/actions/mod.rs | 1 + src/actions/shoot.rs | 24 ++--- src/cli/mod.rs | 2 + src/main.rs | 6 +- 6 files changed, 259 insertions(+), 13 deletions(-) create mode 100644 src/actions/goose.rs diff --git a/Cargo.toml b/Cargo.toml index 06ba7ba..ba3eecf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ opt-level = 3 # Starknet dependencies starknet = "0.6.0" +goose = "0.17.2" env_logger = "0.10.0" log = "0.4.17" tokio = { version = "1", features = ["full"] } @@ -32,3 +33,4 @@ rand = { version = "0.8.5", features = ["rand_chacha"] } lazy_static = "1.4.0" colored = "2.0.4" sysinfo = "0.29.8" +crossbeam-queue = "0.3.11" diff --git a/src/actions/goose.rs b/src/actions/goose.rs new file mode 100644 index 0000000..155eb6f --- /dev/null +++ b/src/actions/goose.rs @@ -0,0 +1,237 @@ +use std::{ + mem::{self, size_of}, + sync::{ + atomic::{AtomicU64, Ordering}, + Arc, + }, +}; + +use color_eyre::owo_colors::OwoColorize; +use crossbeam_queue::ArrayQueue; +use goose::{config::GooseConfiguration, prelude::*}; +use log::trace; +use rand::seq::SliceRandom; +use serde_derive::Serialize; +use serde_json::json; +use starknet::{ + accounts::{ + Account, Call, ConnectedAccount, ExecutionEncoder, ExecutionEncoding, RawExecution, + SingleOwnerAccount, + }, + core::types::FieldElement, + macros::{felt, selector}, + providers::{ + jsonrpc::{HttpTransport, JsonRpcMethod}, + JsonRpcClient, + }, + signers::{LocalWallet, SigningKey}, +}; +use url::Url; + +use crate::{ + actions::shoot::{self, GatlingShooter, CHECK_INTERVAL, MAX_FEE}, + config::GatlingConfig, + generators::get_rng, + utils::wait_for_tx, +}; + +use super::shoot::GatlingReport; + +pub async fn goose(config: GatlingConfig) -> color_eyre::Result<()> { + let mut shooter = GatlingShooter::from_config(config.clone()).await?; + shooter.setup().await?; + let accounts: Arc<[_]> = shooter + .environment()? + .accounts + .iter() + .map(|x| x.address()) + .collect::>() + .into(); + let nonces = Arc::new(ArrayQueue::new(config.run.num_erc721_mints as usize)); + let erc721_address = shooter.environment().unwrap().erc721_address; + + // shooter.run_erc20(config.run.num_erc20_transfers).await; + + let mut nonce = shooter.account.get_nonce().await?; + + for _ in 0..config.run.num_erc721_mints { + nonces + .push(nonce) + .expect("ArrayQueue has capacity for all mints"); + nonce += FieldElement::ONE; + } + + let goose_mint_config = { + let mut default = GooseConfiguration::default(); + default.host = config.rpc.url; + default.iterations = (config.run.num_erc721_mints / config.run.concurrency) as usize; + default.users = Some(config.run.concurrency as usize); + default.report_file = String::from("./report.html"); + default + }; + + let queue = Arc::new(ArrayQueue::new(config.run.num_erc721_mints as usize)); + let queue_mint = queue.clone(); + let queue_mint_verify = queue_mint.clone(); + + let last_mint = Arc::new([ + AtomicU64::new(0), + AtomicU64::new(0), + AtomicU64::new(0), + AtomicU64::new(0), + ]); + let last_mint_clone = last_mint.clone(); + + let mint: TransactionFunction = Arc::new(move |user| { + let queue = queue_mint.clone(); + let nonces = nonces.clone(); + let nonce = nonces.pop().unwrap(); + let mut rng = rand::thread_rng(); + let account = *accounts.choose(&mut rng).unwrap(); + let last_mint = last_mint_clone.clone(); + let from_account = shooter.account.clone(); + Box::pin(async move { + mint( + user, + &queue, + erc721_address, + nonce, + &from_account, + account, + &last_mint, + ) + .await + }) + }); + + #[allow(unused_variables)] + let mint_verify: TransactionFunction = Arc::new(move |user| { + let queue = queue_mint_verify.clone(); + Box::pin(async move { mint_verify(user, &queue).await }) + }); + + GooseAttack::initialize_with_config(goose_mint_config.clone())? + .register_scenario(scenario!("Mint").register_transaction(transaction!(mint))) + // .register_scenario(scenario!("Mint Verify").register_transaction(transaction!(mint_verify))) + .execute() + .await?; + + // Wait for the last transaction to be incorporated in a block + // wait_for_tx( + // &shooter.starknet_rpc, + // queue.pop().unwrap(), + // // FieldElement::from_mont(Arc::try_unwrap(last_mint).unwrap().map(|x| x.into_inner())), + // CHECK_INTERVAL, + // ) + // .await?; + + // GooseAttack::initialize_with_config(goose_mint_config)? + // .register_scenario(scenario!("Mint Verify").register_transaction(transaction!(mint_verify))) + // .execute() + // .await?; + + // todo!() + + Ok(()) +} + +async fn mint( + user: &mut GooseUser, + queue: &ArrayQueue, + erc721_address: FieldElement, + nonce: FieldElement, + from_account: &SingleOwnerAccount>, LocalWallet>, + account: FieldElement, + prev_hash: &[AtomicU64; 4], +) -> TransactionResult { + let (token_id_low, token_id_high) = (get_rng(), felt!("0x0000")); + + let call = Call { + to: erc721_address, + selector: selector!("mint"), + calldata: vec![ + account, // recipient + token_id_low, + token_id_high, + ], + }; + + #[allow(dead_code)] + pub struct FakeRawExecution { + calls: Vec, + nonce: FieldElement, + max_fee: FieldElement, + } + + let raw_exec = FakeRawExecution { + calls: vec![call.clone()], + nonce, + max_fee: MAX_FEE, + }; + + // this needs to be removed later, however we can't construct RawExecution ourselves + let raw_exec = unsafe { mem::transmute::(raw_exec) }; + + let params = starknet::core::types::BroadcastedInvokeTransaction { + sender_address: from_account.address(), + calldata: from_account.encode_calls(&[call.clone()]), + max_fee: MAX_FEE, + signature: from_account.sign_execution(&raw_exec).await.unwrap(), + nonce, + is_query: false, + }; + + let request = JsonRpcRequest { + id: 1, + jsonrpc: "2.0", + method: JsonRpcMethod::AddInvokeTransaction, + params: [params], + }; + + let goose_response = user + .post_json("/", &request) + .await? + .response + .map_err(TransactionError::Reqwest)?; + + let res: starknet::providers::jsonrpc::JsonRpcResponse< + starknet::core::types::InvokeTransactionResult, + > = goose_response.json().await.unwrap(); + + let hash = match res { + starknet::providers::jsonrpc::JsonRpcResponse::Success { result, .. } => result, + // Actually returning this error would probably be a good idea, but we can't for now + starknet::providers::jsonrpc::JsonRpcResponse::Error { error, .. } => panic!("{error}"), + } + .transaction_hash; + + queue.push(hash).unwrap(); + + for (atomic, store) in prev_hash.iter().zip(hash.into_mont()) { + atomic.store(store, Ordering::Relaxed) + } + + // Should ideally be replaced with: + // let result = from_account + // .execute(vec![call]) + // .max_fee(MAX_FEE) + // .nonce(nonce) + // .to_json() + + Ok(()) +} + +// Copied from https://docs.rs/starknet-providers/0.9.0/src/starknet_providers/jsonrpc/transports/http.rs.html#21-27 +#[derive(Debug, Serialize)] +struct JsonRpcRequest { + id: u64, + jsonrpc: &'static str, + method: JsonRpcMethod, + params: T, +} + +async fn mint_verify(user: &mut GooseUser, queue: &ArrayQueue) -> TransactionResult { + println!("{:?}", queue.pop()); + + Ok(()) +} diff --git a/src/actions/mod.rs b/src/actions/mod.rs index 35d8fb5..8d42c59 100644 --- a/src/actions/mod.rs +++ b/src/actions/mod.rs @@ -1 +1,2 @@ +pub mod goose; pub mod shoot; diff --git a/src/actions/shoot.rs b/src/actions/shoot.rs index c571bb3..6180bc4 100644 --- a/src/actions/shoot.rs +++ b/src/actions/shoot.rs @@ -49,7 +49,7 @@ pub async fn shoot(config: GatlingConfig) -> Result { let mut shooter = GatlingShooter::from_config(config.clone()).await?; let mut gatling_report = Default::default(); // Trigger the setup phase. - shooter.setup(&mut gatling_report).await?; + shooter.setup().await?; info!("Using {} threads", config.run.concurrency); @@ -63,19 +63,19 @@ pub async fn shoot(config: GatlingConfig) -> Result { } pub struct GatlingShooter { - config: GatlingConfig, - starknet_rpc: Arc>, - signer: LocalWallet, - account: StarknetAccount, - nonces: HashMap, - environment: Option, // Will be populated in setup phase + pub config: GatlingConfig, + pub starknet_rpc: Arc>, + pub signer: LocalWallet, + pub account: StarknetAccount, + pub nonces: HashMap, + pub environment: Option, // Will be populated in setup phase } #[derive(Clone)] pub struct GatlingEnvironment { - _erc20_address: FieldElement, - erc721_address: FieldElement, - accounts: Vec, + pub _erc20_address: FieldElement, + pub erc721_address: FieldElement, + pub accounts: Vec, } impl GatlingShooter { @@ -137,7 +137,7 @@ impl GatlingShooter { } /// Setup the simulation. - async fn setup<'a>(&mut self, _gatling_report: &'a mut GatlingReport) -> Result<()> { + pub async fn setup(&mut self) -> Result<()> { let chain_id = self.starknet_rpc.chain_id().await?.to_bytes_be(); let block_number = self.starknet_rpc.block_number().await?; info!( @@ -390,7 +390,7 @@ impl GatlingShooter { Ok(()) } - async fn run_erc20(&mut self, num_transfers: u64) -> (Vec, Vec) { + pub async fn run_erc20(&mut self, num_transfers: u64) -> (Vec, Vec) { info!("Sending {num_transfers} ERC20 transfer transactions ..."); let start = SystemTime::now(); diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 04d2f5e..5557625 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -27,6 +27,8 @@ pub struct Cli { pub enum Command { /// Trigger a load test. Shoot {}, + /// Trigger goose load test. + Goose {}, } #[derive(Debug, Args)] diff --git a/src/main.rs b/src/main.rs index 56e7d06..84f528d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ use clap::Parser; use color_eyre::eyre::Result; use dotenvy::dotenv; use gatling::{ - actions::shoot::shoot, + actions::{self, shoot::shoot}, cli::{Cli, Command}, config::GatlingConfig, }; @@ -37,6 +37,10 @@ async fn main() -> Result<()> { let gatling_report = shoot(cfg).await?; info!("Gatling completed: {:#?}", gatling_report); } + Command::Goose { .. } => { + actions::goose::goose(cfg).await?; + } } + Ok(()) } From a7e68f09d4d7d61d330c96809a0106f12e7ab3d6 Mon Sep 17 00:00:00 2001 From: Angel Petrov Date: Fri, 2 Feb 2024 16:21:45 +0200 Subject: [PATCH 06/13] Goose rewrite of shoot --- src/actions/goose.rs | 387 +++++++++++++++++++++++++++---------- src/actions/mod.rs | 18 +- src/actions/shoot.rs | 449 +++---------------------------------------- src/cli/mod.rs | 2 - src/config.rs | 1 - src/main.rs | 8 +- src/metrics.rs | 6 + src/utils.rs | 5 +- 8 files changed, 338 insertions(+), 538 deletions(-) diff --git a/src/actions/goose.rs b/src/actions/goose.rs index 155eb6f..5c47b4e 100644 --- a/src/actions/goose.rs +++ b/src/actions/goose.rs @@ -1,58 +1,111 @@ use std::{ - mem::{self, size_of}, + mem, sync::{ atomic::{AtomicU64, Ordering}, Arc, }, }; -use color_eyre::owo_colors::OwoColorize; use crossbeam_queue::ArrayQueue; use goose::{config::GooseConfiguration, prelude::*}; -use log::trace; -use rand::seq::SliceRandom; -use serde_derive::Serialize; -use serde_json::json; +use serde::{de::DeserializeOwned, Serialize}; use starknet::{ accounts::{ - Account, Call, ConnectedAccount, ExecutionEncoder, ExecutionEncoding, RawExecution, - SingleOwnerAccount, + Account, Call, ConnectedAccount, ExecutionEncoder, RawExecution, SingleOwnerAccount, + }, + core::types::{ + ExecutionResult, FieldElement, InvokeTransactionResult, MaybePendingTransactionReceipt, }, - core::types::FieldElement, macros::{felt, selector}, providers::{ - jsonrpc::{HttpTransport, JsonRpcMethod}, - JsonRpcClient, + jsonrpc::{ + HttpTransport, HttpTransportError, JsonRpcClientError, JsonRpcMethod, JsonRpcResponse, + }, + JsonRpcClient, ProviderError, }, - signers::{LocalWallet, SigningKey}, + signers::LocalWallet, }; -use url::Url; use crate::{ - actions::shoot::{self, GatlingShooter, CHECK_INTERVAL, MAX_FEE}, - config::GatlingConfig, + actions::shoot::{GatlingShooterSetup, CHECK_INTERVAL, MAX_FEE}, generators::get_rng, - utils::wait_for_tx, }; -use super::shoot::GatlingReport; - -pub async fn goose(config: GatlingConfig) -> color_eyre::Result<()> { - let mut shooter = GatlingShooter::from_config(config.clone()).await?; - shooter.setup().await?; - let accounts: Arc<[_]> = shooter - .environment()? - .accounts - .iter() - .map(|x| x.address()) - .collect::>() - .into(); - let nonces = Arc::new(ArrayQueue::new(config.run.num_erc721_mints as usize)); - let erc721_address = shooter.environment().unwrap().erc721_address; +use super::shoot::StarknetAccount; + +pub async fn erc20(shooter: &GatlingShooterSetup) -> color_eyre::Result<()> { + let _erc20_address = shooter.environment().unwrap().erc20_address; + let config = shooter.config(); + + let goose_config = { + let mut default = GooseConfiguration::default(); + default.host = config.rpc.url.clone(); + default.iterations = (config.run.num_erc20_transfers / config.run.concurrency) as usize; + default.users = Some(config.run.concurrency as usize); + default + }; + + let queue = Arc::new(ArrayQueue::new(config.run.num_erc20_transfers as usize)); + let queue_trans = queue.clone(); + let queue_trans_verify = queue_trans.clone(); + + let last_trans = Arc::new([ + AtomicU64::new(0), + AtomicU64::new(0), + AtomicU64::new(0), + AtomicU64::new(0), + ]); + let last_transaction_clone = last_trans.clone(); + + let transfer: TransactionFunction = Arc::new(move |user| { + let queue = queue_trans.clone(); + let last_mint = last_transaction_clone.clone(); + Box::pin(async move { transfer(user, &queue, _erc20_address, &last_mint).await }) + }); + + let transfer_verify: TransactionFunction = Arc::new(move |user| { + let queue = queue_trans_verify.clone(); + Box::pin(async move { verify_transacs(user, &queue).await }) + }); + + let transfer_setup = setup(shooter.environment()?.accounts.clone()).await?; + + GooseAttack::initialize_with_config(goose_config.clone())? + .register_scenario( + scenario!("Transactions") + .register_transaction( + transaction!(transfer_setup) + .set_name("Setup") + .set_on_start(), + ) + .register_transaction(transaction!(transfer).set_name("Transfer")), + ) + .execute() + .await?; + + // Wait for the last transaction to be incorporated in a block + shooter + .wait_for_tx( + FieldElement::from_mont(Arc::try_unwrap(last_trans).unwrap().map(|x| x.into_inner())), + CHECK_INTERVAL, + ) + .await?; + + GooseAttack::initialize_with_config(goose_config)? + .register_scenario( + scenario!("Transactions Verify").register_transaction(transaction!(transfer_verify)), + ) + .execute() + .await?; - // shooter.run_erc20(config.run.num_erc20_transfers).await; + Ok(()) +} - let mut nonce = shooter.account.get_nonce().await?; +pub async fn erc721(shooter: &GatlingShooterSetup) -> color_eyre::Result<()> { + let config = shooter.config(); + let nonces = Arc::new(ArrayQueue::new(config.run.num_erc721_mints as usize)); + let erc721_address = shooter.environment().unwrap().erc721_address; + let mut nonce = shooter.deployer_account().get_nonce().await?; for _ in 0..config.run.num_erc721_mints { nonces @@ -63,10 +116,9 @@ pub async fn goose(config: GatlingConfig) -> color_eyre::Result<()> { let goose_mint_config = { let mut default = GooseConfiguration::default(); - default.host = config.rpc.url; + default.host = config.rpc.url.clone(); default.iterations = (config.run.num_erc721_mints / config.run.concurrency) as usize; default.users = Some(config.run.concurrency as usize); - default.report_file = String::from("./report.html"); default }; @@ -74,22 +126,22 @@ pub async fn goose(config: GatlingConfig) -> color_eyre::Result<()> { let queue_mint = queue.clone(); let queue_mint_verify = queue_mint.clone(); - let last_mint = Arc::new([ + let last_trans = Arc::new([ AtomicU64::new(0), AtomicU64::new(0), AtomicU64::new(0), AtomicU64::new(0), ]); - let last_mint_clone = last_mint.clone(); + let last_mint_clone = last_trans.clone(); + + let from_account = shooter.deployer_account().clone(); let mint: TransactionFunction = Arc::new(move |user| { let queue = queue_mint.clone(); let nonces = nonces.clone(); let nonce = nonces.pop().unwrap(); - let mut rng = rand::thread_rng(); - let account = *accounts.choose(&mut rng).unwrap(); let last_mint = last_mint_clone.clone(); - let from_account = shooter.account.clone(); + let from_account = from_account.clone(); Box::pin(async move { mint( user, @@ -97,141 +149,266 @@ pub async fn goose(config: GatlingConfig) -> color_eyre::Result<()> { erc721_address, nonce, &from_account, - account, &last_mint, ) .await }) }); - #[allow(unused_variables)] let mint_verify: TransactionFunction = Arc::new(move |user| { let queue = queue_mint_verify.clone(); - Box::pin(async move { mint_verify(user, &queue).await }) + Box::pin(async move { verify_transacs(user, &queue).await }) }); + let mint_setup = setup(shooter.environment()?.accounts.clone()).await?; + GooseAttack::initialize_with_config(goose_mint_config.clone())? - .register_scenario(scenario!("Mint").register_transaction(transaction!(mint))) - // .register_scenario(scenario!("Mint Verify").register_transaction(transaction!(mint_verify))) + .register_scenario( + scenario!("Minting") + .register_transaction(transaction!(mint_setup).set_name("Setup").set_on_start()) + .register_transaction(transaction!(mint).set_name("Minting")), + ) .execute() .await?; // Wait for the last transaction to be incorporated in a block - // wait_for_tx( - // &shooter.starknet_rpc, - // queue.pop().unwrap(), - // // FieldElement::from_mont(Arc::try_unwrap(last_mint).unwrap().map(|x| x.into_inner())), - // CHECK_INTERVAL, - // ) - // .await?; - - // GooseAttack::initialize_with_config(goose_mint_config)? - // .register_scenario(scenario!("Mint Verify").register_transaction(transaction!(mint_verify))) - // .execute() - // .await?; + shooter + .wait_for_tx( + FieldElement::from_mont(Arc::try_unwrap(last_trans).unwrap().map(|x| x.into_inner())), + CHECK_INTERVAL, + ) + .await?; + + GooseAttack::initialize_with_config(goose_mint_config)? + .register_scenario(scenario!("Mint Verify").register_transaction(transaction!(mint_verify))) + .execute() + .await?; // todo!() Ok(()) } +#[derive(Debug, Clone)] +struct GooseUserState { + account: StarknetAccount, + nonce: FieldElement, +} + +pub type RpcError = ProviderError>; + +impl GooseUserState { + pub async fn new(account: StarknetAccount) -> Result { + Ok(Self { + nonce: account.get_nonce().await?, + account, + }) + } +} + +async fn setup(accounts: Vec) -> Result { + let queue = ArrayQueue::new(accounts.len()); + for account in accounts { + queue.push(GooseUserState::new(account).await?).unwrap(); + } + let queue = Arc::new(queue); + + Ok(Arc::new(move |user| { + let queue = queue.clone(); + user.set_session_data( + queue + .pop() + .expect("Not enough accounts were created for the amount of users"), + ); + + Box::pin(async { Ok(()) }) + })) +} + +async fn transfer( + user: &mut GooseUser, + queue: &ArrayQueue, + erc20_address: FieldElement, + prev_hash: &[AtomicU64; 4], +) -> TransactionResult { + let GooseUserState { + account, + nonce: state_nonce, + } = user.get_session_data_mut::().unwrap(); + + let nonce = *state_nonce; + *state_nonce += FieldElement::ONE; + let account = account.clone(); + + let (amount_low, amount_high) = (felt!("1"), felt!("0")); + + let call = Call { + to: erc20_address, + selector: selector!("transfer"), + calldata: vec![ + FieldElement::from_hex_be("0xdead").unwrap(), + amount_low, + amount_high, + ], + }; + + let response: InvokeTransactionResult = send_execution( + user, + vec![call], + nonce, + &account, + JsonRpcMethod::AddInvokeTransaction, + ) + .await?; + + queue.push(response.transaction_hash).unwrap(); + + for (atomic, store) in prev_hash.iter().zip(response.transaction_hash.into_mont()) { + atomic.store(store, Ordering::Relaxed) + } + + Ok(()) +} + async fn mint( user: &mut GooseUser, queue: &ArrayQueue, erc721_address: FieldElement, nonce: FieldElement, from_account: &SingleOwnerAccount>, LocalWallet>, - account: FieldElement, prev_hash: &[AtomicU64; 4], ) -> TransactionResult { + let recipient = user + .get_session_data::() + .unwrap() + .account + .clone() + .address(); + let (token_id_low, token_id_high) = (get_rng(), felt!("0x0000")); let call = Call { to: erc721_address, selector: selector!("mint"), calldata: vec![ - account, // recipient + recipient, // recipient token_id_low, token_id_high, ], }; + let response: InvokeTransactionResult = send_execution( + user, + vec![call], + nonce, + from_account, + JsonRpcMethod::AddInvokeTransaction, + ) + .await?; + + queue.push(response.transaction_hash).unwrap(); + + for (atomic, store) in prev_hash.iter().zip(response.transaction_hash.into_mont()) { + atomic.store(store, Ordering::Relaxed) + } + + Ok(()) +} + +async fn verify_transacs( + user: &mut GooseUser, + queue: &ArrayQueue, +) -> TransactionResult { + let transaction = queue.pop().unwrap(); + + let receipt: MaybePendingTransactionReceipt = + send_request(user, JsonRpcMethod::GetTransactionReceipt, transaction).await?; + + match receipt { + MaybePendingTransactionReceipt::Receipt(receipt) => match receipt.execution_result() { + ExecutionResult::Succeeded => Ok(()), + ExecutionResult::Reverted { reason } => { + panic!("Transaction {transaction:#064x} has been rejected/reverted: {reason}"); + } + }, + MaybePendingTransactionReceipt::PendingReceipt(_) => { + panic!("Transaction {transaction:#064x} is pending when no transactions should be") + } + } +} + +pub async fn send_execution( + user: &mut GooseUser, + calls: Vec, + nonce: FieldElement, + from_account: &SingleOwnerAccount>, LocalWallet>, + method: JsonRpcMethod, +) -> Result> { + let calldata = from_account.encode_calls(&calls); + #[allow(dead_code)] - pub struct FakeRawExecution { + struct FakeRawExecution { calls: Vec, nonce: FieldElement, max_fee: FieldElement, } let raw_exec = FakeRawExecution { - calls: vec![call.clone()], + calls, nonce, max_fee: MAX_FEE, }; - // this needs to be removed later, however we can't construct RawExecution ourselves + // TODO: We cannot right now construct RawExecution directly and need to use this hack + // see https://github.com/xJonathanLEI/starknet-rs/issues/538 let raw_exec = unsafe { mem::transmute::(raw_exec) }; - let params = starknet::core::types::BroadcastedInvokeTransaction { + let param = starknet::core::types::BroadcastedInvokeTransaction { sender_address: from_account.address(), - calldata: from_account.encode_calls(&[call.clone()]), + calldata, max_fee: MAX_FEE, signature: from_account.sign_execution(&raw_exec).await.unwrap(), nonce, is_query: false, }; + send_request(user, method, param).await +} + +pub async fn send_request( + user: &mut GooseUser, + method: JsonRpcMethod, + param: impl Serialize, +) -> Result> { + // Copied from https://docs.rs/starknet-providers/0.9.0/src/starknet_providers/jsonrpc/transports/http.rs.html#21-27 + #[derive(Debug, Serialize)] + struct JsonRpcRequest { + id: u64, + jsonrpc: &'static str, + method: JsonRpcMethod, + params: T, + } + let request = JsonRpcRequest { id: 1, jsonrpc: "2.0", - method: JsonRpcMethod::AddInvokeTransaction, - params: [params], + method, + params: [param], }; - let goose_response = user + let body = user .post_json("/", &request) .await? .response + .map_err(TransactionError::Reqwest)? + .json::>() + .await .map_err(TransactionError::Reqwest)?; - - let res: starknet::providers::jsonrpc::JsonRpcResponse< - starknet::core::types::InvokeTransactionResult, - > = goose_response.json().await.unwrap(); - - let hash = match res { - starknet::providers::jsonrpc::JsonRpcResponse::Success { result, .. } => result, - // Actually returning this error would probably be a good idea, but we can't for now - starknet::providers::jsonrpc::JsonRpcResponse::Error { error, .. } => panic!("{error}"), - } - .transaction_hash; - - queue.push(hash).unwrap(); - for (atomic, store) in prev_hash.iter().zip(hash.into_mont()) { - atomic.store(store, Ordering::Relaxed) + match body { + JsonRpcResponse::Success { result, .. } => Ok(result), + // Returning this error would probably be a good idea, + // but the goose error type doesn't allow it + JsonRpcResponse::Error { error, .. } => panic!("{error}"), } - - // Should ideally be replaced with: - // let result = from_account - // .execute(vec![call]) - // .max_fee(MAX_FEE) - // .nonce(nonce) - // .to_json() - - Ok(()) -} - -// Copied from https://docs.rs/starknet-providers/0.9.0/src/starknet_providers/jsonrpc/transports/http.rs.html#21-27 -#[derive(Debug, Serialize)] -struct JsonRpcRequest { - id: u64, - jsonrpc: &'static str, - method: JsonRpcMethod, - params: T, -} - -async fn mint_verify(user: &mut GooseUser, queue: &ArrayQueue) -> TransactionResult { - println!("{:?}", queue.pop()); - - Ok(()) } diff --git a/src/actions/mod.rs b/src/actions/mod.rs index 8d42c59..2f3e0f3 100644 --- a/src/actions/mod.rs +++ b/src/actions/mod.rs @@ -1,2 +1,16 @@ -pub mod goose; -pub mod shoot; +use crate::config::GatlingConfig; + +use self::shoot::GatlingShooterSetup; + +mod goose; +mod shoot; + +pub async fn shoot(config: GatlingConfig) -> color_eyre::Result<()> { + let mut shooter = GatlingShooterSetup::from_config(config).await?; + shooter.setup().await?; + + goose::erc20(&shooter).await?; + goose::erc721(&shooter).await?; + + Ok(()) +} diff --git a/src/actions/shoot.rs b/src/actions/shoot.rs index 6180bc4..3db8c9e 100644 --- a/src/actions/shoot.rs +++ b/src/actions/shoot.rs @@ -1,22 +1,13 @@ use crate::config::{ContractSourceConfig, GatlingConfig}; -use crate::generators::get_rng; -use crate::utils::{ - build_benchmark_report, compute_contract_address, sanitize_filename, wait_for_tx, - BenchmarkType, SYSINFO, -}; +use crate::utils::{compute_contract_address, wait_for_tx}; use color_eyre::eyre::Context; -use color_eyre::{eyre::eyre, Report as EyreReport, Result}; +use color_eyre::{eyre::eyre, Result}; use log::{debug, info, warn}; use starknet::core::types::contract::SierraClass; use std::collections::HashMap; use std::path::Path; -use tokio::task::JoinSet; - -use crate::metrics::BenchmarkReport; - -use rand::seq::SliceRandom; use starknet::accounts::{ Account, AccountFactory, Call, ConnectedAccount, ExecutionEncoding, OpenZeppelinAccountFactory, @@ -33,7 +24,7 @@ use starknet::providers::{MaybeUnknownErrorCode, StarknetErrorWithMessage}; use starknet::signers::{LocalWallet, SigningKey}; use std::str; use std::sync::Arc; -use std::time::{Duration, SystemTime}; +use std::time::Duration; use url::Url; @@ -41,44 +32,25 @@ use url::Url; pub static MAX_FEE: FieldElement = felt!("0x6efb28c75a0000"); pub static CHECK_INTERVAL: Duration = Duration::from_millis(500); -type StarknetAccount = SingleOwnerAccount>, LocalWallet>; - -/// Shoot the load test simulation. -pub async fn shoot(config: GatlingConfig) -> Result { - info!("starting simulation with config: {:#?}", config); - let mut shooter = GatlingShooter::from_config(config.clone()).await?; - let mut gatling_report = Default::default(); - // Trigger the setup phase. - shooter.setup().await?; - - info!("Using {} threads", config.run.concurrency); - - // Run the benchmarks. - shooter.run(&mut gatling_report).await?; +pub type StarknetAccount = SingleOwnerAccount>, LocalWallet>; - // Trigger the teardown phase. - shooter.teardown(&mut gatling_report).await?; - - Ok(gatling_report) -} - -pub struct GatlingShooter { - pub config: GatlingConfig, - pub starknet_rpc: Arc>, - pub signer: LocalWallet, - pub account: StarknetAccount, - pub nonces: HashMap, - pub environment: Option, // Will be populated in setup phase +pub struct GatlingShooterSetup { + config: GatlingConfig, + starknet_rpc: Arc>, + signer: LocalWallet, + account: StarknetAccount, + nonces: HashMap, + environment: Option, // Will be populated in setup phase } #[derive(Clone)] pub struct GatlingEnvironment { - pub _erc20_address: FieldElement, + pub erc20_address: FieldElement, pub erc721_address: FieldElement, pub accounts: Vec, } -impl GatlingShooter { +impl GatlingShooterSetup { pub async fn from_config(config: GatlingConfig) -> Result { let starknet_rpc: Arc> = Arc::new(starknet_rpc_provider(Url::parse(&config.clone().rpc.url)?)); @@ -113,27 +85,22 @@ impl GatlingShooter { }) } - pub fn environment(&self) -> Result { - self.environment.clone().ok_or(eyre!( + pub fn environment(&self) -> Result<&GatlingEnvironment> { + self.environment.as_ref().ok_or(eyre!( "Environment is not yet populated, you should run the setup function first" )) } - /// Return a random account address from the ones deployed during the setup phase - /// or the deployer account address if no accounts were deployed or - /// if the environment is not yet populated - pub fn get_random_account(&self) -> StarknetAccount { - match self.environment() { - Ok(environment) => { - let mut rng = rand::thread_rng(); - environment - .accounts - .choose(&mut rng) - .unwrap_or(&self.account) - .clone() - } - Err(_) => self.account.clone(), - } + pub fn config(&self) -> &GatlingConfig { + &self.config + } + + pub fn deployer_account(&self) -> &StarknetAccount { + &self.account + } + + pub async fn wait_for_tx(&self, tx_hash: FieldElement, check_interval: Duration) -> Result<()> { + wait_for_tx(&self.starknet_rpc, tx_hash, check_interval).await } /// Setup the simulation. @@ -164,20 +131,17 @@ impl GatlingShooter { let erc20_address = self.deploy_erc20(erc20_class_hash).await?; let erc721_address = self.deploy_erc721(erc721_class_hash).await?; - let accounts = if setup_config.num_accounts > 0 { - self.create_accounts( + let accounts = self + .create_accounts( account_class_hash, - setup_config.num_accounts, + self.config.run.concurrency as usize, execution_encoding, erc20_address, ) - .await? - } else { - Vec::new() - }; + .await?; let environment = GatlingEnvironment { - _erc20_address: erc20_address, + erc20_address, erc721_address, accounts, }; @@ -187,313 +151,6 @@ impl GatlingShooter { Ok(()) } - /// Teardown the simulation. - async fn teardown<'a>(&mut self, gatling_report: &'a mut GatlingReport) -> Result<()> { - info!("Tearing down!"); - info!("{}", *SYSINFO); - - info!( - "Writing reports to `{}` directory", - self.config.report.reports_dir.display() - ); - for report in &gatling_report.benchmark_reports { - let report_path = self - .config - .report - .reports_dir - .join(sanitize_filename(&report.name)) - .with_extension("json"); - - std::fs::create_dir_all(&self.config.report.reports_dir)?; - let writer = std::fs::File::create(report_path)?; - serde_json::to_writer(writer, &report.to_json())?; - } - - Ok(()) - } - - async fn check_transactions( - &self, - transactions: Vec, - ) -> (Vec, Vec) { - info!("Checking transactions ..."); - let now = SystemTime::now(); - - let total_txs = transactions.len(); - - let mut accepted_txs = Vec::new(); - let mut errors = Vec::new(); - - let mut set = JoinSet::new(); - - let mut transactions = transactions.into_iter(); - - for _ in 0..self.config.run.concurrency { - if let Some(transaction) = transactions.next() { - let starknet_rpc = Arc::clone(&self.starknet_rpc); - set.spawn(async move { - wait_for_tx(&starknet_rpc, transaction, CHECK_INTERVAL) - .await - .map(|_| transaction) - .map_err(|err| (err, transaction)) - }); - } - } - - while let Some(res) = set.join_next().await { - if let Some(transaction) = transactions.next() { - let starknet_rpc = Arc::clone(&self.starknet_rpc); - set.spawn(async move { - wait_for_tx(&starknet_rpc, transaction, CHECK_INTERVAL) - .await - .map(|_| transaction) - .map_err(|err| (err, transaction)) - }); - } - - match res.unwrap() { - Ok(transaction) => { - accepted_txs.push(transaction); - debug!("Transaction {:#064x} accepted", transaction) - } - Err((err, transaction)) => { - errors.push(err); - debug!("Transaction {:#064x} rejected", transaction) - } - } - } - - info!( - "Took {} seconds to check transactions", - now.elapsed().unwrap().as_secs_f32() - ); - - let accepted_ratio = accepted_txs.len() as f64 / total_txs as f64 * 100.0; - let rejected_ratio = errors.len() as f64 / total_txs as f64 * 100.0; - - info!( - "{} transactions accepted ({:.2}%)", - accepted_txs.len(), - accepted_ratio, - ); - info!( - "{} transactions rejected ({:.2}%)", - errors.len(), - rejected_ratio - ); - - (accepted_txs, errors) - } - - /// Run the benchmarks. - async fn run<'a>(&mut self, gatling_report: &'a mut GatlingReport) -> Result<()> { - info!("❤️‍🔥 FIRING ! ❤️‍🔥"); - - let num_blocks = self.config.report.num_blocks; - let start_block = self.starknet_rpc.block_number().await?; - let mut transactions = Vec::new(); - - let num_erc20_transfers = self.config.run.num_erc20_transfers; - - let erc20_blocks = if num_erc20_transfers > 0 { - // Run ERC20 transfer transactions - let start_block = self.starknet_rpc.block_number().await?; - - let (mut transacs, _) = self.run_erc20(num_erc20_transfers).await; - - // Wait for the last transaction to be incorporated in a block - wait_for_tx( - &self.starknet_rpc, - *transacs.last().unwrap(), - CHECK_INTERVAL, - ) - .await?; - - let end_block = self.starknet_rpc.block_number().await?; - - transactions.append(&mut transacs); - - Ok((start_block, end_block)) - } else { - Err("0 ERC20 transfers to make") - }; - - let num_erc721_mints = self.config.run.num_erc721_mints; - - let erc721_blocks = if num_erc721_mints > 0 { - // Run ERC721 mint transactions - let start_block = self.starknet_rpc.block_number().await?; - - let (mut transacs, _) = self.run_erc721(num_erc721_mints).await; - - // Wait for the last transaction to be incorporated in a block - wait_for_tx( - &self.starknet_rpc, - *transacs.last().unwrap(), - CHECK_INTERVAL, - ) - .await?; - - let end_block = self.starknet_rpc.block_number().await?; - - transactions.append(&mut transacs); - - Ok((start_block, end_block)) - } else { - Err("0 ERC721 mints to make") - }; - - let end_block = self.starknet_rpc.block_number().await?; - - let full_blocks = if start_block < end_block { - Ok((start_block, end_block)) - } else { - Err("no executions were made") - }; - - // Build benchmark reports - for (token, blocks) in [ - ("ERC20", erc20_blocks), - ("ERC721", erc721_blocks), - ("Full", full_blocks), - ] { - match blocks { - Ok((start_block, end_block)) => { - // The transactions we sent will be incorporated in the next accepted block - build_benchmark_report( - self.starknet_rpc.clone(), - token.to_string(), - BenchmarkType::BlockRange(start_block + 1, end_block), - gatling_report, - ) - .await?; - - build_benchmark_report( - self.starknet_rpc.clone(), - format!("{token}_latest_{num_blocks}").to_string(), - BenchmarkType::LatestBlocks(num_blocks), - gatling_report, - ) - .await?; - } - Err(err) => warn!("Skip creating {token} reports because of `{err}`"), - }; - } - - // Check transactions - if !transactions.is_empty() { - self.check_transactions(transactions).await; - } else { - warn!("No load test was executed, are both mints and transfers set to 0?"); - } - - Ok(()) - } - - pub async fn run_erc20(&mut self, num_transfers: u64) -> (Vec, Vec) { - info!("Sending {num_transfers} ERC20 transfer transactions ..."); - - let start = SystemTime::now(); - - let mut accepted_txs = Vec::new(); - let mut errors = Vec::new(); - - for _ in 0..num_transfers { - match self - .transfer( - self.config.setup.fee_token_address, - self.get_random_account(), - FieldElement::from_hex_be("0xdead").unwrap(), - felt!("1"), - ) - .await - { - Ok(transaction_hash) => { - accepted_txs.push(transaction_hash); - } - Err(e) => { - let e = eyre!(e).wrap_err("Error while sending ERC20 transfer transaction"); - errors.push(e); - } - } - } - - let took = start.elapsed().unwrap().as_secs_f32(); - info!( - "Took {} seconds to send {} transfer transactions, on average {} sent per second", - took, - num_transfers, - num_transfers as f32 / took - ); - - let accepted_ratio = accepted_txs.len() as f64 / num_transfers as f64 * 100.0; - let rejected_ratio = errors.len() as f64 / num_transfers as f64 * 100.0; - - info!( - "{} transfer transactions sent successfully ({:.2}%)", - accepted_txs.len(), - accepted_ratio, - ); - info!( - "{} transfer transactions failed ({:.2}%)", - errors.len(), - rejected_ratio - ); - - (accepted_txs, errors) - } - - async fn run_erc721<'a>(&mut self, num_mints: u64) -> (Vec, Vec) { - let environment = self.environment().unwrap(); - - info!("Sending {num_mints} ERC721 mint transactions ..."); - - let start = SystemTime::now(); - - let mut accepted_txs = Vec::new(); - let mut errors = Vec::new(); - - for _ in 0..num_mints { - // TODO: Change the ERC721 contract such that mint is permissionless - match self - .mint(self.account.clone(), environment.erc721_address) - .await - { - Ok(transaction_hash) => { - accepted_txs.push(transaction_hash); - } - Err(e) => { - let e = eyre!(e).wrap_err("Error while sending ERC721 mint transaction"); - errors.push(e); - } - }; - } - - let took = start.elapsed().unwrap().as_secs_f32(); - info!( - "Took {} seconds to send {} mint transactions, on average {} sent per second", - took, - num_mints, - num_mints as f32 / took - ); - - let accepted_ratio = accepted_txs.len() as f64 / num_mints as f64 * 100.0; - let rejected_ratio = errors.len() as f64 / num_mints as f64 * 100.0; - - info!( - "{} mint transactions sent successfully ({:.2}%)", - accepted_txs.len(), - accepted_ratio, - ); - info!( - "{} mint transactions failed ({:.2}%)", - errors.len(), - rejected_ratio - ); - - (accepted_txs, errors) - } - async fn transfer( &mut self, contract_address: FieldElement, @@ -532,46 +189,6 @@ impl GatlingShooter { Ok(result.transaction_hash) } - async fn mint( - &mut self, - account: StarknetAccount, - contract_address: FieldElement, - ) -> Result { - let from_address = account.address(); - let nonce = match self.nonces.get(&from_address) { - Some(nonce) => *nonce, - None => account.get_nonce().await?, - }; - - debug!( - "Minting for address={:#064x} with nonce={}", - contract_address, nonce - ); - - let (token_id_low, token_id_high) = (get_rng(), felt!("0x0000")); - - let call = Call { - to: contract_address, - selector: selector!("mint"), - calldata: vec![ - self.get_random_account().address(), // recipient - token_id_low, - token_id_high, - ], - }; - - let result = account - .execute(vec![call]) - .max_fee(MAX_FEE) - .nonce(nonce) - .send() - .await?; - - self.nonces.insert(from_address, nonce + FieldElement::ONE); - - Ok(result.transaction_hash) - } - async fn deploy_erc721(&mut self, class_hash: FieldElement) -> Result { let contract_factory = ContractFactory::new(class_hash, self.account.clone()); let from_address = self.account.address(); @@ -913,12 +530,6 @@ impl GatlingShooter { } } -/// The simulation report. -#[derive(Debug, Default, Clone)] -pub struct GatlingReport { - pub benchmark_reports: Vec, -} - /// Create a StarkNet RPC provider from a URL. /// # Arguments /// * `rpc` - The URL of the StarkNet RPC provider. diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 5557625..04d2f5e 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -27,8 +27,6 @@ pub struct Cli { pub enum Command { /// Trigger a load test. Shoot {}, - /// Trigger goose load test. - Goose {}, } #[derive(Debug, Args)] diff --git a/src/config.rs b/src/config.rs index 53e40d1..4192f5f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -89,7 +89,6 @@ pub struct SetupConfig { pub erc721_contract: ContractSourceConfig, pub account_contract: ContractSourceConfig, pub fee_token_address: FieldElement, - pub num_accounts: usize, #[serde(deserialize_with = "from_str_deserializer")] pub chain_id: FieldElement, } diff --git a/src/main.rs b/src/main.rs index 84f528d..c3d3643 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ use clap::Parser; use color_eyre::eyre::Result; use dotenvy::dotenv; use gatling::{ - actions::{self, shoot::shoot}, + actions, cli::{Cli, Command}, config::GatlingConfig, }; @@ -34,11 +34,7 @@ async fn main() -> Result<()> { // Execute the command. match cli.command { Command::Shoot { .. } => { - let gatling_report = shoot(cfg).await?; - info!("Gatling completed: {:#?}", gatling_report); - } - Command::Goose { .. } => { - actions::goose::goose(cfg).await?; + actions::shoot(cfg).await?; } } diff --git a/src/metrics.rs b/src/metrics.rs index f2c6a36..6801c98 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -36,6 +36,12 @@ pub struct MetricResult { pub value: f64, } +/// The simulation report. +#[derive(Debug, Default, Clone)] +pub struct GatlingReport { + pub benchmark_reports: Vec, +} + /// A benchmark report contains a name (used for displaying) and a vector of metric results /// of all the metrics that were computed for the benchmark /// A benchmark report can be created from a block range or from the last x blocks diff --git a/src/utils.rs b/src/utils.rs index 51904e3..d62470e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -18,8 +18,7 @@ use starknet::{ use std::time::Duration; use sysinfo::{CpuExt, System, SystemExt}; -use crate::actions::shoot::{GatlingReport, CHECK_INTERVAL}; -use crate::metrics::BenchmarkReport; +use crate::metrics::{BenchmarkReport, GatlingReport}; lazy_static! { pub static ref SYSINFO: SysInfo = SysInfo::new(); @@ -159,7 +158,7 @@ pub async fn wait_for_tx( .. })) => { debug!("Waiting for transaction {tx_hash:#064x} to show up"); - tokio::time::sleep(CHECK_INTERVAL).await; + tokio::time::sleep(check_interval).await; } Err(err) => { return Err(eyre!(err).wrap_err(format!( From 724bbf88c2f38afb84ccf75ab8773dea7543746a Mon Sep 17 00:00:00 2001 From: Angel Petrov Date: Thu, 8 Feb 2024 10:21:47 +0200 Subject: [PATCH 07/13] Make transaction verification be in the same GooseAttack as creation and refactoring --- src/actions/goose.rs | 305 ++++++++++++++++++++++--------------------- src/actions/mod.rs | 16 ++- src/actions/shoot.rs | 8 +- 3 files changed, 174 insertions(+), 155 deletions(-) diff --git a/src/actions/goose.rs b/src/actions/goose.rs index 5c47b4e..f4bf741 100644 --- a/src/actions/goose.rs +++ b/src/actions/goose.rs @@ -1,11 +1,6 @@ -use std::{ - mem, - sync::{ - atomic::{AtomicU64, Ordering}, - Arc, - }, -}; +use std::{mem, sync::Arc}; +use color_eyre::eyre::ensure; use crossbeam_queue::ArrayQueue; use goose::{config::GooseConfiguration, prelude::*}; use serde::{de::DeserializeOwned, Serialize}; @@ -29,71 +24,65 @@ use starknet::{ use crate::{ actions::shoot::{GatlingShooterSetup, CHECK_INTERVAL, MAX_FEE}, generators::get_rng, + utils::wait_for_tx, }; use super::shoot::StarknetAccount; pub async fn erc20(shooter: &GatlingShooterSetup) -> color_eyre::Result<()> { - let _erc20_address = shooter.environment().unwrap().erc20_address; + let environment = shooter.environment()?; + let erc20_address = environment.erc20_address; let config = shooter.config(); + let goose_iterations = config.run.num_erc20_transfers / config.run.concurrency; + let num_erc20_transfers = goose_iterations * config.run.concurrency; + + ensure!( + goose_iterations != 0, + "Too few erc20 transfers for the amount of concurrency" + ); + + if num_erc20_transfers != config.run.num_erc20_transfers { + log::warn!("Number of erc20 transfers is not evenly divisble by concurrency, doing {num_erc20_transfers} transfers instead"); + } + let goose_config = { let mut default = GooseConfiguration::default(); default.host = config.rpc.url.clone(); - default.iterations = (config.run.num_erc20_transfers / config.run.concurrency) as usize; + default.iterations = goose_iterations as usize; default.users = Some(config.run.concurrency as usize); default }; - let queue = Arc::new(ArrayQueue::new(config.run.num_erc20_transfers as usize)); - let queue_trans = queue.clone(); - let queue_trans_verify = queue_trans.clone(); - - let last_trans = Arc::new([ - AtomicU64::new(0), - AtomicU64::new(0), - AtomicU64::new(0), - AtomicU64::new(0), - ]); - let last_transaction_clone = last_trans.clone(); - - let transfer: TransactionFunction = Arc::new(move |user| { - let queue = queue_trans.clone(); - let last_mint = last_transaction_clone.clone(); - Box::pin(async move { transfer(user, &queue, _erc20_address, &last_mint).await }) - }); + let transfer_setup: TransactionFunction = + setup(environment.accounts.clone(), num_erc20_transfers as usize).await?; - let transfer_verify: TransactionFunction = Arc::new(move |user| { - let queue = queue_trans_verify.clone(); - Box::pin(async move { verify_transacs(user, &queue).await }) - }); + let transfer: TransactionFunction = + Arc::new(move |user| Box::pin(transfer(user, erc20_address))); - let transfer_setup = setup(shooter.environment()?.accounts.clone()).await?; + let transfer_wait: TransactionFunction = goose_user_wait_last_tx(shooter.rpc_client().clone()); + + let transfer_verify: TransactionFunction = Arc::new(|user| Box::pin(verify_transacs(user))); GooseAttack::initialize_with_config(goose_config.clone())? .register_scenario( - scenario!("Transactions") + scenario!("Transfer") .register_transaction( transaction!(transfer_setup) - .set_name("Setup") + .set_name("Transfer Setup") .set_on_start(), ) - .register_transaction(transaction!(transfer).set_name("Transfer")), - ) - .execute() - .await?; - - // Wait for the last transaction to be incorporated in a block - shooter - .wait_for_tx( - FieldElement::from_mont(Arc::try_unwrap(last_trans).unwrap().map(|x| x.into_inner())), - CHECK_INTERVAL, - ) - .await?; - - GooseAttack::initialize_with_config(goose_config)? - .register_scenario( - scenario!("Transactions Verify").register_transaction(transaction!(transfer_verify)), + .register_transaction(transaction!(transfer).set_name("Transfer").set_sequence(1)) + .register_transaction( + transaction!(transfer_wait) + .set_name("Transfer Finalizing") + .set_sequence(2), + ) + .register_transaction( + transaction!(transfer_verify) + .set_name("Transfer Verification") + .set_sequence(3), + ), ) .execute() .await?; @@ -103,11 +92,25 @@ pub async fn erc20(shooter: &GatlingShooterSetup) -> color_eyre::Result<()> { pub async fn erc721(shooter: &GatlingShooterSetup) -> color_eyre::Result<()> { let config = shooter.config(); - let nonces = Arc::new(ArrayQueue::new(config.run.num_erc721_mints as usize)); - let erc721_address = shooter.environment().unwrap().erc721_address; + let environment = shooter.environment()?; + + let goose_iterations = config.run.num_erc721_mints / config.run.concurrency; + let num_erc721_mints = goose_iterations * config.run.concurrency; + + ensure!( + goose_iterations != 0, + "Too few erc721 mints for the amount of concurrency" + ); + + if num_erc721_mints != config.run.num_erc721_mints { + log::warn!("Number of erc721 mints is not evenly divisble by concurrency, doing {num_erc721_mints} mints instead"); + } + + let nonces = Arc::new(ArrayQueue::new(num_erc721_mints as usize)); + let erc721_address = environment.erc721_address; let mut nonce = shooter.deployer_account().get_nonce().await?; - for _ in 0..config.run.num_erc721_mints { + for _ in 0..num_erc721_mints { nonces .push(nonce) .expect("ArrayQueue has capacity for all mints"); @@ -122,70 +125,46 @@ pub async fn erc721(shooter: &GatlingShooterSetup) -> color_eyre::Result<()> { default }; - let queue = Arc::new(ArrayQueue::new(config.run.num_erc721_mints as usize)); - let queue_mint = queue.clone(); - let queue_mint_verify = queue_mint.clone(); - - let last_trans = Arc::new([ - AtomicU64::new(0), - AtomicU64::new(0), - AtomicU64::new(0), - AtomicU64::new(0), - ]); - let last_mint_clone = last_trans.clone(); - let from_account = shooter.deployer_account().clone(); + let mint_setup: TransactionFunction = + setup(environment.accounts.clone(), num_erc721_mints as usize).await?; + let mint: TransactionFunction = Arc::new(move |user| { - let queue = queue_mint.clone(); - let nonces = nonces.clone(); - let nonce = nonces.pop().unwrap(); - let last_mint = last_mint_clone.clone(); + let nonce = nonces + .pop() + .expect("Nonce ArrayQueue should have enough nonces for all mints"); let from_account = from_account.clone(); - Box::pin(async move { - mint( - user, - &queue, - erc721_address, - nonce, - &from_account, - &last_mint, - ) - .await - }) + Box::pin(async move { mint(user, erc721_address, nonce, &from_account).await }) }); - let mint_verify: TransactionFunction = Arc::new(move |user| { - let queue = queue_mint_verify.clone(); - Box::pin(async move { verify_transacs(user, &queue).await }) - }); + let mint_wait: TransactionFunction = goose_user_wait_last_tx(shooter.rpc_client().clone()); - let mint_setup = setup(shooter.environment()?.accounts.clone()).await?; + let mint_verify: TransactionFunction = Arc::new(|user| Box::pin(verify_transacs(user))); GooseAttack::initialize_with_config(goose_mint_config.clone())? .register_scenario( scenario!("Minting") - .register_transaction(transaction!(mint_setup).set_name("Setup").set_on_start()) - .register_transaction(transaction!(mint).set_name("Minting")), - ) - .execute() - .await?; - - // Wait for the last transaction to be incorporated in a block - shooter - .wait_for_tx( - FieldElement::from_mont(Arc::try_unwrap(last_trans).unwrap().map(|x| x.into_inner())), - CHECK_INTERVAL, + .register_transaction( + transaction!(mint_setup) + .set_name("Mint Setup") + .set_on_start(), + ) + .register_transaction(transaction!(mint).set_name("Minting").set_sequence(1)) + .register_transaction( + transaction!(mint_wait) + .set_name("Mint Finalizing") + .set_sequence(2), + ) + .register_transaction( + transaction!(mint_verify) + .set_name("Mint Verification") + .set_sequence(3), + ), ) - .await?; - - GooseAttack::initialize_with_config(goose_mint_config)? - .register_scenario(scenario!("Mint Verify").register_transaction(transaction!(mint_verify))) .execute() .await?; - // todo!() - Ok(()) } @@ -193,23 +172,33 @@ pub async fn erc721(shooter: &GatlingShooterSetup) -> color_eyre::Result<()> { struct GooseUserState { account: StarknetAccount, nonce: FieldElement, + prev_tx: Vec, } pub type RpcError = ProviderError>; impl GooseUserState { - pub async fn new(account: StarknetAccount) -> Result { + pub async fn new( + account: StarknetAccount, + transactions_amount: usize, + ) -> Result { Ok(Self { nonce: account.get_nonce().await?, account, + prev_tx: Vec::with_capacity(transactions_amount), }) } } -async fn setup(accounts: Vec) -> Result { +async fn setup( + accounts: Vec, + transactions_amount: usize, +) -> Result { let queue = ArrayQueue::new(accounts.len()); for account in accounts { - queue.push(GooseUserState::new(account).await?).unwrap(); + queue + .push(GooseUserState::new(account, transactions_amount).await?) + .expect("Queue should have enough space for all accounts as it's length is from the accounts vec"); } let queue = Arc::new(queue); @@ -225,62 +214,77 @@ async fn setup(accounts: Vec) -> Result, - erc20_address: FieldElement, - prev_hash: &[AtomicU64; 4], -) -> TransactionResult { - let GooseUserState { - account, - nonce: state_nonce, - } = user.get_session_data_mut::().unwrap(); +fn goose_user_wait_last_tx(provider: Arc>) -> TransactionFunction { + Arc::new(move |user| { + let thing = user + .get_session_data::() + .expect("Should be in a goose user with GooseUserState session data") + .prev_tx + .last() + .expect("At least one transaction should have been done"); - let nonce = *state_nonce; - *state_nonce += FieldElement::ONE; - let account = account.clone(); + let provider = provider.clone(); + + Box::pin(async move { + wait_for_tx(&provider, *thing, CHECK_INTERVAL) + .await + .expect("Transaction should have been successful"); + + Ok(()) + }) + }) +} + +// Hex: 0xdead +// from_hex_be isn't const whereas from_mont is +const VOID_ADDRESS: FieldElement = FieldElement::from_mont([ + 18446744073707727457, + 18446744073709551615, + 18446744073709551615, + 576460752272412784, +]); + +async fn transfer(user: &mut GooseUser, erc20_address: FieldElement) -> TransactionResult { + let GooseUserState { account, nonce, .. } = user + .get_session_data::() + .expect("Should be in a goose user with GooseUserState session data"); let (amount_low, amount_high) = (felt!("1"), felt!("0")); let call = Call { to: erc20_address, selector: selector!("transfer"), - calldata: vec![ - FieldElement::from_hex_be("0xdead").unwrap(), - amount_low, - amount_high, - ], + calldata: vec![VOID_ADDRESS, amount_low, amount_high], }; let response: InvokeTransactionResult = send_execution( user, vec![call], - nonce, - &account, + *nonce, + &account.clone(), JsonRpcMethod::AddInvokeTransaction, ) .await?; - queue.push(response.transaction_hash).unwrap(); + let GooseUserState { nonce, prev_tx, .. } = + user.get_session_data_mut::().unwrap(); - for (atomic, store) in prev_hash.iter().zip(response.transaction_hash.into_mont()) { - atomic.store(store, Ordering::Relaxed) - } + *nonce += FieldElement::ONE; + + prev_tx.push(response.transaction_hash); Ok(()) } async fn mint( user: &mut GooseUser, - queue: &ArrayQueue, erc721_address: FieldElement, nonce: FieldElement, from_account: &SingleOwnerAccount>, LocalWallet>, - prev_hash: &[AtomicU64; 4], ) -> TransactionResult { let recipient = user .get_session_data::() - .unwrap() + .expect("Should be in a goose user with GooseUserState session data") .account .clone() .address(); @@ -290,11 +294,7 @@ async fn mint( let call = Call { to: erc721_address, selector: selector!("mint"), - calldata: vec![ - recipient, // recipient - token_id_low, - token_id_high, - ], + calldata: vec![recipient, token_id_low, token_id_high], }; let response: InvokeTransactionResult = send_execution( @@ -306,20 +306,23 @@ async fn mint( ) .await?; - queue.push(response.transaction_hash).unwrap(); - - for (atomic, store) in prev_hash.iter().zip(response.transaction_hash.into_mont()) { - atomic.store(store, Ordering::Relaxed) - } + user.get_session_data_mut::() + .expect( + "Should be successful as we already asserted that the session data is a GooseUserState", + ) + .prev_tx + .push(response.transaction_hash); Ok(()) } -async fn verify_transacs( - user: &mut GooseUser, - queue: &ArrayQueue, -) -> TransactionResult { - let transaction = queue.pop().unwrap(); +async fn verify_transacs(user: &mut GooseUser) -> TransactionResult { + let transaction = user + .get_session_data_mut::() + .expect("Should be in a goose user with GooseUserState session data") + .prev_tx + .pop() + .expect("There should be enough previous transactions for every verification"); let receipt: MaybePendingTransactionReceipt = send_request(user, JsonRpcMethod::GetTransactionReceipt, transaction).await?; @@ -346,7 +349,7 @@ pub async fn send_execution( ) -> Result> { let calldata = from_account.encode_calls(&calls); - #[allow(dead_code)] + #[allow(dead_code)] // Removes warning for unused fields, we need them to properly transmute struct FakeRawExecution { calls: Vec, nonce: FieldElement, @@ -367,7 +370,10 @@ pub async fn send_execution( sender_address: from_account.address(), calldata, max_fee: MAX_FEE, - signature: from_account.sign_execution(&raw_exec).await.unwrap(), + signature: from_account + .sign_execution(&raw_exec) + .await + .expect("Raw Execution should be correctly constructed for signature"), nonce, is_query: false, }; @@ -408,7 +414,8 @@ pub async fn send_request( match body { JsonRpcResponse::Success { result, .. } => Ok(result), // Returning this error would probably be a good idea, - // but the goose error type doesn't allow it + // but the goose error type doesn't allow it and we are + // required to return it as a constraint of TransactionFunction JsonRpcResponse::Error { error, .. } => panic!("{error}"), } } diff --git a/src/actions/mod.rs b/src/actions/mod.rs index 2f3e0f3..afcb4ef 100644 --- a/src/actions/mod.rs +++ b/src/actions/mod.rs @@ -6,11 +6,23 @@ mod goose; mod shoot; pub async fn shoot(config: GatlingConfig) -> color_eyre::Result<()> { + let run_erc20 = config.run.num_erc20_transfers != 0; + let run_erc721 = config.run.num_erc721_mints != 0; + let mut shooter = GatlingShooterSetup::from_config(config).await?; shooter.setup().await?; - goose::erc20(&shooter).await?; - goose::erc721(&shooter).await?; + if run_erc20 { + goose::erc20(&shooter).await?; + } else { + log::info!("Skipping erc20 transfers") + } + + if run_erc721 { + goose::erc721(&shooter).await?; + } else { + log::info!("Skipping erc721 mints") + } Ok(()) } diff --git a/src/actions/shoot.rs b/src/actions/shoot.rs index 3db8c9e..01d17be 100644 --- a/src/actions/shoot.rs +++ b/src/actions/shoot.rs @@ -95,12 +95,12 @@ impl GatlingShooterSetup { &self.config } - pub fn deployer_account(&self) -> &StarknetAccount { - &self.account + pub fn rpc_client(&self) -> &Arc> { + &self.starknet_rpc } - pub async fn wait_for_tx(&self, tx_hash: FieldElement, check_interval: Duration) -> Result<()> { - wait_for_tx(&self.starknet_rpc, tx_hash, check_interval).await + pub fn deployer_account(&self) -> &StarknetAccount { + &self.account } /// Setup the simulation. From d3d4b96f797ea9998e80eb4cadb7fae8c14524c7 Mon Sep 17 00:00:00 2001 From: Angel Petrov Date: Thu, 8 Feb 2024 10:23:32 +0200 Subject: [PATCH 08/13] Rounding down with warning --- src/actions/goose.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/actions/goose.rs b/src/actions/goose.rs index f4bf741..8e913cd 100644 --- a/src/actions/goose.rs +++ b/src/actions/goose.rs @@ -106,6 +106,14 @@ pub async fn erc721(shooter: &GatlingShooterSetup) -> color_eyre::Result<()> { log::warn!("Number of erc721 mints is not evenly divisble by concurrency, doing {num_erc721_mints} mints instead"); } + let goose_mint_config = { + let mut default = GooseConfiguration::default(); + default.host = config.rpc.url.clone(); + default.iterations = goose_iterations as usize; + default.users = Some(config.run.concurrency as usize); + default + }; + let nonces = Arc::new(ArrayQueue::new(num_erc721_mints as usize)); let erc721_address = environment.erc721_address; let mut nonce = shooter.deployer_account().get_nonce().await?; @@ -117,14 +125,6 @@ pub async fn erc721(shooter: &GatlingShooterSetup) -> color_eyre::Result<()> { nonce += FieldElement::ONE; } - let goose_mint_config = { - let mut default = GooseConfiguration::default(); - default.host = config.rpc.url.clone(); - default.iterations = (config.run.num_erc721_mints / config.run.concurrency) as usize; - default.users = Some(config.run.concurrency as usize); - default - }; - let from_account = shooter.deployer_account().clone(); let mint_setup: TransactionFunction = From 382e674fe0431531cd7170f2b27a2ca193c03e8f Mon Sep 17 00:00:00 2001 From: Angel Petrov Date: Thu, 8 Feb 2024 10:32:02 +0200 Subject: [PATCH 09/13] Replace unwrap with expect for user.get_session_data_mut --- src/actions/goose.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/actions/goose.rs b/src/actions/goose.rs index 8e913cd..48d42ef 100644 --- a/src/actions/goose.rs +++ b/src/actions/goose.rs @@ -267,7 +267,9 @@ async fn transfer(user: &mut GooseUser, erc20_address: FieldElement) -> Transact .await?; let GooseUserState { nonce, prev_tx, .. } = - user.get_session_data_mut::().unwrap(); + user.get_session_data_mut::().expect( + "Should be successful as we already asserted that the session data is a GooseUserState", + ); *nonce += FieldElement::ONE; From a952bab8877535871149c8d1cb8e5c2acae83ed6 Mon Sep 17 00:00:00 2001 From: Angel Petrov Date: Fri, 9 Feb 2024 10:56:49 +0200 Subject: [PATCH 10/13] Remove unneccessary transaction macros and boxing --- src/actions/goose.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/actions/goose.rs b/src/actions/goose.rs index 48d42ef..6a1863e 100644 --- a/src/actions/goose.rs +++ b/src/actions/goose.rs @@ -62,24 +62,26 @@ pub async fn erc20(shooter: &GatlingShooterSetup) -> color_eyre::Result<()> { let transfer_wait: TransactionFunction = goose_user_wait_last_tx(shooter.rpc_client().clone()); - let transfer_verify: TransactionFunction = Arc::new(|user| Box::pin(verify_transacs(user))); - GooseAttack::initialize_with_config(goose_config.clone())? .register_scenario( scenario!("Transfer") .register_transaction( - transaction!(transfer_setup) + Transaction::new(transfer_setup) .set_name("Transfer Setup") .set_on_start(), ) - .register_transaction(transaction!(transfer).set_name("Transfer").set_sequence(1)) .register_transaction( - transaction!(transfer_wait) + Transaction::new(transfer) + .set_name("Transfer") + .set_sequence(1), + ) + .register_transaction( + Transaction::new(transfer_wait) .set_name("Transfer Finalizing") .set_sequence(2), ) .register_transaction( - transaction!(transfer_verify) + transaction!(verify_transactions) .set_name("Transfer Verification") .set_sequence(3), ), @@ -140,24 +142,22 @@ pub async fn erc721(shooter: &GatlingShooterSetup) -> color_eyre::Result<()> { let mint_wait: TransactionFunction = goose_user_wait_last_tx(shooter.rpc_client().clone()); - let mint_verify: TransactionFunction = Arc::new(|user| Box::pin(verify_transacs(user))); - GooseAttack::initialize_with_config(goose_mint_config.clone())? .register_scenario( scenario!("Minting") .register_transaction( - transaction!(mint_setup) + Transaction::new(mint_setup) .set_name("Mint Setup") .set_on_start(), ) - .register_transaction(transaction!(mint).set_name("Minting").set_sequence(1)) + .register_transaction(Transaction::new(mint).set_name("Minting").set_sequence(1)) .register_transaction( - transaction!(mint_wait) + Transaction::new(mint_wait) .set_name("Mint Finalizing") .set_sequence(2), ) .register_transaction( - transaction!(mint_verify) + transaction!(verify_transactions) .set_name("Mint Verification") .set_sequence(3), ), @@ -318,7 +318,7 @@ async fn mint( Ok(()) } -async fn verify_transacs(user: &mut GooseUser) -> TransactionResult { +async fn verify_transactions(user: &mut GooseUser) -> TransactionResult { let transaction = user .get_session_data_mut::() .expect("Should be in a goose user with GooseUserState session data") From fccdd8e640c51612e417a9e948a7d471b237abc1 Mon Sep 17 00:00:00 2001 From: Angel Petrov Date: Fri, 9 Feb 2024 11:18:47 +0200 Subject: [PATCH 11/13] Clarify goose iteration truncation code --- src/actions/goose.rs | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/actions/goose.rs b/src/actions/goose.rs index 6a1863e..2927c33 100644 --- a/src/actions/goose.rs +++ b/src/actions/goose.rs @@ -34,28 +34,31 @@ pub async fn erc20(shooter: &GatlingShooterSetup) -> color_eyre::Result<()> { let erc20_address = environment.erc20_address; let config = shooter.config(); - let goose_iterations = config.run.num_erc20_transfers / config.run.concurrency; - let num_erc20_transfers = goose_iterations * config.run.concurrency; - ensure!( - goose_iterations != 0, + config.run.num_erc20_transfers >= config.run.concurrency, "Too few erc20 transfers for the amount of concurrency" ); - if num_erc20_transfers != config.run.num_erc20_transfers { - log::warn!("Number of erc20 transfers is not evenly divisble by concurrency, doing {num_erc20_transfers} transfers instead"); + // div_euclid will truncate integers when not evenly divisable + let user_iterations = config.run.num_erc20_transfers.div_euclid(config.run.concurrency); + // this will always be a multiple of concurrency, unlike num_erc20_transfers + let total_transactions = user_iterations * config.run.concurrency; + + // If these are not equal that means user_iterations was truncated + if total_transactions != config.run.num_erc20_transfers { + log::warn!("Number of erc20 transfers is not evenly divisble by concurrency, doing {total_transactions} transfers instead"); } let goose_config = { let mut default = GooseConfiguration::default(); default.host = config.rpc.url.clone(); - default.iterations = goose_iterations as usize; + default.iterations = user_iterations as usize; default.users = Some(config.run.concurrency as usize); default }; let transfer_setup: TransactionFunction = - setup(environment.accounts.clone(), num_erc20_transfers as usize).await?; + setup(environment.accounts.clone(), user_iterations as usize).await?; let transfer: TransactionFunction = Arc::new(move |user| Box::pin(transfer(user, erc20_address))); @@ -96,31 +99,34 @@ pub async fn erc721(shooter: &GatlingShooterSetup) -> color_eyre::Result<()> { let config = shooter.config(); let environment = shooter.environment()?; - let goose_iterations = config.run.num_erc721_mints / config.run.concurrency; - let num_erc721_mints = goose_iterations * config.run.concurrency; - ensure!( - goose_iterations != 0, + config.run.num_erc20_transfers >= config.run.concurrency, "Too few erc721 mints for the amount of concurrency" ); - if num_erc721_mints != config.run.num_erc721_mints { - log::warn!("Number of erc721 mints is not evenly divisble by concurrency, doing {num_erc721_mints} mints instead"); + // div_euclid will truncate integers when not evenly divisable + let user_iterations = config.run.num_erc721_mints.div_euclid(config.run.concurrency); + // this will always be a multiple of concurrency, unlike num_erc721_mints + let total_transactions = user_iterations * config.run.concurrency; + + // If these are not equal that means user_iterations was truncated + if total_transactions != config.run.num_erc721_mints { + log::warn!("Number of erc721 mints is not evenly divisble by concurrency, doing {total_transactions} mints instead"); } let goose_mint_config = { let mut default = GooseConfiguration::default(); default.host = config.rpc.url.clone(); - default.iterations = goose_iterations as usize; + default.iterations = user_iterations as usize; default.users = Some(config.run.concurrency as usize); default }; - let nonces = Arc::new(ArrayQueue::new(num_erc721_mints as usize)); + let nonces = Arc::new(ArrayQueue::new(user_iterations as usize)); let erc721_address = environment.erc721_address; let mut nonce = shooter.deployer_account().get_nonce().await?; - for _ in 0..num_erc721_mints { + for _ in 0..total_transactions { nonces .push(nonce) .expect("ArrayQueue has capacity for all mints"); @@ -130,7 +136,7 @@ pub async fn erc721(shooter: &GatlingShooterSetup) -> color_eyre::Result<()> { let from_account = shooter.deployer_account().clone(); let mint_setup: TransactionFunction = - setup(environment.accounts.clone(), num_erc721_mints as usize).await?; + setup(environment.accounts.clone(), user_iterations as usize).await?; let mint: TransactionFunction = Arc::new(move |user| { let nonce = nonces From f7c9254ffff4e8afa3947cd3ee0cd1b57affad26 Mon Sep 17 00:00:00 2001 From: Angel Petrov Date: Fri, 9 Feb 2024 11:30:54 +0200 Subject: [PATCH 12/13] Fix bug --- src/actions/goose.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/actions/goose.rs b/src/actions/goose.rs index 2927c33..78d7024 100644 --- a/src/actions/goose.rs +++ b/src/actions/goose.rs @@ -122,7 +122,7 @@ pub async fn erc721(shooter: &GatlingShooterSetup) -> color_eyre::Result<()> { default }; - let nonces = Arc::new(ArrayQueue::new(user_iterations as usize)); + let nonces = Arc::new(ArrayQueue::new(total_transactions as usize)); let erc721_address = environment.erc721_address; let mut nonce = shooter.deployer_account().get_nonce().await?; From 6c2a71c6dc5f6e6f85c73cc79fcbe3bfee804352 Mon Sep 17 00:00:00 2001 From: Angel Petrov Date: Fri, 9 Feb 2024 12:20:00 +0200 Subject: [PATCH 13/13] cargo fmt --- src/actions/goose.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/actions/goose.rs b/src/actions/goose.rs index 78d7024..c79bef2 100644 --- a/src/actions/goose.rs +++ b/src/actions/goose.rs @@ -40,7 +40,10 @@ pub async fn erc20(shooter: &GatlingShooterSetup) -> color_eyre::Result<()> { ); // div_euclid will truncate integers when not evenly divisable - let user_iterations = config.run.num_erc20_transfers.div_euclid(config.run.concurrency); + let user_iterations = config + .run + .num_erc20_transfers + .div_euclid(config.run.concurrency); // this will always be a multiple of concurrency, unlike num_erc20_transfers let total_transactions = user_iterations * config.run.concurrency; @@ -105,7 +108,10 @@ pub async fn erc721(shooter: &GatlingShooterSetup) -> color_eyre::Result<()> { ); // div_euclid will truncate integers when not evenly divisable - let user_iterations = config.run.num_erc721_mints.div_euclid(config.run.concurrency); + let user_iterations = config + .run + .num_erc721_mints + .div_euclid(config.run.concurrency); // this will always be a multiple of concurrency, unlike num_erc721_mints let total_transactions = user_iterations * config.run.concurrency;