From b5a6aa8f50fa0cb64d48055c740aa2ff194e0f96 Mon Sep 17 00:00:00 2001 From: corey Date: Wed, 8 Jul 2026 17:21:18 +0800 Subject: [PATCH 1/2] feat: adapt run-morph-node to centralized single-sequencer (integrate #92 + #100) Version matrix: - go-ethereum -> morph-v2.2.4 (0eda191) - morph -> main f96a4775 (single-sequencer network config); its node/go.mod pins github.com/morph-l2/tendermint v0.3.8 (a5de063), so tendermint enters as a build-time dep, not a submodule/service here - docker images: geth 2.2.3 -> 2.2.4, node 0.5.7 -> 0.5.8 (provisional; must point to a morph-main build with --derivation.verify-mode before merge) Single self-verifying node (PR #100): - remove the separate validator docker-compose service; merge its env into the single node and expose DERIVATION_VERIFY_MODE (--derivation.verify-mode: local default / layer1 = former validator behavior) - run-binary.sh drops its node|validator mode arg - keep run-validator* Makefile commands as wrappers that run the SAME node with verify-mode=layer1 (no separate container) Legacy cleanup: - remove ZK legacy: .env_zk, .env_hoodi_zk, entrypoint-geth-zk.sh, all run-zk-* / *-zk-snapshot targets, and the ZK README snapshot tables - .env_hoodi: restore NODE_EXTRA_FLAGS=--hoodi (else node -> network=dev and fails "L1 Sequencer contract address is required") - align env snapshots/heights to README newest MPT (mainnet 20260701-1, hoodi 20260630-1) WIP: node:0.5.8 tag provisional; make build not yet run in this environment. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 54 +++++++++---------- go-ethereum | 2 +- morph | 2 +- morph-node/.env | 24 +++++++-- morph-node/.env_hoodi | 30 ++++++++--- morph-node/.env_hoodi_zk | 6 --- morph-node/.env_zk | 6 --- morph-node/Makefile | 89 +++++++++----------------------- morph-node/docker-compose.yml | 49 ++++++------------ morph-node/entrypoint-geth-zk.sh | 37 ------------- morph-node/entrypoint-node.sh | 13 ++++- morph-node/run-binary.sh | 23 ++++----- 12 files changed, 131 insertions(+), 204 deletions(-) delete mode 100644 morph-node/.env_hoodi_zk delete mode 100644 morph-node/.env_zk delete mode 100644 morph-node/entrypoint-geth-zk.sh diff --git a/README.md b/README.md index 41c74e3..ece5d05 100644 --- a/README.md +++ b/README.md @@ -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. @@ -47,16 +47,6 @@ Before setting up a Morph node, ensure you have the following installed: make download-and-decompress-hoodi-snapshot ``` -- For ZK node (legacy), download the ZK-specific snapshot: - - ```bash - make download-and-decompress-mainnet-zk-snapshot - ``` - or - ```bash - make download-and-decompress-hoodi-zk-snapshot - ``` - - After downloading the snapshot, you need to manually place the decompressed data files in the appropriate node data directories. Alternatively, use the `quickstart-*` targets (e.g. `make quickstart-mainnet-node`) which handle snapshot download and placement automatically. - `make download-and-decompress-*` now extracts snapshots under network directories (`../mainnet` or `../hoodi`). - For example, if the snapshot folder is named `snapshot-20260415-1`, move the directory to the `MORPH_HOME` directories: @@ -92,13 +82,32 @@ Before setting up a Morph node, ensure you have the following installed: make run-hoodi-node ``` -- Both commands default to MPT mode. For ZK legacy nodes, use `make run-zk-node` or `make run-hoodi-zk-node`. - - This command will set up and run the node based on the configurations specified in your .env file. +### Running as a validator (batch verification mode) + +There is no separate validator service anymore — every node self-verifies L1 batches. The +mode is controlled by `DERIVATION_VERIFY_MODE` (`--derivation.verify-mode`) in the env file: + +- `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. Requires a working `L1_BEACON_CHAIN_RPC`. + +Convenience targets run the same node in `layer1` mode: + +```bash +make run-validator # mainnet, Docker +make run-hoodi-validator # hoodi, Docker +make run-validator-binary # mainnet, binary +``` + +> `L1_SEQUENCER_CONTRACT` and `CONSENSUS_SWITCH_HEIGHT` are hardcoded per-network defaults +> in the binary and must not be set as operator config. + ## 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. Ensure `DERIVATION_START_HEIGHT`, `L1_MSG_START_HEIGHT`, and `L2_BASE_HEIGHT` in `.env`/`.env_hoodi` match the selected snapshot. **For mainnet** (reth is currently in an internal testing phase and is not yet recommended for production use): @@ -127,23 +136,6 @@ The table below provides the node snapshot data and corresponding download URLs. | [snapshot-archive-reth-20260601-1](https://snapshot.morphl2.io/hoodi/snapshot-archive-reth-20260601-1.tar.gz) | 2930601 | 2916614 | 5828361 | | [snapshot-archive-20260531-1](https://snapshot.morphl2.io/hoodi/snapshot-archive-20260531-1.tar.gz) | 2927766 | 2916614 | 5817801 | -**For mainnet ZK node(legacy)**: - -| Snapshot Name | Derivation Start Height | L1 Msg Start Height | L2 Base Height | -|:--------------|:------------------------|:--------------------|:---------------| -| [snapshot-20260316-1](https://snapshot.morphl2.io/mainnet/snapshot-20260316-1.tar.gz) | 24668486 | 24667943 | 21474974 | -| [snapshot-20260304-1](https://snapshot.morphl2.io/mainnet/snapshot-20260304-1.tar.gz) | 24582164 | 24582123 | 21195806 | -| [snapshot-20260210-1](https://snapshot.morphl2.io/mainnet/snapshot-20260210-1.tar.gz) | 24424695 | 24424698 | 20674922 | - -**For hoodi testnet ZK node(legacy)**: - -| Snapshot Name | Derivation Start Height | L1 Msg Start Height | L2 Base Height | -|:--------------|:------------------------|:--------------------|:---------------| -| [snapshot-20260316-1](https://snapshot.morphl2.io/hoodi/snapshot-20260316-1.tar.gz) | 2427831 | 2408746 | 4001145 | -| [snapshot-20260304-1](https://snapshot.morphl2.io/hoodi/snapshot-20260304-1.tar.gz) | 2349111 | 2346416 | 3713448 | -| [snapshot-20260210-1](https://snapshot.morphl2.io/hoodi/snapshot-20260210-1.tar.gz) | 2205636 | 2201288 | 3187147 | - - ## Documentation For detailed information on Morph and its ecosystem, refer to the official documentation: diff --git a/go-ethereum b/go-ethereum index 1d46057..0eda191 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit 1d4605778e32dd6dc205e74a97b925149bbe6c88 +Subproject commit 0eda19148ddd2c6d8cf252a242def9f940445a24 diff --git a/morph b/morph index a20bbfa..f96a477 160000 --- a/morph +++ b/morph @@ -1 +1 @@ -Subproject commit a20bbfa25014a20ba229ca0c9621001d6b334b44 +Subproject commit f96a47759a613b0c181e9db205482eee9e6080a4 diff --git a/morph-node/.env b/morph-node/.env index 32f359a..de059b6 100644 --- a/morph-node/.env +++ b/morph-node/.env @@ -2,17 +2,31 @@ MORPH_HOME=../mainnet MORPH_FLAG=morph JWT_SECRET_FILE=${MORPH_HOME}/jwt-secret.txt GETH_ENTRYPOINT_FILE=./entrypoint-geth.sh -MAINNET_SNAPSHOT_NAME=snapshot-20260415-1 +MAINNET_SNAPSHOT_NAME=snapshot-20260701-1 -## Environment variables for validator node +## Environment variables for the node L1_CHAIN_ID=1 L1_ETH_RPC=https://eth.drpc.org +# Beacon RPC is only used when DERIVATION_VERIFY_MODE=layer1 (former validator behavior). L1_BEACON_CHAIN_RPC=https://rpc.ankr.com/eth L1MESSAGEQUEUE_CONTRACT=0x3931ade842f5bb8763164bdd81e5361dce6cc1ef ROLLUP_CONTRACT=0x759894ced0e6af42c26668076ffa84d02e3cef60 -DERIVATION_START_HEIGHT=24883902 -L1_MSG_START_HEIGHT=24883052 -L2_BASE_HEIGHT=22181317 +# Heights match MAINNET_SNAPSHOT_NAME above (see the README snapshot table). +DERIVATION_START_HEIGHT=25440822 +L1_MSG_START_HEIGHT=25439950 +L2_BASE_HEIGHT=24216219 ## Extra flags for node NODE_EXTRA_FLAGS=--mainnet + +## Batch verification mode (--derivation.verify-mode). +## 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 (needs L1_BEACON_CHAIN_RPC). +## Leave empty for the binary default (local). `make run-validator` sets layer1. +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 built-in activation. diff --git a/morph-node/.env_hoodi b/morph-node/.env_hoodi index 480741a..33975dd 100644 --- a/morph-node/.env_hoodi +++ b/morph-node/.env_hoodi @@ -2,15 +2,33 @@ MORPH_HOME=../hoodi MORPH_FLAG=morph-hoodi JWT_SECRET_FILE=${MORPH_HOME}/jwt-secret.txt GETH_ENTRYPOINT_FILE=./entrypoint-geth.sh -## todo snapshot -HOODI_SNAPSHOT_NAME=mpt-snapshot-20260402-1 +HOODI_SNAPSHOT_NAME=snapshot-20260630-1 -## Environment variables for validator node +## Environment variables for the node L1_CHAIN_ID=560048 L1_ETH_RPC=${your_layer1_execution_client_rpc_url} +# Beacon RPC is only used when DERIVATION_VERIFY_MODE=layer1 (former validator behavior). L1_BEACON_CHAIN_RPC=${your_layer1_beacon_client_rpc_url} L1MESSAGEQUEUE_CONTRACT=0xd7f39d837f4790b215ba67e0ab63665912648dbe ROLLUP_CONTRACT=0x57e0e6dde89dc52c01fe785774271504b1e04664 -DERIVATION_START_HEIGHT=2534958 -L1_MSG_START_HEIGHT=2528506 -L2_BASE_HEIGHT=4391571 +# Heights match HOODI_SNAPSHOT_NAME above (see the README snapshot table). +DERIVATION_START_HEIGHT=3122685 +L1_MSG_START_HEIGHT=3106743 +L2_BASE_HEIGHT=6543334 + +## Extra flags for morphnode. --hoodi selects the Hoodi network so the node picks +## up the built-in Hoodi L1 sequencer contract address; without it the node +## defaults to network=dev and fails with "L1 Sequencer contract address is required". +NODE_EXTRA_FLAGS=--hoodi + +## Batch verification mode (--derivation.verify-mode). +## 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 (needs L1_BEACON_CHAIN_RPC). +## Leave empty for the binary default (local). `make run-hoodi-validator` sets layer1. +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 built-in activation. diff --git a/morph-node/.env_hoodi_zk b/morph-node/.env_hoodi_zk deleted file mode 100644 index 78404af..0000000 --- a/morph-node/.env_hoodi_zk +++ /dev/null @@ -1,6 +0,0 @@ -# ZK (legacy) override - load after .env_hoodi to switch back to ZK mode -GETH_ENTRYPOINT_FILE=./entrypoint-geth-zk.sh -HOODI_ZK_SNAPSHOT_NAME=snapshot-20260316-1 -DERIVATION_START_HEIGHT=2427831 -L1_MSG_START_HEIGHT=2408746 -L2_BASE_HEIGHT=4001145 diff --git a/morph-node/.env_zk b/morph-node/.env_zk deleted file mode 100644 index 273a3ad..0000000 --- a/morph-node/.env_zk +++ /dev/null @@ -1,6 +0,0 @@ -# ZK (legacy) override - load after .env to switch back to ZK mode -GETH_ENTRYPOINT_FILE=./entrypoint-geth-zk.sh -MAINNET_ZK_SNAPSHOT_NAME=snapshot-20260316-1 -DERIVATION_START_HEIGHT=24668486 -L1_MSG_START_HEIGHT=24667943 -L2_BASE_HEIGHT=21474974 diff --git a/morph-node/Makefile b/morph-node/Makefile index 55d5333..af98c9b 100644 --- a/morph-node/Makefile +++ b/morph-node/Makefile @@ -7,10 +7,6 @@ include .env_hoodi JWT_SECRET_FILE_HOODI := $(JWT_SECRET_FILE) -# Load ZK-specific snapshot variables used by *-zk-snapshot targets -include .env_zk -include .env_hoodi_zk - generate-jwt: @[ -f $(JWT_SECRET_FILE_MAINNET) ] || (echo "Generating $(JWT_SECRET_FILE_MAINNET)..." && openssl rand -hex 32 > $(JWT_SECRET_FILE_MAINNET) && echo "$(JWT_SECRET_FILE_MAINNET) created.") @@ -19,55 +15,35 @@ generate-jwt-hoodi: ## node management run-node: generate-jwt - @echo "You are about to run a Mainnet MPT node." - @echo "To run a ZK (legacy) node instead, use: make run-zk-node" + @echo "You are about to run a Mainnet node." @{ [ -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 node & run-hoodi-node: generate-jwt-hoodi - @echo "You are about to run a Hoodi MPT node." - @echo "To run a ZK (legacy) node instead, use: make run-hoodi-zk-node" + @echo "You are about to run a Hoodi node." @{ [ -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 node & - -run-zk-node: generate-jwt - docker-compose --env-file .env --env-file .env_zk up node & - -run-hoodi-zk-node: generate-jwt-hoodi - docker-compose --env-file .env_hoodi --env-file .env_hoodi_zk up node & - stop-node: docker stop morph-node morph-geth rm-node: docker rm morph-node morph-geth -## validator management +## validator mode = the SAME single node run with layer1 batch verification +## (--derivation.verify-mode=layer1: pull the L1 beacon blob and derive via the +## engine). It is NOT a separate service/container, so stop/rm with the node +## targets above. Requires a working L1_BEACON_CHAIN_RPC in the env file. +## The shell-exported DERIVATION_VERIFY_MODE overrides the empty value in .env. 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" + @echo "You are about to run a Mainnet node in validator mode (--derivation.verify-mode=layer1)." @{ [ -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 & + DERIVATION_VERIFY_MODE=layer1 docker-compose --env-file .env up node & 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" + @echo "You are about to run a Hoodi node in validator mode (--derivation.verify-mode=layer1)." @{ [ -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 + DERIVATION_VERIFY_MODE=layer1 docker-compose --env-file .env_hoodi up node & ## build from source (requires Go) submodule-init: @@ -112,38 +88,27 @@ build: build-geth build-morphnode ## binary mode 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" + @echo "You are about to run a Mainnet node (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 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" + @echo "You are about to run a Hoodi node (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 node .env_hoodi + sh ./run-binary.sh .env_hoodi +## validator mode (binary) = single node with --derivation.verify-mode=layer1. +## MORPH_NODE_DERIVATION_VERIFY_MODE is not set by the env files, so it survives +## sourcing and the entrypoint honors it first. Requires L1_BEACON_CHAIN_RPC. run-validator-binary: generate-jwt - @echo "You are about to run a Mainnet MPT validator (binary mode)." + @echo "You are about to run a Mainnet node in validator mode (--derivation.verify-mode=layer1, 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 validator .env + MORPH_NODE_DERIVATION_VERIFY_MODE=layer1 sh ./run-binary.sh .env run-hoodi-validator-binary: generate-jwt-hoodi - @echo "You are about to run a Hoodi MPT validator (binary mode)." + @echo "You are about to run a Hoodi node in validator mode (--derivation.verify-mode=layer1, 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 validator .env_hoodi - -run-zk-node-binary: generate-jwt - sh ./run-binary.sh node .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 + MORPH_NODE_DERIVATION_VERIFY_MODE=layer1 sh ./run-binary.sh .env_hoodi stop-binary: @if [ -f .geth.pid ]; then kill $$(cat .geth.pid) 2>/dev/null; rm -f .geth.pid; echo "geth stopped."; fi @@ -204,7 +169,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) ===" @@ -214,7 +179,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 @@ -261,11 +226,5 @@ download-and-decompress-mainnet-mpt-snapshot: download-and-decompress-mainnet-sn download-and-decompress-hoodi-mpt-snapshot: download-and-decompress-hoodi-snapshot -download-and-decompress-mainnet-zk-snapshot: - $(call download-and-decompress,$(MAINNET_ZK_SNAPSHOT_NAME),https://snapshot.morphl2.io/mainnet,../mainnet) - -download-and-decompress-hoodi-zk-snapshot: - $(call download-and-decompress,$(HOODI_ZK_SNAPSHOT_NAME),https://snapshot.morphl2.io/hoodi,../hoodi) - diff --git a/morph-node/docker-compose.yml b/morph-node/docker-compose.yml index 0c390d5..3ec789d 100644 --- a/morph-node/docker-compose.yml +++ b/morph-node/docker-compose.yml @@ -1,7 +1,7 @@ services: geth: container_name: morph-geth - image: ghcr.io/morph-l2/go-ethereum:2.2.3 + image: ghcr.io/morph-l2/go-ethereum:2.2.4 restart: unless-stopped ports: - "8545:8545" @@ -19,12 +19,19 @@ services: - "/bin/sh" - "/entrypoint.sh" + # Single self-verifying node. There is no separate validator service anymore: + # every node verifies batches via --derivation.verify-mode. Set + # DERIVATION_VERIFY_MODE=layer1 (in .env/.env_hoodi) for the former validator + # behavior (pull L1 beacon blob + derive); default "local" needs no beacon. node: container_name: morph-node depends_on: geth: condition: service_started - image: ghcr.io/morph-l2/node:0.5.7 + # Provisional: bumped 0.5.7 -> 0.5.8. This must be a build of morph main + # (single-sequencer + --derivation.verify-mode); 0.5.7 and earlier predate + # verify-mode. Confirm/replace with the real published tag before merge. + image: ghcr.io/morph-l2/node:0.5.8 restart: unless-stopped ports: - "26656" @@ -36,41 +43,19 @@ 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} + # Beacon RPC is only used on the layer1 verify path; harmless when unset for local. + - 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" diff --git a/morph-node/entrypoint-geth-zk.sh b/morph-node/entrypoint-geth-zk.sh deleted file mode 100644 index f8e462e..0000000 --- a/morph-node/entrypoint-geth-zk.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/sh - -GETH_BIN=${GETH_BINARY:-geth} -GETH_DATADIR=${GETH_DATADIR:-./db} -JWT_PATH=${JWT_SECRET_PATH:-/jwt-secret.txt} - -if [ ! -f "${JWT_PATH}" ]; then - echo "Error: jwt-secret.txt not found at ${JWT_PATH}." - echo "Please create it before starting the service." - exit 1 -fi - -MORPH_FLAG=${MORPH_FLAG:-"morph"} - -exec "${GETH_BIN}" \ - "--${MORPH_FLAG}" \ - "--datadir=${GETH_DATADIR}" \ - --verbosity=3 \ - --http \ - "--http.corsdomain=*" \ - "--http.vhosts=*" \ - --http.addr=0.0.0.0 \ - --http.port=8545 \ - --http.api=web3,debug,eth,txpool,net,morph,engine,admin \ - --ws \ - --ws.addr=0.0.0.0 \ - --ws.port=8546 \ - "--ws.origins=*" \ - --ws.api=web3,debug,eth,txpool,net,morph,engine,admin \ - --authrpc.addr=0.0.0.0 \ - --authrpc.port=8551 \ - "--authrpc.vhosts=*" \ - "--authrpc.jwtsecret=${JWT_PATH}" \ - --gcmode=archive \ - "--log.filename=${GETH_DATADIR}/geth.log" \ - --metrics \ - --metrics.addr=0.0.0.0 diff --git a/morph-node/entrypoint-node.sh b/morph-node/entrypoint-node.sh index 0796a02..6703b61 100755 --- a/morph-node/entrypoint-node.sh +++ b/morph-node/entrypoint-node.sh @@ -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:-}} @@ -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 \ diff --git a/morph-node/run-binary.sh b/morph-node/run-binary.sh index a766733..a2095da 100755 --- a/morph-node/run-binary.sh +++ b/morph-node/run-binary.sh @@ -1,13 +1,15 @@ #!/bin/sh -# Usage: run-binary.sh [override-env-file] -# mode: node | validator +# Usage: run-binary.sh [override-env-file] # env-file: .env | .env_hoodi -# override-env-file: optional, e.g. .env_zk for ZK legacy mode +# override-env-file: optional extra env file layered on top +# +# There is no node/validator mode arg anymore: every node self-verifies. For the +# former validator behavior, set MORPH_NODE_DERIVATION_VERIFY_MODE=layer1 in the +# environment (see `make run-validator-binary`). -MODE=${1:-node} -ENV_FILE=${2:-.env} -OVERRIDE_ENV_FILE=${3:-} +ENV_FILE=${1:-.env} +OVERRIDE_ENV_FILE=${2:-} # Source environment set -a @@ -50,7 +52,7 @@ trap cleanup INT TERM # Validate entrypoint file case "${GETH_ENTRYPOINT_FILE}" in - ./entrypoint-geth.sh|./entrypoint-geth-zk.sh) ;; + ./entrypoint-geth.sh) ;; *) echo "Error: invalid GETH_ENTRYPOINT_FILE: ${GETH_ENTRYPOINT_FILE}" exit 1 @@ -88,13 +90,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} \ From 5f4b54fedd2344bbe44309281a5f0e30efef36aa Mon Sep 17 00:00:00 2001 From: corey Date: Wed, 15 Jul 2026 14:17:47 +0800 Subject: [PATCH 2/2] fix: node image 0.6.0, correct beacon-RPC docs, restore validator stop/rm aliases - Bump node image 0.5.8 -> 0.6.0 (real published morph-main build; 0.5.8 never existed on ghcr) - Correct L1_BEACON_CHAIN_RPC comments: it is required at startup in BOTH verify modes (the node exits without it), not only on the layer1 path - Restore stop-validator / rm-validator as aliases of stop-node / rm-node so the old validator command surface stays fully backward-compatible (they now operate on the single morph-node container) Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 19 ++++++++++++------- morph-node/.env | 6 ++++-- morph-node/.env_hoodi | 6 ++++-- morph-node/Makefile | 22 ++++++++++++++-------- morph-node/docker-compose.yml | 13 +++++++------ morph-node/run-binary.sh | 4 ++-- 6 files changed, 43 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index ece5d05..f7c01f2 100644 --- a/README.md +++ b/README.md @@ -92,15 +92,20 @@ mode is controlled by `DERIVATION_VERIFY_MODE` (`--derivation.verify-mode`) in t - `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. Requires a working `L1_BEACON_CHAIN_RPC`. + the former validator node. -Convenience targets run the same node in `layer1` mode: +`L1_BEACON_CHAIN_RPC` is required in **both** modes — the node exits at startup without it. +`local` just doesn't fetch the blob on the happy path; `layer1` fetches it every batch. -```bash -make run-validator # mainnet, Docker -make run-hoodi-validator # hoodi, Docker -make run-validator-binary # mainnet, binary -``` +`layer1` is what the former validator node did. Two ways to enable it: + +- **Validator commands** (kept for backward compatibility): `make run-validator` / + `make run-hoodi-validator` (or the `-binary` variants) run the single node in `layer1` + mode. Stop/remove with `make stop-validator` / `make rm-validator` (aliases of + `make stop-node` / `make rm-node`). +- **Env var:** set `DERIVATION_VERIFY_MODE=layer1` in `.env` / `.env_hoodi`, then `make run-node`. + +There is no separate validator container anymore — both paths run the same `morph-node`. > `L1_SEQUENCER_CONTRACT` and `CONSENSUS_SWITCH_HEIGHT` are hardcoded per-network defaults > in the binary and must not be set as operator config. diff --git a/morph-node/.env b/morph-node/.env index de059b6..6bd22c1 100644 --- a/morph-node/.env +++ b/morph-node/.env @@ -7,7 +7,8 @@ MAINNET_SNAPSHOT_NAME=snapshot-20260701-1 ## Environment variables for the node L1_CHAIN_ID=1 L1_ETH_RPC=https://eth.drpc.org -# Beacon RPC is only used when DERIVATION_VERIFY_MODE=layer1 (former validator behavior). +# Required in both verify modes: the node exits at startup without it. Only fetched +# at runtime on the layer1 path (local skips the blob fetch on the happy path). L1_BEACON_CHAIN_RPC=https://rpc.ankr.com/eth L1MESSAGEQUEUE_CONTRACT=0x3931ade842f5bb8763164bdd81e5361dce6cc1ef ROLLUP_CONTRACT=0x759894ced0e6af42c26668076ffa84d02e3cef60 @@ -24,7 +25,8 @@ NODE_EXTRA_FLAGS=--mainnet ## 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 (needs L1_BEACON_CHAIN_RPC). -## Leave empty for the binary default (local). `make run-validator` sets layer1. +## Leave empty for the binary default (local). `make run-validator` / +## `run-validator-binary` set layer1 for you; or set it here and use `make run-node`. DERIVATION_VERIFY_MODE= ## NOTE: do NOT set L1_SEQUENCER_CONTRACT or CONSENSUS_SWITCH_HEIGHT here. These diff --git a/morph-node/.env_hoodi b/morph-node/.env_hoodi index 33975dd..971d5c1 100644 --- a/morph-node/.env_hoodi +++ b/morph-node/.env_hoodi @@ -7,7 +7,8 @@ HOODI_SNAPSHOT_NAME=snapshot-20260630-1 ## Environment variables for the node L1_CHAIN_ID=560048 L1_ETH_RPC=${your_layer1_execution_client_rpc_url} -# Beacon RPC is only used when DERIVATION_VERIFY_MODE=layer1 (former validator behavior). +# Required in both verify modes: the node exits at startup without it. Only fetched +# at runtime on the layer1 path (local skips the blob fetch on the happy path). L1_BEACON_CHAIN_RPC=${your_layer1_beacon_client_rpc_url} L1MESSAGEQUEUE_CONTRACT=0xd7f39d837f4790b215ba67e0ab63665912648dbe ROLLUP_CONTRACT=0x57e0e6dde89dc52c01fe785774271504b1e04664 @@ -26,7 +27,8 @@ NODE_EXTRA_FLAGS=--hoodi ## 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 (needs L1_BEACON_CHAIN_RPC). -## Leave empty for the binary default (local). `make run-hoodi-validator` sets layer1. +## Leave empty for the binary default (local). `make run-hoodi-validator` / +## `run-hoodi-validator-binary` set layer1 for you; or set it here and use `make run-hoodi-node`. DERIVATION_VERIFY_MODE= ## NOTE: do NOT set L1_SEQUENCER_CONTRACT or CONSENSUS_SWITCH_HEIGHT here. These diff --git a/morph-node/Makefile b/morph-node/Makefile index af98c9b..26c36d1 100644 --- a/morph-node/Makefile +++ b/morph-node/Makefile @@ -30,11 +30,11 @@ stop-node: rm-node: docker rm morph-node morph-geth -## validator mode = the SAME single node run with layer1 batch verification -## (--derivation.verify-mode=layer1: pull the L1 beacon blob and derive via the -## engine). It is NOT a separate service/container, so stop/rm with the node -## targets above. Requires a working L1_BEACON_CHAIN_RPC in the env file. -## The shell-exported DERIVATION_VERIFY_MODE overrides the empty value in .env. +## Validator commands, kept for backward compatibility. A "validator" is now just +## the single node run in layer1 batch-verification mode (--derivation.verify-mode=layer1: +## pull the L1 beacon blob and derive via the engine). There is no separate validator +## container anymore, so stop/rm operate on the same morph-node container. +## Requires a working L1_BEACON_CHAIN_RPC in the env file. run-validator: generate-jwt @echo "You are about to run a Mainnet node in validator mode (--derivation.verify-mode=layer1)." @{ [ -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; } @@ -45,6 +45,11 @@ run-hoodi-validator: generate-jwt-hoodi @{ [ -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; } DERIVATION_VERIFY_MODE=layer1 docker-compose --env-file .env_hoodi up node & +## Backward-compat aliases: the validator is now the same container as the node. +stop-validator: stop-node + +rm-validator: rm-node + ## build from source (requires Go) submodule-init: @echo "Initializing git submodules..." @@ -97,9 +102,10 @@ run-hoodi-node-binary: generate-jwt-hoodi @{ [ -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 .env_hoodi -## validator mode (binary) = single node with --derivation.verify-mode=layer1. -## MORPH_NODE_DERIVATION_VERIFY_MODE is not set by the env files, so it survives -## sourcing and the entrypoint honors it first. Requires L1_BEACON_CHAIN_RPC. +## Validator commands (binary), kept for backward compatibility = single node with +## --derivation.verify-mode=layer1. MORPH_NODE_DERIVATION_VERIFY_MODE is not set by +## the env files, so it survives sourcing and the entrypoint honors it first. +## Requires L1_BEACON_CHAIN_RPC. run-validator-binary: generate-jwt @echo "You are about to run a Mainnet node in validator mode (--derivation.verify-mode=layer1, 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; } diff --git a/morph-node/docker-compose.yml b/morph-node/docker-compose.yml index 3ec789d..5cfa1cb 100644 --- a/morph-node/docker-compose.yml +++ b/morph-node/docker-compose.yml @@ -22,16 +22,16 @@ services: # Single self-verifying node. There is no separate validator service anymore: # every node verifies batches via --derivation.verify-mode. Set # DERIVATION_VERIFY_MODE=layer1 (in .env/.env_hoodi) for the former validator - # behavior (pull L1 beacon blob + derive); default "local" needs no beacon. + # behavior (pull L1 beacon blob + derive). L1_BEACON_CHAIN_RPC is required in + # both modes (the node exits at startup without it); "local" simply doesn't + # fetch the blob on the happy path. node: container_name: morph-node depends_on: geth: condition: service_started - # Provisional: bumped 0.5.7 -> 0.5.8. This must be a build of morph main - # (single-sequencer + --derivation.verify-mode); 0.5.7 and earlier predate - # verify-mode. Confirm/replace with the real published tag before merge. - image: ghcr.io/morph-l2/node:0.5.8 + # morph main build with single-sequencer + --derivation.verify-mode support. + image: ghcr.io/morph-l2/node:0.6.0 restart: unless-stopped ports: - "26656" @@ -43,7 +43,8 @@ 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} - # Beacon RPC is only used on the layer1 verify path; harmless when unset for local. + # Required in both verify modes: the node exits at startup if this is empty. + # Only fetched at runtime on the layer1 path (local skips it on the happy path). - MORPH_NODE_L1_ETH_BEACON_RPC=${L1_BEACON_CHAIN_RPC} - MORPH_NODE_L1_CHAIN_ID=${L1_CHAIN_ID} - MORPH_NODE_ROLLUP_ADDRESS=${ROLLUP_CONTRACT} diff --git a/morph-node/run-binary.sh b/morph-node/run-binary.sh index a2095da..40d402e 100755 --- a/morph-node/run-binary.sh +++ b/morph-node/run-binary.sh @@ -5,8 +5,8 @@ # override-env-file: optional extra env file layered on top # # There is no node/validator mode arg anymore: every node self-verifies. For the -# former validator behavior, set MORPH_NODE_DERIVATION_VERIFY_MODE=layer1 in the -# environment (see `make run-validator-binary`). +# former validator behavior, set DERIVATION_VERIFY_MODE=layer1 in the env file +# (or use `make run-validator-binary`, which sets it for you). ENV_FILE=${1:-.env} OVERRIDE_ENV_FILE=${2:-}