Skip to content
This repository was archived by the owner on Jan 12, 2026. It is now read-only.
Open
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ cargo run -p op-rbuilder --bin op-rbuilder -- node \

#### Flashblocks Number Contract

To enable builder tranctions to the [flashblocks number contract](https://github.com/Uniswap/flashblocks_number_contract) for contracts to integrate with flashblocks onchain, specify the address in the CLI args:
To enable builder transactions to the [flashblocks number contract](https://github.com/Uniswap/flashblocks_number_contract) for contracts to integrate with flashblocks onchain, specify the address in the CLI args:

```bash
cargo run -p op-rbuilder --bin op-rbuilder -- node \
Expand Down Expand Up @@ -200,7 +200,7 @@ sudo rm -rf ~/.playground

## Running GitHub actions locally

To verify that CI will allow your PR to be merged before sending it please make sure that our GitHub `checks.yaml` action passes locall by calling:
To verify that CI will allow your PR to be merged before sending it please make sure that our GitHub `checks.yaml` action passes locally by calling:

```
act -W .github/workflows/checks.yaml
Expand Down
46 changes: 42 additions & 4 deletions crates/op-rbuilder/src/gas_limiter/args.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,66 @@
use clap::Args;

/// Configuration arguments for address-based gas rate limiting.
///
/// The gas limiter uses a token bucket algorithm to prevent any single address
/// from consuming excessive gas during block building. This protects against
/// DoS attacks from malicious searchers who might submit many expensive
/// transactions that always revert.
///
/// # Token Bucket Algorithm
///
/// Each address is assigned a "bucket" with a maximum capacity of gas units.
/// When a transaction is processed:
/// 1. The gas used is deducted from the address's available gas
/// 2. If the address doesn't have enough gas, the transaction is rejected
/// 3. Gas is refilled at a configurable rate per block
///
/// # Example Configuration
///
/// For a typical setup protecting against gas-griefing attacks:
/// - `max_gas_per_address`: 10,000,000 (allows ~500 simple transactions)
/// - `refill_rate_per_block`: 1,000,000 (refills 10% per block)
/// - `cleanup_interval`: 100 (cleans up stale buckets every 100 blocks)
#[derive(Debug, Clone, Default, PartialEq, Eq, Args)]
pub struct GasLimiterArgs {
/// Enable address-based gas rate limiting
/// Enable address-based gas rate limiting.
///
/// When enabled, each address is subject to gas limits based on a token
/// bucket algorithm. This helps prevent DoS attacks from addresses that
/// submit many expensive reverting transactions.
#[arg(long = "gas-limiter.enabled", env)]
pub gas_limiter_enabled: bool,

/// Maximum gas per address in token bucket. Defaults to 10 million gas.
/// Maximum gas per address in the token bucket.
///
/// This is the maximum amount of gas that an address can consume before
/// being rate limited. Once exhausted, transactions from this address
/// will be rejected until gas is refilled. Defaults to 10 million gas.
#[arg(
long = "gas-limiter.max-gas-per-address",
env,
default_value = "10000000"
)]
pub max_gas_per_address: u64,

/// Gas refill rate per block. Defaults to 1 million gas per block.
/// Gas refill rate per block.
///
/// After each block, this amount of gas is added back to each address's
/// bucket (up to the maximum capacity). Higher values allow addresses
/// to recover faster but provide less protection. Defaults to 1 million
/// gas per block.
#[arg(
long = "gas-limiter.refill-rate-per-block",
env,
default_value = "1000000"
)]
pub refill_rate_per_block: u64,

/// How many blocks to wait before cleaning up stale buckets for addresses.
/// Number of blocks between cleanup cycles for stale address buckets.
///
/// Periodically, address buckets that have been refilled to full capacity
/// are removed to free memory. Lower values reduce memory usage but
/// increase CPU overhead. Defaults to 100 blocks.
#[arg(long = "gas-limiter.cleanup-interval", env, default_value = "100")]
pub cleanup_interval: u64,
}
14 changes: 13 additions & 1 deletion crates/op-rbuilder/src/gas_limiter/error.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
use alloy_primitives::Address;

/// Errors that can occur during gas limiting operations.
///
/// Gas limiting is used to prevent any single address from consuming
/// excessive gas within a block building cycle, which helps protect
/// against DoS attacks from malicious searchers.
#[derive(Debug, thiserror::Error)]
pub enum GasLimitError {
/// An address has exceeded its allocated gas limit.
///
/// This error occurs when a transaction from an address would
/// consume more gas than the address has available in its token bucket.
#[error(
"Address {address} exceeded gas limit: {requested} gwei requested, {available} gwei available"
"Address {address} exceeded gas limit: {requested} gas units requested, {available} gas units available"
)]
AddressLimitExceeded {
/// The address that exceeded its gas limit
address: Address,
/// The amount of gas requested by the transaction
requested: u64,
/// The amount of gas currently available for this address
available: u64,
},
}
109 changes: 96 additions & 13 deletions crates/op-rbuilder/src/primitives/reth/execution.rs
Original file line number Diff line number Diff line change
@@ -1,51 +1,122 @@
//! Execution primitives for block building.
//!
//! This module contains types used to track transaction execution results
//! and accumulate execution information during block building.
//!
//! Heavily influenced by [reth](https://github.com/paradigmxyz/reth/blob/1e965caf5fa176f244a31c0d2662ba1b590938db/crates/optimism/payload/src/builder.rs#L570)

use alloy_primitives::{Address, U256};
use core::fmt::Debug;
use derive_more::Display;
use op_revm::OpTransactionError;
use reth_optimism_primitives::{OpReceipt, OpTransactionSigned};

/// Result of attempting to execute a transaction during block building.
///
/// This enum captures all possible outcomes when a transaction is considered
/// for inclusion in a block, helping with logging, metrics, and debugging.
#[derive(Debug, Display)]
pub enum TxnExecutionResult {
/// Transaction's data availability size exceeds the per-transaction limit.
TransactionDALimitExceeded,

/// Transaction would cause block to exceed its data availability limit.
#[display("BlockDALimitExceeded: total_da_used={_0} tx_da_size={_1} block_da_limit={_2}")]
BlockDALimitExceeded(u64, u64, u64),
#[display("TransactionGasLimitExceeded: total_gas_used={_0} tx_gas_limit={_1}")]

/// Transaction's gas limit would cause block to exceed its gas limit.
#[display("TransactionGasLimitExceeded: total_gas_used={_0} tx_gas_limit={_1} block_gas_limit={_2}")]
TransactionGasLimitExceeded(u64, u64, u64),

/// Transaction is a sequencer transaction (blob or deposit) which shouldn't
/// come from the pool.
SequencerTransaction,

/// Transaction nonce is lower than the account's current nonce.
NonceTooLow,

/// Transaction failed interop validation (cross-chain deadline expired).
InteropFailed,

/// An internal EVM error occurred during transaction validation.
#[display("InternalError({_0})")]
InternalError(OpTransactionError),

/// A fatal EVM error occurred during transaction execution.
EvmError,

/// Transaction executed successfully.
Success,

/// Transaction reverted but was included (allowed to revert).
Reverted,

/// Transaction reverted and was excluded from the block.
RevertedAndExcluded,

/// Transaction exceeded the per-address gas limit (rate limiting).
MaxGasUsageExceeded,
}

/// Accumulated information about executed transactions during block building.
///
/// This structure tracks all the transactions that have been executed,
/// their receipts, cumulative resource usage, and total fees collected.
/// It is used to build the final block and generate metrics.
#[derive(Default, Debug)]
pub struct ExecutionInfo<Extra: Debug + Default = ()> {
/// All executed transactions (unrecovered).
///
/// These are the raw signed transactions that have been successfully
/// executed and will be included in the block.
pub executed_transactions: Vec<OpTransactionSigned>,

/// The recovered senders for the executed transactions.
///
/// Each entry corresponds to the transaction at the same index in
/// `executed_transactions`.
pub executed_senders: Vec<Address>,
/// The transaction receipts

/// The transaction receipts for executed transactions.
///
/// Each receipt contains the execution result, logs, and cumulative
/// gas used up to and including that transaction.
pub receipts: Vec<OpReceipt>,
/// All gas used so far

/// Total gas consumed by all executed transactions.
pub cumulative_gas_used: u64,
/// Estimated DA size

/// Estimated total data availability bytes used.
///
/// This is used to ensure blocks don't exceed DA limits, which is
/// important for L2 chains where DA costs are significant.
pub cumulative_da_bytes_used: u64,
/// Tracks fees from executed mempool transactions

/// Total fees collected from executed mempool transactions.
///
/// This represents the total priority fees (tips) that will be
/// paid to the block builder.
pub total_fees: U256,

/// Extra execution information that can be attached by individual builders.
///
/// This allows different builder implementations to track additional
/// state without modifying this core structure.
pub extra: Extra,
/// DA Footprint Scalar for Jovian

/// Data Availability footprint scalar for Jovian hardfork.
///
/// After the Jovian hardfork, this scalar is used to calculate
/// the DA footprint gas consumption.
pub da_footprint_scalar: Option<u16>,
}

impl<T: Debug + Default> ExecutionInfo<T> {
/// Create a new instance with allocated slots.
/// Create a new instance with pre-allocated capacity.
///
/// # Arguments
///
/// * `capacity` - The expected number of transactions to be executed
pub fn with_capacity(capacity: usize) -> Self {
Self {
executed_transactions: Vec::with_capacity(capacity),
Expand All @@ -59,12 +130,19 @@ impl<T: Debug + Default> ExecutionInfo<T> {
}
}

/// Returns true if the transaction would exceed the block limits:
/// - block gas limit: ensures the transaction still fits into the block.
/// - tx DA limit: if configured, ensures the tx does not exceed the maximum allowed DA limit
/// per tx.
/// - block DA limit: if configured, ensures the transaction's DA size does not exceed the
/// maximum allowed DA limit per block.
/// Check if a transaction would exceed block limits.
///
/// This method validates that including a transaction won't violate any
/// of the following limits:
/// - Block gas limit: ensures the transaction still fits into the block
/// - Per-transaction DA limit: ensures the transaction doesn't exceed max DA size
/// - Block DA limit: ensures cumulative DA doesn't exceed the block limit
/// - Post-Jovian DA footprint: ensures DA footprint gas stays within limits
///
/// # Returns
///
/// - `Ok(())` if the transaction can be included
/// - `Err(TxnExecutionResult)` describing why the transaction cannot be included
#[allow(clippy::too_many_arguments)]
pub fn is_tx_over_limits(
&self,
Expand All @@ -76,9 +154,12 @@ impl<T: Debug + Default> ExecutionInfo<T> {
da_footprint_gas_scalar: Option<u16>,
block_da_footprint_limit: Option<u64>,
) -> Result<(), TxnExecutionResult> {
// Check per-transaction DA limit
if tx_data_limit.is_some_and(|da_limit| tx_da_size > da_limit) {
return Err(TxnExecutionResult::TransactionDALimitExceeded);
}

// Check block DA limit
let total_da_bytes_used = self.cumulative_da_bytes_used.saturating_add(tx_da_size);
if block_data_limit.is_some_and(|da_limit| total_da_bytes_used > da_limit) {
return Err(TxnExecutionResult::BlockDALimitExceeded(
Expand All @@ -101,13 +182,15 @@ impl<T: Debug + Default> ExecutionInfo<T> {
}
}

// Check block gas limit
if self.cumulative_gas_used + tx_gas_limit > block_gas_limit {
return Err(TxnExecutionResult::TransactionGasLimitExceeded(
self.cumulative_gas_used,
tx_gas_limit,
block_gas_limit,
));
}

Ok(())
}
}
35 changes: 35 additions & 0 deletions crates/op-rbuilder/src/traits.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
//! Trait bounds and type aliases for op-rbuilder components.
//!
//! This module defines trait bounds that constrain the generic types used throughout
//! the block builder. These traits ensure that components work correctly with the
//! Optimism-specific types and configurations.
//!
//! # Overview
//!
//! - [`NodeBounds`]: Constraints for full node type parameters
//! - [`NodeComponents`]: Constraints for full node component parameters
//! - [`PoolBounds`]: Constraints for transaction pool implementations
//! - [`ClientBounds`]: Constraints for state provider and chain spec clients
//! - [`PayloadTxsBounds`]: Constraints for payload transaction iterators

use alloy_consensus::Header;
use reth_node_api::{FullNodeComponents, FullNodeTypes, NodeTypes};
use reth_optimism_chainspec::OpChainSpec;
Expand All @@ -9,6 +23,10 @@ use reth_transaction_pool::TransactionPool;

use crate::tx::FBPoolTransaction;

/// Trait bound for full node types compatible with op-rbuilder.
///
/// This trait ensures that the node types are configured for Optimism,
/// including the correct engine types, chain specification, and primitives.
pub trait NodeBounds:
FullNodeTypes<
Types: NodeTypes<Payload = OpEngineTypes, ChainSpec = OpChainSpec, Primitives = OpPrimitives>,
Expand All @@ -27,6 +45,10 @@ impl<T> NodeBounds for T where
{
}

/// Trait bound for full node components compatible with op-rbuilder.
///
/// Similar to [`NodeBounds`], but for component types that include
/// additional functionality beyond the base node types.
pub trait NodeComponents:
FullNodeComponents<
Types: NodeTypes<Payload = OpEngineTypes, ChainSpec = OpChainSpec, Primitives = OpPrimitives>,
Expand All @@ -45,6 +67,10 @@ impl<T> NodeComponents for T where
{
}

/// Trait bound for transaction pools compatible with op-rbuilder.
///
/// The transaction pool must support flashblock-aware transactions and
/// work with Optimism's signed transaction type.
pub trait PoolBounds:
TransactionPool<Transaction: FBPoolTransaction<Consensus = OpTransactionSigned>> + Unpin + 'static
where
Expand All @@ -61,6 +87,10 @@ where
{
}

/// Trait bound for state and chain specification clients.
///
/// Clients must be able to provide state, chain specification information,
/// and block header data for Optimism chains.
pub trait ClientBounds:
StateProviderFactory
+ ChainSpecProvider<ChainSpec = OpChainSpec>
Expand All @@ -77,6 +107,11 @@ impl<T> ClientBounds for T where
{
}

/// Trait bound for payload transaction iterators.
///
/// Transaction iterators used during payload building must provide
/// flashblock-compatible transactions that can be converted to
/// Optimism's consensus transaction type.
pub trait PayloadTxsBounds:
PayloadTransactions<Transaction: FBPoolTransaction<Consensus = OpTransactionSigned>>
{
Expand Down