Campaigns let you run multiple existing scenarios in parallel, optionally in sequential stages, to mimic real network mixes or replay traffic profiles.
name = "composite-example"
description = "Simple, Stress, and Reverts traffic mix"
[setup]
scenarios = [
"scenario:simple.toml",
"scenario:stress.toml",
"scenario:reverts.toml",
]
[spam]
mode = "tps" # or "tpb"
rate = 20 # default rate if a stage omits one (TPS or TPB via mode)
duration = 600 # default duration (seconds if tps, blocks if tpb)
seed = 42 # optional; falls back to CLI --seed or seed file
[[spam.stage]]
name = "steady"
duration_secs = 600
[[spam.stage.mix]]
scenario = "scenario:simple.toml"
share_pct = 95.0
[[spam.stage.mix]]
scenario = "scenario:stress.toml"
share_pct = 4.8
[[spam.stage.mix]]
scenario = "scenario:reverts.toml"
share_pct = 0.2mode:tps(per-second) ortpb(per-block). Stages can override rate/duration; otherwise they inherit from[spam].rate: rate per mode (TPS ifmode="tps", TPB ifmode="tpb"). Set once at[spam]or per stage.durationat[spam]is a default per-stage duration, not a total campaign time. Each stage runs for its own duration (seconds iftps, blocks iftpb), then the next stage starts.share_pct: scenario weight inside a stage; shares are normalized and rounded, and the last entry absorbs rounding drift to preserve the target rate.[setup].scenarios: run once, in order, before spamming. Uses the standardsetuplogic for each referenced scenario file.
- Stages run sequentially. Each stage inherits
mode/rate/durationfrom[spam]unless the stage overrides them. - Each stage performs its own setup/init (funding, deploy/config for builtins, scenario init), then starts its spammers at the resolved rate/mix.
- Within a stage, we spin up one spammer per
mixentry at the computed per-scenario rate; they share a DB handle and run id. - The next stage starts only after the previous one completes its stage duration (seconds for
tps, blocks fortpb). Campaign duration is the sum of stage durations. - Rates and shares are recomputed per stage, so you can ramp traffic up/down or change blends across time slices.
- You must provide either
[[spam.stage]]entries or a shorthand[spam]+[[spam.mix]]withspam.duration. - If
spam.stageis present,spam.mixis rejected (prefer explicit stages). - Each stage needs a duration (seconds for
tps, blocks fortpb); if omitted, the[spam].durationdefault is used. - Mix entries must be non-empty and share percentages must sum to a positive number (they are normalized automatically).
If you omit [[spam.stage]] and instead set spam.duration plus [[spam.mix]], Contender builds a single implicit stage named steady:
[spam]
mode = "tps"
rate = 20
duration = 600
seed = 42
[[spam.mix]]
scenario = "scenario:simple.toml"
share_pct = 95.0
[[spam.mix]]
scenario = "scenario:stress.toml"
share_pct = 4.8
[[spam.mix]]
scenario = "scenario:reverts.toml"
share_pct = 0.2This is equivalent to writing a single explicit [[spam.stage]] named steady with the same rate/duration and mix.
See campaigns/staged-example.toml for a two-stage campaign that warms up at a lower TPS, then ramps to a steady-state mix.
Preferred: new subcommand.
contender campaign ./campaigns/composite.toml \
-r $RPC_URL -p $PKEY --pending-timeout 12 --rpc-batch-size 0Flags mirror spam where they make sense:
- Connection/auth:
--rpc-url,--priv-key/-p,--builder-url, JWT/auth flags viaScenarioSendTxsoptions. - Funding/runtime:
--pending-timeout,--accounts-per-agent,--rpc-batch-size,--ignore-receipts,--optimistic-nonces,--timeout,--report. - Setup controls:
--skip-setup
- Per-run:
contender report [-i <last_run_id> --preceding-runs N] - Campaign summary:
contender report --campaign [<campaign_id>](alias:--campaign-id)- If
<campaign_id>is omitted, the latest campaign is used. - Generates per-run HTML for all runs in the campaign.
- Writes
campaign-<campaign_id>.htmlandcampaign-<campaign_id>.jsonunder~/.local/state/contender/reports/with links, aggregate metrics, and per-stage/per-scenario rollups. - If you pass
--reporttocontender campaign ..., contender will also generate a report for the run-id range at the end of the campaign. - If transaction logs are incomplete for any run (e.g., tracing/storage gaps), the campaign report will use stored run metadata for totals/durations and will display a notice; error counts may be under-reported in that case.
- If
- When a stage has multiple
[[spam.stage.mix]]entries, do not combine it with--override-senders; using a single sender across mixes is rejected because it would cause nonce conflicts.
- Setup: load each scenario in
[setup].scenariosand run its setup once (reuse existing setup command). - Stages: for each
[[spam.stage]]- Resolve stage mode/rate/duration from stage or
[spam]defaults. - Compute per-scenario rates:
scenario_rate = round(total_rate * share_pct/100), last entry fixed to hit the total. - Spawn one spammer per scenario in the stage, sharing a common
run_idand database handle. - Stage ends after
durationseconds/blocks.
- Resolve stage mode/rate/duration from stage or
- Reporting: if
--reportis set, generate a report for all campaign runs after the final stage.
Run metadata now records campaign_name and stage_name alongside the scenario label (campaign:<name>::<stage>), so reports and DB exports can distinguish composite runs.