diff --git a/Cargo.lock b/Cargo.lock index d803f09..18746ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3104,6 +3104,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + [[package]] name = "signature" version = "2.0.0" @@ -3336,6 +3345,7 @@ dependencies = [ "libc", "mio", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.52.0", diff --git a/crates/cli/src/default_scenarios/runconfig.rs b/crates/cli/src/default_scenarios/runconfig.rs index 476d328..d5218e1 100644 --- a/crates/cli/src/default_scenarios/runconfig.rs +++ b/crates/cli/src/default_scenarios/runconfig.rs @@ -15,15 +15,22 @@ pub enum BuiltinScenarioConfig { max_gas_per_block: u128, num_txs: u64, sender: Address, + fill_percent: u16, }, } impl BuiltinScenarioConfig { - pub fn fill_block(max_gas_per_block: u128, num_txs: u64, sender: Address) -> Self { + pub fn fill_block( + max_gas_per_block: u128, + num_txs: u64, + sender: Address, + fill_percent: u16, + ) -> Self { Self::FillBlock { max_gas_per_block, num_txs, sender, + fill_percent, } } } @@ -35,8 +42,14 @@ impl From for TestConfig { max_gas_per_block, num_txs, sender, + fill_percent, } => { - let gas_per_tx = max_gas_per_block / num_txs as u128; + let gas_per_tx = + ((max_gas_per_block / num_txs as u128) / 100) * fill_percent as u128; + println!( + "Filling blocks to {}% with {} gas per tx", + fill_percent, gas_per_tx + ); let spam_txs = (0..num_txs) .map(|_| { SpamRequest::Tx(FunctionCallDefinition { diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index b6c5c1e..8b374dc 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -2,6 +2,7 @@ mod commands; mod default_scenarios; use std::{ + env, io::Write, str::FromStr, sync::{Arc, LazyLock}, @@ -286,11 +287,16 @@ async fn main() -> Result<(), Box> { None, ))?; + let fill_percent = env::var("C_FILL_PERCENT") + .map(|s| u16::from_str(&s).expect("invalid u16: fill_percent")) + .unwrap_or(100u16); + let scenario_config = match scenario { BuiltinScenario::FillBlock => BuiltinScenarioConfig::fill_block( block_gas_limit, txs_per_duration as u64, admin_signer.address(), + fill_percent, ), }; let testconfig: TestConfig = scenario_config.into(); diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index f24f614..9900569 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -15,7 +15,7 @@ rand = { workspace = true } serde = { workspace = true, features = ["derive"] } futures = { workspace = true } async-trait = { workspace = true } -tokio = { workspace = true } +tokio = { workspace = true, features = ["signal"]} alloy-serde = { workspace = true } serde_json = { workspace = true } contender_bundle_provider = { workspace = true } diff --git a/crates/core/src/spammer/spammer_trait.rs b/crates/core/src/spammer/spammer_trait.rs index 2631c68..7aa6440 100644 --- a/crates/core/src/spammer/spammer_trait.rs +++ b/crates/core/src/spammer/spammer_trait.rs @@ -1,3 +1,4 @@ +use std::sync::Mutex; use std::{pin::Pin, sync::Arc}; use alloy::providers::Provider; @@ -39,6 +40,17 @@ where run_id: Option, sent_tx_callback: Arc, ) -> impl std::future::Future> { + let quit = Arc::new(Mutex::new(false)); + + let quit_clone = quit.clone(); + tokio::task::spawn(async move { + loop { + let _ = tokio::signal::ctrl_c().await; + let mut quit = quit_clone.lock().unwrap(); + *quit = true; + } + }); + async move { let tx_requests = scenario .load_txs(crate::generator::PlanType::Spam( @@ -57,36 +69,45 @@ where let mut cursor = self.on_spam(scenario).await?.take(num_periods); while let Some(trigger) = cursor.next().await { + if *quit.lock().expect("lock failure") { + println!("CTRL-C received, stopping spam and collecting results..."); + let mut quit = quit.lock().expect("lock failure"); + *quit = false; + break; + } + let trigger = trigger.to_owned(); let payloads = scenario.prepare_spam(tx_req_chunks[tick]).await?; let spam_tasks = scenario .execute_spam(trigger, &payloads, sent_tx_callback.clone()) .await?; for task in spam_tasks { - task.await - .map_err(|e| ContenderError::with_err(e, "spam task failed"))?; + let res = task.await; + if let Err(e) = res { + eprintln!("spam task failed: {:?}", e); + } } tick += 1; } - let mut timeout_counter = 0; + let mut block_counter = 0; if let Some(run_id) = run_id { loop { - if timeout_counter > (num_periods * txs_per_period + 1) { - println!("quitting due to timeout"); - break; - } let cache_size = scenario .msg_handle - .flush_cache(run_id, block_num + timeout_counter as u64) + .flush_cache(run_id, block_num + block_counter as u64) .await .expect("failed to flush cache"); if cache_size == 0 { break; } - timeout_counter += 1; + if *quit.lock().expect("lock failure") { + println!("CTRL-C received, stopping result collection..."); + break; + } + block_counter += 1; } - println!("done spamming. run_id={}", run_id); + println!("done. run_id={}", run_id); } Ok(()) diff --git a/crates/core/src/spammer/tx_actor.rs b/crates/core/src/spammer/tx_actor.rs index 53a2675..35d3b87 100644 --- a/crates/core/src/spammer/tx_actor.rs +++ b/crates/core/src/spammer/tx_actor.rs @@ -1,6 +1,6 @@ use std::{sync::Arc, time::Duration}; -use alloy::{primitives::TxHash, providers::Provider}; +use alloy::{network::ReceiptResponse, primitives::TxHash, providers::Provider}; use tokio::sync::{mpsc, oneshot}; use crate::{ @@ -117,7 +117,7 @@ where .await? .unwrap_or_default(); println!( - "found {} receipts for block #{}", + "found {} receipts for block {}", receipts.len(), target_block_num ); @@ -150,6 +150,19 @@ where .iter() .find(|r| r.transaction_hash == pending_tx.tx_hash) .expect("this should never happen"); + if !receipt.status() { + println!("tx failed: {:?}", pending_tx.tx_hash); + } else { + println!( + "tx landed. hash={}\tgas_used={}\tblock_num={}", + pending_tx.tx_hash, + receipt.gas_used, + receipt + .block_number + .map(|n| n.to_string()) + .unwrap_or("N/A".to_owned()) + ); + } RunTx { tx_hash: pending_tx.tx_hash, start_timestamp: pending_tx.start_timestamp, diff --git a/crates/core/src/test_scenario.rs b/crates/core/src/test_scenario.rs index f9b6d7b..49c8810 100644 --- a/crates/core/src/test_scenario.rs +++ b/crates/core/src/test_scenario.rs @@ -13,7 +13,7 @@ use alloy::consensus::Transaction; use alloy::eips::eip2718::Encodable2718; use alloy::hex::ToHexExt; use alloy::network::{AnyNetwork, EthereumWallet, TransactionBuilder}; -use alloy::primitives::{Address, FixedBytes}; +use alloy::primitives::{keccak256, Address, FixedBytes}; use alloy::providers::{PendingTransactionConfig, Provider, ProviderBuilder}; use alloy::rpc::types::TransactionRequest; use alloy::signers::local::PrivateKeySigner; @@ -43,7 +43,7 @@ where pub agent_store: AgentStore, pub nonces: HashMap, pub chain_id: u64, - pub gas_limits: HashMap, u128>, + pub gas_limits: HashMap, u128>, pub msg_handle: Arc, } @@ -295,29 +295,20 @@ where ))? .to_owned(); self.nonces.insert(from.to_owned(), nonce + 1); - let fn_sig = FixedBytes::<4>::from_slice( - tx_req - .input - .input - .to_owned() - .map(|b| b.split_at(4).0.to_owned()) - .ok_or(ContenderError::SetupError( - "invalid function call", - Some(format!("{:?}", tx_req.input.input)), - ))? - .as_slice(), - ); - if !self.gas_limits.contains_key(fn_sig.as_slice()) { + + let key = keccak256(tx_req.input.input.to_owned().unwrap_or_default()); + + if let std::collections::hash_map::Entry::Vacant(_) = self.gas_limits.entry(key) { let gas_limit = self .eth_client .estimate_gas(tx_req) .await .map_err(|e| ContenderError::with_err(e, "failed to estimate gas for tx"))?; - self.gas_limits.insert(fn_sig, gas_limit); + self.gas_limits.insert(key, gas_limit); } let gas_limit = self .gas_limits - .get(&fn_sig) + .get(&key) .ok_or(ContenderError::SetupError( "failed to lookup gas limit", None, @@ -337,7 +328,7 @@ where .with_max_fee_per_gas(gas_price + (gas_price / 5)) .with_max_priority_fee_per_gas(gas_price) .with_chain_id(self.chain_id) - .with_gas_limit(gas_limit + (gas_limit / 6)); + .with_gas_limit(gas_limit); Ok((full_tx, signer)) } @@ -396,7 +387,7 @@ where })?; println!( - "sending tx {} from={} to={:?} input={} value={}", + "sending tx {} from={} to={:?} input={} value={} gas_limit={}", tx_envelope.tx_hash(), tx_req.from.map(|s| s.encode_hex()).unwrap_or_default(), tx_envelope.to().to(), @@ -409,7 +400,11 @@ where tx_req .value .map(|s| s.to_string()) - .unwrap_or_else(|| "0".to_owned()) + .unwrap_or_else(|| "0".to_owned()), + tx_req + .gas + .map(|g| g.to_string()) + .unwrap_or_else(|| "N/A".to_owned()) ); ExecutionPayload::SignedTx(tx_envelope, req.to_owned()) diff --git a/scenarios/mempool.toml b/scenarios/mempool.toml index 5d99fe8..6c1f3db 100644 --- a/scenarios/mempool.toml +++ b/scenarios/mempool.toml @@ -9,7 +9,8 @@ from = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" to = "{SpamMe2}" from_pool = "redpool" signature = "consumeGas(uint256 gasAmount)" -args = ["51000"] +args = ["910000"] +fuzz = [{ param = "gasAmount", min = "10000000", max = "30000000" }] [[spam]] @@ -17,4 +18,4 @@ args = ["51000"] to = "{SpamMe2}" from_pool = "bluepool" signature = "consumeGas(uint256 gasAmount)" -args = ["51000"] +args = ["13500000"] diff --git a/scenarios/stress.toml b/scenarios/stress.toml new file mode 100644 index 0000000..08e5c54 --- /dev/null +++ b/scenarios/stress.toml @@ -0,0 +1,41 @@ +[[create]] +bytecode = "0x608060405234801561001057600080fd5b5060408051808201909152600d81526c48656c6c6f2c20576f726c642160981b602082015260009061004290826100e7565b506101a5565b634e487b7160e01b600052604160045260246000fd5b600181811c9082168061007257607f821691505b60208210810361009257634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156100e257806000526020600020601f840160051c810160208510156100bf5750805b601f840160051c820191505b818110156100df57600081556001016100cb565b50505b505050565b81516001600160401b0381111561010057610100610048565b6101148161010e845461005e565b84610098565b6020601f82116001811461014857600083156101305750848201515b600019600385901b1c1916600184901b1784556100df565b600084815260208120601f198516915b828110156101785787850151825560209485019460019092019101610158565b50848210156101965786840151600019600387901b60f8161c191681555b50505050600190811b01905550565b610a72806101b46000396000f3fe6080604052600436106100555760003560e01c806369f86ec81461005a5780638199ba20146100715780639402c00414610091578063a329e8de146100b1578063c5eeaf17146100d1578063fb0e722b146100d9575b600080fd5b34801561006657600080fd5b5061006f610104565b005b34801561007d57600080fd5b5061006f61008c3660046106f6565b61010f565b34801561009d57600080fd5b5061006f6100ac36600461074f565b610413565b3480156100bd57600080fd5b5061006f6100cc3660046107a0565b610444565b61006f6104d7565b3480156100e557600080fd5b506100ee610506565b6040516100fb91906107dd565b60405180910390f35b5b60325a1161010557565b6040805180820190915260068152657373746f726560d01b6020820152610137908390610594565b1561015d5760005b818110156101585761015060008055565b60010161013f565b505050565b6040805180820190915260058152641cdb1bd85960da1b6020820152610184908390610594565b1561019c5760005b818110156101585760010161018c565b6040805180820190915260068152656d73746f726560d01b60208201526101c4908390610594565b156101e55760005b81811015610158576101dd60008052565b6001016101cc565b6040805180820190915260058152641b5b1bd85960da1b602082015261020c908390610594565b1561022157600081156101585760010161018c565b60408051808201909152600381526218591960ea1b6020820152610246908390610594565b1561025b57600081156101585760010161018c565b60408051808201909152600381526239bab160e91b6020820152610280908390610594565b1561029557600081156101585760010161018c565b6040805180820190915260038152621b5d5b60ea1b60208201526102ba908390610594565b156102cf57600081156101585760010161018c565b6040805180820190915260038152623234bb60e91b60208201526102f4908390610594565b1561030957600081156101585760010161018c565b60408051808201909152600981526832b1b932b1b7bb32b960b91b6020820152610334908390610594565b156103545760005b818110156101585761034c6105ee565b60010161033c565b60408051808201909152600981526835b2b1b1b0b5991a9b60b91b602082015261037f908390610594565b1561039457600081156101585760010161018c565b60408051808201909152600781526662616c616e636560c81b60208201526103bd908390610594565b156103d257600081156101585760010161018c565b60408051808201909152600681526531b0b63632b960d11b60208201526103fa908390610594565b1561040f57600081156101585760010161018c565b5050565b60008160405160200161042792919061084a565b6040516020818303038152906040526000908161040f919061091e565b600081116104985760405162461bcd60e51b815260206004820152601a60248201527f476173206d7573742062652067726561746572207468616e2030000000000000604482015260640160405180910390fd5b600060956104a8610a28846109dd565b6104b291906109fe565b9050806000036104c0575060015b60005b8181101561015857600080556001016104c3565b60405141903480156108fc02916000818181858888f19350505050158015610503573d6000803e3d6000fd5b50565b6000805461051390610810565b80601f016020809104026020016040519081016040528092919081815260200182805461053f90610810565b801561058c5780601f106105615761010080835404028352916020019161058c565b820191906000526020600020905b81548152906001019060200180831161056f57829003601f168201915b505050505081565b6000816040516020016105a79190610a20565b60405160208183030381529060405280519060200120836040516020016105ce9190610a20565b604051602081830303815290604052805190602001201490505b92915050565b604080516000808252602082018084527f7b05e003631381b3ecd0222e748a7900c262a008c4b7f002ce4a9f0a190619539052604292820183905260608201839052608082019290925260019060a0016020604051602081039080840390855afa158015610660573d6000803e3d6000fd5b50505050565b634e487b7160e01b600052604160045260246000fd5b60008067ffffffffffffffff84111561069757610697610666565b50604051601f19601f85018116603f0116810181811067ffffffffffffffff821117156106c6576106c6610666565b6040528381529050808284018510156106de57600080fd5b83836020830137600060208583010152509392505050565b6000806040838503121561070957600080fd5b823567ffffffffffffffff81111561072057600080fd5b8301601f8101851361073157600080fd5b6107408582356020840161067c565b95602094909401359450505050565b60006020828403121561076157600080fd5b813567ffffffffffffffff81111561077857600080fd5b8201601f8101841361078957600080fd5b6107988482356020840161067c565b949350505050565b6000602082840312156107b257600080fd5b5035919050565b60005b838110156107d45781810151838201526020016107bc565b50506000910152565b60208152600082518060208401526107fc8160408501602087016107b9565b601f01601f19169190910160400192915050565b600181811c9082168061082457607f821691505b60208210810361084457634e487b7160e01b600052602260045260246000fd5b50919050565b600080845461085881610810565b60018216801561086f5760018114610884576108b4565b60ff19831686528115158202860193506108b4565b87600052602060002060005b838110156108ac57815488820152600190910190602001610890565b505081860193505b50505083516108c78183602088016107b9565b01949350505050565b601f82111561015857806000526020600020601f840160051c810160208510156108f75750805b601f840160051c820191505b818110156109175760008155600101610903565b5050505050565b815167ffffffffffffffff81111561093857610938610666565b61094c816109468454610810565b846108d0565b6020601f82116001811461098057600083156109685750848201515b600019600385901b1c1916600184901b178455610917565b600084815260208120601f198516915b828110156109b05787850151825560209485019460019092019101610990565b50848210156109ce5786840151600019600387901b60f8161c191681555b50505050600190811b01905550565b818103818111156105e857634e487b7160e01b600052601160045260246000fd5b600082610a1b57634e487b7160e01b600052601260045260246000fd5b500490565b60008251610a328184602087016107b9565b919091019291505056fea264697066735822122040db52b9a7c8a77f16a18198a6085a3ff5f3e5c378e4a9cd497037d20f775eb864736f6c634300081b0033" +name = "SpamMe3" +from = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + +[[spam]] + +[spam.tx] +to = "{SpamMe3}" +from_pool = "pool1" +signature = "consumeGas(string memory method, uint256 iterations)" +args = ["sstore", "8000"] +# note: the `fuzz` field will spam the network with `estimateGas` calls; every unique calldata requires a new call +# fuzz = [{ param = "iterations", min = "3000", max = "8000" }] + +[[spam]] + +[spam.tx] +to = "{SpamMe3}" +from_pool = "pool2" +signature = "consumeGas(string memory method, uint256 iterations)" +args = ["sload", "8000"] +# fuzz = [{ param = "iterations", min = "3000", max = "8000" }] + +[[spam]] + +[spam.tx] +to = "{SpamMe3}" +from_pool = "pool3" +signature = "consumeGas(string memory method, uint256 iterations)" +args = ["mload", "8000"] +# fuzz = [{ param = "iterations", min = "3000", max = "8000" }] + +[[spam]] + +[spam.tx] +to = "{SpamMe3}" +from_pool = "pool4" +signature = "consumeGas(string memory method, uint256 iterations)" +args = ["mstore", "8000"] +# fuzz = [{ param = "iterations", min = "3000", max = "8000" }]