kona-sp1-proposer: review follow-ups from the #22018 nits list - #22097
Merged
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.
Inphi
reviewed
Jul 29, 2026
Co-authored-by: Inphi <mlaw2501@gmail.com>
Co-authored-by: Inphi <mlaw2501@gmail.com>
wwared
approved these changes
Jul 30, 2026
Review follow-up (#22097): acting on a partially discovered topology could select a parent whose unfetched ancestry is invalid, so walk errors bubble again and skip the whole cycle (cursor stays put; the range is re-walked next tick). Per-game containment remains only in the status-classification and pending re-validation phases, which operate on already-cached games. To keep the walk immune to malformed games by construction, decode and conversion failures on game-sourced data now classify the game invalid instead of erroring, starting with the l2SequenceNumber u64 conversion (unreachable today: the contract sources it from an 8-byte field). New sync_cursor and factory_latest_game_index gauges make a stuck cursor directly alertable alongside game_sync_error. Part of #22086.
…path Review agreement on #22097: exempting owned games (and their ancestors, foreign included) from pending eviction is deferred to #21463, where ownership widens to own-plus-ancestors. The TODO carries the requirement and the current limitation (an evicted own game loses tracking until a restart after the supernode heals). Part of #22086.
Review follow-up (#22097): a create whose confirmation timed out may still land, and the next tick could propose at a fresh timestamp, a different uuid the factory cannot dedup, bonding a sibling on the same parent. Record the exact rootClaim+extraData before sending. While the record is unresolved, creation retries that uuid instead of proposing anew: the factory finds the game (adopt ours, or recognize a foreign copy as spending the uuid), or the resend succeeds, or it reverts (the pre-send lookup rules out GameAlreadyExists and the remaining create-revert causes are permanent, so a floating original would revert identically) and the record clears. Transport errors and repeat timeouts keep the record for the next tick. A landed revert of the original clears immediately (the tx is consumed). Also document the fee-cap liveness tradeoff on both config knobs (review consensus: keep them, default off). Part of #22086.
Review catch (#22097, Inphi): the pre-send lookup only rules out GameAlreadyExists at lookup time. The stuck original can be included between our lookup and the resend reaching a builder, making the resend revert with GameAlreadyExists; clearing on that mistook an adoptable game for a permanent failure and left the duplicate guard unarmed (sibling risk in the sync-lag window). A landed revert is final chain state with exactly two causes, so re-running the lookup disambiguates: the uuid's game now exists (adopt it; games are never deleted) or the cause is permanent (clear; a floating original would revert identically). Part of #22086.
…resends Adversarial review of the uuid-retry design found two defects: a resend whose create would revert dies at fee estimation, which never yields the landed-revert prefix, wedging the record forever (and the in-flight path bypasses the parent-standing check); and pending-count nonces queue each resend behind the stuck original, stacking same-uuid duplicates that all revert once fees decay. Closing both properly required a parent adjudication helper and pool checks, at which point the resend itself was the complexity. Drop the resend. The record still holds new proposals; each tick the factory lookup adopts our landed game, and the record clears only when the signer's pool drains (pending == latest), meaning nothing of ours can land anymore. A visibly stuck transaction keeps holding; there is no time-based clearing. Residual, documented: a transaction alive only in another node's pool can land after the clear, re-creating the sibling case, which is benign and self-healing (a valid own game whose bond the normal resolution and claiming flow recovers). Part of #22086.
Inphi
approved these changes
Jul 30, 2026
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
Works through all 40 nit-level items from #22086 (wwared's #22018 review). Every item has an explicit verdict below: 37 fixed here, 2 deferred to their owning issues, 1 declined with reason. Four commits, grouped by risk (verdict-table references: commit 1 = 6cb46a3, commit 2 = 927955f, commit 3 = ec74e4b, commit 4 = 3bd005b; hashes change if the branch is rebased before opening the PR):
kona-sp1-proposer: mechanical review nits from #22018: 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.kona-sp1-proposer: test improvements from #22018 review: 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.kona-sp1-proposer: contain per-game failures in sync_games: per-game error isolation (walk, pending re-validation, classification; new game_sync_error counter; cursor advance gated on a complete walk), one-sided pending eviction past the anchor deadline-lag cutoff, own-game super-root mismatches held re-checkable, DefenderWins-only parent resolution eligibility, retired/blacklisted-parent stall fix (pre-creation isGameBlacklisted/isGameRetired check against the registered registry).kona-sp1-proposer: harden tx submission (collision adoption, fee caps): a create tx landing after its confirmation timeout no longer leads to a duplicate bonded game (the collision arm consults gameCreator() and adopts our own game instead of advancing the timestamp), and MAX_FEE_PER_GAS / MAX_PRIORITY_FEE_PER_GAS env caps bound what the fee estimator may set (clamp-after-fill in both signer arms, unset = uncapped).The sync_games decomposition (walk/step split, trait seams, multicall) deliberately stays in #22019 (blocked on #21463); every change here keeps the loop's current shape.
Verdicts (all 40 items)
Fixed items name the commit and the code; declined/deferred items carry their reason. Comment links are in #22086.
sync_gamescontains per-game failures at the walk (cursor advance gated on a complete walk), pending re-validation, and classification; newkona_sp1_proposer_game_sync_errorcounterPendingcarries the game deadline; one-sidedpending_evictableevicts entries whose deadline fell behind the anchor beyondMAX_GAME_DEADLINE_LAG; ahead-of-anchor games stay re-checkable under a stalled anchorfetch_artifactfile armparent_status_resolvesrestricts eligibility to DefenderWins; ChallengerWins subtrees are removed by sync insteadPending(re-checked each sync, aged out by the eviction cutoff); third-party mismatches stay terminaltry_initretry warn gainserror = %estate.gamesis load-bearingProposerStateSnapshotdeletedstate_snapshotdeletedget_gamedeletedhandle_game_creation's creation-success arm(anchor_root, anchor_sequence_number)toinstead of the anvil contract-creation hackto; the proposer only sends contract calls (verified end to end by the acceptance suite)super_root_attakes the expected timestamp and errors on mismatch before the hash check; both callsites updated; two testsattachSuperChallengerAndProposernow honorsSkipHonestChallenger(all L2ChallengerConfig consumers nil-check)max_proposable_selects_safety_fieldpins safe/finalized selection (the Finalized arm is the untested production default)ProposalStatus::try_from(5).is_err()added<=checkPrestateCachenewtype owns the lock and base URLWaitForAnchorRootAtLeastrestores the root-equality check when the observed anchor sits exactly at the game's height; the wider(root, minHeight)DSL shape stays unnecessary while this is the only callerTest plan
cargo nextest run -p kona-sp1-proposer -p kona-sp1-super-range-executor --all-features: 55 tests (46 + 9), all passing; clippy--all-features --all-targets --lockedclean;just lint-docsclean.op-golangci-lint run ./op-devstack/... ./op-acceptance-tests/tests/proofs/zk/...: 0 issues.KONA_SP1_ELF_DIR)TestProposerClaimsBondAfterResolutionPASS, covering the two-phase claim, MaturesAt change, and metrics paths end to end.Notes for reviewers