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
557 changes: 294 additions & 263 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ eyre = "0.6.12"
tokio = { version = "1.40.0" }
tokio-util = "0.7"
alloy = { version = "0.12.5" }
alloy-signer = { version = "0.14.0", features = ["wallet"] }
serde = "1.0.209"
rand = "0.8.5"
tracing = "0.1.41"
tracing-subscriber = { version = "0.3" }
prometheus = "0.14"

## cli
Expand Down
2 changes: 2 additions & 0 deletions crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ rand.workspace = true
regex = "1.11.1"
serde_json = { workspace = true }
termcolor = "1.4.1"
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
webbrowser = { workspace = true }

[dev-dependencies]
Expand Down
113 changes: 113 additions & 0 deletions crates/cli/src/commands/admin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
use crate::util::data_dir;
use alloy::hex;
use clap::Subcommand;
use contender_core::{
agent_controller::SignerStore, db::DbOps, error::ContenderError, generator::RandSeed,
};
use tracing::info;

#[derive(Debug, Subcommand)]
pub enum AdminCommand {
#[command(
name = "accounts",
about = "Print addresses generated by RandSeed for a given from_pool"
)]
Accounts {
/// From pool to generate accounts for
#[arg(short = 'f', long)]
from_pool: String,

/// Number of signers to generate
#[arg(short = 'n', long, default_value = "10")]
num_signers: usize,
},

#[command(name = "latest-run-id", about = "Print the max run id in the DB")]
LatestRunId,

#[command(name = "seed", about = "Print the contents of ~/.contender/seed")]
Seed,
}

/// Reads and validates the seed file
fn read_seed_file() -> Result<Vec<u8>, ContenderError> {
let data_dir = data_dir()
.map_err(|e| ContenderError::GenericError("Failed to get data dir", e.to_string()))?;
let seed_path = format!("{data_dir}/seed");
let seed_hex = std::fs::read_to_string(&seed_path).map_err(|e| {
ContenderError::AdminError("Failed to read seed file", format!("at {seed_path}: {e}"))
})?;
let decoded = hex::decode(seed_hex.trim()).map_err(|_| {
ContenderError::AdminError("Invalid hex data in seed file", format!("at {seed_path}"))
})?;
if decoded.is_empty() {
return Err(ContenderError::AdminError(
"Empty seed file",
format!("at {seed_path}"),
));
}
Ok(decoded)
}

/// Prompts for confirmation before displaying sensitive information
fn confirm_sensitive_operation(_operation: &str) -> Result<(), ContenderError> {
println!("WARNING: This command will display sensitive information.");
println!("This information should not be shared or exposed in CI environments.");
println!("Press Enter to continue or Ctrl+C to cancel...");
let mut input = String::new();
std::io::stdin()
.read_line(&mut input)
.map_err(|e| ContenderError::AdminError("Failed to read input", format!("{e}")))?;
Ok(())
}

/// Handles the accounts subcommand
fn handle_accounts(
from_pool: String,
num_signers: usize,
) -> Result<(), Box<dyn std::error::Error>> {
let seed_bytes = read_seed_file()?;
let seed = RandSeed::seed_from_bytes(&seed_bytes);
print_accounts_for_pool(&from_pool, num_signers, &seed)?;
Ok(())
}

/// Prints accounts for a specific pool
fn print_accounts_for_pool(
pool: &str,
num_signers: usize,
seed: &RandSeed,
) -> Result<(), ContenderError> {
info!("Generating addresses for pool: {}", pool);
let agent = SignerStore::new(num_signers, seed, pool);
for (i, address) in agent.all_addresses().iter().enumerate() {
info!("Signer {}: {}", i, address);
}
Ok(())
}

/// Handles the seed subcommand
fn handle_seed() -> Result<(), Box<dyn std::error::Error>> {
confirm_sensitive_operation("displaying seed value")?;
let seed_bytes = read_seed_file()?;
println!("{}", hex::encode(seed_bytes));
Ok(())
}

pub fn handle_admin_command(
command: AdminCommand,
db: impl DbOps,
) -> Result<(), Box<dyn std::error::Error>> {
match command {
AdminCommand::Accounts {
from_pool,
num_signers,
} => handle_accounts(from_pool, num_signers),
AdminCommand::LatestRunId => {
let num_runs = db.num_runs()?;
println!("Latest run ID: {num_runs}");
Ok(())
}
AdminCommand::Seed => handle_seed(),
}
}
7 changes: 7 additions & 0 deletions crates/cli/src/commands/contender_subcommand.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use clap::Subcommand;
use std::path::PathBuf;

use super::admin::AdminCommand;
use super::common::AuthCliArgs;
use super::setup::SetupCliArgs;
use super::spam::SpamCliArgs;
Expand All @@ -9,6 +10,12 @@ use crate::util::TxTypeCli;

#[derive(Debug, Subcommand)]
pub enum ContenderSubcommand {
#[command(name = "admin", about = "Admin commands")]
Admin {
#[command(subcommand)]
command: AdminCommand,
},

#[command(name = "db", about = "Database management commands")]
Db {
#[command(subcommand)]
Expand Down
1 change: 1 addition & 0 deletions crates/cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod admin;
pub mod common;
mod contender_subcommand;
pub mod db;
Expand Down
3 changes: 1 addition & 2 deletions crates/cli/src/commands/report/gen_html.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use super::command::SpamRunMetrics;
use crate::{commands::report::chart::ReportChartId, util::report_dir};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

use super::command::SpamRunMetrics;

pub struct ReportMetadata {
pub scenario_name: String,
pub start_run_id: u64,
Expand Down
2 changes: 1 addition & 1 deletion crates/cli/src/commands/setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ pub async fn setup(
continue;
}

let agent = SignerStore::new_random(1, &seed, from_pool);
let agent = SignerStore::new(1, &seed, from_pool);
agents.add_agent(from_pool, agent);
}

Expand Down
13 changes: 12 additions & 1 deletion crates/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod util;

use alloy::hex;
use commands::{
admin::handle_admin_command,
common::{ScenarioSendTxsCliArgs, SendSpamCliArgs},
db::{drop_db, export_db, import_db, reset_db},
ContenderCli, ContenderSubcommand, DbCommand, RunCommandArgs, SetupCliArgs, SetupCommandArgs,
Expand All @@ -27,11 +28,17 @@ static LATENCY_HIST: OnceCell<prometheus::HistogramVec> = OnceCell::const_new();

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let subscriber = tracing_subscriber::FmtSubscriber::new();
tracing::subscriber::set_global_default(subscriber)?;

let args = ContenderCli::parse_args();
if DB.table_exists("run_txs")? {
// check version and exit if DB version is incompatible
let quit_early = DB.version() != DB_VERSION
&& !matches!(&args.command, ContenderSubcommand::Db { command: _ });
&& !matches!(
&args.command,
ContenderSubcommand::Db { command: _ } | ContenderSubcommand::Admin { command: _ }
);
if quit_early {
let recommendation = format!(
"To backup your data, run `contender db export`.\n{}",
Expand Down Expand Up @@ -260,6 +267,10 @@ Remote DB version = {}, contender expected version {}.
)
.await?
}

ContenderSubcommand::Admin { command } => {
handle_admin_command(command, db)?;
}
}
Ok(())
}
14 changes: 8 additions & 6 deletions crates/cli/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -328,8 +328,7 @@ pub async fn fund_account(
let tx = tx_req.build(&eth_wallet).await?;

println!(
"funding account {} with user account {}. tx: {}",
recipient,
"funding account {recipient} with user account {}. tx: {}",
sender.address(),
tx.tx_hash().encode_hex()
);
Expand Down Expand Up @@ -404,10 +403,13 @@ pub fn prompt_cli(msg: impl AsRef<str>) -> String {
/// Returns the path to the data directory.
/// The directory is created if it does not exist.
pub fn data_dir() -> Result<String, Box<dyn std::error::Error>> {
let dir = format!(
"{}/.contender",
std::env::var("HOME").map_err(|_| "Failed to get $HOME from environment")?
);
let home_dir = if cfg!(windows) {
std::env::var("USERPROFILE").map_err(|_| "Failed to get USERPROFILE from environment")?
} else {
std::env::var("HOME").map_err(|_| "Failed to get HOME from environment")?
};

let dir = format!("{home_dir}/.contender");

// ensure directory exists
std::fs::create_dir_all(&dir)?;
Expand Down
8 changes: 4 additions & 4 deletions crates/core/src/agent_controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,21 +52,21 @@ impl AgentStore {
if self.has_agent(agent) {
continue;
}
self.add_random_agent(agent, signers_per_agent, seed);
self.add_new_agent(agent, signers_per_agent, seed);
}
}

pub fn add_agent(&mut self, name: impl AsRef<str>, signers: SignerStore) {
self.agents.insert(name.as_ref().to_owned(), signers);
}

pub fn add_random_agent(
pub fn add_new_agent(
&mut self,
name: impl AsRef<str>,
num_signers: usize,
rand_seeder: &RandSeed,
) {
let signers = SignerStore::new_random(num_signers, rand_seeder, name.as_ref());
let signers = SignerStore::new(num_signers, rand_seeder, name.as_ref());
self.add_agent(name, signers);
}

Expand Down Expand Up @@ -112,7 +112,7 @@ where
}

impl SignerStore {
pub fn new_random(num_signers: usize, rand_seeder: &RandSeed, acct_seed: &str) -> Self {
pub fn new(num_signers: usize, rand_seeder: &RandSeed, acct_seed: &str) -> Self {
// add numerical value of acct_seed to given seed
let new_seed = rand_seeder.as_u256() + U256::from_be_slice(acct_seed.as_bytes());
let rand_seeder = RandSeed::seed_from_u256(new_seed);
Expand Down
9 changes: 5 additions & 4 deletions crates/core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub enum ContenderError {
SpamError(&'static str, Option<String>),
SetupError(&'static str, Option<String>),
GenericError(&'static str, String),
AdminError(&'static str, String),
}

impl ContenderError {
Expand All @@ -16,11 +17,12 @@ impl ContenderError {
impl std::fmt::Display for ContenderError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
ContenderError::SpamError(msg, _) => write!(f, "SpamError: {msg}"),
ContenderError::AdminError(msg, e) => write!(f, "AdminError: {msg} - {e}"),
ContenderError::DbError(msg, _) => write!(f, "DatabaseError: {msg}"),
ContenderError::GenericError(msg, e) => {
write!(f, "{} {}", msg, e.to_owned())
}
ContenderError::SpamError(msg, _) => write!(f, "SpamError: {msg}"),
ContenderError::SetupError(msg, _) => write!(f, "SetupError: {msg}"),
}
}
Expand All @@ -39,9 +41,8 @@ impl std::fmt::Debug for ContenderError {
ContenderError::SetupError(msg, e) => {
write!(f, "SetupError: {} {}", msg, err(e.to_owned()))
}
ContenderError::GenericError(msg, e) => {
write!(f, "{} {}", msg, e.to_owned())
}
ContenderError::GenericError(msg, e) => write!(f, "{msg} {e}"),
ContenderError::AdminError(msg, e) => write!(f, "AdminError: {msg} - {e}"),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/core/src/generator/seeder/rand_seed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ impl RandSeed {
Self::seed_from_u256(n)
}

/// Interprets seed as a U256.
pub fn seed_from_u256(seed: U256) -> Self {
Self {
seed: seed.to_be_bytes(),
Expand Down
10 changes: 2 additions & 8 deletions crates/core/src/spammer/blockwise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,8 @@ mod tests {
let periods = 3u64;
let tx_type = alloy::consensus::TxType::Legacy;
let num_signers = (txs_per_period / periods) as usize;
agents.add_agent(
"pool1",
SignerStore::new_random(num_signers, &seed, "eeeeeeee"),
);
agents.add_agent(
"pool2",
SignerStore::new_random(num_signers, &seed, "11111111"),
);
agents.add_agent("pool1", SignerStore::new(num_signers, &seed, "eeeeeeee"));
agents.add_agent("pool2", SignerStore::new(num_signers, &seed, "11111111"));

let user_signers = get_test_signers();
let mut nonce = provider
Expand Down
7 changes: 2 additions & 5 deletions crates/core/src/test_scenario.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,8 @@ where
pub gas_limits: HashMap<FixedBytes<32>, u64>,
pub msg_handle: Arc<TxActorHandle>,
pub tx_type: TxType,
/// Fixed percentage provided by user to add to gas price.
pub gas_price_percent_add: u64,
/// Timeout for pending transactions in seconds, provided by user.
pub pending_tx_timeout_secs: u64,
/// Execution context for the test scenario; things about the target chain that affect the txs we send.
pub ctx: ExecutionContext,
pub auth_provider: Option<Arc<dyn AdvanceChain + Send + Sync + 'static>>,
prometheus: PrometheusCollector,
Expand Down Expand Up @@ -1552,8 +1549,8 @@ pub mod tests {
&seed,
);

let admin1_signers = SignerStore::new_random(1, &seed, "admin1");
let admin2_signers = SignerStore::new_random(1, &seed, "admin2");
let admin1_signers = SignerStore::new(1, &seed, "admin1");
let admin2_signers = SignerStore::new(1, &seed, "admin2");
agents.add_agent("admin1", admin1_signers);
agents.add_agent("admin2", admin2_signers);

Expand Down
Loading