reth_optimism_evm/
build.rs

1use alloc::sync::Arc;
2use alloy_consensus::{
3    constants::EMPTY_WITHDRAWALS, proofs, Block, BlockBody, Header, TxReceipt,
4    EMPTY_OMMER_ROOT_HASH,
5};
6use alloy_eips::{eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE};
7use alloy_evm::block::BlockExecutorFactory;
8use alloy_op_evm::OpBlockExecutionCtx;
9use alloy_primitives::logs_bloom;
10use reth_evm::execute::{BlockAssembler, BlockAssemblerInput};
11use reth_execution_errors::BlockExecutionError;
12use reth_execution_types::BlockExecutionResult;
13use reth_optimism_consensus::{calculate_receipt_root_no_memo_optimism, isthmus};
14use reth_optimism_forks::OpHardforks;
15use reth_optimism_primitives::DepositReceipt;
16use reth_primitives_traits::{Receipt, SignedTransaction};
17use revm::context::Block as _;
18
19/// Block builder for Optimism.
20#[derive(Debug)]
21pub struct OpBlockAssembler<ChainSpec> {
22    chain_spec: Arc<ChainSpec>,
23}
24
25impl<ChainSpec> OpBlockAssembler<ChainSpec> {
26    /// Creates a new [`OpBlockAssembler`].
27    pub const fn new(chain_spec: Arc<ChainSpec>) -> Self {
28        Self { chain_spec }
29    }
30}
31
32impl<ChainSpec: OpHardforks> OpBlockAssembler<ChainSpec> {
33    /// Builds a block for `input` without any bounds on header `H`.
34    pub fn assemble_block<
35        F: for<'a> BlockExecutorFactory<
36            ExecutionCtx<'a>: Into<OpBlockExecutionCtx>,
37            Transaction: SignedTransaction,
38            Receipt: Receipt + DepositReceipt,
39        >,
40        H,
41    >(
42        &self,
43        input: BlockAssemblerInput<'_, '_, F, H>,
44    ) -> Result<Block<F::Transaction>, BlockExecutionError> {
45        let BlockAssemblerInput {
46            evm_env,
47            execution_ctx: ctx,
48            transactions,
49            output: BlockExecutionResult { receipts, gas_used, blob_gas_used, requests: _ },
50            bundle_state,
51            state_root,
52            state_provider,
53            ..
54        } = input;
55        let ctx = ctx.into();
56
57        let timestamp = evm_env.block_env.timestamp().saturating_to();
58
59        let transactions_root = proofs::calculate_transaction_root(&transactions);
60        let receipts_root =
61            calculate_receipt_root_no_memo_optimism(receipts, &self.chain_spec, timestamp);
62        let logs_bloom = logs_bloom(receipts.iter().flat_map(|r| r.logs()));
63
64        let mut requests_hash = None;
65
66        let withdrawals_root = if self.chain_spec.is_isthmus_active_at_timestamp(timestamp) {
67            // always empty requests hash post isthmus
68            requests_hash = Some(EMPTY_REQUESTS_HASH);
69
70            // withdrawals root field in block header is used for storage root of L2 predeploy
71            // `l2tol1-message-passer`
72            Some(
73                isthmus::withdrawals_root(bundle_state, state_provider)
74                    .map_err(BlockExecutionError::other)?,
75            )
76        } else if self.chain_spec.is_canyon_active_at_timestamp(timestamp) {
77            Some(EMPTY_WITHDRAWALS)
78        } else {
79            None
80        };
81
82        let (excess_blob_gas, blob_gas_used) =
83            if self.chain_spec.is_jovian_active_at_timestamp(timestamp) {
84                // In jovian, we're using the blob gas used field to store the current da
85                // footprint's value.
86                (Some(0), Some(*blob_gas_used))
87            } else if self.chain_spec.is_ecotone_active_at_timestamp(timestamp) {
88                (Some(0), Some(0))
89            } else {
90                (None, None)
91            };
92
93        let header = Header {
94            parent_hash: ctx.parent_hash,
95            ommers_hash: EMPTY_OMMER_ROOT_HASH,
96            beneficiary: evm_env.block_env.beneficiary(),
97            state_root,
98            transactions_root,
99            receipts_root,
100            withdrawals_root,
101            logs_bloom,
102            timestamp,
103            mix_hash: evm_env.block_env.prevrandao().unwrap_or_default(),
104            nonce: BEACON_NONCE.into(),
105            base_fee_per_gas: Some(evm_env.block_env.basefee()),
106            number: evm_env.block_env.number().saturating_to(),
107            gas_limit: evm_env.block_env.gas_limit(),
108            difficulty: evm_env.block_env.difficulty(),
109            gas_used: *gas_used,
110            extra_data: ctx.extra_data,
111            parent_beacon_block_root: ctx.parent_beacon_block_root,
112            blob_gas_used,
113            excess_blob_gas,
114            requests_hash,
115        };
116
117        Ok(Block::new(
118            header,
119            BlockBody {
120                transactions,
121                ommers: Default::default(),
122                withdrawals: self
123                    .chain_spec
124                    .is_canyon_active_at_timestamp(timestamp)
125                    .then(Default::default),
126            },
127        ))
128    }
129}
130
131impl<ChainSpec> Clone for OpBlockAssembler<ChainSpec> {
132    fn clone(&self) -> Self {
133        Self { chain_spec: self.chain_spec.clone() }
134    }
135}
136
137impl<F, ChainSpec> BlockAssembler<F> for OpBlockAssembler<ChainSpec>
138where
139    ChainSpec: OpHardforks,
140    F: for<'a> BlockExecutorFactory<
141        ExecutionCtx<'a> = OpBlockExecutionCtx,
142        Transaction: SignedTransaction,
143        Receipt: Receipt + DepositReceipt,
144    >,
145{
146    type Block = Block<F::Transaction>;
147
148    fn assemble_block(
149        &self,
150        input: BlockAssemblerInput<'_, '_, F>,
151    ) -> Result<Self::Block, BlockExecutionError> {
152        self.assemble_block(input)
153    }
154}