Skip to content

Building Vexor

Docs-site note (2026-07-11): Part of the Vexor documentation site (Anza-style). This page documents the exact, current way to build the Vexor validator binary, grounded in build.zig and the deploy pipeline that gates it. There is one build command for a normal deploy; everything else here explains why.


The toolchain — Zig 0.15.2, exactly

Vexor is written in Zig 0.15.2. The build does not produce a correct binary on any other version, and this is not a "probably fine" version pin — it is load-bearing:

  • Zig 0.15.2 uses the --release=safe|fast|small flag form; it rejects the older -Doptimize=… syntax.
  • The std.fs.File Reader/Writer overhaul and ArrayList becoming unmanaged-by-default both landed between 0.14 and 0.15, and Vexor's code depends on the 0.15.2 shape of both.
  • An older compiler can still link and run a binary — it just silently miscompiles parts of it. That is a worse failure mode than a build error, because nothing tells you it happened.

Check zig version before building. If it doesn't print 0.15.2, install the pinned toolchain rather than using whatever zig resolves to on $PATH.


The one command

zig build -Dprod -Dpure_zig -Dcpu=znver4 --release=safe

Run from the repo root with the pinned Zig 0.15.2. -Dprod is the bundling switch: it turns on all 8 canonical production feature flags at once (the table below). -Dpure_zig is retained on the command line for compatibility: crypto is now unconditionally pure-Zig (the Firedancer Ballet AVX-512 backend was removed outright — see why below), so the flag is a no-op, but passing it costs nothing and keeps the build command self-documenting. Every flag in build.zig defaults to off, so a bare zig build is a deliberately minimal, fast-iterating binary that is not what you want to run a validator with — -Dprod -Dpure_zig is what turns a checkout into a deployable production binary. The bundle is golden-replay-gated as behaviorally identical to enabling all 8 flags explicitly, and each flag remains individually overridable on top of it (e.g. -Dprod -Dpure_zig -Dfec_dedup=false) for forensic or bisect builds that need a narrower flag surface.

-Dcpu=znver4 matches Vexor's reference box (an EPYC 9374F) — see Hardware for why. If your hardware is a different CPU, set -Dcpu= to match (Zig's standard target-CPU option, not something Vexor-specific); getting it wrong doesn't break consensus, but it can silently disable ISA extensions (AVX-512, sha_ni, vaes) that sig-verify and hashing lean on for throughput.

The resulting binary lands at zig-out/bin/vex-fd. The deploy tooling additionally self-checks the binary's compiled-in feature markers before launch, so a mis-built binary fails the deploy rather than silently shipping — see Deploying Vexor.


What -Dprod bundles

-Dprod turns on 8 flags, each of which is individually a -D option in build.zig and defaults to off there:

Flag What it does Why it matters
leader_mode Block-production tick loop Required to produce blocks as leader (separate from whether broadcast is armed).
repair_stake_weighting Stake-weighted repair-peer selection Faster, more correct repair versus round-robin peer choice.
parallel_exec Wave-barrier parallel transaction execution Throughput — parallelizes non-conflicting transactions across a worker pool.
fec_dedup Ed25519 signature dedup across a FEC set Throughput — avoids re-verifying signatures already checked once.
watchdog In-process liveness-observing thread Operational — see supervision.
status_cache Cross-block already-processed cache Needed to safely support transaction-bearing block production.
use_native_quic_votes Route votes over QUIC instead of UDP Networking — reduces reliance on the UDP+relay vote path.

History (2026-07-12 and earlier): two other flags used to be part of this bundle and no longer exist. sig_vote built the Sig-derived vote-execution path that served as a differential/shadow oracle while Vexor's own vote program (the July 2026 rewrite) was being validated; that oracle and the flag that built it were deleted outright in Stage 8 (2026-07-12) once the rewrite's own KAT/carrier/golden-replay gates were sufficient on their own — voteforge is now the sole, unconditional vote executor, with no flag to select an alternative. ballet_bn254 (and its siblings ballet_ed25519/ballet_blake3) linked Firedancer's Ballet AVX-512 crypto library; that backend was fully removed from build.zig (not merely overridden) once the pure-Zig crypto rewrite reached parity — see below.

-Dvex_ledger (included in -Dprod) adds the persistent blockstore on top of this set:

Flag What it does Why it matters
vex_ledger Vexor's own Zig-native blockstore Enables the VexLedger storage path.

Two other feature flags default on in build.zig regardless of which script you use and don't need to be passed: jemalloc (the allocator) and two_tier (the accounts-DB read/commit design). Everything outside these lists is either a default-on baseline or an unvetted, experimental feature gated behind its own flag — The -Dprod option's doc comment in build.zig enumerates what it deliberately still omits and why. Turning one of those on outside of a deliberate test is not part of a normal build.

A handful of the bundled flags are also gated behind a runtime environment variable at deploy time — building the capability in and arming it at runtime are two separate switches, so a production binary with no env set behaves identically to a binary built without that flag at all. See the Environment Variable Reference for which ones.

The crypto backend: Firedancer Ballet vs. pure-Zig

Vexor originally linked Firedancer's ballet AVX-512 C libraries for the leaf-level crypto primitives (Ed25519, BLAKE3, BN254/alt_bn128, Poseidon) behind three flags — ballet_ed25519, ballet_blake3, ballet_bn254. That backend has since been removed from build.zig outright (not just defaulted off): once the pure-Zig rewrite reached parity, the removal was proven behavior-inert (the -Dprod -Dpure_zig binary's .text/.rodata/.data sections came out byte-identical to the pre-removal baseline) and the dead Ballet flags, externs, and linked .a artifacts were deleted. There is no build configuration that links Firedancer crypto code anymoreballet_* are not valid -D flags on any current checkout.

Crypto is now unconditionally pure-Zig, Vexor's own from-scratch implementation of the same primitives (src/vex_crypto/) — the binary links zero Firedancer symbols at runtime, always. -Dpure_zig is kept as an accepted flag on the build command for compatibility and self-documentation, but it is a no-op: zig build -Dprod alone now produces the same pure-Zig binary as -Dprod -Dpure_zig. The pure-Zig crypto is verified byte-exact against known-answer test vectors (including published solana-bn254 / EIP-197 / go-ethereum vectors for BN254) and against live cluster parity. See NOTICE and PROVENANCE for the attribution story and The Vote Program for how the crypto rewrite fits into the broader Firedancer-independence effort.

Every binary still needs one runtime guard: VEX_ALLOW_NO_BN254=1. The BN254 boot guard historically assumed a linked Ballet BN254 library and refuses to boot without it; since no build links that library anymore, this environment variable is required on every deploy, unconditionally — not just for a subset of builds. See the Environment Variable Reference for details.


Why --release=safe

Zig's optimize modes trade off speed, size, and runtime safety checks. --release=safe is optimized code with bounds checks, integer-overflow checks, and similar safety nets still compiled in — it is the mode a validator should run in:

  • A Debug build is roughly 30× slower. A validator in Debug mode falls behind the cluster tip and can't keep up with replay, sig-verify, or PoH hashing in real time.
  • --release=fast strips the safety checks entirely. For a process executing untrusted, adversarial input (arbitrary on-chain transactions) at the center of a consensus system, that is the wrong trade — a checked crash is vastly preferable to unchecked memory corruption.

--release=safe is the canonical release mode for every Vexor build, always paired with the full production flag set (-Dprod) for a production deploy.


Binary size as a sanity check

A correctly built --release=safe binary is on the order of 15–17 MB. A binary over 50 MB is almost certainly an accidental Debug build — the deploy pipeline treats that as a hard warning sign and refuses to launch it unless explicitly overridden, precisely because a Debug binary silently fails to keep up with the cluster rather than failing loudly. If your build comes out far larger than that, check that --release=safe was actually passed.


Verifying the flags landed

Because dropping a single flag produces a binary that runs and votes but silently diverges from the cluster under specific conditions, don't just trust that the build command was typed correctly — verify it landed. The deploy tooling already does this for you: it runs a self-check after every build that greps the output binary for each flag's compiled-in marker string and fails the build if any is missing, rather than letting a silently-incomplete binary reach the deploy step. The deploy pipeline then runs its own, narrower version of the same check before it will launch a binary (see Consensus guards). You can run the same kind of check manually at any time:

strings -n6 zig-out/bin/vex-fd | grep -c voteforge           # Vexor vote program present?
strings -n6 zig-out/bin/vex-fd | grep -c fd_bn254            # expect 0 — no Firedancer BN254 linked, ever

A nonzero voteforge count confirms the vote program is in the binary you're about to run — it is the only vote-execution path there is now, so this should always be nonzero. The fd_bn254 count should always read zero: every current build is crypto-unconditionally pure-Zig, so there is no configuration that links Firedancer's Ballet BN254 library anymore.


Build while the node is stopped

Building a release binary is CPU-intensive across every core on the box. If a validator is currently voting on the same machine, build after stopping it, not concurrently — this removes any question of the build contending with the consensus-critical replay/vote path for CPU or I/O. See the deploy contract for the stop → build → deploy sequence.


Quick reference

# Confirm the toolchain
zig version                              # must print 0.15.2

# Build the production binary (set -Dcpu= to your target CPU)
zig build -Dprod -Dpure_zig -Dcpu=znver4 --release=safe   # the bundle is golden-gated; deploy self-checks the markers

# Sanity-check the output
ls -la zig-out/bin/vex-fd                      # ~15-17 MB, not 50+ MB
strings -n6 zig-out/bin/vex-fd | grep -c voteforge   # Vexor vote program compiled in (expect >0)
strings -n6 zig-out/bin/vex-fd | grep -c fd_bn254    # Firedancer BN254 linked (expect 0 for -Dpure_zig)

# Then deploy — see the Deploy page (pure-Zig binaries need VEX_ALLOW_NO_BN254=1 at launch)