Skip to main content

StakesHandle

Struct StakesHandle 

Source
pub struct StakesHandle { /* private fields */ }
Expand description

Handle for builtin programs to access stake cache data and freeze stakes.

This handle provides:

  • Read/write access to the pending (next epoch) stake cache data
  • Layered lookup across baseline, frozen, and pending
  • The ability to freeze the pending stakes into frozen via freeze_stakes()
  • Callback to check if EpochRewards exists for a given epoch

§Architecture: Baseline + Deltas

The stake cache uses a layered architecture:

  • baseline: Complete historical state (empty at genesis, populated during EpochRewards activation)
  • frozen: VecDeque of per-epoch deltas awaiting reward distribution (FIFO order)
  • pending: Current epoch’s changes being accumulated

Lookups search: pending → frozen (newest to oldest) → baseline

§Epoch Semantics

Important: The pending field contains data for the NEXT epoch (i.e., changes being accumulated that will take effect after FreezeStakes). To get the CURRENT epoch’s frozen data for lookups, use frozen.back() instead.

The handle is cached at block level for performance. Since the handle uses shared Arc<RwLock<...>> references, mutations to pending are immediately visible without needing to recreate the handle.

§Thread Safety

StakeCache and StakeHistory wrap their data in Arc<RwLock<...>> internally, allowing safe concurrent access from builtin programs during transaction execution. Mutations to pending are immediately visible to the owning Bank since they share the same Arc.

§Field Access

The baseline, pending, and frozen fields are private to enforce proper layered lookups. Use the provided methods for queries:

  • get_stake_account() - layered lookup for a single stake account
  • get_validator_account() - layered lookup for a single validator account
  • get_all_validator_accounts() - merged view of all validators
  • freeze_stakes() - freeze pending stakes
  • epoch_rewards_exists() - check if EpochRewards account exists for an epoch

Direct field access is only available via #[cfg(test)] accessors for unit tests.

Implementations§

Source§

impl StakesHandle

Source

pub fn new_shared( baseline: StakeCache, pending: StakeCache, frozen: StakeHistory, epoch_rewards_exists_fn: Arc<dyn Fn(u64) -> bool + Send + Sync>, ) -> Self

Create a new stakes handle with shared references.

This shares the same Arc<RwLock<...>> with the Bank, so mutations to pending by builtin programs are immediately visible to the Bank.

The signaling Arcs (epoch_rewards_init, epoch_stakes_frozen) are created internally with default values. This simplifies the API since callers don’t need to manage these internal signaling mechanisms.

§Arguments
  • baseline - The baseline stake cache
  • pending - The pending stake cache for the next epoch
  • frozen - The frozen stake history
  • epoch_rewards_exists_fn - Callback to check if an EpochRewards account exists
Source

pub fn epoch_rewards_exists(&self, epoch: u64) -> bool

Check if an EpochRewards account exists for the given epoch.

Uses the callback provided at construction time to query the StateStore. This allows DistributeRewards to find the first completed frozen epoch that doesn’t yet have an EpochRewards account.

Source

pub fn set_epoch_stakes_frozen(&self)

Signal that epoch stakes have been frozen (FreezeStakes was called).

This sets the epoch_stakes_frozen flag to true to signal that apply_pending_validator_changes_if_needed() should be called by the Bank.

Source

pub fn take_epoch_stakes_frozen(&self) -> bool

Atomically take the epoch_stakes_frozen signal.

This atomically reads and clears the flag, returning true if it was set. Used by finalize_impl() to consume the signal and perform the deferred pending → frozen swap.

Returns true if FreezeStakes was called and the signal hadn’t been consumed yet.

Source

pub fn is_epoch_stakes_frozen(&self) -> bool

Check if FreezeStakes was signaled this block, without consuming the flag.

Used by apply_pending_validator_changes_if_needed() to detect the epoch boundary while leaving the flag set for finalize_impl() to consume.

Source

pub fn get_stake_account_from_pending( &self, pubkey: &Pubkey, ) -> Option<StakeAccount>

Get a stake account starting from pending (next epoch state).

Searches: pending → frozen (newest to oldest) → baseline

Returns Some(account) if found, None if the account doesn’t exist (either never created or was deleted via tombstone).

Source

pub fn get_validator_account_from_pending( &self, pubkey: &Pubkey, ) -> Option<ValidatorAccount>

Get a validator account starting from pending (next epoch state).

Searches: pending → frozen (newest to oldest) → baseline

Returns Some(account) if found, None if the account doesn’t exist (either never created or was deleted via tombstone).

Source

pub fn get_all_validator_accounts_from_pending( &self, ) -> Vec<(Pubkey, ValidatorAccount)>

Get all validator accounts starting from pending (next epoch state).

Returns a vector of (pubkey, account) pairs for all validators, sorted by pubkey. Includes pending changes (next epoch). Note: This is O(baseline_size + total_deltas).

Source

pub fn get_stake_account_from_last_frozen( &self, pubkey: &Pubkey, ) -> Option<StakeAccount>

Get a stake account starting from the last frozen epoch (current epoch state).

Searches: frozen (newest to oldest) → baseline Skips pending (next epoch changes).

Returns Some(account) if found, None if the account doesn’t exist (either never created or was deleted via tombstone).

Source

pub fn get_validator_account_from_last_frozen( &self, pubkey: &Pubkey, ) -> Option<ValidatorAccount>

Get a validator account starting from the last frozen epoch (current epoch state).

Searches: frozen (newest to oldest) → baseline Skips pending (next epoch changes).

Returns Some(account) if found, None if the account doesn’t exist (either never created or was deleted via tombstone).

Source

pub fn get_all_validator_accounts_from_last_frozen( &self, ) -> Vec<(Pubkey, ValidatorAccount)>

Get all validator accounts from the last frozen epoch (current epoch state).

Returns a vector of (pubkey, account) pairs for all validators, sorted by pubkey. Skips pending (next epoch changes). Note: This is O(baseline_size + total_frozen_deltas).

Source

pub fn get_stake_account_from_first_frozen( &self, pubkey: &Pubkey, ) -> Option<StakeAccount>

Get a stake account starting from the first frozen epoch (oldest pending rewards).

Searches: frozen.front() → baseline only Skips all newer frozen epochs and pending.

Returns Some(account) if found, None if the account doesn’t exist (either never created or was deleted via tombstone).

Source

pub fn get_all_stake_accounts_from_pending(&self) -> Vec<(Pubkey, StakeAccount)>

Get all stake accounts starting from pending (next epoch state).

Returns a vector of (pubkey, account) pairs for all stake accounts, sorted by pubkey. Includes pending changes (next epoch). Note: This is O(baseline_size + total_deltas).

This method is used for operations that need to check all stake accounts including the most recent changes (e.g., checking validator references during Withdraw).

Source

pub fn get_all_stake_accounts_from_first_frozen( &self, ) -> Vec<(Pubkey, StakeAccount)>

Get all stake accounts from the first frozen epoch (oldest pending rewards).

Returns a vector of (pubkey, account) pairs for all stake accounts, sorted by pubkey. Skips all newer frozen epochs and pending.

This method is used by reward calculation to iterate over all stake accounts that were active at the time rewards were frozen (baseline + first frozen delta).

Source

pub fn get_all_stake_accounts_from_frozen_epoch( &self, target_epoch: Epoch, ) -> Vec<(Pubkey, StakeAccount)>

Get all stake accounts from baseline + frozen deltas up to (and including) the specified epoch.

Lookups: baseline + frozen deltas where delta.epoch <= target_epoch

Source

pub fn get_all_stake_accounts_from_baseline( &self, ) -> Vec<(Pubkey, StakeAccount)>

Get all stake accounts from the baseline only.

Returns a vector of (pubkey, account) pairs for all stake accounts in baseline, sorted by pubkey. Does NOT include frozen or pending data.

This is used after the frozen.front() has been merged into baseline, for baseline-based reward calculation. The baseline contains the complete state of the epoch being rewarded after merge.

Source

pub fn get_all_validator_accounts_from_baseline( &self, ) -> Vec<(Pubkey, ValidatorAccount)>

Get all validator accounts from the baseline only.

Returns a vector of (pubkey, account) pairs for all validators in baseline, sorted by pubkey. Does NOT include frozen or pending data.

This is used after the frozen.front() has been merged into baseline, for baseline-based reward calculation.

Source

pub fn freeze_stakes(&self)

Freeze the pending stake cache data.

This performs an O(1) swap of the pending stake cache data using std::mem::take() and pushes it to the back of the frozen queue. This is typically called by the ValidatorRegistry program’s FreezeStakes instruction to capture the validator set at a specific point.

Note: Since the handle uses shared Arc<RwLock<...>> references, the frozen data and updated pending epoch are immediately visible to all handle instances.

To access the frozen validator data after calling this method, use get_all_validator_accounts_from_last_frozen().

Source

pub fn pending_epoch(&self) -> Epoch

Get the epoch of the pending (next) stake cache.

Source

pub fn set_pending_epoch(&self, epoch: Epoch)

Set the epoch of the pending stake cache.

Source

pub fn set_pending_timestamp(&self, timestamp: u64)

Set the timestamp of the pending stake cache.

Source

pub fn last_frozen_timestamp(&self) -> Option<u64>

Get the timestamp of the last frozen epoch (current epoch’s effective state).

Returns None if no frozen snapshots exist yet.

Source

pub fn push_frozen(&self, data: StakeCacheData)

Push a new frozen snapshot to the history.

Source

pub fn frozen_len(&self) -> usize

Get the number of frozen snapshots in the history.

Source

pub fn front_frozen_epoch(&self) -> Option<Epoch>

Get the epoch of the oldest frozen snapshot (front of the queue).

Returns None if no frozen snapshots exist.

Source

pub fn request_epoch_rewards_init(&self, epoch: Epoch, total_rewards: u64)

Request epoch rewards initialization.

This is called by the DistributeRewards instruction to signal that the Bank should create an EpochRewards account. The Bank checks for the request after transaction execution via take_epoch_rewards_init_request().

§Arguments
  • epoch - The epoch for which rewards are being distributed
  • total_rewards - The total rewards to distribute (hardcoded for MVP)
Source

pub fn take_epoch_rewards_init_request(&self) -> Option<EpochRewardsInitRequest>

Take the epoch rewards initialization request, clearing it.

This is called by the Bank after transaction execution to check if epoch rewards init was requested. The Bank uses the returned data to create the EpochRewards account.

Returns Some(request) if a request was pending, None otherwise. After this call, epoch_rewards_init will be None.

Source

pub fn is_epoch_rewards_init_pending(&self) -> bool

Check if an epoch rewards initialization request is pending.

This is used by DistributeRewards to fail if a signal is already set for the current block (prevents multiple DistributeRewards in same block).

Returns true if a request is pending, false otherwise. Does NOT consume the request (unlike take_epoch_rewards_init_request).

Source

pub fn completed_frozen_epochs(&self) -> Vec<Epoch>

Get completed frozen epochs (excludes the last/current epoch).

Returns epoch numbers for all frozen entries except the last one, which represents the currently ongoing epoch. These are epochs that have completed and are eligible for reward distribution.

Returns empty if frozen has 0 or 1 entries (need at least 2 to have completed epochs).

Source

pub fn is_validator_referenced( &self, validator: &Pubkey, validator_info: &ValidatorInfo, last_freeze_timestamp: u64, current_timestamp: u64, ) -> bool

Check if any stake account references the given validator pubkey whose unbonding period is NOT yet complete.

This performs an O(n) search over all stake accounts starting from pending → frozen → baseline. Uses Rayon’s parallel iterator for better performance on multi-core systems.

A stake account is considered to “reference” the validator if:

  • It has validator == Some(target_validator), AND
  • Either:
    • It is active (no deactivation_requested), OR
    • It is still unbonding (unbonding conditions not yet met)

Stake accounts whose unbonding is complete are NOT considered as referencing the validator, since they can be fully withdrawn or reactivated to another validator.

§Unbonding Completion Conditions

Unbonding is complete when BOTH conditions are met:

  1. State transition: deactivation_timestamp < last_freeze_timestamp (at least one FreezeStakes has occurred since deactivation)
  2. Duration enforcement: deactivation_timestamp + unbonding_period < current_timestamp (the unbonding period has actually elapsed)
§Arguments
  • validator - The validator pubkey to check
  • validator_info - The validator’s info (used to compute unbonding end via end_of_unbonding)
  • last_freeze_timestamp - When FreezeStakes was last called (epoch boundary)
  • current_timestamp - Current block timestamp from Clock sysvar (in milliseconds)
§Returns

true if at least one stake account references the validator and is either active or still unbonding, false otherwise.

§Performance

This is an expensive O(n) operation that should only be called when needed (e.g., during Withdraw when checking if a validator can be fully drained).

Source

pub fn has_locked_stakers( &self, validator: &Pubkey, lockup_period: u64, current_timestamp: u64, ) -> bool

Check if any stake account delegated to the given validator is still within its lockup period.

This performs an O(n) search over all stake accounts starting from pending → frozen → baseline. Uses Rayon’s parallel iterator for better performance on multi-core systems.

A staker is considered “locked” if ALL of the following are true:

  • It has validator == Some(target_validator) (delegated to this validator)
  • It has activation_requested == Some(timestamp) (was activated)
  • activation_requested + lockup_period > current_timestamp (lockup hasn’t expired)

Self-bonds are excluded from lockup checks to prevent the validator from being unable to change commission rates or shut down when only the self-bond exists.

§Arguments
  • validator - The validator pubkey to check
  • lockup_period - The validator’s lockup period in milliseconds
  • current_timestamp - Current block timestamp from Clock sysvar (in milliseconds)
§Returns

true if at least one stake account is delegated to the validator and still within its lockup period, false otherwise.

Source

pub fn is_validator_referenced_excluding_self_bond( &self, validator_pubkey: &Pubkey, validator_info: &ValidatorInfo, last_freeze_timestamp: u64, current_timestamp: u64, ) -> bool

Check if a validator is referenced by any stake accounts (excluding the self-bond).

This variant excludes the self-bond PDA from the check to prevent circular logic where the self-bond cannot be deactivated because its existence always makes is_validator_referenced() return true.

§Arguments
  • validator_pubkey - The validator pubkey to check
  • validator_info - The validator’s info (used to compute unbonding end via end_of_unbonding)
  • last_freeze_timestamp - When FreezeStakes was last called (epoch boundary)
  • current_timestamp - Current block timestamp from Clock sysvar (in milliseconds)
§Returns

true if at least one non-self-bond stake account references the validator

Source

pub fn insert_stake_account(&self, pubkey: Pubkey, account: StakeAccount)

Insert a stake account into the pending cache.

Source

pub fn insert_validator_account( &self, pubkey: Pubkey, account: ValidatorAccount, )

Insert a validator account into the pending cache.

Trait Implementations§

Source§

impl Clone for StakesHandle

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for StakesHandle

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for StakesHandle

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DynClone for T
where T: Clone,

Source§

fn __clone_box(&self, _: Private) -> *mut ()

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V