Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7f149d3
add server crate
zeroXbrock Mar 19, 2026
10c9d4e
organize code
zeroXbrock Mar 19, 2026
f7ee486
consolidate info for RPC/serde, add rpc methods...
zeroXbrock Mar 19, 2026
425858b
(demo mode) initialize contender when adding a session
zeroXbrock Mar 19, 2026
03d7bd5
accept scenario file as base64 str
zeroXbrock Mar 20, 2026
30d3d72
export lib contents of contender_cli for server to use
zeroXbrock Mar 20, 2026
9161beb
support default scenarios in add_session
zeroXbrock Mar 20, 2026
ed0ad69
initialize scenarios in bg, return from RPC calls quickly
zeroXbrock Mar 20, 2026
8c12a2b
deserialize eth values w/ serde (as well)
zeroXbrock Mar 20, 2026
68e228d
subscribe to session logs over websocket
zeroXbrock Mar 20, 2026
d4317b4
serve logs over SSE as well as WS
zeroXbrock Mar 20, 2026
88eea28
cleanup main; consolidate code, set server addrs w/ env
zeroXbrock Mar 20, 2026
22b6dfa
simplify ContenderSession constructor
zeroXbrock Mar 20, 2026
6cc7919
(wip) rename confusing var, add dummy spam method, make rpc_server mod
zeroXbrock Mar 20, 2026
c472ccf
break up contents of rpc mod into sub-modules
zeroXbrock Mar 20, 2026
2e1e38b
fix logs, spam for real
zeroXbrock Mar 21, 2026
ba0f781
(bugfix: orchestrator) shutdown scenario after spamming
zeroXbrock Mar 23, 2026
4a8a232
add "Spamming" status; enforce 1 spam run per session
zeroXbrock Mar 23, 2026
0d84914
session: wait for receipt collection to finish...
zeroXbrock Mar 23, 2026
9d52f02
cleanup rpc error messages
zeroXbrock Mar 23, 2026
92fa00b
shutdown log streams when remove_session is called
zeroXbrock Mar 23, 2026
915c462
add 'stop' method to terminate a session's spammer
zeroXbrock Mar 24, 2026
b391cb1
cancel spammer when calling remove
zeroXbrock Mar 24, 2026
d3cabc6
reduce streamed log verbosity
zeroXbrock Mar 24, 2026
ebabd21
add basic web interface for contender API
zeroXbrock Mar 24, 2026
4472035
(web) 2-column ui, auto log subscription & aggregate status polling
zeroXbrock Mar 24, 2026
46c23f3
move stop button to session cabinet
zeroXbrock Mar 24, 2026
71b58c1
cleanup spam status readout
zeroXbrock Mar 24, 2026
d0c8e42
disable spam button when target session is already spamming
zeroXbrock Mar 24, 2026
a3629b6
confirm before removing session
zeroXbrock Mar 24, 2026
1f2d31d
use random nums as session ids, stop array indexing
zeroXbrock Mar 24, 2026
9011fac
use dropdown menu for session selection
zeroXbrock Mar 24, 2026
22c95e7
fix sessionId type in spam button
zeroXbrock Mar 25, 2026
df81514
disable spam button if status is anything but Ready
zeroXbrock Mar 25, 2026
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
333 changes: 287 additions & 46 deletions Cargo.lock

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ members = [
"crates/core/",
"crates/engine_provider",
"crates/report",
"crates/server",
"crates/sqlite_db/",
"crates/testfile/",
]
Expand All @@ -21,12 +22,14 @@ homepage = "https://github.com/flashbots/contender"
repository = "https://github.com/flashbots/contender"

[workspace.dependencies]
contender_cli = { path = "crates/cli" }
contender_core = { path = "crates/core/" }
contender_sqlite = { path = "crates/sqlite_db/" }
contender_testfile = { path = "crates/testfile/" }
contender_bundle_provider = { path = "crates/bundle_provider/" }
contender_engine_provider = { path = "crates/engine_provider/" }
contender_report = { path = "crates/report/" }
contender_server = { path = "crates/server/" }

tokio = { version = "1.40.0" }
tokio-tungstenite = { version = "0.26", features = ["native-tls"] }
Expand All @@ -49,6 +52,11 @@ csv = "1.3.0"
miette = { version = "7.6.0" }
url = "2.5.7"
uuid = "1.19.0"
base64 = "0.22"

## server
axum = "0.8"
tokio-stream = "0.1"

## core
futures = "0.3.30"
Expand All @@ -57,6 +65,7 @@ jsonrpsee = { version = "0.24" }
alloy-serde = "0.5.4"
serde_json = "1.0.132"
tower = "0.5.2"
tower-http = { version = "0.6", features = ["cors"] }
alloy-rpc-types-engine = { version = "1.0.22", default-features = false }
alloy-json-rpc = { version = "1.0.22", default-features = false }
alloy-chains = { version = "0.2.5", default-features = false }
Expand Down
17 changes: 15 additions & 2 deletions crates/cli/src/commands/campaign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ use crate::commands::{
common::{ScenarioSendTxsCliArgs, SendTxsCliArgsInner},
SpamCliArgs,
};
use crate::default_scenarios::fill_block::SpamRate;
use crate::default_scenarios::{BuiltinOptions, BuiltinScenarioCli};
use crate::error::CliError;
use crate::util::load_testconfig;
use crate::util::{load_seedfile, parse_duration};
use crate::BuiltinScenarioCli;
use alloy::primitives::{keccak256, U256};
use clap::Args;
use contender_core::db::DbOps;
use contender_core::error::RuntimeParamErrorKind;
use contender_core::generator::RandSeed;
use contender_testfile::{CampaignConfig, CampaignMode, ResolvedMixEntry, ResolvedStage};
use std::path::Path;
use std::time::Duration;
Expand Down Expand Up @@ -445,10 +447,21 @@ async fn prepare_scenario(
skip_setup,
);

let rand_seed = RandSeed::seed_from_str(&scenario_seed);
let spam_scenario = if let Some(builtin_cli) = parse_builtin_reference(&mix.scenario) {
let provider = args.eth_json_rpc_args.new_rpc_provider()?;
let builtin = builtin_cli
.to_builtin_scenario(&provider, &spam_cli_args, ctx.data_dir)
.to_builtin_scenario(
&provider,
BuiltinOptions {
accounts_per_agent: ctx.args.eth_json_rpc_args.accounts_per_agent,
seed: rand_seed,
spam_rate: Some(match ctx.campaign.spam.mode {
CampaignMode::Tps => SpamRate::TxsPerSecond(mix.rate),
CampaignMode::Tpb => SpamRate::TxsPerBlock(mix.rate),
}),
},
)
.await?;
SpamScenario::Builtin(builtin)
} else {
Expand Down
12 changes: 12 additions & 0 deletions crates/cli/src/commands/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use super::EngineArgs;
use crate::commands::error::ArgsError;
use crate::commands::SpamScenario;
use crate::default_scenarios::fill_block::SpamRate;
use crate::error::CliError;
use crate::util::get_signers_with_defaults;
use alloy::consensus::TxType;
Expand Down Expand Up @@ -336,6 +337,17 @@ Requires --priv-key to be set for each 'from' address in the given testfile.",
pub run_forever: bool,
}

impl SendSpamCliArgs {
pub fn spam_rate(&self) -> Result<SpamRate, ArgsError> {
match (self.txs_per_second, self.txs_per_block) {
(Some(_), Some(_)) => Err(ArgsError::SpamRateNotFound),
(None, None) => Err(ArgsError::SpamRateNotFound),
(Some(tps), None) => Ok(SpamRate::TxsPerSecond(tps)),
(None, Some(tpb)) => Ok(SpamRate::TxsPerBlock(tpb)),
}
}
}

#[derive(Copy, Debug, Clone, clap::ValueEnum)]
pub enum TxTypeCli {
/// Legacy transaction (type `0x0`)
Expand Down
20 changes: 19 additions & 1 deletion crates/cli/src/commands/spam.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{
error::ArgsError,
Result,
},
default_scenarios::BuiltinScenario,
default_scenarios::{BuiltinOptions, BuiltinScenario},
error::CliError,
util::{
bold, check_private_keys, fund_accounts, load_seedfile, load_testconfig, parse_duration,
Expand Down Expand Up @@ -151,6 +151,24 @@ pub struct SpamCliArgs {
)]
pub flashblocks_ws_url: Option<Url>,
}

impl SpamCliArgs {
pub fn builtin_options(&self, data_dir: &PathBuf) -> Result<BuiltinOptions> {
let seed = self
.eth_json_rpc_args
.rpc_args
.seed
.clone()
.unwrap_or(load_seedfile(data_dir)?);
let seed = RandSeed::seed_from_str(&seed);
Ok(BuiltinOptions {
accounts_per_agent: self.eth_json_rpc_args.rpc_args.accounts_per_agent,
seed,
spam_rate: Some(self.spam_args.spam_rate()?),
})
}
}

#[derive(Clone)]
pub enum SpamScenario {
Testfile(String),
Expand Down
3 changes: 2 additions & 1 deletion crates/cli/src/default_scenarios/blobs.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use clap::Parser;
use contender_core::generator::{types::SpamRequest, FunctionCallDefinition};
use contender_testfile::TestConfig;
use serde::{Deserialize, Serialize};

use crate::default_scenarios::builtin::ToTestConfig;

#[derive(Parser, Clone, Debug)]
#[derive(Parser, Clone, Debug, Deserialize, Serialize)]
/// Send blob transactions. Note: the tx type will always be overridden to eip4844.
pub struct BlobsCliArgs {
#[arg(
Expand Down
36 changes: 16 additions & 20 deletions crates/cli/src/default_scenarios/builtin.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
use std::path::Path;

use super::fill_block::{fill_block, FillBlockArgs, FillBlockCliArgs};
use crate::{
commands::SpamCliArgs,
default_scenarios::{
blobs::BlobsCliArgs,
custom_contract::{CustomContractArgs, CustomContractCliArgs},
erc20::{Erc20Args, Erc20CliArgs},
eth_functions::{opcodes::EthereumOpcode, EthFunctionsArgs, EthFunctionsCliArgs},
fill_block::SpamRate,
revert::RevertCliArgs,
setcode::{SetCodeArgs, SetCodeCliArgs, SetCodeSubCommand},
storage::{StorageStressArgs, StorageStressCliArgs},
Expand All @@ -16,7 +14,7 @@ use crate::{
uni_v2::{UniV2Args, UniV2CliArgs},
},
error::CliError,
util::{bold, load_seedfile},
util::bold,
};
use alloy::primitives::U256;
use clap::Subcommand;
Expand All @@ -26,10 +24,12 @@ use contender_core::{
generator::{constants::setcode_placeholder, types::AnyProvider, RandSeed},
};
use contender_testfile::TestConfig;
use serde::{Deserialize, Serialize};
use strum::IntoEnumIterator;
use tracing::warn;

#[derive(Clone, Debug, Subcommand)]
#[derive(Clone, Debug, Subcommand, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum BuiltinScenarioCli {
/// Send EIP-4844 blob transactions.
Blobs(BlobsCliArgs),
Expand Down Expand Up @@ -76,12 +76,18 @@ pub trait ToTestConfig {
fn to_testconfig(&self) -> TestConfig;
}

#[derive(Default)]
pub struct BuiltinOptions {
pub accounts_per_agent: Option<u64>,
pub seed: RandSeed,
pub spam_rate: Option<SpamRate>,
}

impl BuiltinScenarioCli {
pub async fn to_builtin_scenario(
&self,
provider: &AnyProvider,
spam_args: &SpamCliArgs,
data_dir: &Path,
options: BuiltinOptions,
) -> Result<BuiltinScenario, CliError> {
match self.to_owned() {
BuiltinScenarioCli::Blobs(args) => Ok(BuiltinScenario::Blobs(args)),
Expand All @@ -91,21 +97,11 @@ impl BuiltinScenarioCli {
)),

BuiltinScenarioCli::Erc20(args) => {
let seed = spam_args
.eth_json_rpc_args
.rpc_args
.seed
.to_owned()
.unwrap_or(load_seedfile(data_dir)?);
let seed = RandSeed::seed_from_str(&seed);
let mut agents = AgentStore::new();
agents.init(
&["spammers"],
spam_args
.eth_json_rpc_args
.rpc_args
.accounts_per_agent_or(10) as usize,
&seed,
options.accounts_per_agent.unwrap_or(10) as usize,
&options.seed,
);
let spammers = agents
.get_agent("spammers")
Expand All @@ -118,7 +114,7 @@ impl BuiltinScenarioCli {
}

BuiltinScenarioCli::FillBlock(args) => {
fill_block(provider, &spam_args.spam_args, &args).await
fill_block(provider, options.spam_rate.unwrap_or_default(), &args).await
}

BuiltinScenarioCli::EthFunctions(args) => {
Expand Down
3 changes: 2 additions & 1 deletion crates/cli/src/default_scenarios/custom_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ use contender_core::generator::types::SpamRequest;
use contender_core::generator::util::encode_calldata;
use contender_core::generator::{CompiledContract, CreateDefinition, FunctionCallDefinition};
use contender_testfile::TestConfig;
use serde::{Deserialize, Serialize};
use std::process::Command;
use thiserror::Error;
use tracing::debug;

const ARTIFACTS_PATH: &str = "/tmp/contender-contracts";

#[derive(Clone, Debug, clap::Parser)]
#[derive(Clone, Debug, clap::Parser, Deserialize, Serialize)]
pub struct CustomContractCliArgs {
/// Path to smart contract source. Format: <path/to/contract.sol>:<ContractName>
contract_path: std::path::PathBuf,
Expand Down
8 changes: 6 additions & 2 deletions crates/cli/src/default_scenarios/erc20.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
use alloy::primitives::{Address, U256};
use contender_core::generator::{
types::SpamRequest, util::parse_value, CreateDefinition, FunctionCallDefinition, FuzzParam,
types::SpamRequest, util::parse_value, util::deserialize_value, CreateDefinition,
FunctionCallDefinition, FuzzParam,
};
use contender_testfile::TestConfig;
use serde::{Deserialize, Serialize};
use std::str::FromStr;

use crate::default_scenarios::{builtin::ToTestConfig, contracts::test_token};

pub static DEFAULT_TOKENS_SENT: &str = "0.00001 ether";
pub static DEFAULT_TOKENS_FUNDED: &str = "1000000 ether";

#[derive(Clone, Debug, clap::Parser)]
#[derive(Clone, Debug, clap::Parser, Deserialize, Serialize)]
pub struct Erc20CliArgs {
#[arg(
short,
Expand All @@ -19,6 +21,7 @@ pub struct Erc20CliArgs {
default_value = DEFAULT_TOKENS_SENT,
value_parser = parse_value,
)]
#[serde(deserialize_with = "deserialize_value")]
pub send_amount: U256,

#[arg(
Expand All @@ -28,6 +31,7 @@ pub struct Erc20CliArgs {
default_value = DEFAULT_TOKENS_FUNDED,
value_parser = parse_value,
)]
#[serde(deserialize_with = "deserialize_value")]
pub fund_amount: U256,

#[arg(
Expand Down
3 changes: 2 additions & 1 deletion crates/cli/src/default_scenarios/eth_functions/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ use crate::default_scenarios::{
use clap::Parser;
use contender_core::generator::CreateDefinition;
use contender_testfile::TestConfig;
use serde::{Deserialize, Serialize};

#[derive(Parser, Clone, Debug)]
#[derive(Parser, Clone, Debug, Deserialize, Serialize)]
pub struct EthFunctionsCliArgs {
#[arg(
short,
Expand Down
5 changes: 4 additions & 1 deletion crates/cli/src/default_scenarios/eth_functions/opcodes.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use crate::default_scenarios::contracts::SPAM_ME;
use clap::ValueEnum;
use contender_core::generator::{types::SpamRequest, FunctionCallDefinition};
use serde::{Deserialize, Serialize};
use strum::EnumIter;

#[derive(ValueEnum, Clone, Debug, strum::Display, EnumIter, PartialEq, Eq)]
#[derive(
ValueEnum, Clone, Debug, strum::Display, EnumIter, PartialEq, Eq, Deserialize, Serialize,
)]
pub enum EthereumOpcode {
Stop,
Add,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use crate::default_scenarios::contracts::SPAM_ME;
use clap::ValueEnum;
use contender_core::generator::{types::SpamRequest, FunctionCallDefinition};
use serde::{Deserialize, Serialize};
use strum::EnumIter;

#[derive(ValueEnum, Clone, Debug, strum::Display, EnumIter, PartialEq, Eq)]
#[derive(
ValueEnum, Clone, Debug, strum::Display, EnumIter, PartialEq, Eq, Deserialize, Serialize,
)]
// TODO: add missing precompiles to SpamMe contract & here.
pub enum EthereumPrecompile {
#[clap(aliases = ["sha256"])]
Expand Down
Loading
Loading