diff --git a/README.md b/README.md index 2b3092433..83f8b4500 100644 --- a/README.md +++ b/README.md @@ -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 \ @@ -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 diff --git a/crates/op-rbuilder/src/gas_limiter/args.rs b/crates/op-rbuilder/src/gas_limiter/args.rs index ec7f8008b..cd47e55c1 100644 --- a/crates/op-rbuilder/src/gas_limiter/args.rs +++ b/crates/op-rbuilder/src/gas_limiter/args.rs @@ -1,12 +1,41 @@ 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, @@ -14,7 +43,12 @@ pub struct GasLimiterArgs { )] 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, @@ -22,7 +56,11 @@ pub struct GasLimiterArgs { )] 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, } diff --git a/crates/op-rbuilder/src/gas_limiter/error.rs b/crates/op-rbuilder/src/gas_limiter/error.rs index a85b7f77c..2ec8a7160 100644 --- a/crates/op-rbuilder/src/gas_limiter/error.rs +++ b/crates/op-rbuilder/src/gas_limiter/error.rs @@ -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, }, } diff --git a/crates/op-rbuilder/src/primitives/reth/execution.rs b/crates/op-rbuilder/src/primitives/reth/execution.rs index 7865a1c84..22d037b96 100644 --- a/crates/op-rbuilder/src/primitives/reth/execution.rs +++ b/crates/op-rbuilder/src/primitives/reth/execution.rs @@ -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 { /// 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, + /// The recovered senders for the executed transactions. + /// + /// Each entry corresponds to the transaction at the same index in + /// `executed_transactions`. pub executed_senders: Vec
, - /// 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, - /// 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, } impl ExecutionInfo { - /// 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), @@ -59,12 +130,19 @@ impl ExecutionInfo { } } - /// 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, @@ -76,9 +154,12 @@ impl ExecutionInfo { da_footprint_gas_scalar: Option, block_da_footprint_limit: Option, ) -> 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( @@ -101,6 +182,7 @@ impl ExecutionInfo { } } + // Check block gas limit if self.cumulative_gas_used + tx_gas_limit > block_gas_limit { return Err(TxnExecutionResult::TransactionGasLimitExceeded( self.cumulative_gas_used, @@ -108,6 +190,7 @@ impl ExecutionInfo { block_gas_limit, )); } + Ok(()) } } diff --git a/crates/op-rbuilder/src/traits.rs b/crates/op-rbuilder/src/traits.rs index ef9dde45f..4812c6f60 100644 --- a/crates/op-rbuilder/src/traits.rs +++ b/crates/op-rbuilder/src/traits.rs @@ -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; @@ -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, @@ -27,6 +45,10 @@ impl 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, @@ -45,6 +67,10 @@ impl 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> + Unpin + 'static where @@ -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 @@ -77,6 +107,11 @@ impl 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> {