We’ve added a handful of features since introducing contender:
- scenario definition upgrades
- generate EOAs at scale using
from_pool
declarations - send bundles in your scenarios
- spam for longer using
spamd
- spam with the authenticated
engine_
API- with optimism support
- contender reports
There’s a lot of info packed behind those bullets, so I’ll break each one down.
recap
For the unacquainted, contender is a transaction spammer that can run a highly customizable variety of onchain scenarios. Users can deploy contracts, define transactions to set up contracts, and define transactions to spam those contracts, all in one file. That file is plugged into contender to execute the scenario on any target chain.
scenario definition upgrades
scaled EOAs for spamming
It’s not really realistic for one account to send 500 transactions in a single block. That’s why we added the from_pool
flag to spam tx definitions; it specifies a pool of accounts that the spam step should pull from when signing a transaction. The number of EOAs generated for each pool corresponds to --tps
(txs/sec) or --tpb
(txs/block) in the spam commands, so all scenarios automatically span as many accounts as possible, which gives the appearance of many users interacting with the chain.
Here’s an example of a spam
definition that uses from_pool
to assign a pool of spammers:
[[spam]]
[spam.tx]
to = "{SpamMe3}"
from_pool = "spammers"
signature = "consumeGas(string memory method, uint256 iterations)"
args = ["sstore", "8000"]
You can assign different names for different groups, if you want. This can serve to represent the behavior of different groups of people doing different things onchain.
send bundles
Contender now supports sending bundles over the eth_sendBundle
RPC method. To declare a bundle in your scenario, use the [[spam.bundle.tx]]
directive:
# spam bundle
[[spam]]
[[spam.bundle.tx]]
to = "{SpamMe}"
from_pool = "bluepool"
signature = "consumeGas(uint256 gasAmount)"
...
[[spam.bundle.tx]]
to = "{SpamMe}"
from_pool = "bluepool"
signature = "tipCoinbase()"
...
Each spam.bundle.tx
represents a single transaction in a bundle, which is represented by [[spam]]
.
You will also need to provide a builder RPC URL (which hosts the eth_sendBundle
endpoint). To do this, pass the --builder-url
(-b
) flag to spam
or spamd
.
Here’s an example that targets a local builder setup (provided by builder-playground and rbuilder).
contender spam ./scenarios/spamBundles.toml http://localhost:8545 \
-p $PRK \
--tps 50 \
-b http://localhost:8645
See here for an example of a scenario that features bundles.
spamd
spamd
is short for “spam daemon.” It has all the features of the spam
command, but it runs multiple spam runs. By default, it runs indefinitely, but this can be limited using --time-limit
(-l
). This makes it easier to execute long-term spam runs without worrying about whether you’ll need to change the execution parameters.
The most likely outcome from running
spamd
with no time limit is that your accounts run out of funds
Each spam run will wait for its transactions to be included, or it will time out. The timeout period can also be configured using --timeout
(or -w
or --wait
). When a spam run times out, a new run will start with freshly-generated transactions.
Example:
# run 100 batches of 10-block spam runs, each sending 50 txs/sec
cargo run -- spamd ./scenarios/stress.toml $RPC -p $PRK -d 10 --tps 50 -l 100
engine_
API spamming
We’ve added support for calls to the engine_
API, so that we can test execution nodes with more control and less overhead. The current implementation relies on the execution node’s mempool (eth_sendRawTransaction
) to collect the transactions that contender sends. Once contender has sent a batch of transactions, it will call newPayload
& forkchoiceUpdated
to trigger block-building in the EL node. This is probably the most simplistic application of the engine_
API, but it allows us to get a read on how our tx pools are performing, and how quickly our nodes can build blocks.
More work is likely to be done in this area. A replay feature is in the pipeline, where we sync the node back N blocks, and replay the block-building process with the N blocks that have already been built, so we can observe the maximum performance of the target builder under load with full blocks that have been built (using contender spam transactions).
To spam/setup with the auth API, pass the following flags with spam
, setup
, or spamd
:
--auth <auth rpc url>
--jwt <jwt file path>
--fcu
example:
contender spam ./scenarios/stress.toml http://localhost:8545 \
--jwt ~/test-jwt-secret.txt \
--auth http://localhost:8551 \
--fcu \
--tps 50 -d 1
contender reports
Contender generates chain performance reports that measure the following:
- per-block gas usage & utilization
- peak, and charted over time
- txs per block
- block time stability
- rpc response latency per RPC method
- storage update heatmap
- transaction time-to-inclusion
- per-tx gas usage
- pending transactions over time
These metrics are designed to provide a snapshot of how your target chain acts under specific circumstances; ones imposed by running a contender scenario. We will likely add more metrics over time, so please make an issue if you have ideas for more things we ought to measure!
We’ve been using contender to test the Unichain-Sepolia network — here’s a preview of the results so far:
- unichain-sepolia_2025-04-25/report-20-21.html
- unichain-sepolia_2025-04-25/report-22-22.html
- unichain-sepolia_2025-04-25/report-23-23.html
- unichain-sepolia_2025-04-25/report-25-25.html
These reports are very likely to change in the near future – we probably aren’t taking enough measurements. But for now, I hope this gets you excited about contender, and our ultimate goal: putting nodes to the test!
If you want to help us make contender the ultimate blockchain testing tool, there’s plenty of ways to get involved. We need help on all kinds of things from CLI & UX design to DX improvements to better data science, and the list goes on.
To get started, check out contender’s issues page!
If you’ve been using contender and you have an idea to improve it, please write up a proposal in a new issue and/or contact me directly @zeroXbrock on X.
Lastly, but certainly not least, a big shoutout to our latest community contributors:
Thank you all. Every contribution makes a difference.