Validator

Validator Documentation

Complete technical guide to deploying and operating a QELT Mainnet validator node. Covers the installer script, QBFT admission process, security hardening, operational commands, and troubleshooting.

Overview

QELT uses QBFT (Quorum Byzantine Fault Tolerance) with block-header validator selection. Validator admission is governed by on-chain voting — there is no smart contract involved. The protocol is built into the consensus engine itself.

The validator installer is a single idempotent bash script that sets up everything needed to run a production QELT validator node on Ubuntu 22.04/24.04 LTS. It installs Java 21, Hyperledger Besu 25.12.0, configures the genesis, generates keys, sets up systemd, and optionally creates a public HTTPS RPC endpoint.

5
Current Validators
3 of 5
Votes to Admit
30,000
Epoch Length (blocks)
~41.7h
Epoch Duration

How Admission Works

New Operator                      Existing Validators              Blockchain
    |                                     |                           |
    +-- Runs installer script             |                           |
    +-- Node syncs to chain tip           |                           |
    +-- Shares validator address -------->|                           |
    |                                     |                           |
    |                    Validator A calls |                           |
    |                    proposeValidatorVote ---------------------->  |
    |                                     |     Vote in block header  |
    |                    Validator B calls |                           |
    |                    proposeValidatorVote ---------------------->  |
    |                                     |     Vote in block header  |
    |                    Validator C calls |                           |
    |                    proposeValidatorVote ---------------------->  |
    |                                     |     3/5 threshold met!    |
    |                                     |                           |
    |<------------- New validator now participates in consensus ------|

Vote Mechanism

qbft_proposeValidatorVote JSON-RPC call

Activation

When on-chain vote threshold is met (recorded in block headers)

Epoch Behavior

Resets unrecorded pending votes; recorded votes persist

Restart Required?

No — once voted in, your node participates automatically

Validator Rewards

QELT validators earn revenue exclusively from transaction fees. There is no block subsidy and no inflation — the fixed 10 billion QELT supply never grows from block rewards.

Reward Structure

ComponentValueNotes
Block Reward0 QELTNo block subsidy / no inflation
Transaction Fees100% to block proposerEntire fee goes to the validator that proposes the block
Base Fee0zeroBaseFee is enabled in genesis
Minimum Gas Price1,000 wei per gas unitConfigured via --min-gas-price=1000

How Validators Earn

When a user sends a transaction, they pay Gas Used × Gas Price (minimum 1,000 wei per gas unit). The entire fee goes to whichever validator proposes the block containing that transaction.

QBFT uses round-robin block proposing. Validators take turns proposing blocks every 5 seconds. With N validators, each validator proposes roughly 17,280 / N blocks per day (~17,280 blocks/day total at 5-second intervals).

Revenue depends entirely on network activity. More transactions = more fees = more validator income. Empty blocks produce zero revenue.

Current Math (5 Validators)

Blocks per day:           17,280  (86,400 seconds / 5 seconds per block)
Blocks per validator:     3,456   (17,280 / 5 validators)

Example — simple QELT transfer:
  Gas used:               21,000
  Gas price (minimum):    1,000 wei
  Fee per tx:             21,000 × 1,000 = 21,000,000 wei = 0.000000021 QELT

Revenue per block = sum of all transaction fees in that block
Revenue per day   = sum of fees across all ~3,456 blocks you propose

Key Points

No Staking Requirement

QELT is a permissioned QBFT network, not Proof of Stake. There is no capital lock-up.

No Block Reward Inflation

The 10 billion QELT supply does not grow from block rewards. Supply is fixed.

Revenue Scales with Adoption

As more dApps deploy and users transact, validator fee revenue increases proportionally.

Equal Opportunity

All validators earn roughly equally due to round-robin block proposing. More validators = smaller share per validator.

Server Requirements

You need a dedicated server (VPS or bare-metal) running Ubuntu. The installer validates all requirements at startup and will abort with clear error messages if anything is missing.

RequirementMinimumRecommended
OSUbuntu 22.04 LTSUbuntu 24.04 LTS
Architecturex86_64x86_64
RAM8 GB16 GB
CPU4 cores8 cores
Storage100 GB SSD500 GB NVMe
Network100 Mbps, static IP1 Gbps
Ports30303 TCP+UDP open inboundSame

Software installed by the script: OpenJDK 21, Hyperledger Besu 25.12.0, curl, jq, openssl, and optionally nginx + Node.js + certbot for public RPC endpoints.

Installation

SSH into your server and run the following commands. The script requires root (sudo) and is interactive — it will prompt you for decisions at each step.

# Install git if not present
sudo apt update && sudo apt install -y git

# Clone the repository
git clone https://github.com/PRQELT/qelt-validator-node.git

# Enter the directory
cd qelt-validator-node

# Make the installer executable
chmod +x install-qelt-validator.sh

# Run the installer (requires root)
sudo bash install-qelt-validator.sh

Idempotent: The script is safe to re-run. It will NOT overwrite an existing node key unless you explicitly choose to. If Besu is already installed at the correct version, it skips the download.

Alternative one-liner (when available):

curl -sSL https://install.qelt.ai/validator.sh -o install.sh
chmod +x install.sh && sudo ./install.sh

Script Walkthrough (10 Steps)

The installer runs through 10 sequential steps. Here is exactly what each step does:

STEP 1/10

Preflight Checks

Validates your environment before making any changes:

  • • Running as root (required for systemd, package install, user creation)
  • • OS is Ubuntu 22.04 or 24.04, architecture is x86_64
  • • RAM ≥ 8 GB, available disk ≥ 50 GB
  • • Port 30303 is not already in use
  • • Internet connectivity — probes the bootnode RPC with eth_chainId to verify Chain ID 770

If any critical check fails, the script aborts with a clear error message.

STEP 2/10

Installing Dependencies

  • • System packages: curl, wget, jq, openssl, ca-certificates, gnupg, tar, unzip
  • OpenJDK 21 (headless) — the JVM runtime for Besu
  • • Verifies Java version after installation
STEP 3/10

Installing Hyperledger Besu 25.12.0

  • • Downloads from GitHub Releases: besu-25.12.0.tar.gz
  • SHA256 checksum verification against pinned hash — aborts on mismatch
  • • Extracts to /opt/besu/
  • • Creates symlink: /usr/local/bin/besu
  • • Skips download if correct version already installed
STEP 4/10

Creating System User and Directories

  • • Creates besu system user (non-login, no home directory)
  • • Creates directories: /data/qelt/, /data/qelt/keys/, /etc/qelt/
  • • Sets ownership to besu:besu, keys directory chmod 700
STEP 5/10

Deploying Genesis Configuration

  • • Writes production genesis to /etc/qelt/genesis.json
  • SHA256 verification ensures exact match with network
  • • Chain ID 770, QBFT consensus, 5-second block period, Cancun EVM
  • • The extraData field is RLP-encoded initial validator set — never modify
  • • If genesis already exists and matches, it is kept as-is
STEP 6/10

Validator Key Management

  • New key: Generates secp256k1 private key using openssl rand -hex 32
  • Import: Paste a hex-encoded private key or provide a file path
  • Existing key: Keep, replace, or import a new one
  • • Old keys always backed up before replacement
  • • Sets chmod 600 on key file, owned by besu user
  • • Extracts and displays your Validator Address
  +============================================================+
  |           YOUR VALIDATOR IDENTITY                          |
  +============================================================+
  | Validator Address: 0xYOUR_ADDRESS_HERE                     |
  |                                                            |
  | WARNING: SAVE THIS ADDRESS -- you will need it to request  |
  |          admission to the QELT validator set.              |
  +============================================================+
STEP 7/10

Configuring Systemd Service

  • • Auto-detects public IP (with manual override option)
  • • Writes static-nodes.json with bootnode enode URL
  • • Calculates JVM heap size based on available RAM (4/6/8 GB)
  • • RPC bound to 127.0.0.1:8545 (localhost only) with ETH, NET, QBFT, WEB3, TXPOOL APIs
  • • Metrics enabled on 127.0.0.1:9090
  • • Bonsai storage format, FULL sync mode
  • • Systemd security: ProtectHome, ProtectSystem=strict, NoNewPrivileges, PrivateTmp
  • • Service auto-restarts on failure with 10-second delay

Key Besu flags configured:

--data-path=/data/qelt
--genesis-file=/etc/qelt/genesis.json
--node-private-key-file=/data/qelt/keys/nodekey
--bootnodes=enode://710abc...@62.169.25.2:30303
--p2p-host=YOUR_PUBLIC_IP --p2p-port=30303
--discovery-enabled=true --max-peers=25
--rpc-http-enabled --rpc-http-host=127.0.0.1 --rpc-http-port=8545
--rpc-http-api=ETH,NET,QBFT,WEB3,TXPOOL
--metrics-enabled --metrics-port=9090
--data-storage-format=BONSAI --sync-mode=FULL
--target-gas-limit=50000000 --logging=INFO
STEP 8/10

Public HTTPS RPC (Optional)

If you choose to expose a public RPC endpoint, the script sets up a full production stack:

  • Node.js RPC middleware (port 8547) — method whitelisting, parameter validation, 1 MB body limit
  • nginx reverse proxy — rate limiting (100r/m + 20r/s burst), connection limits, security headers
  • Let's Encrypt SSL via certbot — automatic HTTPS with auto-renewal
  • • Architecture: Internet → nginx (443) → middleware (8547) → Besu (8545)
  • • Besu RPC never directly exposed to the internet

The middleware allows 30+ read-only RPC methods and blocks all admin/debug/write methods.

STEP 9/10

Firewall Configuration

  • Always: 30303/tcp, 30303/udp (P2P), 22/tcp (SSH)
  • If public RPC: Also 80/tcp (ACME validation), 443/tcp (HTTPS)
  • • Enables UFW if not already active
STEP 10/10

Starting and Verifying

  • • Starts the systemd service
  • • Waits 15 seconds for JVM initialization
  • • Verifies service is running
  • • Waits for RPC to become responsive (up to 60 seconds)
  • • Verifies Chain ID matches 770
  • • Checks peer count and reports sync status
  • • Starts RPC middleware (if configured)
  • • Displays complete summary with email template
  • • Saves details to /data/qelt/VALIDATOR_INFO.txt

Sync & Verification

CRITICAL: Your node MUST be fully synced before requesting admission.

When voted in, the total validator count increases. If your node is offline or not synced, it counts as a fault — potentially reducing the network's fault tolerance to zero, which could halt block production for the entire network.

Monitor Sync Progress

# Watch live logs
sudo journalctl -u besu-qelt-validator -f

# Check sync status (returns "false" when fully synced)
curl -s -X POST --data '{"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":1}' \
  http://127.0.0.1:8545 | jq .result

# Check current block number
curl -s -X POST --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
  http://127.0.0.1:8545 | jq -r .result

# Check peer count (should be >= 1)
curl -s -X POST --data '{"jsonrpc":"2.0","method":"net_peerCount","params":[],"id":1}' \
  http://127.0.0.1:8545 | jq -r .result

Compare with Network

# Compare your block height with the network
LOCAL=$(curl -s -X POST --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
  http://127.0.0.1:8545 | jq -r .result)
REMOTE=$(curl -s -X POST --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
  https://mainnet.qelt.ai | jq -r .result)
echo "Local: $LOCAL | Network: $REMOTE"

When both values match (or are within a few blocks), your node is fully synced.

View Your Validator Details

cat /data/qelt/VALIDATOR_INFO.txt
besu public-key export-address --node-private-key-file=/data/qelt/keys/nodekey

Validator Admission (For New Operators)

After your node is fully synced, follow these steps to get admitted to the validator set:

Step 1: Confirm Sync

Verify eth_syncing returns false and your block number matches the network.

Step 2: Pay Enrollment Fee

Send 100 USDT or USDC to 0x1ad873Bd75E5bb1a1a3933B3FC1E21F1Da986FA2 on any EVM chain.

Step 3: Email Your Information

Send the following to laurent@qxmp.ai:

  • • Payment transaction hash
  • • Validator Address (e.g., 0x1c0dffbe7183...)
  • • Enode URL (e.g., enode://abc...@84.247.133.98:30303)
  • • Confirmation that your node is fully synced

Find these values anytime: cat /data/qelt/VALIDATOR_INFO.txt

Step 4: Wait for Vote

The QELT team coordinates a vote among existing validators. QBFT requires a majority (currently 3 of 5) to vote for admission. This typically takes 24-48 hours.

Step 5: Verify Admission

Check if your address appears in the validator set:

curl -s -X POST --data '{"jsonrpc":"2.0","method":"qbft_getValidatorsByBlockNumber","params":["latest"],"id":1}' \
  http://127.0.0.1:8545 | jq .result

When your address appears — congratulations, you are a QELT validator! Your node begins participating in consensus automatically. No restart needed.

For Existing Validators (Voting)

This section is for existing QELT validators who need to vote on admitting a new validator.

Prerequisites

  • • Your node must have the QBFT API namespace enabled in --rpc-http-api
  • • You must have local RPC access (localhost:8545)

1. Verify the New Node is Ready

Before casting your vote, confirm: the new node is synced, has peers, and you have the correct validator address.

2. Cast Your Vote

# Replace NEW_VALIDATOR_ADDRESS with the actual address
curl -X POST --data '{
  "jsonrpc":"2.0",
  "method":"qbft_proposeValidatorVote",
  "params":["NEW_VALIDATOR_ADDRESS", true],
  "id":1
}' http://localhost:8545

Expected response: {"jsonrpc":"2.0","id":1,"result":true}

Important Notes About Voting

  • • The vote is NOT immediately written to the chain — it is stored locally and included in the next block YOUR node proposes
  • • QBFT uses round-robin block proposing, so your vote may take several blocks to appear on-chain
  • • You only need to call proposeValidatorVote once — the local proposal persists across epoch boundaries
  • • You need 3 of 5 existing validators to vote for the new address

3. Verify the Vote Passed

curl -s -X POST --data '{
  "jsonrpc":"2.0",
  "method":"qbft_getValidatorsByBlockNumber",
  "params":["latest"],
  "id":1
}' http://localhost:8545 | jq .result

4. Clean Up (Optional)

curl -X POST --data '{
  "jsonrpc":"2.0",
  "method":"qbft_discardValidatorVote",
  "params":["NEW_VALIDATOR_ADDRESS"],
  "id":1
}' http://localhost:8545

Check Pending Votes

curl -s -X POST --data '{
  "jsonrpc":"2.0",
  "method":"qbft_getPendingVotes",
  "params":[],
  "id":1
}' http://localhost:8545 | jq .result

Network Information

ParameterValue
Network NameQELT Mainnet
Chain ID770
ConsensusQBFT (Quorum Byzantine Fault Tolerance)
Block Time5 seconds
EVM VersionCancun
Block FinalityImmediate (no reorgs)
Gas Limit50,000,000
ClientHyperledger Besu 25.12.0
Epoch Length30,000 blocks (~41.7 hours)

Public RPC Endpoints

https://mainnet.qelt.ai       -- Validator 1 (Bootnode)
https://mainnet2.qelt.ai      -- Validator 2
https://mainnet3.qelt.ai      -- Validator 3
https://mainnet4.qelt.ai      -- Validator 4
https://mainnet5.qelt.ai      -- Validator 5
https://archivem.qelt.ai      -- Archive Node (full history)

Bootnode Enode

enode://710abc6491ff7de558de11d6835f64ca10ae3fd58b5a235d5cec068830fbd4e9568ec4e68293232a0a88f242fc7e81703827c9d90cad2bebb7a890cadb4220bc@62.169.25.2:30303

Common Commands

Service Management

sudo systemctl status besu-qelt-validator    # Check status
sudo systemctl restart besu-qelt-validator   # Restart
sudo systemctl stop besu-qelt-validator      # Stop
sudo journalctl -u besu-qelt-validator -f    # Live logs
sudo journalctl -u besu-qelt-validator --no-pager -n 50  # Last 50 lines

Network Queries (Local RPC)

# Current block number
curl -s -X POST --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
  http://127.0.0.1:8545 | jq -r .result

# Peer count
curl -s -X POST --data '{"jsonrpc":"2.0","method":"net_peerCount","params":[],"id":1}' \
  http://127.0.0.1:8545 | jq -r .result

# Sync status
curl -s -X POST --data '{"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":1}' \
  http://127.0.0.1:8545 | jq .result

# Chain ID
curl -s -X POST --data '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}' \
  http://127.0.0.1:8545 | jq -r .result

# Current validator set
curl -s -X POST --data '{"jsonrpc":"2.0","method":"qbft_getValidatorsByBlockNumber","params":["latest"],"id":1}' \
  http://127.0.0.1:8545 | jq .result

Key Management

# View your validator info
cat /data/qelt/VALIDATOR_INFO.txt

# Export validator address from key
besu public-key export-address --node-private-key-file=/data/qelt/keys/nodekey

# Export public key
besu public-key export --node-private-key-file=/data/qelt/keys/nodekey

Files & Directories

File / DirectoryPurpose
/data/qelt/Blockchain data directory
/data/qelt/keys/nodekeyYour validator private key (back this up!)
/data/qelt/static-nodes.jsonBootnode peer entry for resilient discovery
/data/qelt/VALIDATOR_INFO.txtSummary of your node details
/etc/qelt/genesis.jsonNetwork genesis configuration
/etc/systemd/system/besu-qelt-validator.serviceSystemd service unit
/opt/besu/Besu binary installation
/opt/qelt-rpc-validator/RPC middleware (if public RPC enabled)

Security

The installer implements production security best practices:

Dedicated System User

The node daemon runs as besu user, never as root

Systemd Sandboxing

ProtectHome, ProtectSystem=strict, NoNewPrivileges, PrivateTmp

Strict Key Permissions

chmod 600 on node key, chmod 700 on keys directory

RPC Localhost Only

Not exposed to the internet by default

SHA256 Verification

Both Besu binary and genesis file verified against pinned hashes

UFW Firewall

30303 TCP/UDP for P2P, SSH, and optionally 80/443

RPC Method Whitelisting

Node.js middleware blocks admin/debug methods if public RPC enabled

Nginx Rate Limiting

100r/m general + 20r/s burst with security headers

Protect Your Node Key

Your node key (/data/qelt/keys/nodekey) is your validator identity. Back it up securely — if lost, you lose your validator slot. Never share your private key.

Updating

Updating the Installer

cd /root/qelt-validator-node
git pull

Updating Besu

When a new Besu version is approved by the QELT team:

sudo systemctl stop besu-qelt-validator

# Download and extract the new version (update URL accordingly)
sudo rm -rf /opt/besu
sudo mkdir -p /opt/besu
sudo tar -xzf besu-NEW_VERSION.tar.gz -C /opt/besu --strip-components=1
sudo ln -sf /opt/besu/bin/besu /usr/local/bin/besu

# Verify
besu --version

# Restart
sudo systemctl start besu-qelt-validator

Always check Besu release notes for breaking changes before upgrading. Coordinate with the QELT team before updating.

Liveness & Safety Considerations

The 2/3 Rule

QBFT requires 2/3 or more of validators to sign each block. The network can tolerate up to floor((N-1)/3) faulty or offline nodes.

Total ValidatorsFault ToleranceMin SignersRisk if Adding Offline Node
514Adding 6th offline = fault tolerance consumed
614Only 1 more failure allowed before halt
725Safer — can tolerate 2 failures

Rule: NEVER vote in a node that is not fully synced and peered

If you increase the validator set size with a non-participating node, the fault tolerance does NOT increase. You have used up one fault slot on a node that cannot sign blocks. One more failure = network halt.

Recovery from a Stalled Network

If the network halts due to insufficient validators:

  1. Restart all available validator nodes
  2. Ensure P2P connectivity between all alive nodes
  3. Wait for consensus to resume (QBFT has backoff behavior)
  4. As a last resort, Besu supports genesis transition overrides to force a validator set change — this requires coordinated manual intervention across all nodes

Removing a Validator

If a validator needs to be removed (compromised key, operator leaves, accidental admission):

# Vote to REMOVE -- note the second parameter is "false"
curl -X POST --data '{
  "jsonrpc":"2.0",
  "method":"qbft_proposeValidatorVote",
  "params":["ADDRESS_TO_REMOVE", false],
  "id":1
}' http://localhost:8545

Same threshold applies: more than 50% of current validators must vote to remove.

Troubleshooting

Node will not start

sudo journalctl -u besu-qelt-validator --no-pager -n 50

Common causes: port 30303 already in use, insufficient RAM, wrong genesis file, key permissions.

No peers connecting

sudo ss -tlnp | grep 30303       # Check port is open
sudo ufw status | grep 30303     # Check firewall

Ensure both TCP and UDP are open on port 30303. Check that your public IP is correctly configured.

Sync stuck at block 0

  • • Verify genesis file matches: sha256sum /etc/qelt/genesis.json should return fa5b3534...
  • • Check bootnode enode URL in service file
  • • Ensure outbound + inbound traffic on port 30303

Method not enabled when calling qbft_proposeValidatorVote

The QBFT API namespace is not enabled. Edit the systemd service to include QBFT:

--rpc-http-api=ETH,NET,QBFT,WEB3,TXPOOL

Then: sudo systemctl daemon-reload && sudo systemctl restart besu-qelt-validator

Vote cast but validator not appearing in set

  • • Votes are included when your node proposes a block (round-robin). Wait for a few rounds.
  • • Check that 3 of 5 validators have actually voted.
  • • If an epoch boundary passes before enough votes accumulate, the unrecorded votes reset — but local proposals persist.

New validator synced but not producing blocks after admission

  • • Verify the validator address appears in qbft_getValidatorsByBlockNumber
  • • Check that the node private key derives to the correct address:
besu public-key export-address --node-private-key-file=/data/qelt/keys/nodekey

Check logs for consensus errors: sudo journalctl -u besu-qelt-validator --since "10 min ago"

Network stalled after adding new validator

  • • If the new node is offline: vote it out immediately using proposeValidatorVote(address, false)
  • • Restart all available validators if needed
  • • Monitor block production via the public RPC endpoints

Ready to Become a Validator?

Check out the enrollment page for a simplified overview, or head straight to GitHub to clone the installer.