Skip to content
8 changes: 8 additions & 0 deletions crates/op-rbuilder/src/args/op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,14 @@ pub struct FlashblocksArgs {
env = "FLASHBLOCK_LEEWAY_TIME"
)]
pub flashblocks_leeway_time: u64,

/// Should we calculate state root for each flashblock
#[arg(
long = "flashblocks.calculate-state-root",
default_value = "true",
env = "FLASHBLOCKS_CALCULATE_STATE_ROOT"
)]
pub flashblocks_calculate_state_root: bool,
}

impl Default for FlashblocksArgs {
Expand Down
7 changes: 7 additions & 0 deletions crates/op-rbuilder/src/builders/flashblocks/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ pub struct FlashblocksConfig {

/// Disables dynamic flashblocks number adjustment based on FCU arrival time
pub fixed: bool,

/// Should we calculate state root for each flashblock
pub calculate_state_root: bool,
}

impl Default for FlashblocksConfig {
Expand All @@ -37,6 +40,7 @@ impl Default for FlashblocksConfig {
interval: Duration::from_millis(250),
leeway_time: Duration::from_millis(50),
fixed: false,
calculate_state_root: true,
}
}
}
Expand All @@ -56,11 +60,14 @@ impl TryFrom<OpRbuilderArgs> for FlashblocksConfig {

let fixed = args.flashblocks.flashblocks_fixed;

let calculate_state_root = args.flashblocks.flashblocks_calculate_state_root;

Ok(Self {
ws_addr,
interval,
leeway_time,
fixed,
calculate_state_root,
})
}
}
Expand Down
79 changes: 47 additions & 32 deletions crates/op-rbuilder/src/builders/flashblocks/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ use reth_provider::{
use reth_revm::{
State, database::StateProviderDatabase, db::states::bundle_state::BundleRetention,
};
use reth_trie::{HashedPostState, updates::TrieUpdates};
use revm::Database;
use rollup_boost::{
ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, FlashblocksPayloadV1,
Expand Down Expand Up @@ -222,6 +223,7 @@ where

let chain_spec = self.client.chain_spec();
let timestamp = config.attributes.timestamp();
let calculate_state_root = self.config.specific.calculate_state_root;
let block_env_attributes = OpNextBlockEnvAttributes {
timestamp,
suggested_fee_recipient: config.attributes.suggested_fee_recipient(),
Expand Down Expand Up @@ -299,18 +301,20 @@ where
ctx.add_builder_tx(&mut info, &mut state, builder_tx_gas, message.clone());
}

let (payload, fb_payload) = build_block(&mut state, &ctx, &mut info)?;
let (payload, fb_payload) = build_block(&mut state, &ctx, &mut info, calculate_state_root)?;

best_payload.set(payload.clone());
self.send_payload_to_engine(payload);

let flashblock_byte_size = self
.ws_pub
.publish(&fb_payload)
.map_err(PayloadBuilderError::other)?;
ctx.metrics
.flashblock_byte_size_histogram
.record(flashblock_byte_size as f64);
// not emitting flashblock if no_tx_pool in FCU, it's just syncing
if !ctx.attributes().no_tx_pool {
let flashblock_byte_size = self
.ws_pub
.publish(&fb_payload)
.map_err(PayloadBuilderError::other)?;
ctx.metrics
.flashblock_byte_size_histogram
.record(flashblock_byte_size as f64);
}

info!(
target: "payload_builder",
Expand Down Expand Up @@ -513,7 +517,8 @@ where
};

let total_block_built_duration = Instant::now();
let build_result = build_block(&mut state, &ctx, &mut info);
let build_result =
build_block(&mut state, &ctx, &mut info, calculate_state_root);
let total_block_built_duration = total_block_built_duration.elapsed();
ctx.metrics
.total_block_built_duration
Expand Down Expand Up @@ -821,6 +826,7 @@ fn build_block<DB, P, ExtraCtx>(
state: &mut State<DB>,
ctx: &OpPayloadBuilderCtx<ExtraCtx>,
info: &mut ExecutionInfo<ExtraExecutionInfo>,
calculate_state_root: bool,
) -> Result<(OpBuiltPayload, FlashblocksPayloadV1), PayloadBuilderError>
where
DB: Database<Error = ProviderError> + AsRef<P>,
Expand Down Expand Up @@ -865,28 +871,37 @@ where
// TODO: maybe recreate state with bundle in here
// // calculate the state root
let state_root_start_time = Instant::now();
let state_provider = state.database.as_ref();
let hashed_state = state_provider.hashed_post_state(execution_outcome.state());
let (state_root, trie_output) = {
state
.database
.as_ref()
.state_root_with_updates(hashed_state.clone())
.inspect_err(|err| {
warn!(target: "payload_builder",
parent_header=%ctx.parent().hash(),
%err,
"failed to calculate state root for payload"
);
})?
};
let state_root_calculation_time = state_root_start_time.elapsed();
ctx.metrics
.state_root_calculation_duration
.record(state_root_calculation_time);
ctx.metrics
.state_root_calculation_gauge
.set(state_root_calculation_time);
let mut state_root = B256::ZERO;
let mut trie_output = TrieUpdates::default();
let mut hashed_state = HashedPostState::default();

if calculate_state_root {
let state_provider = state.database.as_ref();
let _hashed_state = state_provider.hashed_post_state(execution_outcome.state());
let (_state_root, _trie_output) = {
state
.database
.as_ref()
.state_root_with_updates(hashed_state.clone())
.inspect_err(|err| {
warn!(target: "payload_builder",
parent_header=%ctx.parent().hash(),
%err,
"failed to calculate state root for payload"
);
})?
};
let state_root_calculation_time = state_root_start_time.elapsed();
ctx.metrics
.state_root_calculation_duration
.record(state_root_calculation_time);
ctx.metrics
.state_root_calculation_gauge
.set(state_root_calculation_time);
state_root = _state_root;
trie_output = _trie_output;
hashed_state = _hashed_state;
}

let mut requests_hash = None;
let withdrawals_root = if ctx
Expand Down
52 changes: 52 additions & 0 deletions crates/op-rbuilder/src/tests/flashblocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use crate::{
flashblocks_block_time: 200,
flashblocks_leeway_time: 100,
flashblocks_fixed: false,
flashblocks_calculate_state_root: true,
},
..Default::default()
})]
Expand Down Expand Up @@ -89,6 +90,7 @@ async fn smoke_dynamic_base(rbuilder: LocalInstance) -> eyre::Result<()> {
flashblocks_block_time: 200,
flashblocks_leeway_time: 100,
flashblocks_fixed: false,
flashblocks_calculate_state_root: true,
},
..Default::default()
})]
Expand Down Expand Up @@ -157,6 +159,7 @@ async fn smoke_dynamic_unichain(rbuilder: LocalInstance) -> eyre::Result<()> {
flashblocks_block_time: 200,
flashblocks_leeway_time: 50,
flashblocks_fixed: true,
flashblocks_calculate_state_root: true,
},
..Default::default()
})]
Expand Down Expand Up @@ -225,6 +228,7 @@ async fn smoke_classic_unichain(rbuilder: LocalInstance) -> eyre::Result<()> {
flashblocks_block_time: 200,
flashblocks_leeway_time: 50,
flashblocks_fixed: true,
flashblocks_calculate_state_root: true,
},
..Default::default()
})]
Expand Down Expand Up @@ -293,6 +297,7 @@ async fn smoke_classic_base(rbuilder: LocalInstance) -> eyre::Result<()> {
flashblocks_block_time: 200,
flashblocks_leeway_time: 100,
flashblocks_fixed: false,
flashblocks_calculate_state_root: true,
},
..Default::default()
})]
Expand Down Expand Up @@ -363,6 +368,7 @@ async fn unichain_dynamic_with_lag(rbuilder: LocalInstance) -> eyre::Result<()>
flashblocks_block_time: 200,
flashblocks_leeway_time: 0,
flashblocks_fixed: false,
flashblocks_calculate_state_root: true,
},
..Default::default()
})]
Expand Down Expand Up @@ -430,6 +436,7 @@ async fn dynamic_with_full_block_lag(rbuilder: LocalInstance) -> eyre::Result<()
flashblocks_block_time: 200,
flashblocks_leeway_time: 100,
flashblocks_fixed: false,
flashblocks_calculate_state_root: true,
},
..Default::default()
})]
Expand Down Expand Up @@ -525,6 +532,7 @@ async fn test_flashblock_min_filtering(rbuilder: LocalInstance) -> eyre::Result<
flashblocks_block_time: 200,
flashblocks_leeway_time: 100,
flashblocks_fixed: false,
flashblocks_calculate_state_root: true,
},
..Default::default()
})]
Expand Down Expand Up @@ -590,3 +598,47 @@ async fn test_flashblock_max_filtering(rbuilder: LocalInstance) -> eyre::Result<

Ok(())
}

#[rb_test(flashblocks, args = OpRbuilderArgs {
chain_block_time: 1000,
flashblocks: FlashblocksArgs {
enabled: true,
flashblocks_port: 1239,
flashblocks_addr: "127.0.0.1".into(),
flashblocks_block_time: 200,
flashblocks_leeway_time: 100,
flashblocks_fixed: false,
flashblocks_calculate_state_root: false,
},
..Default::default()
})]
async fn test_flashblocks_no_state_root_calculation(rbuilder: LocalInstance) -> eyre::Result<()> {
use alloy_primitives::B256;

let driver = rbuilder.driver().await?;

// Send a transaction to ensure block has some activity
let _tx = driver
.create_transaction()
.random_valid_transfer()
.send()
.await?;

// Build a block with current timestamp (not historical) and calculate_state_root: false
let block = driver.build_new_block_with_current_timestamp(None).await?;

// Verify that flashblocks are still produced (block should have transactions)
assert!(
block.transactions.len() > 2,
"Block should contain transactions"
); // deposit + builder tx + user tx

// Verify that state root is not calculated (should be zero)
assert_eq!(
block.header.state_root,
B256::ZERO,
"State root should be zero when calculate_state_root is false"
);

Ok(())
}