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

Currently, we use tag v0.4.0 as our beta version geth.

cd morph
git checkout v0.4.0

Build Geth

Notice: You need C compiler to build geth

make geth

Build Node

cd ~/.morph/morph/node 
make build

Sync from the genesis block

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

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

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

cd ~/.morph

## mainnet
export CHAIN_ID=1
export L1MESSAGEQUEUE_CONTRACT=0x3931ade842f5bb8763164bdd81e5361dce6cc1ef
export START_HEIGHT=20996776
export ROLLUP=0x759894ced0e6af42c26668076ffa84d02e3cef60

## 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 ${START_HEIGHT} \
--derivation.rollupAddress ${ROLLUP} \
--derivation.startHeight ${START_HEIGHT} \
--derivation.fetchBlockRange 200 \
--log.filename ./node.log

For holesky network, using

export CHAIN_ID=17000 
export L1MESSAGEQUEUECONTRACT=0x778d1d9a4d8b6b9ade36d967a9ac19455ec3fd0b
export START_HEIGHT=1434640
export ROLLUP=0xd8c5c541d56f59d65cf775de928ccf4a47d4985c
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.