Skip to main content
The kona-engine crate provides a modular execution engine implementation for the OP Stack rollup node. It serves as the bridge between the rollup protocol and the execution layer (EL), managing Engine API interactions through a sophisticated task queue system.

Architecture Overview

The execution engine is built around several key components:
  • Engine Task Queue: A priority-ordered queue that manages Engine API operations
  • Trait Abstractions: Extensible interfaces for tasks, errors, and state management
  • Engine Client: HTTP client for communicating with the execution layer
  • Actor Integration: Service layer integration through the EngineActor

Core Trait Abstractions

ExecutableEngineTask

The ExecutableEngineTask trait defines the interface for all engine tasks:
This trait enables:
  • Atomic operations over the EngineState
  • Extensible task implementation for custom operations
  • Async execution with proper error handling

EngineTaskError

The EngineTaskError trait provides sophisticated error handling with severity levels:
This allows tasks to signal different recovery strategies based on the error type.

Task Queue System

The engine uses a priority-based task queue (a binary heap ordered by the EngineTask Ord implementation) where tasks are ordered according to OP Stack synchronization requirements. Tasks use the concrete EngineClient, whose Alloy providers share their RPC connections. Tests supply mock transports to the same client.

Task Priority (Highest to Lowest)

  1. Canonicalize - Imports sealed blocks after their conductor commit (sequencer mode)
  2. Seal - Seals blocks that have finished building (sequencer mode)
  3. Build - Builds new blocks (sequencer mode)
  4. Insert - Inserts unsafe blocks from gossip
  5. Consolidate - Advances safe chain via derivation
  6. Finalize - Finalizes L2 blocks
Only the queue executes tasks. A task never runs another task: the steps that several tasks share, such as starting a build, fetching a built payload, inserting a payload, and synchronizing forkchoice, are plain functions that the tasks call. Forkchoice updates are not a standalone queue entry. Whenever a task moves the unsafe, safe, or finalized head, it calls the shared synchronize step, which sends engine_forkchoiceUpdated without payload attributes and tracks whether the execution layer has finished syncing. Its failures are reported as SynchronizeTaskError.

Task Types

BuildTask

Starts building a new block in sequencer mode, producing a payload ID:
Rejects sequencer builds whose parent no longer matches the unsafe head before calling engine_forkchoiceUpdated. Build failures reach the sequencer so it can retry with fresh attributes; invalid forkchoice errors also trigger engine recovery. Derivation calls the shared build helper directly and may build on a different parent during a reorg.

SealTask

Seals a block started by a BuildTask in sequencer mode. It checks that the build’s parent is still the unsafe head and retrieves the payload with version-specific engine_getPayload calls. It does not change forkchoice: the sequencer commits the payload to the conductor before a CanonicalizeTask imports it.

CanonicalizeTask

Imports a sealed block in sequencer mode once the sequencer has committed it to the conductor, making it the new unsafe head. The conductor commit happens outside the engine queue, so the task first checks that the block’s parent is still the unsafe head. A rejected block is not replaced with a deposits-only block, because a replacement would need its own conductor commit. The new unsafe head is published before the result is returned to the sequencer, which then gossips the block.

InsertTask

Inserts payloads (for example unsafe blocks received from gossip) into the execution engine:

ConsolidateTask

Advances the safe chain through derivation:
If consolidation fails, the task builds a block from the attributes itself, seals it, and imports it as the new safe head. Post-Holocene, if the execution layer rejects that block, it is replaced with a deposits-only block and the derivation pipeline’s current channel is flushed.

FinalizeTask

Finalizes L2 blocks:

Engine State Management

The EngineState tracks the current state of the execution engine:
The unsafe, safe, and finalized heads live in the nested EngineSyncState. State updates are communicated through watch channels, enabling reactive programming patterns across the system.

Integration with kona-node

The kona-node service layer integrates the engine through the EngineActor:

Actor Pattern

The EngineActor implements the NodeActor trait:

Communication Channels

The EngineActor receives all state-mutating input through a single inbound request channel of EngineActorRequest messages: payload attributes from derivation, unsafe blocks from gossip, reset requests, finalization requests, and block building requests (sequencer mode only). The RollupRpc module serves read-only RPC requests directly. It shares the engine’s L2 connection through an EngineQueryClient that exposes only output_at_block, and reads engine state through a watch channel.

Rollup RPC Reads

RollupRpc clones the rollup configuration for optimism_rollupConfig, combines the engine-state snapshot with the L1 watcher state for optimism_syncStatus, and calls EngineQueryClient::output_at_block for optimism_outputAtBlock. These reads run in jsonrpsee request handlers, so a slow output-root query does not block other engine-state reads.

Usage Patterns

Basic Engine Setup

Adding Tasks

Draining the Queue

Error Handling and Recovery

The engine provides robust error handling through:

Severity-Based Recovery

  • Temporary errors: Automatically retried
  • Critical errors: Propagated to the actor
  • Reset errors: Trigger derivation pipeline reset
  • Flush errors: Trigger derivation pipeline flush
An INVALID payload status from engine_forkchoiceUpdated rejects the requested head, so Kona resets the engine and derivation instead of retrying the same forkchoice. This differs from the -38003 invalid-payload-attributes error, which retains the Holocene deposits-only replacement path. Derivation adopts the recovered safe head and resumes while handling the reset, even when the head has not changed, without waiting for another L1 update or a separate confirmation.

State Consistency

Tasks operate atomically on the EngineState, ensuring consistency even during error conditions.

Version Support

The engine automatically selects appropriate Engine API versions based on hardfork activation:
  • Pre-Ecotone (Bedrock, Canyon, Delta): Uses engine_newPayloadV2 and engine_getPayloadV2
  • Post-Ecotone: Uses engine_newPayloadV3 and engine_getPayloadV3
  • Post-Isthmus: Uses engine_newPayloadV4 and engine_getPayloadV4
  • Post-Karst (Osaka): Uses engine_getPayloadV5 (engine_newPayload and engine_forkchoiceUpdated stay at their V4/V3 versions)

Metrics and Observability

The engine always includes metrics instrumentation for:
  • Task execution success and failure counts
  • Error rates by task type
  • Engine state transitions
  • API call latencies

Extensibility

The trait-based architecture allows for:
  • Custom task implementations via ExecutableEngineTask
  • Custom error handling via EngineTaskError
  • Custom state management extensions
  • Testing and mocking support
This modular design ensures the engine can adapt to future OP Stack protocol changes while maintaining backward compatibility.