reth_optimism_payload_builder/
builder.rs

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