Skip to main content

Validator Node Setup Guide

Comprehensive guide to running a validator node on Nexis Appchain. Validators secure the network, propose blocks, and earn rewards for their participation.

What is a Validator Node?

Validators on Nexis perform critical network functions:

Block Validation

Verify transactions and state transitions

Network Security

Secure the network through stake-backed consensus

Earn Rewards

Receive block rewards and transaction fees

Governance

Participate in network governance decisions

Validator vs RPC Node

FeatureValidator NodeRPC Node
PurposeValidate blocks, earn rewardsServe RPC requests
Stake Required10,000 NZT minimumNone
Slashing RiskYesNo
Block ProductionYesNo
Uptime Requirement99%+95%+
RewardsBlock rewards + feesNone
HardwareHigh-performanceMedium-performance

Staking Requirements

Minimum Stake

NetworkMinimum StakeCurrent APY
Testnet1,000 NZTN/A (testing)
Mainnet10,000 NZT8-12% (estimated)

Economic Model

  • Block Rewards: 2 NZT per block
  • Transaction Fees: Variable (base fee + priority fee)
  • Slashing: Up to 100% for malicious behavior
  • Validator Set: Top 100 by stake (initially)

Hardware Requirements

Minimum Requirements

ComponentMinimumRecommendedNotes
CPU8 cores16+ coresModern Intel Xeon or AMD EPYC
RAM32 GB64+ GBDDR4/DDR5 ECC recommended
Storage1 TB NVMe2+ TB NVMeHigh IOPS critical
Network500 Mbps1+ GbpsSymmetric up/down
Bandwidth5 TB/monthUnlimitedMonitor closely
# Production validator server
CPU: AMD EPYC 7443P (24 cores @ 2.85 GHz)
RAM: 128 GB DDR4 ECC
Storage: 2x 2TB NVMe in RAID 1
Network: 10 Gbps dedicated
Operating System: Ubuntu 22.04 LTS

Cloud Provider Options

AWS:
Instance: c6i.8xlarge
vCPUs: 32
RAM: 64 GB
Storage: 2TB gp3 (16,000 IOPS)
Cost: ~$1,200/month
Google Cloud:
Instance: c2-standard-30
vCPUs: 30
RAM: 120 GB
Storage: 2TB SSD persistent disk
Cost: ~$1,400/month
Hetzner (Cost-effective):
Server: AX102
CPU: AMD Ryzen 9 5950X (16 cores)
RAM: 128 GB DDR4 ECC
Storage: 2x 3.84TB NVMe
Cost: ~$200/month

Architecture Overview

Installation

1. System Preparation

# Update system
sudo apt update && sudo apt upgrade -y

# Install dependencies
sudo apt install -y \
  build-essential \
  git \
  curl \
  wget \
  jq \
  make \
  gcc \
  libc6-dev \
  golang-go \
  ca-certificates \
  gnupg \
  lsb-release

# Install Docker (optional but recommended)
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

# Install Docker Compose
sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

# Create nexis user
sudo useradd -m -s /bin/bash nexis
sudo usermod -aG docker nexis

# Setup directories
sudo mkdir -p /opt/nexis/{data,config,logs}
sudo chown -R nexis:nexis /opt/nexis

2. Install op-geth (Execution Client)

# Clone op-geth
cd /opt/nexis
git clone https://github.com/ethereum-optimism/op-geth.git
cd op-geth
git checkout v1.101315.1 # Use latest stable

# Build
make geth

# Copy binary
sudo cp build/bin/geth /usr/local/bin/op-geth

# Verify installation
op-geth version

3. Install op-node (Consensus Client)

# Clone optimism monorepo
cd /opt/nexis
git clone https://github.com/ethereum-optimism/optimism.git
cd optimism
git checkout op-node/v1.7.0 # Use latest stable

# Build op-node
cd op-node
make op-node

# Copy binary
sudo cp bin/op-node /usr/local/bin/

# Verify installation
op-node --version

4. Configuration

op-geth Configuration

# /opt/nexis/config/geth.toml
cat > /opt/nexis/config/geth.toml << 'EOF'
[Eth]
NetworkId = 84532
SyncMode = "snap"
NoPruning = false

[Eth.Miner]
Etherbase = "YOUR_VALIDATOR_ADDRESS"
GasFloor = 30000000
GasCeil = 30000000

[Node]
DataDir = "/opt/nexis/data/geth"
HTTPHost = "0.0.0.0"
HTTPPort = 8545
HTTPVirtualHosts = ["*"]
HTTPModules = ["eth", "net", "web3", "debug", "txpool"]

WSHost = "0.0.0.0"
WSPort = 8546
WSModules = ["eth", "net", "web3"]

[Node.P2P]
MaxPeers = 50
NoDiscovery = false
ListenAddr = ":30303"

[Metrics]
Enabled = true
HTTP = "0.0.0.0"
Port = 6060
EOF

op-node Configuration

# /opt/nexis/config/node.env
cat > /opt/nexis/config/node.env << 'EOF'
# Network Configuration
OP_NODE_NETWORK=nexis-testnet
OP_NODE_L1_ETH_RPC=https://sepolia.base.org
OP_NODE_L1_BEACON=https://sepolia-beacon.base.org

# L2 Configuration
OP_NODE_L2_ENGINE_RPC=http://localhost:8551
OP_NODE_L2_ENGINE_AUTH=/opt/nexis/data/geth/geth/jwtsecret

# Rollup Configuration
OP_NODE_ROLLUP_CONFIG=/opt/nexis/config/rollup.json

# P2P Configuration
OP_NODE_P2P_LISTEN_IP=0.0.0.0
OP_NODE_P2P_LISTEN_TCP_PORT=9222
OP_NODE_P2P_LISTEN_UDP_PORT=9222
OP_NODE_P2P_BOOTNODES=<BOOTNODE_ENRS>

# Sequencer Mode (Enable for validator)
OP_NODE_SEQUENCER_ENABLED=true
OP_NODE_SEQUENCER_L1_CONFS=4

# RPC Configuration
OP_NODE_RPC_ADDR=0.0.0.0
OP_NODE_RPC_PORT=9545

# Metrics
OP_NODE_METRICS_ENABLED=true
OP_NODE_METRICS_ADDR=0.0.0.0
OP_NODE_METRICS_PORT=7300

# Logging
OP_NODE_LOG_LEVEL=info
EOF

Rollup Configuration

# /opt/nexis/config/rollup.json
cat > /opt/nexis/config/rollup.json << 'EOF'
{
  "genesis": {
    "l1": {
      "hash": "0x...",
      "number": 1234567
    },
    "l2": {
      "hash": "0x...",
      "number": 0
    },
    "l2_time": 1234567890,
    "system_config": {
      "batcherAddr": "0x...",
      "overhead": "0x834",
      "scalar": "0xf4240",
      "gasLimit": 30000000
    }
  },
  "block_time": 2,
  "max_sequencer_drift": 600,
  "seq_window_size": 3600,
  "channel_timeout": 300,
  "l1_chain_id": 84532,
  "l2_chain_id": 84532,
  "regolith_time": 0,
  "canyon_time": 0,
  "delta_time": 0,
  "ecotone_time": 0,
  "batch_inbox_address": "0xff...",
  "deposit_contract_address": "0x...",
  "l1_system_config_address": "0x..."
}
EOF

5. Generate JWT Secret

# Generate JWT secret for Engine API
openssl rand -hex 32 > /opt/nexis/data/geth/geth/jwtsecret
chmod 600 /opt/nexis/data/geth/geth/jwtsecret

6. Initialize Genesis

# Download genesis file
curl -o /opt/nexis/config/genesis.json \
  https://raw.githubusercontent.com/nexis-network/nexis-base-appchain/main/genesis.json

# Initialize op-geth
op-geth init \
  --datadir /opt/nexis/data/geth \
  /opt/nexis/config/genesis.json

Running the Validator

Using Systemd Services

op-geth Service

# /etc/systemd/system/op-geth.service
sudo tee /etc/systemd/system/op-geth.service > /dev/null << 'EOF'
[Unit]
Description=Optimism Execution Client (op-geth)
After=network.target
Wants=network.target

[Service]
Type=simple
User=nexis
Group=nexis
Restart=always
RestartSec=5
TimeoutStopSec=300

ExecStart=/usr/local/bin/op-geth \
  --config /opt/nexis/config/geth.toml \
  --datadir /opt/nexis/data/geth \
  --authrpc.addr 0.0.0.0 \
  --authrpc.port 8551 \
  --authrpc.jwtsecret /opt/nexis/data/geth/geth/jwtsecret \
  --rollup.sequencerhttp https://sequencer.nex-t1.ai \
  --rollup.disabletxpoolgossip=false

StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
EOF

op-node Service

# /etc/systemd/system/op-node.service
sudo tee /etc/systemd/system/op-node.service > /dev/null << 'EOF'
[Unit]
Description=Optimism Consensus Client (op-node)
After=network.target op-geth.service
Wants=network.target
Requires=op-geth.service

[Service]
Type=simple
User=nexis
Group=nexis
Restart=always
RestartSec=5
TimeoutStopSec=120

EnvironmentFile=/opt/nexis/config/node.env

ExecStart=/usr/local/bin/op-node

StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
EOF

Enable and Start Services

# Reload systemd
sudo systemctl daemon-reload

# Enable services
sudo systemctl enable op-geth
sudo systemctl enable op-node

# Start services
sudo systemctl start op-geth
sudo systemctl start op-node

# Check status
sudo systemctl status op-geth
sudo systemctl status op-node

# View logs
sudo journalctl -u op-geth -f
sudo journalctl -u op-node -f

Using Docker Compose

# /opt/nexis/docker-compose.yml
version: '3.8'

services:
  geth:
    image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-geth:v1.101315.1
    container_name: nexis-geth
    restart: unless-stopped
    ports:
      - "8545:8545"
      - "8546:8546"
      - "8551:8551"
      - "30303:30303"
      - "30303:30303/udp"
    volumes:
      - ./data/geth:/data
      - ./config/geth.toml:/config/geth.toml:ro
      - ./data/geth/geth/jwtsecret:/jwtsecret:ro
    command:
      - --config=/config/geth.toml
      - --datadir=/data
      - --authrpc.addr=0.0.0.0
      - --authrpc.port=8551
      - --authrpc.jwtsecret=/jwtsecret
      - --rollup.sequencerhttp=https://sequencer.nex-t1.ai
    logging:
      driver: "json-file"
      options:
        max-size: "100m"
        max-file: "10"

  node:
    image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-node:v1.7.0
    container_name: nexis-node
    restart: unless-stopped
    depends_on:
      - geth
    ports:
      - "9222:9222"
      - "9222:9222/udp"
      - "9545:9545"
      - "7300:7300"
    volumes:
      - ./config/rollup.json:/rollup.json:ro
      - ./data/geth/geth/jwtsecret:/jwtsecret:ro
    env_file:
      - ./config/node.env
    logging:
      driver: "json-file"
      options:
        max-size: "100m"
        max-file: "10"

  prometheus:
    image: prom/prometheus:latest
    container_name: nexis-prometheus
    restart: unless-stopped
    ports:
      - "9090:9090"
    volumes:
      - ./config/prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - ./data/prometheus:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--storage.tsdb.retention.time=30d'

  grafana:
    image: grafana/grafana:latest
    container_name: nexis-grafana
    restart: unless-stopped
    ports:
      - "3000:3000"
    volumes:
      - ./data/grafana:/var/lib/grafana
      - ./config/grafana-dashboards:/etc/grafana/provisioning/dashboards:ro
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
      - GF_INSTALL_PLUGINS=grafana-clock-panel

networks:
  default:
    name: nexis-network
# Start with Docker Compose
cd /opt/nexis
docker-compose up -d

# View logs
docker-compose logs -f geth
docker-compose logs -f node

# Stop
docker-compose down

Staking Your Validator

1. Prepare Your Wallet

// stake-validator.js
const { ethers } = require('ethers');

async function stakeValidator() {
  const provider = new ethers.JsonRpcProvider('https://testnet-rpc.nex-t1.ai');
  const wallet = new ethers.Wallet(process.env.VALIDATOR_PRIVATE_KEY, provider);

  const AGENTS_CONTRACT = '0x1234567890123456789012345678901234567890';
  const AGENTS_ABI = [...]; // Import from @nexis-network/contracts

  const agents = new ethers.Contract(AGENTS_CONTRACT, AGENTS_ABI, wallet);

  // Register validator as an agent
  const agentId = ethers.keccak256(ethers.toUtf8Bytes('validator-node-1'));

  const metadata = {
    type: 'validator',
    name: 'My Validator Node',
    location: 'US-East-1',
    operator: wallet.address,
  };

  const metadataURI = await uploadToIPFS(metadata);

  console.log('Registering validator agent...');
  const registerTx = await agents.register(
    agentId,
    metadataURI,
    'https://validator.example.com'
  );
  await registerTx.wait();

  // Stake minimum amount
  const stakeAmount = ethers.parseEther('10000'); // 10,000 NZT for mainnet

  console.log('Staking', ethers.formatEther(stakeAmount), 'NZT...');
  const stakeTx = await agents.stakeETH(agentId, {
    value: stakeAmount,
  });
  await stakeTx.wait();

  console.log('Validator staked successfully!');
  console.log('Agent ID:', agentId);
  console.log('Stake:', ethers.formatEther(stakeAmount), 'NZT');

  return agentId;
}

stakeValidator().catch(console.error);
# Run staking script
VALIDATOR_PRIVATE_KEY=0xYourPrivateKey node stake-validator.js

2. Configure Validator Key

# Import validator private key into op-geth
op-geth account import \
  --datadir /opt/nexis/data/geth \
  --password /opt/nexis/config/password.txt \
  /path/to/validator-key.txt

# Update geth.toml with validator address
sed -i 's/YOUR_VALIDATOR_ADDRESS/0xYourValidatorAddress/' \
  /opt/nexis/config/geth.toml

# Restart services
sudo systemctl restart op-geth op-node

Monitoring

Prometheus Configuration

# /opt/nexis/config/prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'op-geth'
    static_configs:
      - targets: ['localhost:6060']

  - job_name: 'op-node'
    static_configs:
      - targets: ['localhost:7300']

  - job_name: 'node-exporter'
    static_configs:
      - targets: ['localhost:9100']

Key Metrics to Monitor

MetricTargetAlert Threshold
Sync StatusLatest block> 10 blocks behind
Peer Count25+ peers< 5 peers
CPU Usage< 80%> 90% for 5 min
Memory Usage< 80%> 90% for 5 min
Disk Usage< 80%> 85%
Network In/OutStableSpikes or drops
Block Validation Time< 1s> 2s
Missed Blocks0> 1 per hour

Grafana Dashboards

# Import pre-built Nexis validator dashboard
curl -o /opt/nexis/config/grafana-dashboards/nexis-validator.json \
  https://raw.githubusercontent.com/nexis-network/monitoring/main/grafana/validator-dashboard.json

Alerting Rules

# /opt/nexis/config/alertmanager.yml
groups:
  - name: validator_alerts
    interval: 30s
    rules:
      - alert: ValidatorOutOfSync
        expr: op_node_sync_lag > 10
        for: 2m
        annotations:
          summary: "Validator is out of sync"
          description: "Validator is {{ $value }} blocks behind"

      - alert: LowPeerCount
        expr: op_node_peer_count < 5
        for: 5m
        annotations:
          summary: "Low peer count"
          description: "Only {{ $value }} peers connected"

      - alert: HighCPUUsage
        expr: node_cpu_usage > 90
        for: 5m
        annotations:
          summary: "High CPU usage"
          description: "CPU usage is {{ $value }}%"

      - alert: MissedBlocks
        expr: rate(validator_missed_blocks[1h]) > 0
        annotations:
          summary: "Validator missing blocks"
          description: "Validator has missed {{ $value }} blocks"

Maintenance

Regular Tasks

Daily:
  • Check sync status
  • Monitor resource usage
  • Review logs for errors
Weekly:
  • Update monitoring dashboards
  • Check disk space
  • Review performance metrics
Monthly:
  • Apply security updates
  • Review and optimize configuration
  • Check for software updates

Backup Strategies

#!/bin/bash
# /opt/nexis/scripts/backup.sh

BACKUP_DIR="/backup/nexis"
DATE=$(date +%Y%m%d_%H%M%S)

# Stop services
systemctl stop op-node op-geth

# Backup validator key
cp /opt/nexis/data/geth/keystore/* $BACKUP_DIR/keystore_$DATE/

# Backup configuration
tar -czf $BACKUP_DIR/config_$DATE.tar.gz /opt/nexis/config/

# Backup state (optional, very large)
# tar -czf $BACKUP_DIR/state_$DATE.tar.gz /opt/nexis/data/geth/geth/chaindata/

# Restart services
systemctl start op-geth op-node

# Upload to S3 or remote storage
# aws s3 sync $BACKUP_DIR s3://my-backup-bucket/nexis/

# Keep only last 7 days
find $BACKUP_DIR -mtime +7 -delete

echo "Backup completed: $BACKUP_DIR"

Upgrading

#!/bin/bash
# /opt/nexis/scripts/upgrade.sh

echo "Starting validator upgrade..."

# Stop services
sudo systemctl stop op-node op-geth

# Backup current binaries
sudo cp /usr/local/bin/op-geth /usr/local/bin/op-geth.backup
sudo cp /usr/local/bin/op-node /usr/local/bin/op-node.backup

# Pull latest code
cd /opt/nexis/op-geth
git fetch --all
git checkout v1.101315.2 # New version

# Rebuild
make geth
sudo cp build/bin/geth /usr/local/bin/op-geth

cd /opt/nexis/optimism/op-node
git fetch --all
git checkout op-node/v1.7.1 # New version

make op-node
sudo cp bin/op-node /usr/local/bin/

# Restart services
sudo systemctl start op-geth op-node

# Monitor for 10 minutes
echo "Monitoring for 10 minutes..."
for i in {1..10}; do
  echo "Minute $i/10"
  sudo systemctl status op-geth op-node
  sleep 60
done

echo "Upgrade completed successfully"

Troubleshooting

Node Not Syncing

# Check sync status
curl -X POST http://localhost:9545 \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"optimism_syncStatus","params":[],"id":1}'

# Check logs
sudo journalctl -u op-node -f | grep sync

# Verify L1 connection
curl -X POST $OP_NODE_L1_ETH_RPC \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'

# Reset if necessary (WARNING: Full resync)
sudo systemctl stop op-node op-geth
rm -rf /opt/nexis/data/geth/geth/chaindata/*
op-geth init --datadir /opt/nexis/data/geth /opt/nexis/config/genesis.json
sudo systemctl start op-geth op-node

Low Peer Count

# Add static peers
cat > /opt/nexis/config/static-nodes.json << 'EOF'
[
  "enode://abc...@1.2.3.4:30303",
  "enode://def...@5.6.7.8:30303"
]
EOF

# Update geth config to use static nodes
op-geth attach http://localhost:8545
> admin.addPeer("enode://...")

High Resource Usage

# Check resource usage
htop
iotop -o

# Optimize geth cache
# Edit geth.toml:
[Node.Eth]
DatabaseCache = 4096  # Increase cache
TrieCache = 1024
TrieTimeout = 60

# Restart
sudo systemctl restart op-geth

Validator Not Proposing Blocks

# Check validator key
op-geth account list --datadir /opt/nexis/data/geth

# Verify stake
curl -X POST http://localhost:8545 \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc":"2.0",
    "method":"eth_call",
    "params":[{
      "to":"0x1234...",
      "data":"0x..."
    },"latest"],
    "id":1
  }'

# Check sequencer mode
grep SEQUENCER_ENABLED /opt/nexis/config/node.env

Security Best Practices

Firewall Configuration

# Install UFW
sudo apt install ufw

# Default policies
sudo ufw default deny incoming
sudo ufw default allow outgoing

# Allow SSH (change port if needed)
sudo ufw allow 22/tcp

# Allow P2P
sudo ufw allow 30303/tcp
sudo ufw allow 30303/udp
sudo ufw allow 9222/tcp
sudo ufw allow 9222/udp

# Allow monitoring (restrict to internal network)
sudo ufw allow from 10.0.0.0/8 to any port 9090
sudo ufw allow from 10.0.0.0/8 to any port 3000

# Enable firewall
sudo ufw enable

SSH Hardening

# /etc/ssh/sshd_config
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
Port 2222  # Non-standard port

# Restart SSH
sudo systemctl restart sshd

Key Management

# Never store private keys in plain text
# Use hardware security modules (HSM) for production

# Encrypt keystore
gpg --symmetric --cipher-algo AES256 validator-key.json

# Secure permissions
chmod 600 /opt/nexis/data/geth/keystore/*
chown nexis:nexis /opt/nexis/data/geth/keystore/*

Validator Economics

Revenue Streams

Block Rewards:
  • 2 NZT per block
  • ~43,200 blocks per day (2s block time)
  • ~86,400 NZT per day total network rewards
Transaction Fees:
  • Variable based on network usage
  • Average 0.1-0.5 NZT per block
  • ~4,320-21,600 NZT per day

Cost Analysis

Monthly Operating Costs:
Server (Hetzner AX102): $200
Bandwidth: $50
Monitoring services: $20
Backup storage: $30
Total: ~$300/month
Break-even Analysis:
Stake: 10,000 NZT
Expected APY: 10%
Annual rewards: 1,000 NZT
Monthly rewards: 83.33 NZT

At $10/NZT: $833/month revenue
Operating costs: $300/month
Net profit: $533/month (64% margin)

ROI: (533 * 12) / (10,000 * 10) = 6.4% net APY

Slashing Risks

ViolationPenaltyPrevention
Double signing100% stakeUse single validator key
Long downtime0.1% per dayMonitor uptime (99%+)
Invalid attestation1-10%Keep node synced
Sync issues0.01% per hourMonitor sync status

Resources


Mainnet validators: Ensure you test extensively on testnet before staking on mainnet. Slashing penalties can result in significant loss of stake.
Join the Validators Discord channel to connect with other node operators and stay updated on network changes.
Validator rewards and economics are subject to change based on network governance decisions. Always check the latest documentation before staking.