Skip to content

Conversation

@pcw109550
Copy link
Member

@pcw109550 pcw109550 commented Nov 24, 2025

Description

This PR introduces a new upstream sync loop (Driver → Engine Controller) that (1) detects L1 reorgs in unsafe-only mode and resets the node appropriately, and (2) optionally mirrors external L2 safe/finalized state when --l2.follow.source is enabled. It replaces the derivation-pipeline-based reorg logic for unsafe-only nodes and ensures L2 always tracks L1 and external safe/finalized sources correctly.

This PR builds on #18290 and implements two features:

  1. Task A – Follow an external L2 source for safe and finalized blocks
    Enabled with --l2.follow.source (requires --l2.unsafe-only).

  2. Task B – Trigger an L2 reset when an L1 reorg occurs
    Enabled with --l2.unsafe-only.

Note: --l2.follow.source can only be used when --l2.unsafe-only is enabled.

A new control-flow path is added:

Driver (op-node/rollup/driver/driver.go) → Engine Controller (op-node/rollup/engine/engine_controller.go)

Driver changes

We need a periodical ticker that is responsible to perform upper two tasks. Similar to the altSyncTicker which periodically tries to close the unsafe gap, I added the upstreamSyncTicker to do perform tasks. op-node/rollup/driver/driver.go. upstreamSyncTicker is only enabled when --l2.unsafe-only is enabled.

The ticker calls followUpstream() method periodically. The method implements Task B and calls Engine Controller to do Task A, by below steps

  1. We do not interfere initial EL sync. Let the CL and EL finish the initial EL Sync.
  2. We first check the L1 reorg by inspecting the unsafe head's L1Origin exists/has valid hash. If not, trigger a reset to fetch valid heads.
  3. (Optional) After the L1 sanity check, if --l2.follow.source is enabled the followUpstream() fetches external L2 info (esafe, efinalized) and tries to apply to the Engine Controller by calling s.SyncDeriver.Engine.FollowSource(eSafe, eFinalized)

Engine Controller Changes

FollowSource(eSafe, eFinalized) is implemented at Engine Controller, implementing Task A. Engine Controller has the responsibility to update its internal state based on the injected external state. The Engine Controller performs mirroring by below steps:

  1. First, we check that the external safe > local unsafe. If it is, we update the local unsafe then FCU it to the EL. By this, we may advance the safe and unsafe head together. The underlying EL may was still performing to the previous local unsafe, and we bump to the EL sync target to external safe. This covers the situation that the CLP2P is down, and op-node is advancing its unsafe head with safe head together. No harm since we must prioritize safe head.
  2. If not, external safe <= local unsafe. In this case, we query the local EL using external safe's number. If the EL Sync is complete, the external safe must be queried, because external safe <= local unsafe. In other words, if the EL sync is complete, op-node's unsafe label and the actual EL's unsafe head must match. We queried the block number before the unsafe head (external safe's block number) so it must be queried.
    • If the external safe block number is not queried, this means the EL is still EL Syncing to the unsafe head. Do not interrupt by NOT bumping the unsafe head of the op-node. In most cases, this EL syncing will complete shortly, because we only apply following source after the initial EL sync is complete (the long EL Sync that must not be interrupted).
    • If the external safe is queried, this means the EL finished EL Syncing, and CL and EL is in sync.
  3. We fetched block from local EL, using the external safe's block number. Now we can compare the external and the local blockref. If those match, consolidate. If not, trigger a reorg, and make local safe and unsafe equal to external safe.

Tests

Four tests are added for testing the --l2.follow.source:

  • TestFollowL2_ReorgRecovery: checks follow source seq / ver reorgs when L1 reorgs
  • TestFollowL2_SafeAndFinalized: happy case, external safe and finalized is mirrored when local unsafe head is ahead of external safe and finalized
  • TestFollowL2_WithoutCLP2P: CLP2P down, so the data source of safe / finalized is only the follow source. Unsafe and safe will advance together.
  • TestSyncTesterFollowL2ReachTips: Using the sync tester, test that the op-node can mirror safe / finalized of op-sepolia.

One test was added to test the --l2.unsafe-only:

  • TestUnsafeOnly_ReorgRecovery: checks unsafe only seq / ver reorgs when L1 reorgs

Additional context

Before this PR, when --l2.unsafe-only is enabled, the node did not L2 reorg when L1 reorg. We now always track the L1 reorg by the newly added upstreamSyncTicker and do a proper L2 reorg via reset.

Metadata

@wearedood

This comment was marked as outdated.

@wearedood

This comment was marked as resolved.

@pcw109550 pcw109550 force-pushed the pcw109550/light-cl-unsafe-only branch from 10f056f to 59214ce Compare November 25, 2025 13:19
@pcw109550 pcw109550 force-pushed the pcw109550/light-cl-follow-source branch from 54fbe8d to 12835bb Compare November 25, 2025 13:24
@pcw109550 pcw109550 marked this pull request as ready for review November 30, 2025 14:44
@pcw109550 pcw109550 requested review from a team as code owners November 30, 2025 14:44
@pcw109550 pcw109550 requested review from sebastianst and removed request for a team November 30, 2025 14:44
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.

4 participants