In the challenger dual-proof path (InvalidDualProposal), the driver can build a nullify() proof from an unchallenged game state (tee_prover != 0, zk_prover != 0, countered_index == 0) and move the entry to ReadyToSubmit.
If the game is externally challenged before the submit happens, the on-chain nullify() rules change immediately:
counteredByIntermediateRootIndexPlusOne > 0 requires nullifying the challenged index exactly;
intermediateRootToProve must equal the challenged root;
proof type must be ZK.
The current off-chain submit path does not re-validate countered_index compatibility before sending the pending nullify() call. It only checks status, tee_prover, and zk_prover. As a result, stale pre-challenge (invalid_index, expected_root) is repeatedly submitted and deterministically reverts.
On revert, the entry stays in ReadyToSubmit and is retried every tick. MAX_PROOF_RETRIES is not applied because retry_count only tracks proving retries, not submit retries.
An external actor can race the challenger by challenging a dual-proof game after proof readiness but before submit. This forces deterministic submit reverts on stale nullify() parameters. Because submit retries are unbounded in ReadyToSubmit, the challenger can enter a persistent gas/liveness degradation loop for the same game.
Recommendation
Add submit-side stale-context checks before nullify():
fetch countered_index;
if challenged (countered_index > 0), ensure pending invalid_index and expected_root still match challenged index/root semantics;
otherwise drop/rebuild pending entry from fresh scan classification.
Add MAX_SUBMIT_RETRIES (separate from proof retries) and stop retrying deterministic terminal reverts.
Proof of Concept
steps:
Game is dual-proof and unchallenged (tee_prover != 0, zk_prover != 0, countered_index == 0).
Challenger validates roots and creates a Nullify pending proof with pre-challenge (invalid_index, expected_root).
Before submit, external actor calls challenge(), moving the game into challenged state (countered_index > 0).
Challenger submits stale nullify() parameters; on-chain call reverts deterministically.
Driver keeps entry ReadyToSubmit and retries on every tick.