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