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
9 changes: 4 additions & 5 deletions local-test/.gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Data directories
reth-data/
geth-data/
node-data/
# Per-network data directories (mainnet/, hoodi/)
mainnet/
hoodi/

# Runtime files
jwt-secret.txt
Expand All @@ -13,6 +12,6 @@ jwt-secret.txt

# Downloaded artifacts
mainnet-data.zip
mainnet-data/
hoodi-data.zip
.config-extract/
config-prep.*/
100 changes: 100 additions & 0 deletions local-test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Local Test Scripts

Scripts for running a morph-reth + morphnode full node locally, supporting both **mainnet** and **Hoodi testnet**.

## Prerequisites

- **morph-reth**: `cargo build --release --bin morph-reth`
- **morphnode**: `cd ../morph/node && make build`
- **pm2**: `npm install -g pm2`
- **jq**, **curl**, **unzip**

## Quick Start

```bash
# Mainnet (default)
./local-test/start-all.sh

# Hoodi testnet
./local-test/start-all.sh hoodi
```

All scripts accept an optional network argument as the first positional parameter. If omitted, defaults to `mainnet`.

## Scripts

| Script | Description |
|--------|-------------|
| `start-all.sh [network]` | Prepare config, start morph-reth, wait for RPC, start morphnode |
| `stop-all.sh [network]` | Stop morphnode and morph-reth |
| `status.sh [network]` | Show process status, RPC info, and morphnode sync progress |
| `reset.sh [network] [--yes]` | Wipe chain data (keeps config/keys), requires confirmation |
| `prepare.sh [network]` | Download config bundle and generate JWT secret if missing |
| `reth-start.sh [network]` | Start morph-reth only |
| `reth-stop.sh [network]` | Stop morph-reth only |
| `node-start.sh [network]` | Start morphnode only |
| `node-stop.sh [network]` | Stop morphnode only |

## Data Directory Layout

Each network gets its own isolated data directory:

```
local-test/
jwt-secret.txt # Shared JWT secret (auto-generated)
mainnet/
reth-data/ # morph-reth chain database
node-data/config/ # genesis.json, config.toml, keys
node-data/data/ # Tendermint state
reth.log, node.log
hoodi/
reth-data/
node-data/config/
node-data/data/
reth.log, node.log
```
Comment on lines +42 to +55

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add a language identifier to the fenced block.

The directory layout block is missing a fence language, which triggers markdownlint MD040.

💡 Proposed fix
-```
+```text
 local-test/
   jwt-secret.txt              # Shared JWT secret (auto-generated)
   mainnet/
     reth-data/                 # morph-reth chain database
@@
     node-data/data/
     reth.log, node.log
</details>

<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion

🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 42-42: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

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

In `@local-test/README.md` around lines 42 - 55, The fenced code block showing the
directory layout is missing a language identifier which triggers markdownlint
MD040; update the opening fence for that block (the triple-backticks before the
directory tree) to include a language identifier such as "text" (i.e., change
``` to ```text) so the block becomes a proper fenced code block—look for the
directory layout block content beginning with "local-test/" in README.md and
modify its opening fence accordingly.


Switching networks does not require a reset — data is fully isolated.

## Configuration

All defaults can be overridden via environment variables. Common ones:

| Variable | Default | Description |
|----------|---------|-------------|
| `MORPH_NETWORK` | `mainnet` | Network selection (`mainnet` or `hoodi`) |
| `RETH_BIN` | `./target/release/morph-reth` | Path to morph-reth binary |
| `MORPHNODE_BIN` | `../morph/node/build/bin/morphnode` | Path to morphnode binary |
| `RETH_HTTP_PORT` | `8545` | HTTP RPC port |
| `RETH_AUTHRPC_PORT` | `8551` | Engine API auth RPC port |
| `RETH_BOOTNODES` | *(empty)* | Comma-separated enode URLs |
| `MORPH_NODE_L1_RPC` | *(per-network default)* | L1 Ethereum RPC endpoint |
| `MORPH_MAX_TX_PAYLOAD_BYTES` | `122880` | Max transaction payload size |

Example with overrides:

```bash
RETH_HTTP_PORT=9545 RETH_BOOTNODES="enode://abc@1.2.3.4:30303" ./local-test/start-all.sh hoodi
```

## Monitoring

```bash
pm2 list # Process status
pm2 logs # All logs (live)
pm2 logs morph-reth # morph-reth logs only
pm2 logs morph-node # morphnode logs only
pm2 monit # Real-time resource monitoring
./local-test/status.sh # RPC status + morphnode sync info
```

## Reset

To wipe chain data and start syncing from scratch:

```bash
./local-test/reset.sh # Reset mainnet (interactive)
./local-test/reset.sh hoodi --yes # Reset hoodi (no confirmation)
```

This removes `reth-data/db`, `reth-data/static_files`, and `node-data/data/` for the specified network. Config files (genesis, keys) are preserved.
58 changes: 44 additions & 14 deletions local-test/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,55 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"

# Morphnode configuration (binary is in ../morph/node, data is in local-test)
# Network selection: mainnet (default) or hoodi.
# Accept as first positional arg (e.g. ./start-all.sh hoodi) or MORPH_NETWORK env var.
if [[ "${1:-}" == "mainnet" || "${1:-}" == "hoodi" ]]; then
MORPH_NETWORK="$1"
shift
fi
: "${MORPH_NETWORK:=mainnet}"

if [[ "${MORPH_NETWORK}" != "mainnet" && "${MORPH_NETWORK}" != "hoodi" ]]; then
echo "ERROR: MORPH_NETWORK must be 'mainnet' or 'hoodi', got '${MORPH_NETWORK}'"
exit 1
fi

# Export so child processes (reth-start.sh, node-start.sh, etc.) inherit the value.
export MORPH_NETWORK

# ─── Network-specific defaults ────────────────────────────────────────────────

if [[ "${MORPH_NETWORK}" == "mainnet" ]]; then
: "${MORPH_NODE_L1_RPC:=${MORPH_NODE_L1_ETH_RPC:-https://ethereum.publicnode.com}}"
: "${MORPH_NODE_DEPOSIT_CONTRACT:=${MORPH_NODE_SYNC_DEPOSIT_CONTRACT_ADDRESS:-0x3931ade842f5bb8763164bdd81e5361dce6cc1ef}}"
: "${MORPH_NODE_ROLLUP_CONTRACT:=}"
: "${MORPH_NODE_EXTRA_FLAGS:=--mainnet}"
: "${CONFIG_ZIP_URL:=https://raw.githubusercontent.com/morph-l2/run-morph-node/main/mainnet/data.zip}"
: "${CONFIG_ZIP_PATH:=./local-test/mainnet-data.zip}"
: "${MORPH_CHAIN:=mainnet}"
else
: "${MORPH_NODE_L1_RPC:=${MORPH_NODE_L1_ETH_RPC:-https://ethereum-hoodi-rpc.publicnode.com}}"
: "${MORPH_NODE_DEPOSIT_CONTRACT:=${MORPH_NODE_SYNC_DEPOSIT_CONTRACT_ADDRESS:-0xd7f39d837f4790b215ba67e0ab63665912648dbe}}"
: "${MORPH_NODE_ROLLUP_CONTRACT:=0x57e0e6dde89dc52c01fe785774271504b1e04664}"
: "${MORPH_NODE_EXTRA_FLAGS:=}"
: "${CONFIG_ZIP_URL:=https://raw.githubusercontent.com/morph-l2/run-morph-node/main/hoodi/data.zip}"
: "${CONFIG_ZIP_PATH:=./local-test/hoodi-data.zip}"
: "${MORPH_CHAIN:=hoodi}"
fi

# ─── Shared configuration ─────────────────────────────────────────────────────

: "${MORPHNODE_BIN:=../morph/node/build/bin/morphnode}"
: "${NODE_HOME:=./local-test/node-data}"
: "${NODE_HOME:=./local-test/${MORPH_NETWORK}/node-data}"
: "${JWT_SECRET:=./local-test/jwt-secret.txt}"
: "${NODE_LOG_FILE:=./local-test/node.log}"
: "${NODE_LOG_FILE:=./local-test/${MORPH_NETWORK}/node.log}"
: "${DOWNLOAD_CONFIG_IF_MISSING:=1}"
: "${MAINNET_CONFIG_ZIP_URL:=https://raw.githubusercontent.com/morph-l2/run-morph-node/main/mainnet/data.zip}"
: "${CONFIG_ZIP_PATH:=./local-test/mainnet-data.zip}"
: "${KEEP_CONFIG_ARTIFACTS:=0}"
: "${AUTO_RESET_ON_WRONG_BLOCK:=0}"

# Morph Geth configuration
: "${GETH_BIN:=../morph/go-ethereum/build/bin/geth}"
: "${GETH_DATA_DIR:=./local-test/geth-data}"
: "${GETH_LOG_FILE:=./local-test/geth.log}"

# Morph-Reth configuration
: "${RETH_BIN:=./target/release/morph-reth}"
: "${RETH_DATA_DIR:=./local-test/reth-data}"
: "${RETH_LOG_FILE:=./local-test/reth.log}"
: "${RETH_DATA_DIR:=./local-test/${MORPH_NETWORK}/reth-data}"
: "${RETH_LOG_FILE:=./local-test/${MORPH_NETWORK}/reth.log}"
: "${RETH_HTTP_ADDR:=0.0.0.0}"
: "${RETH_HTTP_PORT:=8545}"
: "${RETH_AUTHRPC_ADDR:=127.0.0.1}"
Expand All @@ -33,6 +62,8 @@ REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
: "${MORPH_MAX_TX_PAYLOAD_BYTES:=122880}"
: "${MORPH_MAX_TX_PER_BLOCK:=}"

# ─── Helper functions ─────────────────────────────────────────────────────────

check_binary() {
local bin_path="$1"
local build_hint="$2"
Expand All @@ -48,7 +79,6 @@ cleanup_runtime_logs() {
rm -rf "$(dirname "${RETH_LOG_FILE}")"/{[0-9]*,*.log*}
}

# pm2 helper functions
pm2_check() {
if ! command -v pm2 &> /dev/null; then
echo "ERROR: pm2 is not installed"
Expand Down
47 changes: 0 additions & 47 deletions local-test/geth-start.sh

This file was deleted.

9 changes: 0 additions & 9 deletions local-test/geth-stop.sh

This file was deleted.

31 changes: 24 additions & 7 deletions local-test/node-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ set -euo pipefail
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common.sh"
cd "${REPO_ROOT}"

echo "Starting morphnode..."
echo "Starting morphnode (${MORPH_NETWORK})..."

# Check prerequisites
pm2_check
Expand All @@ -22,12 +22,29 @@ fi
# Ensure log directory exists
mkdir -p "$(dirname "${NODE_LOG_FILE}")"

# Start morphnode with pm2
pm2 start "${MORPHNODE_BIN}" --name morph-node -- \
--home "${NODE_HOME}" \
--l2.jwt-secret "${JWT_SECRET}" \
--l2.eth "http://${RETH_HTTP_ADDR}:${RETH_HTTP_PORT}" \
--l2.engine "http://${RETH_AUTHRPC_ADDR}:${RETH_AUTHRPC_PORT}" \
# Build node args
args=(
--home "${NODE_HOME}"
--l2.jwt-secret "${JWT_SECRET}"
--l2.eth "http://${RETH_HTTP_ADDR}:${RETH_HTTP_PORT}"
--l2.engine "http://${RETH_AUTHRPC_ADDR}:${RETH_AUTHRPC_PORT}"
--l1.rpc "${MORPH_NODE_L1_RPC}"
--sync.depositContractAddr "${MORPH_NODE_DEPOSIT_CONTRACT}"
--log.filename "${NODE_LOG_FILE}"
)

# Hoodi requires rollup contract address
if [[ -n "${MORPH_NODE_ROLLUP_CONTRACT}" ]]; then
args+=(--derivation.rollupAddress "${MORPH_NODE_ROLLUP_CONTRACT}")
fi

if [[ -n "${MORPH_NODE_EXTRA_FLAGS}" ]]; then
# shellcheck disable=SC2206
extra_flags=(${MORPH_NODE_EXTRA_FLAGS})
args+=("${extra_flags[@]}")
fi

# Start morphnode with pm2
pm2 start "${MORPHNODE_BIN}" --name morph-node -- "${args[@]}"

echo "Logs: pm2 logs morph-node"
2 changes: 1 addition & 1 deletion local-test/node-stop.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ set -euo pipefail
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common.sh"
cd "${REPO_ROOT}"

stop_by_pid_file "morphnode" "${NODE_PID_FILE}"
pm2_stop "morph-node"
33 changes: 17 additions & 16 deletions local-test/prepare.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common.sh"
cd "${REPO_ROOT}"

check_binary "${RETH_BIN}" "cargo build --release --bin morph-reth"
check_binary "${MORPHNODE_BIN}" "run: cd node && make build"
check_binary "${MORPHNODE_BIN}" "run: cd ../morph/node && make build"

mkdir -p "${RETH_DATA_DIR}"
mkdir -p "${NODE_HOME}"
Expand All @@ -32,8 +32,6 @@ if [[ ! -f "${NODE_HOME}/config/config.toml" || ! -f "${NODE_HOME}/config/genesi
exit 1
fi

# Clean legacy extraction directories from previous runs.
rm -rf "./local-test/.config-extract" "./local-test/mainnet-data"
mkdir -p "$(dirname "${CONFIG_ZIP_PATH}")"

temp_extract_dir="$(mktemp -d "${SCRIPT_DIR}/config-prep.XXXXXX")"
Expand All @@ -45,22 +43,24 @@ if [[ ! -f "${NODE_HOME}/config/config.toml" || ! -f "${NODE_HOME}/config/genesi
}
trap cleanup_temp EXIT

echo "Downloading mainnet config bundle..."
curl -fL "${MAINNET_CONFIG_ZIP_URL}" -o "${CONFIG_ZIP_PATH}"
echo "Downloading ${MORPH_NETWORK} config bundle..."
curl -fL "${CONFIG_ZIP_URL}" -o "${CONFIG_ZIP_PATH}"
unzip -oq "${CONFIG_ZIP_PATH}" -d "${temp_extract_dir}"

bundle_root=""
if [[ -f "${temp_extract_dir}/data/node-data/config/config.toml" && -f "${temp_extract_dir}/data/node-data/config/genesis.json" ]]; then
bundle_root="${temp_extract_dir}/data"
elif [[ -f "${temp_extract_dir}/mainnet-data/node-data/config/config.toml" && -f "${temp_extract_dir}/mainnet-data/node-data/config/genesis.json" ]]; then
bundle_root="${temp_extract_dir}/mainnet-data"
elif [[ -f "${temp_extract_dir}/node-data/config/config.toml" && -f "${temp_extract_dir}/node-data/config/genesis.json" ]]; then
bundle_root="${temp_extract_dir}"
fi
for candidate in \
"${temp_extract_dir}/data" \
"${temp_extract_dir}/${MORPH_NETWORK}-data" \
"${temp_extract_dir}"
do
if [[ -f "${candidate}/node-data/config/config.toml" && -f "${candidate}/node-data/config/genesis.json" ]]; then
bundle_root="${candidate}"
break
fi
done

if [[ -z "${bundle_root}" ]]; then
echo "Downloaded zip does not contain expected node-data config files."
echo "Checked bundle roots: ${temp_extract_dir}/data, ${temp_extract_dir}/mainnet-data, ${temp_extract_dir}"
exit 1
fi

Expand All @@ -78,7 +78,7 @@ if [[ ! -f "${NODE_HOME}/config/config.toml" || ! -f "${NODE_HOME}/config/genesi
if [[ -f "${bundle_root}/node-data/data/priv_validator_state.json" ]]; then
cp -f "${bundle_root}/node-data/data/priv_validator_state.json" "${NODE_HOME}/data/priv_validator_state.json"
fi
echo "Config prepared at ${NODE_HOME} from ${MAINNET_CONFIG_ZIP_URL}"
echo "Config prepared at ${NODE_HOME} from ${CONFIG_ZIP_URL}"
else
echo "Warning: node-data is incomplete under ${NODE_HOME}."
echo "Set DOWNLOAD_CONFIG_IF_MISSING=1 or prepare config files manually."
Expand All @@ -98,13 +98,14 @@ if [[ -f "${NODE_LOG_FILE}" ]] && grep -q "wrong block number" "${NODE_LOG_FILE}
echo "Detected historical replay failure in ${NODE_LOG_FILE}: wrong block number"
if [[ "${AUTO_RESET_ON_WRONG_BLOCK}" == "1" ]]; then
echo "AUTO_RESET_ON_WRONG_BLOCK=1, resetting local sync state..."
"${SCRIPT_DIR}/reset-sync-state.sh" --yes
"${SCRIPT_DIR}/reset.sh" --yes
else
echo "If replay fails again, run: ./local-test/reset-sync-state.sh --yes"
echo "If replay fails again, run: $(rel_path "${SCRIPT_DIR}")/reset.sh --yes"
fi
fi

echo "Preparation finished."
echo "MORPH_NETWORK=${MORPH_NETWORK}"
echo "RETH_DATA_DIR=$(rel_path "${RETH_DATA_DIR}")"
echo "NODE_HOME=$(rel_path "${NODE_HOME}")"
echo "JWT_SECRET=$(rel_path "${JWT_SECRET}")"
Loading
Loading