Skip to content

Commit 6bcd1c9

Browse files
authored
fix: don't build flashblocks with more gas than block gas limit (#567)
## 📝 Summary Currently it's possible for the builder to make more Flashblocks than intended during a block (likely due to delays). If more flashblocks are made than desired (e.g. 11 flashblocks for a 2s block w/ 200ms Flashblocks), the 11th flashblock will have a gas limit that is over the blocks gas limit and will cause invalid blocks on the sequencer. ## 💡 Motivation and Context If the builder produces a full block that uses more gas than the gas limit, the local EL client will treat it as invalid. --- ## ✅ I have completed the following steps: * [ ] Run `make lint` * [x] Run `make test` * [x] Added tests (if applicable)
1 parent a55051a commit 6bcd1c9

4 files changed

Lines changed: 121 additions & 9 deletions

File tree

crates/op-rbuilder/src/integration/integration_test.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,4 +490,92 @@ mod tests {
490490

491491
Ok(())
492492
}
493+
494+
#[tokio::test]
495+
#[cfg(feature = "flashblocks")]
496+
async fn integration_test_flashblocks_respects_gas_limit() -> eyre::Result<()> {
497+
// This is a simple test using the integration framework to test that the chain
498+
// produces blocks.
499+
let mut framework =
500+
IntegrationFramework::new("integration_test_flashblocks_respects_gas_limit").unwrap();
501+
502+
// we are going to use a genesis file pre-generated before the test
503+
let mut genesis_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
504+
genesis_path.push("../../genesis.json");
505+
assert!(genesis_path.exists());
506+
507+
let block_time_ms = 1000;
508+
let flashblock_time_ms = 100;
509+
510+
// create the builder
511+
let builder_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string());
512+
let op_rbuilder_config = OpRbuilderConfig::new()
513+
.chain_config_path(genesis_path.clone())
514+
.data_dir(builder_data_dir)
515+
.auth_rpc_port(1244)
516+
.network_port(1245)
517+
.http_port(1248)
518+
.with_builder_private_key(BUILDER_PRIVATE_KEY)
519+
.with_flashblocks_ws_url("localhost:1249")
520+
.with_chain_block_time(block_time_ms)
521+
.with_flashbots_block_time(flashblock_time_ms);
522+
523+
// create the validation reth node
524+
let reth_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string());
525+
let reth = OpRethConfig::new()
526+
.chain_config_path(genesis_path)
527+
.data_dir(reth_data_dir)
528+
.auth_rpc_port(1246)
529+
.network_port(1247);
530+
531+
framework.start("op-reth", &reth).await.unwrap();
532+
533+
let op_rbuilder = framework
534+
.start("op-rbuilder", &op_rbuilder_config)
535+
.await
536+
.unwrap();
537+
538+
let engine_api = EngineApi::new("http://localhost:1244").unwrap();
539+
let validation_api = EngineApi::new("http://localhost:1246").unwrap();
540+
541+
let mut generator = BlockGenerator::new(
542+
&engine_api,
543+
Some(&validation_api),
544+
false,
545+
block_time_ms / 1000,
546+
None,
547+
);
548+
generator.init().await?;
549+
550+
let provider = ProviderBuilder::<Identity, Identity, Optimism>::default()
551+
.on_http("http://localhost:1248".parse()?);
552+
553+
// Delay the payload building by 4s, ensure that the correct number of flashblocks are built
554+
let block_hash = generator.generate_block_with_delay(4).await?;
555+
556+
// query the block and the transactions inside the block
557+
let block = provider
558+
.get_block_by_hash(block_hash)
559+
.await?
560+
.expect("block");
561+
562+
for hash in block.transactions.hashes() {
563+
let _ = provider
564+
.get_transaction_receipt(hash)
565+
.await?
566+
.expect("receipt");
567+
}
568+
569+
op_rbuilder
570+
.find_log_line("Processing new chain commit") // no builder tx for flashblocks builder
571+
.await?;
572+
573+
// check there's no more than 10 flashblocks log lines (2000ms / 200ms)
574+
op_rbuilder.find_log_line("Building flashblock 9").await?;
575+
op_rbuilder
576+
.find_log_line("Skipping flashblock reached target=10 idx=10")
577+
.await?;
578+
579+
Ok(())
580+
}
493581
}

crates/op-rbuilder/src/integration/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ impl ServiceInstance {
133133
if contents.contains(pattern) {
134134
Ok(())
135135
} else {
136-
Err(eyre::eyre!("Pattern not found in log file"))
136+
Err(eyre::eyre!("Pattern not found in log file: {}", pattern))
137137
}
138138
}
139139
}

crates/op-rbuilder/src/payload_builder.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,8 @@ pub struct OpPayloadBuilder<Pool, Client> {
232232
pub chain_block_time: u64,
233233
/// Flashblock block time
234234
pub flashblock_block_time: u64,
235+
/// Number of flashblocks per block
236+
pub flashblocks_per_block: u64,
235237
/// The metrics for the builder
236238
pub metrics: OpRBuilderMetrics,
237239
}
@@ -262,6 +264,7 @@ impl<Pool, Client> OpPayloadBuilder<Pool, Client> {
262264
tx,
263265
chain_block_time,
264266
flashblock_block_time,
267+
flashblocks_per_block: chain_block_time / flashblock_block_time,
265268
metrics: Default::default(),
266269
}
267270
}
@@ -423,8 +426,8 @@ where
423426
return Ok(());
424427
}
425428

426-
let gas_per_batch =
427-
ctx.block_gas_limit() / (self.chain_block_time / self.flashblock_block_time);
429+
let gas_per_batch = ctx.block_gas_limit() / self.flashblocks_per_block;
430+
428431
let mut total_gas_per_batch = gas_per_batch;
429432

430433
let mut flashblock_count = 0;
@@ -482,11 +485,22 @@ where
482485
// Exit loop if channel closed or cancelled
483486
match received {
484487
Some(()) => {
488+
if flashblock_count >= self.flashblocks_per_block {
489+
tracing::info!(
490+
target: "payload_builder",
491+
"Skipping flashblock reached target={} idx={}",
492+
self.flashblocks_per_block,
493+
flashblock_count
494+
);
495+
continue;
496+
}
497+
485498
// Continue with flashblock building
486499
tracing::info!(
487500
target: "payload_builder",
488-
"Building flashblock {}",
501+
"Building flashblock {} {}",
489502
flashblock_count,
503+
total_gas_per_batch,
490504
);
491505

492506
let flashblock_build_start_time = Instant::now();
@@ -512,7 +526,7 @@ where
512526
&mut info,
513527
&mut db,
514528
best_txs,
515-
total_gas_per_batch,
529+
total_gas_per_batch.min(ctx.block_gas_limit()),
516530
)?;
517531
ctx.metrics
518532
.payload_tx_simulation_duration

crates/op-rbuilder/src/tester/mod.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,11 @@ impl<'a> BlockGenerator<'a> {
321321
}
322322

323323
/// Helper function to submit a payload and update chain state
324-
async fn submit_payload(&mut self, transactions: Option<Vec<Bytes>>) -> eyre::Result<B256> {
324+
async fn submit_payload(
325+
&mut self,
326+
transactions: Option<Vec<Bytes>>,
327+
block_building_delay_secs: u64,
328+
) -> eyre::Result<B256> {
325329
let timestamp = SystemTime::now()
326330
.duration_since(UNIX_EPOCH)
327331
.unwrap()
@@ -395,7 +399,8 @@ impl<'a> BlockGenerator<'a> {
395399
}
396400

397401
if !self.no_tx_pool {
398-
tokio::time::sleep(tokio::time::Duration::from_secs(self.block_time_secs)).await;
402+
let sleep_time = self.block_time_secs + block_building_delay_secs;
403+
tokio::time::sleep(tokio::time::Duration::from_secs(sleep_time)).await;
399404
}
400405

401406
let payload = if let Some(flashblocks_service) = &self.flashblocks_service {
@@ -450,7 +455,11 @@ impl<'a> BlockGenerator<'a> {
450455

451456
/// Generate a single new block and return its hash
452457
pub async fn generate_block(&mut self) -> eyre::Result<B256> {
453-
self.submit_payload(None).await
458+
self.submit_payload(None, 0).await
459+
}
460+
461+
pub async fn generate_block_with_delay(&mut self, delay: u64) -> eyre::Result<B256> {
462+
self.submit_payload(None, delay).await
454463
}
455464

456465
/// Submit a deposit transaction to seed an account with ETH
@@ -473,7 +482,8 @@ impl<'a> BlockGenerator<'a> {
473482
let signed_tx = signer.sign_tx(OpTypedTransaction::Deposit(deposit_tx))?;
474483
let signed_tx_rlp = signed_tx.encoded_2718();
475484

476-
self.submit_payload(Some(vec![signed_tx_rlp.into()])).await
485+
self.submit_payload(Some(vec![signed_tx_rlp.into()]), 0)
486+
.await
477487
}
478488
}
479489

0 commit comments

Comments
 (0)