Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 36 additions & 24 deletions crates/op-rbuilder/src/builders/flashblocks/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,24 @@ use alloy_consensus::{
BlockBody, EMPTY_OMMER_ROOT_HASH, Header, constants::EMPTY_WITHDRAWALS, proofs,
};
use alloy_eips::{Encodable2718, eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE};
use alloy_primitives::{Address, B256, U256, map::foldhash::HashMap};
use alloy_primitives::{Address, B256, U256};
use core::time::Duration;
use either::Either;
use eyre::WrapErr as _;
use op_alloy_rpc_types_engine::{
OpFlashblockPayload, OpFlashblockPayloadBase, OpFlashblockPayloadDelta,
OpFlashblockPayloadMetadata,
};
use reth::payload::PayloadBuilderAttributes;
use reth_basic_payload_builder::BuildOutcome;
use reth_chainspec::EthChainSpec;
use reth_evm::{ConfigureEvm, execute::BlockBuilder};
use reth_node_api::{Block, NodePrimitives, PayloadBuilderError};
use reth_node_api::{Block, PayloadBuilderError};
use reth_optimism_consensus::{calculate_receipt_root_no_memo_optimism, isthmus};
use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes};
use reth_optimism_forks::OpHardforks;
use reth_optimism_node::{OpBuiltPayload, OpPayloadBuilderAttributes};
use reth_optimism_primitives::{OpPrimitives, OpReceipt, OpTransactionSigned};
use reth_optimism_primitives::{OpReceipt, OpTransactionSigned};
use reth_payload_primitives::BuiltPayloadExecutedBlock;
use reth_payload_util::BestPayloadTransactions;
use reth_primitives_traits::RecoveredBlock;
Expand All @@ -43,11 +47,8 @@ use reth_revm::{
use reth_transaction_pool::TransactionPool;
use reth_trie::{HashedPostState, updates::TrieUpdates};
use revm::Database;
use rollup_boost::{
ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, FlashblocksPayloadV1,
};
use serde::{Deserialize, Serialize};
use std::{
collections::BTreeMap,
ops::{Div, Rem},
sync::Arc,
time::Instant,
Expand All @@ -56,6 +57,24 @@ use tokio::sync::mpsc;
use tokio_util::sync::CancellationToken;
use tracing::{debug, error, info, metadata::Level, span, warn};

/// Converts a reth OpReceipt to an op-alloy OpReceipt
/// TODO: remove this once reth updates to use the op-alloy defined type as well.
fn convert_receipt(receipt: &OpReceipt) -> op_alloy_consensus::OpReceipt {
match receipt {
OpReceipt::Legacy(r) => op_alloy_consensus::OpReceipt::Legacy(r.clone()),
OpReceipt::Eip2930(r) => op_alloy_consensus::OpReceipt::Eip2930(r.clone()),
OpReceipt::Eip1559(r) => op_alloy_consensus::OpReceipt::Eip1559(r.clone()),
OpReceipt::Eip7702(r) => op_alloy_consensus::OpReceipt::Eip7702(r.clone()),
OpReceipt::Deposit(r) => {
op_alloy_consensus::OpReceipt::Deposit(op_alloy_consensus::OpDepositReceipt {
inner: r.inner.clone(),
deposit_nonce: r.deposit_nonce,
deposit_receipt_version: r.deposit_receipt_version,
})
}
}
}
Comment on lines +62 to +76
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

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

The convert_receipt function performs a deep clone of each receipt variant. This could impact performance in scenarios with many transactions. Consider whether a reference-based approach or shared ownership (Arc) could be used instead, especially since receipts are being collected into a BTreeMap at line 1131.

Copilot uses AI. Check for mistakes.

type NextBestFlashblocksTxs<Pool> = BestFlashblocksTxs<
<Pool as TransactionPool>::Transaction,
Box<
Expand Down Expand Up @@ -916,13 +935,6 @@ where
}
}

#[derive(Debug, Serialize, Deserialize)]
struct FlashblocksMetadata {
receipts: HashMap<B256, <OpPrimitives as NodePrimitives>::Receipt>,
new_account_balances: HashMap<Address, U256>,
block_number: u64,
}

fn execute_pre_steps<DB, ExtraCtx>(
state: &mut State<DB>,
ctx: &OpPayloadBuilderCtx<ExtraCtx>,
Expand All @@ -948,7 +960,7 @@ pub(super) fn build_block<DB, P, ExtraCtx>(
ctx: &OpPayloadBuilderCtx<ExtraCtx>,
info: &mut ExecutionInfo<FlashblocksExecutionInfo>,
calculate_state_root: bool,
) -> Result<(OpBuiltPayload, FlashblocksPayloadV1), PayloadBuilderError>
) -> Result<(OpBuiltPayload, OpFlashblockPayload), PayloadBuilderError>
where
DB: Database<Error = ProviderError> + AsRef<P>,
P: StateRootProvider + HashedPostStateProvider + StorageRootProvider,
Expand Down Expand Up @@ -1115,16 +1127,16 @@ where
let receipts_with_hash = new_transactions
.iter()
.zip(new_receipts.iter())
.map(|(tx, receipt)| (tx.tx_hash(), receipt.clone()))
.collect::<HashMap<B256, OpReceipt>>();
.map(|(tx, receipt)| (tx.tx_hash(), convert_receipt(receipt)))
.collect::<BTreeMap<B256, op_alloy_consensus::OpReceipt>>();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why would we use BTreeMap with B256?

Copy link
Author

@0x00101010 0x00101010 Nov 20, 2025

Choose a reason for hiding this comment

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

To help with cases where you need ordered traversal of receipts, perf wise should be ok since this list is not super big

let new_account_balances = state
.bundle_state
.state
.iter()
.filter_map(|(address, account)| account.info.as_ref().map(|info| (*address, info.balance)))
.collect::<HashMap<Address, U256>>();
.collect::<BTreeMap<Address, U256>>();
Comment on lines +1130 to +1137
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

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

[nitpick] The change from HashMap to BTreeMap for receipts and account balances introduces a performance trade-off. While BTreeMap ensures deterministic ordering (which is likely desirable for serialization), it has O(log n) lookup time compared to HashMap's O(1) average case. If the new OpFlashblockPayloadMetadata type requires ordered maps for compatibility with the op-alloy types, this is correct. Otherwise, consider whether the ordering guarantee is worth the performance cost for large transaction sets.

Copilot uses AI. Check for mistakes.

let metadata: FlashblocksMetadata = FlashblocksMetadata {
let metadata = OpFlashblockPayloadMetadata {
receipts: receipts_with_hash,
new_account_balances,
block_number: ctx.parent().number + 1,
Expand All @@ -1133,10 +1145,10 @@ where
let (_, blob_gas_used) = ctx.blob_fields(info);

// Prepare the flashblocks message
let fb_payload = FlashblocksPayloadV1 {
let fb_payload = OpFlashblockPayload {
payload_id: ctx.payload_id(),
index: 0,
base: Some(ExecutionPayloadBaseV1 {
base: Some(OpFlashblockPayloadBase {
parent_beacon_block_root: ctx
.attributes()
.payload_attributes
Expand All @@ -1149,9 +1161,9 @@ where
gas_limit: ctx.block_gas_limit(),
timestamp: ctx.attributes().payload_attributes.timestamp,
extra_data: ctx.extra_data()?,
base_fee_per_gas: ctx.base_fee().try_into().unwrap(),
base_fee_per_gas: U256::from(ctx.base_fee()),
}),
diff: ExecutionPayloadFlashblockDeltaV1 {
diff: OpFlashblockPayloadDelta {
state_root,
receipts_root,
logs_bloom,
Expand All @@ -1162,7 +1174,7 @@ where
withdrawals_root: withdrawals_root.unwrap_or_default(),
blob_gas_used,
},
metadata: serde_json::to_value(&metadata).unwrap_or_default(),
metadata,
};

// We clean bundle and place initial state transaction back
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use alloy_evm::eth::receipt_builder::ReceiptBuilderCtx;
use alloy_primitives::B64;
use eyre::{WrapErr as _, bail};
use op_alloy_consensus::OpTxEnvelope;
use op_alloy_rpc_types_engine::OpFlashblockPayload;
use reth::revm::{State, database::StateProviderDatabase};
use reth_basic_payload_builder::PayloadConfig;
use reth_evm::FromRecoveredTx;
Expand All @@ -19,7 +20,6 @@ use reth_optimism_node::{OpEngineTypes, OpPayloadBuilderAttributes};
use reth_optimism_payload_builder::OpBuiltPayload;
use reth_optimism_primitives::{OpReceipt, OpTransactionSigned};
use reth_payload_builder::EthPayloadBuilderAttributes;
use rollup_boost::FlashblocksPayloadV1;
use std::sync::Arc;
use tokio::sync::mpsc;
use tracing::warn;
Expand Down Expand Up @@ -135,7 +135,7 @@ fn execute_flashblock<Client>(
ctx: OpPayloadSyncerCtx,
client: Client,
cancel: tokio_util::sync::CancellationToken,
) -> eyre::Result<(OpBuiltPayload, FlashblocksPayloadV1)>
) -> eyre::Result<(OpBuiltPayload, OpFlashblockPayload)>
where
Client: ClientBounds,
{
Expand Down
6 changes: 3 additions & 3 deletions crates/op-rbuilder/src/builders/flashblocks/wspub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use core::{
};
use futures::SinkExt;
use futures_util::StreamExt;
use rollup_boost::FlashblocksPayloadV1;
use op_alloy_rpc_types_engine::OpFlashblockPayload;
use std::{io, net::TcpListener, sync::Arc};
use tokio::{
net::TcpStream,
Expand All @@ -25,7 +25,7 @@ use crate::metrics::OpRBuilderMetrics;
/// A WebSockets publisher that accepts connections from client websockets and broadcasts to them
/// updates about new flashblocks. It maintains a count of sent messages and active subscriptions.
///
/// This is modelled as a `futures::Sink` that can be used to send `FlashblocksPayloadV1` messages.
/// This is modelled as a `futures::Sink` that can be used to send `OpFlashblockPayload` messages.
pub(super) struct WebSocketPublisher {
sent: Arc<AtomicUsize>,
subs: Arc<AtomicUsize>,
Expand Down Expand Up @@ -59,7 +59,7 @@ impl WebSocketPublisher {
})
}

pub(super) fn publish(&self, payload: &FlashblocksPayloadV1) -> io::Result<usize> {
pub(super) fn publish(&self, payload: &OpFlashblockPayload) -> io::Result<usize> {
// Serialize the payload to a UTF-8 string
// serialize only once, then just copy around only a pointer
// to the serialized data for each subscription.
Expand Down