From 1210bbe9bbed16cfcff5af3d8c2596b554e731ad Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Fri, 15 Nov 2024 11:18:17 -0800 Subject: [PATCH 1/9] add simple wallet store (unimplemented) --- crates/core/src/agent_controller.rs | 42 +++++++++++++++++++++++++++++ crates/core/src/lib.rs | 1 + 2 files changed, 43 insertions(+) create mode 100644 crates/core/src/agent_controller.rs diff --git a/crates/core/src/agent_controller.rs b/crates/core/src/agent_controller.rs new file mode 100644 index 0000000..6a3d05b --- /dev/null +++ b/crates/core/src/agent_controller.rs @@ -0,0 +1,42 @@ +use alloy::{primitives::Address, signers::local::PrivateKeySigner}; + +pub trait SignerRegistry { + fn get_signer(&self, idx: Index) -> Option<&PrivateKeySigner>; +} + +pub struct SignerStore { + signers: Vec, +} + +impl SignerRegistry for SignerStore { + fn get_signer(&self, idx: usize) -> Option<&PrivateKeySigner> { + self.signers.get(idx) + } +} + +impl SignerStore { + pub fn new() -> Self { + SignerStore { + signers: Vec::new(), + } + } + + pub fn new_random(num_signers: usize) -> Self { + let signers: Vec = (0..num_signers) + .map(|_| PrivateKeySigner::random()) + .collect(); + SignerStore { signers } + } + + pub fn add_signer(&mut self, signer: PrivateKeySigner) { + self.signers.push(signer); + } + + pub fn remove_signer(&mut self, idx: usize) { + self.signers.remove(idx); + } + + pub fn get_address(&self, idx: usize) -> Option
{ + self.signers.get(idx).map(|s| s.address()) + } +} diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index d84c061..8ba1708 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -1,3 +1,4 @@ +pub mod agent_controller; pub mod bundle_provider; pub mod db; pub mod error; From 928134ca69577cea9470c9e6ee025e7a3df25403 Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Fri, 15 Nov 2024 11:31:57 -0800 Subject: [PATCH 2/9] syntax cleanups --- crates/core/src/generator/mod.rs | 44 +++++++++---------- crates/core/src/generator/seeder/rand_seed.rs | 2 +- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/crates/core/src/generator/mod.rs b/crates/core/src/generator/mod.rs index 9cc2662..ee48607 100644 --- a/crates/core/src/generator/mod.rs +++ b/crates/core/src/generator/mod.rs @@ -189,29 +189,29 @@ where let mut placeholder_map = HashMap::::new(); let mut canonical_fuzz_map = HashMap::>::new(); - for step in spam_steps.iter() { - // finds fuzzed values for a function call definition and populates `canonical_fuzz_map` with fuzzy values. - let mut find_fuzz = |req: &FunctionCallDefinition| { - let fuzz_args = req.fuzz.to_owned().unwrap_or(vec![]); - let fuzz_map = self.create_fuzz_map(num_txs, &fuzz_args)?; // this may create more values than needed, but it's fine - canonical_fuzz_map.extend(fuzz_map); - Ok(()) - }; - - // finds placeholders in a function call definition and populates `placeholder_map` and `canonical_fuzz_map` with injectable values. - let mut lookup_tx_placeholders = |tx: &FunctionCallDefinition| { - let res = templater.find_fncall_placeholders(tx, db, &mut placeholder_map); - if let Err(e) = res { - eprintln!("error finding placeholders: {}", e); - return Err(ContenderError::SpamError( - "failed to find placeholder value", - Some(e.to_string()), - )); - } - find_fuzz(tx)?; - Ok(()) - }; + // finds fuzzed values for a function call definition and populates `canonical_fuzz_map` with fuzzy values. + let mut find_fuzz = |req: &FunctionCallDefinition| { + let fuzz_args = req.fuzz.to_owned().unwrap_or(vec![]); + let fuzz_map = self.create_fuzz_map(num_txs, &fuzz_args)?; // this may create more values than needed, but it's fine + canonical_fuzz_map.extend(fuzz_map); + Ok(()) + }; + + // finds placeholders in a function call definition and populates `placeholder_map` and `canonical_fuzz_map` with injectable values. + let mut lookup_tx_placeholders = |tx: &FunctionCallDefinition| { + let res = templater.find_fncall_placeholders(tx, db, &mut placeholder_map); + if let Err(e) = res { + eprintln!("error finding placeholders: {}", e); + return Err(ContenderError::SpamError( + "failed to find placeholder value", + Some(e.to_string()), + )); + } + find_fuzz(tx)?; + Ok(()) + }; + for step in spam_steps.iter() { // populate maps for each step match step { SpamRequest::Tx(tx) => { diff --git a/crates/core/src/generator/seeder/rand_seed.rs b/crates/core/src/generator/seeder/rand_seed.rs index 664beb2..78f4bf2 100644 --- a/crates/core/src/generator/seeder/rand_seed.rs +++ b/crates/core/src/generator/seeder/rand_seed.rs @@ -94,7 +94,7 @@ impl Seeder for RandSeed { let val = val % (max - min) + min; RandSeed::from_u256(val) }); - Box::new(vals.to_owned()) + Box::new(vals) } } From 76798955fed63cb702bcfc52876e24efb47a749d Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Fri, 15 Nov 2024 11:55:51 -0800 Subject: [PATCH 3/9] move bundle_provider to its own crate (should be replaced entirely, anyhow) --- Cargo.lock | 12 +++++++++++- Cargo.toml | 3 ++- crates/bundle_provider/Cargo.toml | 15 +++++++++++++++ .../src/bundle_provider.rs | 0 crates/bundle_provider/src/lib.rs | 3 +++ crates/core/Cargo.toml | 2 +- crates/core/src/lib.rs | 1 - crates/core/src/spammer/blockwise.rs | 2 +- 8 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 crates/bundle_provider/Cargo.toml rename crates/{core => bundle_provider}/src/bundle_provider.rs (100%) create mode 100644 crates/bundle_provider/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 7e3beac..5ba7608 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1142,6 +1142,16 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "contender_bundle_provider" +version = "0.1.0" +dependencies = [ + "alloy", + "alloy-serde 0.5.4", + "jsonrpsee", + "serde", +] + [[package]] name = "contender_cli" version = "0.1.0" @@ -1163,9 +1173,9 @@ dependencies = [ "alloy", "alloy-serde 0.5.4", "async-trait", + "contender_bundle_provider", "eyre", "futures", - "jsonrpsee", "rand", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 8018d30..8987425 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = [ +members = [ "crates/bundle_provider", "crates/cli/", "crates/core/", "crates/sqlite_db/", @@ -22,6 +22,7 @@ repository = "https://github.com/flashbots/contender" contender_core = { path = "crates/core/" } contender_sqlite = { path = "crates/sqlite_db/" } contender_testfile = { path = "crates/testfile/" } +contender_bundle_provider = { path = "crates/bundle_provider/" } eyre = "0.6.12" tokio = { version = "1.40.0" } diff --git a/crates/bundle_provider/Cargo.toml b/crates/bundle_provider/Cargo.toml new file mode 100644 index 0000000..d6e090c --- /dev/null +++ b/crates/bundle_provider/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "contender_bundle_provider" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +alloy = { workspace = true, features = ["node-bindings", "rpc-types-mev"] } +jsonrpsee = { workspace = true, features = ["http-client", "client-core"] } +serde = { workspace = true, features = ["derive"] } +alloy-serde = { workspace = true } diff --git a/crates/core/src/bundle_provider.rs b/crates/bundle_provider/src/bundle_provider.rs similarity index 100% rename from crates/core/src/bundle_provider.rs rename to crates/bundle_provider/src/bundle_provider.rs diff --git a/crates/bundle_provider/src/lib.rs b/crates/bundle_provider/src/lib.rs new file mode 100644 index 0000000..54aed13 --- /dev/null +++ b/crates/bundle_provider/src/lib.rs @@ -0,0 +1,3 @@ +pub mod bundle_provider; + +pub use bundle_provider::{BundleClient, EthSendBundle, EthSendBundleResponse}; diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index d62fb55..7769afa 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -16,6 +16,6 @@ serde = { workspace = true, features = ["derive"] } futures = { workspace = true } async-trait = { workspace = true } tokio = { workspace = true } -jsonrpsee = { workspace = true, features = ["http-client", "client-core"] } alloy-serde = { workspace = true } serde_json = { workspace = true } +contender_bundle_provider = { workspace = true } diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 8ba1708..05c0219 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -1,5 +1,4 @@ pub mod agent_controller; -pub mod bundle_provider; pub mod db; pub mod error; pub mod generator; diff --git a/crates/core/src/spammer/blockwise.rs b/crates/core/src/spammer/blockwise.rs index e69fb84..e8eeae3 100644 --- a/crates/core/src/spammer/blockwise.rs +++ b/crates/core/src/spammer/blockwise.rs @@ -1,4 +1,3 @@ -use crate::bundle_provider::{BundleClient, EthSendBundle}; use crate::db::DbOps; use crate::error::ContenderError; use crate::generator::named_txs::ExecutionRequest; @@ -16,6 +15,7 @@ use alloy::network::{AnyNetwork, EthereumWallet, TransactionBuilder}; use alloy::primitives::{Address, FixedBytes}; use alloy::providers::{PendingTransactionConfig, Provider, ProviderBuilder}; use alloy::rpc::types::TransactionRequest; +use contender_bundle_provider::{BundleClient, EthSendBundle}; use futures::StreamExt; use std::collections::HashMap; use std::ops::Deref; From 823794c97275cc99125e4eb9cf24a7d9de014305 Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Fri, 15 Nov 2024 11:58:04 -0800 Subject: [PATCH 4/9] db/mod.rs => db.rs --- crates/core/src/{db/mod.rs => db.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename crates/core/src/{db/mod.rs => db.rs} (100%) diff --git a/crates/core/src/db/mod.rs b/crates/core/src/db.rs similarity index 100% rename from crates/core/src/db/mod.rs rename to crates/core/src/db.rs From 88438c6a186fee45015e042e647118c62cea47f3 Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Fri, 15 Nov 2024 17:01:47 -0800 Subject: [PATCH 5/9] inject pool signers with generator (TODO: fund them) --- crates/cli/src/main.rs | 44 ++++++++++++---- crates/core/src/agent_controller.rs | 69 +++++++++++++++++++++--- crates/core/src/generator/mod.rs | 72 +++++++++++++++++++++++--- crates/core/src/generator/templater.rs | 7 +-- crates/core/src/generator/types.rs | 14 ++++- crates/core/src/spammer/blockwise.rs | 22 +++++++- crates/core/src/test_scenario.rs | 21 ++++++-- crates/testfile/src/lib.rs | 23 ++++++-- scenarios/spamMe.toml | 1 + 9 files changed, 236 insertions(+), 37 deletions(-) diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index ec11724..fbe619b 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1,17 +1,20 @@ mod commands; use alloy::{ - network::AnyNetwork, + network::{AnyNetwork, EthereumWallet, TransactionBuilder}, + node_bindings::WEI_IN_ETHER, primitives::{ utils::{format_ether, parse_ether}, Address, U256, }, - providers::{Provider, ProviderBuilder}, + providers::{fillers::WalletFiller, Provider, ProviderBuilder}, + rpc::types::TransactionRequest, signers::local::PrivateKeySigner, transports::http::reqwest::Url, }; use commands::{ContenderCli, ContenderSubcommand}; use contender_core::{ + agent_controller::{AgentStore, SignerStore}, db::{DbOps, RunTx}, generator::{ types::{AnyProvider, FunctionCallDefinition, SpamRequest}, @@ -90,6 +93,7 @@ async fn main() -> Result<(), Box> { None, RandSeed::new(), &signers, + Default::default(), ); scenario.deploy_contracts().await?; @@ -125,13 +129,22 @@ async fn main() -> Result<(), Box> { // distill all FunctionCallDefinitions from the spam requests let mut fn_calls = vec![]; + let mut from_pools = vec![]; for s in spam { match s { SpamRequest::Tx(fn_call) => { fn_calls.push(fn_call.to_owned()); + if let Some(from_pool) = &fn_call.from_pool { + from_pools.push(from_pool); + } } SpamRequest::Bundle(bundle) => { - fn_calls.extend(bundle.txs.iter().map(|s| s.to_owned())); + fn_calls.extend(bundle.txs.iter().map(|s| { + if let Some(from_pool) = &s.from_pool { + from_pools.push(from_pool); + } + s.to_owned() + })); } } } @@ -139,6 +152,18 @@ async fn main() -> Result<(), Box> { check_private_keys(&fn_calls, signers.as_slice()); check_balances(signers.as_slice(), min_balance, &rpc_client).await; + let mut agents = AgentStore::new(); + let signers_per_block = txs_per_block.unwrap_or(spam.len()) / spam.len(); + + for from_pool in from_pools { + if agents.has_agent(from_pool) { + continue; + } + + let agent = SignerStore::new_random(signers_per_block); + agents.add_agent(from_pool, agent); + } + if txs_per_block.is_some() && txs_per_second.is_some() { panic!("Cannot set both --txs-per-block and --txs-per-second"); } @@ -154,6 +179,7 @@ async fn main() -> Result<(), Box> { builder_url.map(|url| Url::parse(&url).expect("Invalid builder URL")), rand_seed, &signers, + agents, ); println!("Blockwise spamming with {} txs per block", txs_per_block); match spam_callback_default(!disable_reports, Arc::new(rpc_client).into()).await { @@ -186,6 +212,7 @@ async fn main() -> Result<(), Box> { None, rand_seed, &signers, + agents, ); let tps = txs_per_second.unwrap_or(10); println!("Timed spamming with {} txs per second", tps); @@ -236,12 +263,11 @@ enum SpamCallbackType { /// Panics if any of the function calls' `from` addresses do not have a corresponding private key. fn check_private_keys(fn_calls: &[FunctionCallDefinition], prv_keys: &[PrivateKeySigner]) { for fn_call in fn_calls { - let address = fn_call - .from - .parse::
() - .expect("invalid 'from' address"); - if prv_keys.iter().all(|k| k.address() != address) { - panic!("No private key found for address: {}", address); + if let Some(from) = &fn_call.from { + let address = from.parse::
().expect("invalid 'from' address"); + if prv_keys.iter().all(|k| k.address() != address) { + panic!("No private key found for address: {}", address); + } } } } diff --git a/crates/core/src/agent_controller.rs b/crates/core/src/agent_controller.rs index 6a3d05b..fb63b4f 100644 --- a/crates/core/src/agent_controller.rs +++ b/crates/core/src/agent_controller.rs @@ -1,16 +1,73 @@ +use std::collections::HashMap; + use alloy::{primitives::Address, signers::local::PrivateKeySigner}; pub trait SignerRegistry { fn get_signer(&self, idx: Index) -> Option<&PrivateKeySigner>; + fn get_address(&self, idx: Index) -> Option
; +} + +pub trait AgentRegistry { + fn get_agent(&self, idx: Index) -> Option<&Address>; } pub struct SignerStore { - signers: Vec, + pub signers: Vec, +} + +pub struct AgentStore { + agents: HashMap, } -impl SignerRegistry for SignerStore { - fn get_signer(&self, idx: usize) -> Option<&PrivateKeySigner> { - self.signers.get(idx) +impl Default for AgentStore { + fn default() -> Self { + Self::new() + } +} + +impl AgentStore { + pub fn new() -> Self { + AgentStore { + agents: HashMap::new(), + } + } + + pub fn add_agent(&mut self, name: impl AsRef, signers: SignerStore) { + self.agents.insert(name.as_ref().to_owned(), signers); + } + + pub fn add_random_agent(&mut self, name: impl AsRef, num_signers: usize) { + let signers = SignerStore::new_random(num_signers); + self.add_agent(name, signers); + } + + pub fn get_agent(&self, name: impl AsRef) -> Option<&SignerStore> { + self.agents.get(name.as_ref()) + } + + pub fn all_agents(&self) -> impl Iterator { + self.agents.iter() + } + + pub fn has_agent(&self, name: impl AsRef) -> bool { + self.agents.contains_key(name.as_ref()) + } + + pub fn remove_agent(&mut self, name: impl AsRef) { + self.agents.remove(name.as_ref()); + } +} + +impl SignerRegistry for SignerStore +where + Idx: Ord + Into, +{ + fn get_signer(&self, idx: Idx) -> Option<&PrivateKeySigner> { + self.signers.get::(idx.into()) + } + + fn get_address(&self, idx: Idx) -> Option
{ + self.signers.get::(idx.into()).map(|s| s.address()) } } @@ -35,8 +92,4 @@ impl SignerStore { pub fn remove_signer(&mut self, idx: usize) { self.signers.remove(idx); } - - pub fn get_address(&self, idx: usize) -> Option
{ - self.signers.get(idx).map(|s| s.address()) - } } diff --git a/crates/core/src/generator/mod.rs b/crates/core/src/generator/mod.rs index ee48607..e77af32 100644 --- a/crates/core/src/generator/mod.rs +++ b/crates/core/src/generator/mod.rs @@ -1,4 +1,5 @@ use crate::{ + agent_controller::{AgentStore, SignerRegistry}, db::DbOps, error::ContenderError, generator::{ @@ -8,13 +9,13 @@ use crate::{ }, Result, }; -use alloy::primitives::U256; +use alloy::{hex::ToHexExt, primitives::U256}; use async_trait::async_trait; use named_txs::ExecutionRequest; pub use named_txs::NamedTxRequestBuilder; pub use seeder::rand_seed::RandSeed; use std::{collections::HashMap, fmt::Debug, hash::Hash}; -use types::SpamRequest; +use types::{FunctionCallDefinitionStrict, SpamRequest}; pub use types::{CallbackResult, NamedTxRequest, PlanType}; @@ -97,6 +98,7 @@ where fn get_templater(&self) -> &T; fn get_db(&self) -> &D; fn get_fuzz_seeder(&self) -> &impl Seeder; + fn get_agent_store(&self) -> &AgentStore; /// Generates a map of N=`num_values` fuzzed values for each parameter in `fuzz_args`. fn create_fuzz_map( @@ -120,6 +122,48 @@ where Ok(map) } + fn make_strict_call( + &self, + funcdef: &FunctionCallDefinition, + idx: usize, + ) -> Result { + let agents = self.get_agent_store(); + let from_address: String = if let Some(from_pool) = &funcdef.from_pool { + let agent = agents + .get_agent(from_pool) + .ok_or(ContenderError::SpamError( + "from_pool not found in agent store", + Some(from_pool.to_owned()), + ))?; + agent + .get_address(idx) + .ok_or(ContenderError::SpamError( + "signer not found in agent store", + Some(format!("from_pool={}, idx={}", from_pool, idx)), + ))? + .encode_hex_with_prefix() + } else if let Some(from) = &funcdef.from { + from.to_owned() + } else { + return Err(ContenderError::SpamError( + "invalid runtime config: must specify 'from' or 'from_pool'", + None, + )); + }; + + println!("[make_strict_call] using 'from' address: {}", from_address); + + Ok(FunctionCallDefinitionStrict { + to: funcdef.to.to_owned(), + from: from_address, + signature: funcdef.signature.to_owned(), + args: funcdef.args.to_owned().unwrap_or_default(), + value: funcdef.value.to_owned(), + fuzz: funcdef.fuzz.to_owned().unwrap_or_default(), + kind: funcdef.kind.to_owned(), + }) + } + async fn load_txs CallbackResult>( &self, plan_type: PlanType, @@ -167,7 +211,10 @@ where // setup tx with template values let tx = NamedTxRequest::new( - templater.template_function_call(step, &placeholder_map)?, + templater.template_function_call( + &self.make_strict_call(step, 0)?, + &placeholder_map, + )?, None, step.kind.to_owned(), ); @@ -212,7 +259,7 @@ where }; for step in spam_steps.iter() { - // populate maps for each step + // populate placeholder map for each step match step { SpamRequest::Tx(tx) => { lookup_tx_placeholders(tx)?; @@ -226,7 +273,7 @@ where } for i in 0..(num_txs / num_steps) { - for step in spam_steps.iter() { + for (j, step) in spam_steps.iter().enumerate() { // converts a FunctionCallDefinition to a NamedTxRequest (filling in fuzzable args), // returns a callback handle and the processed tx request let process_tx = |req| { @@ -234,11 +281,24 @@ where let fuzz_tx_value = get_fuzzed_tx_value(req, &canonical_fuzz_map, i); let mut req = req.to_owned(); req.args = Some(args); + + // DELETE ME ////////////////// + if req.from_pool.is_some() { + println!("i: {}", i); + println!("j: {}", j); + println!("step: {:?}", step); + } + ////////////////////////////// + if fuzz_tx_value.is_some() { req.value = fuzz_tx_value; } + let tx = NamedTxRequest::new( - templater.template_function_call(&req, &placeholder_map)?, + templater.template_function_call( + &self.make_strict_call(&req, j)?, // 'from' address injected here + &placeholder_map, + )?, None, req.kind.to_owned(), ); diff --git a/crates/core/src/generator/templater.rs b/crates/core/src/generator/templater.rs index 055c214..689de2b 100644 --- a/crates/core/src/generator/templater.rs +++ b/crates/core/src/generator/templater.rs @@ -14,6 +14,8 @@ use alloy::{ }; use std::collections::HashMap; +use super::types::FunctionCallDefinitionStrict; + pub trait Templater where K: Eq + std::hash::Hash + ToString + std::fmt::Debug + Send + Sync, @@ -90,12 +92,11 @@ where /// {placeholders} filled in using corresponding values from `placeholder_map`. fn template_function_call( &self, - funcdef: &FunctionCallDefinition, + funcdef: &FunctionCallDefinitionStrict, placeholder_map: &HashMap, ) -> Result { - let step_args = funcdef.args.to_owned().unwrap_or_default(); let mut args = Vec::new(); - for arg in step_args.iter() { + for arg in funcdef.args.iter() { let val = self.replace_placeholders(arg, placeholder_map); args.push(val); } diff --git a/crates/core/src/generator/types.rs b/crates/core/src/generator/types.rs index fe18c1e..753f566 100644 --- a/crates/core/src/generator/types.rs +++ b/crates/core/src/generator/types.rs @@ -24,7 +24,9 @@ pub struct FunctionCallDefinition { /// Address of the contract to call. pub to: String, /// Address of the tx sender. - pub from: String, + pub from: Option, + /// Get a `from` address from the pool of signers specified here. + pub from_pool: Option, /// Name of the function to call. pub signature: String, /// Parameters to pass to the function. @@ -37,6 +39,16 @@ pub struct FunctionCallDefinition { pub kind: Option, } +pub struct FunctionCallDefinitionStrict { + pub to: String, + pub from: String, + pub signature: String, + pub args: Vec, + pub value: Option, + pub fuzz: Vec, + pub kind: Option, +} + /// User-facing definition of a function call to be executed. #[derive(Clone, Deserialize, Debug, Serialize)] pub struct BundleCallDefinition { diff --git a/crates/core/src/spammer/blockwise.rs b/crates/core/src/spammer/blockwise.rs index e8eeae3..4d310fa 100644 --- a/crates/core/src/spammer/blockwise.rs +++ b/crates/core/src/spammer/blockwise.rs @@ -68,9 +68,22 @@ where )); let callback_handler = Arc::new(callback_handler); - // get nonce for each signer and put it into a hashmap let mut nonces = HashMap::new(); - for (addr, _) in scenario.wallet_map.iter() { + + // collect addresses from both wallet_map (user prvkeys) and agent_store (system prvkeys) + let mut all_addrs = scenario + .wallet_map + .iter() + .map(|(k, _)| *k) + .collect::>(); + for (_, agent) in scenario.agent_store.all_agents() { + for signer in agent.signers.iter() { + all_addrs.push(signer.address()); + } + } + + // all nonces for all addrs into the map + for addr in &all_addrs { let nonce = eth_client .get_transaction_count(*addr) .await @@ -191,6 +204,9 @@ where let mut tasks = vec![]; while let Some(block_hash) = stream.next().await { + // TODO: fund accounts if needed + // ... + let block_txs = tx_req_chunks[block_offset].clone(); block_offset += 1; @@ -400,6 +416,7 @@ where #[cfg(test)] mod tests { use crate::{ + agent_controller::AgentStore, db::MockDb, generator::util::test::spawn_anvil, spammer::util::test::{get_test_signers, MockCallback}, @@ -420,6 +437,7 @@ mod tests { None, seed, get_test_signers().as_slice(), + AgentStore::new(), ); let callback_handler = MockCallback; let mut spammer = BlockwiseSpammer::new(scenario, callback_handler).await; diff --git a/crates/core/src/test_scenario.rs b/crates/core/src/test_scenario.rs index cd84413..7217d11 100644 --- a/crates/core/src/test_scenario.rs +++ b/crates/core/src/test_scenario.rs @@ -1,3 +1,4 @@ +use crate::agent_controller::AgentStore; use crate::db::{DbOps, NamedTx}; use crate::error::ContenderError; use crate::generator::templater::Templater; @@ -24,7 +25,10 @@ where pub rpc_url: Url, pub builder_rpc_url: Option, pub rand_seed: S, + /// Wallets explicitly given by the user pub wallet_map: HashMap, + /// Wallets generated by the system + pub agent_store: AgentStore, } impl TestScenario @@ -40,6 +44,7 @@ where builder_rpc_url: Option, rand_seed: S, signers: &[PrivateKeySigner], + agent_store: AgentStore, ) -> Self { let mut wallet_map = HashMap::new(); let wallets = signers.iter().map(|s| { @@ -57,6 +62,7 @@ where builder_rpc_url, rand_seed, wallet_map, + agent_store, } } @@ -253,10 +259,15 @@ where fn get_fuzz_seeder(&self) -> &impl Seeder { &self.rand_seed } + + fn get_agent_store(&self) -> &AgentStore { + &self.agent_store + } } #[cfg(test)] pub mod tests { + use crate::agent_controller::AgentStore; use crate::db::MockDb; use crate::generator::templater::Templater; use crate::generator::types::{ @@ -297,7 +308,8 @@ pub mod tests { Ok(vec![ FunctionCallDefinition { to: "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D".to_owned(), - from: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".to_owned(), + from: Some("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".to_owned()), + from_pool: None, value: Some("4096".to_owned()), signature: "swap(uint256 x, uint256 y, address a, bytes b)".to_owned(), args: vec![ @@ -312,7 +324,8 @@ pub mod tests { }, FunctionCallDefinition { to: "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D".to_owned(), - from: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".to_owned(), + from: Some("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".to_owned()), + from_pool: None, value: Some("0x1000".to_owned()), signature: "swap(uint256 x, uint256 y, address a, bytes b)".to_owned(), args: vec![ @@ -332,7 +345,8 @@ pub mod tests { let fn_call = |data: &str, from_addr: &str| { SpamRequest::Tx(FunctionCallDefinition { to: "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D".to_owned(), - from: from_addr.to_owned(), + from: Some(from_addr.to_owned()), + from_pool: None, value: None, signature: "swap(uint256 x, uint256 y, address a, bytes b)".to_owned(), args: vec![ @@ -399,6 +413,7 @@ pub mod tests { None, seed, &signers, + AgentStore::new(), ) } diff --git a/crates/testfile/src/lib.rs b/crates/testfile/src/lib.rs index 3dd5c3f..8109794 100644 --- a/crates/testfile/src/lib.rs +++ b/crates/testfile/src/lib.rs @@ -143,7 +143,10 @@ pub mod tests { pub fn get_testconfig() -> TestConfig { let fncall = FunctionCallDefinition { to: "0x7a250d5630B4cF539739dF2C5dAcb4c659F248DD".to_owned(), - from: "0x7a250d5630B4cF539739dF2C5dAcb4c659F248DD".to_owned(), + from: "0x7a250d5630B4cF539739dF2C5dAcb4c659F248DD" + .to_owned() + .into(), + from_pool: None, signature: "swap(uint256 x, uint256 y, address a, bytes b)".to_owned(), args: vec![ "1".to_owned(), @@ -168,7 +171,8 @@ pub mod tests { pub fn get_fuzzy_testconfig() -> TestConfig { let fn_call = |data: &str, from_addr: &str| FunctionCallDefinition { to: "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D".to_owned(), - from: from_addr.to_owned(), + from: from_addr.to_owned().into(), + from_pool: None, value: None, signature: "swap(uint256 x, uint256 y, address a, bytes b)".to_owned(), args: vec![ @@ -224,7 +228,10 @@ pub mod tests { setup: vec![ FunctionCallDefinition { to: "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D".to_owned(), - from: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".to_owned(), + from: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + .to_owned() + .into(), + from_pool: None, value: Some("4096".to_owned()), signature: "swap(uint256 x, uint256 y, address a, bytes b)".to_owned(), args: vec![ @@ -239,7 +246,10 @@ pub mod tests { }, FunctionCallDefinition { to: "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D".to_owned(), - from: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".to_owned(), + from: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + .to_owned() + .into(), + from_pool: None, value: Some("0x1000".to_owned()), signature: "swap(uint256 x, uint256 y, address a, bytes b)".to_owned(), args: vec![ @@ -299,7 +309,7 @@ pub mod tests { match spam[0] { SpamRequest::Tx(ref fncall) => { assert_eq!( - fncall.from, + *fncall.from.as_ref().unwrap(), "0x70997970C51812dc3A010C7d01b50e0d17dc79C8".to_owned() ); assert_eq!(setup.len(), 1); @@ -370,6 +380,7 @@ pub mod tests { None, seed, &get_test_signers(), + Default::default(), ); // this seed can be used to recreate the same test tx(s) let spam_txs = test_gen @@ -407,6 +418,7 @@ pub mod tests { None, seed.to_owned(), &signers, + Default::default(), ); let scenario2 = TestScenario::new( test_file, @@ -415,6 +427,7 @@ pub mod tests { None, seed, &signers, + Default::default(), ); let num_txs = 13; diff --git a/scenarios/spamMe.toml b/scenarios/spamMe.toml index 0c8e0fe..cfba9db 100644 --- a/scenarios/spamMe.toml +++ b/scenarios/spamMe.toml @@ -10,6 +10,7 @@ from = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" [spam.tx] to = "0x90F79bf6EB2c4f870365E785982E1f101E93b906" from = "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" +# from_pool = "beans" signature = "transfer()" [[spam.tx.fuzz]] From c851144e454d1d489cacb34bca0fc8379ac518ec Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Sun, 17 Nov 2024 11:03:05 -0800 Subject: [PATCH 6/9] fund pool accounts w/ user account at spam startup --- crates/cli/src/main.rs | 192 +++++++++++++++++++++------ crates/core/src/agent_controller.rs | 1 + crates/core/src/spammer/blockwise.rs | 6 +- crates/core/src/test_scenario.rs | 6 + scenarios/spamMe.toml | 31 +---- 5 files changed, 165 insertions(+), 71 deletions(-) diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index fbe619b..7a54371 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -2,12 +2,11 @@ mod commands; use alloy::{ network::{AnyNetwork, EthereumWallet, TransactionBuilder}, - node_bindings::WEI_IN_ETHER, primitives::{ utils::{format_ether, parse_ether}, Address, U256, }, - providers::{fillers::WalletFiller, Provider, ProviderBuilder}, + providers::{PendingTransactionConfig, Provider, ProviderBuilder}, rpc::types::TransactionRequest, signers::local::PrivateKeySigner, transports::http::reqwest::Url, @@ -17,7 +16,7 @@ use contender_core::{ agent_controller::{AgentStore, SignerStore}, db::{DbOps, RunTx}, generator::{ - types::{AnyProvider, FunctionCallDefinition, SpamRequest}, + types::{AnyProvider, EthProvider, FunctionCallDefinition, SpamRequest}, RandSeed, }, spammer::{BlockwiseSpammer, LogCallback, NilCallback, TimedSpammer}, @@ -35,26 +34,6 @@ static DB: LazyLock = std::sync::LazyLock::new(|| { SqliteDb::from_file("contender.db").expect("failed to open contender.db") }); -fn get_signers_with_defaults(private_keys: Option>) -> Vec { - if private_keys.is_none() { - println!("No private keys provided. Using default private keys."); - } - let private_keys = private_keys.unwrap_or_default(); - let private_keys = [ - private_keys, - DEFAULT_PRV_KEYS - .into_iter() - .map(|s| s.to_owned()) - .collect::>(), - ] - .concat(); - - private_keys - .into_iter() - .map(|k| PrivateKeySigner::from_str(&k).expect("Invalid private key")) - .collect::>() -} - #[tokio::main] async fn main() -> Result<(), Box> { let args = ContenderCli::parse_args(); @@ -84,7 +63,15 @@ async fn main() -> Result<(), Box> { &testconfig.setup.to_owned().unwrap_or(vec![]), signers.as_slice(), ); - check_balances(&user_signers, min_balance, &rpc_client).await; + let broke_accounts = find_insufficient_balance_addrs( + &user_signers.iter().map(|s| s.address()).collect::>(), + min_balance, + &rpc_client, + ) + .await?; + if !broke_accounts.is_empty() { + panic!("Some accounts do not have sufficient balance"); + } let scenario = TestScenario::new( testconfig.to_owned(), @@ -118,10 +105,12 @@ async fn main() -> Result<(), Box> { let rpc_client = ProviderBuilder::new() .network::() .on_http(url.to_owned()); + let eth_client = ProviderBuilder::new().on_http(url.to_owned()); + let duration = duration.unwrap_or_default(); let min_balance = parse_ether(&min_balance)?; - let signers = get_signers_with_defaults(private_keys); + let user_signers = get_signers_with_defaults(private_keys); let spam = testconfig .spam .as_ref() @@ -129,7 +118,9 @@ async fn main() -> Result<(), Box> { // distill all FunctionCallDefinitions from the spam requests let mut fn_calls = vec![]; + // distill all from_pool arguments from the spam requests let mut from_pools = vec![]; + for s in spam { match s { SpamRequest::Tx(fn_call) => { @@ -149,21 +140,73 @@ async fn main() -> Result<(), Box> { } } - check_private_keys(&fn_calls, signers.as_slice()); - check_balances(signers.as_slice(), min_balance, &rpc_client).await; - let mut agents = AgentStore::new(); let signers_per_block = txs_per_block.unwrap_or(spam.len()) / spam.len(); + let mut all_signers = vec![]; + all_signers.extend_from_slice(&user_signers); + for from_pool in from_pools { if agents.has_agent(from_pool) { continue; } let agent = SignerStore::new_random(signers_per_block); + all_signers.extend_from_slice(&agent.signers); agents.add_agent(from_pool, agent); } + check_private_keys(&fn_calls, &all_signers); + + let insufficient_balance_addrs = find_insufficient_balance_addrs( + &all_signers.iter().map(|s| s.address()).collect::>(), + min_balance, + &rpc_client, + ) + .await?; + + let admin_signer = &user_signers[0]; + let mut pending_fund_txs = vec![]; + let admin_nonce = rpc_client + .get_transaction_count(admin_signer.address()) + .await?; + for (idx, address) in insufficient_balance_addrs.iter().enumerate() { + if !is_balance_sufficient(&admin_signer.address(), min_balance, &rpc_client).await? + { + // panic early if admin account runs out of funds + return Err(format!( + "Admin account {} has insufficient balance to fund this account.", + admin_signer.address() + ) + .into()); + } + + let balance = rpc_client.get_balance(*address).await?; + println!( + "Account {} has insufficient balance. (has {}, needed {})", + address, + format_ether(balance), + format_ether(min_balance) + ); + + let fund_amount = min_balance; // TODO: accept new fund amount from user + pending_fund_txs.push( + fund_account( + &admin_signer, + *address, + fund_amount, + ð_client, + Some(admin_nonce + idx as u64), + ) + .await?, + ); + } + + for tx in pending_fund_txs { + let pending = rpc_client.watch_pending_transaction(tx).await?; + println!("funding tx confirmed ({})", pending.await?); + } + if txs_per_block.is_some() && txs_per_second.is_some() { panic!("Cannot set both --txs-per-block and --txs-per-second"); } @@ -178,7 +221,7 @@ async fn main() -> Result<(), Box> { url, builder_url.map(|url| Url::parse(&url).expect("Invalid builder URL")), rand_seed, - &signers, + &user_signers, agents, ); println!("Blockwise spamming with {} txs per block", txs_per_block); @@ -211,7 +254,7 @@ async fn main() -> Result<(), Box> { url, None, rand_seed, - &signers, + &user_signers, agents, ); let tps = txs_per_second.unwrap_or(10); @@ -285,6 +328,64 @@ const DEFAULT_PRV_KEYS: [&str; 10] = [ "0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6", ]; +fn get_signers_with_defaults(private_keys: Option>) -> Vec { + if private_keys.is_none() { + println!("No private keys provided. Using default private keys."); + } + let private_keys = private_keys.unwrap_or_default(); + let private_keys = [ + private_keys, + DEFAULT_PRV_KEYS + .into_iter() + .map(|s| s.to_owned()) + .collect::>(), + ] + .concat(); + + private_keys + .into_iter() + .map(|k| PrivateKeySigner::from_str(&k).expect("Invalid private key")) + .collect::>() +} + +async fn fund_account( + admin_signer: &PrivateKeySigner, + recipient: Address, + amount: U256, + rpc_client: &EthProvider, + nonce: Option, +) -> Result> { + // TODO + println!( + "funding account {} with user account {}", + recipient, + admin_signer.address() + ); + + let gas_price = rpc_client.get_gas_price().await?; + let nonce = nonce.unwrap_or( + rpc_client + .get_transaction_count(admin_signer.address()) + .await?, + ); + let chain_id = rpc_client.get_chain_id().await?; + let tx_req = TransactionRequest { + from: Some(admin_signer.address()), + to: Some(alloy::primitives::TxKind::Call(recipient)), + value: Some(amount), + gas: Some(21000), + gas_price: Some(gas_price + 4_200_000_000), + nonce: Some(nonce), + chain_id: Some(chain_id), + ..Default::default() + }; + let eth_wallet = EthereumWallet::from(admin_signer.to_owned()); + let tx = tx_req.build(ð_wallet).await?; + let res = rpc_client.send_tx_envelope(tx).await?; + + Ok(res.into_inner()) +} + async fn spam_callback_default( log_txs: bool, rpc_client: Option>, @@ -297,23 +398,28 @@ async fn spam_callback_default( SpamCallbackType::Nil(NilCallback::new()) } -async fn check_balances( - prv_keys: &[PrivateKeySigner], +async fn is_balance_sufficient( + address: &Address, min_balance: U256, rpc_client: &AnyProvider, -) { - for prv_key in prv_keys { - let address = prv_key.address(); - let balance = rpc_client.get_balance(address).await.unwrap(); - if balance < min_balance { - panic!( - "Insufficient balance for address {}. Required={} Actual={}. If needed, use --min-balance to set a lower threshold.", - address, - format_ether(min_balance), - format_ether(balance) - ); +) -> Result> { + let balance = rpc_client.get_balance(*address).await?; + Ok(balance >= min_balance) +} + +/// Returns an error if any of the private keys do not have sufficient balance. +async fn find_insufficient_balance_addrs( + addresses: &[Address], + min_balance: U256, + rpc_client: &AnyProvider, +) -> Result, Box> { + let mut insufficient_balance_addrs = vec![]; + for address in addresses { + if !is_balance_sufficient(address, min_balance, rpc_client).await? { + insufficient_balance_addrs.push(*address); } } + Ok(insufficient_balance_addrs) } fn write_run_txs( diff --git a/crates/core/src/agent_controller.rs b/crates/core/src/agent_controller.rs index fb63b4f..55ec30e 100644 --- a/crates/core/src/agent_controller.rs +++ b/crates/core/src/agent_controller.rs @@ -78,6 +78,7 @@ impl SignerStore { } } + // TODO: add RandSeed to allow for deterministic random generation pub fn new_random(num_signers: usize) -> Self { let signers: Vec = (0..num_signers) .map(|_| PrivateKeySigner::random()) diff --git a/crates/core/src/spammer/blockwise.rs b/crates/core/src/spammer/blockwise.rs index 4d310fa..50e3d6f 100644 --- a/crates/core/src/spammer/blockwise.rs +++ b/crates/core/src/spammer/blockwise.rs @@ -147,11 +147,12 @@ where .get(&fn_sig) .expect("failed to get gas limit") .to_owned(); + let signer = self .scenario .wallet_map .get(&from) - .expect("failed to create signer") + .expect("failed to get signer from scenario wallet_map") .to_owned(); let full_tx = tx_req @@ -204,9 +205,6 @@ where let mut tasks = vec![]; while let Some(block_hash) = stream.next().await { - // TODO: fund accounts if needed - // ... - let block_txs = tx_req_chunks[block_offset].clone(); block_offset += 1; diff --git a/crates/core/src/test_scenario.rs b/crates/core/src/test_scenario.rs index 7217d11..3b6fdd1 100644 --- a/crates/core/src/test_scenario.rs +++ b/crates/core/src/test_scenario.rs @@ -54,6 +54,12 @@ where for (addr, wallet) in wallets { wallet_map.insert(addr, wallet); } + for (name, signers) in agent_store.all_agents() { + println!("adding {} signers to wallet map", name); + for signer in signers.signers.iter() { + wallet_map.insert(signer.address(), EthereumWallet::new(signer.clone())); + } + } Self { config, diff --git a/scenarios/spamMe.toml b/scenarios/spamMe.toml index cfba9db..fe5fed0 100644 --- a/scenarios/spamMe.toml +++ b/scenarios/spamMe.toml @@ -9,8 +9,8 @@ from = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" [spam.tx] to = "0x90F79bf6EB2c4f870365E785982E1f101E93b906" -from = "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" -# from_pool = "beans" +# from = "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" +from_pool = "redpool" signature = "transfer()" [[spam.tx.fuzz]] @@ -23,32 +23,15 @@ max = "1000000000000000" [[spam.bundle.tx]] to = "{SpamMe}" -from = "0x90F79bf6EB2c4f870365E785982E1f101E93b906" -signature = "consumeGas(uint256)" -args = ["510000"] - - -[[spam.bundle.tx]] -to = "{SpamMe}" -from = "0x90F79bf6EB2c4f870365E785982E1f101E93b906" -signature = "tipCoinbase()" -value = "10000000000000000" - -[[spam]] - -[[spam.bundle.tx]] -to = "{SpamMe}" -from = "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720" +# from = "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720" +from_pool = "bluepool" signature = "consumeGas(uint256 gasAmount)" args = ["510000"] - -[[spam.bundle.tx.fuzz]] -param = "gasAmount" -min = "100000" -max = "500000" +fuzz = [{ param = "gasAmount", min = "100000", max = "500000" }] [[spam.bundle.tx]] to = "{SpamMe}" -from = "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720" +from_pool = "bluepool" +# from = "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720" signature = "tipCoinbase()" value = "10000000000000000" \ No newline at end of file From a71aa83730a881ad828e4e3661419596e0c05150 Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Sun, 17 Nov 2024 11:29:52 -0800 Subject: [PATCH 7/9] use RandSeed to generate agent signer keys - persist "randomly generated" accounts across sessions by using the same seed --- crates/cli/src/main.rs | 2 +- crates/core/src/agent_controller.rs | 34 +++++++++++++++++++++++----- crates/core/src/spammer/blockwise.rs | 2 +- crates/core/src/test_scenario.rs | 2 +- 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 7a54371..59ded02 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -151,7 +151,7 @@ async fn main() -> Result<(), Box> { continue; } - let agent = SignerStore::new_random(signers_per_block); + let agent = SignerStore::new_random(signers_per_block, &rand_seed); all_signers.extend_from_slice(&agent.signers); agents.add_agent(from_pool, agent); } diff --git a/crates/core/src/agent_controller.rs b/crates/core/src/agent_controller.rs index 55ec30e..4b79217 100644 --- a/crates/core/src/agent_controller.rs +++ b/crates/core/src/agent_controller.rs @@ -1,6 +1,14 @@ use std::collections::HashMap; -use alloy::{primitives::Address, signers::local::PrivateKeySigner}; +use alloy::{ + primitives::{Address, FixedBytes}, + signers::local::PrivateKeySigner, +}; + +use crate::generator::{ + seeder::{SeedValue, Seeder}, + RandSeed, +}; pub trait SignerRegistry { fn get_signer(&self, idx: Index) -> Option<&PrivateKeySigner>; @@ -36,8 +44,13 @@ impl AgentStore { self.agents.insert(name.as_ref().to_owned(), signers); } - pub fn add_random_agent(&mut self, name: impl AsRef, num_signers: usize) { - let signers = SignerStore::new_random(num_signers); + pub fn add_random_agent( + &mut self, + name: impl AsRef, + num_signers: usize, + rand_seeder: &RandSeed, + ) { + let signers = SignerStore::new_random(num_signers, rand_seeder); self.add_agent(name, signers); } @@ -79,10 +92,19 @@ impl SignerStore { } // TODO: add RandSeed to allow for deterministic random generation - pub fn new_random(num_signers: usize) -> Self { - let signers: Vec = (0..num_signers) - .map(|_| PrivateKeySigner::random()) + pub fn new_random(num_signers: usize, rand_seeder: &RandSeed) -> Self { + let prv_keys = rand_seeder + .seed_values(num_signers, None, None) + .map(|sv| sv.as_bytes().to_vec()) + .collect::>(); + let signers: Vec = prv_keys + .into_iter() + .map(|s| FixedBytes::from_slice(&s)) + .map(|b| PrivateKeySigner::from_bytes(&b).expect("Failed to create random seed signer")) .collect(); + // let signers: Vec = (0..num_signers) + // .map(|_| PrivateKeySigner::random()) + // .collect(); SignerStore { signers } } diff --git a/crates/core/src/spammer/blockwise.rs b/crates/core/src/spammer/blockwise.rs index 50e3d6f..f36bb05 100644 --- a/crates/core/src/spammer/blockwise.rs +++ b/crates/core/src/spammer/blockwise.rs @@ -433,7 +433,7 @@ mod tests { MockDb.into(), anvil.endpoint_url(), None, - seed, + seed.to_owned(), get_test_signers().as_slice(), AgentStore::new(), ); diff --git a/crates/core/src/test_scenario.rs b/crates/core/src/test_scenario.rs index 3b6fdd1..678394c 100644 --- a/crates/core/src/test_scenario.rs +++ b/crates/core/src/test_scenario.rs @@ -417,7 +417,7 @@ pub mod tests { MockDb.into(), anvil.endpoint_url(), None, - seed, + seed.to_owned(), &signers, AgentStore::new(), ) From 5359e7700feb7f25e4316c464f0ae70c3e641486 Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Sun, 17 Nov 2024 11:37:05 -0800 Subject: [PATCH 8/9] cleanup logs --- crates/core/src/generator/mod.rs | 10 ---------- crates/core/src/test_scenario.rs | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/crates/core/src/generator/mod.rs b/crates/core/src/generator/mod.rs index e77af32..f641bcb 100644 --- a/crates/core/src/generator/mod.rs +++ b/crates/core/src/generator/mod.rs @@ -151,8 +151,6 @@ where )); }; - println!("[make_strict_call] using 'from' address: {}", from_address); - Ok(FunctionCallDefinitionStrict { to: funcdef.to.to_owned(), from: from_address, @@ -282,14 +280,6 @@ where let mut req = req.to_owned(); req.args = Some(args); - // DELETE ME ////////////////// - if req.from_pool.is_some() { - println!("i: {}", i); - println!("j: {}", j); - println!("step: {:?}", step); - } - ////////////////////////////// - if fuzz_tx_value.is_some() { req.value = fuzz_tx_value; } diff --git a/crates/core/src/test_scenario.rs b/crates/core/src/test_scenario.rs index 678394c..926e7c9 100644 --- a/crates/core/src/test_scenario.rs +++ b/crates/core/src/test_scenario.rs @@ -55,7 +55,7 @@ where wallet_map.insert(addr, wallet); } for (name, signers) in agent_store.all_agents() { - println!("adding {} signers to wallet map", name); + println!("adding '{}' signers to wallet map", name); for signer in signers.signers.iter() { wallet_map.insert(signer.address(), EthereumWallet::new(signer.clone())); } From cbf64c80743a45b2d8a5ac5cba91091bbe406907 Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Sun, 17 Nov 2024 12:15:21 -0800 Subject: [PATCH 9/9] cleanup comments & clones --- crates/cli/src/main.rs | 3 +-- crates/core/src/agent_controller.rs | 4 ---- crates/core/src/spammer/blockwise.rs | 2 +- scenarios/spamMe.toml | 5 ++--- 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 59ded02..5218603 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -189,7 +189,7 @@ async fn main() -> Result<(), Box> { format_ether(min_balance) ); - let fund_amount = min_balance; // TODO: accept new fund amount from user + let fund_amount = min_balance; pending_fund_txs.push( fund_account( &admin_signer, @@ -355,7 +355,6 @@ async fn fund_account( rpc_client: &EthProvider, nonce: Option, ) -> Result> { - // TODO println!( "funding account {} with user account {}", recipient, diff --git a/crates/core/src/agent_controller.rs b/crates/core/src/agent_controller.rs index 4b79217..82c235e 100644 --- a/crates/core/src/agent_controller.rs +++ b/crates/core/src/agent_controller.rs @@ -91,7 +91,6 @@ impl SignerStore { } } - // TODO: add RandSeed to allow for deterministic random generation pub fn new_random(num_signers: usize, rand_seeder: &RandSeed) -> Self { let prv_keys = rand_seeder .seed_values(num_signers, None, None) @@ -102,9 +101,6 @@ impl SignerStore { .map(|s| FixedBytes::from_slice(&s)) .map(|b| PrivateKeySigner::from_bytes(&b).expect("Failed to create random seed signer")) .collect(); - // let signers: Vec = (0..num_signers) - // .map(|_| PrivateKeySigner::random()) - // .collect(); SignerStore { signers } } diff --git a/crates/core/src/spammer/blockwise.rs b/crates/core/src/spammer/blockwise.rs index f36bb05..50e3d6f 100644 --- a/crates/core/src/spammer/blockwise.rs +++ b/crates/core/src/spammer/blockwise.rs @@ -433,7 +433,7 @@ mod tests { MockDb.into(), anvil.endpoint_url(), None, - seed.to_owned(), + seed, get_test_signers().as_slice(), AgentStore::new(), ); diff --git a/scenarios/spamMe.toml b/scenarios/spamMe.toml index fe5fed0..d35bb49 100644 --- a/scenarios/spamMe.toml +++ b/scenarios/spamMe.toml @@ -1,3 +1,4 @@ +# deploy contract [[create]] bytecode = "0x6080604052348015600f57600080fd5b506105668061001f6000396000f3fe60806040526004361061004a5760003560e01c806369f86ec81461004f5780639402c00414610066578063a329e8de14610086578063c5eeaf17146100a6578063fb0e722b146100ae575b600080fd5b34801561005b57600080fd5b506100646100d9565b005b34801561007257600080fd5b50610064610081366004610218565b6100e4565b34801561009257600080fd5b506100646100a13660046102d1565b610119565b610064610145565b3480156100ba57600080fd5b506100c3610174565b6040516100d0919061030e565b60405180910390f35b5b60325a116100da57565b6000816040516020016100f892919061037b565b60405160208183030381529060405260009081610115919061044f565b5050565b600061012660d98361050e565b905060005b8181101561014057600160008190550161012b565b505050565b60405141903480156108fc02916000818181858888f19350505050158015610171573d6000803e3d6000fd5b50565b6000805461018190610341565b80601f01602080910402602001604051908101604052809291908181526020018280546101ad90610341565b80156101fa5780601f106101cf576101008083540402835291602001916101fa565b820191906000526020600020905b8154815290600101906020018083116101dd57829003601f168201915b505050505081565b634e487b7160e01b600052604160045260246000fd5b60006020828403121561022a57600080fd5b813567ffffffffffffffff81111561024157600080fd5b8201601f8101841361025257600080fd5b803567ffffffffffffffff81111561026c5761026c610202565b604051601f8201601f19908116603f0116810167ffffffffffffffff8111828210171561029b5761029b610202565b6040528181528282016020018610156102b357600080fd5b81602084016020830137600091810160200191909152949350505050565b6000602082840312156102e357600080fd5b5035919050565b60005b838110156103055781810151838201526020016102ed565b50506000910152565b602081526000825180602084015261032d8160408501602087016102ea565b601f01601f19169190910160400192915050565b600181811c9082168061035557607f821691505b60208210810361037557634e487b7160e01b600052602260045260246000fd5b50919050565b600080845461038981610341565b6001821680156103a057600181146103b5576103e5565b60ff19831686528115158202860193506103e5565b87600052602060002060005b838110156103dd578154888201526001909101906020016103c1565b505081860193505b50505083516103f88183602088016102ea565b01949350505050565b601f82111561014057806000526020600020601f840160051c810160208510156104285750805b601f840160051c820191505b818110156104485760008155600101610434565b5050505050565b815167ffffffffffffffff81111561046957610469610202565b61047d816104778454610341565b84610401565b6020601f8211600181146104b157600083156104995750848201515b600019600385901b1c1916600184901b178455610448565b600084815260208120601f198516915b828110156104e157878501518255602094850194600190920191016104c1565b50848210156104ff5786840151600019600387901b60f8161c191681555b50505050600190811b01905550565b60008261052b57634e487b7160e01b600052601260045260246000fd5b50049056fea264697066735822122043ea7522db98264cdc5157a0f2d3f9fc75e28c6078f917dfe9a946bf9b21af7f64736f6c634300081b0033" name = "SpamMe" @@ -9,7 +10,6 @@ from = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" [spam.tx] to = "0x90F79bf6EB2c4f870365E785982E1f101E93b906" -# from = "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" from_pool = "redpool" signature = "transfer()" @@ -18,12 +18,12 @@ value = true min = "10000000000000" max = "1000000000000000" + # spam bundle [[spam]] [[spam.bundle.tx]] to = "{SpamMe}" -# from = "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720" from_pool = "bluepool" signature = "consumeGas(uint256 gasAmount)" args = ["510000"] @@ -32,6 +32,5 @@ fuzz = [{ param = "gasAmount", min = "100000", max = "500000" }] [[spam.bundle.tx]] to = "{SpamMe}" from_pool = "bluepool" -# from = "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720" signature = "tipCoinbase()" value = "10000000000000000" \ No newline at end of file