From 02e66db12006f71b1ecf47f1becafb7fc22af62a Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 14 Jan 2026 15:15:27 -0500 Subject: [PATCH 1/7] enforce ordering at execution level --- .../op-rbuilder/src/flashblocks/context.rs | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/crates/builder/op-rbuilder/src/flashblocks/context.rs b/crates/builder/op-rbuilder/src/flashblocks/context.rs index 95ed07ba..d42be104 100644 --- a/crates/builder/op-rbuilder/src/flashblocks/context.rs +++ b/crates/builder/op-rbuilder/src/flashblocks/context.rs @@ -38,7 +38,7 @@ use reth_revm::{State, context::Block}; use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction}; use revm::{DatabaseCommit, context::result::ResultAndState, interpreter::as_u64_saturated}; use tokio_util::sync::CancellationToken; -use tracing::{debug, info, trace}; +use tracing::{debug, info, trace, warn}; use crate::{ flashblocks::payload::FlashblocksExecutionInfo, @@ -442,6 +442,12 @@ impl OpPayloadBuilderCtx { timestamp: self.attributes().timestamp(), }; + // Track the last priority fee to enforce descending order. + // Due to a reth bug (fixed in PR #19940), late-arriving high-priority transactions + // can break the ordering guarantee. This check ensures we skip out-of-order txs, + // deferring them to the next flashblock. + let mut last_priority_fee: Option = None; + while let Some(tx) = best_txs.next(()) { let interop = tx.interop_deadline(); let conditional = tx.conditional().cloned(); @@ -462,6 +468,25 @@ impl OpPayloadBuilderCtx { num_txs_considered += 1; + // Check priority fee ordering - skip if current tx has higher priority than last. + // This handles cases where late-arriving high-priority txs break the ordering. + if let Some(current_priority) = tx.effective_tip_per_gas(base_fee) { + if let Some(last_priority) = last_priority_fee { + if current_priority > last_priority { + warn!( + target: "payload_builder", + tx_hash = ?tx_hash, + current_priority = current_priority, + last_priority = last_priority, + "Skipping transaction due to priority fee ordering violation" + ); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue; + } + } + last_priority_fee = Some(current_priority); + } + let TxData { metering: _resource_usage, backrun_bundles } = self.tx_data_store.get(&tx_hash); From f9f0543e641be6714d8740d452446c3362e69a17 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 14 Jan 2026 15:27:07 -0500 Subject: [PATCH 2/7] modify comments --- crates/builder/op-rbuilder/src/flashblocks/context.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/builder/op-rbuilder/src/flashblocks/context.rs b/crates/builder/op-rbuilder/src/flashblocks/context.rs index d42be104..8cc07cef 100644 --- a/crates/builder/op-rbuilder/src/flashblocks/context.rs +++ b/crates/builder/op-rbuilder/src/flashblocks/context.rs @@ -443,9 +443,11 @@ impl OpPayloadBuilderCtx { }; // Track the last priority fee to enforce descending order. - // Due to a reth bug (fixed in PR #19940), late-arriving high-priority transactions - // can break the ordering guarantee. This check ensures we skip out-of-order txs, - // deferring them to the next flashblock. + // Due to a reth bug (https://github.com/paradigmxyz/reth/pull/19940), blocked + // transactions are discarded instead of saved, breaking nonce chain tracking. + // Later transactions from the same sender can't find the blocked one, so they + // get processed as if they have no dependencies and may be yielded out of order. + // This check skips out-of-order txs, deferring them to the next flashblock. let mut last_priority_fee: Option = None; while let Some(tx) = best_txs.next(()) { @@ -469,7 +471,6 @@ impl OpPayloadBuilderCtx { num_txs_considered += 1; // Check priority fee ordering - skip if current tx has higher priority than last. - // This handles cases where late-arriving high-priority txs break the ordering. if let Some(current_priority) = tx.effective_tip_per_gas(base_fee) { if let Some(last_priority) = last_priority_fee { if current_priority > last_priority { From 51c453f9ae3ce11fdbe75cb08ab02a30f93cad3b Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 14 Jan 2026 15:46:45 -0500 Subject: [PATCH 3/7] fix clippy --- .../op-rbuilder/src/flashblocks/context.rs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/builder/op-rbuilder/src/flashblocks/context.rs b/crates/builder/op-rbuilder/src/flashblocks/context.rs index 8cc07cef..28f91ddd 100644 --- a/crates/builder/op-rbuilder/src/flashblocks/context.rs +++ b/crates/builder/op-rbuilder/src/flashblocks/context.rs @@ -472,18 +472,18 @@ impl OpPayloadBuilderCtx { // Check priority fee ordering - skip if current tx has higher priority than last. if let Some(current_priority) = tx.effective_tip_per_gas(base_fee) { - if let Some(last_priority) = last_priority_fee { - if current_priority > last_priority { - warn!( - target: "payload_builder", - tx_hash = ?tx_hash, - current_priority = current_priority, - last_priority = last_priority, - "Skipping transaction due to priority fee ordering violation" - ); - best_txs.mark_invalid(tx.signer(), tx.nonce()); - continue; - } + if let Some(last_priority) = last_priority_fee + && current_priority > last_priority + { + warn!( + target: "payload_builder", + tx_hash = ?tx_hash, + current_priority = current_priority, + last_priority = last_priority, + "Skipping transaction due to priority fee ordering violation" + ); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue; } last_priority_fee = Some(current_priority); } From 803f8c66d24ded06c32c70f73c9d1a99aa9a5b66 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 14 Jan 2026 16:00:38 -0500 Subject: [PATCH 4/7] added a metric --- crates/builder/op-rbuilder/src/flashblocks/context.rs | 1 + crates/builder/op-rbuilder/src/metrics.rs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/crates/builder/op-rbuilder/src/flashblocks/context.rs b/crates/builder/op-rbuilder/src/flashblocks/context.rs index 28f91ddd..062decd1 100644 --- a/crates/builder/op-rbuilder/src/flashblocks/context.rs +++ b/crates/builder/op-rbuilder/src/flashblocks/context.rs @@ -482,6 +482,7 @@ impl OpPayloadBuilderCtx { last_priority = last_priority, "Skipping transaction due to priority fee ordering violation" ); + self.metrics.priority_fee_ordering_violations.increment(1); best_txs.mark_invalid(tx.signer(), tx.nonce()); continue; } diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index 6c575aea..3dc2c0fd 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -181,6 +181,8 @@ pub struct OpRBuilderMetrics { pub backrun_bundle_insert_duration: Histogram, /// Duration of executing all backrun bundles for a target transaction pub backrun_bundle_execution_duration: Histogram, + /// Number of transactions skipped due to priority fee ordering violations + pub priority_fee_ordering_violations: Counter, } impl OpRBuilderMetrics { From 5d31bb8ceb50fd93cbd124e595f7d0527c71a70e Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 14 Jan 2026 16:24:38 -0500 Subject: [PATCH 5/7] add config flags --- crates/builder/op-rbuilder/src/args/op.rs | 10 ++++++ .../op-rbuilder/src/flashblocks/config.rs | 16 ++++++++- .../op-rbuilder/src/flashblocks/context.rs | 34 +++++++++++-------- .../op-rbuilder/src/flashblocks/ctx.rs | 1 + .../op-rbuilder/src/flashblocks/payload.rs | 4 +++ 5 files changed, 49 insertions(+), 16 deletions(-) diff --git a/crates/builder/op-rbuilder/src/args/op.rs b/crates/builder/op-rbuilder/src/args/op.rs index 6d17d93c..f8c704eb 100644 --- a/crates/builder/op-rbuilder/src/args/op.rs +++ b/crates/builder/op-rbuilder/src/args/op.rs @@ -113,6 +113,16 @@ pub struct FlashblocksArgs { env = "FLASHBLOCKS_DISABLE_STATE_ROOT" )] pub flashblocks_disable_state_root: bool, + + /// Whether to enforce priority fee ordering within flashblocks. + /// When enabled, transactions that would violate descending priority fee order are skipped + /// and deferred to the next flashblock. + #[arg( + long = "flashblocks.enforce-priority-fee-ordering", + default_value = "true", + env = "FLASHBLOCKS_ENFORCE_PRIORITY_FEE_ORDERING" + )] + pub flashblocks_enforce_priority_fee_ordering: bool, } impl Default for FlashblocksArgs { diff --git a/crates/builder/op-rbuilder/src/flashblocks/config.rs b/crates/builder/op-rbuilder/src/flashblocks/config.rs index 69d4d007..3eb87a1b 100644 --- a/crates/builder/op-rbuilder/src/flashblocks/config.rs +++ b/crates/builder/op-rbuilder/src/flashblocks/config.rs @@ -33,6 +33,9 @@ pub struct FlashblocksConfig { /// Should we disable state root calculation for each flashblock pub disable_state_root: bool, + + /// Whether to enforce priority fee ordering within flashblocks + pub enforce_priority_fee_ordering: bool, } impl Default for FlashblocksConfig { @@ -43,6 +46,7 @@ impl Default for FlashblocksConfig { leeway_time: Duration::from_millis(50), fixed: false, disable_state_root: false, + enforce_priority_fee_ordering: true, } } } @@ -64,7 +68,17 @@ impl TryFrom for FlashblocksConfig { let disable_state_root = args.flashblocks.flashblocks_disable_state_root; - Ok(Self { ws_addr, interval, leeway_time, fixed, disable_state_root }) + let enforce_priority_fee_ordering = + args.flashblocks.flashblocks_enforce_priority_fee_ordering; + + Ok(Self { + ws_addr, + interval, + leeway_time, + fixed, + disable_state_root, + enforce_priority_fee_ordering, + }) } } diff --git a/crates/builder/op-rbuilder/src/flashblocks/context.rs b/crates/builder/op-rbuilder/src/flashblocks/context.rs index 062decd1..ebd114b5 100644 --- a/crates/builder/op-rbuilder/src/flashblocks/context.rs +++ b/crates/builder/op-rbuilder/src/flashblocks/context.rs @@ -117,6 +117,8 @@ pub struct OpPayloadBuilderCtx { pub address_gas_limiter: AddressGasLimiter, /// Unified transaction data store (backrun bundles + resource metering) pub tx_data_store: TxDataStore, + /// Whether to enforce priority fee ordering within flashblocks + pub enforce_priority_fee_ordering: bool, } impl OpPayloadBuilderCtx { @@ -471,22 +473,24 @@ impl OpPayloadBuilderCtx { num_txs_considered += 1; // Check priority fee ordering - skip if current tx has higher priority than last. - if let Some(current_priority) = tx.effective_tip_per_gas(base_fee) { - if let Some(last_priority) = last_priority_fee - && current_priority > last_priority - { - warn!( - target: "payload_builder", - tx_hash = ?tx_hash, - current_priority = current_priority, - last_priority = last_priority, - "Skipping transaction due to priority fee ordering violation" - ); - self.metrics.priority_fee_ordering_violations.increment(1); - best_txs.mark_invalid(tx.signer(), tx.nonce()); - continue; + if self.enforce_priority_fee_ordering { + if let Some(current_priority) = tx.effective_tip_per_gas(base_fee) { + if let Some(last_priority) = last_priority_fee + && current_priority > last_priority + { + warn!( + target: "payload_builder", + tx_hash = ?tx_hash, + current_priority = current_priority, + last_priority = last_priority, + "Skipping transaction due to priority fee ordering violation" + ); + self.metrics.priority_fee_ordering_violations.increment(1); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue; + } + last_priority_fee = Some(current_priority); } - last_priority_fee = Some(current_priority); } let TxData { metering: _resource_usage, backrun_bundles } = diff --git a/crates/builder/op-rbuilder/src/flashblocks/ctx.rs b/crates/builder/op-rbuilder/src/flashblocks/ctx.rs index 5e20b2ab..430a8e21 100644 --- a/crates/builder/op-rbuilder/src/flashblocks/ctx.rs +++ b/crates/builder/op-rbuilder/src/flashblocks/ctx.rs @@ -88,6 +88,7 @@ impl OpPayloadSyncerCtx { max_gas_per_txn: self.max_gas_per_txn, address_gas_limiter: AddressGasLimiter::new(GasLimiterArgs::default()), tx_data_store: self.tx_data_store, + enforce_priority_fee_ordering: true, // default to enabled for tests } } } diff --git a/crates/builder/op-rbuilder/src/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/flashblocks/payload.rs index 48d2933a..59fd5b5c 100644 --- a/crates/builder/op-rbuilder/src/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/flashblocks/payload.rs @@ -201,6 +201,10 @@ where max_gas_per_txn: self.config.max_gas_per_txn, address_gas_limiter: self.address_gas_limiter.clone(), tx_data_store: self.config.tx_data_store.clone(), + enforce_priority_fee_ordering: self + .config + .flashblocks + .enforce_priority_fee_ordering, }) } From e82bdf50c815852b06ca4bce8b32185706fc8f69 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 14 Jan 2026 16:34:57 -0500 Subject: [PATCH 6/7] fix ci --- crates/builder/op-rbuilder/src/flashblocks/payload.rs | 5 +---- crates/builder/op-rbuilder/src/tests/flashblocks.rs | 1 + 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/builder/op-rbuilder/src/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/flashblocks/payload.rs index 59fd5b5c..aa4cfe72 100644 --- a/crates/builder/op-rbuilder/src/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/flashblocks/payload.rs @@ -201,10 +201,7 @@ where max_gas_per_txn: self.config.max_gas_per_txn, address_gas_limiter: self.address_gas_limiter.clone(), tx_data_store: self.config.tx_data_store.clone(), - enforce_priority_fee_ordering: self - .config - .flashblocks - .enforce_priority_fee_ordering, + enforce_priority_fee_ordering: self.config.flashblocks.enforce_priority_fee_ordering, }) } diff --git a/crates/builder/op-rbuilder/src/tests/flashblocks.rs b/crates/builder/op-rbuilder/src/tests/flashblocks.rs index 56270514..eb9fd1d3 100644 --- a/crates/builder/op-rbuilder/src/tests/flashblocks.rs +++ b/crates/builder/op-rbuilder/src/tests/flashblocks.rs @@ -228,6 +228,7 @@ async fn test_flashblocks_no_state_root_calculation() -> eyre::Result<()> { flashblocks_leeway_time: 100, flashblocks_fixed: false, flashblocks_disable_state_root: true, + flashblocks_enforce_priority_fee_ordering: true, }, ..Default::default() }; From 0ae3a6f0e35c7c7e6b711b231f361e7c0592515c Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 14 Jan 2026 16:50:20 -0500 Subject: [PATCH 7/7] make clippy happy --- .../op-rbuilder/src/flashblocks/context.rs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/crates/builder/op-rbuilder/src/flashblocks/context.rs b/crates/builder/op-rbuilder/src/flashblocks/context.rs index ebd114b5..7a1fecd8 100644 --- a/crates/builder/op-rbuilder/src/flashblocks/context.rs +++ b/crates/builder/op-rbuilder/src/flashblocks/context.rs @@ -473,24 +473,24 @@ impl OpPayloadBuilderCtx { num_txs_considered += 1; // Check priority fee ordering - skip if current tx has higher priority than last. - if self.enforce_priority_fee_ordering { - if let Some(current_priority) = tx.effective_tip_per_gas(base_fee) { - if let Some(last_priority) = last_priority_fee - && current_priority > last_priority - { - warn!( - target: "payload_builder", - tx_hash = ?tx_hash, - current_priority = current_priority, - last_priority = last_priority, - "Skipping transaction due to priority fee ordering violation" - ); - self.metrics.priority_fee_ordering_violations.increment(1); - best_txs.mark_invalid(tx.signer(), tx.nonce()); - continue; - } - last_priority_fee = Some(current_priority); + if self.enforce_priority_fee_ordering + && let Some(current_priority) = tx.effective_tip_per_gas(base_fee) + { + if let Some(last_priority) = last_priority_fee + && current_priority > last_priority + { + warn!( + target: "payload_builder", + tx_hash = ?tx_hash, + current_priority = current_priority, + last_priority = last_priority, + "Skipping transaction due to priority fee ordering violation" + ); + self.metrics.priority_fee_ordering_violations.increment(1); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue; } + last_priority_fee = Some(current_priority); } let TxData { metering: _resource_usage, backrun_bundles } =