STAKING — Solana × Partitioned Rewards

When Solana Staking Rewards Arrive, and Why Yours Was Zero

6 min read
SolanaStakingRewards

The epoch ended an hour ago. Your friend’s stake account already shows its reward; yours does not. Or it shows one, and it is zero, with no explanation anywhere. Both are normal, and both are computable — the node knows exactly which block your reward lands in and exactly why it paid nothing, and it keeps neither fact anywhere you can see it.

This post is both facts, from the runtime.


Rewards are paid in a window, not at the boundary

Staking rewards are not credited at the epoch boundary. They are credited over a window of blocks at the start of the new epoch, one partition of stake accounts per block, because paying a million accounts in a single block would stall the network.

The schedule is fixed by three rules:

Distribution begins one block after the first block of the epoch.

Each block pays a fixed number of stake accounts — 4,096 at the original 400 ms slot, 2,560 at the 250 ms slot in force when this was written, 2,048 at 200 ms.

The whole distribution must finish within the first 10% of the epoch. The number of partitions is the number of rewarded accounts divided by the per-block count, clamped to at most a tenth of the epoch’s slots.

With a million rewarded stake accounts at 2,560 per block, that is about 390 blocks — under two minutes at 250 ms slots. A stake account paid in partition 300 sees its reward about a minute and a quarter after one paid in partition 1.


Which block is yours: a hash of your address

The partition a stake account lands in is not random and not by size. It is a hash of the stake account’s address, keyed by the blockhash of the epoch’s first block, taken modulo the number of partitions. The runtime computes it to pay you; the RPC recomputes it to answer getInflationReward, which is how it knows which block to look in.

Everything in that formula is public the moment the epoch’s first block lands: the parent blockhash is in the block, the partition count follows from the number of rewarded accounts. Your reward block is therefore knowable at the start of the window, before it arrives. A wallet that shows “reward pending” could show “reward in block N, about 40 seconds away”. None does.

Worth knowing: ask the RPC for a reward during the window and it refuses with error -32017, “epoch rewards period active”, and hands back the block height at which the distribution completes. That number is the end of the window for everyone, not your partition.


Nine reasons the node pays zero

When the reward calculation skips a stake account, it records a reason. There are nine. The runtime discards them; here is what each one means for you.

  • DisabledInflation — inflation is off for the cluster. Not a mainnet case.
  • JustActivated — the stake became effective this epoch and has no credits yet. Your first reward is next epoch, and it covers only the portion that was effective. See the cool-down post for why that portion can be partial.
  • ZeroPoints — the stake earned no points, which happens when it had no effective lamports during the epoch or its validator earned no credits.
  • ZeroPointValue — the epoch’s total points were zero, so no reward could be priced. Rare; a cluster-level condition.
  • ZeroReward — the computed reward rounded to zero lamports. Dust stakes on low-credit validators hit this.
  • ZeroCreditsAndReturnZero, ZeroCreditsAndReturnCurrent, ZeroCreditsAndReturnRewound — the validator’s vote account earned no new credits this epoch. The middle one is the most common and the source code annotates it with a single word: delinquent. Your validator did not vote, and the stake delegated to it earned nothing.
  • TooEarlyUnfairSplit — the reward was so small that splitting it between you and the validator’s commission would have rounded one side to zero, so the runtime paid neither and did not advance your credits. Under tower voting your points carry over; under Alpenglow the residual lamport goes to the voter instead.

The practical reading: a zero reward on an account that had a reward last epoch almost always means one of the three “zero credits” cases — your validator was offline. A zero reward on a newly delegated account means JustActivated. A zero reward on a tiny account means ZeroReward or the unfair split.


The commission you were charged is two epochs old

One more thing the runtime does that no dashboard shows. The commission applied to your reward is not the validator’s current rate. It is the rate from the vote state saved a full epoch before the rewarded epoch — the source comment says this is “to prevent last minute commission rugs”. A validator that raises its commission today does not take the higher cut from this epoch’s reward or the next one’s.

That protection predates the newer rule that delays all commission changes by an epoch, and the two now agree. It also means the number on your validator’s profile page today is not the number in your last reward, if it changed recently.


Staking Rewards: Questions People Actually Ask

When are Solana staking rewards paid?

Over a window of blocks at the start of each epoch, starting one block after the epoch’s first block and finishing within the first 10% of the epoch. Each block pays a fixed partition of stake accounts.

Which block will my reward be in?

The partition is a hash of your stake account’s address keyed by the epoch’s first blockhash, modulo the number of partitions. It is computable the moment the epoch’s first block lands.

Why did I get zero staking rewards this epoch?

Most often because the validator earned no vote credits that epoch (it was delinquent), or because the stake only became effective this epoch. The runtime records one of nine reasons; the common ones are those two.

Why was my reward smaller than the validator’s advertised commission implies?

The commission applied is the rate saved a full epoch before the rewarded epoch, not the current one. A recent change does not apply to the reward you just received.


What to check, in order

Was the account effective last epoch? If it was activating, the first reward is partial and arrives next epoch.

Did the validator vote? Zero credits in its vote account for the epoch means a zero reward for everyone delegated to it, with no notice.

Is the window still open? If the RPC returns -32017, the distribution is in progress; your partition may simply be later than your friend’s.

Is the stake tiny? Rewards below one lamport, or too small to split with the commission, are not paid.

Every one of these is a plain account read. The node computed the answer when it skipped you; it just did not write it down.

The commission taken from that reward has its own delays — two commissions, one epoch delay — and under Alpenglow the reward itself changes shape: half of every vote reward goes to the leader.

Constants read from the Agave validator source at commit beee69b958: runtime/src/bank/partitioned_epoch_rewards/mod.rs, runtime/src/inflation_rewards/points.rs and rpc/src/rpc.rs. Per-block counts follow the slot regime in force when this was written.

xroot.devOn-chain research, published in full
𝕏 @xrootdev
← Back to all notes