External Node Deployment

Deployment Overview

  • Recommended configuration for mainnet, can appropriately downgrade for testnet.

  • For mainnet: Ensure Docker's home directory is set to the maximum available disk space and configure disk usage alerts.

  • For mainnet: Choose the Singapore region as the deployment location for your synchronization nodes.

ServerComponentsRecommended hardwareComments
  • Database

  • Sync Server

  • state-db, prover-db

  • sync, prover

  • CPU: 64VCORE, >= 3050MHZ

  • RAM: 128G

  • DISK: 2000GB, NVMe SSD

Synchronize the L2 state from master node and L1, only one can run at a time.

  • RPC Server(s)

  • json-rpc, prover

  • CPU: 64VCORE, >= 3050MHZ

  • RAM: 128G

  • DISK: 2000GB, NVMe SSD

Provide access entry for external requests, can run multiple instances.

Deployment Steps

Prepare configuration files

docker-compose.yml

If you have modified the default configuration of the database container [merlin-state-db], please synchronize this configuration with [StateDB] in node.config.toml and databaseURL in prover.config.json.

version: "3.5"
networks:
  default:
    name: merlin-rpc-network

services:
  cdk-validium-state-db:
    container_name: merlin-state-db
    restart: unless-stopped
    image: postgres:15
    cpu_shares: 4096
    ports:
      - 5432:5432
    environment:
      - POSTGRES_USER=state_user
      - POSTGRES_PASSWORD=state_password
      - POSTGRES_DB=state_db
    volumes:
      - ./init_prover_db.sql:/docker-entrypoint-initdb.d/init.sql
      - /data/merlin/data/statedb:/var/lib/postgresql/data
    command:
      - "postgres"
      - "-N"
      - "500"
      - "-c"
      - "max_connections=20000"
      - "-c"
      - "shared_buffers=16GB"
      - "-c"
      - "work_mem=32MB"
      - "-c"
      - "wal_buffers=8MB"
      - "-c"
      - "effective_cache_size=16GB"
      - "-c"
      - "maintenance_work_mem=128MB"
      - "-c"
      - "checkpoint_completion_target=0.7"
      - "-c"
      - "max_wal_size=4GB"
      - "-c"
      - "min_wal_size=200MB"
      - "-c"
      - "max_worker_processes=16"

  cdk-validium-pool-db:
    container_name: merlin-pool-db
    restart: unless-stopped
    image: postgres:15
    cpu_shares: 4096
    ports:
      - 5433:5432
    environment:
      - POSTGRES_USER=pool_user
      - POSTGRES_PASSWORD=pool_password
      - POSTGRES_DB=pool_db
    volumes:
      - /data/merlin/data/pooldb:/var/lib/postgresql/data
    command:
      - "postgres"
      - "-N"
      - "500"
      - "-c"
      - "max_connections=10000"

  cdk-validium-prover:
    container_name: merlin-prover
    image: merlinadmin/zkprover:v1.0.0
    restart: unless-stopped
    logging:
      options:
        max-size: '500m'
        max-file: '3'
    ports:
      - 50061:50061 # MT
      - 50071:50071 # Executor
    volumes:
      - ./prover.config.json:/usr/src/app/config.json
    command: >
      zkProver -c /usr/src/app/config.json

  cdk-validium-sync:
    container_name: merlin-sync
    image: merlinadmin/zkevm-node:v1.2.3
    restart: unless-stopped
    logging:
      options:
        max-size: '500m'
        max-file: '3'
    ports:
      - 9091:9091
    volumes:
      - ./node.config.toml:/app/config.toml
      - ./genesis.json:/app/genesis.json
    command:
      - "/bin/sh"
      - "-c"
      - "/app/cdk-validium-node run --network custom --custom-network-file /app/genesis.json --cfg /app/config.toml --components synchronizer"

  cdk-validium-json-rpc:
    container_name: merlin-rpc
    image: merlinadmin/zkevm-node:v1.2.3
    restart: unless-stopped
    logging:
      options:
        max-size: '500m'
        max-file: '5'
    ports:
      - 8123:8123
      #- 8133:8133 # needed if WebSockets enabled
      #- 9092:9091 # needed if metrics enabled
    volumes:
      - ./node.config.toml:/app/config.toml
      - ./genesis.json:/app/genesis.json
    command:
      - "/bin/sh"
      - "-c"
      - "/app/cdk-validium-node run --network custom --custom-network-file /app/genesis.json --cfg /app/config.toml --components rpc --http.api eth,net,debug,zkevm,txpool,web3" #with debug mode. delete "--http.api eth,net,debug,zkevm,txpool,web3" will remove debug endpoint.

init_prover_db.sql

CREATE DATABASE prover_db;
\connect prover_db;

CREATE SCHEMA state;

CREATE TABLE state.nodes (hash BYTEA PRIMARY KEY, data BYTEA NOT NULL);
CREATE TABLE state.program (hash BYTEA PRIMARY KEY, data BYTEA NOT NULL);

CREATE USER prover_user with password 'prover_password';
ALTER DATABASE prover_db OWNER TO prover_user;
ALTER SCHEMA state OWNER TO prover_user;
ALTER SCHEMA public OWNER TO prover_user;
ALTER TABLE state.nodes OWNER TO prover_user;
ALTER TABLE state.program OWNER TO prover_user;
ALTER USER prover_user SET SEARCH_PATH=state;

node.config.toml

Update [StateDB] if you use a customized database configuration.

IsTrustedSequencer = false

[Log]
Environment = "production" # "production" or "development"
Level = "info"
Outputs = ["stderr"]

[Synchronizer]
SyncInterval = "1s"
SyncChunkSize = 300
SyncOnlyTrusted = true
TrustedSequencerURL = "https://rpc.merlinchain.io" # L2 rpc, for testnet: https://testnet-rpc.merlinchain.io

[Etherman]
URL = "http://18.142.49.94:8545" # L1 rpc, for testnet: http://61.10.9.18:7545
ForkIDChunkSize = 20000
MultiGasProvider = false

[RPC]
Host = "0.0.0.0"
Port = 8123
ReadTimeout = "60s"
WriteTimeout = "60s"
MaxRequestsPerIPAndSecond = 100
SequencerNodeURI = "https://rpc.merlinchain.io" # L2 rpc, for testnet: https://testnet-rpc.merlinchain.io
EnableL2SuggestedGasPricePolling = false
TraceBatchUseHTTPS = true
    [RPC.WebSockets]
    Enabled = true
    Host = "0.0.0.0"
    Port = 8133

[StateDB]
User = "state_user"
Password = "state_password"
Name = "state_db"
Host = "merlin-state-db"
Port = "5432"
EnableLog = false
MaxConns = 800

[Pool]
DefaultMinGasPriceAllowed = 50000000

[Pool.DB]
User = "pool_user"
Password = "pool_password"
Name = "pool_db"
Host = "merlin-pool-db"
Port = "5432"
EnableLog = false
MaxConns = 800

[Metrics]
Host = "0.0.0.0"
Port = 9091
Enabled = true
ProfilingHost = "0.0.0.0"
ProfilingPort = 6060
ProfilingEnabled = true

[MTClient]
URI  = "cdk-validium-prover:50061"

[Executor]
URI = "cdk-validium-prover:50071"
MaxResourceExhaustedAttempts = 5
WaitOnResourceExhaustion = "1s"
MaxGRPCMessageSize = 100000000

prover.config.json

Update databaseURL if you use a customized database configuration.

{
  "runExecutorServer": true,
  "runExecutorClient": false,
  "runExecutorClientMultithread": false,

  "runHashDBServer": true,
  "runHashDBTest": false,

  "runAggregatorServer": false,
  "runAggregatorClient": false,

  "runFileGenBatchProof": false,
  "runFileGenAggregatedProof": false,
  "runFileGenFinalProof": false,
  "runFileProcessBatch": false,
  "runFileProcessBatchMultithread": false,
  "runFileExecutor": false,

  "runKeccakScriptGenerator": false,
  "runKeccakTest": false,
  "runStorageSMTest": false,
  "runBinarySMTest": false,
  "runMemAlignSMTest": false,
  "runSHA256Test": false,
  "runBlakeTest": false,
  "runECRecoverTest": false,

  "executeInParallel": true,
  "useMainExecGenerated": true,

  "saveRequestToFile": false,
  "saveInputToFile": false,
  "saveDbReadsToFile": false,
  "saveDbReadsToFileOnChange": false,
  "saveOutputToFile": false,
  "saveProofToFile": false,
  "saveResponseToFile": false,
  "saveFilesInSubfolders": false,

  "loadDBToMemCache": true,
  "loadDBToMemCacheInParallel": false,
  "loadDBToMemTimeout": 30000000,
  "dbMTCacheSize": 6144,
  "dbProgramCacheSize": 6144,

  "opcodeTracer": false,
  "logRemoteDbReads": false,
  "logExecutorServerInput": false,
  "logExecutorServerInputGasThreshold": 0,
  "logExecutorServerResponses": false,
  "logExecutorServerTxs": true,

  "executorServerPort": 50071,
  "executorROMLineTraces": false,
  "executorTimeStatistics": false,
  "executorClientPort": 50071,
  "executorClientHost": "127.0.0.1",
  "executorClientLoops": 1,

  "hashDBServerPort": 50061,
  "hashDBURL": "local",
  "dbCacheSynchURL_disabled": "127.0.0.1:50061",

  "aggregatorServerPort": 50081,
  "aggregatorClientPort": 50081,
  "aggregatorClientHost": "127.0.0.1",
  "aggregatorClientMockTimeout": 10000000,
  "aggregatorClientWatchdogTimeout": 60000000,

  "mapConstPolsFile": false,
  "mapConstantsTreeFile": false,

  "inputFile": "testvectors/aggregatedProof/recursive1.zkin.proof_0.json",
  "inputFile2": "testvectors/aggregatedProof/recursive1.zkin.proof_1.json",

  "outputPath": "runtime/output",
  "configPath": "config",

  "zkevmCmPols_disabled": "runtime/zkevm.commit",
  "zkevmCmPolsAfterExecutor_disabled": "runtime/zkevm.commit",
  "c12aCmPols": "runtime/c12a.commit",
  "recursive1CmPols_disabled": "runtime/recursive1.commit",
  "recursive2CmPols_disabled": "runtime/recursive2.commit",
  "recursivefCmPols_disabled": "runtime/recursivef.commit",
  "finalCmPols_disabled": "runtime/final.commit",
  "publicsOutput": "public.json",
  "proofFile": "proof.json",

  "databaseURL": "postgresql://prover_user:prover_password@merlin-state-db:5432/prover_db",
  "dbNodesTableName": "state.nodes",
  "dbProgramTableName": "state.program",
  "dbMultiWrite": true,
  "dbConnectionsPool": true,
  "dbNumberOfPoolConnections": 1000,
  "dbMetrics": false,
  "dbClearCache": false,
  "dbGetTree": false,
  "dbReadRetryDelay": 100000,
  "cleanerPollingPeriod": 600,
  "requestsPersistence": 3600,
  "maxExecutorThreads": 500,
  "maxProverThreads": 20,
  "maxHashDBThreads": 120,

  "fullTracerTraceReserveSize": 262144
}

genesis.json

  • testnet

https://download.merlinchain.io/merlin/testnet/genesis.json
  • mainnet

https://download.merlinchain.io/merlin/mainnet/genesis.json

Launch database

Database Server

docker compose up -d cdk-validium-state-db

Snapshot Recovery

  • Install postgresql-client-15

# Install dependency
sudo apt install gnupg2 wget vim -y
# Enable official package repo
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
# Import GPG signing key
wget -qO- https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo tee /etc/apt/trusted.gpg.d/pgdg.asc &>/dev/null
# Update system packages
sudo apt update -y
# Install postgres client
sudo apt install postgresql-client-15 -y
  • Download recovery database snapshot

# Confirm the creation date of below downloaded files; they must be on the same day.
# Testnet
wget https://rpc-snapshot.merlinchain.io/testnet_state_db.sql.tar.gz
wget https://rpc-snapshot.merlinchain.io/testnet_prover_db.sql.tar.gz

# Mainnet
wget https://rpc-snapshot.merlinchain.io/state_db.sql.tar.gz
wget https://rpc-snapshot.merlinchain.io/prover_db.sql.tar.gz
  • Prepare recovery script

git clone https://github.com/0xPolygon/cdk-validium-node.git
cd cdk-validium-node
# Recommend using Go 1.21 or a later version
go build -o ./build ./cmd
  • Prepare recovery configuration

# touch a config file named "snapshot_restore.toml"
vim snapshot_restore.toml
# update the database connection info based on your "docker-compose.yml"
[Log]
Environment = "production" # "production" or "development"
Level = "debug" # "info"
Outputs = ["stderr"]

[StateDB]
User = "state_user"
Password = "state_password"
Name = "state_db"
Host = "127.0.0.1"
Port = "5432"
EnableLog = false
MaxConns = 200

[HashDB]
User = "prover_user"
Password = "prover_password"
Name = "prover_db"
Host = "127.0.0.1"
Port = "5432"
EnableLog = false
MaxConns = 200
# Add a logic host to /etc/hosts
127.0.0.1  zkevm-state-db  ## change ip to your database IP
  • Execute recovery

# Assuming you are currently under previous Git directory, and the snapshot recovery file you downloaded is also in the current directory (or you can manually specify the location), execute the following command for recovery:
./build restore --cfg ./snapshot_restore.toml -is ./state_db.sql.tar.gz -ih ./prover_db.sql.tar.gz
  • Add extra index for state db

CREATE INDEX IF NOT EXISTS l2block_block_hash_idx ON state.l2block (block_hash);
CREATE INDEX IF NOT EXISTS l2block_created_at_idx ON state.l2block (created_at);
CREATE INDEX IF NOT EXISTS log_log_index_idx ON state.log (log_index);
CREATE INDEX IF NOT EXISTS log_topic0_idx ON state.log (topic0);
CREATE INDEX IF NOT EXISTS log_topic1_idx ON state.log (topic1);
CREATE INDEX IF NOT EXISTS log_topic2_idx ON state.log (topic2);
CREATE INDEX IF NOT EXISTS log_topic3_idx ON state.log (topic3);
CREATE INDEX IF NOT EXISTS idx_receipt_tx_index ON state.receipt (block_num, tx_index);
CREATE INDEX IF NOT EXISTS receipt_block_num_idx ON state.receipt USING btree (block_num);

Launch other containers

Sync Server

docker compose up -d cdk-validium-prover
docker compose up -d cdk-validium-pool-db
sleep 5
docker compose up -d cdk-validium-sync

RPC Server

docker compose up -d cdk-validium-prover
docker compose up -d cdk-validium-pool-db
sleep 5
docker compose up -d cdk-validium-json-rpc

Installation Verification

On the RPC host, execute the following command:

curl --location 'http://localhost:8123' \
    --header 'Content-Type: application/json' \
    --data '{
    "jsonrpc": "2.0",
    "method": "eth_blockNumber",
    "params": [],
    "id": 1
}'

The expected result should be a valid block number:

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

Troubleshooting

  • Please note that there should be no indication of port occupation when checking the port mappings for Docker.

  • Ensure that in all your configuration files, the databases have the correct host / user / password value. You can test connectivity using the command line before proceeding with the installation.

Commercial RPC nodes

For reference, you can also subscribe to the following third-party RPC vendors.

Last updated