Skip to content

feat: SIMD-0185 vote state v4 support#209

Open
7layermagik wants to merge 2 commits intodevfrom
7layer/simd-0185-vote-state-v4
Open

feat: SIMD-0185 vote state v4 support#209
7layermagik wants to merge 2 commits intodevfrom
7layer/simd-0185-vote-state-v4

Conversation

@7layermagik
Copy link

@7layermagik 7layermagik commented Feb 26, 2026

Note: This PR depends on #208 (SIMD-0321) as its base. Merge that first.

Summary

Implements SIMD-0185 (Vote State V4), the next structural revision of the on-chain vote account format. V4 removes PriorVoters, changes commission from u8 percentage to u16 basis points, and adds new fields for revenue splitting and BLS keys. Gated behind feature vote_state_v4 (Gx4XFcrVMt4HUvPzTpTSVkdDVgcDSjKhDN1RqRS6KDuZ).

Changes

New struct and serialization (pkg/sealevel/vote_state.go)

  • VoteState4 struct with all V4 fields: InflationRewardsCollector, BlockRevenueCollector, InflationRewardsCommissionBps (u16), BlockRevenueCommissionBps (u16), PendingDelegatorRewards, BlsPubkeyCompressed (Option<[u8;48]>), plus the standard fields (NodePubkey, AuthorizedWithdrawer, Votes as LandedVote deque, RootSlot, AuthorizedVoters, EpochCredits, LastTimestamp).
  • Discriminant 3 (VoteStateVersionV4) added to the existing iota enum (V0_23_5=0, V1_14_11=1, Current/V3=2, V4=3).
  • Full UnmarshalWithDecoder and MarshalWithEncoder matching Agave's frame_v4 layout byte-for-byte.

VoteStateVersions switch sites

All 6 switch sites in VoteStateVersions updated with V4 cases:

  • UnmarshalWithDecoder / MarshalWithEncoder — delegates to VoteState4 codec
  • IsInitialized — checks V4 AuthorizedVoters length
  • ConvertToCurrent — converts V4 to the VoteState working struct, preserving V4-specific fields via internal preservation fields (wasV4, v4InflationRewardsCollector, etc.)
  • LastTimestamp / NodePubkey — direct field access on V4

V4 field preservation through the processing loop

Mithril processes all vote state versions through a common VoteState (V3) working struct. V4-only fields (collectors, bps commission, pending rewards, BLS key) are preserved via unexported fields on VoteState. On write-back:

  • wasV4 = true: preserved original V4 values are restored (no lossy round-trip)
  • wasV4 = false (first V3→V4 conversion): Agave-matching defaults are used:
    • InflationRewardsCollector = vote account pubkey
    • BlockRevenueCollector = node pubkey
    • InflationRewardsCommissionBps = u8_commission * 100
    • BlockRevenueCommissionBps = 10000 (100%)
    • PendingDelegatorRewards = 0, BlsPubkeyCompressed = None

Write path (setVoteAccountState)

When VoteStateV4 feature is active (checked BEFORE VoteStateAddVoteLatency):

  • Serializes as discriminant 3 using newVoteState4FromCurrent
  • Same allocation size as V3 (3762 bytes) — no resize needed for V3→V4
  • No V1_14_11 fallback on resize failure — returns InstrErrAccountNotRentExempt per SIMD-0185
  • Commission updates via VoteProgramUpdateCommission sync the preserved v4InflationRewardsCommBps field

Behavioral changes

  • Authorized voter purge policy: V4 purges at currentEpoch - 1 (retains one extra epoch), vs V3's currentEpoch. Implemented in GetAndUpdateAuthorizedVoter which now accepts features.Features.
  • PriorVoters tracking: SetNewAuthorizedVoter skips PriorVoters.Append when V4 is active (V4 has no PriorVoters field). Both methods now accept features.Features.

External consumer updates

  • pkg/sealevel/vote_program.go: verifyAndGetVoteState signature updated to accept features; all 3 callers (VoteProgramProcessVote, VoteProgramProcessVoteStateUpdate, VoteProgramProcessTowerSync) pass features through. VoteProgramAuthorize passes features to SetNewAuthorizedVoter.
  • pkg/rewards/rewards.go: voteCommissionSplit reads V4 InflationRewardsCommissionBps / 100; calculateStakePointsAndCredits reads V4 EpochCredits.
  • pkg/replay/transaction.go: recordVoteTimestampAndSlot reads V4 LastTimestamp.
  • pkg/snapshot/manifest_decoder.go: Snapshot vote account loading handles V4 LastTimestamp and NodePubkey.

Correctness notes

  • Commission round-trip: u8 × 100 → u16 bps; bps / 100 → u8. No precision loss for values 0–100. Original bps preserved exactly for V4→V4 round-trips.
  • V4 immutable fields: InflationRewardsCollector, BlockRevenueCollector, PendingDelegatorRewards, BlsPubkeyCompressed — no vote program instruction modifies these in SIMD-0185, so compute-on-first-write defaults are correct.
  • Agave references: agave/programs/vote/src/vote_state/handler.rs (convert_to_v4, V4 purge, V4 set_new_authorized_voter skips prior_voters)

🤖 Generated with Claude Code

7layermagik and others added 2 commits February 25, 2026 23:36
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add VoteState4 struct with all V4 fields (inflation/block revenue
collectors, commission as u16 bps, pending delegator rewards, BLS
pubkey). Handle discriminant 3 in all VoteStateVersions switch sites
(unmarshal, marshal, IsInitialized, ConvertToCurrent, LastTimestamp,
NodePubkey). V4 write path serializes as discriminant 3 with no
V1_14_11 fallback on resize failure (returns AccountNotRentExempt per
SIMD-0185). Preserve V4-specific fields through the VoteState working
struct round-trip; first-time V3→V4 conversion defaults match Agave
(inflation_rewards_collector=vote_pubkey, block_revenue_collector=
node_pubkey). Behavioral changes: V4 purges authorized voters at
currentEpoch-1 (retains one extra epoch) and skips PriorVoters
tracking. Update all external consumers (rewards, transaction,
manifest_decoder).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant