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
10 changes: 5 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,14 @@ cargo test -p morph-consensus
Before submitting a pull request, ensure all checks pass:

```bash
# Format code (requires nightly toolchain)
cargo +nightly fmt --all
# Format code
cargo fmt --all

# Run clippy lints
cargo clippy --all --all-targets -- -D warnings

# Build documentation
cargo doc --no-deps --document-private-items
# Run doc tests
cargo test --doc --all --verbose
```

## Pull Request Process
Expand Down Expand Up @@ -82,7 +82,7 @@ See [README.md](README.md#architecture) for an overview of the crate structure.
## Code Style

- Follow standard Rust conventions and idioms
- Use `cargo fmt` formatting (nightly)
- Use `cargo fmt` formatting (stable toolchain)
- All public items should have documentation comments
- Avoid `unsafe` code unless absolutely necessary and well-documented

Expand Down
41 changes: 22 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ Morph Reth is the next-generation execution client for [Morph](https://www.morph
### Key Features

- **L1 Message Support**: Seamless bridging of assets and messages from Ethereum L1 to Morph L2
- **Morph Transaction**: Morph EVM+ transaction enabling alternative token fees, reference key indexing, and memo attachment
- **Morph Hardforks**: Full support for Morph's upgrade schedule (Bernoulli, Curie, Morph203, Viridian, Emerald, Jade)
- **Morph Transaction**: Versioned Morph EVM+ transaction with alternative fee-token support and Jade-era reference/memo fields
- **Morph Hardforks**: Implements Morph hardfork logic through Jade, with bundled Mainnet and Hoodi chainspecs currently scheduled through Emerald
- **Custom Engine API**: L2-specific Engine API for sequencer block building and validation
- **L1 Fee Validation**: Transaction pool with L1 data fee affordability checks

Expand Down Expand Up @@ -81,7 +81,7 @@ cargo build --release

### Running the Node

Morph Reth is a sequencer-driven L2 execution client. It does **not** sync blocks via P2P — blocks are delivered through the custom L2 Engine API by an external sequencer or derivation pipeline.
Morph Reth is a sequencer-driven L2 execution client. Block production and import are driven through the custom L2 Engine API by an external sequencer or derivation pipeline, and the execution layer must stay aligned with the Morph consensus node state.

```bash
# Generate a JWT secret for Engine API authentication
Expand All @@ -106,15 +106,14 @@ openssl rand -hex 32 > jwt.hex
--authrpc.jwtsecret jwt.hex
```

> **Note:** The node requires a sequencer or derivation pipeline to call the Engine API (`engine_assembleL2Block`, `engine_newL2Block`, etc.) for block production and import. See [Morph Documentation](https://docs.morph.network/) for full deployment guides.
> **Note:** The commands above only start the Morph execution client. In production, bootstrap with a paired `reth` + `node` snapshot at the same height, because Morph EL state must stay aligned with the consensus node's `node-data`. The node still requires a sequencer or derivation pipeline to drive the custom Engine API (`engine_assembleL2Block`, `engine_newL2Block`, etc.) for block production and import. See [Morph Documentation](https://docs.morph.network/) for deployment guides.

#### Morph-Specific CLI Flags

| Flag | Default | Description |
|------|---------|-------------|
| `--morph.max-tx-payload-bytes` | 122880 (120KB) | Maximum transaction payload bytes per block |
| `--morph.max-tx-per-block` | None (unlimited) | Maximum number of transactions per block |
| `--morph.geth-rpc-url` | None | Geth RPC URL for cross-validating MPT state root via `morph_diskRoot` |

### Running Tests

Expand All @@ -130,13 +129,13 @@ cargo test -p morph-consensus

```bash
# Format code
cargo +nightly fmt --all
cargo fmt --all

# Run clippy
cargo clippy --all --all-targets -- -D warnings

# Check documentation
cargo doc --no-deps --document-private-items
# Run doc tests
cargo test --doc --all --verbose
```

## Morph L2 Specifics
Expand Down Expand Up @@ -165,37 +164,41 @@ L1 messages are special deposit transactions that originate from Ethereum L1:

### Morph Transaction

Morph Transaction (`0x7f`) is Morph's EVM+ transaction type, extending standard EVM transactions for better user experience and enterprise integration:
Morph Transaction (`0x7f`) is a versioned custom transaction type that extends EIP-1559-style transactions with alternative fee payment and, from Jade onward, optional metadata fields:

| Feature | Description |
|---------|-------------|
| **Alternative Fee Tokens** | Pay gas in stablecoins (USDT, USDC) or other ERC-20 tokens — no ETH required |
| **Transaction Reference** | Tag transactions with a 32-byte key for order tracking and payment reconciliation |
| **Memo Field** | Attach notes or invoice numbers (up to 64 bytes) for auditing and record-keeping |
| Version | Availability | Description |
|---------|--------------|-------------|
| V0 | Always | Requires `fee_token_id > 0`, uses an active fee token from the L2 Token Registry, and does not support `reference` or `memo` |
| V1 | Jade+ | Adds optional `reference` (32 bytes) and `memo` (max 64 bytes); `fee_token_id == 0` uses the normal ETH-fee path, while `fee_token_id > 0` uses an active registry token |

### Hardforks

Bernoulli and Curie use block-based activation; Morph203, Viridian, Emerald, and Jade use timestamp-based activation.

The codebase implements hardfork logic through Jade, but the bundled Mainnet and Hoodi chainspecs currently schedule through Emerald.

| Hardfork | Activation | Description |
|----------|------------|-------------|
| Bernoulli | Block | Initial L2 launch with disabled ripemd160 and blake2f precompiles |
| Curie | Block | EIP-1559 fee market activation with blob-based L1 data fee |
| Curie | Block | Introduces blob-based L1 data-fee calculation and initializes the Curie L1 Gas Price Oracle fields |
| Morph203 | Timestamp | Re-enable ripemd160 and blake2f precompiles |
| Viridian | Timestamp | EIP-7702 EOA delegation support |
| Emerald | Timestamp | BLS12-381 and P256verify precompiles |
| Jade | Timestamp | MPT state root validation, MorphTx V1 with reference and memo fields |

Before Jade, Morph uses ZK-trie state roots, so morph-reth intentionally skips state-root equality checks and only enables MPT state-root validation at Jade.

### Engine API

Morph provides a custom L2 Engine API (different from the standard Ethereum Engine API) for sequencer interaction:

| Method | Description |
|--------|-------------|
| `engine_assembleL2Block` | Build a new block with given transactions |
| `engine_validateL2Block` | Validate a block without importing |
| `engine_newL2Block` | Import and finalize a block |
| `engine_newSafeL2Block` | Import a safe block from derivation |
| `engine_assembleL2Block` | Build executable L2 block data for the next height; the sequencer supplies L1-message transactions via the `transactions` parameter, and L2 transactions are pulled from the txpool |
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify AssembleL2BlockParams structure and engine API handler implementation

# Check AssembleL2BlockParams structure
echo "=== AssembleL2BlockParams structure ==="
ast-grep --pattern 'struct AssembleL2BlockParams {
  $$$
}'

# Check the transactions field specifically
echo -e "\n=== Searching for transactions field in AssembleL2BlockParams ==="
rg -nP -A10 'struct AssembleL2BlockParams' --type=rust crates/payload/types/src/params.rs

# Check engine API handler for assembleL2Block
echo -e "\n=== engine_assembleL2Block handler implementation ==="
rg -nP -B5 -A20 'engine_assembleL2Block|assembleL2Block' --type=rust crates/engine-api/src/rpc.rs

# Search for any txpool interaction in the assemble code path
echo -e "\n=== Checking for txpool usage in payload building ==="
rg -nP 'txpool|pool.*transaction|best_transaction' --type=rust crates/payload/builder/src/

Repository: morph-l2/morph-reth

Length of output: 3932


🏁 Script executed:

#!/bin/bash
# Find the implementation of MorphL2EngineRpc trait
echo "=== Searching for MorphL2EngineRpc implementation ==="
rg -nP 'impl.*MorphL2EngineRpc|fn assemble_l2_block' --type=rust -A15 crates/

# Search for handler implementations in engine-api
echo -e "\n=== Checking engine API handler files ==="
fd -type f -name "*.rs" crates/engine-api/ | head -20

# Look for any struct that implements the RPC trait
echo -e "\n=== Looking for concrete implementations ==="
rg -nP 'struct.*Handler|impl MorphL2EngineRpc' --type=rust crates/engine-api/src/

Repository: morph-l2/morph-reth

Length of output: 6412


🏁 Script executed:

#!/bin/bash
# Find build_l2_payload implementation
echo "=== Searching for build_l2_payload implementation ==="
rg -nP 'fn build_l2_payload' --type=rust -A30 crates/engine-api/src/builder.rs

# Check if transactions from params are used directly
echo -e "\n=== Checking how params.transactions are used ==="
rg -nP 'params\.transactions' --type=rust crates/engine-api/src/builder.rs

Repository: morph-l2/morph-reth

Length of output: 1518


🏁 Script executed:

#!/bin/bash
# Get more context around line 557
echo "=== Context around params.transactions usage (line 557) ==="
sed -n '545,570p' crates/engine-api/src/builder.rs

# Check if MorphPayloadBuilder is called and if it uses txpool
echo -e "\n=== Checking MorphPayloadBuilder usage in build_l2_payload ==="
sed -n '527,600p' crates/engine-api/src/builder.rs | head -80

Repository: morph-l2/morph-reth

Length of output: 3933


🏁 Script executed:

#!/bin/bash
# Check if send_new_payload or best_payload access txpool
echo "=== Checking payload builder methods ==="
rg -nP 'fn send_new_payload|fn best_payload' --type=rust -B2 -A10 crates/payload/builder/src/

# Verify the payload builder is using the supplied transactions
echo -e "\n=== Checking MorphPayloadBuilder build logic ==="
rg -nP 'pub async fn build.*attributes' --type=rust -B2 -A15 crates/payload/builder/src/builder.rs | head -50

Repository: morph-l2/morph-reth

Length of output: 153


Correct the engine_assembleL2Block description — the sequencer supplies all transactions, not just L1 messages.

The AssembleL2BlockParams struct has a single transactions: Vec<Bytes> field where the caller must supply all transactions (both L1 messages and L2 transactions). The handler uses these caller-supplied transactions directly with no txpool interaction. The current description misleads sequencer implementers about how this API works.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 197, Update the `engine_assembleL2Block` description to
state that the sequencer supplies all transactions (both L1 messages and L2
transactions) via the AssembleL2BlockParams.transactions: Vec<Bytes> field and
that the handler uses these caller-supplied transactions directly with no txpool
interaction; replace the current phrasing that implies only L1 messages are
provided and remove any mention of txpool usage so implementers understand the
sequencer must provide every transaction.

| `engine_validateL2Block` | Validate executable block data without importing it |
| `engine_newL2Block` | Import a new L2 block via `newPayload` + `forkchoiceUpdated` and advance the canonical head |
| `engine_newSafeL2Block` | Rebuild and import a safe L2 block from derivation inputs |
| `engine_setBlockTags` | Update safe/finalized block tags without importing a block |

## Contributing

Expand Down
1 change: 0 additions & 1 deletion local-test/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
: "${RETH_BOOTNODES:=}"
: "${MORPH_MAX_TX_PAYLOAD_BYTES:=122880}"
: "${MORPH_MAX_TX_PER_BLOCK:=}"
: "${MORPH_GETH_RPC_URL:=http://localhost:8546}"

check_binary() {
local bin_path="$1"
Expand Down
5 changes: 0 additions & 5 deletions local-test/reth-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,6 @@ if [[ -n "${MORPH_MAX_TX_PER_BLOCK}" ]]; then
args+=(--morph.max-tx-per-block "${MORPH_MAX_TX_PER_BLOCK}")
fi

# Add optional geth RPC URL for state root cross-validation
if [[ -n "${MORPH_GETH_RPC_URL}" ]]; then
args+=(--morph.geth-rpc-url "${MORPH_GETH_RPC_URL}")
fi

# Add bootnodes if configured
if [[ -n "${RETH_BOOTNODES}" ]]; then
args+=(--bootnodes "${RETH_BOOTNODES}")
Expand Down
Loading