::Base,
+>;
+
/// Maximum number of cached sequences in the ring buffer.
const CACHE_SIZE: usize = 3;
/// 200 ms flashblock time.
@@ -29,21 +40,21 @@ pub(crate) const FLASHBLOCK_BLOCK_TIME: u64 = 200;
/// - Finding the best sequence to build based on local chain tip
/// - Broadcasting completed sequences to subscribers
#[derive(Debug)]
-pub(crate) struct SequenceManager {
+pub(crate) struct SequenceManager {
/// Current pending sequence being built up from incoming flashblocks
- pending: FlashBlockPendingSequence,
+ pending: FlashBlockPendingSequence
,
/// Cached recovered transactions for the pending sequence
- pending_transactions: Vec>>,
+ pending_transactions: Vec>>,
/// Ring buffer of recently completed sequences bundled with their decoded transactions (FIFO,
/// size 3)
- completed_cache: AllocRingBuffer<(FlashBlockCompleteSequence, Vec>>)>,
+ completed_cache: AllocRingBuffer>,
/// Broadcast channel for completed sequences
- block_broadcaster: broadcast::Sender,
+ block_broadcaster: broadcast::Sender>,
/// Whether to compute state roots when building blocks
compute_state_root: bool,
}
-impl SequenceManager {
+impl SequenceManager
{
/// Creates a new sequence manager.
pub(crate) fn new(compute_state_root: bool) -> Self {
let (block_broadcaster, _) = broadcast::channel(128);
@@ -59,12 +70,14 @@ impl SequenceManager {
/// Returns the sender half of the flashblock sequence broadcast channel.
pub(crate) const fn block_sequence_broadcaster(
&self,
- ) -> &broadcast::Sender {
+ ) -> &broadcast::Sender> {
&self.block_broadcaster
}
/// Gets a subscriber to the flashblock sequences produced.
- pub(crate) fn subscribe_block_sequence(&self) -> crate::FlashBlockCompleteSequenceRx {
+ pub(crate) fn subscribe_block_sequence(
+ &self,
+ ) -> broadcast::Receiver> {
self.block_broadcaster.subscribe()
}
@@ -76,12 +89,12 @@ impl SequenceManager {
/// with computed `state_root`.
///
/// Transactions are recovered once and cached for reuse during block building.
- pub(crate) fn insert_flashblock(&mut self, flashblock: FlashBlock) -> eyre::Result<()> {
+ pub(crate) fn insert_flashblock(&mut self, flashblock: P) -> eyre::Result<()> {
// If this starts a new block, finalize and cache the previous sequence BEFORE inserting
- if flashblock.index == 0 && self.pending.count() > 0 {
+ if flashblock.index() == 0 && self.pending.count() > 0 {
let completed = self.pending.finalize()?;
let block_number = completed.block_number();
- let parent_hash = completed.payload_base().parent_hash;
+ let parent_hash = completed.payload_base().parent_hash();
trace!(
target: "flashblocks",
@@ -114,7 +127,7 @@ impl SequenceManager {
}
/// Returns the current pending sequence for inspection.
- pub(crate) const fn pending(&self) -> &FlashBlockPendingSequence {
+ pub(crate) const fn pending(&self) -> &FlashBlockPendingSequence
{
&self.pending
}
@@ -129,21 +142,21 @@ impl SequenceManager {
&mut self,
local_tip_hash: B256,
local_tip_timestamp: u64,
- ) -> Option>>>> {
+ ) -> Option> {
// Try to find a buildable sequence: (base, last_fb, transactions, cached_state,
// source_name)
let (base, last_flashblock, transactions, cached_state, source_name) =
// Priority 1: Try current pending sequence
- if let Some(base) = self.pending.payload_base().filter(|b| b.parent_hash == local_tip_hash) {
- let cached_state = self.pending.take_cached_reads().map(|r| (base.parent_hash, r));
- let last_fb = self.pending.last_flashblock()?;
+ if let Some(base) = self.pending.payload_base().filter(|b| b.parent_hash() == local_tip_hash) {
+ let cached_state = self.pending.take_cached_reads().map(|r| (base.parent_hash(), r));
+ let last_fb = self.pending.last_flashblock()?.clone();
let transactions = self.pending_transactions.clone();
(base, last_fb, transactions, cached_state, "pending")
}
// Priority 2: Try cached sequence with exact parent match
- else if let Some((cached, txs)) = self.completed_cache.iter().find(|(c, _)| c.payload_base().parent_hash == local_tip_hash) {
- let base = cached.payload_base().clone();
- let last_fb = cached.last();
+ else if let Some((cached, txs)) = self.completed_cache.iter().find(|(c, _)| c.payload_base().parent_hash() == local_tip_hash) {
+ let base = cached.payload_base();
+ let last_fb = cached.last().clone();
let transactions = txs.clone();
let cached_state = None;
(base, last_fb, transactions, cached_state, "cached")
@@ -179,20 +192,20 @@ impl SequenceManager {
// compute the state root, causing FlashblockConsensusClient to lack precomputed state for
// engine_newPayload. This is safe: we still have op-node as backstop to maintain
// chain progression.
- let block_time_ms = (base.timestamp - local_tip_timestamp) * 1000;
+ let block_time_ms = (base.timestamp() - local_tip_timestamp) * 1000;
let expected_final_flashblock = block_time_ms / FLASHBLOCK_BLOCK_TIME;
let compute_state_root = self.compute_state_root &&
- last_flashblock.diff.state_root.is_zero() &&
- last_flashblock.index >= expected_final_flashblock.saturating_sub(1);
+ last_flashblock.diff().state_root().is_zero() &&
+ last_flashblock.index() >= expected_final_flashblock.saturating_sub(1);
trace!(
target: "flashblocks",
- block_number = base.block_number,
+ block_number = base.block_number(),
source = source_name,
- flashblock_index = last_flashblock.index,
+ flashblock_index = last_flashblock.index(),
expected_final_flashblock,
compute_state_root_enabled = self.compute_state_root,
- state_root_is_zero = last_flashblock.diff.state_root.is_zero(),
+ state_root_is_zero = last_flashblock.diff().state_root().is_zero(),
will_compute_state_root = compute_state_root,
"Building from flashblock sequence"
);
@@ -201,8 +214,8 @@ impl SequenceManager {
base,
transactions,
cached_state,
- last_flashblock_index: last_flashblock.index,
- last_flashblock_hash: last_flashblock.diff.block_hash,
+ last_flashblock_index: last_flashblock.index(),
+ last_flashblock_hash: last_flashblock.diff().block_hash(),
compute_state_root,
})
}
@@ -227,7 +240,7 @@ impl SequenceManager {
});
// Update pending sequence with execution results
- if self.pending.payload_base().is_some_and(|base| base.parent_hash == parent_hash) {
+ if self.pending.payload_base().is_some_and(|base| base.parent_hash() == parent_hash) {
self.pending.set_execution_outcome(execution_outcome);
self.pending.set_cached_reads(cached_reads);
trace!(
@@ -241,7 +254,7 @@ impl SequenceManager {
else if let Some((cached, _)) = self
.completed_cache
.iter_mut()
- .find(|(c, _)| c.payload_base().parent_hash == parent_hash)
+ .find(|(c, _)| c.payload_base().parent_hash() == parent_hash)
{
// Only re-broadcast if we computed new information (state_root was missing).
// If sequencer already provided state_root, we already broadcast in insert_flashblock,
@@ -266,19 +279,18 @@ impl SequenceManager {
#[cfg(test)]
mod tests {
use super::*;
- use crate::test_utils::TestFlashBlockFactory;
+ use crate::{test_utils::TestFlashBlockFactory, FlashBlock};
use alloy_primitives::B256;
- use op_alloy_consensus::OpTxEnvelope;
#[test]
fn test_sequence_manager_new() {
- let manager: SequenceManager = SequenceManager::new(true);
+ let manager: SequenceManager = SequenceManager::new(true);
assert_eq!(manager.pending().count(), 0);
}
#[test]
fn test_insert_flashblock_creates_pending_sequence() {
- let mut manager: SequenceManager = SequenceManager::new(true);
+ let mut manager: SequenceManager = SequenceManager::new(true);
let factory = TestFlashBlockFactory::new();
let fb0 = factory.flashblock_at(0).build();
@@ -290,7 +302,7 @@ mod tests {
#[test]
fn test_insert_flashblock_caches_completed_sequence() {
- let mut manager: SequenceManager = SequenceManager::new(true);
+ let mut manager: SequenceManager = SequenceManager::new(true);
let factory = TestFlashBlockFactory::new();
// Build first sequence
@@ -314,7 +326,7 @@ mod tests {
#[test]
fn test_next_buildable_args_returns_none_when_empty() {
- let mut manager: SequenceManager = SequenceManager::new(true);
+ let mut manager: SequenceManager = SequenceManager::new(true);
let local_tip_hash = B256::random();
let local_tip_timestamp = 1000;
@@ -324,7 +336,7 @@ mod tests {
#[test]
fn test_next_buildable_args_matches_pending_parent() {
- let mut manager: SequenceManager = SequenceManager::new(true);
+ let mut manager: SequenceManager = SequenceManager::new(true);
let factory = TestFlashBlockFactory::new();
let fb0 = factory.flashblock_at(0).build();
@@ -340,7 +352,7 @@ mod tests {
#[test]
fn test_next_buildable_args_returns_none_when_parent_mismatch() {
- let mut manager: SequenceManager = SequenceManager::new(true);
+ let mut manager: SequenceManager = SequenceManager::new(true);
let factory = TestFlashBlockFactory::new();
let fb0 = factory.flashblock_at(0).build();
@@ -354,7 +366,7 @@ mod tests {
#[test]
fn test_next_buildable_args_prefers_pending_over_cached() {
- let mut manager: SequenceManager = SequenceManager::new(true);
+ let mut manager: SequenceManager = SequenceManager::new(true);
let factory = TestFlashBlockFactory::new();
// Create and finalize first sequence
@@ -373,7 +385,7 @@ mod tests {
#[test]
fn test_next_buildable_args_finds_cached_sequence() {
- let mut manager: SequenceManager = SequenceManager::new(true);
+ let mut manager: SequenceManager = SequenceManager::new(true);
let factory = TestFlashBlockFactory::new();
// Build and cache first sequence
@@ -396,7 +408,7 @@ mod tests {
#[test]
fn test_compute_state_root_logic_near_expected_final() {
- let mut manager: SequenceManager = SequenceManager::new(true);
+ let mut manager: SequenceManager = SequenceManager::new(true);
let block_time = 2u64;
let factory = TestFlashBlockFactory::new().with_block_time(block_time);
@@ -420,7 +432,7 @@ mod tests {
#[test]
fn test_no_compute_state_root_when_provided_by_sequencer() {
- let mut manager: SequenceManager = SequenceManager::new(true);
+ let mut manager: SequenceManager = SequenceManager::new(true);
let block_time = 2u64;
let factory = TestFlashBlockFactory::new().with_block_time(block_time);
@@ -437,7 +449,7 @@ mod tests {
#[test]
fn test_no_compute_state_root_when_disabled() {
- let mut manager: SequenceManager = SequenceManager::new(false);
+ let mut manager: SequenceManager = SequenceManager::new(false);
let block_time = 2u64;
let factory = TestFlashBlockFactory::new().with_block_time(block_time);
@@ -461,7 +473,7 @@ mod tests {
#[test]
fn test_cache_ring_buffer_evicts_oldest() {
- let mut manager: SequenceManager = SequenceManager::new(true);
+ let mut manager: SequenceManager = SequenceManager::new(true);
let factory = TestFlashBlockFactory::new();
// Fill cache with 4 sequences (cache size is 3, so oldest should be evicted)
diff --git a/crates/optimism/flashblocks/src/consensus.rs b/crates/optimism/flashblocks/src/consensus.rs
index 0b502c07387..42a03d9945c 100644
--- a/crates/optimism/flashblocks/src/consensus.rs
+++ b/crates/optimism/flashblocks/src/consensus.rs
@@ -1,7 +1,10 @@
-use crate::{FlashBlockCompleteSequence, FlashBlockCompleteSequenceRx};
+use crate::{
+ traits::FlashblockPayloadBase, FlashBlock, FlashBlockCompleteSequence,
+ FlashBlockCompleteSequenceRx,
+};
use alloy_primitives::B256;
use alloy_rpc_types_engine::PayloadStatusEnum;
-use op_alloy_rpc_types_engine::OpExecutionData;
+use op_alloy_rpc_types_engine::{OpExecutionData, OpFlashblockPayload};
use reth_engine_primitives::ConsensusEngineHandle;
use reth_optimism_payload_builder::OpPayloadTypes;
use reth_payload_primitives::{EngineApiMessageVersion, ExecutionPayload, PayloadTypes};
@@ -22,18 +25,19 @@ where
/// Handle to execution client.
engine_handle: ConsensusEngineHandle
,
/// Receiver for completed flashblock sequences from `FlashBlockService`.
- sequence_receiver: FlashBlockCompleteSequenceRx,
+ sequence_receiver: FlashBlockCompleteSequenceRx,
}
impl
FlashBlockConsensusClient
where
P: PayloadTypes,
- P::ExecutionData: for<'a> TryFrom<&'a FlashBlockCompleteSequence, Error: std::fmt::Display>,
+ P::ExecutionData:
+ for<'a> TryFrom<&'a FlashBlockCompleteSequence, Error: std::fmt::Display>,
{
/// Create a new `FlashBlockConsensusClient` with the given Op engine and sequence receiver.
pub const fn new(
engine_handle: ConsensusEngineHandle
,
- sequence_receiver: FlashBlockCompleteSequenceRx,
+ sequence_receiver: FlashBlockCompleteSequenceRx,
) -> eyre::Result {
Ok(Self { engine_handle, sequence_receiver })
}
@@ -44,12 +48,12 @@ where
/// in which case this returns the `parent_hash` instead to drive the chain forward.
///
/// Returns the block hash to use for FCU (either the new block or parent).
- async fn submit_new_payload(&self, sequence: &FlashBlockCompleteSequence) -> B256 {
+ async fn submit_new_payload(&self, sequence: &FlashBlockCompleteSequence) -> B256 {
let payload = match P::ExecutionData::try_from(sequence) {
Ok(payload) => payload,
Err(err) => {
trace!(target: "flashblocks", %err, "Failed payload conversion, using parent hash");
- return sequence.payload_base().parent_hash;
+ return sequence.payload_base().parent_hash();
}
};
@@ -93,11 +97,11 @@ where
async fn submit_forkchoice_update(
&self,
head_block_hash: B256,
- sequence: &FlashBlockCompleteSequence,
+ sequence: &FlashBlockCompleteSequence,
) {
let block_number = sequence.block_number();
- let safe_hash = sequence.payload_base().parent_hash;
- let finalized_hash = sequence.payload_base().parent_hash;
+ let safe_hash = sequence.payload_base().parent_hash();
+ let finalized_hash = sequence.payload_base().parent_hash();
let fcu_state = alloy_rpc_types_engine::ForkchoiceState {
head_block_hash,
safe_block_hash: safe_hash,
@@ -157,10 +161,10 @@ where
}
}
-impl TryFrom<&FlashBlockCompleteSequence> for OpExecutionData {
+impl TryFrom<&FlashBlockCompleteSequence> for OpExecutionData {
type Error = &'static str;
- fn try_from(sequence: &FlashBlockCompleteSequence) -> Result {
+ fn try_from(sequence: &FlashBlockCompleteSequence) -> Result {
let mut data = Self::from_flashblocks_unchecked(sequence);
// If execution outcome is available, use the computed state_root and block_hash.
@@ -320,7 +324,7 @@ mod tests {
assert!(conversion_result.is_err());
// In the actual run loop, submit_new_payload would return parent_hash
- assert_eq!(sequence.payload_base().parent_hash, parent_hash);
+ assert_eq!(sequence.payload_base().parent_hash(), parent_hash);
}
#[test]
@@ -357,7 +361,7 @@ mod tests {
let sequence = FlashBlockCompleteSequence::new(vec![fb0], None).unwrap();
// Verify the expected forkchoice state
- assert_eq!(sequence.payload_base().parent_hash, parent_hash);
+ assert_eq!(sequence.payload_base().parent_hash(), parent_hash);
}
#[test]
@@ -389,7 +393,7 @@ mod tests {
let sequence = FlashBlockCompleteSequence::new(vec![fb0], None).unwrap();
// The head_block_hash for FCU would be parent_hash (fallback)
- assert_eq!(sequence.payload_base().parent_hash, parent_hash);
+ assert_eq!(sequence.payload_base().parent_hash(), parent_hash);
}
}
@@ -426,7 +430,7 @@ mod tests {
assert!(conversion.is_err());
// But FCU should still happen with parent_hash
- assert!(sequence.payload_base().parent_hash != B256::ZERO);
+ assert!(sequence.payload_base().parent_hash() != B256::ZERO);
}
#[test]
diff --git a/crates/optimism/flashblocks/src/lib.rs b/crates/optimism/flashblocks/src/lib.rs
index 6c5d9c1e86e..cfe18d2ea88 100644
--- a/crates/optimism/flashblocks/src/lib.rs
+++ b/crates/optimism/flashblocks/src/lib.rs
@@ -14,6 +14,11 @@ use std::sync::Arc;
// Included to enable serde feature for OpReceipt type used transitively
use reth_optimism_primitives as _;
+pub mod traits;
+pub use traits::{FlashblockDiff, FlashblockPayload, FlashblockPayloadBase};
+
+mod op_impl;
+
mod consensus;
pub use consensus::FlashBlockConsensusClient;
@@ -21,7 +26,9 @@ mod payload;
pub use payload::{FlashBlock, PendingFlashBlock};
mod sequence;
-pub use sequence::{FlashBlockCompleteSequence, FlashBlockPendingSequence};
+pub use sequence::{
+ FlashBlockCompleteSequence, FlashBlockPendingSequence, SequenceExecutionOutcome,
+};
mod service;
pub use service::{FlashBlockBuildInfo, FlashBlockService};
@@ -34,49 +41,43 @@ mod cache;
mod test_utils;
mod ws;
-pub use ws::{WsConnect, WsFlashBlockStream};
+pub use ws::{FlashBlockDecoder, WsConnect, WsFlashBlockStream};
-/// Receiver of the most recent [`PendingFlashBlock`] built out of [`FlashBlock`]s.
-///
-/// [`FlashBlock`]: crate::FlashBlock
+/// Receiver of the most recent [`PendingFlashBlock`] built out of flashblocks.
pub type PendingBlockRx = tokio::sync::watch::Receiver