ethereum-optimism / ethereum-optimism/optimism

kona-node: L2Finalizer emits finalization targets above the cross-safe head

Open
#22,582 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
6.5k
Forks
4k
Avg merge
2d 18h
Merged PRs (30d)
134

Description

Under interop the cross-safe head lags the local-safe head by design. The derivation actor's finalizer does not know that, and will name finalization targets the engine cannot honour.

**Mechanism.** In `rust/kona/crates/node/service/src/actors/derivation/finalizer.rs`:

- `L2Finalizer::enqueue_for_finalization` records `L1 origin block number -> highest locally derived L2 block number` for each batch of derived attributes. That value comes from local derivation only.
- `L2Finalizer::try_finalize_next(new_finalized_l1_block)` returns the highest such L2 block whose L1 origin is at or below the newly finalized L1 block.

The cross-safe head is never consulted. The target is a function of L1 finality and local derivation alone, so whenever cross-safe verification lags local derivation — the normal interop state — the emitted target sits above the cross-safe head. The derivation actor passes it straight on as `FinalizeBlockId::ByNumber` from the `ProcessFinalizedL1Block` arm in `rust/kona/crates/node/service/src/actors/derivation/actor.rs`.

**Current stopgap.** #22576 makes the engine's finalize task drop such a signal with a `warn!` and return `Ok(())`, instead of failing `Critical` and killing the engine actor. That keeps a routine interop condition off the fatal path, but it is a last line of defence rather than a fix: finality stalls until the next finalized L1 block yields a target that has since been cross-verified, and the log fills with warnings that are expected rather than exceptional.

**What a real fix needs.** The finalizer should bound its target by the cross-safe head, emitting the highest L2 block that is both derived from finalized L1 data and cross-verified, and holding the remainder queued rather than emitting it to be dropped. That requires the derivation actor to observe the cross-safe head, which the interop wiring introduces.

Clamping in the engine instead of dropping is deliberately not the answer: finalization is irreversible, so writing a finalized head chosen from a stale comparison is worse than emitting nothing.

**Scope.** This belongs to the interop work rather than to the phase-1 shared-component refactor — it only becomes live once the interop wiring feeds cross-safe promotions from an external verifier. Related: #22525, #22576.

---
_Generated by [Claude Code](https://claude.ai/code)_

Contributor guide

Open the contributing guide

Research direction

Start in rust/kona/crates/node/service/src/actors/derivation/finalizer.rs and trace how ProcessFinalizedL1Block in rust/kona/crates/node/service/src/actors/derivation/actor.rs receives the cross-safe head through the interop wiring. Check related issues #22525 and #22576 for context. Done means finalization targets are limited to derived, cross-verified L2 blocks, with the remainder held for a later eligible promotion rather than emitted and dropped.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
backend, distributed-systems
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.