Skip to main content

Run a validator from source

Run a Morph Validator

This guide describes the approach to running a Morph validator node. If you are unfamiliar with the validator duties, please refer to our optimistic zkEVM design.

Create the folder ~/.morph as our home directory for this example.

Build executable binary

Clone Morph

mkdir -p ~/.morph 
cd ~/.morph
git clone https://github.com/morph-l2/morph.git

Next, you need to check out a release version. You can find the available release versions on the Morph Releases page. It is recommended to use the latest release version.

cd morph
git checkout ${RELEASE_VERSION}

Build Geth

Notice: You need C compiler to build geth

make geth

Build Node

cd ~/.morph/morph/node 
make build

Sync from the snapshot

Config Preparation

  1. Download the config files and make data dir
cd ~/.morph

## mainnet
wget https://raw.githubusercontent.com/morph-l2/run-morph-node/main/mainnet/data.zip

## testnet
wget https://raw.githubusercontent.com/morph-l2/run-morph-node/main/holesky/data.zip

unzip data.zip
  1. Create a shared secret with node
cd ~/.morph
openssl rand -hex 32 > jwt-secret.txt

Download snapshot

Download the latest snapshot corresponding to either the mainnet or testnet network.

A complete record of historical snapshots is available here. Below is an example of how to download a snapshot

## mainnet
wget -q --show-progress https://snapshot.morphl2.io/mainnet/${SNAPSHOT_NAME}.tar.gz
tar -xzvf ${SNAPSHOT_NAME}.tar.gz

## holesky
wget -q --show-progress https://snapshot.morphl2.io/holesky/${SNAPSHOT_NAME}.tar.gz
tar -xzvf ${SNAPSHOT_NAME}.tar.gz

Extracting snapshot data to the data directory your node points to

mv ${SNAPSHOT_NAME}/geth geth-data
mv ${SNAPSHOT_NAME}/data node-data

Script to start the process

Geth

Script for starting mainnet geth
./morph/go-ethereum/build/bin/geth --morph \
--datadir "./geth-data" \
--http --http.api=web3,debug,eth,txpool,net,engine \
--authrpc.addr localhost \
--authrpc.vhosts="localhost" \
--authrpc.port 8551 \
--authrpc.jwtsecret=./jwt-secret.txt \
--log.filename=./geth.log

note

For testnet, using --morph-holesky instead

tail -f geth.log to check if the Geth is running properly, or you can also execute the below curl command to check if you are connected to the peer.

curl --location --request POST 'localhost:8545/' \
--header 'Content-Type: application/json' \
--data-raw '{
"jsonrpc":"2.0",
"method":"eth_blockNumber",
"id":1
}'

{"jsonrpc":"2.0","id":1,"result":"0x148e39"}

Node

You need to set the DERIVATION_START_HEIGHT and L1_MSG_START_HEIGHT variables correctly to match the snapshot version you downloaded. From the snapshot information, you can find the corresponding height values.

cd ~/.morph

## mainnet
export CHAIN_ID=1
export L1MESSAGEQUEUE_CONTRACT=0x3931ade842f5bb8763164bdd81e5361dce6cc1ef
export ROLLUP=0x759894ced0e6af42c26668076ffa84d02e3cef60
export DERIVATION_START_HEIGHT=${The height matches your snapshot}
export L1_MSG_START_HEIGHT=${The height matches your snapshot}

## start node
./morph/node/build/bin/morphnode --validator --home ./node-data \
--l2.jwt-secret ./jwt-secret.txt \
--l2.eth http://localhost:8545 \
--l2.engine http://localhost:8551 \
--l1.rpc $(Ethereum RPC) \
--l1.beaconrpc $(Ethereum beacon chain RPC) \
--l1.chain-id ${CHAIN_ID} \
--validator.privateKey 0x0000000000000000000000000000000000000000000000000000000000000001 \
--sync.depositContractAddr ${L1MESSAGEQUEUE_CONTRACT} \
--sync.startHeight ${L1_MSG_START_HEIGHT} \
--derivation.rollupAddress ${ROLLUP} \
--derivation.startHeight ${DERIVATION_START_HEIGHT} \
--derivation.fetchBlockRange 200 \
--log.filename ./node.log

For holesky network, using

export CHAIN_ID=17000 
export L1MESSAGEQUEUECONTRACT=0x778d1d9a4d8b6b9ade36d967a9ac19455ec3fd0b
export ROLLUP=0xd8c5c541d56f59d65cf775de928ccf4a47d4985c
export DERIVATION_START_HEIGHT=${The height matches your snapshot}
export L1_MSG_START_HEIGHT=${The height matches your snapshot}
note

Note the validator.privateKey is of no use to you. It is used to send challenges when the state root is found to be incorrect. However, we do not currently accept challenges from third party addresses. But it is also a required parameter for the morphnode command, so we give a 0x00... 1.

Check Status

If your node is successfully started, you will see the following response:

I[2024-06-06|15:57:35.216] metrics server enabled                       module=derivation host=0.0.0.0 port=26660
derivation node starting
ID> 24-06-06|15:57:35.216] initial sync start module=syncer msg="Running initial sync of L1 messages before starting sequencer, this might take a while..."
I[2024-06-06|15:57:35.242] initial sync completed module=syncer latestSyncedBlock=1681622
I[2024-06-06|15:57:35.242] derivation start pull rollupData form l1 module=derivation startBlock=1681599 end=1681622
I[2024-06-06|15:57:35.244] fetched rollup tx module=derivation txNum=8 latestBatchIndex=59201
I[2024-06-06|15:57:35.315] fetch rollup transaction success module=derivation txNonce=8764 txHash=0x5fb8a98472d1be73be2bc6be0807b9e0c68b7ba14a648c8a17bdaff7b26eb923 l1BlockNumber=1681599 firstL2BlockNumber=1347115 lastL2BlockNumber=1347129
I[2024-06-06|15:57:35.669] new l2 block success module=derivation blockNumber=1347115

You can use the following command to check the newest block height to make sure you are aligned.

curl --location --request POST 'localhost:8545/' \
--header 'Content-Type: application/json' \
--data-raw '{
"jsonrpc":"2.0",
"method":"eth_blockNumber",
"id":1
}'
{"jsonrpc":"2.0","id":1,"result":"0x148e39"}

Make sure you check the validator status constantly, if you find response

[2024-06-14|16:43:50.904] root hash or withdrawal hash is not equal    originStateRootHash=0x13f91d1c272e48e2d864ce7bfb421506d5b2a04def64d45c75391cdcdd69cd78 deriveStateRootHash=0x27e10420c0e34676a7d75c4189d7ccd1c3407cc8fd0b3eafb01c15e250a1215f batchWithdrawalRoot=0xa3e4a7cf45c7591a6bd9868f1fa7485ae345f10067acaade5f5b07d418b2e172 deriveWithdrawalRoot=0xa3e4a7cf45c7591a6bd9868f1fa7485ae345f10067acaade5f5b07d418b2e172

This means your validators find inconsistent between sequencer submission and your own observation.