Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 137 additions & 0 deletions crates/cli/src/commands/spamd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
use super::{SpamCampaignContext, SpamCommandArgs};
use crate::CliError;
use crate::{
commands::{self},
util::data_dir,
};
use contender_core::db::DbOps;
use std::{
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
time::Duration,
};
use tracing::{debug, error, info, warn};

/// Runs spam in a loop, potentially executing multiple spam runs.
///
/// If `limit_loops` is `None`, it will run indefinitely.
///
/// If `limit_loops` is `Some(n)`, it will run `n` times.
///
/// If `gen_report` is `true`, it will generate a report at the end.
pub async fn spamd(
db: &(impl DbOps + Clone + Send + Sync + 'static),
args: SpamCommandArgs,
gen_report: bool,
limit_loops: Option<u64>,
) -> Result<(), CliError> {
let is_done = Arc::new(AtomicBool::new(false));
let mut scenario = args.init_scenario(db).await?;

// collects run IDs from the spam command
let mut run_ids = vec![];

// if CTRL-C signal is received, set `is_done` to true
{
let is_done = is_done.clone();
tokio::task::spawn(async move {
tokio::signal::ctrl_c()
.await
.expect("Failed to listen for CTRL-C");
info!(
"CTRL-C received. Spam daemon will shut down as soon as current batch finishes..."
);
is_done.store(true, Ordering::SeqCst);
});
}

// runs spam command in a loop
let mut i = 0;
// this holds a Some value only when a timeout has been started.
let mut timeout_start = None;
loop {
let mut do_finish = false;
if let Some(loops) = &limit_loops {
if i >= *loops {
do_finish = true;
}
i += 1;
}
if is_done.load(Ordering::SeqCst) {
do_finish = true;
}
if do_finish {
debug!("spamd loop finished");
break;
}

let db = db.clone();
let spam_res =
commands::spam(&db, &args, &mut scenario, SpamCampaignContext::default()).await;
let wait_time = Duration::from_secs(3);

if let Err(e) = spam_res {
error!("spam run failed: {e:?}");

if timeout_start.is_none() {
let start_time = std::time::Instant::now();
timeout_start = Some(start_time);
warn!("retrying in {} seconds...", wait_time.as_secs());
tokio::time::sleep(wait_time).await;
continue;
}

if let Some(timeout_start) = timeout_start {
if std::time::Instant::now().duration_since(timeout_start)
> args.spam_args.spam_timeout
{
warn!("timeout reached, quitting spam loop...");
scenario.ctx.cancel_token.cancel();
break;
} else {
tokio::time::sleep(wait_time).await;
}
} else {
scenario.ctx.cancel_token.cancel();
break;
}
} else {
timeout_start = None;
let run_id = spam_res.expect("spam");
if let Some(run_id) = run_id {
run_ids.push(run_id);
}
}
}

// generate a report if requested; in closure for tokio::select to handle CTRL-C
let run_report = || async move {
if gen_report {
if run_ids.is_empty() {
warn!("No runs found, exiting.");
return Ok::<_, CliError>(());
}
let first_run_id = run_ids.iter().min().expect("no run IDs found");
let last_run_id = *run_ids.iter().max().expect("no run IDs found");
contender_report::command::report(
Some(last_run_id),
last_run_id - first_run_id,
db,
&data_dir()?,
)
.await?;
}
Ok(())
};

tokio::select! {
_ = run_report() => {},
_ = tokio::signal::ctrl_c() => {
info!("CTRL-C received, cancelling report...");
}
}

Ok(())
}
7 changes: 1 addition & 6 deletions crates/cli/src/default_scenarios/blobs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,6 @@ fn blob_txs(blob_data: impl AsRef<str>, recipient: Option<String>) -> Vec<SpamRe

impl ToTestConfig for BlobsCliArgs {
fn to_testconfig(&self) -> contender_testfile::TestConfig {
TestConfig {
env: None,
create: None,
setup: None,
spam: Some(blob_txs(&self.blob_data, self.recipient.to_owned())),
}
TestConfig::new().with_spam(blob_txs(&self.blob_data, self.recipient.to_owned()))
}
}
2 changes: 1 addition & 1 deletion crates/cli/src/default_scenarios/custom_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ impl NameAndArgs {
impl ToTestConfig for CustomContractArgs {
fn to_testconfig(&self) -> contender_testfile::TestConfig {
TestConfig::new()
.with_create(vec![CreateDefinition::new(&self.contract)])
.with_create(vec![CreateDefinition::new(&self.contract.clone().into())])
.with_setup(self.setup.to_owned())
.with_spam(self.spam.iter().map(SpamRequest::new_tx).collect())
}
Expand Down
75 changes: 37 additions & 38 deletions crates/cli/src/default_scenarios/erc20.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,48 +69,47 @@ impl ToTestConfig for Erc20Args {
.with_args(&[recipient.to_string(), self.fund_amount.to_string()])
})
.collect();
let spam_steps = vec![SpamRequest::new_tx(&{
let mut func_def = FunctionCallDefinition::new(token.template_name())
.with_from_pool("spammers") // Senders from limited pool
.with_signature("transfer(address guy, uint256 wad)")
.with_args(&[
// Use token_recipient if provided (via --recipient flag),
// otherwise this is a placeholder for fuzzing
self.token_recipient
.as_ref()
.map(|addr| addr.to_string())
.unwrap_or_else(|| {
"0x0000000000000000000000000000000000000000".to_string()
}),
self.send_amount.to_string(),
])
.with_gas_limit(55000);

TestConfig {
env: None,
create: Some(vec![CreateDefinition {
contract: token.to_owned(),
// Only add fuzzing if token_recipient is NOT provided
if self.token_recipient.is_none() {
func_def = func_def.with_fuzz(&[FuzzParam {
param: Some("guy".to_string()),
value: None,
min: Some(U256::from(1)),
max: Some(
U256::from_str("0x0000000000ffffffffffffffffffffffffffffffff").unwrap(),
),
}]);
}

func_def
})];

TestConfig::new()
.with_create(vec![CreateDefinition {
contract: token.to_owned().into(),
signature: None,
args: None,
from: None,
from_pool: Some("admin".to_owned()),
}]),
setup: Some(setup_steps),
spam: Some(vec![SpamRequest::new_tx(&{
let mut func_def = FunctionCallDefinition::new(token.template_name())
.with_from_pool("spammers") // Senders from limited pool
.with_signature("transfer(address guy, uint256 wad)")
.with_args(&[
// Use token_recipient if provided (via --recipient flag),
// otherwise this is a placeholder for fuzzing
self.token_recipient
.as_ref()
.map(|addr| addr.to_string())
.unwrap_or_else(|| {
"0x0000000000000000000000000000000000000000".to_string()
}),
self.send_amount.to_string(),
])
.with_gas_limit(55000);

// Only add fuzzing if token_recipient is NOT provided
if self.token_recipient.is_none() {
func_def = func_def.with_fuzz(&[FuzzParam {
param: Some("guy".to_string()),
value: None,
min: Some(U256::from(1)),
max: Some(
U256::from_str("0x0000000000ffffffffffffffffffffffffffffffff").unwrap(),
),
}]);
}

func_def
})]),
}
}])
.with_setup(setup_steps)
.with_spam(spam_steps)
}
}
11 changes: 4 additions & 7 deletions crates/cli/src/default_scenarios/eth_functions/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,17 +73,14 @@ impl ToTestConfig for EthFunctionsArgs {
let opcode_txs = opcode_txs(opcodes, *num_iterations);
let txs = [precompile_txs, opcode_txs].concat();

TestConfig {
env: None,
create: Some(vec![CreateDefinition {
TestConfig::new()
.with_create(vec![CreateDefinition {
contract: contracts::SPAM_ME.into(),
signature: None,
args: None,
from: None,
from_pool: Some("admin".to_owned()),
}]),
setup: None,
spam: Some(txs),
}
}])
.with_spam(txs)
}
}
11 changes: 4 additions & 7 deletions crates/cli/src/default_scenarios/fill_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,17 +81,14 @@ impl ToTestConfig for FillBlockArgs {
})
.collect::<Vec<_>>();

TestConfig {
env: None,
create: Some(vec![CreateDefinition {
TestConfig::new()
.with_create(vec![CreateDefinition {
contract: contracts::SPAM_ME.into(),
signature: None,
args: None,
from: None,
from_pool: Some("admin".to_owned()),
}]),
setup: None,
spam: Some(spam_txs),
}
}])
.with_spam(spam_txs)
}
}
13 changes: 5 additions & 8 deletions crates/cli/src/default_scenarios/revert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,19 @@ impl Default for RevertCliArgs {

impl ToTestConfig for RevertCliArgs {
fn to_testconfig(&self) -> contender_testfile::TestConfig {
TestConfig {
env: None,
create: Some(vec![CreateDefinition {
TestConfig::new()
.with_create(vec![CreateDefinition {
contract: SPAM_ME_6.into(),
signature: None,
args: None,
from: None,
from_pool: Some("admin".to_owned()),
}]),
setup: None,
spam: Some(vec![SpamRequest::new_tx(
}])
.with_spam(vec![SpamRequest::new_tx(
&FunctionCallDefinition::new(SPAM_ME_6.template_name())
.with_signature("consumeGasAndRevert(uint256 gas)")
.with_args(&[self.gas_use.to_string()])
.with_gas_limit(self.gas_use + 35000),
)]),
}
)])
}
}
11 changes: 4 additions & 7 deletions crates/cli/src/default_scenarios/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,14 @@ impl ToTestConfig for StorageStressArgs {
.map(SpamRequest::Tx)
.collect::<Vec<_>>();

TestConfig {
env: None,
create: Some(vec![CreateDefinition {
TestConfig::new()
.with_create(vec![CreateDefinition {
contract: contracts::SPAM_ME.into(),
signature: None,
args: None,
from: None,
from_pool: Some("admin".to_owned()),
}]),
setup: None,
spam: Some(txs),
}
}])
.with_spam(txs)
}
}
11 changes: 4 additions & 7 deletions crates/cli/src/default_scenarios/stress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,17 +206,14 @@ impl ToTestConfig for StressCliArgs {
.flat_map(|config| config.spam.unwrap_or_default())
.collect::<Vec<_>>();

TestConfig {
env: None,
create: Some(vec![CreateDefinition {
TestConfig::new()
.with_create(vec![CreateDefinition {
contract: contracts::SPAM_ME.into(),
signature: None,
args: None,
from: None,
from_pool: Some("admin".to_owned()),
}]),
setup: None,
spam: Some(txs),
}
}])
.with_spam(txs)
}
}
7 changes: 1 addition & 6 deletions crates/cli/src/default_scenarios/transfers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,6 @@ impl ToTestConfig for TransferStressArgs {
.map(Box::new)
.map(SpamRequest::Tx)
.collect::<Vec<_>>();
contender_testfile::TestConfig {
env: None,
create: None,
setup: None,
spam: Some(txs),
}
contender_testfile::TestConfig::new().with_spam(txs)
}
}
Loading
Loading