ethereum-optimism / ethereum-optimism/optimism
kona-node: L2Finalizer emits finalization targets above the cross-safe head
- 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
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