Skip to content

kona,lokahi: answer the fault-proof queries as op-supernode does - #22647

Draft
claude[bot] wants to merge 5 commits into
claude/lokahi-invalidate-replacementfrom
claude/lokahi-fp-queries
Draft

kona,lokahi: answer the fault-proof queries as op-supernode does#22647
claude[bot] wants to merge 5 commits into
claude/lokahi-invalidate-replacementfrom
claude/lokahi-fp-queries

Conversation

@claude

@claude claude Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Requested by Sebastian Stammler · Slack thread

Part of #22524. Stacked on #22645.

Closes the query gaps behind the fault-proof acceptance bucket (TestInteropFaultProofs_*, TestPreinteropFaultProofs_*, TestFPP, TestNextSuperRootNotFound, TestChallengerCountersPreGenesisGame — ~24 of the 52 failures at the last measurement, job 5515477). Four behaviours of the superroot_atTimestamp path diverged from op-supernode; each is mirrored with the op-node/op-supernode source cited at the change site.

Before

  • A timestamp one second past a chain's local-safe head answered NotLocalSafeYet, forever on a frozen or slower chain. The verifier stalled at every unaligned boundary (super-root at timestamp N was not validated in time, then supernode did not fully process L1 block N in time), and the optimistic branch omitted the chain (no optimistic output for chain 901 at timestamp N).
  • The safe-head database's earliest entry was the first recorded advance, so history below it answered L1AtSafeHeadUnavailable — permanently. TestFPP failed its absolute prestate read with chain 901 no longer records which L1 block made its block at timestamp N safe.
  • A genesis-anchored query went to the database at all, so a pre-genesis game's challenger could not read its prestate.
  • The optimistic branch served the replacement block's output at an invalidated height; the archive lokahi-interop,kona: apply Decision::Invalidate as the deposits-only block replacement #22645 introduced existed but was never read. The fpp subtests derived transition states the proof program refutes (Failed to validate progressed transition state), claims meant to be invalid verified clean, and the SP1 runner counted zero optimistic root replacements.

After — each mirrors the Go stack's behaviour:

  • kona-engine: local_safe_snapshot_at resolves the timestamp by op-node's TargetBlockNumber flooring (op-node/rollup/types.go:237), as op-supernode's LocalSafeBlockAtTimestamp does — a chain without a block at that second contributes its preceding block.
  • kona-node-service: a reset landing at L2 genesis records genesis as safe from L1 block 0 (op-node/rollup/driver/sync_deriver.go:204-220), which holds the safe-head database's floor at genesis across the initial reset and every rewind.
  • lokahi: both history readers (query pairing and the verifier's behind-head lookup) short-circuit genesis to L1 {number: 0} before the database, op-supernode's virtual-node guard (virtual_node.go:263-269).
  • lokahi: the optimistic branch consults the invalidated-output archive first, op-supernode's LastDeniedOutputV0 consult, with the same fail-the-call posture on a read error. The chain's own route keeps answering without the archive — op-node's single-chain superroot namespace consults no deny list.

Every behaviour is pinned by a test that failed before its fix (the history-gap error text is pinned verbatim). Not run here: the acceptance suites themselves — per the standing guidance, CI runs those.


Generated by Claude Code

claude and others added 5 commits August 22, 2026 17:49
The fault-proof acceptance suites drive superroot_atTimestamp harder than
anything else: the proposer reads it per proposal, the challenger per claim,
and the DSL polls it for validation and L1 progress. Three behaviours of that
path diverged from op-supernode, and each one fails a slice of the family.

Timestamps floor onto the preceding block. op-node's TargetBlockNumber
(op-node/rollup/types.go:237) floors an unaligned timestamp, and
op-supernode's LocalSafeBlockAtTimestamp answers whenever the floored number
is at or below the local-safe head — super roots exist at every second, and a
chain without a block at that second contributes its preceding block. kona's
local_safe_snapshot_at classified by raw timestamp instead, so a boundary
timestamp one second past a frozen head answered NotLocalSafeYet forever: the
verifier stalled at every unaligned boundary ("super-root at timestamp N was
not validated in time", and downstream "supernode did not fully process L1
block N in time"), and the optimistic branch omitted the chain ("no
optimistic output for chain 901 at timestamp N" in the preinterop suites).

A reset landing at genesis seeds the safe-head database. op-node's
onEngineConfirmedReset (op-node/rollup/driver/sync_deriver.go:204-220)
records L2 genesis as safe from L1 block 0 whenever a reset walks back to
genesis — deliberately block 0 rather than cfg.Genesis.L1, because dispute
contracts may predate the L2 genesis's L1 origin. That entry is the
database's floor. Without it lokahi's earliest record was the first
post-reset advance, and l1_at_safe_head below it answered
L1AtSafeHeadUnavailable permanently — surfacing as "chain 901 no longer
records which L1 block made its block at timestamp N safe" ("Failed to get
absolute prestate") in TestFPP.

Genesis short-circuits the history lookup. op-supernode's virtual node
(op-supernode/supernode/chain_container/virtual_node/virtual_node.go:263-269)
answers L1AtSafeHead for the genesis block with {number: 0} before touching
the database. Mirrored in both readers of the safe-head history: the query
path's pairing resolution and the interop verifier's behind-head lookup,
which otherwise halts for good on a database that answers the permanent
error at the very timestamp verification starts from.

The optimistic branch consults the invalidated-output archive first.
op-supernode's OptimisticOutputAtTimestamp asks the deny list's
LastDeniedOutputV0 before the canonical read, so a height whose block was
invalidated and replaced reports the original block's output — what
"optimistically, had verification succeeded" means once a replacement
exists. lokahi's archive (the deny list) existed but the query path never
read it, so it served the replacement's output: the fpp subtests derived
transition states the proof program refutes ("Failed to validate progressed
transition state"), claims meant to be invalid verified clean, and the SP1
runner counted zero optimistic root replacements. The chain's own route
keeps answering without the archive, matching op-node's single-chain
superroot namespace, which consults no deny list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Sebastian Stammler <seb@oplabs.co>
Claude-Session: https://claude.ai/code/session_01WEDeDWcHt2X1mZYhvHmnZ5
CI clippy: needless_pass_by_ref_mut — seed_genesis_safe_head writes only
through the safe-head database handle, never through the controller.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Sebastian Stammler <seb@oplabs.co>
Claude-Session: https://claude.ai/code/session_01WEDeDWcHt2X1mZYhvHmnZ5
CI clippy: doc_markdown on SafeDB and the two fixture timestamps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Sebastian Stammler <seb@oplabs.co>
Claude-Session: https://claude.ai/code/session_01WEDeDWcHt2X1mZYhvHmnZ5
…develop merge)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Sebastian Stammler <seb@oplabs.co>
Claude-Session: https://claude.ai/code/session_01WEDeDWcHt2X1mZYhvHmnZ5
…st too

The base's controller-test cleanup switched the file to the imported name;
the seeding fixture added here kept the fully-qualified path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Sebastian Stammler <seb@oplabs.co>
Claude-Session: https://claude.ai/code/session_01WEDeDWcHt2X1mZYhvHmnZ5
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