reth_optimism_payload_builder/
builder.rs

1//! Optimism payload builder implementation.
2
3use crate::{
4    config::{OpBuilderConfig, OpDAConfig},
5    error::OpPayloadBuilderError,
6    payload::{OpBuiltPayload, OpPayloadBuilderAttributes},
7    OpPayloadPrimitives,
8};
9use alloy_consensus::{Transaction, Typed2718};
10use alloy_primitives::{Bytes, B256, U256};
11use alloy_rlp::Encodable;
12use alloy_rpc_types_debug::ExecutionWitness;
13use alloy_rpc_types_engine::PayloadId;
14use op_alloy_rpc_types_engine::OpPayloadAttributes;
15use reth_basic_payload_builder::*;
16use reth_chain_state::{ExecutedBlock, ExecutedBlockWithTrieUpdates};
17use reth_chainspec::{ChainSpecProvider, EthChainSpec};
18use reth_evm::{
19    execute::{
20        BlockBuilder, BlockBuilderOutcome, BlockExecutionError, BlockExecutor, BlockValidationError,
21    },
22    ConfigureEvm, Database, Evm,
23};
24use reth_execution_types::ExecutionOutcome;
25use reth_optimism_evm::OpNextBlockEnvAttributes;
26use reth_optimism_forks::OpHardforks;
27use reth_optimism_primitives::transaction::signed::OpTransaction;
28use reth_optimism_txpool::OpPooledTx;
29use reth_payload_builder_primitives::PayloadBuilderError;
30use reth_payload_primitives::PayloadBuilderAttributes;
31use reth_payload_util::{BestPayloadTransactions, NoopPayloadTransactions, PayloadTransactions};
32use reth_primitives::{transaction::SignedTransaction, NodePrimitives, SealedHeader, TxTy};
33use reth_provider::{ProviderError, StateProvider, StateProviderFactory};
34use reth_revm::{
35    cancelled::CancelOnDrop, database::StateProviderDatabase, db::State,
36    witness::ExecutionWitnessRecord,
37};
38use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction, TransactionPool};
39use revm::context::{Block, BlockEnv};
40use std::sync::Arc;
41use tracing::{debug, trace, warn};
42
43/// Optimism's payload builder
44#[derive(Debug, Clone)]
45pub struct OpPayloadBuilder<Pool, Client, Evm, Txs = ()> {
46    /// The rollup's compute pending block configuration option.
47    // TODO(clabby): Implement this feature.
48    pub compute_pending_block: bool,
49    /// The type responsible for creating the evm.
50    pub evm_config: Evm,
51    /// Transaction pool.
52    pub pool: Pool,
53    /// Node client.
54    pub client: Client,
55    /// Settings for the builder, e.g. DA settings.
56    pub config: OpBuilderConfig,
57    /// The type responsible for yielding the best transactions for the payload if mempool
58    /// transactions are allowed.
59    pub best_transactions: Txs,
60}
61
62impl<Pool, Client, Evm> OpPayloadBuilder<Pool, Client, Evm> {
63    /// `OpPayloadBuilder` constructor.
64    ///
65    /// Configures the builder with the default settings.
66    pub fn new(pool: Pool, client: Client, evm_config: Evm) -> Self {
67        Self::with_builder_config(pool, client, evm_config, Default::default())
68    }
69
70    /// Configures the builder with the given [`OpBuilderConfig`].
71    pub fn with_builder_config(
72        pool: Pool,
73        client: Client,
74        evm_config: Evm,
75        config: OpBuilderConfig,
76    ) -> Self {
77        Self {
78            pool,
79            client,
80            compute_pending_block: true,
81            evm_config,
82            config,
83            best_transactions: (),
84        }
85    }
86}
87
88impl<Pool, Client, Evm, Txs> OpPayloadBuilder<Pool, Client, Evm, Txs> {
89    /// Sets the rollup's compute pending block configuration option.
90    pub const fn set_compute_pending_block(mut self, compute_pending_block: bool) -> Self {
91        self.compute_pending_block = compute_pending_block;
92        self
93    }
94
95    /// Configures the type responsible for yielding the transactions that should be included in the
96    /// payload.
97    pub fn with_transactions<T>(
98        self,
99        best_transactions: T,
100    ) -> OpPayloadBuilder<Pool, Client, Evm, T> {
101        let Self { pool, client, compute_pending_block, evm_config, config, .. } = self;
102        OpPayloadBuilder {
103            pool,
104            client,
105            compute_pending_block,
106            evm_config,
107            best_transactions,
108            config,
109        }
110    }
111
112    /// Enables the rollup's compute pending block configuration option.
113    pub const fn compute_pending_block(self) -> Self {
114        self.set_compute_pending_block(true)
115    }
116
117    /// Returns the rollup's compute pending block configuration option.
118    pub const fn is_compute_pending_block(&self) -> bool {
119        self.compute_pending_block
120    }
121}
122
123impl<Pool, Client, Evm, N, T> OpPayloadBuilder<Pool, Client, Evm, T>
124where
125    Pool: TransactionPool<Transaction: OpPooledTx<Consensus = N::SignedTx>>,
126    Client: StateProviderFactory + ChainSpecProvider<ChainSpec: EthChainSpec + OpHardforks>,
127    N: OpPayloadPrimitives,
128    Evm: ConfigureEvm<Primitives = N, NextBlockEnvCtx = OpNextBlockEnvAttributes>,
129{
130    /// Constructs an Optimism payload from the transactions sent via the
131    /// Payload attributes by the sequencer. If the `no_tx_pool` argument is passed in
132    /// the payload attributes, the transaction pool will be ignored and the only transactions
133    /// included in the payload will be those sent through the attributes.
134    ///
135    /// Given build arguments including an Optimism client, transaction pool,
136    /// and configuration, this function creates a transaction payload. Returns
137    /// a result indicating success with the payload or an error in case of failure.
138    fn build_payload<'a, Txs>(
139        &self,
140        args: BuildArguments<OpPayloadBuilderAttributes<N::SignedTx>, OpBuiltPayload<N>>,
141        best: impl FnOnce(BestTransactionsAttributes) -> Txs + Send + Sync + 'a,
142    ) -> Result<BuildOutcome<OpBuiltPayload<N>>, PayloadBuilderError>
143    where
144        Txs: PayloadTransactions<Transaction: PoolTransaction<Consensus = N::SignedTx>>,
145    {
146        let BuildArguments { mut cached_reads, config, cancel, best_payload } = args;
147
148        let ctx = OpPayloadBuilderCtx {
149            evm_config: self.evm_config.clone(),
150            da_config: self.config.da_config.clone(),
151            chain_spec: self.client.chain_spec(),
152            config,
153            cancel,
154            best_payload,
155        };
156
157        let builder = OpBuilder::new(best);
158
159        let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?;
160        let state = StateProviderDatabase::new(&state_provider);
161
162        if ctx.attributes().no_tx_pool {
163            builder.build(state, &state_provider, ctx)
164        } else {
165            // sequencer mode we can reuse cachedreads from previous runs
166            builder.build(cached_reads.as_db_mut(state), &state_provider, ctx)
167        }
168        .map(|out| out.with_cached_reads(cached_reads))
169    }
170
171    /// Computes the witness for the payload.
172    pub fn payload_witness(
173        &self,
174        parent: SealedHeader,
175        attributes: OpPayloadAttributes,
176    ) -> Result<ExecutionWitness, PayloadBuilderError> {
177        let attributes = OpPayloadBuilderAttributes::try_new(parent.hash(), attributes, 3)
178            .map_err(PayloadBuilderError::other)?;
179
180        let config = PayloadConfig { parent_header: Arc::new(parent), attributes };
181        let ctx = OpPayloadBuilderCtx {
182            evm_config: self.evm_config.clone(),
183            da_config: self.config.da_config.clone(),
184            chain_spec: self.client.chain_spec(),
185            config,
186            cancel: Default::default(),
187            best_payload: Default::default(),
188        };
189
190        let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?;
191
192        let builder = OpBuilder::new(|_| NoopPayloadTransactions::<Pool::Transaction>::default());
193        builder.witness(state_provider, &ctx)
194    }
195}
196
197/// Implementation of the [`PayloadBuilder`] trait for [`OpPayloadBuilder`].
198impl<Pool, Client, Evm, N, Txs> PayloadBuilder for OpPayloadBuilder<Pool, Client, Evm, Txs>
199where
200    Client: StateProviderFactory + ChainSpecProvider<ChainSpec: EthChainSpec + OpHardforks> + Clone,
201    N: OpPayloadPrimitives,
202    Pool: TransactionPool<Transaction: OpPooledTx<Consensus = N::SignedTx>>,
203    Evm: ConfigureEvm<Primitives = N, NextBlockEnvCtx = OpNextBlockEnvAttributes>,
204    Txs: OpPayloadTransactions<Pool::Transaction>,
205{
206    type Attributes = OpPayloadBuilderAttributes<N::SignedTx>;
207    type BuiltPayload = OpBuiltPayload<N>;
208
209    fn try_build(
210        &self,
211        args: BuildArguments<Self::Attributes, Self::BuiltPayload>,
212    ) -> Result<BuildOutcome<Self::BuiltPayload>, PayloadBuilderError> {
213        let pool = self.pool.clone();
214        self.build_payload(args, |attrs| self.best_transactions.best_transactions(pool, attrs))
215    }
216
217    fn on_missing_payload(
218        &self,
219        _args: BuildArguments<Self::Attributes, Self::BuiltPayload>,
220    ) -> MissingPayloadBehaviour<Self::BuiltPayload> {
221        // we want to await the job that's already in progress because that should be returned as
222        // is, there's no benefit in racing another job
223        MissingPayloadBehaviour::AwaitInProgress
224    }
225
226    // NOTE: this should only be used for testing purposes because this doesn't have access to L1
227    // system txs, hence on_missing_payload we return [MissingPayloadBehaviour::AwaitInProgress].
228    fn build_empty_payload(
229        &self,
230        config: PayloadConfig<Self::Attributes>,
231    ) -> Result<Self::BuiltPayload, PayloadBuilderError> {
232        let args = BuildArguments {
233            config,
234            cached_reads: Default::default(),
235            cancel: Default::default(),
236            best_payload: None,
237        };
238        self.build_payload(args, |_| NoopPayloadTransactions::<Pool::Transaction>::default())?
239            .into_payload()
240            .ok_or_else(|| PayloadBuilderError::MissingPayload)
241    }
242}
243
244/// The type that builds the payload.
245///
246/// Payload building for optimism is composed of several steps.
247/// The first steps are mandatory and defined by the protocol.
248///
249/// 1. first all System calls are applied.
250/// 2. After canyon the forced deployed `create2deployer` must be loaded
251/// 3. all sequencer transactions are executed (part of the payload attributes)
252///
253/// Depending on whether the node acts as a sequencer and is allowed to include additional
254/// transactions (`no_tx_pool == false`):
255/// 4. include additional transactions
256///
257/// And finally
258/// 5. build the block: compute all roots (txs, state)
259#[derive(derive_more::Debug)]
260pub struct OpBuilder<'a, Txs> {
261    /// Yields the best transaction to include if transactions from the mempool are allowed.
262    #[debug(skip)]
263    best: Box<dyn FnOnce(BestTransactionsAttributes) -> Txs + 'a>,
264}
265
266impl<'a, Txs> OpBuilder<'a, Txs> {
267    fn new(best: impl FnOnce(BestTransactionsAttributes) -> Txs + Send + Sync + 'a) -> Self {
268        Self { best: Box::new(best) }
269    }
270}
271
272impl<Txs> OpBuilder<'_, Txs> {
273    /// Builds the payload on top of the state.
274    pub fn build<EvmConfig, ChainSpec, N>(
275        self,
276        db: impl Database<Error = ProviderError>,
277        state_provider: impl StateProvider,
278        ctx: OpPayloadBuilderCtx<EvmConfig, ChainSpec>,
279    ) -> Result<BuildOutcomeKind<OpBuiltPayload<N>>, PayloadBuilderError>
280    where
281        EvmConfig: ConfigureEvm<Primitives = N, NextBlockEnvCtx = OpNextBlockEnvAttributes>,
282        ChainSpec: EthChainSpec + OpHardforks,
283        N: OpPayloadPrimitives,
284        Txs: PayloadTransactions<Transaction: PoolTransaction<Consensus = N::SignedTx>>,
285    {
286        let Self { best } = self;
287        debug!(target: "payload_builder", id=%ctx.payload_id(), parent_header = ?ctx.parent().hash(), parent_number = ctx.parent().number, "building new payload");
288
289        let mut db = State::builder().with_database(db).with_bundle_update().build();
290
291        let mut builder = ctx.block_builder(&mut db)?;
292
293        // 1. apply pre-execution changes
294        builder.apply_pre_execution_changes().map_err(|err| {
295            warn!(target: "payload_builder", %err, "failed to apply pre-execution changes");
296            PayloadBuilderError::Internal(err.into())
297        })?;
298
299        // 2. execute sequencer transactions
300        let mut info = ctx.execute_sequencer_transactions(&mut builder)?;
301
302        // 3. if mem pool transactions are requested we execute them
303        if !ctx.attributes().no_tx_pool {
304            let best_txs = best(ctx.best_transaction_attributes(builder.evm_mut().block()));
305            if ctx.execute_best_transactions(&mut info, &mut builder, best_txs)?.is_some() {
306                return Ok(BuildOutcomeKind::Cancelled)
307            }
308
309            // check if the new payload is even more valuable
310            if !ctx.is_better_payload(info.total_fees) {
311                // can skip building the block
312                return Ok(BuildOutcomeKind::Aborted { fees: info.total_fees })
313            }
314        }
315
316        let BlockBuilderOutcome { execution_result, hashed_state, trie_updates, block } =
317            builder.finish(state_provider)?;
318
319        let sealed_block = Arc::new(block.sealed_block().clone());
320        debug!(target: "payload_builder", id=%ctx.attributes().payload_id(), sealed_block_header = ?sealed_block.header(), "sealed built block");
321
322        let execution_outcome = ExecutionOutcome::new(
323            db.take_bundle(),
324            vec![execution_result.receipts],
325            block.number,
326            Vec::new(),
327        );
328
329        // create the executed block data
330        let executed: ExecutedBlockWithTrieUpdates<N> = ExecutedBlockWithTrieUpdates {
331            block: ExecutedBlock {
332                recovered_block: Arc::new(block),
333                execution_output: Arc::new(execution_outcome),
334                hashed_state: Arc::new(hashed_state),
335            },
336            trie: Arc::new(trie_updates),
337        };
338
339        let no_tx_pool = ctx.attributes().no_tx_pool;
340
341        let payload =
342            OpBuiltPayload::new(ctx.payload_id(), sealed_block, info.total_fees, Some(executed));
343
344        if no_tx_pool {
345            // if `no_tx_pool` is set only transactions from the payload attributes will be included
346            // in the payload. In other words, the payload is deterministic and we can
347            // freeze it once we've successfully built it.
348            Ok(BuildOutcomeKind::Freeze(payload))
349        } else {
350            Ok(BuildOutcomeKind::Better { payload })
351        }
352    }
353
354    /// Builds the payload and returns its [`ExecutionWitness`] based on the state after execution.
355    pub fn witness<Evm, ChainSpec, N>(
356        self,
357        state_provider: impl StateProvider,
358        ctx: &OpPayloadBuilderCtx<Evm, ChainSpec>,
359    ) -> Result<ExecutionWitness, PayloadBuilderError>
360    where
361        Evm: ConfigureEvm<Primitives = N, NextBlockEnvCtx = OpNextBlockEnvAttributes>,
362        ChainSpec: EthChainSpec + OpHardforks,
363        N: OpPayloadPrimitives,
364        Txs: PayloadTransactions<Transaction: PoolTransaction<Consensus = N::SignedTx>>,
365    {
366        let mut db = State::builder()
367            .with_database(StateProviderDatabase::new(&state_provider))
368            .with_bundle_update()
369            .build();
370        let mut builder = ctx.block_builder(&mut db)?;
371
372        builder.apply_pre_execution_changes()?;
373        ctx.execute_sequencer_transactions(&mut builder)?;
374        builder.into_executor().apply_post_execution_changes()?;
375
376        let ExecutionWitnessRecord { hashed_state, codes, keys } =
377            ExecutionWitnessRecord::from_executed_state(&db);
378        let state = state_provider.witness(Default::default(), hashed_state)?;
379        Ok(ExecutionWitness { state: state.into_iter().collect(), codes, keys })
380    }
381}
382
383/// A type that returns a the [`PayloadTransactions`] that should be included in the pool.
384pub trait OpPayloadTransactions<Transaction>: Clone + Send + Sync + Unpin + 'static {
385    /// Returns an iterator that yields the transaction in the order they should get included in the
386    /// new payload.
387    fn best_transactions<Pool: TransactionPool<Transaction = Transaction>>(
388        &self,
389        pool: Pool,
390        attr: BestTransactionsAttributes,
391    ) -> impl PayloadTransactions<Transaction = Transaction>;
392}
393
394impl<T: PoolTransaction> OpPayloadTransactions<T> for () {
395    fn best_transactions<Pool: TransactionPool<Transaction = T>>(
396        &self,
397        pool: Pool,
398        attr: BestTransactionsAttributes,
399    ) -> impl PayloadTransactions<Transaction = T> {
400        BestPayloadTransactions::new(pool.best_transactions_with_attributes(attr))
401    }
402}
403
404/// Holds the state after execution
405#[derive(Debug)]
406pub struct ExecutedPayload<N: NodePrimitives> {
407    /// Tracked execution info
408    pub info: ExecutionInfo,
409    /// Withdrawal hash.
410    pub withdrawals_root: Option<B256>,
411    /// The transaction receipts.
412    pub receipts: Vec<N::Receipt>,
413    /// The block env used during execution.
414    pub block_env: BlockEnv,
415}
416
417/// This acts as the container for executed transactions and its byproducts (receipts, gas used)
418#[derive(Default, Debug)]
419pub struct ExecutionInfo {
420    /// All gas used so far
421    pub cumulative_gas_used: u64,
422    /// Estimated DA size
423    pub cumulative_da_bytes_used: u64,
424    /// Tracks fees from executed mempool transactions
425    pub total_fees: U256,
426}
427
428impl ExecutionInfo {
429    /// Create a new instance with allocated slots.
430    pub fn new() -> Self {
431        Self { cumulative_gas_used: 0, cumulative_da_bytes_used: 0, total_fees: U256::ZERO }
432    }
433
434    /// Returns true if the transaction would exceed the block limits:
435    /// - block gas limit: ensures the transaction still fits into the block.
436    /// - tx DA limit: if configured, ensures the tx does not exceed the maximum allowed DA limit
437    ///   per tx.
438    /// - block DA limit: if configured, ensures the transaction's DA size does not exceed the
439    ///   maximum allowed DA limit per block.
440    pub fn is_tx_over_limits(
441        &self,
442        tx: &(impl Encodable + Transaction),
443        block_gas_limit: u64,
444        tx_data_limit: Option<u64>,
445        block_data_limit: Option<u64>,
446    ) -> bool {
447        if tx_data_limit.is_some_and(|da_limit| tx.length() as u64 > da_limit) {
448            return true;
449        }
450
451        if block_data_limit
452            .is_some_and(|da_limit| self.cumulative_da_bytes_used + (tx.length() as u64) > da_limit)
453        {
454            return true;
455        }
456
457        self.cumulative_gas_used + tx.gas_limit() > block_gas_limit
458    }
459}
460
461/// Container type that holds all necessities to build a new payload.
462#[derive(derive_more::Debug)]
463pub struct OpPayloadBuilderCtx<Evm: ConfigureEvm, ChainSpec> {
464    /// The type that knows how to perform system calls and configure the evm.
465    pub evm_config: Evm,
466    /// The DA config for the payload builder
467    pub da_config: OpDAConfig,
468    /// The chainspec
469    pub chain_spec: Arc<ChainSpec>,
470    /// How to build the payload.
471    pub config: PayloadConfig<OpPayloadBuilderAttributes<TxTy<Evm::Primitives>>>,
472    /// Marker to check whether the job has been cancelled.
473    pub cancel: CancelOnDrop,
474    /// The currently best payload.
475    pub best_payload: Option<OpBuiltPayload<Evm::Primitives>>,
476}
477
478impl<Evm, ChainSpec> OpPayloadBuilderCtx<Evm, ChainSpec>
479where
480    Evm: ConfigureEvm<Primitives: OpPayloadPrimitives, NextBlockEnvCtx = OpNextBlockEnvAttributes>,
481    ChainSpec: EthChainSpec + OpHardforks,
482{
483    /// Returns the parent block the payload will be build on.
484    pub fn parent(&self) -> &SealedHeader {
485        &self.config.parent_header
486    }
487
488    /// Returns the builder attributes.
489    pub const fn attributes(&self) -> &OpPayloadBuilderAttributes<TxTy<Evm::Primitives>> {
490        &self.config.attributes
491    }
492
493    /// Returns the extra data for the block.
494    ///
495    /// After holocene this extracts the extra data from the payload
496    pub fn extra_data(&self) -> Result<Bytes, PayloadBuilderError> {
497        if self.is_holocene_active() {
498            self.attributes()
499                .get_holocene_extra_data(
500                    self.chain_spec.base_fee_params_at_timestamp(
501                        self.attributes().payload_attributes.timestamp,
502                    ),
503                )
504                .map_err(PayloadBuilderError::other)
505        } else {
506            Ok(Default::default())
507        }
508    }
509
510    /// Returns the current fee settings for transactions from the mempool
511    pub fn best_transaction_attributes(&self, block_env: &BlockEnv) -> BestTransactionsAttributes {
512        BestTransactionsAttributes::new(
513            block_env.basefee,
514            block_env.blob_gasprice().map(|p| p as u64),
515        )
516    }
517
518    /// Returns the unique id for this payload job.
519    pub fn payload_id(&self) -> PayloadId {
520        self.attributes().payload_id()
521    }
522
523    /// Returns true if holocene is active for the payload.
524    pub fn is_holocene_active(&self) -> bool {
525        self.chain_spec.is_holocene_active_at_timestamp(self.attributes().timestamp())
526    }
527
528    /// Returns true if the fees are higher than the previous payload.
529    pub fn is_better_payload(&self, total_fees: U256) -> bool {
530        is_better_payload(self.best_payload.as_ref(), total_fees)
531    }
532
533    /// Prepares a [`BlockBuilder`] for the next block.
534    pub fn block_builder<'a, DB: Database>(
535        &'a self,
536        db: &'a mut State<DB>,
537    ) -> Result<impl BlockBuilder<Primitives = Evm::Primitives> + 'a, PayloadBuilderError> {
538        self.evm_config
539            .builder_for_next_block(
540                db,
541                self.parent(),
542                OpNextBlockEnvAttributes {
543                    timestamp: self.attributes().timestamp(),
544                    suggested_fee_recipient: self.attributes().suggested_fee_recipient(),
545                    prev_randao: self.attributes().prev_randao(),
546                    gas_limit: self.attributes().gas_limit.unwrap_or(self.parent().gas_limit),
547                    parent_beacon_block_root: self.attributes().parent_beacon_block_root(),
548                    extra_data: self.extra_data()?,
549                },
550            )
551            .map_err(PayloadBuilderError::other)
552    }
553}
554
555impl<Evm, ChainSpec> OpPayloadBuilderCtx<Evm, ChainSpec>
556where
557    Evm: ConfigureEvm<Primitives: OpPayloadPrimitives, NextBlockEnvCtx = OpNextBlockEnvAttributes>,
558    ChainSpec: EthChainSpec + OpHardforks,
559{
560    /// Executes all sequencer transactions that are included in the payload attributes.
561    pub fn execute_sequencer_transactions(
562        &self,
563        builder: &mut impl BlockBuilder<Primitives = Evm::Primitives>,
564    ) -> Result<ExecutionInfo, PayloadBuilderError> {
565        let mut info = ExecutionInfo::new();
566
567        for sequencer_tx in &self.attributes().transactions {
568            // A sequencer's block should never contain blob transactions.
569            if sequencer_tx.value().is_eip4844() {
570                return Err(PayloadBuilderError::other(
571                    OpPayloadBuilderError::BlobTransactionRejected,
572                ))
573            }
574
575            // Convert the transaction to a [RecoveredTx]. This is
576            // purely for the purposes of utilizing the `evm_config.tx_env`` function.
577            // Deposit transactions do not have signatures, so if the tx is a deposit, this
578            // will just pull in its `from` address.
579            let sequencer_tx = sequencer_tx.value().try_clone_into_recovered().map_err(|_| {
580                PayloadBuilderError::other(OpPayloadBuilderError::TransactionEcRecoverFailed)
581            })?;
582
583            let gas_used = match builder.execute_transaction(sequencer_tx.clone()) {
584                Ok(gas_used) => gas_used,
585                Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx {
586                    error,
587                    ..
588                })) => {
589                    trace!(target: "payload_builder", %error, ?sequencer_tx, "Error in sequencer transaction, skipping.");
590                    continue
591                }
592                Err(err) => {
593                    // this is an error that we should treat as fatal for this attempt
594                    return Err(PayloadBuilderError::EvmExecutionError(Box::new(err)))
595                }
596            };
597
598            // add gas used by the transaction to cumulative gas used, before creating the receipt
599            info.cumulative_gas_used += gas_used;
600        }
601
602        Ok(info)
603    }
604
605    /// Executes the given best transactions and updates the execution info.
606    ///
607    /// Returns `Ok(Some(())` if the job was cancelled.
608    pub fn execute_best_transactions(
609        &self,
610        info: &mut ExecutionInfo,
611        builder: &mut impl BlockBuilder<Primitives = Evm::Primitives>,
612        mut best_txs: impl PayloadTransactions<
613            Transaction: PoolTransaction<Consensus = TxTy<Evm::Primitives>>,
614        >,
615    ) -> Result<Option<()>, PayloadBuilderError> {
616        let block_gas_limit = builder.evm_mut().block().gas_limit;
617        let block_da_limit = self.da_config.max_da_block_size();
618        let tx_da_limit = self.da_config.max_da_tx_size();
619        let base_fee = builder.evm_mut().block().basefee;
620
621        while let Some(tx) = best_txs.next(()) {
622            let tx = tx.into_consensus();
623            if info.is_tx_over_limits(tx.inner(), block_gas_limit, tx_da_limit, block_da_limit) {
624                // we can't fit this transaction into the block, so we need to mark it as
625                // invalid which also removes all dependent transaction from
626                // the iterator before we can continue
627                best_txs.mark_invalid(tx.signer(), tx.nonce());
628                continue
629            }
630
631            // A sequencer's block should never contain blob or deposit transactions from the pool.
632            if tx.is_eip4844() || tx.is_deposit() {
633                best_txs.mark_invalid(tx.signer(), tx.nonce());
634                continue
635            }
636
637            // check if the job was cancelled, if so we can exit early
638            if self.cancel.is_cancelled() {
639                return Ok(Some(()))
640            }
641
642            let gas_used = match builder.execute_transaction(tx.clone()) {
643                Ok(gas_used) => gas_used,
644                Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx {
645                    error,
646                    ..
647                })) => {
648                    if error.is_nonce_too_low() {
649                        // if the nonce is too low, we can skip this transaction
650                        trace!(target: "payload_builder", %error, ?tx, "skipping nonce too low transaction");
651                    } else {
652                        // if the transaction is invalid, we can skip it and all of its
653                        // descendants
654                        trace!(target: "payload_builder", %error, ?tx, "skipping invalid transaction and its descendants");
655                        best_txs.mark_invalid(tx.signer(), tx.nonce());
656                    }
657                    continue
658                }
659                Err(err) => {
660                    // this is an error that we should treat as fatal for this attempt
661                    return Err(PayloadBuilderError::EvmExecutionError(Box::new(err)))
662                }
663            };
664
665            // add gas used by the transaction to cumulative gas used, before creating the
666            // receipt
667            info.cumulative_gas_used += gas_used;
668            info.cumulative_da_bytes_used += tx.length() as u64;
669
670            // update add to total fees
671            let miner_fee = tx
672                .effective_tip_per_gas(base_fee)
673                .expect("fee is always valid; execution succeeded");
674            info.total_fees += U256::from(miner_fee) * U256::from(gas_used);
675        }
676
677        Ok(None)
678    }
679}