Skip to content

fix(fault-proof): evict expired challenged games and guard proving with gameOver()#849

Open
fakedev9999 wants to merge 7 commits into
mainfrom
feat/skip-gameover-proving
Open

fix(fault-proof): evict expired challenged games and guard proving with gameOver()#849
fakedev9999 wants to merge 7 commits into
mainfrom
feat/skip-gameover-proving

Conversation

@fakedev9999
Copy link
Copy Markdown
Member

@fakedev9999 fakedev9999 commented Mar 30, 2026

Summary

Two-layer fix for proposer wasting prover-network gas on dead games (games that are Challenged + expired and will always resolve as CHALLENGER_WINS).

Layer 1: Evict dead games from cache (root fix)

In sync_games step 2, Challenged + expired games are evicted via RemoveSubtree. This is safe because:

  • Challenged + expired = guaranteed CHALLENGER_WINS on resolve (contract line 456-459)
  • Parent CHALLENGER_WINS cascades to ALL descendants (contract line 442-446) — descendants can never be DEFENDER_WINS
  • sync_games reads fresh on-chain claim_data — if a prove() tx landed, status would be ChallengedAndValidProofProvided, not Challenged

Without this, dead games stay IN_PROGRESS forever if nobody calls resolve(), causing infinite prove → revert → retry loops.

Layer 2: gameOver() guards (transient protection)

Covers the window between sync cycles:

  • should_skip_proving(): on-chain gameOver() check after cheaper deadline check, before proof generation
  • prove_game(): best-effort gameOver() check after proof generation, before tx submission. Tolerates transient RPC failure to avoid discarding expensive proofs.

Addresses succinctlabs/cloud-ops#75
References celo-org/op-succinct#91

Test plan

  • cargo check -p op-succinct-fp
  • cargo test -p op-succinct-fp --lib — 46/46 pass
  • cargo fmt / cargo clippy clean
  • Integration test on devnet with expired challenged games

The proposer wastes prover-network gas by generating proofs for games
that are already over (expired or already proven by someone else). The
on-chain prove() call reverts with GameOver() in both cases.

Add gameOver() checks in two places:
- should_skip_proving(): before starting proof generation, to avoid
  wasting compute on games that are already over
- prove_game(): after proof generation completes but before submitting
  the on-chain tx, to catch games that expired during the minutes-to-
  hours proof generation window

Addresses succinctlabs/cloud-ops#75
References celo-org#91
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 30, 2026

Metric Value
Batch Start 16,293,083
Batch End 16,293,088
Witness Generation (seconds) 0
Execution Duration (seconds) 59
Total Instruction Count 756,250,674
Oracle Verify Cycles 85,627,968
Derivation Cycles 578,082,553
Block Execution Cycles 6,806,213
Blob Verification Cycles 31,185,041
Total SP1 Gas 1,096,057,380
Number of Blocks 5
Number of Transactions 5
Ethereum Gas Used 250,666
Cycles per Block 151,250,134
Cycles per Transaction 151,250,134
Transactions per Block 1
Gas Used per Block 50,133
Gas Used per Transaction 50,133
BN Pair Cycles 0
BN Add Cycles 0
BN Mul Cycles 0
KZG Eval Cycles 0
EC Recover Cycles 0
P256 Verify Cycles 0

- Move gameOver() RPC call after the local deadline check in
  should_skip_proving() — avoids unnecessary RPC for games already
  caught by the cheaper cached-deadline check
- Add tracing::warn before bail in prove_game() so operators can
  see when hours of proof work are discarded
- Update doc comment to reflect new check order
Don't discard hours of proof work on a transient RPC hiccup. If the
gameOver() call fails, proceed with submission — the on-chain prove()
will revert if the game is truly over (cheap gas vs. wasted proof).
Games that are Challenged + expired (deadline < now) are guaranteed to
resolve as CHALLENGER_WINS. This cascades to all descendants via the
contract's resolve() logic (parent CHALLENGER_WINS → child CHALLENGER_WINS).

Without eviction, these dead games stay IN_PROGRESS in cache forever
(nobody calls resolve), causing the proposer to repeatedly attempt
proving → revert → retry.

RemoveSubtree is safe because:
- Challenged + expired = CHALLENGER_WINS regardless of parent state
- All descendants also guaranteed CHALLENGER_WINS (parent cascade)
- sync_games reads fresh on-chain claim_data, so if a prove() tx
  landed, status would be ChallengedAndValidProofProvided (not evicted)
@fakedev9999 fakedev9999 changed the title fix(fault-proof): skip proving for games where gameOver() is true fix(fault-proof): evict expired challenged games and guard proving with gameOver() Mar 30, 2026
Align test expectations with the new RemoveSubtree behavior for
Challenged + expired games:

- test_mixed_states_multiple_branches: first sync now evicts games 3+4
  immediately (previously required a second sync after on-chain
  resolution). Added a stability check confirming a second sync after
  on-chain resolution is a no-op.

- test_in_progress_proposal_status_multi_branch: game 1 (Challenged +
  expired) is now evicted during sync. Assert absence instead of
  inspecting stale cache entry. Challenged status coverage for
  should_attempt_to_resolve is already in
  test_in_progress_games_resolution_marking.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant