kona-sp1-proposer: defend challenged games with SP1 super-aggregation proofs - #22121
Open
digorithm wants to merge 13 commits into
Open
kona-sp1-proposer: defend challenged games with SP1 super-aggregation proofs#22121digorithm wants to merge 13 commits into
digorithm wants to merge 13 commits into
Conversation
Dead code (state snapshot trio, duplicate confirmation constants, backup-era serde derives and deps, devstack metrics block), PrestateCache newtype, startup logging for signer and resolved config with URL redaction, GamesCreated and GamesBondsClaimed double counting, tokio::fs artifact reads, named anchor root fields, missing-to signer error, TX_CONFIRMATION_TIMEOUT=0 rejection, required super-root timestamp fields, doc fixes. Part of #22086.
Pending-exclusion contrast test, bond_claim_action ordering cases, ProposalStatus decode rejection, PROPOSAL_SAFETY field selection test, revert-prefix pin test, super-root response timestamp verification, WithoutHonestChallenger honored in the non-ZK path, wait.For-based factory waits, opportunistic root check in WaitForAnchorRootAtLeast, race-free chaining test assertions. Part of #22086.
One unreadable game no longer aborts the sync cycle (walk, pending re-validation, and status classification each contain per-game errors and surface them via a new game_sync_error counter; the cursor only advances past a complete walk). Pending entries whose deadline fell behind the anchor beyond the lag cutoff are evicted (one-sided; fresh games under a stalled anchor stay re-checkable). Own-game super-root mismatches stay re-checkable instead of terminally dropping our bond on bad supernode data. is_parent_resolved only counts DefenderWins parents. Creation checks the chosen parent against the registered registry's isGameBlacklisted and isGameRetired (a retired or blacklisted parent reverts child creation forever and nothing pruned it; the stall survived restarts). Structural decomposition of sync_games stays in #22019 (blocked on #21463). Part of #22086.
A create tx that lands after its confirmation timeout left the duplicate creation guard unarmed; the next tick treated the collision as third-party, advanced the timestamp, and bonded a duplicate game on the same parent. The collision arm now consults gameCreator(): our own game is adopted (guard re-armed), third-party collisions still advance. MAX_FEE_PER_GAS and MAX_PRIORITY_FEE_PER_GAS (wei, unset = uncapped) bound what the fee estimator may set: fees are clamped after fill in both signer arms, restoring the priority <= max_fee invariant. The local signer fills on a wallet-less provider first, since a wallet provider signs during fill and a signed envelope cannot be clamped. Part of #22086.
Co-authored-by: Inphi <mlaw2501@gmail.com>
Co-authored-by: Inphi <mlaw2501@gmail.com>
…amp spans The defend path proves whole game spans, not single timestamps. The guest input types already supported spans; this generalizes synthesize_execution host-side (responses slice covers span.start-1..=span.end) so no vkey changes. The executor binary keeps single-timestamp behavior, pinned by a bundle-equality regression test plus the six migrated tests. Also extracts HostInputs (deployment-scoped endpoints and config paths) and makes the witness-collection helpers pub so the proposer can drive the same machinery as a library.
Port of upstream fault-proof (@ 13716c2c) defense: challenged games in the owned set get a defense task (deadline-ascending, capped, per-game dedup) that fetches the span's super roots, collects witnesses, obtains an SP1 super-aggregation proof bound to the signer, and submits prove(). The existing resolution and claim tasks finish the game. Departures from upstream, each forced by this repo's architecture: - proving.rs replaces the single-chain host machinery with the super-root stack; the aggregation guest validates one shared l1Head, so the header chain walk is gone. - Ownership is prestate-based (option A, #22111): prove, resolve, and claim are one set, keyed by loadable prestate artifacts. Sync loads every cached game's prestate so post-rotation games stay owned. - Providers are Network (SPN request/poll, ported) and Mock (native-core, no ELFs, placeholder bytes; dev verifiers only). No cluster arm. - Proving keys are per-prestate; setup verifies the aggregation vkey hashes to the on-chain prestate (the PR-1 deferred check). Poisoned prestates close the creation gate and heal only when artifacts change. - Permanent unprovable verdicts are trust-gated on current_l1 so a stale mid-rewind supernode answer cannot burn an honest game's bond. - Pre-submit re-check avoids guaranteed-revert prove() txs after long proving runs. No restart recovery, matching upstream. Part of #21463. Fast finality is deliberately not ported (#22112).
The devstack launcher now plumbs the witness endpoints (L1 beacon, both L2 ELs) and materializes the rollup, L1, and depset configs the InteropHost needs, running the proposer with PROOF_PROVIDER=mock. TestChallengedValidProposalAnchors drops its manual prove(): the proposer detects the challenge and submits the proof itself. The new TestProposerDefendsForeignValidGame pins prestate-based ownership end to end (foreign game proven, resolved, challenger bond claimed), created off the proposer's proposal grid to avoid a factory UUID collision. The ZKGame Prove DSL helper is deleted with its last caller. zkProveDuration goes to 120s: the defend flow now does real work inside the prove window on CI runners, and time-traveled expiries keep wall cost flat. TestZKDisputeGameWithdrawal stays skipped for its real blocker: the single-chain preset fails bring-up installing the ZK game type.
Wiz Scan Summary
To detect these findings earlier in the dev lifecycle, try the Wiz Code extension for VS Code, JetBrains, or Visual Studio. |
A stale local format pass committed leading-operator style on untouched lines; re-running the pinned nightly formatter restores the repo style. Groth16 is the SNARK scheme behind AGG_PROOF_MODE=groth16, not a typo for growth.
Public docs may not link private items under -D warnings; inline the trust condition instead.
…oser-defend # Conflicts: # rust/Cargo.lock # rust/kona/sp1/crates/proposer/src/bin/kona-sp1-proposer.rs # rust/kona/sp1/crates/proposer/src/config.rs # rust/kona/sp1/crates/proposer/src/metrics.rs # rust/kona/sp1/crates/proposer/src/proposer.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Ports the defend path from op-succinct's
fault-proofproposer (@13716c2c): when a game the proposer can prove gets challenged, it fetches the span's super roots, collects witnesses, obtains an SP1 super-aggregation proof bound to its signer, and submitsprove(). The existing resolution and claim tasks then finish the game (including the challenger-bond prover credit).Closes #22147. Fast finality (prove at creation) is deliberately not ported; tracked in #22112. Stacked on #22097 (merged; now targeting develop).
What is upstream and what is ours
prover.rs(SPN request/poll state machine; cluster arm and two request knobs dropped), the defense scheduling inproposer.rs(spawn_game_defense_tasks,should_skip_proving,spawn_game_proving_task, deadline tiers),RangeSplitCount.proving.rsreplaces upstream's single-chain witness/stdin machinery with the super-root stack (super-range-executorwitness collection,SuperAggregationInputs);synthesize_executiongeneralized to timestamp spans (host-side only, no vkey change); the multi-prestatePrestateCachegains per-prestate proving keys and the deferred vkey verification (agg vk must hash to the on-chain prestate).No real proofs in this PR
There are no SPN calls and no SNARK cryptography in any test. All coverage runs the mock provider: submitted proof bytes are a placeholder constant and devstack's
ZKMockVerifieraccepts any bytes. What the tests exercise for real is every computation around the proof: span fetching, witness collection from live nodes, native re-derivation of the state transition (the same code the guest programs compile from), consolidation checks, aggregation-input validation, and the exact public-values tupleprove()reconstructs on-chain. The one unexercised seam,generate_*_proof()returning verifier-accepted bytes, is upstream's code nearly verbatim and gets a manual SPN smoke once credentials are provisioned.Hardening beyond upstream
Two serious issues caught in pre-merge review, fixed with pinning unit tests. Neither exists upstream; both are invariants upstream gets structurally for free that our architectural departures initially violated:
undefendableskip set (added to bound retry spend; upstream retries forever) only condemns a game on a supernode answer withcurrent_l1 >= verified_required_l1. Without the gate, a mid-rewind stale answer could permanently mark an honest game undefendable and burn its bond.ensure_loadednow returns false for poisoned entries and heals only when the published artifacts actually change.Smaller deliberate divergences, flagged inline: pre-submit re-check before the prove tx (upstream submits blind), sync loads every cached game's prestate so post-rotation games stay owned (upstream is single-identity), mock provider is native-core rather than emulator-based (stub artifacts are not ELFs).
Test plan
--all-features --all-targets --lockedandjust lint-docsclean.TestChallengedValidProposalAnchorsnow proposer-driven (theTODO(#21463)flip) and newTestProposerDefendsForeignValidGamepinning option A end to end (foreign game proven, resolved, challenger bond claimed).TestChallengedValidProposalAnchors+TestProposerClaimsBondAfterResolutionpass with real artifacts.TestZKDisputeGameWithdrawalstays skipped: its single-chain preset fails bring-up installing the ZK game type (pre-existing, unrelated to the proposer; issue to follow).