Deploying Vexor¶
Docs-site note (rewritten 2026-07-11): Part of the Vexor documentation site (Anza-style). This page documents the deploy contract Vexor's launch script implements: an ordered sequence of stop, verify, tune, launch, and confirm steps, plus the operational patterns (snapshot policy, supervision, rollback) built around it. This rewrite folds in the current baked-by-default runtime story — most of what used to be a long, hand-typed
VEX_*recipe is now filled in automatically by the binary itself — so the surviving explicit configuration is genuinely host-specific: which binary, which cores, which keys, which address. Values shown for identity, IP, and ports are illustrative — substitute your own.
Overview¶
Vexor is deployed with a launch script that does not compile anything — it deploys a pre-built binary (see Building Vexor). It stops any running validator, gates the binary against consensus-critical self-checks, wipes per-boot state, tunes the kernel and NIC, launches the validator, and verifies it came up.
The binary bakes its own proven-good defaults for the flags a standard voting deploy needs — 23
VEX_* variables — the first time it boots, unless you've already set one yourself (an explicit value on your
command line always wins over the bake). That means the honest, current operator surface is small: which binary
to run, which CPU cores to pin to, whether this host's NIC supports AF_XDP, plus your keys and network address.
See the Environment Variable Reference for the complete baked-default table and the
Build-Flag Reference for the build-time half of the same story.
The deploy cadence is always stop → build → deploy: bring the running validator fully down before building a new binary, then deploy the fresh one. Building a release binary is CPU-intensive across every core; doing it while a validator is live on the same box needlessly contends with the replay/vote path for no benefit on a node you can safely bring down for a couple of minutes.
Before you deploy¶
A few things need to be true before the contract below can do its job:
- A production binary, built with
zig build -Dprod -Dpure_zig -Dcpu=<your-cpu-target> --release=safe(see Building Vexor) — the deploy pipeline's consensus guards refuse to launch anything else.-Dpure_zigis the canonical crypto backend (Vexor's own Zig ed25519/BLAKE3/BN254/Poseidon, no Firedancer linked); a Ballet-backed binary (-Dprodwithout-Dpure_zig) is also supported but needs a different guard path — see Consensus guards below. - Your own identity and vote keypairs — never the reference deployment's. See Staking Setup.
- A reachable public IP and open ports for gossip/TVU/repair/RPC — see the CONFIG block below and Network Setup.
- AF_XDP is optional, and needs a Mellanox ConnectX-6 Dx (or compatible) NIC — see Network
Setup. Without one, leave
VEX_ENABLE_AFXDPunset and the node runs on the standard kernel UDP/QUIC path instead.
Two honest limits, stated here rather than discovered mid-deploy:
- Vexor is testnet-only, version 0.9.x pre-production — see Client Identity. Nothing on this page implies mainnet readiness.
- Blocks Vexor produces as leader are currently empty. Transaction-bearing block production exists behind an experimental gate that is not enabled in a standard deploy — see the Environment Variable Reference.
The ordered contract¶
Every deploy runs the same seven steps, in this order, regardless of which binary or flags are in play:
| # | Step | What happens |
|---|---|---|
| 1 | Stop | Kill every running validator process by name, loop until none remain, then wait a settle period (default ~10s) so the kernel releases sockets, AF_XDP/UMEM regions, and mmaps before anything new touches them. |
| 2 | Verify binary | Confirm the binary exists, is a plausible size for a release build (a binary far larger than expected is almost certainly an accidental Debug build — see Build), and log its identity (git commit, content hash, size) so this exact deploy is traceable later. |
| 3 | Capabilities | Grant the binary the Linux capabilities AF_XDP networking needs (cap_net_raw, cap_net_admin, cap_bpf, cap_perfmon) rather than running the whole process as root. |
| 4 | Clean state | Wipe the working account cache, ledger, and tower state from the previous boot — a half-written prior boot must never be allowed to poison consensus by being partially reused. The snapshot itself follows a separate snapshot policy — fresh by default, reusable on request. |
| 5 | Kernel & NIC tuning | Enable the BPF JIT, disable NUMA balancing, size network buffers and backlog queues for a high-throughput UDP/QUIC workload, and (if using AF_XDP zero-copy networking) set up flow-steering and pin the XDP program to the NIC. |
| 6 | Launch | Pin the process to its CPU set and start the validator with its identity, network, and consensus flags — most of which are already baked in (see Overview). |
| 7 | Verify running | Confirm the process is alive, its threads landed on the cores they were pinned to, and (if AF_XDP is enabled) that the zero-copy socket actually bound. |
Steps 3 and 5 need elevated privileges (capability-setting and sysctl writes). A shareable deploy script
should get that privilege from a narrowly-scoped, passwordless sudoers entry for exactly those commands, or
an equivalent askpass hook — not a credential embedded in the script.
Minimal deploy command¶
Between the production build on one side and the runtime baked-defaults on the other, a standard voting deploy no longer needs a long flag list:
VEX_BINARY=/path/to/vex-fd \
VEX_ALLOW_NO_BN254=1 \
VEX_ENABLE_AFXDP=1 \
VEX_FRESH_SNAPSHOT=1 \
VEX_PARALLEL_EXEC=1 \
VEX_PARALLEL_EXEC_CORES=25,26 \
VEX_TASKSET_CORES=1-3,5-24,27 \
bash deploy.sh dev \
--identity /path/to/validator-keypair.json \
--vote-account /path/to/vote-account-keypair.json \
--public-ip <YOUR_PUBLIC_IP>
VEX_PARALLEL_EXEC=1 above is shown explicitly for clarity, but already matches what the binary bakes in on its
own. VEX_FRESH_SNAPSHOT=1 is not baked — it's one of the operator-facing envs this doc teaches explicitly
(see snapshot policy below). VEX_ALLOW_NO_BN254=1 is likewise not baked and is required
for the canonical pure-Zig binary (built with -Dpure_zig, see Building Vexor)
— omit it only if you are deliberately running a Ballet-backed (non-pure-Zig) binary. So the genuinely
load-bearing, host-specific pieces are VEX_BINARY, VEX_ALLOW_NO_BN254, VEX_FRESH_SNAPSHOT, whether
VEX_ENABLE_AFXDP applies to this NIC, and the two core lists. See the Environment Variable
Reference for everything filled in automatically, and the CONFIG
block below for what the --identity / --vote-account /
--public-ip values actually are.
Snapshot policy¶
Snapshot handling is the step most likely to hide corruption if done casually, so it's deliberately opinionated:
- Fresh (recommended for a standard voting deploy — set
VEX_FRESH_SNAPSHOT=1explicitly). Every boot re-extracts the snapshot, and if the local one is more than a configurable slot-age threshold behind the live cluster tip (VEX_SNAP_MAX_AGE_SLOTS,0forces a refetch on every boot), downloads a fresh base snapshot plus the matching incremental from the cluster's entrypoints. This is the production-correct path — a validator should never inherit possibly-corrupt local state across a restart when a clean snapshot is one download away. - Reuse (opt-in —
VEX_REUSE_SNAPSHOT=1). For reproducing a specific slot — debugging a regression, replaying a known state — boot from the existing extracted snapshot without deleting or re-downloading it. The working account cache, ledger, and tower state are still wiped and rebuilt from that snapshot; only the snapshot download is skipped. This is the safe way to pin a specific starting state without accidentally fetching a newer one out from under you. - On any uncertainty (cluster tip unreachable, a malformed slot number), the freshness check preserves the existing snapshot rather than stranding the validator with none at all.
Snapshot loading itself is parallelized — the loader mmaps the snapshot and walks it with a worker-thread pool, which is what makes a near-tip incremental boot fast (tens of seconds rather than many minutes for tens of millions of accounts) instead of a slow single-threaded unpack.
Consensus guards¶
Before launching, the deploy pipeline runs hard, binary-inspecting checks — not just "did the build command succeed" but "does the artifact actually contain the paths a validator must not run without":
- Vote-program guard. Vote-instruction execution is by far the validator's hottest code path. Vexor's vote
program is a from-scratch rewrite derived directly from Agave 4.2 semantics, and is now the sole,
unconditional live executor — no build flag selects it, and no alternate path exists to select instead. A
Sig-derived component served as the in-binary differential test oracle during development, checked
byte-for-byte on every vote instruction over 990k+ live instructions with zero mismatches, and has been
fully removed from the tree (Stage 8, 2026-07-12, −31,181 lines). See
The Vote Program for the full methodology. The deploy pipeline checks the
binary for the compiled-in
voteforgemarker and refuses to launch a binary that lacks it, since a binary without it can't be a Vexor vote-forge build at all. - BN254/Poseidon guard. Similarly, the deploy pipeline checks that the binary has a working BN254/Poseidon
leaf-crypto path before launch. Crypto is now unconditionally Vexor's own pure-Zig backend — the Firedancer
Ballet library alternative (
ballet_bn254flag) was removed from the tree outright (2026-07-12) and is no longer a build option. Since no binary links that library anymore,VEX_ALLOW_NO_BN254=1must be set explicitly at launch on every deploy, unconditionally, to tell the guard that the absence is expected rather than a build mistake — see the Environment Variable Reference.
Both are hard gates: if the vote-execution marker is missing, or the BN254/Poseidon backend isn't present and armed, the deploy aborts rather than launching a binary that would silently diverge from the network under the right transaction.
Supervision and restart¶
Vexor ships an in-process liveness watchdog (the watchdog build flag, part of a production build). It watches
every critical thread for a stall, and — unless you explicitly disarm it — is armed by default to restart the
process on a sustained wedge (VEX_WATCHDOG_RESTART, baked on; see the baked necessity
set). Set VEX_WATCHDOG_RESTART=0 explicitly to run a diagnostic
boot in alert-only mode instead.
Because restart-on-wedge is armed out of the box, the operational lesson here matters even more, and it generalizes to any external supervisor (systemd or similar) wrapping the deploy script with its own automatic restart: arm automatic restart — in-process or external — only after the freshly deployed binary has been confirmed voting non-delinquent, credits advancing, for a sustained period, not immediately at launch. A fresh boot can legitimately take a while to catch up to the cluster tip; a supervisor that restarts too eagerly during that catch-up window can turn a slow-but-healthy boot into a restart loop that never gets a chance to finish catching up — which looks like an outage but is actually the supervisor causing one. Confirm CURRENT first (see verifying the deploy) before trusting any restart authority to stay armed unattended.
If you run the validator under a process supervisor, make sure every consensus-relevant environment variable your deploy uses is captured explicitly in the unit that starts it — a supervisor that clears the environment before invoking the launch command must not silently fall back to different defaults for anything that matters.
Verifying the deploy is green¶
Two independent signals matter after every deploy: the node's own boot log, and the public cluster, never the node's own RPC for cluster-wide facts.
1. The boot log¶
The launch script logs its own progress, and the validator's own startup messages confirm the consensus-critical paths armed — the same guard names from the consensus guards section print a pass/fail line, and (if AF_XDP is enabled) a final confirmation that the zero-copy socket is bound and receiving packets.
2. The cluster oracle¶
Don't trust a validator's own RPC for facts about the wider cluster — a minimal or partially-implemented RPC
surface (which is what Vexor currently ships) can return stale or naive answers for things like current epoch or
leader schedule. For voting health, query the public cluster RPC (e.g. https://api.testnet.solana.com for
testnet) keyed on your vote account pubkey:
curl -s https://api.testnet.solana.com -X POST -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"getVoteAccounts",
"params":[{"votePubkey":"<YOUR_VOTE_PUBKEY>"}]}' \
| python3 -m json.tool
Green looks like: the vote account appears under current, not delinquent; lastVote advances on
each poll; and rootSlot advances too (the node is rooting on the canonical chain, not just replaying it).
For metrics, Vexor reports through SOLANA_METRICS_CONFIG in the same format Agave uses
(host=<url>,db=<database>,u=<user>,p=<password>) — see Observability for the wire format
and what gets sent, and docs.anza.xyz/clusters/available for the
current public testnet endpoint and credentials rather than copying a value from anywhere else.
Rollback discipline¶
Keep the previous known-good binary staged, identified by its content hash rather than a mutable filename — "the binary that was voting CURRENT before this deploy" should always be one file, not a moving pointer. Rollback is then just redeploying that binary with the same environment recipe used when it was last proven live. Because the deploy contract always wipes working state (step 4) and re-runs the consensus guards (they inspect whatever binary is actually being launched), a rollback goes through exactly the same safety checks as a forward deploy — there's no separate, less-checked rollback path to trust.
For a fast, no-rebuild disarm of an optional feature (rather than a full binary rollback), set its runtime
environment variable explicitly to 0 — for example, VEX_PARALLEL_EXEC=0 falls back to the proven serial
execution path on the same binary. This has to be an explicit 0, not just an unset variable: most performance
and resilience flags are baked on by default at boot, so omitting the
variable does not disarm it — see value-parse semantics.
If a fresh-snapshot download fails (cluster snapshot infrastructure unreachable), recover from the last local snapshot instead of retrying the download — see snapshot policy's reuse mode.
What you have to change (the CONFIG block)¶
Everything above is mechanism, identical for any operator. This table is different per validator — treat it as the checklist for pointing the deploy contract at your node:
| Setting | What it is | How to get it |
|---|---|---|
| Genesis hash | Identifies the cluster you're joining | solana genesis-hash against a cluster RPC endpoint |
| Shred version | A cluster-generation fingerprint; changes across coordinated restarts | From a healthy peer's gossip info — a wrong value means zero valid peers and no shreds |
| Identity keypair | The validator node's own identity | Generate your own — see Staking Setup; never reuse a keypair across roles |
| Vote keypair | The account votes are signed with | Generate your own; always vote with it — don't run --no-voting outside a clearly-labelled diagnostic |
| Public IP | Advertised in gossip so peers know where to send you shreds | <YOUR_PUBLIC_IP> — must be externally reachable, not a private/NAT address (our reference deployment advertises 38.58.183.154, shown for illustration only) |
| Ports | gossip / TVU / repair / RPC | Must be open inbound for the ports you advertise — see Network Setup |
| Known validators / entrypoints | Trusted peers used for cluster discovery | Cluster-specific public list; note that this flag has zero effect today — the current build's --known-validator parsing is an unwired no-op and the hardcoded fallback list has no callers on the snapshot-fetch path, which instead uses RPC/URL-based peer discovery. Passing the flag is forward-compatible (it is intended to become a real trust boundary) but does not currently gate or filter anything |
| NIC | The interface AF_XDP binds to, if using zero-copy networking | Host-specific; non-Mellanox NICs need their own queue/RSS/XDP setup or should run with AF_XDP disabled — see Network Setup |
Everything else in the deploy contract — the stop/verify/tune/launch/verify sequence, the consensus guards, the snapshot policy — is the same regardless of who's running it.
Coordinated cluster restarts¶
A coordinated cluster restart is the one time --expected-bank-hash and --wait-for-supermajority apply.
Leave them unset for a normal boot — a validator booting from a later snapshot must never be halted waiting for
a supermajority that has already moved past that point. Arm them only for a genuine coordinated restart, using
the slot and bank hash agreed by cluster operators for that event; the validator then holds voting and block
production until enough of the epoch's activated stake is visible in gossip, and proceeds automatically once
that threshold is met. See Staking Setup — the coordinated-restart
gate for the exact flag/env names and an example invocation.
See also¶
- Building Vexor — the production build and what it bundles.
- Build-Flag Reference — every
-Dbuild flag, exhaustively. - Environment Variable Reference — every
VEX_*runtime variable, including the full baked-default table. - Staking Setup — keys,
--identity/--vote-account, and the coordinated-restart gate in detail. - Network Setup — AF_XDP prerequisites, ports, and core pinning.
- The Vote Program — the vote-program rewrite's architecture and current status.
- Observability — metrics wire format and what Vexor reports.