# Failed deposit transactions cause nonce mismatch and break L2 block production

Failed deposit tx in Citrea L2 block production doesn't decrement the system-tx nonce, so subsequent system txs get bad nonces and the whole L2 block build fails.

- Payout: Contest reward
- Severity: medium
- Program: Citrea
- Credited: marchev
- Disclosed: 2025-10-21
- Write-up quality: 7/10
- Original: https://cdn.cantina.xyz/reports/cantina_competition_citrea_jul2025.pdf#3.2.9
- Library page: https://bugbountylibrary.com/r/cantina%3Acitrea_jul2025%3A3.2.9
- Tags: domain:web3 impact:dos kind:contest platform:cantina sev:medium tech:evm technique:race-window technique:source-review vuln:accounting-error vuln:dos

**Target:** L2 sequencer block production / deposit system transactions (runner.rs)
**Root cause:** In process_sys_txs(), a reverting deposit transaction reverts the working set and continues without decrementing the local signing nonce, unlike dry_run_transactions() which decrements on failure. The next system tx is signed with a stale nonce and rejected by pre_dispatch_tx_hook() with SovTxBadNonce, aborting block production.
**Steps:**
1. Observe deposit txs are signed with a local counter: `self.sign_tx(..., *nonce)?; *nonce += 1;`
2. On `EvmSystemTransactionNotSuccessful && is_deposit`, code does `working_set.revert()` and `continue` without `*nonce -= 1`
3. Bypass mempool simulation via stateless sim: submit the same valid deposit 3x quickly; each simulates against a fresh `WorkingSet::new(storage.clone())` so all pass
4. During execution first succeeds, second fails (reused move tx), nonce desyncs, third rejected with `SovTxBadNonce`, block build fails
5. Alt vector: deploy contract whose `receive()` gates on `block.timestamp`; `get_pending_block_env()` copies latest header without updating timestamp, so sim uses stale timestamp while execution uses `now()`; submit two deposits, first reverts at execution, nonce desyncs, block fails
**How found:** Code reading of process_sys_txs() vs dry_run_transactions() revealed the missing nonce decrement; then analyzed the send_raw_deposit_transaction() simulation safeguard and found two divergences (per-deposit fresh working set, stale pending-block timestamp) that let an attacker make simulation pass while execution reverts.
**Exploitation:** Attacker submits crafted deposits that pass eth_call simulation but revert during real block execution (duplicate deposits or timestamp-gated contract), triggering the nonce desync so the sequencer's L2 block production fails and all deposits in that block are dropped from the mempool.
**Insight:** When a mempool pre-simulation guard exists, hunt for state the simulation doesn't share with execution: per-tx fresh working sets that ignore earlier pending txs, and pending-block env builders that copy the latest header without refreshing timestamp. Any sim-vs-exec divergence can weaponize error paths like nonce counters.
