Skip to content
Closed
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
10 changes: 10 additions & 0 deletions crates/builder/op-rbuilder/src/args/op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
16 changes: 15 additions & 1 deletion crates/builder/op-rbuilder/src/flashblocks/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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,
}
}
}
Expand All @@ -64,7 +68,17 @@ impl TryFrom<OpRbuilderArgs> 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,
})
}
}

Expand Down
33 changes: 32 additions & 1 deletion crates/builder/op-rbuilder/src/flashblocks/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -442,6 +444,14 @@ impl OpPayloadBuilderCtx {
timestamp: self.attributes().timestamp(),
};

// Track the last priority fee to enforce descending order.
// 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<u128> = None;

while let Some(tx) = best_txs.next(()) {
let interop = tx.interop_deadline();
let conditional = tx.conditional().cloned();
Expand All @@ -462,6 +472,27 @@ 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
&& 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 } =
self.tx_data_store.get(&tx_hash);

Expand Down
1 change: 1 addition & 0 deletions crates/builder/op-rbuilder/src/flashblocks/ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
1 change: 1 addition & 0 deletions crates/builder/op-rbuilder/src/flashblocks/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +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,
})
}

Expand Down
2 changes: 2 additions & 0 deletions crates/builder/op-rbuilder/src/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions crates/builder/op-rbuilder/src/tests/flashblocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
};
Expand Down
Loading