Flashblocks
Table of Contents
- Abstract
- Prerequisites
- Motivation
- Specification
- Terminology
- Parameters
- Data structures
- System architecture
- Out-of-Protocol Design
- Assumptions About Op Stack
- Flashblock Lifecycle
- Flashblock Construction Process
- Flashblocks Metadata
- Rationale for Including State Roots in Flashblocks
- Builder-to-Rollup-boost Communication Flow
- Flashblock Validity Rules
- Flashblock System Invariants
- Flashblock Propagation
- Flashblock JSON-RPC APIs
- Reliability and Operational Considerations
- Rationale
- Backwards Compatibility
- Implementation
Abstract
Introduces a standard for partial blocks called “Flashblocks,” inspired but not entirely identical to Solana Shreds, enabling rapid preconfirmations on Ethereum Layer 2 networks such as OP Stack. Flashblocks propagate transaction batches incrementally and expose their state via a modified Ethereum JSON-RPC interface, giving users immediate feedback equivalent to drastically reduced block times without modifying the underlying OP Stack protocol. Flashblocks can be combined with Trusted Execution Environment technology to enable quick verifiability over various networks of machines in addition to protection from equivocation.
Prerequisites
This document assumes knowledge of the terminology, definitions, and other material in
- [🔗 Ethereum Optimism Protocol Specs]ethereum-optimism
- 🔗 OP Stack Engine API
- [🔗 External Block Production in OP Stack Design Doc]ethereum-optimism
- 🔗 Ethereum Execution APIs
- 🔗 Introducing Rollup-Boost - Launching on Unichain
- 🔗 Rollup-boost design doc
Motivation
As of April 2025, Layer 2 (L2) protocols built with the OP Stack have a minimum block time of one second, imposing significant constraints on user experience. The limitation on minimum block times is primarily historical and architectural, reflecting earlier assumptions of Ethereum network as well as deeply-integrated type definitions, from the L2 blockchain client all the way down to smart contracts on the L1, making modification a very large task.
Due to similar constraints on Ethereum Layer 1, preconfirmations have gained attention as a promising method to decouple blockchain user experiences from rigid block-time limitations and sidestep the longstanding debate between block time and block size. Existing preconfirmation solutions predominantly depend on economic security in the form of cryptoeconomic mechanisms such as staking. As well as focus on per-transaction preconfirmations, inadvertently pushing protocols into the “Latency Auction” region of the MEV Trilemma. Furthermore, previous approaches have often introduced entirely new Ethereum JSON-RPC methods, presenting substantial integration barriers and hindering practical adoption.
Inspired by modern blockchain networks like Solana and Celestia, Flashblocks introduce an “out-of-protocol” standard for incremental delivery of partial blocks containing batches of transactions. This approach significantly reduces perceived latency for end-users and improves network bandwidth without modifying underlying protocol rules, offering a streamlined path for incremental adoption by node operators and existing infrastructure.
Specification
Terminology
All terms, actors, and components are used in this document identically to how they are defined in the [OP Stack protocol definition]ethereum-optimism
Additional terms introduced:
- External Block Builder - External Block Builders are first introduced to the OP Stack in the [External Block Production Design Document]ethereum-optimism ****where they are described as an external party that the Sequencer can request blocks from.
- Rollup Boost - A sidecar piece of software first introduced without name in the [External Block Production Design
Document]ethereum-optimism with two
roles:
- obfuscate the presence of External Block Builder software from the
op-node
andop-geth
software - manage communication from the sequencer with External Block Builders and handle block delivery to
op-node
.
- obfuscate the presence of External Block Builder software from the
- Fallback EL - The standard Execution Layer of the Sequencer, used by Rollup Boost as a fallback mechanism when it cannot successfully build a block through the External Block Builder. This is an unmodified EL node that maintains the ability to construct valid blocks according to standard OP Stack protocol rules.
- RPC Provider - Ethereum RPC software operator with the purpose of serving Ethereum state.
Parameters
Constant | Value | Description |
---|---|---|
FLASHBLOCKS_TIME | 200ms | Default wall clock time per flashblock. |
FLASHBLOCKS_PER_L2_BLOCK | L2_BLOCK_TIME /FLASHBLOCKS_TIME | Supported number of flashblocks per L2 block. (Ex: 2s/200ms = 10 Flashblocks) |
MAX_EXTRA_DATA_BYTES | 32 | Extra data size in an Optimism block |
BYTES_PER_LOGS_BLOOM | 256 | Size of a logs bloom field in an Optimism block |
Data structures
FlashblocksPayloadV1
The core data structure sent from the Block Builder to Rollup Boost and then external parties. A container representing a Flashblock payload, encapsulating block deltas, base configuration, and additional metadata.
class FlashblocksPayloadV1():
version: Bytes4
payload_id: Bytes8
parent_flash_hash: Optional[Bytes32]
index: uint64
static: Optional[ExecutionPayloadStaticV1]
diff: ExecutionPayloadFlashblockDeltaV1
metadata: FlashblocksMetadata
Field descriptions:
payload_id
: PayloadID is an identifier of the payload build process. The same for all flashblocks.index
: Index of the Flashblock within the parent block.parent_flash_hash
: SSZ hash of the parent flashblock in the sequence. For the first flashblock (index 0), the field is empty.base
(Optional): Reference execution payload serving as the unchanging base configuration.diff
: Container with fields representing changes from the base payload.metadata
: Supplementary information about the execution of the flashblock. For example: account state changes, storage modifications, transaction receipts.
ExecutionPayloadFlashblockDeltaV1
Container encoding only the mutable portions of the execution payload updated during Flashblock construction.
class ExecutionPayloadFlashblockDeltaV1():
state_root: Bytes32
receipts_root: Bytes32
logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM]
gas_used: uint64
block_hash: Bytes32
transactions: List[Transaction]
withdrawals: List[Withdrawal]
withdrawals_root: Bytes32
Field descriptions:
state_root
: Root hash of the post-execution state trie.receipts_root
: Root hash of the transaction receipts trie.logs_bloom
: Bloom filter of all logs emitted by the block.gas_used
: Gas consumed by included transactions.block_hash
: Final hash of the completed execution block.transactions
: List of transactions included in the Flashblock.withdrawals
: Withdrawals included (as per Optimism specification). Must be non-nil but empty whenwithdrawals_root
is used directly.withdrawals_root
: OP-Stack Isthmus specific field: instead of computing the root from a withdrawals list, set it directly. The "withdrawals" list attribute must be non-nil but empty.
Supporting Type Definitions
Transaction
: Transaction bytes as per execution payload specification.Withdrawal
: Standard Ethereum Capella withdrawal container.
All fields in this structure represent the cumulative state of the entire block up to and including the current flashblock, not just the changes from this specific flashblock.
ExecutionPayloadStaticV1
Container representing immutable fundamental block properties established at initial block creation, unchanged throughout construction.
class ExecutionPayloadStaticV1():
parent_beacon_block_root: Bytes32
parent_hash: Bytes32
fee_recipient: ExecutionAddress
prev_randao: Bytes32
block_number: uint64
gas_limit: uint64
timestamp: uint64
extra_data: ByteList[MAX_EXTRA_DATA_BYTES]
base_fee_per_gas: uint256
Field descriptions:
parent_beacon_block_root
: Ecotone parent beacon block root.parent_hash
: Hash of the parent execution block.fee_recipient
: Address receiving transaction fees.prev_randao
: Previous block’s RANDAO reveal for randomness.block_number
: Sequential execution block number.gas_limit
: Maximum allowable gas consumption for the block.timestamp
: Unix timestamp at block creation.extra_data
: Arbitrary extra data bytes included in the block header.base_fee_per_gas
: Base fee per gas unit at the block.
Metadata
Container encapsulating all metadata for a flashblock, including account state changes and transaction results.
class FlashblockMetadata():
accounts: List[AccountMetadata]
transactions: List[TransactionMetadata]
Field descriptions:
accounts
: List of accounts with modified state in this flashblock.transactions
: List of transaction execution results in this flashblock.
AccountMetadata
Container representing account state changes included in the Flashblock metadata. It is used by providers to fulfill the RPC requests.
class AccountMetadata():
address: ExecutionAddress
balance: Optional[uint256]
nonce: uint64
code: Optional[Bytes]
storage_slots: List[StorageSlot]
Field descriptions:
address
: Ethereum address of the affected account.balance
: Updated account balance after the Flashblock's execution (None if unchanged).nonce
: Updated account nonce (transaction count) after the Flashblock's execution.code_created
: Contract bytecode if created in this Flashblock.storage_slots
: List of modified storage slots and their new values.
Storage slot keys must be de-duplicated (only the final value for each key should be included) and sorted in ascending byte order for deterministic processing.
StorageSlot
Container representing a single modified storage slot within an account.
class StorageSlot():
key: Bytes32
value: Bytes32
Field descriptions:
key
: Storage slot location (32-byte key).value
: New value stored at this slot after the Flashblock's execution.
TransactionMetadata
Container representing succinct transaction execution results.
class TransactionMetadata():
status: uint8
gas_used: uint64
contract_address: Optional[ExecutionAddress]
Field descriptions:
status
: Execution status (1 for success, 0 for failure).gas_used
: Amount of gas used by this specific transaction.contract_address
: Address of created contract (None for non-creation transactions).
System architecture
The following diagram illustrates the Flashblocks system architecture, showing the relationships between key components:
flowchart LR subgraph Sequencer ON[OP Node] RB[Rollup Boost] FEL[Fallback EL] BB[Block Builder] end subgraph Network WSP[WebSocket Proxy] end subgraph Clients RPC[RPC Providers] Users[End Users] end ON --> RB RB --> FEL RB <--> BB RB --> WSP WSP --> RPC RPC --> Users
This architecture shows the flow of data through the Flashblocks system:
- The OP Node initiates block production and sends requests to Rollup Boost
- Rollup Boost coordinates between multiple components: [-+] It communicates with the Block Builder to create Flashblocks [-+] It maintains a connection to the Fallback EL for reliability if the Block Builder fails [-*+] It propagates validated Flashblocks to the network via the WebSocket Proxy
- The WebSocket Proxy distributes Flashblocks to multiple RPC Providers
- RPC Providers serve preconfirmation data to End Users
The rest of this document provides detailed specifications for each component and their interactions, explaining the protocols, data structures, and operational considerations.
Out-of-Protocol Design
The Flashblocks specification follows a deliberate "out-of-protocol" design philosophy. This section clarifies what we mean by this term and explains its implications for the OP Stack ecosystem.
In-Protocol vs. Out-of-Protocol
In the context of OP Stack, "in-protocol" components form the core protocol itself. These components implement fundamental consensus rules, are required for basic rollup functionality, and need standardization across all participants. Modifying in-protocol components requires protocol-level changes and network-wide upgrades.
By contrast, "out-of-protocol" components like Flashblocks operate as optional extensions to the core protocol. They can be added or removed without breaking the consensus rules of the network, though they may still impact network performance or operations if implemented poorly.
The only in-protocol guarantee that Flashblocks must uphold is producing valid blocks at the established block time interval (typically 1-2 seconds in current OP Stack implementations).
Design Rationale and Benefits
The out-of-protocol design for Flashblocks emerged from practical constraints during initial development. Without strong coordination with the OP Stack team at the outset, and given the complexity of the challenge, working within the existing protocol boundaries was the most pragmatic approach.
This constraint ultimately proved beneficial, as it forced the design to be minimally invasive. Flashblocks can be implemented immediately on any OP Stack chain without waiting for protocol upgrades or network-wide consensus.
Any issues with the Flashblocks implementation remain isolated from the core protocol, protecting overall network stability. In case of serious problems, Flashblocks can be disabled entirely, allowing the system to revert to normal operation without disrupting the underlying rollup. This clean fallback mechanism benefits from the centralized trust model of L2s, where the sequencer has the authority to quickly enact such operational changes without requiring network-wide consensus.
Now that the usefulness of the system has been proven, as more collaboration venues with the OP Stack team emerge, integrating parts of Flashblocks directly into the protocol could provide even stronger guarantees and open the design space for future innovations. We are considering that approach too in the future.
Implications for This Specification
Most elements defined in this document are out-of-protocol components that operate as extensions to the core OP Stack. The only hard guarantee the system must provide is that valid blocks are delivered at the expected intervals.
Everything else—from how Flashblocks are constructed and propagated to how RPC providers implement preconfirmation caches—represents iterative improvements designed to achieve the goal of faster user feedback as efficiently and impactfully as possible.
This means the specification describes a recommended implementation path rather than rigid protocol requirements. Components can evolve independently without requiring protocol-level coordination, and implementations may vary in how they achieve the same functional goals.
Assumptions About Op Stack
The Flashblocks design makes several assumptions about OP Stack behavior:
- Quick Response for engine_getPayload: We assume that
engine_getPayload
requests should return as quickly as possible for a normal and healthy chain. - Deterministic Payload IDs: While not specific to Flashblocks but to Rollup Boost in general, we assume that payload IDs from different execution layer nodes are deterministically computed for the same ForkChoiceUpdate request. This is not explicitly enforced in specifications, but execution layers tend to maintain this consistency as a practical implementation detail.
Flashblock Lifecycle
Note that familiarity with Rollup-boost is expected throughout this entire document, as Flashblocks is designed as an extension built on top of the existing Rollup-boost architecture.
The lifecycle of a Flashblock begins with the Sequencer initiating block creation and ends with a normal L2 block consisting of all delivered flashblocks propagating according to the OP Stack protocol. The process proceeds as follows:
-
Fork Choice Update:
The Sequencer initiates the block-building cycle by sending an
engine_forkchoiceUpdated
with attributes call to Rollup Boost as it normally would to its local Execution Engine. -
Fork Choice Update Forwarding:
Rollup Boost forwards the
engine_forkchoiceUpdated
call concurrently to:- The Sequencer's local Execution Engine
- The External Block Builder
-
Flashblock Construction:
Upon receiving the fork choice update, the External Block Builder constructs and continuously delivers
FlashblocksPayloadV1
at intervals defined byFLASHBLOCKS_TIME
following the Flashblocks Construction Process defined in this document.It's important to emphasize that during this process, the External Block Builder sends only the incremental changes in each Flashblock, not the full block state each time. Each
FlashblocksPayloadV1
contains just the delta from the previous state (new transactions, updated state roots, etc.), allowing for efficient bandwidth usage and faster propagation.Only the first Flashblock (with
index
0) includes thestatic
field containing immutable block data, while subsequent Flashblocks omit this field since this information remains constant throughout the block's construction. Each Flashblock includes aparent_flash_hash
that references the SSZ hash of the previous Flashblock in the sequence, creating a hash-linked chain within the block.The combined information received across all flashblocks is sufficient to fully reconstruct the complete block without any additional data.
-
Flashblock Validation and Propagation:
For each received
FlashblocksPayloadV1
, Rollup Boost validates it against the Sequencer's local Execution Engine and according to the Flashblocks Validity Rules defined in this document. Upon successful validation, Rollup Boost propagates the payload to all subscribed Flashblock-compatible RPC providers. -
Preconfirmed State Updates:
Flashblock-compatible RPC providers insert validated payloads into their local Preconfirmed State Overlay, providing immediate preconfirmation states to end-users via Flashblock-enhanced Ethereum JSON-RPC endpoints.
-
Final L2 Block Delivery:
When the Sequencer calls
engine_getPayload
, Rollup Boost returns a single coherent block payload based on the validated Flashblocks received since the last fork choice update. Note that this does not require additional external requests or any last-minute processing. -
Full Block Propagation:
The Sequencer propagates the aggregated block following standard OP Stack protocol rules.
sequenceDiagram participant S as Sequencer Driver (op-node) participant RB as Rollup Boost participant EE as Sequencer Execution Engine (op-geth) participant BB as External Block Builder participant RPC as Flashblock RPC Providers participant U as End Users rect rgb(230,247,255) Note over S: 1. **Fork Choice Update** S->>RB: engine_forkchoiceUpdated end rect rgb(255,242,230) Note over RB: 2. **Fork Choice Update Forwarding** RB->>EE: engine_forkchoiceUpdated RB->>BB: engine_forkchoiceUpdated end rect rgb(230,255,235) loop **Flashblock Construction** (every FLASHBLOCKS_TIME) Note over BB: **3. Flashblock Construction** BB->>RB: FlashblocksPayloadV1 rect rgb(252,244,255) Note over RB, EE: 4. **Flashblock Validation and Propagation** RB->>EE: Validate Payload EE-->>RB: Validation Result alt Success RB->>RPC: Propagate Valid Payload Note over RPC: 5. **Preconfirmed State Updates** RPC->>RPC: Update State Overlay RPC->>U: Serve Preconfirmed State else Failure RB-->>RB: Discard and fallback end end end end rect rgb(255,249,196) Note over S, RB: 6. **Final L2 Block Delivery** S->>RB: engine_getPayload RB->>S: Aggregate Payload end rect rgb(240,240,240) Note over S: 7. **Full Block Propagation** S->>S: Propagate Block (standard OP Stack) end
Flashblock Construction Process
The External Block Builder initiates the construction of Flashblocks upon receiving a fork choice update
(engine_forkchoiceUpdated
) call forwarded by Rollup Boost. The construction of Flashblocks follows a defined sequence
of steps repeated every FLASHBLOCKS_TIME
interval, ensuring consistent, incremental, and ordered propagation of
preconfirmed state to end-users. It's important to note that FLASHBLOCKS_TIME
serves as a target interval rather than
a strictly enforced rule in Rollup Boost.
Handling of Sequencer Transactions
An important protocol rule that the Flashblock construction process must adhere to involves handling "system transactions" within the OP Stack. These include deposits and system transactions that arrive with the Fork Choice Update (FCU) and must always be executed as the first transactions in any valid block.
From an "in-protocol" perspective, a block is not considered valid if these sequencer transactions are missing. Consequently, the minimum valid block that can be constructed must include the execution of these transactions.
The External Block Builder follows this mandate by executing these sequencer transactions first and including them in the initial Flashblock (index 0). This serves as the foundation upon which all subsequent Flashblocks in the sequence will build.
When processing these mandatory sequencer transactions, the builder does not apply the same gas allocation heuristics used for regular transactions in later Flashblocks. While these transactions do consume gas like any other transaction, they receive special handling as they must be included regardless of gas considerations to maintain protocol validity.
Transaction Inclusion Heuristics
As part of the flashblock construction process, the External Block Builder makes sophisticated decisions about transaction inclusion. Unlike rigid gas limit enforcement, the decision of when to stop including transactions in a flashblock involves nuanced heuristics that may evolve over time.
The builder must balance multiple factors when deciding which transactions to include in each flashblock:
- Optimizing for user experience by providing quick feedback
- Ensuring transactions with higher gas usage aren't permanently excluded
- Maintaining efficient gas utilization across the entire block
- Accounting for execution time constraints within the
FLASHBLOCKS_TIME
window
In some cases, the builder might include a transaction that exceeds what would be a strict per-flashblock gas limit because executing that transaction is important for user experience or economic reasons. This flexibility is a key advantage of the out-of-protocol approach.
The specific heuristics for transaction allocation across flashblocks are intentionally not prescribed in this specification. Rather than codifying particular strategies, we leave this as an area where builders can innovate and optimize. Different chains can develop custom heuristics based on their specific transaction patterns, user expectations, and economic models. As implementations mature, we expect some general principles will emerge for handling common scenarios, but this specification intentionally avoids prematurely constraining this design space.
Post-block Execution Rules
In the OP Stack protocol, certain operations such as withdrawals and system requests are applied at the end of block execution. Since each flashblock must function as a valid standalone block for preconfirmation purposes, these post-block execution rules must be applied at the end of each flashblock's construction.
When constructing a flashblock, the builder applies all required post-block operations after executing the selected transactions. These operations modify the state according to protocol rules, ensuring the flashblock represents a complete and valid block state.
However, an important implementation detail is that these post-block changes must be reverted before beginning execution for the next flashblock. This reversion is necessary because the post-block operations should only be applied once per actual L2 block, not cumulatively for each flashblock. Failing to revert these changes would lead to their repeated application across multiple flashblocks, potentially creating invalid cumulative state and ultimately an invalid final block.
Construction Steps
After handling the mandatory sequencer transactions in the initial Flashblock, the External Block Builder proceeds with constructing subsequent Flashblocks by following these steps for each interval:
-
Transaction Selection
- Retrieve transactions from local or external mempool:
- Prioritize and sort transactions based on predefined sequencing policies, such as priority ordering or by MEV paid.
-
Transaction Execution
- Sequentially execute selected transactions against a state snapshot derived from the current execution payload base (ExecutionPayloadBaseV1) or the last validated flashblock
- Apply the transaction inclusion heuristics described earlier to determine when to stop including transactions
- After transaction execution completes, apply all post-block execution rules as described in the Post-Block Execution Rules section
-
Flashblock Payload Assembly
- After transaction execution, compute and record the following execution state updates:
state_root
: The new post-execution state root resulting from the executed transactions.receipts_root
: The receipts trie root derived from execution outcomes.logs_bloom
: Aggregated logs bloom from all emitted transaction logs within this flashblock.gas_used
: Total gas consumed by executed transactions.transactions
: Serialized transaction payloads included within the flashblock.withdrawals
(if applicable): Withdrawals executed during the current flashblock interval (as per OP Stack withdrawal specification).block_hash
: Computed block hash uniquely identifying this flashblock execution state.
Note that each flashblock builds upon the state of all previous flashblocks, with these fields reflecting the cumulative state after applying the new transactions in this particular flashblock.
- Encapsulate these computed updates into
ExecutionPayloadFlashblockDeltaV1
.
- After transaction execution, compute and record the following execution state updates:
-
Flashblock Indexing and Metadata
- Assign a monotonically incremented
index
to the newly constructed Flashblock payload. - Compute the SSZ hash of the previous Flashblock and assign it as the
parent_flash_hash
(for the first Flashblock with index 0, this field is empty)
- Assign a monotonically incremented
-
Flashblock Delivery
- Package the
index
,payload_id
,ExecutionPayloadFlashblockDeltaV1
, and metadata into aFlashblocksPayloadV1
payload. - Deliver the assembled
FlashblocksPayloadV1
payload promptly to Rollup Boost via the designated Flashblocks submission API.
- Package the
-
Subsequent Flashblock Construction
- Immediately after successful delivery, increment the Flashblock
index
. - Revert any post-block execution changes as described in the Post-Block Execution Rules section
- Reset the transaction execution context based on the newly delivered state.
- Begin constructing the next
FlashblocksPayloadV1
payload, repeating from step 1 until a termination condition is reached (e.g., end of block building period viaengine_getPayload
request).
- Immediately after successful delivery, increment the Flashblock
-
Flashblock Construction Termination
- Flashblock construction continues iteratively until:
- Rollup Boost signals final block aggregation and propagation via
engine_getPayload
. - A failure or timeout condition arises requiring failover procedures, detailed separately.
- Rollup Boost signals final block aggregation and propagation via
- Flashblock construction continues iteratively until:
sequenceDiagram participant BB as External Block Builder participant RB as Rollup Boost participant EE as Execution Engine (local) participant M as Mempool loop Every FLASHBLOCKS_TIME BB->>M: Retrieve and Prioritize Transactions M-->>BB: Transactions batch Note over BB: Execute transactions sequentially BB->>EE: Execute transactions and compute state root EE-->>BB: Execution results (state root, receipts, gas used) Note over BB: Construct Flashblock Delta BB->>BB: Assemble FlashblocksPayloadV1 (state_root, receipts_root, logs_bloom, gas_used, block_hash, txs, withdrawals, metadata) BB->>RB: Submit FlashblocksPayloadV1 RB-->>BB: Acknowledge reception (async) Note over BB: Increment index, prepare next Flashblock end
Flashblocks Metadata
The FlashblocksPayloadV1
structure defined above contains the minimum required data for Rollup Boost to return a
valid block. The metadata
field provides additional information to enable preconfirmations.
This metadata contains supplementary information about the execution state that is not strictly necessary for block construction but is valuable for RPC providers to offer comprehensive preconfirmation services. Examples of such metadata include:
- Account state changes (which accounts have been modified)
- Updated account balances
- Storage slot modifications
- Contract deployment information
- Detailed transaction execution results
Alternative Design Consideration
While this specification includes detailed metadata in Flashblocks, a viable alternative would be for RPC providers to execute transactions themselves as they receive them through the stream. In this approach, providers would receive only transaction data, execute them in order, maintain their own state cache, and use it to fulfill RPC requests. This would significantly reduce bandwidth requirements by eliminating metadata transmission.
Rationale for Including State Roots in Flashblocks
One of the most discussed aspects of the Flashblocks design is the decision to include state roots with every flashblock. This section explains the rationale behind this design choice.
Non-Blocking Block Production
We operate under the assumption that engine_getPayload
requests should return quickly with a valid, complete block.
This assumption, which we believe to be correct based on our understanding of the OP Stack, guides our design decisions.
Currently in OP Stack implementations, execution layer nodes compute payloads in the background and can return them
immediately when requested via engine_getPayload
. This allows for near-instant responses, maintaining the flow of
block production without delays. For Flashblocks to provide similar performance, it must have all block components -
including state roots - readily available when engine_getPayload
is called.
Without pre-computed state roots for each flashblock, Rollup Boost would face a critical decision when handling
engine_getPayload
:
- Request the state root from the Execution Layer: This approach would be problematic because the Execution Layer does not maintain a "hot" state that matches the current flashblock sequence. It would need to apply all pending transactions and compute a new state root, which is exactly the operation we're trying to optimize with flashblocks.
- Request the state root from the External Block Builder: This would require an additional synchronous request to the builder with a protocol which is not engine-specific. Not only does this introduce an extra communication hop and latency, but it also creates a single point of failure - if the builder is unavailable at that moment, Rollup Boost cannot fulfill the request and we fall into the failure path rather than the happy path.
Builder Availability and System Reliability
The key advantage of including state roots with each flashblock is system reliability. By having state roots
immediately available, Rollup Boost can respond to engine_getPayload
requests without additional external
dependencies at that critical moment.
Without pre-included state roots, a builder failure at the moment of block production would force the system to either:
- Recompute the entire state from scratch (time-consuming and potentially disruptive)
- Fail to produce a block on time (violating protocol assumptions)
- Be unable to fulfill the preconfirmations that have already been exposed to users
Future Design Considerations
This approach represents our current understanding of the optimal design given existing constraints. However, as mentioned in the Out-of-Protocol Design section, alternative approaches may be worth exploring as we gain production experience. Future iterations might consider different state root handling approaches, particularly in the context of high-availability sequencer setups and deeper integration with OP Stack components.
Builder-to-Rollup-boost Communication Flow
Rollup Boost maintains an open WebSocket connection with the External Block Builder. Through this persistent
connection, the builder pushes the FlashblocksPayloadV1
payloads as soon as they're constructed, without waiting for
requests from Rollup Boost.
If the WebSocket connection goes down, the builder buffers (queues) the messages internally and attempts to resend them once the connection is restored. This buffering only applies for the current block being built; when a new block cycle begins, any queued messages from the previous block are discarded as they are no longer relevant to the current state.
SSZ Encoding for Flashblocks Messages
Flashblocks messages transmitted between the Block Builder and Rollup Boost use Simple Serialize (SSZ) for binary encoding. Unlike JSON or other self-describing formats, SSZ is schema-less and does not embed field names or type information in the serialized data. This makes explicit versioning necessary, especially in a streaming context where message types cannot be inferred from surrounding context.
For the FlashblocksPayloadV1
structure, a version field is placed as the first field in the container.
This design leverages SSZ's deterministic encoding characteristics, where fixed-size fields like Bytes4
appear at
predictable offsets in the serialized data. When a recipient receives a serialized Flashblocks message over the
WebSocket stream:
- It first reads the initial 4 bytes to determine the message version
- Based on the version identifier, it selects the appropriate container structure for deserializing the remainder of the data
Flashblock Validity Rules
For a flashblock to be considered valid the following must hold:
-
Monotonically Increasing Payload Index: Each successive Flashblock payload delivered within the same L2 block cycle must have an index exactly one greater than the previous payload. Any skipped indices or duplicated indices constitute a violation. When a violation occurs, Rollup Boost will ignore the invalid flashblock and maintain its internal state, only updating when it receives a new flashblock with the correct next index value.
-
Immutable Payload Base: Immutable block header fields (
parent_hash
,block_number
,prev_randao
, etc.) set by the initialExecutionPayloadBaseV1
cannot be altered by subsequent Flashblocks during the same L2 block period. -
Execution Validity: Every Flashblock must be validated successfully against the Sequencer’s local execution engine state to ensure OP protocol-level correctness.
-
Valid Full Block: Every flashblock, when combined with prior flashblocks, should be a valid L2 Block without requiring Rollup Boost to perform any additional operations other than repackaging the data structure. This means that state roots are calculated on each Flashblock contrary to publication due to the out-of-protocol nature of the implementation.
A flashblock is considered a valid block if:
[-+] It includes the first flashblock (with index 0 containing the base data) [-+] It comprises a continuous sequence of flashblocks with incrementing indices.
Flashblock System Invariants
The following invariants must hold true for the Flashblocks protocol to function reliably:
- No Equivocation: At no point should multiple distinct Flashblocks for the same payload index be delivered or propagated to RPC subscribers.
- Preconfirmation Preservation: The system always gives strong preference to maintaining the integrity of issued preconfirmations. Once a transaction has been included in a flashblock and made visible to users as preconfirmed, the system will prioritize preserving this state over other considerations such as block value optimization or alternative builder selection.
Flashblock Propagation
Once Rollup Boost has validated a flashblock, it is then propagated to the rest of the network to be included in each RPC Provider’s Preconfirmation Cache.
sequenceDiagram participant B as Block Builder participant R as Rollup-boost box Node participant RPC as JSON-RPC Interface end participant U as Users B->>R: Preconfirmation batches R->>R: Validate batches R->>RPC: Preconfirmation updates U->>RPC: Standard RPC queries note right of U: Regular users
Flashblocks Compatible RPC Providers subscribe to the Flashblocks websocket stream from Rollup Boost and maintain an in-memory representation of the preconfirmation state. RPC providers validate that the flashblock sequence is correct before updating their preconfirmation state. This preconfirmation state is ephemeral, maintained only until the corresponding block is propagated and the information becomes available through standard chain state.
Throughout the entire propagation path, flashblocks are transmitted in binary SSZ-encoded format.
Secure propagation
Since the preconfirmation data originates directly from the Sequencer's Rollup Boost instance, exposing this WebSocket endpoint directly to external parties presents security and scalability concerns. Instead, a reverse proxy should be implemented between Rollup Boost and external RPC providers to relay this information securely.
This mirror simply relays WebSocket data without requiring any Flashblocks-specific knowledge, acting purely as a transport layer that forwards WebSocket messages from Rollup Boost to subscribed RPC providers. You can find an example implementation in the flashblocks-websocket-proxy repository.
flowchart TD subgraph Sequencer BB[Block Builder] RB[Rollup Boost] Mirror[Flashblocks Mirror] end subgraph RPC Providers RPC1[RPC Provider 1] RPC2[RPC Provider 2] RPC3[RPC Provider 3] RPCN[RPC Provider N] end BB -->|Flashblocks| RB RB -->|Flashblocks| Mirror Mirror -->|Flashblocks| RPC1 Mirror -->|Flashblocks| RPC2 Mirror -->|Flashblocks| RPC3 Mirror -->|Flashblocks| RPCN
Flashblock JSON-RPC APIs
Ethereum JSON RPC Modifications
All modifications done to the existing Ethereum JSON RPC methods are confined to overloading the existing pending
tag. Originally, this tag was designed to return block data being processed by the node's internal miner. It's fitting
that we now use it for a similar purpose: exposing blocks in their preconfirmation stage. When queried with the
pending
tag, the endpoint uses the preconfirmation cache state to construct the response. The response might include
not only transactions but also block metadata like state root and receipt root.
The tag is currently in a soft-deprecated state due to inconsistent implementations across clients, particularly after
The Merge. However, it's worth noting that it's still actively used for certain endpoints, particularly
eth_getTransactionCount
where it serves the important function of returning the next available nonce for an account
(including transactions in the mempool). This presents an opportunity: the tag is well-defined enough to be supported
by client libraries, yet loosely defined enough to allow for our preconfirmation use case. While there's a possibility
of the tag being removed in the future (see EIP discussions),
the design could adapt by introducing a flashblocks-specific tag if needed.
We repurpose the pending
tag in the following RPC calls to enable consuming preconfirmed state:
- eth_getTransactionReceipt
- eth_getBlockByHash
- eth_getBalance
- eth_call
- eth_getCode
- eth_getTransactionCount
- eth_getStorageAt
op_supportedCapabilities
This endpoint allows clients to discover whether the RPC provider supports certain features, including Flashblocks.
Request
{
"method": "op_supportedCapabilities",
"params": [],
"id": 1,
"jsonrpc": "2.0"
}
Response
{
"id": 1,
"jsonrpc": "2.0",
"result": ["flashblocksv1"]
}
When this method is called on a Flashblocks-compatible RPC provider, the response includes "flashblocksv1" in the returned array of supported capabilities. This allows clients to programmatically determine whether they can utilize Flashblocks functionality before making related requests.
This endpoint follows a similar pattern to the Engine API's engine_exchangeCapabilities
method, which allows
consensus and execution clients to exchange information about supported features.
This is the only new RPC endpoint introduced by the Flashblocks specification. We consider this addition acceptable because it provides necessary feature discovery while keeping the name abstract enough to accommodate future extensions to the protocol or for other protocols.
eth_getTransactionReceipt
Request
{
"method": "eth_getTransactionReceipt",
"params": ["0x..."],// Transaction hash
"id": 1,
"jsonrpc": "2.0"
}
Response
{
"transactionHash": "0x...",
"blockHash": "0x0", // Empty hash as placeholder
"blockNumber": "0x...", // Current pending block number
"transactionIndex": "0x0",
"from": "0x...",
"to": "0x...",
"gasUsed": "0x...",
"status": "0x1",
"cumulativeGasUsed": "0x...",
"effectiveGasPrice": "0x...",
"contractAddress": "0x...", // For contract creations
"logs": [],
"logsBloom": "0x..."
}
When queried, this endpoint first checks the preconfirmation cache for the requested transaction hash before falling back to the standard chain state lookup.
Some fields in the response cannot be final at the preconfirmation stage and require placeholder values:
blockHash
: Uses empty hash as placeholderblockNumber
: Can be set to the current block number being processed
eth_getBlockByHash
Request
{
"method": "eth_getBlockByHash",
"params": ["pending", false], // Second parameter indicates full transaction objects (true) or only hashes (false)
"id": 1,
"jsonrpc": "2.0"
}
Response
{
"hash": "0x0", // Empty hash as placeholder
"parentHash": "0x...",
"stateRoot": "0x...",
"transactionsRoot": "0x...",
"receiptsRoot": "0x...",
"number": "0x...", // Current pending block number
"gasUsed": "0x...",
"gasLimit": "0x...",
"timestamp": "0x...",
"extraData": "0x...",
"mixHash": "0x...",
"nonce": "0x...", // // Used to signal flashblock index
"transactions": [] // Array of transaction hashes or full transaction objects
}
The endpoint implements an append-only pattern - multiple queries during the same block's preconfirmation phase will show an expanding list of transactions as new flashblocks are processed. Each query reflects the current state of all preconfirmed transactions at that moment.
sequenceDiagram participant U as User participant RPC participant R as Rollup-boost Note over R: Block building starts R->>RPC: Batch 1 (txs: A, B) U->>RPC: Query 1 RPC-->>U: Block with txs: A, B R->>RPC: Batch 2 (txs: C, D) U->>RPC: Query 2 RPC-->>U: Block with txs: A, B, C, D R->>RPC: Batch 3 (txs: E) U->>RPC: Query 3 RPC-->>U: Block with txs: A, B, C, D, E R->>RPC: Batch 4 (txs: F, G) U->>RPC: Query 4 RPC-->>U: Block with txs: A, B, C, D, E, F, G Note over R: Block sealed
eth_getBalance
Request
{
"method": "eth_getBalance",
"params": ["0x...", "pending"], // Account address and block parameter
"id": 1,
"jsonrpc": "2.0"
}
Response
"0x..." // Balance in wei
When queried with the "pending" tag, the endpoint uses the preconfirmation cache state to return the account balance.
If the requested account appears in the AccountMetadata
of a received Flashblock with a non-null balance
field, the
RPC provider can directly return this value without needing to access the full state. The response reflects all changes
from preconfirmed transactions that affect the requested account's balance.
eth_call
Request
{
"method": "eth_call",
"params": [{"to": "0x...", "data": "0x..."}, "pending"], // Transaction call object and block parameter
"id": 1,
"jsonrpc": "2.0"
}
Response
"0x..." // Return data from the call
When queried with the "pending" tag, the endpoint uses the preconfirmation cache state to return the call result. For this endpoint to work, the preconfirmation stream needs to include state differences for both accounts and storage after each flashblock.
Similar to the current override functionality in eth_call
where EVM transitions are executed on top of modified
state, this implementation executes the call on top of the preconfirmation state changes.
eth_getCode
Request
{
"method": "eth_getCode",
"params": ["0x...", "pending"],// Contract address and block parameter
"id": 1,
"jsonrpc": "2.0"
}
Response
"0x..."// Contract bytecode
When queried with the "pending" tag, the endpoint returns the contract bytecode from the preconfirmation cache state.
If the requested account appears in the AccountMetadata
of a received Flashblock with a non-null code
field, the
RPC provider can directly return this value without accessing the full state.
eth_getTransactionCount
Request
{
"method": "eth_getTransactionCount",
"params": ["0x...", "pending"],// Account address and block parameter
"id": 1,
"jsonrpc": "2.0"
}
Response
"0x..."// Nonce value as a hex string
When queried with the "pending" tag, the endpoint returns the transaction count (nonce) of the account from the
preconfirmation cache. If the requested account appears in the AccountMetadata
of a received Flashblock, the RPC
provider can directly use the nonce
field without additional state access.
eth_getStorageAt
Request
{
"method": "eth_getStorageAt",
"params": ["0x...", "0x...", "pending"],// Contract address, storage position, and block parameter
"id": 1,
"jsonrpc": "2.0"
}
Response
"0x..." // Storage value as a hex string
When queried with the "pending" tag, the endpoint returns the value from the specified storage slot using the
preconfirmation cache state. If the requested account appears in the AccountMetadata
of a received Flashblock, the
RPC provider scans the storage_slots
list for the requested key and returns the corresponding value directly.
Reliability and Operational Considerations
Transaction Propagation
Similar to the design laid out in the [External Block Production]ethereum-optimism design document, Flashblocks makes no assumptions about how transactions are delivered to the block builder. A non-exhaustive list of valid approaches:
- transaction forwarding via mutliplex’ing software at the Rollup Operator’s RPC
- Private p2p connections between Sequencer transaction ingress nodes and block building nodes
Failover scenarios
Block Builder
As per the normal Rollup-boost behavior, if the builder is down, the Rollup-boost picks up the block from the fallback builder. However, since we are dealing with preconfirmations, we must consider the relative value of preserving preconfirmations versus building a potentially more valuable block.
In this design document, we follow the invariant that preserving preconfirmations takes precedence. If the block builder goes down after the first flashblocks have been delivered, we still return those flashblocks to maintain the integrity of any preconfirmations already issued to users. The next block would work as expected through the normal fallback mechanism, as the builder is down and the fallback builder would be used.
We could technically discard the partial flashblocks and use the fallback block entirely, but this would violate the preconfirmations commitment. Our design assumes normal execution conditions. If losing the builder mid-flashblock becomes a common occurrence, this would indicate fundamental architectural issues that require separate improvements beyond the scope of this failover mechanism.
The Sequencer or Rollup-boost
These failure scenarios are addressed as part of the High Availability (HA) sequencer setups. The HA architecture ensures continuity of operations by automatically failing over to standby instances when failures occur.
Integration with High Availability Sequencer Setups
The integration of Flashblocks with High Availability (HA) Sequencer setups is outside the scope of this initial specification document. For details on managing Flashblock state across multiple sequencer instances and maintaining preconfirmation integrity during failovers, please refer to the resources linked below.
- what do with a rotating set of sequencers like with OP conductor op-conductor
- World HA design discussionflashbots
- Base Technical Design Document TDD: Rollup Boost Integration with HA Sequencer
Faults
Safety Faults
In the rollup security vocabulary safety implies that “no one can create or withdraw assets they are not entitled to.” A safety fault therefore occurs the moment an invalid L2 state root is accepted on Ethereum and at least one L2→L1 action (withdrawal, message relay, etc.) that depends on that root is executed and the dispute game period has ended. After that point the canonical record on Ethereum says the invalid state is final and the rollup’s honesty assumption is broken.
The safety of a flashblock is directly equivalent to the safety of an L2 block. Additionally, on each submission of a flashblock to Rollup Boost, it is simulated against the Sequencer’s local execution engine, ensuring the Block Builder’s view is equivalent to the Sequencer’s.
The real thing we are interested in regards to safety faults for the Flashblock stream is whether they can be reorged. The answer to this question is that the preconfirmed state can be reorged out if the Sequencer reorgs. Given that the sequencer is the one validating the block builder blocks, then there is no additional risk of reorg from the introduction of the External Block Builder and Flashblocks stream, as in both cases, the reorg is due to Sequencer Operator error.
Liveness Faults
In the rollup vocabulary *Liveness implies that “*every honest user can (a) get a transaction included within a bounded time and (b) complete a withdrawal within the 7‑day challenge window.” A liveness fault is any condition that makes either promise untrue without violating safety (no invalid state is accepted).
The liveness of a flashblock is therefore directly equivalent to the liveness of L2 blocks as user’s are able to force include via the L1 as normal.
Rationale
Why out-of-protocol
The design is implemented as an out-of-protocol solution rather than a core protocol modification to allow for faster iteration and development. This approach respects the stability guarantees of the OP Stack while allowing participants to adopt the features at their own pace.
We do not, however, discard the possibility of enshrining these features inside the OP Stack protocol as both teams become more comfortable working together and more familiar with the specification. This out-of-protocol approach serves as a proving ground that can inform a potential future core integration.
Why not shorter block times
While reducing block times is a potential solution, it would require non-trivial changes to the OP Stack codebase, where the current minimum timestamp used is 1 second. Additionally, extremely short block times (sub-200ms) might introduce significant performance issues in other blockchain infrastructure like block explorers and indexers.
Flashblocks provide a more balanced approach: they maintain reasonable block times for network decentralization and stability, while offering a fast-lane feedback mechanism for users who need immediate transaction status.
This approach also opens the door to an interesting possibility: chains could potentially implement longer block times (tens of seconds) while still maintaining quick preconfirmations via Flashblocks. This combination might enable new and interesting use cases that benefit from both paradigms.
Backwards Compatibility
End Users
At present, consuming Flashblocks data is completely opt-in through the use of the pending
tag, therefore once turned
on, no applications will require changes to how they consume data from their RPC. Instead an additional opt-in flow is
enabled.
Infrastructure Operators
For Sequencer Operators, Flashblocks and Rollup Boost can be enabled and disabled with no additional harm to the system.
For RPC Operators, Flashblocks will require a modified RPC node that subscribes to the Flashblock stream in addition to
maintaining a Preconfirmation cache and responding with the relevant data on request with the pending
tag.
Implementation
A feature complete implementation of all components described in this document can be found in the [rollup-boost]flashbots [op-rbuilder]flashbots flashblocks-websocket-proxy, and reth-flashblocks.