fix(taiko-client-rs): abort event sync when the execution engine rewinds below derived state - #22004
fix(taiko-client-rs): abort event sync when the execution engine rewinds below derived state#22004davidtaikocha wants to merge 4 commits into
Conversation
…nds below derived state After an unclean execution-engine restart (e.g. OOMKill), the engine can rewind below blocks derivation already produced while head_l1_origin still points above the head. The running event syncer then retries the same proposal forever with BlockUnavailable(parent): the missing parent can never reappear on its own because event derivation itself is the only producer of that range, and the safe resume-head resolution only runs at driver startup. The node silently freezes until an operator restarts the pod (hoodi l2-node-reth-0 incidents on 2026-08-02 and 2026-08-07, ~4h+ of frozen head each). Probe the execution head when a proposal fails with BlockUnavailable. Three consecutive observations of the missing block sitting strictly above the live head classify the failure as a rewind and abort the event-sync run instead of retrying. The runner already treats an event-syncer exit as fatal, so the driver restarts and re-resolves a safe resume head (checkpoint-synced, or min(head, head_l1_origin)), which re-derives the lost range — exactly the manual pod-restart remedy, automated, and without touching the execution engine container. A failed head probe or a missing block at/below the head keeps the ordinary retry and canonicality classification in charge. Adds the driver_event_execution_rewind_aborts_total counter. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
🐋 DeepSeek Code Review🔴 Critical IssuesNone. 🟡 Warnings
🔵 Suggestions
🟢 What Looks Good
Automatically triggered on PR update • model: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d406fb0b74
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…hans, and mixed evidence Address three review findings on the execution-rewind escape hatch: - decode_log_to_event_context reused BlockUnavailable to carry an L1 block number when the proposal's source block is missing from the L1 provider view, so on chains where L1 height exceeds the L2 head three transient L1 misses would terminate the driver in a restart loop. Introduce DerivationError::SourceBlockUnavailable for that path; the rewind probe only ever sees L2 numbers now. - The probe ran before proposal_log_canonicality, so a finalized-orphan log whose derivation also reported an above-head parent would abort the driver instead of being skipped. Orphanhood now settles first; rewind evidence only accumulates on non-orphaned logs. - The streak counter ignored WHICH block was missing, letting observations of different blocks pool toward the threshold. Replace it with an identity-keyed RewindStreak: a different missing block restarts the count. A failed head probe still deliberately leaves the streak intact — a flaky probe must not indefinitely postpone escape from a real rewind. New coverage: finalized orphan is skipped (not escalated), missing L1 source blocks keep retrying past the threshold, and streak identity semantics. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The bug
After an unclean execution-engine restart (OOMKill), reth rewinds to its last persisted block, losing the in-memory tail — while
head_l1_originstill points above the new head. The running event syncer then retries the same proposal forever:The missing parent can never reappear on its own — event derivation is the only producer of that range and it is stuck behind this proposal; gossip drops the gap blocks as stale, and the safe resume-head resolution (
resolve_resume_head_block_number, checkpoint path) only runs at driver startup. The node freezes silently (and keeps reporting Ready) until an operator restarts the pod.Two production incidents on hoodi
l2-node-reth-0: 2026-08-02 (~14.5h frozen) and 2026-08-07 (~4.5h frozen, chain head alert), both ended by a manual pod delete. The rewind trigger (an alethia-reth memory leak on sub-5-CPU nodes) is being removed by taikoxyz/k8s-configs#1478, but any future EL rewind under a running driver reproduces the freeze.The fix
When a proposal fails with
BlockUnavailable(n), probe the execution head. Three consecutive observations ofnstrictly above the live head classify the failure as a rewind: abort the event-sync run (SyncError::ExecutionEngineRewound) instead of retrying. The whitelist runner already treats an event-syncer exit as fatal, so the driver process exits and restarts (k8s restarts only the driver container — the EL is untouched), re-resolves a safe resume head (checkpoint-synced, ormin(head, head_l1_origin)), and re-derives the lost range. This is exactly the manual remedy from both incidents, automated.Deliberately out of scope: re-resolving the resume head in-place. Startup resolution is the single tested authority for choosing a safe resume point; duplicating it mid-run would fork that logic. The abort-and-restart path reuses it wholesale.
Conservative by construction:
BlockUnavailableat/below the head is not a rewind → streak resets, existing retry/canonicality classification untouched;Adds
driver_event_execution_rewind_aborts_total.Tests
TDD: the abort test was written first and failed on
mainby looping exactly like production (virtual-clock timeout with the same retry warns), then the fix made it pass.process_log_batch_aborts_after_persistent_block_unavailable_above_execution_head— bounded abort at the threshold withExecutionEngineRewound { missing_block, execution_head }process_log_batch_keeps_retrying_block_unavailable_at_or_below_execution_head— boundary guard (not a rewind → retry then succeed)process_log_batch_keeps_retrying_when_execution_head_probe_fails— missing evidence must not abortcargo test -p driver --lib: 129 passed. Both CI clippy passes clean; nightly rustfmt applied.🤖 Generated with Claude Code