perf(bot): parallelize independent bot factory setup steps#24581
Merged
Conversation
PhilWindle
approved these changes
Jul 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Parallelizes the independent setup steps in the bot factory (
@aztec/bot,bot/src/factory.ts).This is production bot code that runs against live networks, so the changes are strictly
behavior-preserving: same contracts at the same (salt-derived) addresses, same amounts, same final
state. Only the ordering/concurrency of provably-independent setup steps changes.
The bot factory is the single largest cost in the
single-node/bote2e suite: each*.createrunsthe production sequencer at 12s L2 slots (
PIPELINING_SETUP_OPTS) and deploys/mints one tx per slot,fully serial.
AmmBot.createalone was ~74s (7 serial txs).Dependency graph per bot type
Edges below are "must be mined before", verified against the Noir contract source
(
token_contract,amm_contract).Transaction bot --
setup()(unchanged)setupAccount-> register recipient ->ensureFeeJuiceBalance-> deploy token -> mint. This chain isfully data-dependent: funding gates the deploy, the deploy gates the mint. The recipient
(
createSchnorrAccount) is register-only (no tx), and the private+public mints are already batchedinto one tx. Nothing is independent, so
setup()is left serial.AMM bot --
setupAmm()Steps: deploy token0 (D0), token1 (D1), LP token (DL), AMM (DA);
set_mintergranting the AMM rightsover the LP token (SM); mint token0+token1 to the provider (M, one batched tx);
add_liquidity(AL).addresses are salt-derived, so DA needs only the (pre-derived) addresses, not the token deploys mined.
Token::set_minterjust writesminters[amm] = true; it does not call or validate the AMMcontract. It needs the LP token deployed and the (derivable) AMM address only -- not the AMM deployed.
The deployer is authorized because the Token constructor sets
minters[admin] = true.mint_to_private/mint_to_publicrequire the caller be a minter; the deployer is admin=minterfrom the constructor, so the token0/token1 mints need only those tokens deployed (no
set_minter).add_liquiditytransfers token0/token1 from the provider (needs M for balances), mints LPtokens (needs SM so the AMM is an LP minter), and targets the AMM (needs DA).
Restructured from 7 serial txs into 3 slot-phases:
Promise.all([D0, D1, DL])Promise.all([DA, SM, M])ALCross-chain bot --
setupCrossChain()Steps: deploy TestContract (an L2 tx), seed N L1->L2 messages (L1 txs), wait for the first message ready.
messages are queued on L1 and do not require the L2 contract to exist yet; they are consumed later in
bot.run(), after setup completes. So the L2 deploy and the L1 seeding are independent.accounts, so there is no L1 nonce interaction between them.
Restructured to overlap the deploy with the seed loop:
Promise.all([deploy TestContract, seed loop]),then the message-ready wait.
What stayed serial, and why
setup()(data-dependent chain, nothing independent).ensureFeeJuiceBalancebefore every deploy (funding must precede spending).add_liquidityafter its mints + minter grant + AMM deploy.sends would race on the L1 nonce. The suite already documents this nonce hazard. Only the loop as a
whole overlaps the (L2) TestContract deploy.
Production safety / idempotent re-entry
registerOrDeployContract, which checksgetContractMetadata(address).isContractPublishedper contract and only registers (no tx) if alreadydeployed. If one parallel deploy fails and the others succeed, the next
*.createrecovers: thesucceeded contracts register-only, the failed one redeploys. Addresses are salt-derived and identical
across runs.
set_minteris idempotent (writestrueagain). The AMM path's mint andadd_liquidityalways run(no balance guard) -- unchanged from the previous
fundAmm..send()s are safe by construction: the PXE serializes simulate/prove throughits
SerialQueueand setup txs pay with random tx nonces. The bot runs the sameEmbeddedWallet->PXE path in the e2e suite and in production.
longer prevents the others from being sent. Their effects (a deployed-but-unused AMM, a minter grant,
an extra mint) are all idempotent-safe, and
add_liquidityuses fixed amounts, so re-entry convergesto the same final state.
withNoMinTxsPerBlock(theflushSetupTransactionssave/zero/restore wrapper around each setup tx)was not safe to re-enter concurrently: interleaved save/zero/restore could read the already-zeroed
value and "restore"
minTxsPerBlockto 0 permanently. It now reference-counts entrants -- the firstsaves and zeroes, the last restores -- with unit tests covering the overlapping and failure paths
(
bot/src/factory.test.ts). Serial callers see the exact same RPC sequence as before.Final state is identical in every path: same contract addresses, same minter grant, same minted
amounts, same liquidity.
Local evidence
Two full local runs of
single-node/bot/bot.test.ts(production sequencer, 12s L2 slots), all 10tests passing in both. Hook durations from factory log timestamps, against the round-4 local baseline
measured on the same machine at the same cadence (findings from #24534):
Bot.create(transaction-bot hook, untouched): 31.8s vs 32.2s baseline -- unchanged, as intended.AmmBot.create: 57.2s vs 73.9s baseline (-16.7s). The three token deploy txs go out within 300ms ofeach other; the AMM deploy,
set_minterand the mint batch all go out within the following slot.CrossChainBot.create: 24.0s vs 43.5s baseline (-19.5s). The TestContract deploy overlaps both L1seeds; the first message is ready almost immediately after the deploy is mined.
beforeHooksMs: 116.9s vs 153.7s baseline (-36.8s), matching the per-hook deltas.bot/src/factory.test.ts(4 tests) covering thewithNoMinTxsPerBlockreentrancy fix.Expected effect on CI
AmmBot.create: 7 serial txs -> 3 slot-phases (~87s on CI per test(e2e): instrument and diagnose bot suite setup cost #24534's spans -> ~55-60s).CrossChainBot.create: deploy overlaps the seed pipeline (~43s -> ~25s).setup:botinstrumentation is on this base): the amm-bot andcross-chain
setup:botoccurrences drop per the numbers above. Measured CI numbers to be appendedwhen a full run lands.
Measured impact
The bot suite (a single shard) drops 216.3s → 118.3s total (−98.0s, −45%); beforeHooks 215.9s →
118.0s.
setup:botspans exist on this base (that instrumentation lives in the unmerged test(e2e): instrument and diagnose bot suite setup cost #24534), so themetric is the suite-level hook fold, which covers all three nested bot factory setups (Bot, AmmBot,
CrossChainBot).
(private_initialization −2.9s/shard over 20 shards, deploy_method −2.1s/shard over 14), and the
largest counter-move is validators_sentinel +24s (multi-node variance). A −98s single-shard delta is
far outside that envelope.
serial tx (baseline beforeHooks 215.9s on CI vs 153.7s locally at the same 12s cadence); collapsing
7 serial txs into 3 slot-phases removes proportionally more where slots are more often missed.
Baseline CI run 1783393028773172 (merge-base 30966a4, full). PR CI run 1783427250198215 (x-fast).
Fixes A-1408