Skip to content

Commit

Permalink
Merge pull request #45 from flashbots/organize-workspace
Browse files Browse the repository at this point in the history
idiomatic workspace structure
  • Loading branch information
zeroXbrock authored Nov 15, 2024
2 parents ccbac0d + 3677be8 commit bbd7e5a
Show file tree
Hide file tree
Showing 35 changed files with 237 additions and 99 deletions.
32 changes: 16 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

57 changes: 38 additions & 19 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,30 +1,49 @@
[package]
name = "contender"
[workspace]
members = [
"crates/cli/",
"crates/core/",
"crates/sqlite_db/",
"crates/testfile/"
]

resolver = "2"

[workspace.package]
# name = "contender"
version = "0.1.0"
edition = "2021"
authors = ["Brock Smedley"]
rust-version = "1.80"
authors = ["Flashbots"]
license = "MIT OR Apache-2.0"
homepage = "https://github.com/flashbots/contender"
repository = "https://github.com/flashbots/contender"

[workspace.dependencies]
contender_core = { path = "crates/core/" }
contender_sqlite = { path = "crates/sqlite_db/" }
contender_testfile = { path = "crates/testfile/" }

eyre = "0.6.12"
tokio = { version = "1.40.0" }
alloy = { version = "0.3.6" }
serde = "1.0.209"

[lib]
name = "contender_core"
path = "src/lib.rs"
## cli
clap = { version = "4.5.16" }
csv = "1.3.0"

[dependencies]
alloy = { workspace = true, features = ["full", "node-bindings", "rpc-types-mev"] }
eyre = { workspace = true }
## core
rand = "0.8.5"
serde = { workspace = true, features = ["derive"] }
futures = "0.3.30"
async-trait = "0.1.82"
tokio = { workspace = true }
jsonrpsee = { version = "0.24", features = ["http-client", "client-core"] }
jsonrpsee = { version = "0.24" }
alloy-serde = "0.5.4"
serde_json = "1.0.132"

[workspace]
members = ["cli", "sqlite_db", "testfile"]
## sqlite
r2d2_sqlite = "0.25.0"
rusqlite = "0.32.1"
r2d2 = "0.8.10"

[workspace.dependencies]
eyre = "0.6.12"
tokio = { version = "1.40.0" }
alloy = { version = "0.3.6" }
serde = "1.0.209"
## testfile
toml = "0.8.19"
21 changes: 15 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ Contender is a high-performance Ethereum network spammer and testing tool design
To install the Contender CLI, you need to have the [Rust toolchain](https://rustup.rs/) installed on your system. Then install from github:

```bash
cargo install --git https://github.com/flashbots/contender --bin contender_cli
alias contender=contender_cli
cargo install --git https://github.com/flashbots/contender --bin contender
```

## Usage
Expand Down Expand Up @@ -46,7 +45,7 @@ To use Contender as a library in your Rust project, add the crates you need to y
```toml
[dependencies]
...
contender = { git = "https://github.com/flashbots/contender" }
contender_core = { git = "https://github.com/flashbots/contender" }
contender_sqlite = { git = "https://github.com/flashbots/contender" }
contender_testfile = { git = "https://github.com/flashbots/contender" }
# not necessarily required, but recommended:
Expand Down Expand Up @@ -97,7 +96,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {

## Configuration

Contender uses TOML files for test configuration. The key directives are:
Contender uses TOML files for test configuration. Single brackets `[]` indicate the item may only be specified once. Double brackets `[[]]` indicate an array, which allows the directive to be specified multiple times.

The key directives are:

- `[env]`: Defines environment variables that can be used throughout the configuration.

Expand All @@ -107,9 +108,17 @@ Contender uses TOML files for test configuration. The key directives are:

- `[[spam]]`: Describes the transactions to be repeatedly sent during the spam test. These form the core of the network stress test.

- `[[spam.fuzz]]`: (Sub-directive of `spam`) Configures fuzzing parameters for specific fields in spam transactions, allowing for randomized inputs within defined ranges.
- Spam directives can send bundles or single txs.

- `[[spam.bundle.tx]]` defines transactions in a bundle

- `[spam.tx]` defines a single transaction

- Each tx directive can include various fields such as `to`, `from`, `signature`, `args`, and `value` to specify the details of the transactions or contract interactions.

- `[[spam.bundle.tx.fuzz]]` or `[[spam.tx.fuzz]]`: Configures fuzzing parameters for specific fields in spam transactions, allowing for randomized inputs or ETH values within defined ranges.

Each directive can include various fields such as `to`, `from`, `signature`, `args`, and `value` to specify the details of the transactions or contract interactions.
See [scenarios/](./scenarios/) for examples.

## Architecture

Expand Down
15 changes: 0 additions & 15 deletions cli/Cargo.toml

This file was deleted.

File renamed without changes.
File renamed without changes.
19 changes: 19 additions & 0 deletions crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "contender_cli"
version = "0.1.0"
edition = "2021"

[[bin]]
name = "contender"
path = "src/main.rs"

[dependencies]
tokio = { workspace = true, features = ["rt-multi-thread"] }
serde = { workspace = true }
contender_core = { workspace = true }
contender_sqlite = { workspace = true }
contender_testfile = { workspace = true }

clap = { workspace = true, features = ["derive"] }
alloy = { workspace = true, features = ["full", "node-bindings"] }
csv = { workspace = true }
File renamed without changes.
File renamed without changes.
21 changes: 21 additions & 0 deletions crates/core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "contender_core"
version = "0.1.0"
edition = "2021"
authors = ["Brock Smedley"]

[lib]
name = "contender_core"
path = "src/lib.rs"

[dependencies]
alloy = { workspace = true, features = ["full", "node-bindings", "rpc-types-mev"] }
eyre = { workspace = true }
rand = { workspace = true }
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 }
File renamed without changes.
File renamed without changes.
File renamed without changes.
96 changes: 83 additions & 13 deletions src/generator/mod.rs → crates/core/src/generator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ pub mod types;
/// Utility functions used in the generator module.
pub mod util;

const VALUE_KEY: &str = "__tx_value_contender__";

pub trait PlanConfig<K>
where
K: Eq + Hash + Debug + Send + Sync,
Expand All @@ -53,6 +55,37 @@ where
fn get_spam_steps(&self) -> Result<Vec<SpamRequest>>;
}

fn parse_map_key(fuzz: FuzzParam) -> Result<String> {
if fuzz.param.is_none() && fuzz.value.is_none() {
return Err(ContenderError::SpamError(
"fuzz must specify either `param` or `value`",
None,
));
}
if fuzz.param.is_some() && fuzz.value.is_some() {
return Err(ContenderError::SpamError(
"fuzz cannot specify both `param` and `value`; choose one per fuzz directive",
None,
));
}

let key = if let Some(param) = &fuzz.param {
param.to_owned()
} else if let Some(value) = fuzz.value {
if !value {
return Err(ContenderError::SpamError(
"fuzz.value is false, but no param is specified",
None,
));
}
VALUE_KEY.to_owned()
} else {
return Err(ContenderError::SpamError("this should never happen", None));
};

Ok(key)
}

#[async_trait]
pub trait Generator<K, D, T>
where
Expand All @@ -70,15 +103,21 @@ where
&self,
num_values: usize,
fuzz_args: &[FuzzParam],
) -> HashMap<String, Vec<U256>> {
) -> Result<HashMap<String, Vec<U256>>> {
let seed = self.get_fuzz_seeder();
HashMap::<String, Vec<U256>>::from_iter(fuzz_args.iter().map(|fuzz| {
let values: Vec<U256> = seed
.seed_values(num_values, fuzz.min, fuzz.max)
.map(|v| v.as_u256())
.collect();
(fuzz.param.to_owned(), values)
}))
let mut map = HashMap::<String, Vec<U256>>::new();

for fuzz in fuzz_args.iter() {
let key = parse_map_key(fuzz.to_owned())?;
map.insert(
key,
seed.seed_values(num_values, fuzz.min, fuzz.max)
.map(|v| v.as_u256())
.collect(),
);
}

Ok(map)
}

async fn load_txs<F: Send + Sync + Fn(NamedTxRequest) -> CallbackResult>(
Expand Down Expand Up @@ -154,28 +193,33 @@ where
// 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
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;
return Err(ContenderError::SpamError(
"failed to find placeholder value",
Some(e.to_string()),
));
}
find_fuzz(tx);
find_fuzz(tx)?;
Ok(())
};

// populate maps for each step
match step {
SpamRequest::Tx(tx) => {
lookup_tx_placeholders(tx);
lookup_tx_placeholders(tx)?;
}
SpamRequest::Bundle(req) => {
for tx in req.txs.iter() {
lookup_tx_placeholders(tx);
lookup_tx_placeholders(tx)?;
}
}
};
Expand All @@ -187,8 +231,12 @@ where
// returns a callback handle and the processed tx request
let process_tx = |req| {
let args = get_fuzzed_args(req, &canonical_fuzz_map, i);
let fuzz_tx_value = get_fuzzed_tx_value(req, &canonical_fuzz_map, i);
let mut req = req.to_owned();
req.args = Some(args);
if fuzz_tx_value.is_some() {
req.value = fuzz_tx_value;
}
let tx = NamedTxRequest::new(
templater.template_function_call(&req, &placeholder_map)?,
None,
Expand Down Expand Up @@ -268,3 +316,25 @@ fn get_fuzzed_args(
})
.collect()
}

fn get_fuzzed_tx_value(
tx: &FunctionCallDefinition,
fuzz_map: &HashMap<String, Vec<U256>>,
fuzz_idx: usize,
) -> Option<String> {
if let Some(fuzz) = &tx.fuzz {
for fuzz_param in fuzz {
if let Some(value) = fuzz_param.value {
if value {
return Some(
fuzz_map
.get(VALUE_KEY)
.expect("value fuzzer was not initialized")[fuzz_idx]
.to_string(),
);
}
}
}
}
None
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit bbd7e5a

Please sign in to comment.