A bug in the respective layer 0/1/2 network code that results in unintended smart contract behavior with no concrete funds at direct risk
Description
Brief/Intro
The proposer wraps L1 dispute game creation in a 10-minute tokio::time::timeout, but the transaction path underneath it is not safe to cancel during initial preparation. If that outer timeout fires after the TxManager reserves a nonce but before the transaction is published, the nonce is lost from the in-memory nonce manager. After that, the proposer can keep signing later transactions with higher nonces that cannot be mined because the missing nonce was never published.
Vulnerability Details
SimpleTxManager::prepare() documents the exact problem:
/// This future is **not** cancellation-safe./// .../// Callers must not wrap this future in `tokio::select!`,/// `tokio::time::timeout`, or similar combinators that may drop it/// mid-flight.
The proposer does exactly that during submission:
That call reaches:
Because nonce_override is None, craft_tx_with_caps() reserves a new nonce through NonceManager::next_nonce():
The problem is what happens if the outer timeout drops the future here. NonceGuard::Drop does not roll the nonce back; it only logs that the nonce was consumed. Rollback only happens through an explicit rollback() call on the signing error path.
The TxManager has an internal cleanup path for TxManagerError::SendTimeout, but it is disabled by default. tx_send_timeout defaults to Duration::ZERO, and the CLI default is "0s". So the internal timeout never fires, and the nonce reset logic only runs if send_event_loop().await returns. With the proposer’s outer timeout, the whole future is dropped before that cleanup can run.
Impact Details
This is a high-severity liveness issue for the proposer. Once nonce n is leaked, every later proposal transaction is signed with nonce n+1 or higher. Ethereum will not mine those transactions while nonce n is missing, so the proposer can get stuck behind a transaction that was never even published.
The practical result is ugly: L1 dispute game / output proposal submission can stop until someone notices and recovers the service by restarting, resetting nonce state, or manually clearing the gap. In the meantime, legitimate proposals are not created on L1. That means the smart-contract workflow is not just delayed by normal network conditions; it is completely wedged by the proposer’s own nonce state.
There is no direct theft here, and this does not halt the whole L1/L2 network. But for the proposal system, it is a hard availability failure. The trigger is realistic too: a congested L1, degraded RPC, repeated prepare retries, fee-bump churn, or a slow remote signer/HSM can all push the outer 10-minute timeout over the edge. With tx_send_timeout disabled by default, a stock proposer has no earlier internal timeout cleanup to catch it first. One timeout at the wrong point can turn into an indefinite submission outage for that proposer instance.
References
crates/utilities/tx-manager/src/manager.rs
crates/proof/proposer/src/pipeline.rs
crates/proof/proposer/src/output_proposer.rs
crates/utilities/tx-manager/src/nonce.rs
crates/utilities/tx-manager/src/config.rs
crates/utilities/tx-manager/src/macros.rs
Proof of Concept
Make these changes
which outputs
test poc_outer_timeout_during_initial_send_leaks_nonce ... ok
The test uses a signer that blocks after signing starts, after the nonce has already been reserved. The outer timeout cancels send(), and the next nonce returned by the manager is 1, showing nonce 0 was leaked without publication. This test demonstrates the TxManager failure mode directly; the production proposer reaches the same send() path through ProposalSubmitter::propose_output() under the outer timeout in pipeline.rs.