1use 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#[derive(Debug)]
48pub struct OpPayloadBuilder<
49 Pool,
50 Client,
51 Evm,
52 Txs = (),
53 Attrs = OpPayloadBuilderAttributes<TxTy<<Evm as ConfigureEvm>::Primitives>>,
54> {
55 pub compute_pending_block: bool,
58 pub evm_config: Evm,
60 pub pool: Pool,
62 pub client: Client,
64 pub config: OpBuilderConfig,
66 pub best_transactions: Txs,
69 _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 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 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 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 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 pub const fn compute_pending_block(self) -> Self {
147 self.set_compute_pending_block(true)
148 }
149
150 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 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 builder.build(cached_reads.as_db_mut(state), &state_provider, ctx)
205 }
206 .map(|out| out.with_cached_reads(cached_reads))
207 }
208
209 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
238impl<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 MissingPayloadBehaviour::AwaitInProgress
270 }
271
272 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#[derive(derive_more::Debug)]
306pub struct OpBuilder<'a, Txs> {
307 #[debug(skip)]
309 best: Box<dyn FnOnce(BestTransactionsAttributes) -> Txs + 'a>,
310}
311
312impl<'a, Txs> OpBuilder<'a, Txs> {
313 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 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 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 let mut info = ctx.execute_sequencer_transactions(&mut builder)?;
353
354 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 if !ctx.is_better_payload(info.total_fees) {
363 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 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 Ok(BuildOutcomeKind::Freeze(payload))
401 } else {
402 Ok(BuildOutcomeKind::Better { payload })
403 }
404 }
405
406 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 _ = 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
450pub trait OpPayloadTransactions<Transaction>: Clone + Send + Sync + Unpin + 'static {
452 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#[derive(Debug)]
473pub struct ExecutedPayload<N: NodePrimitives> {
474 pub info: ExecutionInfo,
476 pub withdrawals_root: Option<B256>,
478 pub receipts: Vec<N::Receipt>,
480 pub block_env: BlockEnv,
482}
483
484#[derive(Default, Debug)]
486pub struct ExecutionInfo {
487 pub cumulative_gas_used: u64,
489 pub cumulative_da_bytes_used: u64,
491 pub total_fees: U256,
493}
494
495impl ExecutionInfo {
496 pub const fn new() -> Self {
498 Self { cumulative_gas_used: 0, cumulative_da_bytes_used: 0, total_fees: U256::ZERO }
499 }
500
501 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#[derive(derive_more::Debug)]
531pub struct OpPayloadBuilderCtx<
532 Evm: ConfigureEvm,
533 ChainSpec,
534 Attrs = OpPayloadBuilderAttributes<TxTy<<Evm as ConfigureEvm>::Primitives>>,
535> {
536 pub evm_config: Evm,
538 pub da_config: OpDAConfig,
540 pub chain_spec: Arc<ChainSpec>,
542 pub config: PayloadConfig<Attrs, HeaderTy<Evm::Primitives>>,
544 pub cancel: CancelOnDrop,
546 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 pub fn parent(&self) -> &SealedHeaderFor<Evm::Primitives> {
561 self.config.parent_header.as_ref()
562 }
563
564 pub const fn attributes(&self) -> &Attrs {
566 &self.config.attributes
567 }
568
569 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 pub fn payload_id(&self) -> PayloadId {
579 self.attributes().payload_id()
580 }
581
582 pub fn is_better_payload(&self, total_fees: U256) -> bool {
584 is_better_payload(self.best_payload.as_ref(), total_fees)
585 }
586
587 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 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 if sequencer_tx.value().is_eip4844() {
616 return Err(PayloadBuilderError::other(
617 OpPayloadBuilderError::BlobTransactionRejected,
618 ))
619 }
620
621 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 return Err(PayloadBuilderError::EvmExecutionError(Box::new(err)))
641 }
642 };
643
644 info.cumulative_gas_used += gas_used;
646 }
647
648 Ok(info)
649 }
650
651 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 best_txs.mark_invalid(tx.signer(), tx.nonce());
682 continue
683 }
684
685 if tx.is_eip4844() || tx.is_deposit() {
687 best_txs.mark_invalid(tx.signer(), tx.nonce());
688 continue
689 }
690
691 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 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 trace!(target: "payload_builder", %error, ?tx, "skipping nonce too low transaction");
713 } else {
714 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 return Err(PayloadBuilderError::EvmExecutionError(Box::new(err)))
724 }
725 };
726
727 info.cumulative_gas_used += gas_used;
730 info.cumulative_da_bytes_used += tx_da_size;
731
732 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}