Skip to Content
NCNVerifier ServiceOverview

Verifier Service

The Verifier Service is a standalone Rust/Axum HTTP service that operators run alongside their NCN infrastructure. It indexes uploaded snapshots into SQLite and serves Merkle proof data to governance participants (validators and stakers) who need proofs to cast votes.

Default port: 3000


Start Here (Server)

For a user-friendly server install flow, use the dropdown in the left sidebar:

  • Requirements
  • Install & Start
  • Upgrade / Re-deploy

The rest of this page mainly documents environment variables and API endpoints.


Purpose

When a validator or delegator wants to cast a governance vote, they need:

  1. Their MetaMerkleLeaf (stake weight data)
  2. The meta_merkle_proof (sibling hashes from their leaf to the root)
  3. For delegators: their StakeMerkleLeaf and stake_merkle_proof

Rather than computing these from the raw snapshot file, clients query the verifier service which pre-indexes all this data into SQLite for fast lookups.


Environment Variables

VariableDefaultRequiredDescription
OPERATOR_PUBKEYYesBase58 operator public key for upload authentication
METRICS_AUTH_TOKENYesBearer token for the /admin/stats endpoint
DB_PATH/data/governance.dbNoSQLite database path (:memory: for tests)
PORT3000NoHTTP listen port
SQLITE_MAX_CONNECTIONS4 (file) / 1 (memory)NoConnection pool size
UPLOAD_BODY_LIMIT104857600 (100 MiB)NoMax upload body size in bytes
GLOBAL_REFILL_INTERVAL10NoGlobal rate limit: requests/second
GLOBAL_RATE_BURST10NoGlobal rate limit burst size
UPLOAD_REFILL_INTERVAL60NoUpload rate limit: requests/second
UPLOAD_RATE_BURST2NoUpload rate limit burst size
NCN_SNAPSHOT_MAX_MB256NoMax decompressed snapshot size (MiB)

Server Requirements

  • Ubuntu server (recommended) with sudo access
  • Docker installed (the provided setup.sh script can install it if missing)
  • Disk space for the persistent SQLite DB and indexed snapshot data (Docker volume at /srv/verifier/data)
  • Network access to your chosen host port (you will map host PORT_HOST -> container port 3000)
  • The operator’s base58 OPERATOR_PUBKEY (used to authenticate snapshot uploads)
  • A METRICS_AUTH_TOKEN (used for /admin/stats)

Install & Start on the Server (Docker)

Deploy flow (single path):

  1. SSH into your server.
  2. Install & start:
cd ~/solana-governance make install-verifier-service

During the run, you will be prompted to select mainnet vs testnet, then enter:

  • OPERATOR_PUBKEY
  • METRICS_AUTH_TOKEN
  • PORT_HOST (example: 8000, which maps 8000 -> 3000)

The Docker image tag is auto-selected as verifier-service:latest-mainnet or verifier-service:latest-testnet (there is no separate IMAGE prompt).

make install-verifier-service runs setup.sh, which starts container verifier, persists data under /srv/verifier/data, and skips docker pull if the image tag already exists locally.


Verify the Deployment

Run these commands on the server:

sudo docker ps # Replace <PORT_HOST> with the port you entered when running setup.sh curl -i http://127.0.0.1:<PORT_HOST>/healthz curl -s http://127.0.0.1:<PORT_HOST>/version

If curl fails, check logs:

sudo docker logs --tail=200 verifier

Upgrade / Re-deploy

To upgrade to a new version:

  1. Re-run make install-verifier-service (select the same network; enter the same OPERATOR_PUBKEY/METRICS_AUTH_TOKEN and your chosen PORT_HOST).

Persistent data stays mounted under /srv/verifier/data, so upgrades should not require re-uploading everything.


API Reference

POST /upload

Upload and index a snapshot. Only the whitelisted operator (identified by OPERATOR_PUBKEY) can upload.

Authentication: Ed25519 signature over slot.to_le_bytes() ‖ merkle_root_bs58_string.as_bytes()

Request: multipart/form-data

FieldTypeDescription
slotstringSnapshot slot number
networkstringNetwork identifier (e.g., mainnet, testnet)
merkle_rootstringBase58-encoded Merkle root
signaturestringBase58-encoded Ed25519 signature
filebinaryThe snapshot .zip file

On upload:

  1. Verifies the Ed25519 signature against OPERATOR_PUBKEY
  2. Decompresses and Borsh-deserializes the snapshot
  3. Validates size against NCN_SNAPSHOT_MAX_MB
  4. Indexes all vote accounts and stake accounts into SQLite

Response: 200 OK on success, 4xx on auth/validation failure.


GET /healthz

Health check endpoint.

curl http://localhost:3000/healthz # → "ok"

GET /version

Returns service version metadata.

curl http://localhost:3000/version
{ "name": "verifier-service", "version": "0.1.0", "git_hash": "abc1234", "build_time_unix": 1710000000 }

GET /meta

Returns the latest SnapshotMetaRecord for a given network.

Query params: network (required)

curl "http://localhost:3000/meta?network=mainnet"
{ "network": "mainnet", "slot": 300000000, "merkle_root": "AbCd...", "snapshot_hash": "XyZw...", "created_at": "2025-01-01T00:00:00Z" }

GET /voter/:voting_wallet

Returns all vote accounts and stake account summaries associated with a voting wallet address.

Query params: network (required), slot (optional — defaults to latest)

curl "http://localhost:3000/voter/AbCd...?network=mainnet"
{ "voting_wallet": "AbCd...", "vote_accounts": [ { "vote_account": "VoTe...", "active_stake": 1000000000000, "stake_merkle_root": "MrKl...", "stake_accounts": [ { "stake_account": "StAk...", "active_stake": 500000000000 } ] } ] }

GET /proof/vote_account/:vote_account

Returns the MetaMerkleLeaf and meta_merkle_proof for a validator’s vote account.

Query params: network (required), slot (optional)

curl "http://localhost:3000/proof/vote_account/VoTe...?network=mainnet"
{ "meta_merkle_leaf": { "voting_wallet": "AbCd...", "vote_account": "VoTe...", "stake_merkle_root": "MrKl...", "active_stake": 1000000000000 }, "meta_merkle_proof": [ "SiblingHash1Base58...", "SiblingHash2Base58...", "SiblingHash3Base58..." ] }

This is the primary endpoint used by the svmgov CLI when preparing validator votes.


GET /proof/stake_account/:stake_account

Returns the StakeMerkleLeaf, stake_merkle_proof, and parent vote_account for a delegator’s stake account.

Query params: network (required), slot (optional)

curl "http://localhost:3000/proof/stake_account/StAk...?network=mainnet"
{ "stake_merkle_leaf": { "voting_wallet": "AbCd...", "stake_account": "StAk...", "active_stake": 500000000000 }, "stake_merkle_proof": [ "SiblingHash1Base58..." ], "vote_account": "VoTe..." }

This endpoint is used by the svmgov CLI when a delegator calls cast-vote-override.


GET /admin/stats

Returns runtime metrics. Requires X-Metrics-Token: <METRICS_AUTH_TOKEN> header.

curl -H "X-Metrics-Token: secret" http://localhost:3000/admin/stats

Database Schema

The service uses SQLite with three tables:

vote_account_records

ColumnTypeDescription
networkTEXTNetwork identifier
snapshot_slotINTEGERSlot
vote_accountTEXTValidator vote account (base58)
voting_walletTEXTVoting wallet (base58)
stake_merkle_rootTEXTBase58 stake Merkle root
active_stakeINTEGERTotal active stake (lamports)
meta_merkle_proofTEXT (JSON array)Array of base58 sibling hashes

stake_account_records

ColumnTypeDescription
networkTEXTNetwork identifier
snapshot_slotINTEGERSlot
stake_accountTEXTStake account (base58)
vote_accountTEXTParent validator vote account
voting_walletTEXTVoting wallet (base58)
active_stakeINTEGERStake amount (lamports)
stake_merkle_proofTEXT (JSON array)Array of base58 sibling hashes

snapshot_meta_records

ColumnTypeDescription
networkTEXTNetwork identifier
slotINTEGERSnapshot slot
merkle_rootTEXTBase58 Merkle root
snapshot_hashTEXTBase58 snapshot content hash
created_atTEXTISO 8601 UTC timestamp

Local Development (Cargo)

# Set required env vars export OPERATOR_PUBKEY="YourOperatorPubkeyBase58..." export METRICS_AUTH_TOKEN="your-secret-token" export DB_PATH="/data/governance.db" export PORT=3000 # Build and run cargo build --release -p verifier-service ./target/release/verifier-service

The verifier service is operator infrastructure. Validators and stakers use the public read endpoints (/proof/*) but do not need to run their own instance.


Rate Limiting

The service has two independent rate limiters:

LimiterDefaultApplies to
Global10 req/s, burst 10All endpoints
Upload1 req/60s, burst 2POST /upload only

Configure via GLOBAL_REFILL_INTERVAL, GLOBAL_RATE_BURST, UPLOAD_REFILL_INTERVAL, UPLOAD_RATE_BURST.

Last updated on