Skip to content
This repository was archived by the owner on Jan 12, 2026. It is now read-only.

Commit 23f42c8

Browse files
authored
Merge pull request #36 from base/cody/handle_evm_error
fix: handle EVM errors gracefully in backrun bundle execution
2 parents ac1f545 + 5e67689 commit 23f42c8

3 files changed

Lines changed: 108 additions & 0 deletions

File tree

crates/op-rbuilder/src/builders/context.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,19 @@ impl<ExtraCtx: Debug + Default> OpPayloadBuilderCtx<ExtraCtx> {
678678
let ResultAndState { result, state } = match evm.transact(&consensus_tx) {
679679
Ok(res) => res,
680680
Err(err) => {
681+
if err.as_invalid_tx_err().is_some() {
682+
self.metrics.backrun_bundles_invalid_tx_total.increment(1);
683+
info!(
684+
target: "payload_builder",
685+
target_tx = ?tx_hash,
686+
failed_tx = ?backrun_tx.hash(),
687+
bundle_id = ?stored_bundle.bundle_id,
688+
error = %err,
689+
"Backrun bundle failed with invalid tx error"
690+
);
691+
continue 'bundle_loop;
692+
}
693+
self.metrics.backrun_bundles_fatal_error_total.increment(1);
681694
return Err(PayloadBuilderError::evm(err));
682695
}
683696
};

crates/op-rbuilder/src/metrics.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,10 @@ pub struct OpRBuilderMetrics {
175175
pub backrun_bundles_received_total: Counter,
176176
/// Number of backrun bundles that reverted during execution (all-or-nothing)
177177
pub backrun_bundles_reverted_total: Counter,
178+
/// Number of backrun bundles skipped due to invalid tx errors (nonce too low, etc.)
179+
pub backrun_bundles_invalid_tx_total: Counter,
180+
/// Number of backrun bundles that caused fatal EVM errors
181+
pub backrun_bundles_fatal_error_total: Counter,
178182
/// Number of backrun bundles rejected due to priority fee below target tx
179183
pub backrun_bundles_rejected_low_fee_total: Counter,
180184
/// Number of backrun bundles rejected due to exceeding block limits

crates/op-rbuilder/src/tests/backrun.rs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,3 +560,94 @@ async fn backrun_bundle_rejected_exceeds_da_limit(rbuilder: LocalInstance) -> ey
560560

561561
Ok(())
562562
}
563+
564+
/// Tests that backrun bundles with invalid tx errors (e.g. nonce too low) are skipped gracefully
565+
#[rb_test(flashblocks)]
566+
async fn backrun_bundle_invalid_tx_skipped(rbuilder: LocalInstance) -> eyre::Result<()> {
567+
let driver = rbuilder.driver().await?;
568+
let accounts = driver.fund_accounts(3, ONE_ETH).await?;
569+
570+
let target_tx = driver
571+
.create_transaction()
572+
.with_signer(accounts[0])
573+
.with_max_priority_fee_per_gas(20)
574+
.build()
575+
.await;
576+
let target_tx_hash = target_tx.tx_hash().clone();
577+
578+
let provider = rbuilder.provider().await?;
579+
let _ = provider
580+
.send_raw_transaction(target_tx.encoded_2718().as_slice())
581+
.await?;
582+
583+
let backrun_tx = driver
584+
.create_transaction()
585+
.with_signer(accounts[1])
586+
.with_max_priority_fee_per_gas(50)
587+
.with_nonce(0)
588+
.build()
589+
.await;
590+
let backrun_tx_hash = backrun_tx.tx_hash().clone();
591+
592+
// Send a conflicting tx with same nonce but higher fee - it will be included first
593+
let conflicting_tx = driver
594+
.create_transaction()
595+
.with_signer(accounts[1])
596+
.with_max_priority_fee_per_gas(100)
597+
.with_nonce(0)
598+
.send()
599+
.await?;
600+
let conflicting_tx_hash = conflicting_tx.tx_hash().clone();
601+
602+
let bundle = AcceptedBundle {
603+
uuid: Uuid::new_v4(),
604+
txs: vec![target_tx, backrun_tx],
605+
block_number: driver.latest().await?.header.number + 1,
606+
flashblock_number_min: None,
607+
flashblock_number_max: None,
608+
min_timestamp: None,
609+
max_timestamp: None,
610+
reverting_tx_hashes: vec![],
611+
replacement_uuid: None,
612+
dropping_tx_hashes: vec![],
613+
meter_bundle_response: MeterBundleResponse {
614+
bundle_gas_price: U256::ZERO,
615+
bundle_hash: TxHash::ZERO,
616+
coinbase_diff: U256::ZERO,
617+
eth_sent_to_coinbase: U256::ZERO,
618+
gas_fees: U256::ZERO,
619+
results: vec![],
620+
state_block_number: 0,
621+
state_flashblock_index: None,
622+
total_gas_used: 0,
623+
total_execution_time_us: 0,
624+
},
625+
};
626+
627+
rbuilder
628+
.tx_data_store()
629+
.insert_backrun_bundle(bundle)
630+
.expect("Failed to insert backrun bundle");
631+
632+
driver.build_new_block().await?;
633+
634+
let block = driver.latest_full().await?;
635+
let tx_hashes: Vec<_> = block.transactions.hashes().collect();
636+
637+
assert!(
638+
tx_hashes.contains(&target_tx_hash),
639+
"Target tx should be included in block"
640+
);
641+
642+
assert!(
643+
tx_hashes.contains(&conflicting_tx_hash),
644+
"Conflicting tx should be included in block"
645+
);
646+
647+
assert!(
648+
!tx_hashes.contains(&backrun_tx_hash),
649+
"Backrun tx should NOT be in block (nonce-too-low EVM error)"
650+
);
651+
652+
Ok(())
653+
}

0 commit comments

Comments
 (0)