Skip to content

Conversation

@arpitkarnatak
Copy link
Contributor

@arpitkarnatak arpitkarnatak commented May 21, 2025

Motivation

This PR is step 1 for composite scenario execution, as suggested in #145

Solution

Adding a new compose command, similar to docker compose

Composite scenario execution, setup and spam multiple contracts from a single YML file.

Usage: contender compose [OPTIONS]

Options:
  -f, --filename <FILENAME>
          File path for composite scenario file
          
          [default: ./contender-compose.yml]

A contender-compose.yml file looks like

setup:
  simpler:
    testfile: ./scenarios/simpler.toml
    min_balance: 12.1

  uniV2:
    testfile: ./scenarios/uniV2.toml
    rpc_url: http://localhost:8545 # Optional
    min_balance: "11"
    env:
      - Key1=Valu1
      - Key2=Valu2
    private_keys: # Optional (these Private keys are from Anvil)
      - 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
      - 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
    tx_type: eip1559  # Optional

  uniV3:
    testfile: ./scenarios/simple.toml
    rpc_url:  http://localhost:8545 # Optional
    min_balance: 0.01  # Optional
    tx_type: eip1559  # Optional

Currently this only supports setup command, it parses a contender-compose.yml file and deploys multiple contracts from a single contender compose command.

The next step would be to include the spam from this file. Before implementing that, I want to know if all these spams are going to be run parallely and fall under a single run ID, or be separate runs one after the other.

Also, I feel it would be better to have all the logic for the compose file to be a separate crate of its own, similar to testfile. Let me know your thoughts, and any feedback on how to improve/move forward with this.

PR Checklist

  • Added Tests
  • Added Documentation
  • Breaking changes

@arpitkarnatak arpitkarnatak requested a review from zeroXbrock as a code owner May 21, 2025 17:50
@zeroXbrock
Copy link
Member

@arpitkarnatak this is sick, thanks for the PR!

The next step would be to include the spam from this file. Before implementing that, I want to know if all these spams are going to be run parallely and fall under a single run ID, or be separate runs one after the other.

They should all run in parallel with different run IDs. Something I'd like to add in the "compose config" is the ability to define dependencies, so that we may execute them in sequence, but I think the default should be to run them all together at once.

Also, I feel it would be better to have all the logic for the compose file to be a separate crate of its own, similar to testfile. Let me know your thoughts, and any feedback on how to improve/move forward with this.

Yeah I agree, this should live in its own crate.

@arpitkarnatak
Copy link
Contributor Author

@zeroXbrock Thanks for the feedback.

So I'll have to do the following next

  1. Implement spam and it's relevant parameters for running multiple scenarios parallely.
  2. Add a depends_on for setup command so if we have to run certain scenarios sequentially.
  3. Move the logic for compose file into a new crate of its own contender_composefile.
  4. More detailed documentation on contender-compose.yml file specs.

I have one little doubt,

Currently I'm using the pre-existing setup() and spam() functions from inside the compose file. Are we supposed to use that only or take the logic pieces from it into the new compose() function. Pros for using existing functions: Less lines of code, no code repetition , even docker does the same. Cons would be, jumbled logs for all the different scenarios, since each scenario/set of scenarios will be a separate task)

@arpitkarnatak
Copy link
Contributor Author

arpitkarnatak commented May 26, 2025

@zeroXbrock So I've done the following

  1. Added spam into compose file, with the stages as mentioned in the requirement. The setups run parallely, and so do the spams for each stage.
  2. contender_composefile is a separate crate now.
  3. There's some documentation for compose file now, highlighting how it maps to the spam and setup commands

Some things that I've had confusions with.

  1. I wanna hold off on implementing depends_on for now, since those dependencies exists in the scenario.toml file itself (as per issue scenario file dependencies #253) so I guess we might wanna be done with scenario file dependencies #253 before this.
  2. There are a few parameters seed, engine_params in SpamCommandArgs and SetupCommandArgs which I don't understand what they mean or wasn't able to make sense of (as in where they come from), so those are hardcoded.

I want to do the following before merge

  1. Write some more tests for the new crate composefile.
  2. Adding additional documentation on each and every parameter of the contender-compose.yml file.

But apart from that, it's ready for review, do let me know what needs fixing/clarity

@arpitkarnatak arpitkarnatak changed the title Composite Scenarios Execution (Part 1) - Running setup for multiple contracts from a single contender-compose.yml file, Composite Scenarios Execution - Setup parallely, spam parallely (and in stages) May 26, 2025
@zeroXbrock
Copy link
Member

@arpitkarnatak thanks for the PR! I should have time later in the week to review

@zeroXbrock
Copy link
Member

sorry for such a long delay @arpitkarnatak -- I've been meaning to get to this but we haven't needed this feature for the tests we're currently doing with contender, so this has fallen by the wayside. I'll take another look and hopefully get this merged soon!

@arpitkarnatak
Copy link
Contributor Author

Absolutely understandable @zeroXbrock please take your time

I was just meaning to get an initial review for the time being to get a sense of whether this is in-line with what you had in mind for this feature, and I could polish it further if it was. Thanks and would be looking forward to the review and comments

Copy link
Member

@zeroXbrock zeroXbrock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you for being so patient @arpitkarnatak. I think we can get this merged soon!

Just had a few comments. Let me know if I can clarify anything!

Comment on lines 13 to 15
private_keys: # Optional (these Private keys are from Anvil)
- 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
- 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private keys should only be specified as a cli param -- we don't want people accidentally saving their keys into files that they might share with the world

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, I think it would be best that way, will change it accordingly

tracing = { workspace = true }
tracing-subscriber = { workspace = true }
webbrowser = { workspace = true }
yaml-rust2 = "0.10.2"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know I specified yaml when I wrote the issue but I wonder if toml would be better -- wdyt?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Toml can be nice, since we're using it for the other configurations too. For this case, I would prefer YAML solely because of the readability and syntax familiarity with Docker compose syntax.

private_keys: setup_object.private_keys,
env: setup_object.env,
min_balance: setup_object.min_balance,
tx_type: if setup_object.tx_type == *"eip1559" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should use enum matching here instead of string comparison

},
loops: spam_object.loops,

// TODO: Need more knowledge, hence hardcoding them for now
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these should inherit their values from the same CLI args that we use to run spam.
They're just CLI args, but you'll have to check the spam impl to see how the defaults are filled in.

pub timeout_secs: u64,
pub env: Option<Vec<(String, String)>>,
pub loops: Option<u64>,
// TODO: These params are hardcoded for now, I'll need some more info in these before implementing them
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see above comment about CLI args inheritance

use std::error::Error;
use yaml_rust2::Yaml;

pub fn get_tx_type(yaml_object: &LinkedHashMap<Yaml, Yaml>) -> Result<String, Box<dyn Error>> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should delete this and use enum matching instead

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This utility is for handling strings from Compose file. These functions are to extract strings from YAML

In the compose file crate, we have utility to read YAML files, and convert each setup and spam scenario into a JSON, and then from the command we convert that JSON to SetupCommandArgs and SpamCommandArgs (in crates/cli/src/commands/setup and crates/cli/src/commands/spam)

Copy link
Member

@zeroXbrock zeroXbrock Sep 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like there's a simpler approach. There's too much boilerplate here (and in the other utils / yaml parsing functions in general), and the tests are essentially just testing the YAML parser.

serde_yaml exists, which would parse all of this automatically, but it's not maintained anymore and probably isn't compatible with the modern version of serde that we already use. I'd prefer to swap out YAML for TOML, so we can use serde.

@zeroXbrock
Copy link
Member

hey @arpitkarnatak just wondering if you're still working on this

@arpitkarnatak
Copy link
Contributor Author

@zeroXbrock Yes I am on this, I'll try to get this done over the weekend with the changes you asked

@arpitkarnatak
Copy link
Contributor Author

Hello @zeroXbrock I added the following features

  1. Private keys via CLI arg
  2. Adding Engine Params from compose file

The relevant snippets are in the last 4 commits

Copy link
Member

@zeroXbrock zeroXbrock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice work so far @arpitkarnatak -- thanks so much! The system works well, but I left a few suggestions for the code in the comments below.

My biggest concern is that the manual YAML parsing adds too much boilerplate code to maintain efficiently. I think TOML is the way to go -- it'll allow us to use serde (which we already use) and reduce the amount of code needed by a huge amount.

Also, the version of contender your branch is based off is pretty old -- you may want to merge main into your branch soon, but it's up to you whether you do that before or after making the changes I suggested. I imagine it will be easier to do afterwards, since there should be less code to change once the duplication is removed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure this should be in the project root -- maybe it would fit better under scenarios/? Or we could have a new directory called composite-scenarios/ or something like that.
We should also use valid values, so that users with a simple node (e.g. anvil w/ default config) running on localhost:8545 can run the compose file without error. Any advanced examples we want to show off should go in a document under docs/.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we should also enforce a .compose.toml filename restriction, so users don't accidentally try to run core scenario files as compose files

@@ -0,0 +1,48 @@
setup:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be cool if we didn't have to manually call setup. Feels like manually specifying rpc_urls for setup could get confusing in a large file (i.e. the user tries to spam on an alternate chain but forgets to run setup on that chain).

Instead, we could check if the scenario TOML file has any setup directives, then run TestScenario::estimate_setup_cost to find the required min_balance, then check the spam directives in the compose file to see which chains need to run which setup and run it automatically at the start.


futures::future::join_all(setup_tasks).await;

info!("================================================================================================= Done Composite run for setup =================================================================================================");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's remove the equal signs from this -- simple logs are fine

Comment on lines +5 to +43
pub struct SpamCommandArgsJsonAdapter {
pub scenario: String,
pub rpc_url: String,
pub builder_url: Option<String>,
pub txs_per_block: Option<u64>,
pub txs_per_second: Option<u64>,
pub duration: u64,
pub private_keys: Option<Vec<String>>,
pub min_balance: String,
pub tx_type: String,
pub timeout_secs: u64,
pub env: Option<Vec<(String, String)>>,
pub loops: Option<u64>,
pub seed: Option<String>,

pub call_fcu: bool,
pub use_op: bool,
pub auth_rpc_url: Option<String>,
pub jwt_secret: Option<String>,
pub disable_reporting: bool,
pub gas_price_percent_add: Option<u64>,
}

// In `contender_cli` we can retrieve the SetupCommandArgs struct from this using SetupCommandArgs::from<SetupCommandArgsJsonAdapter>()
#[derive(Deserialize, Debug, Clone)]
pub struct SetupCommandArgsJsonAdapter {
pub testfile: String,
pub rpc_url: String,
pub private_keys: Option<Vec<String>>,
pub min_balance: String,
pub tx_type: String,
pub env: Option<Vec<(String, String)>>,
pub call_fcu: bool,
pub use_op: bool,
pub auth_rpc_url: Option<String>,
pub jwt_secret: Option<String>,
pub seed: Option<String>,
// pub engine_params: EngineParams,
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These include lots of duplicate variables from structs that already exist in contender_cli.
Since compose is a cli command, I think it would make more sense to just move this whole crate into a module in crates/cli/src/commands/.

Then, instead of duplicating the variables manually for this struct, just compose these structs using the existing structs and #[serde(flatten)]

use std::error::Error;
use yaml_rust2::Yaml;

pub fn get_tx_type(yaml_object: &LinkedHashMap<Yaml, Yaml>) -> Result<String, Box<dyn Error>> {
Copy link
Member

@zeroXbrock zeroXbrock Sep 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like there's a simpler approach. There's too much boilerplate here (and in the other utils / yaml parsing functions in general), and the tests are essentially just testing the YAML parser.

serde_yaml exists, which would parse all of this automatically, but it's not maintained anymore and probably isn't compatible with the modern version of serde that we already use. I'd prefer to swap out YAML for TOML, so we can use serde.

@zeroXbrock
Copy link
Member

hey @arpitkarnatak are you still working on this?

@arpitkarnatak
Copy link
Contributor Author

arpitkarnatak commented Oct 22, 2025

Hey @zeroXbrock I can't give more time to this in near future at least given my current commitments. Sorry :(

@zeroXbrock
Copy link
Member

Hey @zeroXbrock I can't give more time to this in near future at least given my current commitments. Sorry :(

no worries, thanks for letting me know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants