op-batcher: key the publish gates on batch-auth enforcement, not fork time - #499
op-batcher: key the publish gates on batch-auth enforcement, not fork time#499philippecamacho wants to merge 6 commits into
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 73c383eac2
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d2dd993418
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if l.batchAuthEnforced.Load() { | ||
| return true, nil |
There was a problem hiding this comment.
Re-evaluate enforcement after L1 reorgs
When the L1 head first reaches the enforcement timestamp and then reorgs to a head below it, this cached true bypasses the tip check forever even though the verifier evaluates enforcement independently for each canonical L1 block. With activeIsEspresso set, the fallback batcher therefore remains stopped while the TEE batcher publishes transactions that pre-enforcement derivation rejects because they are not sent by the SystemConfig batcher, stalling safe-head progress until the replacement chain crosses the boundary again. Avoid permanently memoizing a predicate derived from the reorgable L1 tip.
Useful? React with 👍 / 👎.
|
Codex's P1 is right. The publish gate stops consulting So on a default deployment the twenty minutes goes from "TEE publishes, verifiers drop, stall" to "fallback publishes, auth reverts, stall", now with a reverted tx and a cancellation no-op every tick. The gate's own comment says the fallback publishes "without consulting activeIsEspresso, whose default of true would leave no publisher". With that default there's still no publisher. And It only works if Smaller stuff:
One more for the PR body while you're in there: a TEE batcher with |
… time Derivation enforces event-based batch authentication only for L1 blocks with origin time >= EspressoTime + BatchAuthEnforcementDelaySecs; through that grace window it accepts only sender-authenticated batches from the SystemConfig batcher key. The publish gate, however, consulted only BatchAuthenticator.activeIsEspresso (default true), so on the default migration trajectory the TEE batcher published all through the window - batches that mined, burned L1 fees, and were dropped by every verifier - while the fallback batcher, seeing the flag mismatch, stood down. Net effect: up to a full grace window without safe-head progress. shouldSkipPublishForActiveSeq now keys ownership on the verifier's own predicate, derive.IsEspressoAuthEnforced (newly exported), evaluated at the L1 tip: pre-enforcement the fallback publishes unconditionally (the contract is not consulted at all) and the TEE batcher stands down; post-enforcement the activeIsEspresso flag and the sender-identity check decide, exactly as before. Errors keep failing closed. Gating only the TEE side would have left a guaranteed no-publisher gap, since the fallback was standing down on the flag through the window. The auth-dispatch gate (isFallbackAuthRequired) deliberately stays keyed at fork time: the fallback event-authenticates from activation, which is what keeps its batches valid on both sides of the enforcement boundary and makes the tip-time decision safe under inclusion delay. The new gate test pins the whole ownership matrix. Pre-enforcement cases use an L1 mock whose contract backend is nil, so any contract consult panics the test - proving in-window publish decisions are contract- independent. Post-enforcement cases run against a fake backend serving activeIsEspresso/espressoBatcher/systemConfig/batcherHash by selector, covering flag ownership, identity mismatches, and fail-closed paths. Closes #492. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e test Cleanup pass over the publish-gate change: - The gate fetched the L1 tip on every publishStateToL1 iteration, forever, although enforcement is monotone in time. isBatchAuthEnforcedAtTip now memoizes the first true observation (atomic.Bool, same pattern as clearStateRequested), returning the post-enforcement steady state to the contract reads alone - cheaper than the pre-fix gate for both roles - and answers an unscheduled fork without any RPC. The one-time transition is logged at info; the TEE batcher's per-tick in-window deferral drops to debug (it could otherwise repeat for weeks pre-fork). - The test derives method IDs and return words from the abigen bindings (BatchAuthenticatorMetaData/SystemConfigMetaData) instead of hand-typed signature strings and hand-packed 32-byte words, so it cannot drift from the real ABI; this also drops the per-dispatch keccak recomputation. - The role-mirrored test table collapses to one row per scenario with a want column per role, run for both roles - half the fixture duplication, and the pre-enforcement invariant reads directly off the columns. The post-enforcement rows now all pin the exact boundary time instead of a meaningless mix of enforcementTime and enforcementTime+1. - Comment dedup: the ownership rationale lives once, on the gate; the derive export note and the test doc comment now just point at it, and isBatcherActive documents its post-enforcement-only precondition. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same content, 18 lines instead of 26: the ownership rule collapses to one paragraph, keeping only the non-obvious arguments (why the fallback ignores activeIsEspresso pre-enforcement, why the TEE stands down, and why deciding on the L1 tip time is safe across the boundary). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
d2dd993 to
593c55d
Compare
Both deploy scripts initialized activeIsEspresso true, which reverts every fallback auth call through the pre-enforcement grace window and stalls the safe head. Default it false and add a property test over role x region x flag pinning that exactly one batcher can make progress in every cell. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a publish_gate_decision gauge so a batcher standing down can be told apart from a wedged one, throttles the per-tick warnings through degradedLog, warns when a TEE batcher has no espresso_time scheduled, and pins the enforcement memo. Test mocks now fail loudly instead of skipping silently. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Re smaller stuff + PR body: Done in 3a2882a
PR body updated. |
|
P1 issue addressed in eabfd4c: |
Closes #492. Stacked on #459 (
espresso/batcher).Problem
Derivation only enforces event-based batch auth for L1 blocks with origin time >=
EspressoTime + BatchAuthEnforcementDelaySecs(a ~20-minute grace window); inside the window it accepts only sender-authenticated batches from the SystemConfig batcher key. The publish gate ignored that boundary and consulted onlyBatchAuthenticator.activeIsEspresso. So during the window the TEE batcher published batches that mined, burned L1 fees, and were dropped by every verifier, while the fallback batcher stood down on the flag mismatch — up to a full grace window without safe-head progress.Fixing only the publish gate left a second stall on the same path (caught by @palango in review). The fallback's send path authenticates from
EspressoTimeonward, andauthenticateBatchInfoonly accepts the SystemConfig batcher whileactiveIsEspressoisfalse. Both deploy scripts initialized it totrue, so in the window the fallback's auth leg reverted, the reverted leg cancelled the paired batch leg, and the safe head stalled anyway — now with a reverted tx and a nonce-cancellation no-op every tick.Fix
Publish gate. Key both roles' publish decisions on the verifier's own predicate,
derive.IsEspressoAuthEnforced(newly exported), evaluated at the L1 tip:activeIsEspressoplus the sender-identity check decide, exactly as before.isBatcherActive, which still fails closed.The auth-dispatch gate (
isFallbackAuthRequired) deliberately stays keyed at fork time: the fallback event-authenticates through the window, which keeps its batches valid on both sides of the boundary.Deploy default. Both deploy scripts now initialize
activeIsEspressotofalse. This is not a workaround but the invariant the design implies: the fallback must event-authenticate through the window (straddle protection), and the contract only accepts it while the flag is false. Under the oldtruedefault, no deployment — greenfield included — had a working publisher for the first grace window. It also decouples the handoff from the fork, since the fallback keeps event-authenticating past the boundary:setActiveIsEspresso(true)can happen whenever the TEE batcher is ready rather than being an operation timed againstespresso_time. Runbook inop-batcher/readme.md.Observability. A batcher standing down and a wedged one previously looked identical. New
op_batcher_publish_gate_decisiongauge (publishing/awaiting_enforcement/not_active_batcher/boundary_unavailable/active_check_failed) —publishingwith a flatbatch_tx_submittedis the wedge. The per-tick warnings now go throughdegradedLog, which matters because a TEE hot standby is the normal post-fork state under the new default.admin_flushBatcherstill cannot override the gate (that would make a stood-down batcher double-publish or revert), but it now says so instead of silently dropping the request.A TEE batcher with
EspressoTimeunset can never publish, since the boundary is never reached. That was visible only atDebug; it is now a startup warning.Tests
TestShouldSkipPublish_EnforcementBoundary— every boundary scenario for both roles. Pre-enforcement cases run againstnoContractL1Client, which fails the test on any contract read, proving in-window decisions are contract-independent.TestPublishAuthInvariant_RoleTimeFlag— composes the publish gate with the auth-dispatch gate across role x tip region x flag, asserting that exactly one role publishes and its batch lands. The single excluded cell (grace window with the flag set) is asserted as a stall, recording the dependency on the deploy default. Reverting either gate fix fails it.TestIsBatchAuthEnforcedAtTip_Memoization— the memo's observable effects: one tip fetch in steady state, a tip going backwards staying enforced, and the fail-closed change once latched.Contract-side,
BatchAuthenticator.t.solalready pins the revert the excluded cell depends on.🤖 Generated with Claude Code