diff --git a/Cargo.lock b/Cargo.lock index f6897d8..a2a1d81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4514,8 +4514,8 @@ dependencies = [ "morph-primitives", "morph-revm", "reth-chainspec", + "reth-ethereum-primitives", "reth-evm", - "reth-evm-ethereum", "reth-primitives-traits", "reth-revm", "reth-rpc-eth-api", @@ -6772,7 +6772,6 @@ dependencies = [ "alloy-evm", "alloy-primitives", "alloy-rpc-types-engine", - "derive_more", "reth-chainspec", "reth-ethereum-forks", "reth-ethereum-primitives", diff --git a/crates/evm/Cargo.toml b/crates/evm/Cargo.toml index 97d6884..b5027c7 100644 --- a/crates/evm/Cargo.toml +++ b/crates/evm/Cargo.toml @@ -13,12 +13,12 @@ workspace = true [dependencies] morph-chainspec.workspace = true -morph-primitives.workspace = true +morph-primitives = { workspace = true, features = ["serde-bincode-compat"] } morph-revm.workspace = true reth-chainspec.workspace = true reth-evm.workspace = true -reth-evm-ethereum.workspace = true +reth-ethereum-primitives = { workspace = true, features = ["serde", "serde-bincode-compat"] } reth-revm.workspace = true reth-primitives-traits.workspace = true reth-rpc-eth-api = { workspace = true, optional = true } diff --git a/crates/evm/src/assemble.rs b/crates/evm/src/assemble.rs index f2142ac..31cd2c7 100644 --- a/crates/evm/src/assemble.rs +++ b/crates/evm/src/assemble.rs @@ -1,67 +1,136 @@ -use crate::{ - MorphEvmConfig, MorphEvmFactory, block::MorphReceiptBuilder, context::MorphBlockExecutionCtx, -}; -use alloy_evm::{block::BlockExecutionError, eth::EthBlockExecutorFactory}; +use crate::MorphEvmConfig; +use alloy_consensus::{BlockBody, EMPTY_OMMER_ROOT_HASH, Header, TxReceipt, proofs}; +use alloy_evm::block::{BlockExecutionError, BlockExecutionResult}; +use alloy_primitives::{Address, B64, logs_bloom}; use morph_chainspec::MorphChainSpec; -use morph_primitives::MorphHeader; +use morph_primitives::{MorphHeader, receipt::calculate_receipt_root_no_memo}; +use reth_chainspec::EthereumHardforks; use reth_evm::execute::{BlockAssembler, BlockAssemblerInput}; -use reth_evm_ethereum::EthBlockAssembler; -use reth_primitives_traits::SealedHeader; +use revm::context::Block; use std::sync::Arc; /// Assembler for Morph blocks. +/// +/// This assembler builds Morph blocks from the execution output. +/// Unlike `EthBlockAssembler`, it produces `MorphHeader` with proper +/// L2-specific fields (next_l1_msg_index, batch_hash). #[derive(Debug, Clone)] pub struct MorphBlockAssembler { - pub(crate) inner: EthBlockAssembler, + /// Chain specification + chain_spec: Arc, } impl MorphBlockAssembler { + /// Creates a new [`MorphBlockAssembler`] with the given chain specification. + /// + /// # Arguments + /// * `chain_spec` - The Morph chain specification used for hardfork detection pub fn new(chain_spec: Arc) -> Self { - Self { - inner: EthBlockAssembler::new(chain_spec), - } + Self { chain_spec } + } + + /// Returns the chain spec. + pub const fn chain_spec(&self) -> &Arc { + &self.chain_spec } } impl BlockAssembler for MorphBlockAssembler { type Block = morph_primitives::Block; + /// Assembles a Morph block from execution results. + /// + /// This method constructs a complete [`Block`] from the execution output, + /// including building the proper [`MorphHeader`] with L2-specific fields. + /// + /// # Block Assembly Process + /// 1. **Calculate Merkle Roots**: Computes transaction root and receipt root + /// 2. **Build Logs Bloom**: Aggregates logs from all receipts + /// 3. **Check Hardforks**: Determines if EIP-1559 (London) is active for base fee + /// 4. **Build Header**: Creates standard Ethereum header fields + /// 5. **Wrap in MorphHeader**: Adds L2-specific fields (next_l1_msg_index, batch_hash) + /// 6. **Build Block Body**: Combines transactions and empty ommers + /// + /// # L2-Specific Fields + /// - `next_l1_msg_index`: Inherited from parent block + /// - `batch_hash`: Set to default (will be filled by payload builder) + /// + /// # Arguments + /// * `input` - Contains execution context, transactions, receipts, and state root + /// + /// # Returns + /// A fully assembled Morph block ready for sealing. + /// + /// # Errors + /// Returns error if block assembly fails (should not occur in normal operation). fn assemble_block( &self, input: BlockAssemblerInput<'_, '_, MorphEvmConfig, MorphHeader>, ) -> Result { let BlockAssemblerInput { evm_env, - execution_ctx: MorphBlockExecutionCtx { inner }, + execution_ctx, parent, transactions, - output, - bundle_state, - state_provider, + output: BlockExecutionResult { + receipts, gas_used, .. + }, state_root, .. } = input; - // Convert MorphHeader parent to standard Header for the inner assembler. - // We extract the inner Header since EthBlockAssembler works with standard Headers. - let inner_parent = SealedHeader::new_unhashed(parent.header().inner.clone()); + let timestamp = evm_env.block_env.timestamp(); + let block_number: u64 = evm_env.block_env.number().to(); - // Delegate block building to the inner assembler - let block = self.inner.assemble_block(BlockAssemblerInput::< - EthBlockExecutorFactory, - >::new( - evm_env, - inner, - &inner_parent, - transactions, - output, - bundle_state, - state_provider, + // Calculate roots and bloom + let transactions_root = proofs::calculate_transaction_root(&transactions); + let receipts_root = calculate_receipt_root_no_memo(receipts); + let logs_bloom = logs_bloom(receipts.iter().flat_map(|r| r.logs())); + + // Determine if EIP-1559 is active + let is_london_active = self.chain_spec.is_london_active_at_block(block_number); + + // Build standard Ethereum header + let inner = Header { + parent_hash: execution_ctx.parent_hash, + ommers_hash: EMPTY_OMMER_ROOT_HASH, + beneficiary: Address::ZERO, state_root, - ))?; + transactions_root, + receipts_root, + withdrawals_root: None, + logs_bloom, + timestamp: timestamp.to(), + mix_hash: evm_env.block_env.prevrandao().unwrap_or_default(), + nonce: B64::ZERO, + // Only include base_fee_per_gas after London (EIP-1559) + base_fee_per_gas: is_london_active.then_some(evm_env.block_env.basefee()), + number: block_number, + gas_limit: evm_env.block_env.gas_limit(), + difficulty: evm_env.block_env.difficulty(), + gas_used: *gas_used, + extra_data: Default::default(), + parent_beacon_block_root: None, + blob_gas_used: None, + excess_blob_gas: None, + requests_hash: None, + }; + + // Wrap in MorphHeader with L2-specific fields + // Note: next_l1_msg_index and batch_hash will be set by the payload builder + let header = MorphHeader { + inner, + next_l1_msg_index: parent.header().next_l1_msg_index, + batch_hash: Default::default(), + }; - // Convert the standard Header back to MorphHeader. - // The next_l1_msg_index and batch_hash will be set by the payload builder. - Ok(block.map_header(MorphHeader::from)) + Ok(alloy_consensus::Block::new( + header, + BlockBody { + transactions, + ommers: Default::default(), + withdrawals: None, + }, + )) } } diff --git a/crates/evm/src/block/factory.rs b/crates/evm/src/block/factory.rs new file mode 100644 index 0000000..969a35b --- /dev/null +++ b/crates/evm/src/block/factory.rs @@ -0,0 +1,86 @@ +//! Block executor factory for Morph. +//! +//! This module provides the [`MorphBlockExecutorFactory`] which creates block executors +//! that can execute Morph L2 blocks with proper L1 fee calculation and receipt building. + +use crate::{ + block::{DefaultMorphReceiptBuilder, MorphBlockExecutor}, + evm::MorphEvm, +}; +use alloy_evm::{ + Database, + block::{BlockExecutorFactory, BlockExecutorFor}, + eth::EthBlockExecutionCtx, + revm::{Inspector, database::State}, +}; +use morph_chainspec::MorphChainSpec; +use morph_primitives::{MorphReceipt, MorphTxEnvelope}; +use morph_revm::evm::MorphContext; +use std::sync::Arc; + +use crate::evm::MorphEvmFactory; + +/// Block executor factory for Morph. +/// +/// This factory creates [`MorphBlockExecutor`] instances that handle Morph-specific +/// block execution logic including: +/// - L1 fee calculation for transactions +/// - Token fee information extraction for MorphTx (0x7F) transactions +/// - Curie hardfork application +/// +/// Unlike using `EthBlockExecutorFactory`, this factory uses the custom +/// `MorphReceiptBuilder` trait which includes `l1_fee` in its context, +/// ensuring receipts are built with complete information. +#[derive(Debug, Clone)] +pub(crate) struct MorphBlockExecutorFactory { + /// Receipt builder + receipt_builder: DefaultMorphReceiptBuilder, + /// Chain specification + spec: Arc, + /// EVM factory + evm_factory: MorphEvmFactory, +} + +impl MorphBlockExecutorFactory { + /// Creates a new [`MorphBlockExecutorFactory`]. + pub(crate) fn new(spec: Arc, evm_factory: MorphEvmFactory) -> Self { + Self { + receipt_builder: DefaultMorphReceiptBuilder, + spec, + evm_factory, + } + } + + /// Returns the chain spec. + pub(crate) const fn spec(&self) -> &Arc { + &self.spec + } + + /// Returns the EVM factory. + pub(crate) const fn evm_factory(&self) -> &MorphEvmFactory { + &self.evm_factory + } +} + +impl BlockExecutorFactory for MorphBlockExecutorFactory { + type EvmFactory = MorphEvmFactory; + type ExecutionCtx<'a> = EthBlockExecutionCtx<'a>; + type Transaction = MorphTxEnvelope; + type Receipt = MorphReceipt; + + fn evm_factory(&self) -> &Self::EvmFactory { + &self.evm_factory + } + + fn create_executor<'a, DB, I>( + &'a self, + evm: MorphEvm<&'a mut State, I>, + _ctx: Self::ExecutionCtx<'a>, + ) -> impl BlockExecutorFor<'a, Self, DB, I> + where + DB: Database + 'a, + I: Inspector>> + 'a, + { + MorphBlockExecutor::new(evm, &self.spec, &self.receipt_builder) + } +} diff --git a/crates/evm/src/block/mod.rs b/crates/evm/src/block/mod.rs index f6e6839..ee3573b 100644 --- a/crates/evm/src/block/mod.rs +++ b/crates/evm/src/block/mod.rs @@ -2,35 +2,77 @@ //! //! This module provides block execution functionality for Morph, including: //! - [`MorphBlockExecutor`]: The main block executor +//! - [`MorphBlockExecutorFactory`]: Factory for creating block executors //! - [`MorphReceiptBuilder`]: Receipt construction for transactions //! - Hardfork application logic (Curie, etc.) pub(crate) mod curie; +mod factory; mod receipt; -pub(crate) use receipt::MorphReceiptBuilder; +pub(crate) use factory::MorphBlockExecutorFactory; +pub(crate) use receipt::{ + DefaultMorphReceiptBuilder, MorphReceiptBuilder, MorphReceiptBuilderCtx, MorphTxFields, +}; -use crate::{MorphBlockExecutionCtx, evm::MorphEvm}; +use crate::evm::MorphEvm; +use alloy_consensus::Transaction; +use alloy_consensus::transaction::TxHashRef; use alloy_evm::{ Database, Evm, block::{BlockExecutionError, BlockExecutionResult, BlockExecutor, ExecutableTx, OnStateHook}, - eth::EthBlockExecutor, }; +use alloy_primitives::U256; use curie::apply_curie_hard_fork; use morph_chainspec::{MorphChainSpec, MorphHardfork, MorphHardforks}; use morph_primitives::{MorphReceipt, MorphTxEnvelope}; -use morph_revm::{L1_GAS_PRICE_ORACLE_ADDRESS, MorphHaltReason, evm::MorphContext}; -use reth_revm::{Inspector, State, context::result::ResultAndState}; +use morph_revm::{ + L1_GAS_PRICE_ORACLE_ADDRESS, L1BlockInfo, MorphHaltReason, TokenFeeInfo, evm::MorphContext, +}; +use reth_chainspec::EthereumHardforks; +use reth_revm::{DatabaseCommit, Inspector, State, context::result::ResultAndState}; +use revm::context::Block; +use std::marker::PhantomData; -/// Block executor for Morph. Wraps an inner [`EthBlockExecutor`]. +/// Block executor for Morph L2 blocks. +/// +/// This executor handles Morph-specific block execution logic, differing from +/// standard Ethereum execution in several key ways: +/// +/// ## L1 Fee Calculation +/// All L2 transactions (except L1 messages) must pay an L1 data fee for posting +/// transaction data to L1. This fee is calculated based on: +/// - The RLP-encoded transaction size +/// - Current L1 gas price from the L1 Gas Price Oracle contract +/// - Hardfork-specific fee calculation logic (pre-Curie vs post-Curie) +/// +/// ## Token Fee Support (MorphTx 0x7F) +/// MorphTx transactions allow users to pay gas fees using ERC20 tokens. +/// The executor extracts token fee information from the L2TokenRegistry contract, +/// including exchange rate and scale factor. +/// +/// ## Hardfork Application +/// The executor applies hardfork-specific state changes at transition blocks, +/// such as the Curie hardfork which updates the L1 Gas Price Oracle contract. +/// +/// ## Execution Flow +/// 1. `apply_pre_execution_changes`: Set up state, load contracts, apply hardforks +/// 2. `execute_transaction_without_commit`: Execute transaction in EVM +/// 3. `commit_transaction`: Calculate fees, build receipt, commit state +/// 4. `finish`: Return final execution result with all receipts pub(crate) struct MorphBlockExecutor<'a, DB: Database, I> { - /// Inner Ethereum block executor. - pub(crate) inner: EthBlockExecutor< - 'a, - MorphEvm<&'a mut State, I>, - &'a MorphChainSpec, - MorphReceiptBuilder, - >, + /// The EVM used by executor + evm: MorphEvm<&'a mut State, I>, + /// Chain specification + spec: &'a MorphChainSpec, + /// Receipt builder + receipt_builder: &'a DefaultMorphReceiptBuilder, + /// Receipts of executed transactions + receipts: Vec, + /// Total gas used by executed transactions + gas_used: u64, + /// Phantom data for inspector type + _phantom: PhantomData, } impl<'a, DB, I> MorphBlockExecutor<'a, DB, I> @@ -38,20 +80,142 @@ where DB: Database, I: Inspector>>, { + /// Creates a new [`MorphBlockExecutor`]. + /// + /// # Arguments + /// * `evm` - The EVM instance configured for Morph execution + /// * `spec` - Chain specification containing hardfork information + /// * `receipt_builder` - Builder for constructing transaction receipts pub(crate) fn new( evm: MorphEvm<&'a mut State, I>, - ctx: MorphBlockExecutionCtx<'a>, - chain_spec: &'a MorphChainSpec, + spec: &'a MorphChainSpec, + receipt_builder: &'a DefaultMorphReceiptBuilder, ) -> Self { Self { - inner: EthBlockExecutor::new( - evm, - ctx.inner, - chain_spec, - MorphReceiptBuilder::default(), - ), + evm, + spec, + receipt_builder, + receipts: Vec::new(), + gas_used: 0, + _phantom: PhantomData, } } + + /// Calculate the L1 data fee for a transaction. + /// + /// The L1 fee compensates for the cost of posting transaction data to Ethereum L1. + /// This is a key component of L2 transaction costs on Morph. + /// + /// # Calculation Steps + /// 1. Check if transaction is an L1 message (which don't pay L1 fees) + /// 2. Get RLP-encoded transaction bytes + /// 3. Fetch L1 block info from L1 Gas Price Oracle contract + /// 4. Calculate fee based on transaction size and L1 gas price + /// + /// # Arguments + /// * `tx` - The transaction to calculate L1 fee for + /// * `hardfork` - The current Morph hardfork (affects fee calculation formula) + /// + /// # Returns + /// - `Ok(U256::ZERO)` for L1 message transactions + /// - `Ok(fee)` for regular transactions, where fee = f(tx_size, l1_gas_price, hardfork) + /// - `Err` if L1 block info cannot be fetched + /// + /// # Errors + /// Returns error if the L1 Gas Price Oracle contract state cannot be read. + fn calculate_l1_fee( + &mut self, + tx: &MorphTxEnvelope, + hardfork: MorphHardfork, + ) -> Result { + // L1 message transactions don't pay L1 fees + if tx.is_l1_msg() { + return Ok(U256::ZERO); + } + + // Get the RLP-encoded transaction bytes + let rlp_bytes = tx.rlp(); + + // Fetch L1 block info from the L1 Gas Price Oracle contract + let l1_block_info = L1BlockInfo::try_fetch(self.evm.db_mut(), hardfork).map_err(|e| { + BlockExecutionError::msg(format!("Failed to fetch L1 block info: {e:?}")) + })?; + + // Calculate L1 data fee + Ok(l1_block_info.calculate_tx_l1_cost(rlp_bytes.as_ref(), hardfork)) + } + + /// Extract MorphTx-specific fields for MorphTx (0x7F) transactions. + /// + /// MorphTx transactions include: + /// - Token fee information (when using ERC20 for gas payment) + /// - Transaction metadata (version, reference, memo) + /// + /// # How MorphTx Token Fees Work + /// 1. User specifies a `fee_token_id` (registered ERC20 token) + /// 2. User specifies a `fee_limit` (max tokens willing to pay) + /// 3. System fetches token exchange rate from L2TokenRegistry + /// 4. System converts ETH fee to token amount using: `token_fee = eth_fee * fee_rate / token_scale` + /// 5. System validates user has sufficient token balance + /// + /// # Arguments + /// * `tx` - The transaction to extract fields from + /// * `hardfork` - The current Morph hardfork (affects token registry behavior) + /// + /// # Returns + /// - `Ok(None)` for non-MorphTx transactions + /// - `Ok(Some(fields))` for MorphTx with valid fields + /// - `Err` if MorphTx is missing required fields or token info cannot be fetched + /// + /// # Errors + /// Returns error if: + /// - MorphTx is missing `fee_token_id` or `fee_limit` + /// - Transaction sender cannot be extracted + /// - L2TokenRegistry contract cannot be queried + fn get_morph_tx_fields( + &mut self, + tx: &MorphTxEnvelope, + hardfork: MorphHardfork, + ) -> Result, BlockExecutionError> { + // Only MorphTx transactions have these fields + if !tx.is_morph_tx() { + return Ok(None); + } + + let fee_token_id = tx + .fee_token_id() + .ok_or_else(|| BlockExecutionError::msg("MorphTx missing fee_token_id"))?; + let fee_limit = tx + .fee_limit() + .ok_or_else(|| BlockExecutionError::msg("MorphTx missing fee_limit"))?; + + // Extract version, reference, and memo from the transaction + let version = tx.version().unwrap_or(0); + let reference = tx.reference(); + let memo = tx.memo(); + + // Fetch token fee info from L2TokenRegistry contract + // Note: We use the transaction sender as the caller address + // This is needed to check token balance when validating MorphTx + let sender = tx + .signer_unchecked() + .map_err(|_| BlockExecutionError::msg("Failed to extract signer from MorphTx"))?; + + let token_info = TokenFeeInfo::fetch(self.evm.db_mut(), fee_token_id, sender, hardfork) + .map_err(|e| { + BlockExecutionError::msg(format!("Failed to fetch token fee info: {e:?}")) + })?; + + Ok(token_info.map(|info| MorphTxFields { + version, + fee_token_id, + fee_rate: info.price_ratio, + token_scale: info.scale, + fee_limit, + reference, + memo, + })) + } } impl<'a, DB, I> BlockExecutor for MorphBlockExecutor<'a, DB, I> @@ -63,27 +227,43 @@ where type Receipt = MorphReceipt; type Evm = MorphEvm<&'a mut State, I>; + /// Applies pre-execution state changes before processing transactions. + /// + /// This method performs initialization required before executing any transactions: + /// + /// 1. **State Clear Flag**: Sets the flag that enables EIP-161 state trie clearing + /// if the Spurious Dragon hardfork is active + /// + /// 2. **L1 Gas Oracle Cache**: Loads the L1 Gas Price Oracle contract into the + /// account cache to optimize L1 fee calculations for all transactions + /// + /// 3. **Curie Hardfork**: At the exact Curie activation block, applies the + /// hardfork state changes (updates to L1 Gas Price Oracle contract) + /// + /// # Errors + /// Returns error if: + /// - L1 Gas Price Oracle account cannot be loaded + /// - Curie hardfork application fails at transition block fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> { - // 1. Apply base Ethereum pre-execution changes (state clear flag, EIP-2935, EIP-4788) - self.inner.apply_pre_execution_changes()?; + // 1. Set state clear flag if the block is after the Spurious Dragon hardfork + let block_number: u64 = self.evm.block().number.to(); + let state_clear_flag = self.spec.is_spurious_dragon_active_at_block(block_number); + self.evm.db_mut().set_state_clear_flag(state_clear_flag); - // 2. Load L1 gas oracle contract into cache for Curie hardfork upgrade + // 2. Load L1 gas oracle contract into cache let _ = self - .inner - .evm_mut() + .evm .db_mut() .load_cache_account(L1_GAS_PRICE_ORACLE_ADDRESS) .map_err(BlockExecutionError::other)?; // 3. Apply Curie hardfork at the transition block // Only executes once at the exact block where Curie activates - let block_number = self.inner.evm.block().number.saturating_to(); if self - .inner .spec .morph_fork_activation(MorphHardfork::Curie) .transitions_at_block(block_number) - && let Err(err) = apply_curie_hard_fork(self.inner.evm_mut().db_mut()) + && let Err(err) = apply_curie_hard_fork(self.evm.db_mut()) { return Err(BlockExecutionError::msg(format!( "error occurred at Curie fork: {err:?}" @@ -93,36 +273,144 @@ where Ok(()) } + /// Executes a transaction without committing state changes. + /// + /// This method validates the transaction can fit in the remaining block gas, + /// then executes it in the EVM. The state changes are returned but not yet + /// committed to the database. + /// + /// # Gas Validation + /// Before execution, validates that: + /// ```text + /// tx.gas_limit + cumulative_gas_used <= block.gas_limit + /// ``` + /// + /// # Returns + /// Returns the execution result and state changes that can be committed later. + /// + /// # Errors + /// Returns error if: + /// - Transaction gas limit exceeds available block gas + /// - EVM execution fails (reverts, halts, out of gas, etc.) fn execute_transaction_without_commit( &mut self, tx: impl ExecutableTx, ) -> Result, BlockExecutionError> { - self.inner.execute_transaction_without_commit(tx) + // The sum of the transaction's gas limit and the gas utilized in this block prior, + // must be no greater than the block's gasLimit. + let block_available_gas = self.evm.block().gas_limit() - self.gas_used; + if tx.tx().gas_limit() > block_available_gas { + return Err(BlockExecutionError::msg(format!( + "transaction gas limit {} exceeds block available gas {}", + tx.tx().gas_limit(), + block_available_gas + ))); + } + + // Execute the transaction + self.evm + .transact(&tx) + .map_err(|err| BlockExecutionError::evm(err, *tx.tx().tx_hash())) } + /// Commits a transaction's execution result and builds its receipt. + /// + /// This method performs post-execution processing for a transaction: + /// + /// 1. **L1 Fee Calculation**: Calculates the L1 data fee for the transaction + /// 2. **Token Fee Info**: For MorphTx, extracts token fee information + /// 3. **Gas Accounting**: Updates cumulative gas used for the block + /// 4. **Receipt Building**: Constructs receipt with all Morph-specific fields + /// 5. **State Commit**: Commits the EVM state changes to the database + /// + /// # Arguments + /// * `output` - The execution result from `execute_transaction_without_commit` + /// * `tx` - The original transaction + /// + /// # Returns + /// The gas used by this transaction. + /// + /// # Errors + /// Returns error if L1 fee calculation or token fee info extraction fails. fn commit_transaction( &mut self, output: ResultAndState, tx: impl ExecutableTx, ) -> Result { - self.inner.commit_transaction(output, tx) + let ResultAndState { result, state } = output; + + // Determine hardfork once and reuse for both L1 fee and token fee calculations + let block = self.evm.block(); + let block_number: u64 = block.number.to(); + let timestamp: u64 = block.timestamp.to(); + let hardfork = self.spec.morph_hardfork_at(block_number, timestamp); + + // Calculate L1 fee for the transaction + let l1_fee = self.calculate_l1_fee(tx.tx(), hardfork)?; + + // Get MorphTx-specific fields for MorphTx transactions + let morph_tx_fields = self.get_morph_tx_fields(tx.tx(), hardfork)?; + + // Update cumulative gas used + let gas_used = result.gas_used(); + self.gas_used += gas_used; + + // Build receipt + let ctx: MorphReceiptBuilderCtx<'_, Self::Evm> = MorphReceiptBuilderCtx { + tx: tx.tx(), + result, + cumulative_gas_used: self.gas_used, + l1_fee, + morph_tx_fields, + }; + self.receipts.push(self.receipt_builder.build_receipt(ctx)); + + // Commit state changes + self.evm.db_mut().commit(state); + + Ok(gas_used) } + /// Finalizes block execution and returns the results. + /// + /// Consumes the executor and returns the EVM instance along with the + /// complete execution results including all transaction receipts. + /// + /// # Returns + /// A tuple containing: + /// - The EVM instance (for potential reuse or state access) + /// - Block execution result with receipts, gas used, and empty requests + /// + /// Note: `blob_gas_used` is always 0 as Morph doesn't support EIP-4844 blobs. fn finish( self, ) -> Result<(Self::Evm, BlockExecutionResult), BlockExecutionError> { - self.inner.finish() + Ok(( + self.evm, + BlockExecutionResult { + receipts: self.receipts, + requests: Default::default(), + gas_used: self.gas_used, + blob_gas_used: 0, + }, + )) } - fn set_state_hook(&mut self, hook: Option>) { - self.inner.set_state_hook(hook) + /// Sets a state hook for observing state changes. + /// + /// Note: State hooks are not yet implemented for the Morph block executor. + /// This is a no-op placeholder for future implementation. + fn set_state_hook(&mut self, _hook: Option>) { + // State hooks are not yet supported for Morph block executor } + /// Returns a mutable reference to the EVM instance. fn evm_mut(&mut self) -> &mut Self::Evm { - self.inner.evm_mut() + &mut self.evm } + /// Returns a reference to the EVM instance. fn evm(&self) -> &Self::Evm { - self.inner.evm() + &self.evm } } diff --git a/crates/evm/src/block/receipt.rs b/crates/evm/src/block/receipt.rs index 590b564..d078459 100644 --- a/crates/evm/src/block/receipt.rs +++ b/crates/evm/src/block/receipt.rs @@ -1,35 +1,153 @@ //! Receipt builder for Morph block execution. //! -//! This module provides the [`MorphReceiptBuilder`] which constructs receipts -//! for executed transactions based on their type. +//! This module provides the receipt building infrastructure for Morph L2 blocks. +//! Receipts contain essential information about transaction execution results, +//! including Morph-specific fields like L1 data fees and token fee information. +//! +//! # Why Custom Receipt Builder? +//! +//! Unlike standard Ethereum receipts, Morph receipts include: +//! - **L1 Data Fee**: The cost charged for posting transaction data to L1 +//! - **Token Fee Info**: For MorphTx (0x7F), includes exchange rate, fee limit, reference, and memo +//! +//! The standard `EthBlockExecutor` doesn't have access to L1 fee information +//! during receipt building. This module provides a custom builder that receives +//! the pre-calculated L1 fee as part of its context. +//! +//! # Receipt Types +//! +//! | Transaction Type | Receipt Content | +//! |-----------------|-----------------| +//! | Legacy (0x00) | inner + l1_fee | +//! | EIP-2930 (0x01) | inner + l1_fee | +//! | EIP-1559 (0x02) | inner + l1_fee | +//! | EIP-7702 (0x04) | inner + l1_fee | +//! | L1Message (0x7E) | inner only (no L1 fee) | +//! | MorphTx (0x7F) | inner + l1_fee + token_fee_info + reference + memo | use alloy_consensus::Receipt; -use alloy_evm::{ - Evm, - eth::receipt_builder::{ReceiptBuilder, ReceiptBuilderCtx}, -}; +use alloy_consensus::transaction::TxHashRef; +use alloy_evm::Evm; +use alloy_primitives::{B256, Bytes, U256}; use morph_primitives::{MorphReceipt, MorphTransactionReceipt, MorphTxEnvelope, MorphTxType}; +use revm::context::result::ExecutionResult; +use tracing::warn; + +/// Context for building a Morph receipt. +/// +/// This struct aggregates all information needed to construct a receipt for +/// an executed transaction. It is populated by the block executor after +/// transaction execution and L1 fee calculation. +/// +/// # Fields +/// - `tx`: The original transaction (needed for determining receipt type) +/// - `result`: EVM execution result (success/failure, logs, gas used) +/// - `cumulative_gas_used`: Running total of gas used in the block +/// - `l1_fee`: Pre-calculated L1 data fee for this transaction +/// - `token_fee_info`: Token fee details for MorphTx transactions +#[derive(Debug)] +pub(crate) struct MorphReceiptBuilderCtx<'a, E: Evm> { + /// The executed transaction + pub tx: &'a MorphTxEnvelope, + /// Result of transaction execution + pub result: ExecutionResult, + /// Cumulative gas used in the block up to and including this transaction + pub cumulative_gas_used: u64, + /// L1 data fee for this transaction + pub l1_fee: U256, + /// MorphTx-specific fields (token fee info, version, reference, memo) + pub morph_tx_fields: Option, +} -/// Builder for [`MorphReceipt`]. +/// MorphTx (0x7F) specific fields for receipts. /// -/// Creates the appropriate receipt variant based on transaction type. +/// This struct aggregates all Morph-specific transaction fields that need to be +/// included in the receipt, including: +/// - Token fee information (when using ERC20 for gas payment) +/// - Transaction metadata (version, reference, memo) +/// +/// # Token Fee Calculation Formula +/// ```text +/// token_fee = eth_fee * fee_rate / token_scale +/// ``` +/// +/// # Fields +/// - `version`: The version of the Morph transaction format (0 = legacy, 1 = with reference/memo) +/// - `fee_token_id`: ID of the ERC20 token registered in L2TokenRegistry +/// - `fee_rate`: Exchange rate from L2TokenRegistry (token per ETH) +/// - `token_scale`: Decimal scale factor for the token (e.g., 10^18) +/// - `fee_limit`: Maximum tokens the user agreed to pay +/// - `reference`: 32-byte key for transaction indexing by external systems +/// - `memo`: Arbitrary data field (up to 64 bytes) +#[derive(Debug, Clone)] +pub(crate) struct MorphTxFields { + /// Version of the Morph transaction format + pub version: u8, + /// Token ID for fee payment + pub fee_token_id: u16, + /// Exchange rate for the fee token + pub fee_rate: U256, + /// Scale factor for the token + pub token_scale: U256, + /// Fee limit specified in the transaction + pub fee_limit: U256, + /// Reference key for transaction indexing + pub reference: Option, + /// Memo field for arbitrary data + pub memo: Option, +} + +/// Trait for building Morph receipts from execution context. +/// +/// This trait abstracts receipt construction to allow different implementations +/// (e.g., for testing or custom receipt formats). The default implementation +/// is [`DefaultMorphReceiptBuilder`]. +/// +/// # Thread Safety +/// Implementations must be `Send + Sync` as the builder is shared across +/// the block executor and may be accessed concurrently. +pub(crate) trait MorphReceiptBuilder: Send + Sync { + /// Builds a receipt from the execution context. + /// + /// # Arguments + /// * `ctx` - Context containing transaction, execution result, and fee info + /// + /// # Returns + /// A [`MorphReceipt`] variant appropriate for the transaction type. + fn build_receipt(&self, ctx: MorphReceiptBuilderCtx<'_, E>) -> MorphReceipt; +} + +/// Default builder for [`MorphReceipt`]. +/// +/// This builder creates the appropriate receipt variant based on transaction type: +/// +/// ## Standard Transactions (Legacy, EIP-2930, EIP-1559, EIP-7702) +/// - Wraps the base receipt with L1 fee using `with_l1_fee()` +/// - L1 fee is non-zero for all L2-originated transactions +/// +/// ## L1 Message Transactions (0x7E) +/// - Uses base receipt without L1 fee +/// - These transactions originate from L1 and don't pay L1 data fees +/// +/// ## MorphTx Transactions (0x7F) +/// - Includes L1 fee plus MorphTx-specific fields +/// - Uses `with_morph_tx_v1()` to populate all MorphTx fields +/// - Falls back to `with_l1_fee()` if MorphTx fields are unexpectedly missing +/// +/// # Note +/// The builder is stateless and can be reused across multiple receipts. #[derive(Debug, Clone, Copy, Default)] #[non_exhaustive] -pub(crate) struct MorphReceiptBuilder; - -impl ReceiptBuilder for MorphReceiptBuilder { - type Transaction = MorphTxEnvelope; - type Receipt = MorphReceipt; +pub(crate) struct DefaultMorphReceiptBuilder; - fn build_receipt( - &self, - ctx: ReceiptBuilderCtx<'_, Self::Transaction, E>, - ) -> Self::Receipt { - let ReceiptBuilderCtx { +impl MorphReceiptBuilder for DefaultMorphReceiptBuilder { + fn build_receipt(&self, ctx: MorphReceiptBuilderCtx<'_, E>) -> MorphReceipt { + let MorphReceiptBuilderCtx { tx, result, cumulative_gas_used, - .. + l1_fee, + morph_tx_fields, } = ctx; let inner = Receipt { @@ -39,14 +157,54 @@ impl ReceiptBuilder for MorphReceiptBuilder { }; // Create the appropriate receipt variant based on transaction type - // TODO: Add L1 fee calculation from execution context match tx.tx_type() { - MorphTxType::Legacy => MorphReceipt::Legacy(MorphTransactionReceipt::new(inner)), - MorphTxType::Eip2930 => MorphReceipt::Eip2930(MorphTransactionReceipt::new(inner)), - MorphTxType::Eip1559 => MorphReceipt::Eip1559(MorphTransactionReceipt::new(inner)), - MorphTxType::Eip7702 => MorphReceipt::Eip7702(MorphTransactionReceipt::new(inner)), - MorphTxType::L1Msg => MorphReceipt::L1Msg(inner), - MorphTxType::Morph => MorphReceipt::Morph(MorphTransactionReceipt::new(inner)), + MorphTxType::Legacy => { + MorphReceipt::Legacy(MorphTransactionReceipt::with_l1_fee(inner, l1_fee)) + } + MorphTxType::Eip2930 => { + MorphReceipt::Eip2930(MorphTransactionReceipt::with_l1_fee(inner, l1_fee)) + } + MorphTxType::Eip1559 => { + MorphReceipt::Eip1559(MorphTransactionReceipt::with_l1_fee(inner, l1_fee)) + } + MorphTxType::Eip7702 => { + MorphReceipt::Eip7702(MorphTransactionReceipt::with_l1_fee(inner, l1_fee)) + } + MorphTxType::L1Msg => { + // L1 messages don't pay L1 fees + MorphReceipt::L1Msg(inner) + } + MorphTxType::Morph => { + // MorphTx transactions should always have MorphTx-specific fields. + // If fields are missing, it indicates one of the following: + // 1. The fee token is not registered in L2TokenRegistry + // 2. TokenFeeInfo::fetch returned None (token inactive or query failed) + // 3. A bug in get_morph_tx_fields logic + // + // We log a warning and fallback to L1-fee-only receipt to avoid + // blocking block execution, but this should be investigated. + if let Some(fields) = morph_tx_fields { + MorphReceipt::Morph(MorphTransactionReceipt::with_morph_tx_v1( + inner, + l1_fee, + fields.version, + fields.fee_token_id, + fields.fee_rate, + fields.token_scale, + fields.fee_limit, + fields.reference, + fields.memo, + )) + } else { + warn!( + target: "morph::receipt", + tx_hash = ?tx.tx_hash(), + "MorphTx missing token fee fields - receipt will not include fee token info. \ + This may indicate an unregistered/inactive token or a bug." + ); + MorphReceipt::Morph(MorphTransactionReceipt::with_l1_fee(inner, l1_fee)) + } + } } } } diff --git a/crates/evm/src/config.rs b/crates/evm/src/config.rs index f26ed54..785ca17 100644 --- a/crates/evm/src/config.rs +++ b/crates/evm/src/config.rs @@ -1,7 +1,4 @@ -use crate::{ - MorphBlockAssembler, MorphBlockExecutionCtx, MorphEvmConfig, MorphEvmError, - MorphNextBlockEnvAttributes, -}; +use crate::{MorphBlockAssembler, MorphEvmConfig, MorphEvmError, MorphNextBlockEnvAttributes}; use alloy_consensus::BlockHeader; use morph_chainspec::hardfork::MorphHardforks; use morph_primitives::Block; @@ -85,15 +82,13 @@ impl ConfigureEvm for MorphEvmConfig { fn context_for_block<'a>( &self, block: &'a SealedBlock, - ) -> Result, Self::Error> { - Ok(MorphBlockExecutionCtx { - inner: EthBlockExecutionCtx { - parent_hash: block.header().parent_hash(), - parent_beacon_block_root: block.header().parent_beacon_block_root(), - ommers: &[], - withdrawals: block.body().withdrawals.as_ref().map(Cow::Borrowed), - extra_data: block.extra_data().clone(), - }, + ) -> Result, Self::Error> { + Ok(EthBlockExecutionCtx { + parent_hash: block.header().parent_hash(), + parent_beacon_block_root: block.header().parent_beacon_block_root(), + ommers: &[], + withdrawals: block.body().withdrawals.as_ref().map(Cow::Borrowed), + extra_data: block.extra_data().clone(), }) } @@ -101,15 +96,13 @@ impl ConfigureEvm for MorphEvmConfig { &self, parent: &SealedHeader, attributes: Self::NextBlockEnvCtx, - ) -> Result, Self::Error> { - Ok(MorphBlockExecutionCtx { - inner: EthBlockExecutionCtx { - parent_hash: parent.hash(), - parent_beacon_block_root: attributes.parent_beacon_block_root, - ommers: &[], - withdrawals: attributes.inner.withdrawals.map(Cow::Owned), - extra_data: attributes.inner.extra_data, - }, + ) -> Result, Self::Error> { + Ok(EthBlockExecutionCtx { + parent_hash: parent.hash(), + parent_beacon_block_root: attributes.parent_beacon_block_root, + ommers: &[], + withdrawals: attributes.inner.withdrawals.map(Cow::Owned), + extra_data: attributes.inner.extra_data, }) } } diff --git a/crates/evm/src/context.rs b/crates/evm/src/context.rs index 534eefa..98dd45a 100644 --- a/crates/evm/src/context.rs +++ b/crates/evm/src/context.rs @@ -1,16 +1,7 @@ -use alloy_evm::eth::EthBlockExecutionCtx; use reth_evm::NextBlockEnvAttributes; #[cfg(feature = "rpc")] use reth_primitives_traits::SealedHeader; -/// Execution context for Morph block. -#[derive(Debug, Clone, derive_more::Deref)] -pub struct MorphBlockExecutionCtx<'a> { - /// Inner [`EthBlockExecutionCtx`]. - #[deref] - pub inner: EthBlockExecutionCtx<'a>, -} - /// Context required for next block environment. #[derive(Debug, Clone, derive_more::Deref)] pub struct MorphNextBlockEnvAttributes { diff --git a/crates/evm/src/lib.rs b/crates/evm/src/lib.rs index ae73572..be1fed7 100644 --- a/crates/evm/src/lib.rs +++ b/crates/evm/src/lib.rs @@ -1,49 +1,71 @@ //! Morph EVM implementation. //! //! This crate provides the EVM configuration and block execution logic for Morph L2. +//! It implements reth's trait system to integrate Morph-specific behavior into the +//! standard reth node architecture. //! //! # Main Components //! //! - [`MorphEvmConfig`]: Main EVM configuration that implements `BlockExecutorFactory` -//! - [`MorphEvmFactory`]: Factory for creating Morph EVM instances -//! - [`MorphBlockAssembler`]: Block assembly logic for payload building -//! - [`MorphBlockExecutionCtx`]: Execution context for block processing +//! - [`MorphEvmFactory`]: Factory for creating Morph EVM instances with custom context +//! - [`MorphBlockAssembler`]: Block assembly logic for building `MorphHeader` blocks +//! - [`MorphNextBlockEnvAttributes`]: Attributes for configuring the next block environment //! //! # Architecture //! +//! The crate is structured around reth's EVM trait hierarchy: +//! //! ```text //! ┌─────────────────────────────────────────────────────────────────┐ //! │ MorphEvmConfig │ -//! │ ┌─────────────────────┐ ┌─────────────────────────────────┐ │ -//! │ │ EthEvmConfig │ │ MorphBlockAssembler │ │ -//! │ │ (inner config) │ │ (block building) │ │ -//! │ └─────────────────────┘ └─────────────────────────────────┘ │ +//! │ ┌─────────────────────────┐ ┌─────────────────────────────┐ │ +//! │ │ MorphBlockExecutorFactory│ │ MorphBlockAssembler │ │ +//! │ │ (creates executors) │ │ (block building) │ │ +//! │ └─────────────────────────┘ └─────────────────────────────┘ │ //! │ │ │ //! │ ▼ │ //! │ ┌─────────────────────────────────────────────────────────┐ │ //! │ │ MorphBlockExecutor │ │ -//! │ │ - Executes transactions │ │ -//! │ │ - Handles L1 messages │ │ -//! │ │ - Calculates L1 data fee │ │ +//! │ │ - Executes transactions with Morph EVM │ │ +//! │ │ - Handles L1 message transactions (0x7E) │ │ +//! │ │ - Calculates L1 data fee for all L2 transactions │ │ +//! │ │ - Extracts token fee info for MorphTx (0x7F) │ │ +//! │ │ - Builds receipts with full Morph-specific context │ │ +//! │ │ - Applies hardfork state changes (Curie, etc.) │ │ //! │ └─────────────────────────────────────────────────────────┘ │ //! └─────────────────────────────────────────────────────────────────┘ //! ``` //! -//! # Features +//! # Key Differences from Standard Ethereum +//! +//! 1. **L1 Data Fee**: Every L2 transaction (except L1 messages) pays an L1 fee +//! calculated from the L1 Gas Price Oracle contract state. +//! +//! 2. **Token Gas Payment**: MorphTx (0x7F) allows users to pay gas with ERC20 tokens. +//! The executor queries L2TokenRegistry for exchange rates. //! -//! - `serde-bincode-compat`: Enable `ConfigureEvm` implementation for reth integration -//! - `engine`: Enable engine API types +//! 3. **Custom Receipts**: Receipts include L1 fee and optional token fee info, +//! requiring a custom receipt builder. +//! +//! 4. **L2-Specific Header**: `MorphHeader` extends standard header with +//! `next_l1_msg_index` and `batch_hash` fields. +//! +//! 5. **No Blob Transactions**: EIP-4844 blob transactions are not supported. #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg))] +// reth_ethereum_primitives is explicitly depended on to ensure feature unification +// for NodePrimitives trait bounds that require serde features. +use reth_ethereum_primitives as _; + mod config; mod assemble; pub use assemble::MorphBlockAssembler; mod block; mod context; -pub use context::{MorphBlockExecutionCtx, MorphNextBlockEnvAttributes}; +pub use context::MorphNextBlockEnvAttributes; mod error; pub use error::MorphEvmError; @@ -53,62 +75,92 @@ use std::sync::Arc; use alloy_evm::{ Database, block::{BlockExecutorFactory, BlockExecutorFor}, + eth::EthBlockExecutionCtx, revm::{Inspector, database::State}, }; pub use evm::MorphEvmFactory; use morph_primitives::{MorphReceipt, MorphTxEnvelope}; -use crate::{block::MorphBlockExecutor, evm::MorphEvm}; +use crate::{block::MorphBlockExecutorFactory, evm::MorphEvm}; use morph_chainspec::MorphChainSpec; use morph_revm::evm::MorphContext; -use reth_evm_ethereum::EthEvmConfig; pub use morph_revm::{MorphBlockEnv, MorphHaltReason}; -/// Morph-related EVM configuration. +/// Morph EVM configuration and block executor factory. +/// +/// This is the main entry point for Morph EVM integration with reth. It implements +/// both [`ConfigureEvm`] and [`BlockExecutorFactory`] traits, providing: +/// +/// - EVM environment configuration for block execution and building +/// - Block executor creation with Morph-specific execution logic +/// - Block assembler for constructing `MorphHeader` blocks +/// +/// # Usage +/// +/// Create with a chain specification: +/// # Trait Implementations +/// +/// - `ConfigureEvm`: Provides EVM environment setup and block context creation +/// - `BlockExecutorFactory`: Creates `MorphBlockExecutor` instances for block execution +/// +/// # Thread Safety +/// +/// `MorphEvmConfig` is `Clone` and can be safely shared across threads. +/// The internal executor factory is lightweight and stateless. #[derive(Debug, Clone)] pub struct MorphEvmConfig { - /// Inner evm config - pub inner: EthEvmConfig, + /// Internal block executor factory that creates `MorphBlockExecutor` instances. + executor_factory: MorphBlockExecutorFactory, - /// Block assembler + /// Block assembler for building `MorphHeader` blocks from execution results. pub block_assembler: MorphBlockAssembler, } impl MorphEvmConfig { - /// Create a new [`MorphEvmConfig`] with the given chain spec and EVM factory. + /// Creates a new [`MorphEvmConfig`] with the given chain spec and EVM factory. + /// + /// This is the primary constructor that allows specifying a custom EVM factory + /// for advanced use cases (e.g., custom inspector or precompile configuration). pub fn new(chain_spec: Arc, evm_factory: MorphEvmFactory) -> Self { - let inner = EthEvmConfig::new_with_evm_factory(chain_spec.clone(), evm_factory); + let executor_factory = MorphBlockExecutorFactory::new(chain_spec.clone(), evm_factory); Self { - inner, + executor_factory, block_assembler: MorphBlockAssembler::new(chain_spec), } } - /// Create a new [`MorphEvmConfig`] with the given chain spec and default EVM factory. + /// Creates a new [`MorphEvmConfig`] with the given chain spec and default EVM factory. + /// + /// This is the recommended constructor for most use cases. It uses the default + /// Morph EVM factory with standard configuration. pub fn new_with_default_factory(chain_spec: Arc) -> Self { Self::new(chain_spec, MorphEvmFactory::default()) } - /// Returns the chain spec + /// Returns a reference to the Morph chain specification. + /// + /// The chain spec contains hardfork configuration and network parameters. pub const fn chain_spec(&self) -> &Arc { - self.inner.chain_spec() + self.executor_factory.spec() } - /// Returns the inner EVM config - pub const fn inner(&self) -> &EthEvmConfig { - &self.inner + /// Returns a reference to the Morph EVM factory. + /// + /// The factory is used to create EVM instances for block execution. + pub const fn evm_factory(&self) -> &MorphEvmFactory { + self.executor_factory.evm_factory() } } impl BlockExecutorFactory for MorphEvmConfig { type EvmFactory = MorphEvmFactory; - type ExecutionCtx<'a> = MorphBlockExecutionCtx<'a>; + type ExecutionCtx<'a> = EthBlockExecutionCtx<'a>; type Transaction = MorphTxEnvelope; type Receipt = MorphReceipt; fn evm_factory(&self) -> &Self::EvmFactory { - self.inner.executor_factory.evm_factory() + self.executor_factory.evm_factory() } fn create_executor<'a, DB, I>( @@ -120,7 +172,7 @@ impl BlockExecutorFactory for MorphEvmConfig { DB: Database + 'a, I: Inspector>> + 'a, { - MorphBlockExecutor::new(evm, ctx, self.chain_spec()) + self.executor_factory.create_executor(evm, ctx) } } diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 62d2232..edfba2e 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -50,9 +50,6 @@ serde-bincode-compat = [ "alloy-consensus/serde-bincode-compat", "alloy-eips/serde-bincode-compat", "reth-primitives-traits/serde-bincode-compat", - "dep:reth-ethereum-primitives", - "reth-ethereum-primitives/serde", - "reth-ethereum-primitives/serde-bincode-compat", ] reth-codec = [ "serde", diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 7b0b95c..589a73e 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -30,7 +30,8 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg), allow(unexpected_cfgs))] -#[cfg(feature = "serde-bincode-compat")] +// reth-ethereum-primitives is only used when the reth-codec feature is enabled +#[cfg(feature = "reth-codec")] use reth_ethereum_primitives as _; pub mod header; @@ -56,14 +57,11 @@ pub use transaction::{ L1_TX_TYPE_ID, MORPH_TX_TYPE_ID, MorphTxEnvelope, MorphTxType, TxL1Msg, TxMorph, TxMorphExt, }; -/// A [`NodePrimitives`] implementation for Morph. -/// -/// This implementation is only available when the `serde-bincode-compat` feature is enabled. +/// A [`reth_primitives_traits::NodePrimitives`] implementation for Morph. #[derive(Debug, Clone, Default, Eq, PartialEq)] #[non_exhaustive] pub struct MorphPrimitives; -#[cfg(feature = "serde-bincode-compat")] impl reth_primitives_traits::NodePrimitives for MorphPrimitives { type Block = Block; type BlockHeader = MorphHeader;