Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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,
};
Comment thread
panos-xyz marked this conversation as resolved.

// 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(),
};
Comment thread
panos-xyz marked this conversation as resolved.

// 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