Skip to content

The Vote Program — Rewriting Vexor's Most-Executed Code Path

Docs-site note (2026-07-11, updated 2026-07-12 for Stage 8): This page documents a from-scratch rewrite that is now Vexor's sole, unconditional live vote executor on testnet — no build flag selects it, and no alternate path exists to select instead. The vote program described in Consensus is this implementation. A Sig-derived component served as the differential test oracle throughout development; the soak completed and it has been fully removed from the tree (Stage 8, commit 34ffb4e, 2026-07-12, −31,181 lines). Style and grounding rules match the rest of this section: plain language, claims tied to source and to measurement, caveats stated honestly.


Overview

Every vote a Solana validator casts is an ordinary on-chain transaction: it invokes the vote program, a built-in program that mutates a validator's vote account (its lockout tower, its credits, its root slot). On Vexor's testnet node, vote transactions are not a niche code path — they are the large majority of everything the runtime executes. Across a measured ~990,000-instruction window, vote instructions accounted for the large majority of transaction signatures processed by the node. A validator's execution time is, to first order, the time it takes to run the vote program.

That makes the vote program's implementation quality — both its correctness and its speed — one of the highest- leverage places in the entire client. Vexor rewrote it from scratch, derived directly from Agave 4.2 source, shaped after Firedancer's zero-allocation implementation style, and verified against the prior implementation before being trusted to run alone. It is now the implementation that executes every vote instruction on Vexor's testnet node.


Where the vote program started

Vexor's original vote-program implementation was adapted from the Sig validator (Syndica)'s Zig port of Agave's Rust vote processor — a faithful, line-by-line translation, including Rust's allocation-heavy idioms: every vote instruction round-tripped through a generic reflection-based codec, a heap-allocated vote-state struct backed by a hash map and a growable list, a mutate step, and a full re-serialize of an up-to-3,762-byte account. That implementation was honest and correct, but it was never optimized for a Zig validator's needs, and — like any adapted code — it carried a version lineage of its own: it tracked an Agave revision one minor version behind the one Vexor's own reference validator runs, which meant it was missing at least one instruction Agave 4.2 added (DepositDelegatorRewards, SIMD-0123) and only stubbed another (InitializeAccountV2, SIMD-0464).

Rather than keep patching an adapted implementation to keep it current, Vexor replaced it outright with an implementation derived directly from Agave source — closing the version gap, adding the missing instructions, and building in a shape suited to Zig and to Firedancer-style zero-allocation execution from the start.


The four-layer architecture

The rewrite splits cleanly into four layers, mirroring a separation Agave's own source already keeps (state- transition logic and instruction dispatch are separate modules in vote_state/mod.rs vs vote_processor.rs) and matching the shape Firedancer's fd_vote_program.c uses in practice:

  1. Codec layer. Version-aware serialize/deserialize between raw account bytes and an in-memory vote-state struct. Instead of a generic reflection-based (de)serializer, this is a comptime-specialized, fixed-offset codec: it reads and writes exactly the fields a given operation touches, at known byte offsets, with bounded collections (a 31-entry lockout ring, a small fixed-capacity authorized-voters table) instead of a growable list or hash map. Zero heap allocation. This layer also owns the one field in the modern vote-state layout that exists specifically for a future Alpenglow/Votor consensus engine — a compressed BLS public key — without needing to know anything about how that field will eventually be used.
  2. Account-I/O layer. A borrow-only-what's-touched account model, modeled on Firedancer's fd_borrowed_account_t: a vote instruction typically names 2-4 accounts (the vote account, an authority, a sysvar), so this layer is built so a caller can only ever materialize the accounts an instruction actually names — never a whole transaction's worth of account state.
  3. State-transition layer. Pure functions operating on an in-memory vote-state value: pushing and popping the lockout tower, awarding credits, advancing the root slot, authorizing a new voter, updating commission. This layer is intentionally instruction-agnostic — it has no idea whether it was invoked by a TowerSync instruction or something else. That property is what makes it reusable: a future certificate-based consensus engine would call into this same layer to update the same bookkeeping, without touching it at all.
  4. Dispatch layer. The thin front door: decode the instruction, do the account-ownership and signer checks every instruction needs, route to the right state-transition function, and serialize the result back out through layers 1-2. Kept deliberately thin so it stays an auditable, line-for-line mirror of Agave's own dispatch table.

This is also the module boundary that keeps the vote program ready for Solana's proposed Alpenglow consensus upgrade without requiring a second rewrite later: layers 1-3 are shared infrastructure that any future certificate-processing engine would reuse, and only the dispatch layer would need a sibling. Vexor has not built any Alpenglow/Votor logic — no BLS certificate verification, no such consensus state machine exists yet — this is purely a decision about where the seams go, made now because it costs nothing extra.


Anchored to Agave, shaped like Firedancer

Every state-transition rule in the rewrite is derived directly from Agave 4.2 source — the version Vexor's own reference validator runs live, not a historical snapshot — so behavior can be checked against source and against a real running cluster node, not just against another Zig implementation's interpretation of an older Agave revision.

The implementation shape, in contrast, is closer to Firedancer's fd_vote_program.c: stack-allocated instruction decoding bounded by a fixed transaction-size limit (no heap allocation on the decode path), flat fixed-capacity structs instead of generic hash maps and growable lists, and account-ownership checks done once up front rather than re-derived per field. Vexor keeps Zig's typed error unions for control flow (Agave's own Result-returning style maps onto them naturally) rather than adopting Firedancer's C-style integer return codes — the two approaches cost about the same at runtime, and the typed version is easier to audit.

One concrete example of anchoring to current Agave semantics rather than an inherited assumption: Agave 4.2 has exactly one target vote-state version — every successful mutating instruction migrates an older account to the modern V4 layout in memory and always writes V4 back out, unconditionally. The prior, adapted implementation carried a feature-gated V3-vs-V4 branch reflecting an older Agave revision where that was still conditional. On live testnet the feature in question has been active since a specific historical slot, so the two behave identically today — but the rewrite reflects what the current source actually says, not an assumption inherited from an earlier port.


Proving it correct before trusting it: the differential-oracle methodology

Rewriting the single most-executed, most consensus-critical code path in the client is not something to get subtly wrong — a bug here doesn't crash the validator, it silently produces a wrong account byte, which is exactly the kind of error that turns into a bank-hash divergence hours or days later. So the rewrite was built and proven in stages, never swapped in wholesale:

  • Known-answer tests ported from Agave itself. Rather than trust only hand-built test vectors, the rewrite ports Agave's own unit-test expectations from vote_processor.rs and vote_state/mod.rs — same inputs, same expected outcomes — so correctness is checked against ground truth, not against another implementation's interpretation of it.
  • A live, in-binary A/B (shadow) mode. For every vote instruction the node processed during development, both implementations executed, and the results — mutated account bytes and error outcome — were compared byte-for-byte, with any mismatch logged loudly. Early on, the prior (Sig-derived) implementation's result was the one that committed, while the rewrite ran silently alongside it; that relationship inverted once the rewrite was trusted to commit state, with the prior implementation running alongside it in the shadow role through a full-epoch-boundary soak (see "Honest status" below).
  • Staged, gated rollout. Each layer was built and proven independently before the next depended on it: the codec layer first (byte-exact round-trips against real cluster vote-account data), then account I/O, then each non-lockout instruction family (authorize, withdraw, commission, initialize), then dispatch, and last — deserving the most test density of any stage — the TowerSync family: the lockout, credit, and root-advance logic that is the actual historical source of past divergences in this client.
  • Full-scale live comparison. At the final gate before promotion, 990,000 live vote instructions were compared byte-for-byte with zero mismatches, alongside a full slot-range replay (1,992 slots) matching the cluster's bank hash under both implementations.
  • A real bug the process caught. The gating process itself found a genuine, pre-existing bug shared by the old code path: an account-lookup routine could point a result read at the wrong copy of an account for validators whose vote-authority key happened to equal their vote-account key. Production execution was unaffected — the cluster-visible outcome was already correct — but the internal verification path was reading the wrong bytes. It was found and fixed because the new implementation's independent result exposed the disagreement. That is the methodology doing exactly what it is for.

Honest status: the rewrite is the sole, unconditional implementation that commits state for every vote instruction on Vexor's testnet node — no build flag selects it, and no alternate path exists to select instead. The prior, adapted implementation ran as a shadow-comparison oracle through a soak period spanning a full epoch boundary with continued zero mismatches (epoch-boundary authorized-voter bookkeeping was exactly the kind of narrow window a short test run could have missed). Once that soak completed, the old implementation and its three build flags (-Dsig_vote, -Dvote_ab, -Dvote_live) were removed outright — Stage 8, commit 34ffb4e, 2026-07-12, −31,181 lines.


Measured performance

Because the vote program is such a large share of total execution time, its performance is not a micro- optimization — it materially affects how much of a slot's CPU budget goes to consensus bookkeeping versus everything else. Measured across a 990,000-instruction live comparison window:

Prior implementation Rewrite
Time per vote instruction ~8.9 µs ~1.9-2.0 µs
Relative baseline ~4.7× faster

The improvement comes directly from the architectural choices above: no heap allocation on the hot path, no generic reflection-based codec, and account materialization bounded to only what an instruction actually touches instead of an entire transaction's account set. With vote instructions making up the large majority of testnet transaction signatures, a 4.7× reduction here is a meaningful reduction in the runtime's total per-slot execution cost, not just a narrow benchmark win.


Capabilities the rewrite adds

Because it is derived directly from current Agave source rather than an older, adapted implementation, the rewrite closes gaps that existed in the adapted code and consolidates functionality that had drifted into two separate, unreconciled places:

  • DepositDelegatorRewards (SIMD-0123). A 4.2-native instruction with no equivalent in the adapted implementation at all — it simply could not be parsed before. The rewrite implements it directly from Agave source.
  • InitializeAccountV2 (SIMD-0464). Previously implemented twice in two different places in the codebase — once as a permanent-error placeholder in the adapted vote program, and once as a real, working implementation built entirely outside it. The rewrite consolidates this into the one place it belongs.
  • Unconditional V4 vote-state handling, matching current Agave semantics exactly rather than carrying a feature-gate branch that reflects an older Agave revision (see above).

None of this changes what a validator operator needs to do — these are correctness and completeness properties of the vote program itself, verified the same way as everything else in this section: against source, and against a running cluster.


Status and what's next

  • Live on testnet as the sole, unconditional production vote executor. Vexor's rewrite commits the state that lands on chain for every vote instruction. No build flag selects it and no alternate path exists to select instead.
  • Fully retired. The prior, adapted implementation ran as a live shadow-comparison oracle through a soak period spanning a full epoch boundary with continued zero result mismatches — epoch-boundary authorized-voter bookkeeping was the narrow window a short run could have missed. That soak completed, and the old implementation, its three build flags (-Dsig_vote/-Dvote_ab/-Dvote_live), and its differential harness were removed entirely (Stage 8, commit 34ffb4e, 2026-07-12, −31,181 lines).
  • This completes the provenance goal: the entire live vote-execution path is Vexor-authored code, derived from Agave source, with no adapted third-party implementation left in the tree.

Vexor is an independent Solana validator client implemented from scratch in Zig. Consensus behavior is validated byte-for-byte against the Agave reference implementation, with Firedancer and Sig used as engineering references.