Skip to content
Open
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
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

## Features

- **Dockerized Deployment**: Simplifies the process of setting up Morph validator nodes using Docker containers.
- **Dockerized Deployment**: Simplifies the process of setting up Morph nodes using Docker containers.
- **Network Support**: Provides configurations for both Mainnet and Hoodi testnet environments.
- **Snapshot Synchronization**: Supports synchronizing node data from snapshots to expedite the setup process.

Expand Down Expand Up @@ -96,9 +96,25 @@ Before setting up a Morph node, ensure you have the following installed:

- This command will set up and run the node based on the configurations specified in your .env file.

### Node startup and batch verification mode

There is a single node startup (one `node` service). Batch verification behavior is
controlled by the `DERIVATION_VERIFY_MODE` operator variable in `.env` / `.env_hoodi`
(maps to the `--derivation.verify-mode` flag; behavior from morph PR #966):

- `local` (default): rebuild the blob from local L2 blocks and compare versioned hashes against L1 (no beacon fetch on the happy path).
- `layer1`: pull the L1 beacon blob, decode it, and derive via the engine — equivalent to the **former validator node** that derives from L1.

Leave it empty to fall back to the binary default (`local`).

> **Do not** set `L1_SEQUENCER_CONTRACT` or `CONSENSUS_SWITCH_HEIGHT` in the `.env`
> files. These are hardcoded per-network defaults in the binary, not operator
> configuration. Setting them (especially `CONSENSUS_SWITCH_HEIGHT=-1`) overrides the
> hardcoded defaults and can mistakenly disable the consensus upgrade.

## Snapshot Information

The table below provides the node snapshot data and corresponding download URLs. When starting the validator, ensure `DERIVATION_START_HEIGHT`, `L1_MSG_START_HEIGHT`, and `L2_BASE_HEIGHT` match the selected snapshot: use `.env`/`.env_hoodi` for MPT, and `.env_zk`/`.env_hoodi_zk` for ZK legacy.
The table below provides the node snapshot data and corresponding download URLs. When starting the node, ensure `DERIVATION_START_HEIGHT`, `L1_MSG_START_HEIGHT`, and `L2_BASE_HEIGHT` match the selected snapshot: use `.env`/`.env_hoodi` for MPT, and `.env_zk`/`.env_hoodi_zk` for ZK legacy.

**For mainnet**:

Expand Down
15 changes: 14 additions & 1 deletion morph-node/.env
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ JWT_SECRET_FILE=${MORPH_HOME}/jwt-secret.txt
GETH_ENTRYPOINT_FILE=./entrypoint-geth.sh
MAINNET_SNAPSHOT_NAME=snapshot-20260415-1

## Environment variables for validator node
## Environment variables for the node
L1_CHAIN_ID=1
L1_ETH_RPC=https://eth.drpc.org
L1_BEACON_CHAIN_RPC=https://rpc.ankr.com/eth
Expand All @@ -16,3 +16,16 @@ L2_BASE_HEIGHT=22181317

## Extra flags for node
NODE_EXTRA_FLAGS=--mainnet

## Batch verification mode (--derivation.verify-mode), from morph PR #966.
## local (default): rebuild the blob from local L2 blocks and compare
## versioned hashes against L1 (no beacon fetch on the happy path).
## layer1: pull the L1 beacon blob, decode it, and derive via the engine —
## equivalent to the former validator node. Set this for that behavior.
## Leave empty to use the binary default (local).
DERIVATION_VERIFY_MODE=

## NOTE: do NOT set L1_SEQUENCER_CONTRACT or CONSENSUS_SWITCH_HEIGHT here.
## These are hardcoded per-network defaults in the binary, not operator config.
## Setting them (especially CONSENSUS_SWITCH_HEIGHT=-1) overrides the hardcoded
## defaults and can mistakenly disable the consensus upgrade.
15 changes: 14 additions & 1 deletion morph-node/.env_hoodi
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ GETH_ENTRYPOINT_FILE=./entrypoint-geth.sh
## todo snapshot
HOODI_SNAPSHOT_NAME=mpt-snapshot-20260402-1

## Environment variables for validator node
## Environment variables for the node
L1_CHAIN_ID=560048
L1_ETH_RPC=${your_layer1_execution_client_rpc_url}
L1_BEACON_CHAIN_RPC=${your_layer1_beacon_client_rpc_url}
Expand All @@ -14,3 +14,16 @@ ROLLUP_CONTRACT=0x57e0e6dde89dc52c01fe785774271504b1e04664
DERIVATION_START_HEIGHT=2534958
L1_MSG_START_HEIGHT=2528506
L2_BASE_HEIGHT=4391571

## Batch verification mode (--derivation.verify-mode), from morph PR #966.
## local (default): rebuild the blob from local L2 blocks and compare
## versioned hashes against L1 (no beacon fetch on the happy path).
## layer1: pull the L1 beacon blob, decode it, and derive via the engine —
## equivalent to the former validator node. Set this for that behavior.
## Leave empty to use the binary default (local).
DERIVATION_VERIFY_MODE=

## NOTE: do NOT set L1_SEQUENCER_CONTRACT or CONSENSUS_SWITCH_HEIGHT here.
## These are hardcoded per-network defaults in the binary, not operator config.
## Setting them (especially CONSENSUS_SWITCH_HEIGHT=-1) overrides the hardcoded
## defaults and can mistakenly disable the consensus upgrade.
54 changes: 6 additions & 48 deletions morph-node/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -43,32 +43,6 @@ stop-node:
rm-node:
docker rm morph-node morph-geth

## validator management
run-validator: generate-jwt
@echo "You are about to run a Mainnet MPT validator."
@echo "To run a ZK (legacy) validator instead, use: make run-zk-validator"
@{ [ -t 0 ] || { echo "Error: interactive confirmation required (run in a TTY)."; exit 1; }; printf "Continue? [Y/n] "; read confirm; if [ "$$confirm" = "n" ] || [ "$$confirm" = "N" ]; then exit 1; fi; }
docker-compose --env-file .env up validator &

run-hoodi-validator: generate-jwt-hoodi
@echo "You are about to run a Hoodi MPT validator."
@echo "To run a ZK (legacy) validator instead, use: make run-hoodi-zk-validator"
@{ [ -t 0 ] || { echo "Error: interactive confirmation required (run in a TTY)."; exit 1; }; printf "Continue? [Y/n] "; read confirm; if [ "$$confirm" = "n" ] || [ "$$confirm" = "N" ]; then exit 1; fi; }
docker-compose --env-file .env_hoodi up validator &


run-zk-validator: generate-jwt
docker-compose --env-file .env --env-file .env_zk up validator &

run-hoodi-zk-validator: generate-jwt-hoodi
docker-compose --env-file .env_hoodi --env-file .env_hoodi_zk up validator &

stop-validator:
docker stop validator_node morph-geth

rm-validator:
docker rm validator_node morph-geth

## build from source (requires Go)
submodule-init:
@echo "Initializing git submodules..."
Expand Down Expand Up @@ -115,35 +89,19 @@ run-node-binary: generate-jwt
@echo "You are about to run a Mainnet MPT node (binary mode)."
@echo "To run a ZK (legacy) node instead, use: make run-zk-node-binary"
@{ [ -t 0 ] || { echo "Error: interactive confirmation required (run in a TTY)."; exit 1; }; printf "Continue? [Y/n] "; read confirm; if [ "$$confirm" = "n" ] || [ "$$confirm" = "N" ]; then exit 1; fi; }
sh ./run-binary.sh node .env
sh ./run-binary.sh .env

run-hoodi-node-binary: generate-jwt-hoodi
@echo "You are about to run a Hoodi MPT node (binary mode)."
@echo "To run a ZK (legacy) node instead, use: make run-hoodi-zk-node-binary"
@{ [ -t 0 ] || { echo "Error: interactive confirmation required (run in a TTY)."; exit 1; }; printf "Continue? [Y/n] "; read confirm; if [ "$$confirm" = "n" ] || [ "$$confirm" = "N" ]; then exit 1; fi; }
sh ./run-binary.sh node .env_hoodi

run-validator-binary: generate-jwt
@echo "You are about to run a Mainnet MPT validator (binary mode)."
@{ [ -t 0 ] || { echo "Error: interactive confirmation required (run in a TTY)."; exit 1; }; printf "Continue? [Y/n] "; read confirm; if [ "$$confirm" = "n" ] || [ "$$confirm" = "N" ]; then exit 1; fi; }
sh ./run-binary.sh validator .env

run-hoodi-validator-binary: generate-jwt-hoodi
@echo "You are about to run a Hoodi MPT validator (binary mode)."
@{ [ -t 0 ] || { echo "Error: interactive confirmation required (run in a TTY)."; exit 1; }; printf "Continue? [Y/n] "; read confirm; if [ "$$confirm" = "n" ] || [ "$$confirm" = "N" ]; then exit 1; fi; }
sh ./run-binary.sh validator .env_hoodi
sh ./run-binary.sh .env_hoodi

run-zk-node-binary: generate-jwt
sh ./run-binary.sh node .env .env_zk
sh ./run-binary.sh .env .env_zk

run-hoodi-zk-node-binary: generate-jwt-hoodi
sh ./run-binary.sh node .env_hoodi .env_hoodi_zk

run-zk-validator-binary: generate-jwt
sh ./run-binary.sh validator .env .env_zk

run-hoodi-zk-validator-binary: generate-jwt-hoodi
sh ./run-binary.sh validator .env_hoodi .env_hoodi_zk
sh ./run-binary.sh .env_hoodi .env_hoodi_zk

stop-binary:
@if [ -f .geth.pid ]; then kill $$(cat .geth.pid) 2>/dev/null; rm -f .geth.pid; echo "geth stopped."; fi
Expand Down Expand Up @@ -204,7 +162,7 @@ quickstart-hoodi-node: generate-jwt-hoodi
quickstart-hoodi-node-binary: generate-jwt-hoodi build
@echo "=== Quickstart: Hoodi Node (Binary) ==="
$(call setup-snapshot-data,$(HOODI_SNAPSHOT_NAME),https://snapshot.morphl2.io/hoodi,../hoodi)
sh ./run-binary.sh node .env_hoodi
sh ./run-binary.sh .env_hoodi

quickstart-mainnet-node: generate-jwt
@echo "=== Quickstart: Mainnet Node (Docker) ==="
Expand All @@ -214,7 +172,7 @@ quickstart-mainnet-node: generate-jwt
quickstart-mainnet-node-binary: generate-jwt build
@echo "=== Quickstart: Mainnet Node (Binary) ==="
$(call setup-snapshot-data,$(MAINNET_SNAPSHOT_NAME),https://snapshot.morphl2.io/mainnet,../mainnet)
sh ./run-binary.sh node .env
sh ./run-binary.sh .env


# Common function for download and decompress
Expand Down
37 changes: 7 additions & 30 deletions morph-node/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,41 +36,18 @@ services:
- MORPH_NODE_L2_ENGINE_RPC=http://morph-geth:8551
- MORPH_NODE_L2_ENGINE_AUTH=/jwt-secret.txt
- MORPH_NODE_L1_ETH_RPC=${L1_ETH_RPC}
- MORPH_NODE_L1_ETH_BEACON_RPC=${L1_BEACON_CHAIN_RPC}
- MORPH_NODE_L1_CHAIN_ID=${L1_CHAIN_ID}
- MORPH_NODE_ROLLUP_ADDRESS=${ROLLUP_CONTRACT}
- MORPH_NODE_SYNC_START_HEIGHT=${L1_MSG_START_HEIGHT}
- NODE_EXTRA_FLAGS=${NODE_EXTRA_FLAGS:-}
volumes:
- "${MORPH_HOME}/node-data:/db"
- "${JWT_SECRET_FILE}:/jwt-secret.txt"
- "./entrypoint-node.sh:/entrypoint-node.sh"
entrypoint:
- "/bin/sh"
- "/entrypoint-node.sh"

validator:
container_name: validator_node
restart: unless-stopped
depends_on:
geth:
condition: service_started
image: ghcr.io/morph-l2/node:0.5.7
ports:
- "26660"
environment:
- MORPH_NODE_L2_ETH_RPC=http://morph-geth:8545
- MORPH_NODE_L2_ENGINE_RPC=http://morph-geth:8551
- MORPH_NODE_L2_ENGINE_AUTH=/jwt-secret.txt
# TODO: replace these with public network RPC URLs in .env_hoodi before running Hoodi validator
- MORPH_NODE_L1_ETH_RPC=${L1_ETH_RPC}
- MORPH_NODE_L1_ETH_BEACON_RPC=${L1_BEACON_CHAIN_RPC}
- MORPH_NODE_SYNC_DEPOSIT_CONTRACT_ADDRESS=${L1MESSAGEQUEUE_CONTRACT}
- MORPH_NODE_ROLLUP_ADDRESS=${ROLLUP_CONTRACT}
- MORPH_NODE_SYNC_START_HEIGHT=${L1_MSG_START_HEIGHT}
- MORPH_NODE_DERIVATION_START_HEIGHT=${DERIVATION_START_HEIGHT}
- MORPH_NODE_DERIVATION_BASE_HEIGHT=${L2_BASE_HEIGHT}
- MORPH_NODE_SYNC_START_HEIGHT=${L1_MSG_START_HEIGHT}
- MORPH_NODE_L1_CHAIN_ID=${L1_CHAIN_ID}
- NODE_EXTRA_FLAGS=--validator ${NODE_EXTRA_FLAGS:-}
# Batch verification mode: "local" (default, rebuild blob from L2 + compare
# vs L1) or "layer1" (pull L1 beacon blob + derive, former validator behavior).
# Empty => binary default (local).
- MORPH_NODE_DERIVATION_VERIFY_MODE=${DERIVATION_VERIFY_MODE:-}
- NODE_EXTRA_FLAGS=${NODE_EXTRA_FLAGS:-}
volumes:
- "${MORPH_HOME}/node-data:/db"
- "${JWT_SECRET_FILE}:/jwt-secret.txt"
Expand Down
13 changes: 12 additions & 1 deletion morph-node/entrypoint-node.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export MORPH_NODE_L2_ENGINE_AUTH=${MORPH_NODE_L2_ENGINE_AUTH:-${JWT_PATH}}
export MORPH_NODE_L1_ETH_RPC=${MORPH_NODE_L1_ETH_RPC:-${L1_ETH_RPC:-}}
export MORPH_NODE_ROLLUP_ADDRESS=${MORPH_NODE_ROLLUP_ADDRESS:-${ROLLUP_CONTRACT:-}}

# Validator-specific mappings
# Map remaining .env variables to MORPH_NODE_* for binary mode (Docker sets these directly)
export MORPH_NODE_L1_ETH_BEACON_RPC=${MORPH_NODE_L1_ETH_BEACON_RPC:-${L1_BEACON_CHAIN_RPC:-}}
export MORPH_NODE_SYNC_DEPOSIT_CONTRACT_ADDRESS=${MORPH_NODE_SYNC_DEPOSIT_CONTRACT_ADDRESS:-${L1MESSAGEQUEUE_CONTRACT:-}}
DERIVATION_START_HEIGHT_VALUE=${MORPH_NODE_DERIVATION_START_HEIGHT:-${DERIVATION_START_HEIGHT:-}}
Expand All @@ -29,6 +29,17 @@ fi
export MORPH_NODE_SYNC_START_HEIGHT=${MORPH_NODE_SYNC_START_HEIGHT:-${L1_MSG_START_HEIGHT:-}}
export MORPH_NODE_L1_CHAIN_ID=${MORPH_NODE_L1_CHAIN_ID:-${L1_CHAIN_ID:-}}

# Batch verification mode (--derivation.verify-mode). "local" (default) rebuilds
# the blob from local L2 and compares versioned hashes vs L1; "layer1" pulls the
# L1 beacon blob and derives via the engine (former validator behavior).
# Export only when set; empty/unset falls back to the binary default (local).
DERIVATION_VERIFY_MODE_VALUE=${MORPH_NODE_DERIVATION_VERIFY_MODE:-${DERIVATION_VERIFY_MODE:-}}
if [ -n "${DERIVATION_VERIFY_MODE_VALUE}" ]; then
export MORPH_NODE_DERIVATION_VERIFY_MODE="${DERIVATION_VERIFY_MODE_VALUE}"
else
unset MORPH_NODE_DERIVATION_VERIFY_MODE
fi

COMMAND="${NODE_BIN} \
--home ${NODE_HOME} \
--log.filename ${NODE_HOME}/node.log \
Expand Down
15 changes: 4 additions & 11 deletions morph-node/run-binary.sh
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
#!/bin/sh

# Usage: run-binary.sh <mode> <env-file> [override-env-file]
# mode: node | validator
# Usage: run-binary.sh <env-file> [override-env-file]
# env-file: .env | .env_hoodi
# override-env-file: optional, e.g. .env_zk for ZK legacy mode

MODE=${1:-node}
ENV_FILE=${2:-.env}
OVERRIDE_ENV_FILE=${3:-}
ENV_FILE=${1:-.env}
OVERRIDE_ENV_FILE=${2:-}

# Source environment
set -a
Expand Down Expand Up @@ -88,13 +86,8 @@ if ! nc -z localhost 8551 2>/dev/null; then
exit 1
fi

# Set validator mode
if [ "${MODE}" = "validator" ]; then
NODE_EXTRA_FLAGS="--validator ${NODE_EXTRA_FLAGS:-}"
fi

# Start morphnode
echo "Starting morphnode (${MODE} mode)..."
echo "Starting morphnode..."
NODE_BINARY=${NODE_BINARY} \
NODE_HOME=${MORPH_HOME}/node-data \
JWT_SECRET_PATH=${JWT_SECRET_FILE} \
Expand Down