For the complete documentation index, see llms.txt. This page is also available as Markdown.

75310 bc low offchain proposer submit retries are unbounded on deterministic parent invalid proposal reverts

Submitted on Apr 28th 2026 at 13:05:56 UTC by @pks271 for Audit Comp | Base Azul

  • Report ID: #75310

  • Report Type: Blockchain/DLT

  • Report severity: Low

  • Target: https://github.com/base/base/tree/v0.8.0-rc.28

  • Impacts:

    • 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

    • Unbounded gas consumption in any in-scope contract function callable by external parties

Description

Bug Description

commit: e3467a2048881213b56739a54a876efb9c6ea103 (v0.8.0-rc.28)

The off-chain proposer pipeline recovers the latest valid proposal chain, caches the recovered parent_address, generates the next output proof, and then submits the proof to L1 through DisputeGameFactory.createWithInitData().

This flow assumes the recovered parent remains valid until the L1 proposal transaction is accepted. If the parent game becomes invalid after recovery/proving but before the proposal transaction lands, AggregateVerifier.initializeWithInitData() deterministically reverts with InvalidParentGame.

The parent can be successfully challenged and resolve CHALLENGER_WINS, or it can be blacklisted/retired by the registry, while the proposer still holds a proved child output using the stale parent.

After this deterministic submit failure, the proposer treats the failure as retryable, reinserts the same proof back into state.proved, clears state.submitting, and tries the same submission again on later ticks. The configured max_retries only bounds proof-generation failures in handle_proof_result, it is not applied to submit failures.

call trace:

crates/proof/proposer/src/pipeline.rs::run -> try_recover_and_plan -> dispatch_proofs -> handle_proof_result stores proof in state.proved -> try_submit uses cached parent_address -> validate_and_submit -> OutputProposer.propose_output -> DisputeGameFactory.createWithInitData() -> AggregateVerifier.initializeWithInitData() revert InvalidParentGame -> SubmitOutcome::Failed -> state.proved.insert(target_block, proof) -> next tick repeats

code:

Impact

A parent-invalidating actor in the normal dispute flow can make the official proposer repeatedly submit the same already-doomed proposal transaction against the same stale parent. Each retry consumes L1 gas and prevents that proposer process from progressing to a fresh recovery/re-proof path for the next valid parent, cause gas burn and delay dispute resolution.

Recommendation

Add a bounded submit retry counter separate from proof retries, classify deterministic terminal reverts such as InvalidParentGame as drop/reset/recover conditions instead of requeueing the same proof forever, and revalidate the cached parent immediately before submission.

Proof of Concept

steps:

  1. A parent game P is valid when the proposer performs recovery walk.

  2. The proposer proves the child output using cached parent_address = P.

  3. Before the proposal transaction is included, P becomes invalid through a normal protocol action, for example a valid challenge resolves P as CHALLENGER_WINS, or the registry blacklists/retires P.

  4. The proposer submits the child proposal through DisputeGameFactory.createWithInitData().

  5. AggregateVerifier.initializeWithInitData() reverts with InvalidParentGame.

  6. The proposer requeues the same proof and retries the same stale-parent submission on later ticks.

patch:

Run:

Result:

Was this helpful?