Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- clean up html report UI, support batched `eth_sendRawTransaction` latency metrics ([#455](https://github.com/flashbots/contender/pull/455))
- added `--scenario-label` flag to deploy and spam the same scenario under different labels ([#456](https://github.com/flashbots/contender/pull/456))
- fix: generate report when `--gen-report` is passed to `spam` ([#457](https://github.com/flashbots/contender/pull/457))
- control `SETUP_CONCURRENCY_LIMIT` with env var ([#461](https://github.com/flashbots/contender/pull/461))

## [0.8.1](https://github.com/flashbots/contender/releases/tag/v0.8.1) - 2026-02-09

Expand Down
3 changes: 2 additions & 1 deletion crates/core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

- added `scenario_label` support to apply contract name labels at the DB boundary
- added `scenario_label` support to apply contract name labels at the DB boundary ([#456](https://github.com/flashbots/contender/pull/456))
- control `SETUP_CONCURRENCY_LIMIT` with env var ([#461](https://github.com/flashbots/contender/pull/461))

### Breaking changes

Expand Down
36 changes: 30 additions & 6 deletions crates/core/src/test_scenario.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ use contender_bundle_provider::{
use contender_engine_provider::ControlChain;
use futures::{Stream, StreamExt};
use serde_json::json;
use std::sync::LazyLock;
use std::{
collections::{BTreeMap, HashMap},
pin::Pin,
Expand All @@ -58,8 +59,16 @@ use tracing::{debug, info, trace, warn};

pub use alloy::transports::http::reqwest::Url;

/// Reads the SETUP_CONCURRENCY_LIMIT from the environment, defaults to 25 if not set or invalid.
fn read_setup_concurrency_limit() -> usize {
std::env::var("SETUP_CONCURRENCY_LIMIT")
.ok()
.and_then(|val| val.parse::<usize>().ok())
.unwrap_or(25)
}

/// Maximum concurrent setup tasks (limits open HTTP connections).
const SETUP_CONCURRENCY_LIMIT: usize = 25;
pub static SETUP_CONCURRENCY_LIMIT: LazyLock<usize> = LazyLock::new(read_setup_concurrency_limit);

#[derive(Clone)]
pub struct PrometheusCollector {
Expand Down Expand Up @@ -681,7 +690,7 @@ where
let genesis_hash = self.ctx.genesis_hash;
let blob_base_fee = get_blob_fee_maybe(&self.rpc_client, self.tx_type).await;

let semaphore = Arc::new(tokio::sync::Semaphore::new(SETUP_CONCURRENCY_LIMIT));
let semaphore = Arc::new(tokio::sync::Semaphore::new(*SETUP_CONCURRENCY_LIMIT));

let (_txs, updated_nonces) = self
.load_txs(PlanType::Setup(|tx_req| {
Expand Down Expand Up @@ -1917,7 +1926,7 @@ pub mod tests {
};
use crate::spammer::util::test::get_test_signers;
use crate::spammer::{BlockwiseSpammer, NilCallback, Spammer};
use crate::test_scenario::TestScenario;
use crate::test_scenario::{read_setup_concurrency_limit, TestScenario};
use crate::Error;
use crate::Result;
use alloy::consensus::constants::GWEI_TO_WEI;
Expand Down Expand Up @@ -2705,7 +2714,7 @@ pub mod tests {
/// Reproduces the erc20-like pattern where many steps share one sender.
#[tokio::test]
async fn run_setup_sequential_nonces_single_sender() {
let num_steps = SETUP_CONCURRENCY_LIMIT * 3;
let num_steps = *SETUP_CONCURRENCY_LIMIT * 3;
let (_anvil, mut scenario) = setup_scenario_with_anvil(
SequentialNonceMockConfig { num_steps },
AgentSpec::default(),
Expand Down Expand Up @@ -2735,8 +2744,9 @@ pub mod tests {
let num_setup_steps = HighConcurrencyMockConfig.get_setup_steps().unwrap().len();
let expected_tasks = num_setup_steps * num_setup_accounts;
assert!(
expected_tasks > SETUP_CONCURRENCY_LIMIT,
"test should generate more tasks ({expected_tasks}) than the concurrency limit ({SETUP_CONCURRENCY_LIMIT})"
expected_tasks > *SETUP_CONCURRENCY_LIMIT,
"test should generate more tasks ({expected_tasks}) than the concurrency limit ({})",
*SETUP_CONCURRENCY_LIMIT
);

let result = scenario.run_setup().await;
Expand All @@ -2746,4 +2756,18 @@ pub mod tests {
result.err()
);
}

#[test]
fn env_controls_setup_concurrency_limit() {
std::env::set_var("SETUP_CONCURRENCY_LIMIT", "5");
let new_limit = read_setup_concurrency_limit();
assert_eq!(new_limit, 5, "SETUP_CONCURRENCY_LIMIT should be set to 5");

std::env::remove_var("SETUP_CONCURRENCY_LIMIT");
let default_limit = read_setup_concurrency_limit();
assert_eq!(
default_limit, 25,
"SETUP_CONCURRENCY_LIMIT should default to 25 when env var is unset"
);
}
}
17 changes: 17 additions & 0 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,20 @@ args = []
```bash
contender spam ./example.toml --tps 10 -e testAddr=0x0000000000000000000000000000000000000013
```

## setup concurrency

**Setup steps** can be executed in two ways: `contender setup` with a file-based scenario, or `contender spam` with a builtin scenario.

By default, setup steps will send up to **25 transactions**, and wait for them to land onchain before sending more.

To change this amount, set `SETUP_CONCURRENCY_LIMIT` in your environment:

```bash
# only send 10 txs at a time
# run the erc20 scenario with 50 accounts per agent (-a)
SETUP_CONCURRENCY_LIMIT=10 \
contender spam --tps 50 -a 50 erc20
```

The builtin `erc20` scenario creates a setup step for each account, so in this case we'd have 50 setup txs to send, and you'd see 5 batches of 10 txs landing onchain, one after another.
Loading