Skip to content
This repository was archived by the owner on Jan 16, 2026. It is now read-only.

Comments

feat(node): Kona Light CL - First iteration#3199

Closed
pcw109550 wants to merge 11 commits intomainfrom
pcw109550/kona-light-cl
Closed

feat(node): Kona Light CL - First iteration#3199
pcw109550 wants to merge 11 commits intomainfrom
pcw109550/kona-light-cl

Conversation

@pcw109550
Copy link
Contributor

@pcw109550 pcw109550 commented Dec 16, 2025

Light CL

Public Design doc at ethereum-optimism/design-docs#359. The PR description may overlap, but more implementation oriented.

Summary

This PR implements "follow mode" for the Kona rollup node, enabling it to sync safe/finalized L2 heads + CurrentL1 view from an external L2 Consensus Layer (CL) node instead of deriving them from L1 data. This provides a "light CL" mode that trusts an external source while maintaining local EL execution.

CLI Flag: --l2.follow.source <URL> or Env: KONA_NODE_L2_FOLLOW_SOURCE=<URL>

Architecture Changes

Component Flow in Follow Mode

When follow mode is enabled (--l2.follow.source provided):

  1. Rollup Node Service orchestrates and spawns Follow Actor (Derivation Actor not created)
  2. Engine Actor completes initial EL sync
  3. Engine Actor signals Follow Actor that initial EL sync is complete
  4. Follow Actor starts polling External L2 CL for sync status via optimism_syncStatus RPC (every 2 seconds)
  5. Follow Actor validates L1 block canonicality by checking against L1 EL RPC
  6. Follow Actor sends validated FollowStatus to Engine Actor
  7. L2 Sequencer Gossip continues to send unsafe blocks to Engine Actor (P2P remains active)

Key Architectural Points

  1. New Follow Actor: Added as a new actor in the Rollup Node Service

    • Waits for initial EL sync signal before starting polls (mirrors DerivationActor pattern)
    • Polls external L2 CL source at 2-second intervals after initial EL sync completes
    • Validates L1 blocks(L1 Origins) are canonical before triggering updates
  2. Bidirectional Communication with Engine Actor:

    • Engine → Follow: Notifies when initial EL sync completes (via el_sync_complete signal)
    • Follow → Engine: Sends FollowStatus with external safe/finalized heads
  3. Compatible with Sequencer Mode: A sequencer can use follow mode for safe/finalized heads while still producing unsafe blocks

Implementation Details

Core Components

1. FollowActor (crates/node/service/src/actors/follow/actor.rs)

  • Gates on initial EL sync signal before starting polls
  • Polls external L2 CL via optimism_syncStatus RPC
  • Validates L1 block canonicality (checks external L1 origins against local L1 chain)
  • Sends validated FollowStatus to Engine Actor

2. FollowTask (crates/node/engine/src/task_queue/tasks/follow/task.rs)

  • New engine task type for processing external sync status
  • Calls SynchronizeTask to apply safe/finalized head updates
  • Priority: Higher than Consolidate/Finalize, lower than Insert (unsafe blocks). Consolidate/Finalize will never be scheduled when follow mode.

3. 5-Case Follow Source Algorithm (crates/node/service/src/actors/engine/actor.rs:790-880)

  • Case 1: External safe ahead of local unsafe → full update (sets unsafe = external safe)
  • Case 2a: Block not found locally → safe-only update (EL still syncing)
  • Case 2b: Query error → skip update
  • Case 3: Hashes match → consolidation (safe-only update)
  • Case 4: Hash mismatch → reorg detected (full update)

4. Channel Wiring (crates/node/service/src/service/node.rs:154-174)

  • In follow mode: Creates dummy channels for derivation, routes el_sync_complete_rx to FollowActor
  • In normal mode: Creates DerivationActor with standard channels

Components in Follow Mode

Component Status Behavior
DerivationActor Not created Disabled when follow enabled
FollowActor Active Polls external source after EL sync, validates, sends status
Engine Actor Active Processes FollowTask, updates state
L2Finalizer Inactive Queue stays empty (no derived attributes), harmless no-op
NetworkActor Active Receives unsafe blocks via P2P gossip
SequencerActor Optional Can produce unsafe blocks when in sequencer mode

L2Finalizer Behavior

The L2Finalizer component becomes inactive in follow mode but remains safely enabled:

  • Normal mode: Finalizer tracks derived L2 blocks and finalizes them when L1 finalizes
  • Follow mode: Finalizer receives L1 updates but has nothing to finalize (queue empty, enqueue_for_finalization() never called)
  • Result: Component is inactive but harmless, finalization handled by FollowTask instead

Testing

Commit a50256c is baked to dev image us-docker.pkg.dev/oplabs-tools-artifacts/dev-images/kona-node:a50256c-light-cl and running as a

  • Replica with follow source enabled
  • Sequencer as a leader with follow source enabled.

Note: rebased so relevant commit is not included at this PR's commit trail.

Acceptance Tests:

  • TestFollowL2_Safe_Finalized_CurrentL1
  • TestFollowL2_WithoutCLP2P
  • TestFollowL2_ReorgRecovery

For running the existing monorepo acceptance tests, build kona-node at place it at /tmp/kona-node. Then clone monorepo(Patch https://github.com/ethereum-optimism/optimism/tree/pcw109550/temp-kona-light-cl-acceptance-test-monkey-patch) and cd op-acceptance-tests/tests/sync/follow_l2. Run

DEVSTACK_L2CL_KIND=kona RUST_BINARY_PATH_KONA_NODE=/tmp/kona-node go test ./... -run ^[testname]$   -count=1 -v 

TestFollowL2_ReorgRecovery fails:

For op-node:

  1. op-node Sequencer light CL triggers a reset because it detected L1 reorg while block building.
  2. This is notified to the batcher, and batcher will re-batch the new L2 blocks and write to L1 which becomes canonical L2 safe blocks
  3. op-node Verifier Light CL disabled will derive as usual, also triggering reset because it detects L1 reorg inside derivation pipeline.
  4. op-node Sequencer Light CL enabled will consolidate because it already reset.
  5. op-node Verifier Light CL enabled will reorg because the external source experienced L2 reorg.

For kona-node(which makes TestFollowL2_ReorgRecovery not pass)

  1. kona-node sequencer Light CL does not reorg because its derivation pipeline is disabled, not emitting ResetError::ReorgDetected.
  2. kona-node verifier Light CL disabled will derive as usual, also triggering reset because it detects L1 reorg inside derivation pipeline.
  3. kona-node sequencer Light CL did not reorg, but keep building with invalid L2 blocks.
  4. Leading safe head to stall.

We may explicitly detect L1 reorg at follow actor and trigger a explicit reset resulting in reorg. Need to find out why there is this difference at kona node, and implement a fix at follow up PR.

All the happy case acceptance tests are passing.

Known Limitations

current_l1 not mirrored: External current_l1 not propagated to local state

  • Blocker: Pre-existing issue with current_l1 tracking (documented in current_l1.md)
  • Plan: Fix in separate PR, then integrate with follow mode

L2 Reorg not working: Light CL kona sequencer does not trigger L2 reorg when L1 reorg. Causing TestFollowL2_ReorgRecovery acceptance test to fail.

  • Blocker: Find out the reason why op-node has L1 reorg detection at block building, but not at kona node.
  • Plan: Fix in separate PR.

@pcw109550 pcw109550 added the M-do-not-merge Meta: Do not merge label Dec 16, 2025
@wiz-b4c72f16a4
Copy link

wiz-b4c72f16a4 bot commented Dec 16, 2025

Wiz Scan Summary

Scanner Findings
Vulnerability Finding Vulnerabilities 1 Low
Data Finding Sensitive Data -
Secret Finding Secrets -
IaC Misconfiguration IaC Misconfigurations -
SAST Finding SAST Findings -
Software Supply Chain Finding Software Supply Chain Findings -
Total 1 Low

View scan details in Wiz

To detect these findings earlier in the dev lifecycle, try using Wiz Code VS Code Extension.

@codecov
Copy link

codecov bot commented Dec 16, 2025

Codecov Report

❌ Patch coverage is 0% with 194 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.7%. Comparing base (fe6dfcf) to head (c626ee7).
⚠️ Report is 9 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
crates/node/service/src/actors/follow/actor.rs 0.0% 68 Missing ⚠️
crates/node/service/src/service/node.rs 0.0% 48 Missing ⚠️
crates/node/service/src/actors/engine/actor.rs 0.0% 41 Missing ⚠️
crates/node/service/src/follow.rs 0.0% 20 Missing ⚠️
crates/node/engine/src/task_queue/tasks/task.rs 0.0% 8 Missing ⚠️
...s/node/engine/src/task_queue/tasks/follow/error.rs 0.0% 4 Missing ⚠️
crates/node/service/src/service/builder.rs 0.0% 3 Missing ⚠️
...es/node/engine/src/task_queue/tasks/follow/task.rs 0.0% 2 Missing ⚠️

❗ There is a different number of reports uploaded between BASE (fe6dfcf) and HEAD (c626ee7). Click for more details.

HEAD has 23 uploads less than BASE
Flag BASE (fe6dfcf) HEAD (c626ee7)
proof 11 0
e2e 11 0
unit 2 1

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@pcw109550 pcw109550 marked this pull request as ready for review December 16, 2025 11:56
@pcw109550 pcw109550 force-pushed the pcw109550/kona-light-cl branch from 99c207f to 6ed2a49 Compare December 16, 2025 11:57
@pcw109550 pcw109550 marked this pull request as draft December 16, 2025 12:10
@pcw109550 pcw109550 changed the title feat(node): Kona Light CL PoC feat(node): Kona Light CL Dec 17, 2025
@pcw109550 pcw109550 removed the M-do-not-merge Meta: Do not merge label Dec 17, 2025
@pcw109550 pcw109550 force-pushed the pcw109550/kona-light-cl branch from befcf52 to c626ee7 Compare December 19, 2025 12:38
@pcw109550 pcw109550 marked this pull request as ready for review December 19, 2025 14:20
Copy link
Collaborator

@op-will op-will left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

I left a few comments that lead to the question "are we doing something entirely new, or do we now have multiple ways to achieve the same thing?"

If the latter, and there's enough in common, we may opt to unify some interfaces. That said, I have not fully considered how this evolves in the future, so I may be missing something obvious that indicates that they should be kept separate.

Comment on lines +135 to +142
/// L2 follow client arguments.
#[derive(Clone, Debug, Default, clap::Args)]
pub struct FollowClientArgs {
/// URL of the L2 follow source RPC API.
/// The source must be the L2 CL RPC.
#[arg(long, visible_alias = "l2.follow.source", env = "KONA_NODE_L2_FOLLOW_SOURCE")]
pub l2_follow_source: Option<Url>,
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// L2 follow client arguments.
#[derive(Clone, Debug, Default, clap::Args)]
pub struct FollowClientArgs {
/// URL of the L2 follow source RPC API.
/// The source must be the L2 CL RPC.
#[arg(long, visible_alias = "l2.follow.source", env = "KONA_NODE_L2_FOLLOW_SOURCE")]
pub l2_follow_source: Option<Url>,
}
/// L2 derivation delegate connection arguments.
#[derive(Clone, Debug, Default, clap::Args)]
pub struct DerivationDelegateArgs {
/// URL of the L2 derivation delegate RPC API.
/// The url must be the L2 CL RPC.
#[arg(long, visible_alias = "l2.follow.url", env = "KONA_NODE_L2_DERIVATION_DELEGATE_URL")]
pub l2_derivation_delegate_url: Option<Url>,
}

/// This struct contains a subset of fields from [`kona_protocol::SyncStatus`],
/// focusing only on the fields needed for following another rollup node.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FollowStatus {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub struct FollowStatus {
pub struct DerivationStatus {

Comment on lines +50 to +83
/// Follow client trait for querying sync status from an L2 consensus layer RPC.
///
/// This trait defines the interface for communicating with another rollup node's
/// consensus layer to fetch its synchronization status and querying L1 blocks.
/// The main reason this trait exists is for mocking and unit testing.
#[async_trait]
pub trait FollowClient: Send + Sync {
/// Gets the synchronization status from the follow source.
///
/// Calls the `optimism_syncStatus` RPC method on the remote rollup node,
/// extracts the essential fields, and returns a simplified status.
///
/// # Returns
///
/// Returns the [`FollowStatus`] containing only the current L1, safe L2,
/// and finalized L2 blocks from the remote rollup node, or an error if
/// the RPC call fails.
async fn get_follow_status(&self) -> Result<FollowStatus, FollowClientError>;

/// Fetches the L1 [`BlockInfo`] by block number.
///
/// Queries the L1 execution layer for the block at the given number and
/// returns the block information.
///
/// # Arguments
///
/// * `number` - The L1 block number to fetch
///
/// # Returns
///
/// Returns the [`BlockInfo`] for the requested L1 block, or an error if
/// the block cannot be fetched or does not exist.
async fn l1_block_info_by_number(&self, number: u64) -> Result<BlockInfo, FollowClientError>;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're not using this client for sync status (which is ambiguous), but L2 derivation status, right? Would DerivationDelegate + DerivationStatus be more accurate names? If a developer reads FollowClient, they might not have an understanding of what it does and why, whereas DerivationDelegate says exactly what we're doing (delegating derivation to a 3rd party), and it is clear that we should use this trait to get info on the status of L2 derivation.

Just commenting here, but all of the other Follow* cases would need to be updated as well.

Suggested change
/// Follow client trait for querying sync status from an L2 consensus layer RPC.
///
/// This trait defines the interface for communicating with another rollup node's
/// consensus layer to fetch its synchronization status and querying L1 blocks.
/// The main reason this trait exists is for mocking and unit testing.
#[async_trait]
pub trait FollowClient: Send + Sync {
/// Gets the synchronization status from the follow source.
///
/// Calls the `optimism_syncStatus` RPC method on the remote rollup node,
/// extracts the essential fields, and returns a simplified status.
///
/// # Returns
///
/// Returns the [`FollowStatus`] containing only the current L1, safe L2,
/// and finalized L2 blocks from the remote rollup node, or an error if
/// the RPC call fails.
async fn get_follow_status(&self) -> Result<FollowStatus, FollowClientError>;
/// Fetches the L1 [`BlockInfo`] by block number.
///
/// Queries the L1 execution layer for the block at the given number and
/// returns the block information.
///
/// # Arguments
///
/// * `number` - The L1 block number to fetch
///
/// # Returns
///
/// Returns the [`BlockInfo`] for the requested L1 block, or an error if
/// the block cannot be fetched or does not exist.
async fn l1_block_info_by_number(&self, number: u64) -> Result<BlockInfo, FollowClientError>;
}
/// Derivation delegate trait for querying derivation status from an L2 consensus layer RPC.
///
/// This trait defines the interface for communicating with another rollup node's
/// consensus layer to fetch its derivation status and querying L1 blocks.
/// The main reason this trait exists is for mocking and unit testing.
#[async_trait]
pub trait DerivationDelegate: Send + Sync {
/// Gets the derivation status from the configured delegate.
///
/// Calls the `optimism_syncStatus` RPC method on the remote rollup node,
/// extracts the essential fields, and returns a simplified status.
///
/// # Returns
///
/// Returns the [`DerivationStatus`] containing only the current L1, safe L2,
/// and finalized L2 blocks from the remote rollup node, or an error if
/// the RPC call fails.
async fn get_derivation_status(&self) -> Result<DerivationStatus, DerivationDelegateError>;
/// Fetches the L1 [`BlockInfo`] by block number.
///
/// Queries the L1 execution layer for the block at the given number and
/// returns the block information.
///
/// # Arguments
///
/// * `number` - The L1 block number to fetch
///
/// # Returns
///
/// Returns the [`BlockInfo`] for the requested L1 block, or an error if
/// the block cannot be fetched or does not exist.
async fn l1_block_info_by_number(&self, number: u64) -> Result<BlockInfo, DerivationDelegateError>;
}

Comment on lines +89 to +92
pub l2_url: Url,
/// The L1 execution layer RPC URL.
pub l1_url: Url,
/// The timeout duration for requests.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: The name can indicate all info from the comment above it.

Suggested change
pub l2_url: Url,
/// The L1 execution layer RPC URL.
pub l1_url: Url,
/// The timeout duration for requests.
pub l2_cl_rpc_url: Url,
/// The L1 execution layer RPC URL.
pub l1_el_rpc_url: Url,
/// The timeout duration for requests.

Comment on lines +69 to +82
/// Fetches the L1 [`BlockInfo`] by block number.
///
/// Queries the L1 execution layer for the block at the given number and
/// returns the block information.
///
/// # Arguments
///
/// * `number` - The L1 block number to fetch
///
/// # Returns
///
/// Returns the [`BlockInfo`] for the requested L1 block, or an error if
/// the block cannot be fetched or does not exist.
async fn l1_block_info_by_number(&self, number: u64) -> Result<BlockInfo, FollowClientError>;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to bundle this into FollowClient?

It makes it seem like we're fetching the L1 block from the 3rd party that we trust for derivation, when we never should be. It would probably be clearer to make FollowActor generic over Provider and inject a RootProvider into it. Otherwise, it looks like we're asking the follow_client to validate data that it gave us, rather than obviously validating it using our own sources:

async fn validate_l1_block(
&self,
number: u64,
hash: alloy_primitives::B256,
) -> Result<bool, FollowActorError> {
// Fetch the canonical block at this number from L1
let canonical_block = self
.follow_client
.l1_block_info_by_number(number)
.await
.map_err(|e| FollowActorError::L1ValidationError(e.to_string()))?;
// Compare hashes
Ok(canonical_block.hash == hash)
}
}

state.engine.enqueue(task);
}
attributes = self.attributes_rx.recv() => {
Some(attributes) = OptionFuture::from(self.attributes_rx.as_mut().map(|rx| rx.recv())), if self.attributes_rx.is_some() => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if it makes sense to try to unify this with the follow logic since conceptually they're both advancing the unsafe head?

See this interface question and this task question. If so, we can leave finalization as is (see this)

@pcw109550 pcw109550 changed the title feat(node): Kona Light CL feat(node): Kona Light CL - First iteration Jan 9, 2026
@pcw109550
Copy link
Contributor Author

Closing due to the re-design. Following ethereum-optimism/design-docs#359

@pcw109550 pcw109550 closed this Jan 9, 2026
@pcw109550 pcw109550 mentioned this pull request Jan 12, 2026
3 tasks
github-merge-queue bot pushed a commit that referenced this pull request Jan 14, 2026
~Base branch: #3242

~May conflict with #3229,
#3253

Implement the kona light CL([Design
doc](https://github.com/ethereum-optimism/design-docs/blob/main/protocol/kona-node-light-cl.md)):
- [DerivationActor - Target
Determination](https://github.com/ethereum-optimism/design-docs/blob/main/protocol/kona-node-light-cl.md#derivationactor---target-determination)
- [EngineActor - Fork Choice
Update](https://github.com/ethereum-optimism/design-docs/blob/main/protocol/kona-node-light-cl.md#engineactor---fork-choice-update)

```mermaid
flowchart TB

subgraph A ["Normal Mode (Derivation)"]
  direction TB

  subgraph A0 ["Rollup Node Service"]
    direction TB
    A_Derivation["DerivationActor<br/>(L1->L2 derivation)"]
    A_Engine["EngineActor"]
    A_UnsafeSrc["Unsafe Source<br/>(P2P gossip / Sequencer)"]
  end

  A_L1[(L1 RPC)]
  A_EL[(Execution Layer)]

  A_L1 -->|L1 info| A_Derivation
  A_UnsafeSrc -->|unsafe| A_Engine
  A_Derivation -->|"safe(attr)/finalized"| A_Engine
  A_Engine -->|engine API| A_EL
end

subgraph B ["Light CL Mode"]
  direction TB

  subgraph B0 ["Rollup Node Service"]
    direction TB
    B_DerivationX[["DerivationActor<br/>(NEW: Poll external syncStatus)"]]
    B_Engine["EngineActor"]
    B_UnsafeSrc["Unsafe Source<br/>(P2P gossip / Sequencer)"]
  end

  B_L1[(L1 RPC)]
  B_Ext[(External CL RPC<br/>optimism_syncStatus)]
  B_EL[(Execution Layer)]

  %% Connections
  B_Ext -->|safe/finalized/currentL1| B_DerivationX
  B_L1 -->|canonical L1 check| B_DerivationX
  B_DerivationX -->|"safe(blockInfo)/finalized (validated)"| B_Engine
  B_UnsafeSrc -->|unsafe| B_Engine

  %% Visual indicator for disabled actor
  B_Engine -->|engine API| B_EL
end
```

### Testing

#### Acceptance Tests

Running guidelines detailed at #3199:

- [x] `TestFollowL2_Safe_Finalized_CurrentL1`
- [x] `TestFollowL2_WithoutCLP2P`
- [ ] `TestFollowL2_ReorgRecovery` (blocked by [kona: Check L2 reorg due
to L1
reorg](ethereum-optimism/optimism#18676))

Injecting CurrentL1 is blocked by [kona: Revise SyncStatus CurrentL1
Selection](ethereum-optimism/optimism#18673)

#### Local Sync Tests

Validated with syncing op-sepolia between kona-node light CL <> sync
tester, successfully finishing the initial EL sync and progress every
safety levels reaching each tip.

#### Devnet Tests

Commit
0b36fdd
is baked to
`us-docker.pkg.dev/oplabs-tools-artifacts/dev-images/kona-node:0b36fdd-light-cl`
and deployed at `changwan-0` devnet:
- As a verifier: `changwan-0-kona-geth-f-rpc-3`
[[grafana]](https://optimistic.grafana.net/d/nUSlc3d4k/bedrock-networks?orgId=1&refresh=30s&from=now-1h&to=now&timezone=browser&var-network=changwan-0&var-node=$__all&var-layer=$__all&var-safety=l2_finalized&var-cluster=$__all&var-konaNodes=changwan-0-kona-geth-f-rpc-3)
- As a sequencer: `changwan-0-kona-geth-f-sequencer-3`
[[grafana]](https://optimistic.grafana.net/d/nUSlc3d4k/bedrock-networks?orgId=1&refresh=30s&from=now-1h&to=now&timezone=browser&var-network=changwan-0&var-node=$__all&var-layer=$__all&var-safety=l2_finalized&var-cluster=$__all&var-konaNodes=changwan-0-kona-geth-f-sequencer-3)
    - As a standby | leader

Noticed all {unsafe, safe, finalized} head progression as a kona node
light CL.
theochap pushed a commit to ethereum-optimism/optimism that referenced this pull request Jan 15, 2026
~Base branch: op-rs/kona#3242

~May conflict with op-rs/kona#3229,
op-rs/kona#3253

Implement the kona light CL([Design
doc](https://github.com/ethereum-optimism/design-docs/blob/main/protocol/kona-node-light-cl.md)):
- [DerivationActor - Target
Determination](https://github.com/ethereum-optimism/design-docs/blob/main/protocol/kona-node-light-cl.md#derivationactor---target-determination)
- [EngineActor - Fork Choice
Update](https://github.com/ethereum-optimism/design-docs/blob/main/protocol/kona-node-light-cl.md#engineactor---fork-choice-update)

```mermaid
flowchart TB

subgraph A ["Normal Mode (Derivation)"]
  direction TB

  subgraph A0 ["Rollup Node Service"]
    direction TB
    A_Derivation["DerivationActor<br/>(L1->L2 derivation)"]
    A_Engine["EngineActor"]
    A_UnsafeSrc["Unsafe Source<br/>(P2P gossip / Sequencer)"]
  end

  A_L1[(L1 RPC)]
  A_EL[(Execution Layer)]

  A_L1 -->|L1 info| A_Derivation
  A_UnsafeSrc -->|unsafe| A_Engine
  A_Derivation -->|"safe(attr)/finalized"| A_Engine
  A_Engine -->|engine API| A_EL
end

subgraph B ["Light CL Mode"]
  direction TB

  subgraph B0 ["Rollup Node Service"]
    direction TB
    B_DerivationX[["DerivationActor<br/>(NEW: Poll external syncStatus)"]]
    B_Engine["EngineActor"]
    B_UnsafeSrc["Unsafe Source<br/>(P2P gossip / Sequencer)"]
  end

  B_L1[(L1 RPC)]
  B_Ext[(External CL RPC<br/>optimism_syncStatus)]
  B_EL[(Execution Layer)]

  %% Connections
  B_Ext -->|safe/finalized/currentL1| B_DerivationX
  B_L1 -->|canonical L1 check| B_DerivationX
  B_DerivationX -->|"safe(blockInfo)/finalized (validated)"| B_Engine
  B_UnsafeSrc -->|unsafe| B_Engine

  %% Visual indicator for disabled actor
  B_Engine -->|engine API| B_EL
end
```

### Testing

#### Acceptance Tests

Running guidelines detailed at op-rs/kona#3199:

- [x] `TestFollowL2_Safe_Finalized_CurrentL1`
- [x] `TestFollowL2_WithoutCLP2P`
- [ ] `TestFollowL2_ReorgRecovery` (blocked by [kona: Check L2 reorg due
to L1
reorg](#18676))

Injecting CurrentL1 is blocked by [kona: Revise SyncStatus CurrentL1
Selection](#18673)

#### Local Sync Tests

Validated with syncing op-sepolia between kona-node light CL <> sync
tester, successfully finishing the initial EL sync and progress every
safety levels reaching each tip.

#### Devnet Tests

Commit
op-rs/kona@0b36fdd
is baked to
`us-docker.pkg.dev/oplabs-tools-artifacts/dev-images/kona-node:0b36fdd-light-cl`
and deployed at `changwan-0` devnet:
- As a verifier: `changwan-0-kona-geth-f-rpc-3`
[[grafana]](https://optimistic.grafana.net/d/nUSlc3d4k/bedrock-networks?orgId=1&refresh=30s&from=now-1h&to=now&timezone=browser&var-network=changwan-0&var-node=$__all&var-layer=$__all&var-safety=l2_finalized&var-cluster=$__all&var-konaNodes=changwan-0-kona-geth-f-rpc-3)
- As a sequencer: `changwan-0-kona-geth-f-sequencer-3`
[[grafana]](https://optimistic.grafana.net/d/nUSlc3d4k/bedrock-networks?orgId=1&refresh=30s&from=now-1h&to=now&timezone=browser&var-network=changwan-0&var-node=$__all&var-layer=$__all&var-safety=l2_finalized&var-cluster=$__all&var-konaNodes=changwan-0-kona-geth-f-sequencer-3)
    - As a standby | leader

Noticed all {unsafe, safe, finalized} head progression as a kona node
light CL.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants