Skip to content

Conversation

@panos-xyz
Copy link
Contributor

@panos-xyz panos-xyz commented Jan 21, 2026

Summary by CodeRabbit

  • New Features

    • Added MPTFork (timestamp activation) and Curie hardfork with L1 Gas Price Oracle and Curie-era storage initialization.
    • Full Morph payload builder for L2 blocks, plus builder configuration, breaker controls, and error types.
    • New Morph header type with L2 fields and updated payload/execution types; L1 transaction 2718 encoding support.
  • Refactor

    • Hardfork activation now considers block number + timestamp and reordered mappings.
    • Simplified L1 message/transaction model and expanded public exports for builder, header, and oracle items.

✏️ Tip: You can customize this high-level summary in your review settings.

- Preserve MorphHeader, MPTFork hardfork
- Use block+timestamp based hardforks (Bernoulli/Curie block-based, Morph203/Viridian/Emerald/MPTFork timestamp-based)
- Move ConfigureEvm to config.rs with reth-codec feature gate for zkvm compatibility
- Fix morph_hardfork_at to take both block_number and timestamp
- Use MorphExecutionData for custom payload handling
- Add morph field to test genesis configurations
@coderabbitai
Copy link

coderabbitai bot commented Jan 21, 2026

📝 Walkthrough

Walkthrough

This PR adds MPTFork hardfork support, introduces a Morph L2 payload builder and its config/breaker/error types, refactors L1 messages (Signed→Sealed) and TxL1Msg fields with EIP-2718 support, introduces MorphHeader, applies Curie L1 gas-oracle initialization, updates many public APIs to use MorphHeader, and adjusts workspace Cargo manifests.

Changes

Cohort / File(s) Summary
Workspace & Manifests
Cargo.toml, crates/*/Cargo.toml
Added git-based reth workspace dependencies, new workspace entries and flags, adjusted features across multiple crate manifests.
Chainspec & Hardforks
crates/chainspec/src/{hardfork.rs,genesis.rs,spec.rs,lib.rs}
Added MorphHardfork::MPTFork and mpt_fork_time field; updated activation logic to consider (block_number, timestamp); switched ChainSpec to use MorphHeader; added new modules and re-exports.
Primitives: Header & Transactions
crates/primitives/src/{header.rs,lib.rs,transaction/*}
Introduced concrete MorphHeader (next_l1_msg_index, batch_hash) and reworked exports; refactored TxL1Msg (sender, gas_limit u64, to: Address), added EIP‑2718 encode/decode, and changed MorphTxEnvelope::L1Msg from SignedSealed.
Consensus
crates/consensus/src/{validation.rs,lib.rs}
Replaced alloy_consensus::Header usages with MorphHeader in HeaderValidator impls and updated validation method signatures.
EVM core & config
crates/evm/src/{evm.rs,assemble.rs,config.rs,context.rs,lib.rs}
Switched to PrecompilesMap, added precompiles_map field, converted between MorphHeader and inner Header in assemble, updated calls to morph_hardfork_at to accept (block_number, timestamp), exported assembler/context types, removed engine feature gate and engine glue.
EVM block logic & Curie
crates/evm/src/block/{curie.rs,mod.rs,receipt.rs}
Added apply_curie_hard_fork to initialize Curie-era L1 gas-oracle storage, integrated oracle loading and Curie application in pre-execution, implemented MorphReceiptBuilder, removed prior MorphReceiptBuilder export in other module.
Payload types & builder
crates/payload/{builder,types}/src/*, crates/payload/*/Cargo.toml
Added full MorphPayloadBuilder, MorphBuilderConfig, PayloadBuildingBreaker, MorphPayloadBuilderError; refactored builder attributes to embed EthPayloadBuilderAttributes, added MorphPayloadTypes and MorphExecutionData, large new control flow for sequencer+pool execution and DA/gas/timeout breaker.
Engine integration removal
crates/evm/src/engine.rs, crates/evm/src/lib.rs
Removed ConfigureEngineEvm glue (RecoveredInBlock, tx iterator, ToTxEnv impls); made MorphBlockAssembler and execution context types public.
revm L1 oracle
crates/revm/src/{l1block.rs,lib.rs}
Added L1 Gas Price Oracle address, storage-slot constants, Curie initial values and mapping; updated L1BlockInfo fetch logic and expanded public re-exports.
Misc docs/tests/formatting
assorted crates
Documentation edits and test updates to reflect new types/fields, minor feature/format tweaks across crates.

Sequence Diagram(s)

sequenceDiagram
    participant Builder as MorphPayloadBuilder
    participant Pool as TransactionPool
    participant EVM as MorphEvm
    participant State as State<DB>
    participant Breaker as PayloadBuildingBreaker

    Builder->>Breaker: create breaker(block_gas_limit)
    Builder->>State: prepare_state() / pre-execution
    State-->>Builder: state ready

    Builder->>Builder: execute_sequencer_transactions()
    Builder->>EVM: execute forced sequencer txs
    EVM->>State: apply changes
    State-->>EVM: result
    EVM-->>Builder: gas/fees

    loop pool execution until Breaker.should_break()
        Builder->>Pool: best_transactions()
        Pool-->>Builder: next_tx
        Builder->>EVM: execute pool tx
        EVM->>State: update
        State-->>EVM: result
        EVM-->>Builder: gas_used, bytes
        Builder->>Breaker: should_break(cumulative_gas, da_bytes)
    end

    Builder->>State: read_withdraw_trie_root()
    Builder->>EVM: finish_block(mpt_root)
    EVM-->>Builder: MorphBuiltPayload
Loading
sequenceDiagram
    participant Executor as MorphBlockExecutor
    participant State as State<DB>
    participant Oracle as L1GasPriceOracle
    participant Curie as apply_curie_hard_fork

    Executor->>State: pre_execute(block)
    State->>Oracle: ensure_cached(L1_GAS_PRICE_ORACLE_ADDRESS)
    Oracle-->>State: contract loaded

    alt at Curie transition
        State->>Curie: apply_curie_hard_fork()
        Curie->>State: read oracle storage
        Curie->>State: queue storage transitions (blob_base_fee, commit_scalar, blob_scalar, is_curie)
        State-->>Curie: transitions applied
    end

    Executor->>State: execute_transactions()
    State-->>Executor: execution result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • chengwenxi
  • anylots

Poem

🐰 Hopping through headers, I seal and I bind,

forks and timestamps now orderly aligned.
Builders hum softly—sequencer then pool,
Curie's oracle wakes, set by a storage rule.
A carrot for code, a small rabbit cheer—hop near!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Implement MorphPayloadBuilder for L2 block construction' accurately describes the primary change—the introduction of a comprehensive MorphPayloadBuilder implementation for constructing L2 blocks.
Docstring Coverage ✅ Passed Docstring coverage is 96.02% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@panos-xyz panos-xyz requested review from anylots and chengwenxi and removed request for anylots January 21, 2026 07:11
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/chainspec/src/spec.rs (1)

139-153: Comment on timestamp-based forks should include MPTFork.

The inline note still lists only Morph203/Viridian/Emerald, but Line 153 adds MPTFork.

✏️ Suggested comment update
-        // while Morph203, Viridian, and Emerald use timestamp-based activation.
+        // while Morph203, Viridian, Emerald, and MPTFork use timestamp-based activation.
🤖 Fix all issues with AI agents
In `@crates/payload/types/src/lib.rs`:
- Around line 49-60: The serde dependency in this crate needs the "rc" feature
enabled because MorphExecutionData derives Serialize/Deserialize for
Arc<SealedBlock<Block>>; update the serde dependency entry in the crate's
Cargo.toml to include "rc" in its features (so the features include "derive" and
"rc") to allow serialization of Arc types used by MorphExecutionData.

In `@crates/primitives/src/lib.rs`:
- Line 1: The crate documentation incorrectly states that MorphHeader is a
"Header type alias (same as Ethereum)"; update the top-level docs in lib.rs to
reflect that MorphHeader is now a dedicated type defined in the header module
(reference the header module and the MorphHeader type name), remove or reword
any "type alias" / "same as Ethereum" phrasing, and adjust the related doc
paragraphs that mention the alias (the sections referencing MorphHeader and the
header module) to briefly describe the dedicated header type and point readers
to the header module for details.

In `@crates/primitives/src/receipt/mod.rs`:
- Around line 425-429: The doctest imports an unused type; remove the
unnecessary `use alloy_consensus::Receipt;` line from the doctest block that
references `MorphReceipt` and `calculate_receipt_root_no_memo` so the example
only imports/uses symbols that are actually needed and eliminates doctest
warnings.

In `@crates/revm/src/l1block.rs`:
- Around line 92-118: The INITIAL_COMMIT_SCALAR constant is wrong and must be
increased by 1000×; update the value used to construct INITIAL_COMMIT_SCALAR
(the U256::from_limbs call that currently uses 230759955285) to the corrected
literal 230_000_000_000_000 (i.e., 230_000e9) so
CURIE_L1_GAS_PRICE_ORACLE_STORAGE contains the correct commitScalar; modify the
const INITIAL_COMMIT_SCALAR declaration to use the new limbs value and ensure no
other dependent constants are changed.
🧹 Nitpick comments (6)
crates/primitives/src/transaction/envelope.rs (1)

240-244: Consolidate the empty‑signature definition.
There are now two sources of “empty signature.” Reuse TxL1Msg::signature() to keep a single source of truth.

♻️ Suggested tweak
-    const L1_MSG_SIGNATURE: Signature = Signature::new(
-        alloy_primitives::U256::ZERO,
-        alloy_primitives::U256::ZERO,
-        false,
-    );
+    const L1_MSG_SIGNATURE: Signature = TxL1Msg::signature();

Also applies to: 262-273, 282-282

crates/chainspec/src/hardfork.rs (1)

178-199: The From<SpecId> implementation has unreachable branches.

Since all MorphHardfork variants map to SpecId::OSAKA (lines 167-174), the conversion from SpecId back to MorphHardfork will always return MPTFork for any spec >= OSAKA. The subsequent else if branches (Emerald, Viridian, Morph203, Curie) are effectively dead code.

The comment at lines 179-183 acknowledges this isn't a strict inverse, but this implementation makes it impossible to recover the original hardfork from a SpecId. If this is intentional (e.g., defaulting to the latest hardfork), consider simplifying:

♻️ Suggested simplification if defaulting to latest is intentional
 impl From<SpecId> for MorphHardfork {
-    /// Maps a [`SpecId`] to the *latest compatible* [`MorphHardfork`].
-    ///
-    /// Note: this is intentionally not a strict inverse of
-    /// `From<MorphHardfork> for SpecId`, because multiple Morph
-    /// hardforks may share the same underlying EVM spec.
+    /// Maps a [`SpecId`] to the *latest* [`MorphHardfork`].
+    ///
+    /// Note: All Morph hardforks currently share the same underlying EVM spec (OSAKA),
+    /// so this always returns MPTFork for OSAKA-compatible specs.
     fn from(spec: SpecId) -> Self {
-        if spec.is_enabled_in(SpecId::from(Self::MPTFork)) {
-            Self::MPTFork
-        } else if spec.is_enabled_in(SpecId::from(Self::Emerald)) {
-            Self::Emerald
-        } else if spec.is_enabled_in(SpecId::from(Self::Viridian)) {
-            Self::Viridian
-        } else if spec.is_enabled_in(SpecId::from(Self::Morph203)) {
-            Self::Morph203
-        } else if spec.is_enabled_in(SpecId::from(Self::Curie)) {
-            Self::Curie
-        } else {
-            Self::Bernoulli
+        if spec.is_enabled_in(SpecId::OSAKA) {
+            Self::MPTFork
+        } else {
+            Self::Bernoulli
         }
     }
 }

Alternatively, if different hardforks will eventually map to different SpecId values, keep the current structure but add a comment explaining the future plan.

crates/revm/src/lib.rs (1)

58-77: Re-exports are comprehensive but could use better organization.

The re-exports include all necessary constants. Minor nit: the comment // Storage slots on line 66 is oddly placed after several storage slot constants have already been listed. Consider reordering so all storage slots are grouped together after the comment.

♻️ Suggested reordering
 pub use l1block::{
     CURIE_L1_GAS_PRICE_ORACLE_STORAGE,
+    // Storage slots
+    GPO_OWNER_SLOT,
+    GPO_L1_BASE_FEE_SLOT,
+    GPO_OVERHEAD_SLOT,
+    GPO_SCALAR_SLOT,
+    GPO_WHITELIST_SLOT,
+    GPO_L1_BLOB_BASE_FEE_SLOT,
+    GPO_COMMIT_SCALAR_SLOT,
     GPO_BLOB_SCALAR_SLOT,
-    GPO_COMMIT_SCALAR_SLOT,
     GPO_IS_CURIE_SLOT,
-    GPO_L1_BASE_FEE_SLOT,
-    GPO_L1_BLOB_BASE_FEE_SLOT,
-    GPO_OVERHEAD_SLOT,
-    // Storage slots
-    GPO_OWNER_SLOT,
-    GPO_SCALAR_SLOT,
-    GPO_WHITELIST_SLOT,
+    // Curie initial values
+    INITIAL_L1_BLOB_BASE_FEE,
+    INITIAL_COMMIT_SCALAR,
     INITIAL_BLOB_SCALAR,
-    INITIAL_COMMIT_SCALAR,
-    // Curie initial values
-    INITIAL_L1_BLOB_BASE_FEE,
     IS_CURIE,
+    // Address and types
     L1_GAS_PRICE_ORACLE_ADDRESS,
     L1BlockInfo,
 };
crates/payload/builder/src/config.rs (1)

197-210: Test with thread sleep may be flaky in CI.

The test test_breaker_should_break_on_time_limit uses thread::sleep(Duration::from_millis(150)) to exceed a 100ms time limit. On heavily loaded CI systems, this could occasionally fail if sleep undershoots. Consider increasing the margin or using a more deterministic approach if flakiness is observed.

crates/primitives/src/header.rs (1)

215-221: Consider validating that all bytes were consumed during decompression.

The from_compact returns the number of bytes consumed, but it's discarded. If the input contains trailing garbage, this could silently succeed with incomplete data.

🔧 Optional: Add validation
 impl reth_db_api::table::Decompress for MorphHeader {
     fn decompress(value: &[u8]) -> Result<Self, reth_db_api::DatabaseError> {
         let (obj, _) = reth_codecs::Compact::from_compact(value, value.len());
+        // Note: Could validate that consumed == value.len() if strict parsing is needed
         Ok(obj)
     }
 }
crates/payload/builder/src/builder.rs (1)

678-678: Minor: Comment step number should be 7, not 6.

The previous steps are numbered 1-6, so "Build ExecutableL2Data" should be step 7.

-    // 6. Build ExecutableL2Data from the sealed block
+    // 7. Build ExecutableL2Data from the sealed block

@panos-xyz panos-xyz changed the title Feat/payload builder feat: Implement MorphPayloadBuilder for L2 block construction Jan 21, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@crates/payload/builder/src/builder.rs`:
- Around line 288-381: execute_sequencer_transactions currently never updates DA
accounting so sequencer (forced) txs are excluded from
ExecutionInfo.cumulative_da_bytes_used; after you obtain tx_bytes (via
tx_with_encoded.encoded_bytes()) increment info.cumulative_da_bytes_used by
tx_bytes.len() (cast to the same integer type as cumulative_da_bytes_used), and
then enforce the DA limit if present by comparing against the DA limit exposed
on ExecutionInfo or the builder/block (e.g., info.max_da_block_size or
builder/evm block DA limit) and return an appropriate PayloadBuilderError when
exceeded; update execute_sequencer_transactions to do this before executing the
transaction so DA usage prevents adding sequencer txs that would overflow the DA
cap.
♻️ Duplicate comments (1)
crates/primitives/src/lib.rs (1)

21-24: Doc still says MorphHeader is a type alias; update to reflect the dedicated header type.

This is the same mismatch noted previously; the bullet still reads “type alias” even though MorphHeader now lives in the header module.

✏️ Suggested doc tweak
-//! - [`MorphHeader`]: Morph header type alias
+//! - [`MorphHeader`]: Morph-specific header type with L2 fields (see `header` module)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@crates/evm/src/block/curie.rs`:
- Around line 1-67: The file fails rustfmt checks; run rustfmt/cargo fmt to fix
formatting inconsistencies in the Curie hard fork module—format the file
containing apply_curie_hard_fork and related symbols
(CURIE_L1_GAS_PRICE_ORACLE_STORAGE, L1_GAS_PRICE_ORACLE_ADDRESS, StorageSlot) so
the code adheres to cargo fmt rules, then commit the formatted changes;
alternatively run `cargo fmt --all` locally or in CI to automatically apply the
correct formatting.

In `@crates/evm/src/block/mod.rs`:
- Around line 1-11: The file's formatting doesn't meet rustfmt checks; run cargo
fmt --all (or rustfmt this file) to fix whitespace/indentation and comment
formatting in this module (ensure the module header docs and items like the
curie mod declaration and the pub(crate) use receipt::MorphReceiptBuilder line
are properly formatted) so CI `cargo fmt --all -- --check` passes.

In `@crates/evm/src/block/receipt.rs`:
- Around line 1-52: The file fails rustfmt; run cargo fmt --all and commit the
formatting changes so the code matches rustfmt rules. Specifically reformat the
impl for MorphReceiptBuilder and its build_receipt function (see symbols
MorphReceiptBuilder, build_receipt, and ReceiptBuilderCtx) so whitespace,
imports, and indentation conform to rustfmt, then re-run the CI check.

In `@crates/revm/src/lib.rs`:
- Around line 60-79: Remove inline comments from inside the pub use l1block {
... } block (they break rustfmt ordering) and let cargo fmt reorder the items;
if you need grouping semantics, move the comments to a module-level doc comment
above the pub use or add separate commented sections outside the use block.
Ensure the exported symbols (e.g., CURIE_L1_GAS_PRICE_ORACLE_STORAGE,
GPO_OWNER_SLOT, INITIAL_L1_BLOB_BASE_FEE, L1BlockInfo) remain listed but without
inline // Storage slots or // Curie initial values comments inside the braces,
then run cargo fmt to verify.
🧹 Nitpick comments (2)
crates/evm/src/block/receipt.rs (1)

41-50: Receipt construction logic looks correct.

The mapping from MorphTxType to MorphReceipt variants is well-structured. The distinction between L1Msg (using bare Receipt) and other types (using MorphTransactionReceipt::new(inner)) correctly reflects their different receipt structures.

The TODO for L1 fee calculation is noted—this will be important for accurate fee tracking in receipts.

Would you like me to open an issue to track the L1 fee calculation implementation?

crates/evm/src/block/curie.rs (1)

62-64: Add logging or assertion to detect if transitions are silently discarded during Curie fork application.

The transitions are silently discarded if state.transition_state is None, which could mask configuration issues where the fork appears to apply successfully but doesn't persist state changes. While the test explicitly uses .with_bundle_update() to enable transition tracking, production State initialization should be verified to ensure transitions are always captured.

Consider adding a debug log or documenting this requirement:

Suggested debug logging
     // Add transition to state
     if let Some(s) = state.transition_state.as_mut() {
         s.add_transitions(vec![(L1_GAS_PRICE_ORACLE_ADDRESS, transition)]);
+    } else {
+        tracing::debug!(target: "morph::evm", "Curie fork applied but transition_state is None; changes may not persist");
     }

Alternatively, document that apply_curie_hard_fork requires a State initialized with with_bundle_update() to function correctly.

@panos-xyz panos-xyz merged commit 6f7e874 into main Jan 23, 2026
9 checks passed
@panos-xyz panos-xyz deleted the feat/payload-builder branch January 23, 2026 08:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants