Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions crates/evm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
135 changes: 102 additions & 33 deletions crates/evm/src/assemble.rs
Original file line number Diff line number Diff line change
@@ -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<MorphChainSpec>,
/// Chain specification
chain_spec: Arc<MorphChainSpec>,
}

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<MorphChainSpec>) -> Self {
Self {
inner: EthBlockAssembler::new(chain_spec),
}
Self { chain_spec }
}

/// Returns the chain spec.
pub const fn chain_spec(&self) -> &Arc<MorphChainSpec> {
&self.chain_spec
}
}

impl BlockAssembler<MorphEvmConfig> 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<Self::Block, BlockExecutionError> {
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<MorphReceiptBuilder, MorphChainSpec, MorphEvmFactory>,
>::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,
},
))
}
}
86 changes: 86 additions & 0 deletions crates/evm/src/block/factory.rs
Original file line number Diff line number Diff line change
@@ -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<MorphChainSpec>,
/// EVM factory
evm_factory: MorphEvmFactory,
}

impl MorphBlockExecutorFactory {
/// Creates a new [`MorphBlockExecutorFactory`].
pub(crate) fn new(spec: Arc<MorphChainSpec>, evm_factory: MorphEvmFactory) -> Self {
Self {
receipt_builder: DefaultMorphReceiptBuilder,
spec,
evm_factory,
}
}

/// Returns the chain spec.
pub(crate) const fn spec(&self) -> &Arc<MorphChainSpec> {
&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<DB>, I>,
_ctx: Self::ExecutionCtx<'a>,
) -> impl BlockExecutorFor<'a, Self, DB, I>
where
DB: Database + 'a,
I: Inspector<MorphContext<&'a mut State<DB>>> + 'a,
{
MorphBlockExecutor::new(evm, &self.spec, &self.receipt_builder)
}
}
Loading