Skip to main content

rialo_stake_cache_interface/
lib.rs

1// Copyright (c) Subzero Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Shared types for the Stake Cache.
5//!
6//! This crate provides types that are shared between `svm-execution` and
7//! `rialo-s-program-runtime`, allowing builtin programs to access and
8//! manipulate stake cache data during transaction execution.
9//!
10//! ## Reward Distribution Flow
11//!
12//! The reward distribution follows a specific flow:
13//!
14//! 1. **FreezeStakes**: At epoch boundary, push pending to frozen (creates epoch snapshot)
15//! 2. **DistributeRewards**: Creates EpochRewards account (initially inactive/queued)
16//! 3. **Activation**: When EpochRewards becomes active:
17//!    - `pop_front_and_merge_to_baseline()` is called
18//!    - frozen.front() is merged into baseline
19//!    - Rewards are calculated from baseline only
20//! 4. **Distribution**: Rewards distributed across partitions
21//! 5. **Completion**: EpochRewards marked inactive
22//!
23//! ## Reward Eligibility
24//!
25//! Stakes are eligible for rewards based on the following checks:
26//! - `activation_requested.is_some()` → stake was activated
27//! - deactivation not yet effective (timestamp-based check against epoch boundary)
28//! - `validator.is_some()` → stake is delegated to a validator
29//!
30//! ## Lookup Methods
31//!
32//! Different lookup methods for different use cases:
33//!
34//! - **From Pending** (`get_*_from_pending`): Includes next epoch changes
35//! - **From Last Frozen** (`get_*_from_last_frozen`): Current epoch's effective state
36//! - **From First Frozen** (`get_*_from_first_frozen`): Oldest pending rewards epoch
37//! - **From Baseline** (`get_*_from_baseline`): Post-merge state for reward calculation
38
39use std::{
40    collections::{HashMap, HashSet, VecDeque},
41    sync::{
42        atomic::{AtomicBool, Ordering},
43        RwLock,
44    },
45};
46
47use rayon::prelude::*;
48use rialo_s_account::ReadableAccount;
49use rialo_s_clock::Epoch;
50use rialo_s_pubkey::Pubkey;
51use rialo_s_type_overrides::sync::Arc;
52use rialo_stake_manager_interface::instruction::StakeInfo;
53// Re-export ValidatorInfo so downstream crates (e.g., rialo-s-program-runtime) can reference the
54// type without adding a direct dependency on rialo-validator-registry-interface.
55pub use rialo_validator_registry_interface::instruction::ValidatorInfo;
56
57/// PDA derivation helpers for self-bond accounts.
58pub mod pda;
59pub use pda::{derive_self_bond_address, derive_self_bond_address_with_bump, SELF_BOND_SEED};
60
61/// A cache of stake and validator accounts.
62///
63/// This wraps `StakeCacheData` in `Arc<RwLock<...>>` to allow thread-safe shared
64/// access during parallel transaction execution. The Arc allows the same data
65/// to be shared between the Bank and StakesHandle, so mutations to pending
66/// stake data by builtin programs are visible to the Bank.
67#[derive(Debug, Clone)]
68pub struct StakeCache(Arc<RwLock<StakeCacheData>>);
69
70impl Default for StakeCache {
71    fn default() -> Self {
72        Self(Arc::new(RwLock::new(StakeCacheData::default())))
73    }
74}
75
76impl StakeCache {
77    /// Create a new empty stake cache.
78    pub fn new() -> Self {
79        Self::default()
80    }
81
82    /// Create a stake cache with the given data.
83    pub fn with_data(data: StakeCacheData) -> Self {
84        Self(Arc::new(RwLock::new(data)))
85    }
86
87    /// Create a stake cache from an existing Arc (for sharing references).
88    pub fn from_arc(arc: Arc<RwLock<StakeCacheData>>) -> Self {
89        Self(arc)
90    }
91
92    /// Get a clone of the inner Arc for sharing.
93    pub fn arc_clone(&self) -> Arc<RwLock<StakeCacheData>> {
94        Arc::clone(&self.0)
95    }
96
97    /// Acquire a read lock on the inner data.
98    pub fn read(&self) -> std::sync::RwLockReadGuard<'_, StakeCacheData> {
99        self.0.read().expect("Failed to acquire read lock")
100    }
101
102    /// Acquire a write lock on the inner data.
103    pub fn write(&self) -> std::sync::RwLockWriteGuard<'_, StakeCacheData> {
104        self.0.write().expect("Failed to acquire write lock")
105    }
106
107    /// Get a stake account by pubkey.
108    ///
109    /// Note: This is a single-layer lookup on just this cache.
110    /// For layered lookup across baseline/frozen/pending, use `StakesHandle::get_stake_account`.
111    pub fn get_stake_account(&self, pubkey: &Pubkey) -> Option<StakeAccount> {
112        let data = self.read();
113        data.stake_accounts.get(pubkey).and_then(|opt| opt.clone())
114    }
115
116    /// Get a validator account by pubkey.
117    ///
118    /// Note: This is a single-layer lookup on just this cache.
119    /// For layered lookup across baseline/frozen/pending, use `StakesHandle::get_validator_account`.
120    pub fn get_validator_account(&self, pubkey: &Pubkey) -> Option<ValidatorAccount> {
121        let data = self.read();
122        data.validator_accounts
123            .get(pubkey)
124            .and_then(|opt| opt.clone())
125    }
126
127    /// Get all validator accounts from this cache (single layer).
128    ///
129    /// Note: This is a single-layer lookup. For merged view across all layers,
130    /// use `StakesHandle::get_all_validator_accounts`.
131    pub fn get_all_validator_accounts(&self) -> Vec<(Pubkey, ValidatorAccount)> {
132        let data = self.read();
133        data.validator_accounts
134            .iter()
135            .filter_map(|(k, v)| v.as_ref().map(|account| (*k, account.clone())))
136            .collect()
137    }
138
139    /// Check if a stake account exists in this cache (single layer).
140    pub fn contains_stake_account(&self, pubkey: &Pubkey) -> bool {
141        let data = self.read();
142        matches!(data.stake_accounts.get(pubkey), Some(Some(_)))
143    }
144
145    /// Check if a validator account exists in this cache (single layer).
146    pub fn contains_validator_account(&self, pubkey: &Pubkey) -> bool {
147        let data = self.read();
148        matches!(data.validator_accounts.get(pubkey), Some(Some(_)))
149    }
150
151    /// Insert or update a stake account.
152    ///
153    /// Also tracks the pubkey as modified for persistence.
154    pub fn insert_stake_account(&self, pubkey: Pubkey, account: StakeAccount) {
155        let mut data = self.write();
156        data.stake_accounts.insert(pubkey, Some(account));
157        data.modified_stake_pubkeys.insert(pubkey);
158    }
159
160    /// Insert or update a validator account.
161    ///
162    /// Also tracks the pubkey as modified for persistence.
163    pub fn insert_validator_account(&self, pubkey: Pubkey, account: ValidatorAccount) {
164        let mut data = self.write();
165        data.validator_accounts.insert(pubkey, Some(account));
166        data.modified_validator_pubkeys.insert(pubkey);
167    }
168
169    /// Insert a tombstone for a stake account (marks as deleted).
170    ///
171    /// Also tracks the pubkey as modified for persistence.
172    pub fn tombstone_stake_account(&self, pubkey: Pubkey) {
173        let mut data = self.write();
174        data.stake_accounts.insert(pubkey, None);
175        data.modified_stake_pubkeys.insert(pubkey);
176    }
177
178    /// Insert a tombstone for a validator account (marks as deleted).
179    ///
180    /// Also tracks the pubkey as modified for persistence.
181    pub fn tombstone_validator_account(&self, pubkey: Pubkey) {
182        let mut data = self.write();
183        data.validator_accounts.insert(pubkey, None);
184        data.modified_validator_pubkeys.insert(pubkey);
185    }
186
187    /// Get the epoch of this cache.
188    pub fn epoch(&self) -> Epoch {
189        self.read().epoch
190    }
191
192    /// Get the timestamp of this cache.
193    pub fn timestamp(&self) -> u64 {
194        self.read().timestamp
195    }
196
197    /// Set the epoch of this cache.
198    pub fn set_epoch(&self, epoch: Epoch) {
199        self.write().epoch = epoch;
200    }
201
202    /// Set the timestamp of this cache.
203    pub fn set_timestamp(&self, timestamp: u64) {
204        self.write().timestamp = timestamp;
205    }
206
207    /// Check an account and store it in the appropriate cache if it belongs to
208    /// StakeManager or ValidatorRegistry programs.
209    ///
210    /// - If the account has zero kelvins, it is evicted from the cache (tombstoned)
211    /// - If the account is owned by StakeManager, it is stored in stake_accounts
212    /// - If the account is owned by ValidatorRegistry, it is stored in validator_accounts
213    pub fn check_and_update(&self, pubkey: &Pubkey, account: &impl ReadableAccount) {
214        let owner = account.owner();
215
216        // Zero kelvin accounts should be marked as tombstones (None) in the delta
217        if account.kelvins() == 0 {
218            if rialo_stake_manager_interface::check_id(owner) {
219                // Insert tombstone (None) to mark deletion in this epoch's delta
220                self.tombstone_stake_account(*pubkey);
221            } else if rialo_validator_registry_interface::check_id(owner) {
222                // Insert tombstone (None) to mark deletion in this epoch's delta
223                self.tombstone_validator_account(*pubkey);
224            }
225        } else if rialo_stake_manager_interface::check_id(owner) {
226            // Handle StakeManager accounts
227            if let Ok(stake_info) = bincode::deserialize::<StakeInfo>(account.data()) {
228                self.insert_stake_account(
229                    *pubkey,
230                    StakeAccount {
231                        kelvins: account.kelvins(),
232                        data: stake_info,
233                    },
234                );
235            }
236        } else if rialo_validator_registry_interface::check_id(owner) {
237            // Handle ValidatorRegistry accounts
238            if let Ok(validator_info) = bincode::deserialize::<ValidatorInfo>(account.data()) {
239                self.insert_validator_account(
240                    *pubkey,
241                    ValidatorAccount {
242                        kelvins: account.kelvins(),
243                        data: validator_info,
244                    },
245                );
246            }
247        }
248    }
249}
250
251/// Data structure holding the cached stake and validator accounts.
252///
253/// Uses `HashMap<Pubkey, Option<T>>` to support the delta-based persistence model:
254/// - `Some(account)` = account was added or updated
255/// - `None` = account was deleted (tombstone)
256///
257/// In `baseline`, values are always `Some(...)` since it represents complete state.
258/// In `pending` and `frozen` deltas, `None` indicates deletion.
259#[derive(Debug, Default, Clone)]
260pub struct StakeCacheData {
261    /// Map of stake accounts by public key.
262    /// `None` value indicates a tombstone (account was deleted during this epoch).
263    pub stake_accounts: HashMap<Pubkey, Option<StakeAccount>>,
264    /// Map of validator accounts by public key.
265    /// `None` value indicates a tombstone (account was deleted during this epoch).
266    pub validator_accounts: HashMap<Pubkey, Option<ValidatorAccount>>,
267    /// The epoch counter when this snapshot was taken.
268    pub epoch: Epoch,
269    /// The block's Unix timestamp (in milliseconds) when this snapshot was taken.
270    /// This is set when FreezeStakes is called and represents the epoch boundary.
271    pub timestamp: u64,
272    /// Set of stake account pubkeys modified during the current block.
273    /// Used to track which accounts need to be persisted to the deltas CF.
274    /// This is cleared after each `finalize()` call.
275    pub modified_stake_pubkeys: HashSet<Pubkey>,
276    /// Set of validator account pubkeys modified during the current block.
277    /// Used to track which accounts need to be persisted to the deltas CF.
278    /// This is cleared after each `finalize()` call.
279    pub modified_validator_pubkeys: HashSet<Pubkey>,
280    /// Block timestamp when consensus adopted this epoch's stakes via Handover.
281    /// - `None` = not yet adopted (FreezeStakes guard will block the next freeze)
282    /// - `Some(0)` = genesis epoch (adopted at network start, rewards zeroed)
283    /// - `Some(ts)` = adopted at timestamp `ts` (Handover was processed)
284    pub consensus_adopted_at: Option<u64>,
285}
286
287impl StakeCacheData {
288    /// Drain the modified pubkey sets, returning the pubkeys and clearing the sets.
289    ///
290    /// This is called by `StateStore::finalize()` to get the list of accounts
291    /// that need to be persisted to the deltas CF. After this call, both
292    /// `modified_stake_pubkeys` and `modified_validator_pubkeys` will be empty.
293    ///
294    /// Returns a tuple of `(stake_pubkeys, validator_pubkeys)`.
295    pub fn drain_modified(&mut self) -> (HashSet<Pubkey>, HashSet<Pubkey>) {
296        let stake_pubkeys = std::mem::take(&mut self.modified_stake_pubkeys);
297        let validator_pubkeys = std::mem::take(&mut self.modified_validator_pubkeys);
298        (stake_pubkeys, validator_pubkeys)
299    }
300
301    /// Check if there are any modified accounts pending persistence.
302    pub fn has_modified(&self) -> bool {
303        !self.modified_stake_pubkeys.is_empty() || !self.modified_validator_pubkeys.is_empty()
304    }
305}
306
307/// A history of frozen stake cache snapshots across epochs.
308///
309/// This wraps `VecDeque<StakeCacheData>` in `Arc<RwLock<...>>` to allow thread-safe
310/// shared access. The Arc allows the same history to be shared between the Bank
311/// and StakesHandle.
312///
313/// This maintains a queue of stake snapshots, with the oldest at the front
314/// and the most recent at the back. The ValidatorRegistry builtin pushes
315/// new snapshots, and the Bank pops completed epochs after reward distribution.
316#[derive(Debug, Clone)]
317pub struct StakeHistory(Arc<RwLock<VecDeque<StakeCacheData>>>);
318
319impl Default for StakeHistory {
320    fn default() -> Self {
321        Self(Arc::new(RwLock::new(VecDeque::new())))
322    }
323}
324
325impl StakeHistory {
326    /// Create a new empty stake history.
327    pub fn new() -> Self {
328        Self::default()
329    }
330
331    /// Create a stake history with an initial entry.
332    pub fn with_entry(data: StakeCacheData) -> Self {
333        let mut deque = VecDeque::new();
334        deque.push_back(data);
335        Self(Arc::new(RwLock::new(deque)))
336    }
337
338    /// Create a stake history from an existing Arc (for sharing references).
339    pub fn from_arc(arc: Arc<RwLock<VecDeque<StakeCacheData>>>) -> Self {
340        Self(arc)
341    }
342
343    /// Get a clone of the inner Arc for sharing.
344    pub fn arc_clone(&self) -> Arc<RwLock<VecDeque<StakeCacheData>>> {
345        Arc::clone(&self.0)
346    }
347
348    /// Acquire a read lock on the inner data.
349    pub fn read(&self) -> std::sync::RwLockReadGuard<'_, VecDeque<StakeCacheData>> {
350        self.0.read().expect("Failed to acquire read lock")
351    }
352
353    /// Acquire a write lock on the inner data.
354    pub fn write_lock(&self) -> std::sync::RwLockWriteGuard<'_, VecDeque<StakeCacheData>> {
355        self.0.write().expect("Failed to acquire write lock")
356    }
357
358    /// Push a new snapshot to the back of the history.
359    pub fn push_back(&self, data: StakeCacheData) {
360        self.0
361            .write()
362            .expect("Failed to acquire lock")
363            .push_back(data);
364    }
365
366    /// Pop the oldest snapshot from the front of the history.
367    pub fn pop_front(&self) -> Option<StakeCacheData> {
368        self.0.write().expect("Failed to acquire lock").pop_front()
369    }
370
371    /// Get the number of snapshots in the history.
372    pub fn len(&self) -> usize {
373        self.0.read().expect("Failed to acquire lock").len()
374    }
375
376    /// Check if the history is empty.
377    pub fn is_empty(&self) -> bool {
378        self.0.read().expect("Failed to acquire lock").is_empty()
379    }
380
381    /// Get a clone of the oldest snapshot (front).
382    pub fn front(&self) -> Option<StakeCacheData> {
383        self.0
384            .read()
385            .expect("Failed to acquire lock")
386            .front()
387            .cloned()
388    }
389
390    /// Get a clone of the newest snapshot (back).
391    ///
392    /// This returns the CURRENT epoch's frozen stake data. In normal operation,
393    /// this is never `None` because Bank initialization guarantees at least one
394    /// entry exists after genesis/register_validators.
395    ///
396    /// Use this for lookups that need the current epoch's effective stake state
397    /// (as opposed to `StakesHandle::pending` which is the next epoch being accumulated).
398    pub fn back(&self) -> Option<StakeCacheData> {
399        self.0
400            .read()
401            .expect("Failed to acquire lock")
402            .back()
403            .cloned()
404    }
405
406    /// Iterate over all snapshots from oldest to newest, returning cloned data.
407    ///
408    /// Note: This clones all entries. For large histories, consider accessing
409    /// specific entries via `front()` or `back()` instead.
410    pub fn iter_cloned(&self) -> Vec<StakeCacheData> {
411        self.0
412            .read()
413            .expect("Failed to acquire lock")
414            .iter()
415            .cloned()
416            .collect()
417    }
418}
419
420/// Represents a stake account with its data.
421#[derive(Debug, Clone)]
422pub struct StakeAccount {
423    /// The kelvins balance of the stake account.
424    pub kelvins: u64,
425    /// The deserialized stake info.
426    pub data: StakeInfo,
427}
428
429/// Represents a validator account with its data.
430#[derive(Debug, Clone)]
431pub struct ValidatorAccount {
432    /// The kelvins balance of the validator account.
433    pub kelvins: u64,
434    /// The deserialized validator info.
435    pub data: ValidatorInfo,
436}
437
438/// Handle for builtin programs to access stake cache data and freeze stakes.
439///
440/// This handle provides:
441/// - Read/write access to the pending (next epoch) stake cache data
442/// - Layered lookup across baseline, frozen, and pending
443/// - The ability to freeze the pending stakes into frozen via `freeze_stakes()`
444/// - Callback to check if EpochRewards exists for a given epoch
445///
446/// # Architecture: Baseline + Deltas
447///
448/// The stake cache uses a layered architecture:
449/// - **baseline**: Complete historical state (empty at genesis, populated during EpochRewards activation)
450/// - **frozen**: VecDeque of per-epoch deltas awaiting reward distribution (FIFO order)
451/// - **pending**: Current epoch's changes being accumulated
452///
453/// Lookups search: pending → frozen (newest to oldest) → baseline
454///
455/// # Epoch Semantics
456///
457/// **Important:** The `pending` field contains data for the NEXT epoch (i.e., changes being
458/// accumulated that will take effect after FreezeStakes). To get the CURRENT epoch's frozen
459/// data for lookups, use `frozen.back()` instead.
460///
461/// The handle is cached at block level for performance. Since the handle uses shared
462/// `Arc<RwLock<...>>` references, mutations to pending are immediately visible without
463/// needing to recreate the handle.
464///
465/// # Thread Safety
466///
467/// `StakeCache` and `StakeHistory` wrap their data in `Arc<RwLock<...>>` internally,
468/// allowing safe concurrent access from builtin programs during transaction execution.
469/// Mutations to `pending` are immediately visible to the owning Bank since they share
470/// the same Arc.
471///
472/// # Field Access
473///
474/// The `baseline`, `pending`, and `frozen` fields are private to enforce proper layered lookups.
475/// Use the provided methods for queries:
476/// - `get_stake_account()` - layered lookup for a single stake account
477/// - `get_validator_account()` - layered lookup for a single validator account
478/// - `get_all_validator_accounts()` - merged view of all validators
479/// - `freeze_stakes()` - freeze pending stakes
480/// - `epoch_rewards_exists()` - check if EpochRewards account exists for an epoch
481///
482/// Direct field access is only available via `#[cfg(test)]` accessors for unit tests.
483pub struct StakesHandle {
484    /// Complete state at historical epoch boundary (for fallback lookups).
485    ///
486    /// At genesis, this is empty. After EpochRewards activation, it contains all accounts
487    /// that existed before the oldest epoch still awaiting reward distribution.
488    /// Values are always `Some(...)` in the baseline (no tombstones).
489    baseline: StakeCache,
490
491    /// Stake cache data for the NEXT epoch (pending/accumulating changes).
492    ///
493    /// This is a mutable working copy that accumulates stake and validator account
494    /// modifications throughout the epoch. These changes will become effective after
495    /// the next FreezeStakes call. For current epoch lookups (the frozen effective
496    /// state), use `frozen.back()` instead.
497    ///
498    /// The `StakeCache` wrapper contains `Arc<RwLock<...>>` internally, allowing
499    /// builtin programs to mutate the pending stake data during transaction execution,
500    /// with changes visible to the Bank.
501    pending: StakeCache,
502
503    /// Frozen snapshots for epochs awaiting reward distribution (FIFO order).
504    ///
505    /// Each entry contains ONLY the accounts that changed during that epoch
506    /// (delta, not full state). `Some(account)` = added/updated, `None` = deleted.
507    /// The oldest entry is at the front, the newest at the back.
508    ///
509    /// The `StakeHistory` wrapper contains `Arc<RwLock<...>>` internally.
510    frozen: StakeHistory,
511
512    /// Data for epoch rewards initialization (epoch number and total rewards).
513    /// Set by `request_epoch_rewards_init()`, consumed by `take_epoch_rewards_init_request()`.
514    epoch_rewards_init: Arc<RwLock<Option<EpochRewardsInitRequest>>>,
515
516    /// Signal that FreezeStakes has been called this epoch.
517    /// Set to true by `freeze_stakes()`, consumed by
518    /// `take_epoch_stakes_frozen()` in Bank's `apply_pending_validator_changes_if_needed()`.
519    ///
520    /// This signal is used to trigger the application of pending validator changes
521    /// (e.g., new_commission_rate → commission_rate) at the epoch boundary, even
522    /// when DistributeRewards hasn't run yet.
523    epoch_stakes_frozen: Arc<AtomicBool>,
524
525    /// Callback to check if an EpochRewards account exists for a given epoch.
526    /// Provided by Bank with access to StateStore. Used by DistributeRewards
527    /// to find the first completed frozen epoch without an EpochRewards account.
528    /// Set at construction time, immutable afterwards.
529    epoch_rewards_exists_fn: Arc<dyn Fn(u64) -> bool + Send + Sync>,
530
531    /// Signal carrying the block timestamp when a Handover admin transaction was
532    /// detected. Set by `signal_handover()` in the ExecutionEngine, consumed by
533    /// `take_handover()` in `Bank::finalize_impl()`.
534    handover_ts: Arc<RwLock<Option<u64>>>,
535}
536
537/// Request data for epoch rewards initialization.
538/// Used to pass information from DistributeRewards instruction to Bank.
539#[derive(Debug, Clone)]
540pub struct EpochRewardsInitRequest {
541    /// The epoch for which rewards are being distributed.
542    pub epoch: Epoch,
543    /// The total rewards to distribute (hardcoded for MVP).
544    pub total_rewards: u64,
545}
546
547impl std::fmt::Debug for StakesHandle {
548    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
549        f.debug_struct("StakesHandle")
550            .field("baseline", &self.baseline)
551            .field("pending", &self.pending)
552            .field("frozen", &self.frozen)
553            .field("epoch_rewards_init", &self.epoch_rewards_init)
554            .field("epoch_stakes_frozen", &self.epoch_stakes_frozen)
555            .field("epoch_rewards_exists_fn", &"<callback>")
556            .finish()
557    }
558}
559
560impl Clone for StakesHandle {
561    fn clone(&self) -> Self {
562        Self {
563            baseline: self.baseline.clone(),
564            pending: self.pending.clone(),
565            frozen: self.frozen.clone(),
566            epoch_rewards_init: self.epoch_rewards_init.clone(),
567            epoch_stakes_frozen: Arc::clone(&self.epoch_stakes_frozen),
568            epoch_rewards_exists_fn: Arc::clone(&self.epoch_rewards_exists_fn),
569            handover_ts: self.handover_ts.clone(),
570        }
571    }
572}
573
574impl Default for StakesHandle {
575    fn default() -> Self {
576        Self {
577            baseline: StakeCache::default(),
578            pending: StakeCache::default(),
579            frozen: StakeHistory::default(),
580            epoch_rewards_init: Arc::new(RwLock::new(None)),
581            epoch_stakes_frozen: Arc::new(AtomicBool::new(false)),
582            epoch_rewards_exists_fn: Arc::new(|_| false),
583            handover_ts: Arc::new(RwLock::new(None)),
584        }
585    }
586}
587
588impl StakesHandle {
589    /// Create a new stakes handle with shared references.
590    ///
591    /// This shares the same `Arc<RwLock<...>>` with the Bank, so mutations
592    /// to `pending` by builtin programs are immediately visible to the Bank.
593    ///
594    /// The signaling Arcs (`epoch_rewards_init`, `epoch_stakes_frozen`)
595    /// are created internally with default values. This simplifies the API since callers
596    /// don't need to manage these internal signaling mechanisms.
597    ///
598    /// # Arguments
599    /// * `baseline` - The baseline stake cache
600    /// * `pending` - The pending stake cache for the next epoch
601    /// * `frozen` - The frozen stake history
602    /// * `epoch_rewards_exists_fn` - Callback to check if an EpochRewards account exists
603    pub fn new_shared(
604        baseline: StakeCache,
605        pending: StakeCache,
606        frozen: StakeHistory,
607        epoch_rewards_exists_fn: Arc<dyn Fn(u64) -> bool + Send + Sync>,
608    ) -> Self {
609        Self {
610            baseline,
611            pending,
612            frozen,
613            epoch_rewards_init: Arc::new(RwLock::new(None)),
614            epoch_stakes_frozen: Arc::new(AtomicBool::new(false)),
615            epoch_rewards_exists_fn,
616            handover_ts: Arc::new(RwLock::new(None)),
617        }
618    }
619
620    /// Check if an EpochRewards account exists for the given epoch.
621    ///
622    /// Uses the callback provided at construction time to query the StateStore.
623    /// This allows DistributeRewards to find the first completed frozen epoch
624    /// that doesn't yet have an EpochRewards account.
625    pub fn epoch_rewards_exists(&self, epoch: u64) -> bool {
626        (self.epoch_rewards_exists_fn)(epoch)
627    }
628
629    /// Signal that epoch stakes have been frozen (FreezeStakes was called).
630    ///
631    /// This sets the `epoch_stakes_frozen` flag to true to signal that
632    /// `apply_pending_validator_changes_if_needed()` should be called by the Bank.
633    pub fn set_epoch_stakes_frozen(&self) {
634        self.epoch_stakes_frozen.store(true, Ordering::Release);
635    }
636
637    /// Atomically take the epoch_stakes_frozen signal.
638    ///
639    /// This atomically reads and clears the flag, returning `true` if it was set.
640    /// Used by `finalize_impl()` to consume the signal and perform the deferred
641    /// pending → frozen swap.
642    ///
643    /// Returns `true` if FreezeStakes was called and the signal hadn't been consumed yet.
644    pub fn take_epoch_stakes_frozen(&self) -> bool {
645        self.epoch_stakes_frozen.swap(false, Ordering::AcqRel)
646    }
647
648    /// Check if FreezeStakes was signaled this block, without consuming the flag.
649    ///
650    /// Used by `apply_pending_validator_changes_if_needed()` to detect the epoch
651    /// boundary while leaving the flag set for `finalize_impl()` to consume.
652    pub fn is_epoch_stakes_frozen(&self) -> bool {
653        self.epoch_stakes_frozen.load(Ordering::Acquire)
654    }
655
656    // ========== Layered Lookups from Pending ==========
657    // These methods include pending changes (next epoch) in the lookup.
658
659    /// Get a stake account starting from pending (next epoch state).
660    ///
661    /// Searches: pending → frozen (newest to oldest) → baseline
662    ///
663    /// Returns `Some(account)` if found, `None` if the account doesn't exist
664    /// (either never created or was deleted via tombstone).
665    pub fn get_stake_account_from_pending(&self, pubkey: &Pubkey) -> Option<StakeAccount> {
666        // 1. Check pending (next epoch)
667        {
668            let pending_data = self.pending.read();
669            if let Some(value) = pending_data.stake_accounts.get(pubkey) {
670                return value.clone(); // Some(account) or None (tombstone)
671            }
672        }
673
674        // 2. Check frozen epochs in reverse order (newest to oldest)
675        {
676            let frozen_data = self.frozen.read();
677            for frozen_entry in frozen_data.iter().rev() {
678                if let Some(value) = frozen_entry.stake_accounts.get(pubkey) {
679                    return value.clone();
680                }
681            }
682        }
683
684        // 3. Check baseline
685        {
686            let baseline_data = self.baseline.read();
687            baseline_data
688                .stake_accounts
689                .get(pubkey)
690                .and_then(|v| v.clone())
691        }
692    }
693
694    /// Get a validator account starting from pending (next epoch state).
695    ///
696    /// Searches: pending → frozen (newest to oldest) → baseline
697    ///
698    /// Returns `Some(account)` if found, `None` if the account doesn't exist
699    /// (either never created or was deleted via tombstone).
700    pub fn get_validator_account_from_pending(&self, pubkey: &Pubkey) -> Option<ValidatorAccount> {
701        // 1. Check pending (next epoch)
702        {
703            let pending_data = self.pending.read();
704            if let Some(value) = pending_data.validator_accounts.get(pubkey) {
705                return value.clone(); // Some(account) or None (tombstone)
706            }
707        }
708
709        // 2. Check frozen epochs in reverse order (newest to oldest)
710        {
711            let frozen_data = self.frozen.read();
712            for frozen_entry in frozen_data.iter().rev() {
713                if let Some(value) = frozen_entry.validator_accounts.get(pubkey) {
714                    return value.clone();
715                }
716            }
717        }
718
719        // 3. Check baseline
720        {
721            let baseline_data = self.baseline.read();
722            baseline_data
723                .validator_accounts
724                .get(pubkey)
725                .and_then(|v| v.clone())
726        }
727    }
728
729    /// Get all validator accounts starting from pending (next epoch state).
730    ///
731    /// Returns a vector of `(pubkey, account)` pairs for all validators, sorted by pubkey.
732    /// Includes pending changes (next epoch).
733    /// Note: This is O(baseline_size + total_deltas).
734    pub fn get_all_validator_accounts_from_pending(&self) -> Vec<(Pubkey, ValidatorAccount)> {
735        let mut result: HashMap<Pubkey, Option<ValidatorAccount>> = HashMap::new();
736
737        // 1. Start with baseline
738        {
739            let baseline_data = self.baseline.read();
740            for (pubkey, value) in baseline_data.validator_accounts.iter() {
741                result.insert(*pubkey, value.clone());
742            }
743        }
744
745        // 2. Apply frozen deltas in order (oldest to newest)
746        {
747            let frozen_data = self.frozen.read();
748            for frozen_entry in frozen_data.iter() {
749                for (pubkey, value) in frozen_entry.validator_accounts.iter() {
750                    result.insert(*pubkey, value.clone());
751                }
752            }
753        }
754
755        // 3. Apply pending deltas
756        {
757            let pending_data = self.pending.read();
758            for (pubkey, value) in pending_data.validator_accounts.iter() {
759                result.insert(*pubkey, value.clone());
760            }
761        }
762
763        // 4. Filter out tombstones and collect
764        let mut sorted: Vec<_> = result
765            .into_iter()
766            .filter_map(|(k, v)| v.map(|account| (k, account)))
767            .collect();
768
769        // Sort by pubkey for deterministic ordering
770        sorted.sort_by_key(|(pubkey, _)| *pubkey);
771        sorted
772    }
773
774    // ========== Layered Lookups from Last Frozen ==========
775    // These methods represent the current epoch's effective state (skip pending).
776
777    /// Get a stake account starting from the last frozen epoch (current epoch state).
778    ///
779    /// Searches: frozen (newest to oldest) → baseline
780    /// Skips pending (next epoch changes).
781    ///
782    /// Returns `Some(account)` if found, `None` if the account doesn't exist
783    /// (either never created or was deleted via tombstone).
784    pub fn get_stake_account_from_last_frozen(&self, pubkey: &Pubkey) -> Option<StakeAccount> {
785        // 1. Check frozen epochs in reverse order (newest to oldest)
786        {
787            let frozen_data = self.frozen.read();
788            for frozen_entry in frozen_data.iter().rev() {
789                if let Some(value) = frozen_entry.stake_accounts.get(pubkey) {
790                    return value.clone();
791                }
792            }
793        }
794
795        // 2. Check baseline
796        {
797            let baseline_data = self.baseline.read();
798            baseline_data
799                .stake_accounts
800                .get(pubkey)
801                .and_then(|v| v.clone())
802        }
803    }
804
805    /// Get a validator account starting from the last frozen epoch (current epoch state).
806    ///
807    /// Searches: frozen (newest to oldest) → baseline
808    /// Skips pending (next epoch changes).
809    ///
810    /// Returns `Some(account)` if found, `None` if the account doesn't exist
811    /// (either never created or was deleted via tombstone).
812    pub fn get_validator_account_from_last_frozen(
813        &self,
814        pubkey: &Pubkey,
815    ) -> Option<ValidatorAccount> {
816        // 1. Check frozen epochs in reverse order (newest to oldest)
817        {
818            let frozen_data = self.frozen.read();
819            for frozen_entry in frozen_data.iter().rev() {
820                if let Some(value) = frozen_entry.validator_accounts.get(pubkey) {
821                    return value.clone();
822                }
823            }
824        }
825
826        // 2. Check baseline
827        {
828            let baseline_data = self.baseline.read();
829            baseline_data
830                .validator_accounts
831                .get(pubkey)
832                .and_then(|v| v.clone())
833        }
834    }
835
836    /// Get all validator accounts from the last frozen epoch (current epoch state).
837    ///
838    /// Returns a vector of `(pubkey, account)` pairs for all validators, sorted by pubkey.
839    /// Skips pending (next epoch changes).
840    /// Note: This is O(baseline_size + total_frozen_deltas).
841    pub fn get_all_validator_accounts_from_last_frozen(&self) -> Vec<(Pubkey, ValidatorAccount)> {
842        let mut result: HashMap<Pubkey, Option<ValidatorAccount>> = HashMap::new();
843
844        // 1. Start with baseline
845        {
846            let baseline_data = self.baseline.read();
847            for (pubkey, value) in baseline_data.validator_accounts.iter() {
848                result.insert(*pubkey, value.clone());
849            }
850        }
851
852        // 2. Apply all frozen deltas in order (oldest to newest)
853        {
854            let frozen_data = self.frozen.read();
855            for frozen_entry in frozen_data.iter() {
856                for (pubkey, value) in frozen_entry.validator_accounts.iter() {
857                    result.insert(*pubkey, value.clone());
858                }
859            }
860        }
861
862        // 3. Filter out tombstones and collect (skip pending)
863        let mut sorted: Vec<_> = result
864            .into_iter()
865            .filter_map(|(k, v)| v.map(|account| (k, account)))
866            .collect();
867
868        // Sort by pubkey for deterministic ordering
869        sorted.sort_by_key(|(pubkey, _)| *pubkey);
870        sorted
871    }
872
873    /// Find a validator by their authority key from the last frozen epoch (current epoch state).
874    ///
875    /// Performs the same layered merge as `get_all_validator_accounts_from_last_frozen()`
876    /// (baseline + frozen deltas, skips pending) but scans for a matching `authority_key`
877    /// instead of collecting all validators into a sorted Vec.
878    ///
879    /// Returns the `ValidatorInfo` if found, `None` otherwise.
880    pub fn find_validator_by_authority_key_from_last_frozen(
881        &self,
882        authority_key: &[u8],
883    ) -> Option<ValidatorInfo> {
884        let mut result: HashMap<Pubkey, Option<ValidatorAccount>> = HashMap::new();
885
886        // 1. Start with baseline
887        {
888            let baseline_data = self.baseline.read();
889            for (pubkey, value) in baseline_data.validator_accounts.iter() {
890                result.insert(*pubkey, value.clone());
891            }
892        }
893
894        // 2. Apply all frozen deltas in order (oldest to newest)
895        {
896            let frozen_data = self.frozen.read();
897            for frozen_entry in frozen_data.iter() {
898                for (pubkey, value) in frozen_entry.validator_accounts.iter() {
899                    result.insert(*pubkey, value.clone());
900                }
901            }
902        }
903
904        // 3. Scan for matching authority_key (skip tombstones, no sort needed)
905        result
906            .into_values()
907            .flatten()
908            .find(|account| account.data.authority_key == authority_key)
909            .map(|account| account.data)
910    }
911
912    // ========== Layered Lookups from First Frozen ==========
913    // These methods represent the oldest pending rewards epoch state.
914
915    /// Get a stake account starting from the first frozen epoch (oldest pending rewards).
916    ///
917    /// Searches: frozen.front() → baseline only
918    /// Skips all newer frozen epochs and pending.
919    ///
920    /// Returns `Some(account)` if found, `None` if the account doesn't exist
921    /// (either never created or was deleted via tombstone).
922    pub fn get_stake_account_from_first_frozen(&self, pubkey: &Pubkey) -> Option<StakeAccount> {
923        // 1. Check first frozen epoch only
924        {
925            let frozen_data = self.frozen.read();
926            if let Some(first_frozen) = frozen_data.front() {
927                if let Some(value) = first_frozen.stake_accounts.get(pubkey) {
928                    return value.clone();
929                }
930            }
931        }
932
933        // 2. Check baseline
934        {
935            let baseline_data = self.baseline.read();
936            baseline_data
937                .stake_accounts
938                .get(pubkey)
939                .and_then(|v| v.clone())
940        }
941    }
942
943    /// Get all stake accounts starting from pending (next epoch state).
944    ///
945    /// Returns a vector of `(pubkey, account)` pairs for all stake accounts, sorted by pubkey.
946    /// Includes pending changes (next epoch).
947    /// Note: This is O(baseline_size + total_deltas).
948    ///
949    /// This method is used for operations that need to check all stake accounts
950    /// including the most recent changes (e.g., checking validator references during Withdraw).
951    pub fn get_all_stake_accounts_from_pending(&self) -> Vec<(Pubkey, StakeAccount)> {
952        let mut result: HashMap<Pubkey, Option<StakeAccount>> = HashMap::new();
953
954        // 1. Start with baseline
955        {
956            let baseline_data = self.baseline.read();
957            for (pubkey, value) in baseline_data.stake_accounts.iter() {
958                result.insert(*pubkey, value.clone());
959            }
960        }
961
962        // 2. Apply frozen deltas in order (oldest to newest)
963        {
964            let frozen_data = self.frozen.read();
965            for frozen_entry in frozen_data.iter() {
966                for (pubkey, value) in frozen_entry.stake_accounts.iter() {
967                    result.insert(*pubkey, value.clone());
968                }
969            }
970        }
971
972        // 3. Apply pending deltas
973        {
974            let pending_data = self.pending.read();
975            for (pubkey, value) in pending_data.stake_accounts.iter() {
976                result.insert(*pubkey, value.clone());
977            }
978        }
979
980        // 4. Filter out tombstones and collect
981        let mut sorted: Vec<_> = result
982            .into_iter()
983            .filter_map(|(k, v)| v.map(|account| (k, account)))
984            .collect();
985
986        // Sort by pubkey for deterministic ordering
987        sorted.sort_by_key(|(pubkey, _)| *pubkey);
988        sorted
989    }
990
991    /// Get all stake accounts from the first frozen epoch (oldest pending rewards).
992    ///
993    /// Returns a vector of `(pubkey, account)` pairs for all stake accounts, sorted by pubkey.
994    /// Skips all newer frozen epochs and pending.
995    ///
996    /// This method is used by reward calculation to iterate over all stake accounts
997    /// that were active at the time rewards were frozen (baseline + first frozen delta).
998    pub fn get_all_stake_accounts_from_first_frozen(&self) -> Vec<(Pubkey, StakeAccount)> {
999        let mut result: HashMap<Pubkey, Option<StakeAccount>> = HashMap::new();
1000
1001        // 1. Start with baseline
1002        {
1003            let baseline_data = self.baseline.read();
1004            for (pubkey, value) in baseline_data.stake_accounts.iter() {
1005                result.insert(*pubkey, value.clone());
1006            }
1007        }
1008
1009        // 2. Apply only the first frozen delta
1010        {
1011            let frozen_data = self.frozen.read();
1012            if let Some(first_frozen) = frozen_data.front() {
1013                for (pubkey, value) in first_frozen.stake_accounts.iter() {
1014                    result.insert(*pubkey, value.clone());
1015                }
1016            }
1017        }
1018
1019        // 3. Filter out tombstones and collect
1020        let mut sorted: Vec<_> = result
1021            .into_iter()
1022            .filter_map(|(k, v)| v.map(|account| (k, account)))
1023            .collect();
1024
1025        // Sort by pubkey for deterministic ordering
1026        sorted.sort_by_key(|(pubkey, _)| *pubkey);
1027        sorted
1028    }
1029
1030    /// Get all stake accounts from baseline + frozen deltas up to (and including)
1031    /// the specified epoch.
1032    ///
1033    /// Lookups: baseline + frozen deltas where `delta.epoch <= target_epoch`
1034    pub fn get_all_stake_accounts_from_frozen_epoch(
1035        &self,
1036        target_epoch: Epoch,
1037    ) -> Vec<(Pubkey, StakeAccount)> {
1038        let mut result: HashMap<Pubkey, Option<StakeAccount>> = HashMap::new();
1039
1040        // 1. Start with baseline
1041        {
1042            let baseline_data = self.baseline.read();
1043            for (pubkey, value) in baseline_data.stake_accounts.iter() {
1044                result.insert(*pubkey, value.clone());
1045            }
1046        }
1047
1048        // 2. Apply frozen deltas up to and including target_epoch
1049        {
1050            let frozen_data = self.frozen.read();
1051            for frozen_entry in frozen_data.iter() {
1052                if frozen_entry.epoch > target_epoch {
1053                    break; // Frozen is ordered oldest-to-newest, stop at target
1054                }
1055                for (pubkey, value) in frozen_entry.stake_accounts.iter() {
1056                    result.insert(*pubkey, value.clone());
1057                }
1058            }
1059        }
1060
1061        // 3. Filter out tombstones and collect
1062        let mut sorted: Vec<_> = result
1063            .into_iter()
1064            .filter_map(|(k, v)| v.map(|account| (k, account)))
1065            .collect();
1066
1067        // Sort by pubkey for deterministic ordering
1068        sorted.sort_by_key(|(pubkey, _)| *pubkey);
1069        sorted
1070    }
1071
1072    // ========== Baseline-Only Lookups ==========
1073    // These methods return data from the baseline only (after merge has happened).
1074    // Used by the new reward calculation model where merge happens at activation.
1075
1076    /// Get all stake accounts from the baseline only.
1077    ///
1078    /// Returns a vector of `(pubkey, account)` pairs for all stake accounts in baseline,
1079    /// sorted by pubkey. Does NOT include frozen or pending data.
1080    ///
1081    /// This is used after the frozen.front() has been merged into baseline,
1082    /// for baseline-based reward calculation. The baseline contains the complete
1083    /// state of the epoch being rewarded after merge.
1084    pub fn get_all_stake_accounts_from_baseline(&self) -> Vec<(Pubkey, StakeAccount)> {
1085        let baseline_data = self.baseline.read();
1086        let mut sorted: Vec<_> = baseline_data
1087            .stake_accounts
1088            .iter()
1089            .filter_map(|(k, v)| v.as_ref().map(|account| (*k, account.clone())))
1090            .collect();
1091
1092        // Sort by pubkey for deterministic ordering
1093        sorted.sort_by_key(|(pubkey, _)| *pubkey);
1094        sorted
1095    }
1096
1097    /// Get all validator accounts from the baseline only.
1098    ///
1099    /// Returns a vector of `(pubkey, account)` pairs for all validators in baseline,
1100    /// sorted by pubkey. Does NOT include frozen or pending data.
1101    ///
1102    /// This is used after the frozen.front() has been merged into baseline,
1103    /// for baseline-based reward calculation.
1104    pub fn get_all_validator_accounts_from_baseline(&self) -> Vec<(Pubkey, ValidatorAccount)> {
1105        let baseline_data = self.baseline.read();
1106        let mut sorted: Vec<_> = baseline_data
1107            .validator_accounts
1108            .iter()
1109            .filter_map(|(k, v)| v.as_ref().map(|account| (*k, account.clone())))
1110            .collect();
1111
1112        // Sort by pubkey for deterministic ordering
1113        sorted.sort_by_key(|(pubkey, _)| *pubkey);
1114        sorted
1115    }
1116
1117    /// Freeze the pending stake cache data.
1118    ///
1119    /// This performs an O(1) swap of the pending stake cache data using `std::mem::take()`
1120    /// and pushes it to the back of the frozen queue. This is typically called by the
1121    /// ValidatorRegistry program's FreezeStakes instruction to capture the validator set
1122    /// at a specific point.
1123    ///
1124    /// **Note:** Since the handle uses shared `Arc<RwLock<...>>` references, the frozen
1125    /// data and updated pending epoch are immediately visible to all handle instances.
1126    ///
1127    /// To access the frozen validator data after calling this method, use
1128    /// `get_all_validator_accounts_from_last_frozen()`.
1129    pub fn freeze_stakes(&self) {
1130        // 1. Atomically swap pending data with empty and initialize new pending
1131        // Using a single lock scope eliminates any race condition window
1132        let frozen_data = {
1133            let mut pending_guard = self.pending.write();
1134            let frozen_data = std::mem::take(&mut *pending_guard);
1135            // Initialize new pending's epoch and timestamp for next epoch
1136            // This ensures any stake changes after FreezeStakes within the same block
1137            // have the correct epoch/timestamp (not the Default values of 0)
1138            pending_guard.epoch = frozen_data.epoch + 1;
1139            pending_guard.timestamp = frozen_data.timestamp;
1140            frozen_data
1141        };
1142
1143        // 2. Push frozen data to history
1144        self.frozen.push_back(frozen_data);
1145
1146        // 3. Signal that FreezeStakes was called (for apply_pending_validator_changes)
1147        self.epoch_stakes_frozen.store(true, Ordering::Release);
1148    }
1149
1150    // ========== Epoch and Timestamp Accessors ==========
1151
1152    /// Get the epoch of the pending (next) stake cache.
1153    pub fn pending_epoch(&self) -> Epoch {
1154        self.pending.epoch()
1155    }
1156
1157    /// Set the epoch of the pending stake cache.
1158    pub fn set_pending_epoch(&self, epoch: Epoch) {
1159        self.pending.set_epoch(epoch);
1160    }
1161
1162    /// Set the timestamp of the pending stake cache.
1163    pub fn set_pending_timestamp(&self, timestamp: u64) {
1164        self.pending.set_timestamp(timestamp);
1165    }
1166
1167    /// Get the timestamp of the last frozen epoch (current epoch's effective state).
1168    ///
1169    /// Returns `None` if no frozen snapshots exist yet.
1170    pub fn last_frozen_timestamp(&self) -> Option<u64> {
1171        self.frozen.read().back().map(|data| data.timestamp)
1172    }
1173
1174    /// Get the epoch number of the last frozen snapshot (current epoch).
1175    /// Returns `None` if no frozen snapshots exist yet.
1176    pub fn last_frozen_epoch(&self) -> Option<Epoch> {
1177        self.frozen.read().back().map(|data| data.epoch)
1178    }
1179
1180    /// Get the timestamp of the pending stake cache.
1181    pub fn pending_timestamp(&self) -> u64 {
1182        self.pending.read().timestamp
1183    }
1184
1185    /// Push a new frozen snapshot to the history.
1186    pub fn push_frozen(&self, data: StakeCacheData) {
1187        self.frozen.push_back(data);
1188    }
1189
1190    /// Get the number of frozen snapshots in the history.
1191    pub fn frozen_len(&self) -> usize {
1192        self.frozen.len()
1193    }
1194
1195    /// Get the epoch of the oldest frozen snapshot (front of the queue).
1196    ///
1197    /// Returns `None` if no frozen snapshots exist.
1198    pub fn front_frozen_epoch(&self) -> Option<Epoch> {
1199        self.frozen.front().map(|data| data.epoch)
1200    }
1201
1202    // ========== Epoch Rewards Signaling ==========
1203
1204    /// Request epoch rewards initialization.
1205    ///
1206    /// This is called by the DistributeRewards instruction to signal that the Bank
1207    /// should create an EpochRewards account. The Bank checks for the request after
1208    /// transaction execution via `take_epoch_rewards_init_request()`.
1209    ///
1210    /// # Arguments
1211    /// * `epoch` - The epoch for which rewards are being distributed
1212    /// * `total_rewards` - The total rewards to distribute (hardcoded for MVP)
1213    pub fn request_epoch_rewards_init(&self, epoch: Epoch, total_rewards: u64) {
1214        // Store the request data - the presence of Some indicates a request is pending
1215        *self
1216            .epoch_rewards_init
1217            .write()
1218            .expect("Failed to acquire lock") = Some(EpochRewardsInitRequest {
1219            epoch,
1220            total_rewards,
1221        });
1222    }
1223
1224    /// Take the epoch rewards initialization request, clearing it.
1225    ///
1226    /// This is called by the Bank after transaction execution to check if epoch
1227    /// rewards init was requested. The Bank uses the returned data to create
1228    /// the EpochRewards account.
1229    ///
1230    /// Returns `Some(request)` if a request was pending, `None` otherwise.
1231    /// After this call, `epoch_rewards_init` will be `None`.
1232    pub fn take_epoch_rewards_init_request(&self) -> Option<EpochRewardsInitRequest> {
1233        // Take and return the request data
1234        self.epoch_rewards_init
1235            .write()
1236            .expect("Failed to acquire lock")
1237            .take()
1238    }
1239
1240    /// Check if an epoch rewards initialization request is pending.
1241    ///
1242    /// This is used by DistributeRewards to fail if a signal is already set
1243    /// for the current block (prevents multiple DistributeRewards in same block).
1244    ///
1245    /// Returns `true` if a request is pending, `false` otherwise.
1246    /// Does NOT consume the request (unlike `take_epoch_rewards_init_request`).
1247    pub fn is_epoch_rewards_init_pending(&self) -> bool {
1248        self.epoch_rewards_init
1249            .read()
1250            .expect("Failed to acquire lock")
1251            .is_some()
1252    }
1253
1254    /// Get completed frozen epochs (excludes the last/current epoch).
1255    ///
1256    /// Returns epoch numbers for all frozen entries except the last one,
1257    /// which represents the currently ongoing epoch. These are epochs
1258    /// that have completed and are eligible for reward distribution.
1259    ///
1260    /// Returns empty if frozen has 0 or 1 entries (need at least 2 to have completed epochs).
1261    pub fn completed_frozen_epochs(&self) -> Vec<Epoch> {
1262        let frozen_data = self.frozen.read();
1263        let len = frozen_data.len();
1264        if len < 2 {
1265            return vec![];
1266        }
1267        frozen_data
1268            .iter()
1269            .take(len - 1) // Exclude last (current epoch)
1270            .map(|data| data.epoch)
1271            .collect()
1272    }
1273
1274    // ========== Validator Reference Checking ==========
1275
1276    /// Check if any stake account references the given validator pubkey whose
1277    /// unbonding period is NOT yet complete.
1278    ///
1279    /// This performs an O(n) search over all stake accounts starting from
1280    /// pending → frozen → baseline. Uses Rayon's parallel iterator for better
1281    /// performance on multi-core systems.
1282    ///
1283    /// A stake account is considered to "reference" the validator if:
1284    /// - It has `validator == Some(target_validator)`, AND
1285    /// - Either:
1286    ///   - It is **active** (no `deactivation_requested`), OR
1287    ///   - It is **still unbonding** (unbonding conditions not yet met)
1288    ///
1289    /// Stake accounts whose unbonding is complete are NOT considered as referencing
1290    /// the validator, since they can be fully withdrawn or reactivated to another validator.
1291    ///
1292    /// # Unbonding Completion Conditions
1293    ///
1294    /// Unbonding is complete when BOTH conditions are met:
1295    /// 1. **State transition**: `deactivation_timestamp < last_freeze_timestamp`
1296    ///    (at least one FreezeStakes has occurred since deactivation)
1297    /// 2. **Duration enforcement**: `deactivation_timestamp + unbonding_period < current_timestamp`
1298    ///    (the unbonding period has actually elapsed)
1299    ///
1300    /// # Arguments
1301    /// * `validator` - The validator pubkey to check
1302    /// * `validator_info` - The validator's info (used to compute unbonding end via `end_of_unbonding`)
1303    /// * `last_freeze_timestamp` - When FreezeStakes was last called (epoch boundary)
1304    /// * `current_timestamp` - Current block timestamp from Clock sysvar (in milliseconds)
1305    ///
1306    /// # Returns
1307    /// `true` if at least one stake account references the validator and is either
1308    /// active or still unbonding, `false` otherwise.
1309    ///
1310    /// # Performance
1311    ///
1312    /// This is an expensive O(n) operation that should only be called when needed
1313    /// (e.g., during Withdraw when checking if a validator can be fully drained).
1314    pub fn is_validator_referenced(
1315        &self,
1316        validator: &Pubkey,
1317        validator_info: &ValidatorInfo,
1318        last_freeze_timestamp: u64,
1319        current_timestamp: u64,
1320    ) -> bool {
1321        // Get all stake accounts and check if any reference the validator using parallel iteration
1322        let all_stake_accounts = self.get_all_stake_accounts_from_pending();
1323        all_stake_accounts.par_iter().any(|(_, stake_account)| {
1324            // First check: does this stake reference our target validator?
1325            if stake_account.data.validator.as_ref() != Some(validator) {
1326                return false;
1327            }
1328
1329            // If not deactivating (active stake), it counts as referencing
1330            let Some(deactivation_timestamp) = stake_account.data.deactivation_requested else {
1331                return true;
1332            };
1333
1334            // Check if unbonding is complete using the two-step validation:
1335            // 1. State transition: deactivation must have taken effect
1336            if deactivation_timestamp >= last_freeze_timestamp {
1337                // Still deactivating, counts as referencing
1338                return true;
1339            }
1340
1341            // 2. Duration enforcement: unbonding period must have elapsed
1342            let unbonding_end = validator_info.end_of_unbonding(deactivation_timestamp);
1343
1344            // If unbonding is NOT complete, the stake still counts as referencing
1345            unbonding_end >= current_timestamp
1346        })
1347    }
1348
1349    // ========== Locked Staker Checking ==========
1350
1351    /// Check if any stake account delegated to the given validator is still within
1352    /// its lockup period.
1353    ///
1354    /// This performs an O(n) search over all stake accounts starting from
1355    /// pending → frozen → baseline. Uses Rayon's parallel iterator for better
1356    /// performance on multi-core systems.
1357    ///
1358    /// A staker is considered "locked" if ALL of the following are true:
1359    /// - It has `validator == Some(target_validator)` (delegated to this validator)
1360    /// - It has `activation_requested == Some(timestamp)` (was activated)
1361    /// - `activation_requested + lockup_period > current_timestamp` (lockup hasn't expired)
1362    ///
1363    /// Self-bonds are excluded from lockup checks to prevent the validator from being
1364    /// unable to change commission rates or shut down when only the self-bond exists.
1365    ///
1366    /// # Arguments
1367    /// * `validator` - The validator pubkey to check
1368    /// * `lockup_period` - The validator's lockup period in milliseconds
1369    /// * `current_timestamp` - Current block timestamp from Clock sysvar (in milliseconds)
1370    ///
1371    /// # Returns
1372    /// `true` if at least one stake account is delegated to the validator and still
1373    /// within its lockup period, `false` otherwise.
1374    pub fn has_locked_stakers(
1375        &self,
1376        validator: &Pubkey,
1377        lockup_period: u64,
1378        current_timestamp: u64,
1379    ) -> bool {
1380        let self_bond_pubkey = derive_self_bond_address(validator);
1381        let all_stake_accounts = self.get_all_stake_accounts_from_pending();
1382        all_stake_accounts
1383            .par_iter()
1384            .any(|(pubkey, stake_account)| {
1385                // Skip self-bond PDA
1386                if *pubkey == self_bond_pubkey {
1387                    return false;
1388                }
1389
1390                // First check: does this stake reference our target validator?
1391                if stake_account.data.validator.as_ref() != Some(validator) {
1392                    return false;
1393                }
1394
1395                // Must have been activated to have a lockup
1396                let Some(activation_requested) = stake_account.data.activation_requested else {
1397                    return false;
1398                };
1399
1400                // Check if the lockup period hasn't expired yet
1401                let lockup_end = activation_requested.saturating_add(lockup_period);
1402                lockup_end > current_timestamp
1403            })
1404    }
1405
1406    /// Check if a validator is referenced by any stake accounts (excluding the self-bond).
1407    ///
1408    /// This variant excludes the self-bond PDA from the check to prevent circular logic
1409    /// where the self-bond cannot be deactivated because its existence always makes
1410    /// is_validator_referenced() return true.
1411    ///
1412    /// # Arguments
1413    /// * `validator_pubkey` - The validator pubkey to check
1414    /// * `validator_info` - The validator's info (used to compute unbonding end via `end_of_unbonding`)
1415    /// * `last_freeze_timestamp` - When FreezeStakes was last called (epoch boundary)
1416    /// * `current_timestamp` - Current block timestamp from Clock sysvar (in milliseconds)
1417    ///
1418    /// # Returns
1419    /// `true` if at least one non-self-bond stake account references the validator
1420    pub fn is_validator_referenced_excluding_self_bond(
1421        &self,
1422        validator_pubkey: &Pubkey,
1423        validator_info: &ValidatorInfo,
1424        last_freeze_timestamp: u64,
1425        current_timestamp: u64,
1426    ) -> bool {
1427        let self_bond_pubkey = derive_self_bond_address(validator_pubkey);
1428        let all_stake_accounts = self.get_all_stake_accounts_from_pending();
1429        all_stake_accounts
1430            .par_iter()
1431            .any(|(pubkey, stake_account)| {
1432                // Skip self-bond PDA
1433                if *pubkey == self_bond_pubkey {
1434                    return false;
1435                }
1436
1437                // First check: does this stake reference our target validator?
1438                if stake_account.data.validator.as_ref() != Some(validator_pubkey) {
1439                    return false;
1440                }
1441
1442                // If not deactivating (active stake), it counts as referencing
1443                let Some(deactivation_timestamp) = stake_account.data.deactivation_requested else {
1444                    return true;
1445                };
1446
1447                // Check if unbonding is complete using the two-step validation:
1448                // 1. State transition: deactivation must have taken effect
1449                if deactivation_timestamp >= last_freeze_timestamp {
1450                    // Still deactivating, counts as referencing
1451                    return true;
1452                }
1453
1454                // 2. Duration enforcement: unbonding period must have elapsed
1455                let unbonding_end = validator_info.end_of_unbonding(deactivation_timestamp);
1456
1457                // If unbonding is NOT complete, the stake still counts as referencing
1458                unbonding_end >= current_timestamp
1459            })
1460    }
1461
1462    // ========== Pending Cache Mutation Accessors ==========
1463
1464    /// Insert a stake account into the pending cache.
1465    pub fn insert_stake_account(&self, pubkey: Pubkey, account: StakeAccount) {
1466        self.pending.insert_stake_account(pubkey, account);
1467    }
1468
1469    /// Insert a validator account into the pending cache.
1470    pub fn insert_validator_account(&self, pubkey: Pubkey, account: ValidatorAccount) {
1471        self.pending.insert_validator_account(pubkey, account);
1472    }
1473
1474    // ========== Adoption Tracking ==========
1475
1476    /// Check if the previous (most recent) frozen epoch has been adopted by consensus.
1477    ///
1478    /// Returns `true` if:
1479    /// - The frozen deque is empty (no previous epoch to adopt), OR
1480    /// - `frozen.back().consensus_adopted_at` is `Some(_)` (adopted)
1481    ///
1482    /// Returns `false` when the last frozen entry exists but hasn't been adopted yet.
1483    /// Used by the FreezeStakes guard to enforce the no-skip invariant.
1484    pub fn is_previous_epoch_adopted(&self) -> bool {
1485        let frozen_data = self.frozen.read();
1486        match frozen_data.back() {
1487            None => true, // No frozen epochs — nothing to adopt
1488            Some(last) => last.consensus_adopted_at.is_some(),
1489        }
1490    }
1491
1492    /// Set `consensus_adopted_at` on the newest frozen entry (back of deque).
1493    ///
1494    /// This is called from `Bank::finalize_impl()` after `take_handover()` returns
1495    /// a timestamp, and from `RequestConsensusEpochChange` in testing mode for
1496    /// auto-adoption.
1497    ///
1498    /// No-op if the frozen deque is empty.
1499    pub fn set_consensus_adopted_at(&self, ts: u64) {
1500        let mut frozen_data = self.frozen.write_lock();
1501        if let Some(last) = frozen_data.back_mut() {
1502            last.consensus_adopted_at = Some(ts);
1503        }
1504    }
1505
1506    /// Signal that a Handover admin transaction was detected in the current block.
1507    ///
1508    /// Called by the ExecutionEngine after `execute_blob()` when it finds an
1509    /// `AdminTransaction::Handover` in the block's admin transactions.
1510    /// The timestamp is consumed by `take_handover()` in `Bank::finalize_impl()`.
1511    pub fn signal_handover(&self, ts: u64) {
1512        *self.handover_ts.write().expect("Failed to acquire lock") = Some(ts);
1513    }
1514
1515    /// Atomically take the handover signal, returning the timestamp if set.
1516    ///
1517    /// Called by `Bank::finalize_impl()` to consume the signal and record
1518    /// adoption on `frozen.back()`.
1519    ///
1520    /// Returns `Some(ts)` if `signal_handover()` was called, `None` otherwise.
1521    /// After this call, the signal is cleared.
1522    pub fn take_handover(&self) -> Option<u64> {
1523        self.handover_ts
1524            .write()
1525            .expect("Failed to acquire lock")
1526            .take()
1527    }
1528}
1529
1530// ========== Read-Only View ==========
1531
1532/// Read-only view of the stake cache for external consumers (e.g., RPC handlers).
1533///
1534/// This type wraps a `StakesHandle` and exposes only read-only query methods.
1535/// Mutation methods (`insert_stake_account`, `insert_validator_account`, `freeze_stakes`,
1536/// `request_epoch_rewards_init`, etc.) are intentionally not exposed.
1537///
1538/// # Usage
1539///
1540/// External code (outside the `svm-execution` crate) should use `Bank::stakes_view()`
1541/// to obtain a `StakesView` instead of accessing the full `StakesHandle` directly.
1542/// This prevents accidental state corruption from RPC handlers or other non-transaction
1543/// code paths.
1544pub struct StakesView(StakesHandle);
1545
1546impl StakesView {
1547    /// Create a new read-only view from a `StakesHandle`.
1548    pub fn new(handle: StakesHandle) -> Self {
1549        Self(handle)
1550    }
1551
1552    // ========== Layered Lookups from Pending ==========
1553
1554    /// Get a stake account starting from pending (next epoch state).
1555    ///
1556    /// Searches: pending → frozen (newest to oldest) → baseline
1557    pub fn get_stake_account_from_pending(&self, pubkey: &Pubkey) -> Option<StakeAccount> {
1558        self.0.get_stake_account_from_pending(pubkey)
1559    }
1560
1561    /// Get a validator account starting from pending (next epoch state).
1562    ///
1563    /// Searches: pending → frozen (newest to oldest) → baseline
1564    pub fn get_validator_account_from_pending(&self, pubkey: &Pubkey) -> Option<ValidatorAccount> {
1565        self.0.get_validator_account_from_pending(pubkey)
1566    }
1567
1568    /// Get all validator accounts starting from pending (next epoch state).
1569    pub fn get_all_validator_accounts_from_pending(&self) -> Vec<(Pubkey, ValidatorAccount)> {
1570        self.0.get_all_validator_accounts_from_pending()
1571    }
1572
1573    // ========== Layered Lookups from Last Frozen ==========
1574
1575    /// Get a stake account starting from the last frozen epoch (current epoch state).
1576    ///
1577    /// Searches: frozen (newest to oldest) → baseline. Skips pending.
1578    pub fn get_stake_account_from_last_frozen(&self, pubkey: &Pubkey) -> Option<StakeAccount> {
1579        self.0.get_stake_account_from_last_frozen(pubkey)
1580    }
1581
1582    /// Get a validator account starting from the last frozen epoch (current epoch state).
1583    ///
1584    /// Searches: frozen (newest to oldest) → baseline. Skips pending.
1585    pub fn get_validator_account_from_last_frozen(
1586        &self,
1587        pubkey: &Pubkey,
1588    ) -> Option<ValidatorAccount> {
1589        self.0.get_validator_account_from_last_frozen(pubkey)
1590    }
1591
1592    /// Get all validator accounts from the last frozen epoch (current epoch state).
1593    pub fn get_all_validator_accounts_from_last_frozen(&self) -> Vec<(Pubkey, ValidatorAccount)> {
1594        self.0.get_all_validator_accounts_from_last_frozen()
1595    }
1596
1597    /// Find a validator by their authority key from the last frozen epoch (current epoch state).
1598    ///
1599    /// Performs a layered merge (baseline + frozen deltas, skips pending) and scans
1600    /// for a matching `authority_key`. Avoids the Vec allocation and sort of
1601    /// `get_all_validator_accounts_from_last_frozen()`.
1602    pub fn find_validator_by_authority_key_from_last_frozen(
1603        &self,
1604        authority_key: &[u8],
1605    ) -> Option<ValidatorInfo> {
1606        self.0
1607            .find_validator_by_authority_key_from_last_frozen(authority_key)
1608    }
1609
1610    // ========== Timestamp Accessors ==========
1611
1612    /// Get the timestamp of the last frozen epoch (current epoch's effective state).
1613    ///
1614    /// Returns `None` if no frozen snapshots exist yet.
1615    pub fn last_frozen_timestamp(&self) -> Option<u64> {
1616        self.0.last_frozen_timestamp()
1617    }
1618
1619    /// Get the epoch number of the last frozen snapshot (current epoch).
1620    /// Returns `None` if no frozen snapshots exist yet.
1621    pub fn last_frozen_epoch(&self) -> Option<Epoch> {
1622        self.0.last_frozen_epoch()
1623    }
1624
1625    /// Get the epoch of the pending (next) stake cache.
1626    pub fn pending_epoch(&self) -> Epoch {
1627        self.0.pending_epoch()
1628    }
1629
1630    /// Get the timestamp of the pending stake cache.
1631    pub fn pending_timestamp(&self) -> u64 {
1632        self.0.pending_timestamp()
1633    }
1634}
1635
1636// ========== Test-only accessors ==========
1637#[cfg(test)]
1638impl StakesHandle {
1639    /// Get direct access to baseline for test assertions.
1640    pub fn raw_baseline(&self) -> &StakeCache {
1641        &self.baseline
1642    }
1643
1644    /// Get direct access to pending for test assertions.
1645    pub fn raw_pending(&self) -> &StakeCache {
1646        &self.pending
1647    }
1648
1649    /// Get direct access to frozen for test assertions.
1650    pub fn raw_frozen(&self) -> &StakeHistory {
1651        &self.frozen
1652    }
1653}
1654
1655#[cfg(test)]
1656mod tests {
1657    use rialo_stake_manager_interface::instruction::StakeInfo;
1658    use rialo_validator_registry_interface::instruction::ValidatorInfo;
1659
1660    use super::*;
1661
1662    // ========================================================================
1663    // Test Helper Functions
1664    // ========================================================================
1665
1666    fn create_test_stake_account(kelvins: u64, validator: Pubkey) -> StakeAccount {
1667        StakeAccount {
1668            kelvins,
1669            data: StakeInfo {
1670                activation_requested: Some(0),
1671                deactivation_requested: None,
1672                delegated_balance: kelvins,
1673                validator: Some(validator),
1674                admin_authority: Pubkey::new_unique(),
1675                withdraw_authority: Pubkey::new_unique(),
1676                reward_receiver: None,
1677            },
1678        }
1679    }
1680
1681    fn create_test_validator_account(kelvins: u64, stake: u64) -> ValidatorAccount {
1682        ValidatorAccount {
1683            kelvins,
1684            data: ValidatorInfo {
1685                signing_key: Pubkey::new_unique(),
1686                withdrawal_key: Pubkey::new_unique(),
1687                registration_time: 0,
1688                stake,
1689                address: vec![],
1690                state_sync_address: vec![],
1691                hostname: String::new(),
1692                authority_key: vec![0u8; 96],
1693                protocol_key: Pubkey::new_unique(),
1694                network_key: Pubkey::new_unique(),
1695                last_update: 0,
1696                unbonding_periods: std::collections::BTreeMap::from([(0, 0)]),
1697                lockup_period: 0,
1698                commission_rate: 500,
1699                new_commission_rate: None,
1700                earliest_shutdown: None,
1701            },
1702        }
1703    }
1704
1705    // ========================================================================
1706    // Layered Lookup Tests: pending → frozen → baseline
1707    // ========================================================================
1708
1709    #[test]
1710    fn test_layered_lookup_stake_account_from_pending() {
1711        let pubkey = Pubkey::new_unique();
1712        let validator = Pubkey::new_unique();
1713        let handle = StakesHandle::default();
1714
1715        // Insert into pending
1716        let pending_account = create_test_stake_account(1000, validator);
1717        handle.insert_stake_account(pubkey, pending_account.clone());
1718
1719        // Lookup should find in pending
1720        let found = handle.get_stake_account_from_pending(&pubkey);
1721        assert!(found.is_some());
1722        assert_eq!(found.unwrap().kelvins, 1000);
1723    }
1724
1725    #[test]
1726    fn test_layered_lookup_stake_account_from_frozen() {
1727        let pubkey = Pubkey::new_unique();
1728        let validator = Pubkey::new_unique();
1729        let handle = StakesHandle::default();
1730
1731        // Insert into pending and freeze
1732        let account = create_test_stake_account(2000, validator);
1733        handle.insert_stake_account(pubkey, account);
1734        handle.freeze_stakes();
1735
1736        // Account should now be in frozen, pending should be empty
1737        let found = handle.get_stake_account_from_pending(&pubkey);
1738        assert!(found.is_some());
1739        assert_eq!(found.unwrap().kelvins, 2000);
1740
1741        // Confirm pending is empty
1742        assert!(handle.raw_pending().get_stake_account(&pubkey).is_none());
1743    }
1744
1745    #[test]
1746    fn test_layered_lookup_stake_account_from_baseline() {
1747        let pubkey = Pubkey::new_unique();
1748        let validator = Pubkey::new_unique();
1749
1750        // Create a handle with account in baseline
1751        let mut baseline_data = StakeCacheData::default();
1752        baseline_data
1753            .stake_accounts
1754            .insert(pubkey, Some(create_test_stake_account(3000, validator)));
1755        let baseline = StakeCache::with_data(baseline_data);
1756        let handle = StakesHandle::new_shared(
1757            baseline,
1758            StakeCache::default(),
1759            StakeHistory::default(),
1760            Arc::new(|_| false),
1761        );
1762
1763        // Lookup should find in baseline
1764        let found = handle.get_stake_account_from_pending(&pubkey);
1765        assert!(found.is_some());
1766        assert_eq!(found.unwrap().kelvins, 3000);
1767    }
1768
1769    #[test]
1770    fn test_layered_lookup_priority_pending_over_frozen() {
1771        let pubkey = Pubkey::new_unique();
1772        let validator = Pubkey::new_unique();
1773        let handle = StakesHandle::default();
1774
1775        // Insert into pending with value 1000
1776        handle.insert_stake_account(pubkey, create_test_stake_account(1000, validator));
1777        // Freeze it
1778        handle.freeze_stakes();
1779
1780        // Insert into pending again with value 2000 (overwrites for next epoch)
1781        handle.insert_stake_account(pubkey, create_test_stake_account(2000, validator));
1782
1783        // Lookup from pending should find 2000 (pending wins)
1784        let found = handle.get_stake_account_from_pending(&pubkey);
1785        assert!(found.is_some());
1786        assert_eq!(found.unwrap().kelvins, 2000);
1787
1788        // Lookup from last frozen should find 1000 (skips pending)
1789        let found_frozen = handle.get_stake_account_from_last_frozen(&pubkey);
1790        assert!(found_frozen.is_some());
1791        assert_eq!(found_frozen.unwrap().kelvins, 1000);
1792    }
1793
1794    #[test]
1795    fn test_layered_lookup_priority_frozen_over_baseline() {
1796        let pubkey = Pubkey::new_unique();
1797        let validator = Pubkey::new_unique();
1798
1799        // Create baseline with value 1000
1800        let mut baseline_data = StakeCacheData::default();
1801        baseline_data
1802            .stake_accounts
1803            .insert(pubkey, Some(create_test_stake_account(1000, validator)));
1804        let baseline = StakeCache::with_data(baseline_data);
1805        let handle = StakesHandle::new_shared(
1806            baseline,
1807            StakeCache::default(),
1808            StakeHistory::default(),
1809            Arc::new(|_| false),
1810        );
1811
1812        // Insert into pending with value 2000 and freeze
1813        handle.insert_stake_account(pubkey, create_test_stake_account(2000, validator));
1814        handle.freeze_stakes();
1815
1816        // Lookup should find 2000 (frozen wins over baseline)
1817        let found = handle.get_stake_account_from_pending(&pubkey);
1818        assert!(found.is_some());
1819        assert_eq!(found.unwrap().kelvins, 2000);
1820    }
1821
1822    #[test]
1823    fn test_layered_lookup_multiple_frozen_epochs() {
1824        let pubkey = Pubkey::new_unique();
1825        let validator = Pubkey::new_unique();
1826        let handle = StakesHandle::default();
1827
1828        // Epoch 1: Insert and freeze with value 1000
1829        handle.insert_stake_account(pubkey, create_test_stake_account(1000, validator));
1830        handle.freeze_stakes();
1831
1832        // Epoch 2: Insert and freeze with value 2000
1833        handle.insert_stake_account(pubkey, create_test_stake_account(2000, validator));
1834        handle.freeze_stakes();
1835
1836        // Epoch 3: Insert and freeze with value 3000
1837        handle.insert_stake_account(pubkey, create_test_stake_account(3000, validator));
1838        handle.freeze_stakes();
1839
1840        // Lookup from last frozen should find 3000 (newest frozen)
1841        let found = handle.get_stake_account_from_last_frozen(&pubkey);
1842        assert!(found.is_some());
1843        assert_eq!(found.unwrap().kelvins, 3000);
1844
1845        // Verify frozen history has 3 entries
1846        assert_eq!(handle.frozen_len(), 3);
1847    }
1848
1849    #[test]
1850    fn test_layered_lookup_validator_account() {
1851        let pubkey = Pubkey::new_unique();
1852
1853        // Create baseline with validator
1854        let mut baseline_data = StakeCacheData::default();
1855        baseline_data
1856            .validator_accounts
1857            .insert(pubkey, Some(create_test_validator_account(1000, 500)));
1858        let baseline = StakeCache::with_data(baseline_data);
1859        let handle = StakesHandle::new_shared(
1860            baseline,
1861            StakeCache::default(),
1862            StakeHistory::default(),
1863            Arc::new(|_| false),
1864        );
1865
1866        // Lookup should find in baseline
1867        let found = handle.get_validator_account_from_pending(&pubkey);
1868        assert!(found.is_some());
1869        assert_eq!(found.unwrap().kelvins, 1000);
1870
1871        // Add update in pending
1872        handle.insert_validator_account(pubkey, create_test_validator_account(2000, 600));
1873
1874        // Lookup should now find pending value
1875        let found = handle.get_validator_account_from_pending(&pubkey);
1876        assert!(found.is_some());
1877        assert_eq!(found.unwrap().kelvins, 2000);
1878    }
1879
1880    // ========================================================================
1881    // Tombstone Handling Tests
1882    // ========================================================================
1883
1884    #[test]
1885    fn test_tombstone_in_pending_hides_frozen() {
1886        let pubkey = Pubkey::new_unique();
1887        let validator = Pubkey::new_unique();
1888        let handle = StakesHandle::default();
1889
1890        // Insert and freeze
1891        handle.insert_stake_account(pubkey, create_test_stake_account(1000, validator));
1892        handle.freeze_stakes();
1893
1894        // Add tombstone in pending (marks as deleted for next epoch)
1895        handle.raw_pending().tombstone_stake_account(pubkey);
1896
1897        // Lookup from pending should return None (tombstone = deleted)
1898        let found = handle.get_stake_account_from_pending(&pubkey);
1899        assert!(
1900            found.is_none(),
1901            "Tombstone in pending should hide frozen value"
1902        );
1903
1904        // Lookup from last frozen should still find the value (skips pending)
1905        let found_frozen = handle.get_stake_account_from_last_frozen(&pubkey);
1906        assert!(found_frozen.is_some());
1907        assert_eq!(found_frozen.unwrap().kelvins, 1000);
1908    }
1909
1910    #[test]
1911    fn test_tombstone_in_frozen_hides_baseline() {
1912        let pubkey = Pubkey::new_unique();
1913        let validator = Pubkey::new_unique();
1914
1915        // Create baseline with account
1916        let mut baseline_data = StakeCacheData::default();
1917        baseline_data
1918            .stake_accounts
1919            .insert(pubkey, Some(create_test_stake_account(1000, validator)));
1920        let baseline = StakeCache::with_data(baseline_data);
1921        let handle = StakesHandle::new_shared(
1922            baseline,
1923            StakeCache::default(),
1924            StakeHistory::default(),
1925            Arc::new(|_| false),
1926        );
1927
1928        // Add tombstone in pending and freeze
1929        handle.raw_pending().tombstone_stake_account(pubkey);
1930        handle.freeze_stakes();
1931
1932        // Lookup from last frozen should return None (tombstone hides baseline)
1933        let found = handle.get_stake_account_from_last_frozen(&pubkey);
1934        assert!(
1935            found.is_none(),
1936            "Tombstone in frozen should hide baseline value"
1937        );
1938
1939        // First frozen lookup should also see tombstone
1940        let found_first = handle.get_stake_account_from_first_frozen(&pubkey);
1941        assert!(found_first.is_none());
1942    }
1943
1944    #[test]
1945    fn test_tombstone_validator_account() {
1946        let pubkey = Pubkey::new_unique();
1947
1948        // Create baseline with validator
1949        let mut baseline_data = StakeCacheData::default();
1950        baseline_data
1951            .validator_accounts
1952            .insert(pubkey, Some(create_test_validator_account(1000, 500)));
1953        let baseline = StakeCache::with_data(baseline_data);
1954        let handle = StakesHandle::new_shared(
1955            baseline,
1956            StakeCache::default(),
1957            StakeHistory::default(),
1958            Arc::new(|_| false),
1959        );
1960
1961        // Lookup should find in baseline initially
1962        assert!(handle.get_validator_account_from_pending(&pubkey).is_some());
1963
1964        // Add tombstone in pending
1965        handle.raw_pending().tombstone_validator_account(pubkey);
1966
1967        // Lookup from pending should now return None
1968        let found = handle.get_validator_account_from_pending(&pubkey);
1969        assert!(found.is_none(), "Tombstone should hide baseline validator");
1970    }
1971
1972    #[test]
1973    fn test_get_all_validators_excludes_tombstones() {
1974        let pubkey1 = Pubkey::new_unique();
1975        let pubkey2 = Pubkey::new_unique();
1976
1977        // Create baseline with two validators
1978        let mut baseline_data = StakeCacheData::default();
1979        baseline_data
1980            .validator_accounts
1981            .insert(pubkey1, Some(create_test_validator_account(1000, 100)));
1982        baseline_data
1983            .validator_accounts
1984            .insert(pubkey2, Some(create_test_validator_account(2000, 200)));
1985        let baseline = StakeCache::with_data(baseline_data);
1986        let handle = StakesHandle::new_shared(
1987            baseline,
1988            StakeCache::default(),
1989            StakeHistory::default(),
1990            Arc::new(|_| false),
1991        );
1992
1993        // Initially should have 2 validators
1994        let all = handle.get_all_validator_accounts_from_pending();
1995        assert_eq!(all.len(), 2);
1996
1997        // Add tombstone for pubkey1 in pending
1998        handle.raw_pending().tombstone_validator_account(pubkey1);
1999
2000        // Now should only have 1 validator (pubkey2)
2001        let all = handle.get_all_validator_accounts_from_pending();
2002        assert_eq!(all.len(), 1);
2003        assert_eq!(all[0].0, pubkey2);
2004    }
2005
2006    #[test]
2007    fn test_tombstone_then_readd() {
2008        let pubkey = Pubkey::new_unique();
2009        let validator = Pubkey::new_unique();
2010
2011        // Create baseline with account
2012        let mut baseline_data = StakeCacheData::default();
2013        baseline_data
2014            .stake_accounts
2015            .insert(pubkey, Some(create_test_stake_account(1000, validator)));
2016        let baseline = StakeCache::with_data(baseline_data);
2017        let handle = StakesHandle::new_shared(
2018            baseline,
2019            StakeCache::default(),
2020            StakeHistory::default(),
2021            Arc::new(|_| false),
2022        );
2023
2024        // Delete in epoch 1
2025        handle.raw_pending().tombstone_stake_account(pubkey);
2026        handle.freeze_stakes();
2027
2028        // Should be deleted
2029        let found = handle.get_stake_account_from_last_frozen(&pubkey);
2030        assert!(found.is_none());
2031
2032        // Re-add in epoch 2 with new value
2033        handle.insert_stake_account(pubkey, create_test_stake_account(5000, validator));
2034        handle.freeze_stakes();
2035
2036        // Should be visible again with new value
2037        let found = handle.get_stake_account_from_last_frozen(&pubkey);
2038        assert!(found.is_some());
2039        assert_eq!(found.unwrap().kelvins, 5000);
2040    }
2041
2042    // ========================================================================
2043    // Empty Epoch Handling Tests
2044    // ========================================================================
2045
2046    #[test]
2047    fn test_empty_pending_freeze() {
2048        let handle = StakesHandle::default();
2049
2050        // Freeze with empty pending
2051        handle.freeze_stakes();
2052
2053        // Frozen should have 1 entry (empty delta)
2054        assert_eq!(handle.frozen_len(), 1);
2055
2056        // Lookup should still work (returns None for nonexistent)
2057        let pubkey = Pubkey::new_unique();
2058        assert!(handle.get_stake_account_from_pending(&pubkey).is_none());
2059    }
2060
2061    #[test]
2062    fn test_empty_frozen_epochs() {
2063        let pubkey = Pubkey::new_unique();
2064        let validator = Pubkey::new_unique();
2065
2066        // Create baseline with account
2067        let mut baseline_data = StakeCacheData::default();
2068        baseline_data
2069            .stake_accounts
2070            .insert(pubkey, Some(create_test_stake_account(1000, validator)));
2071        let baseline = StakeCache::with_data(baseline_data);
2072        let handle = StakesHandle::new_shared(
2073            baseline,
2074            StakeCache::default(),
2075            StakeHistory::default(),
2076            Arc::new(|_| false),
2077        );
2078
2079        // Freeze several empty epochs
2080        handle.freeze_stakes();
2081        handle.freeze_stakes();
2082        handle.freeze_stakes();
2083
2084        // Lookup should still find baseline value through empty frozen epochs
2085        let found = handle.get_stake_account_from_pending(&pubkey);
2086        assert!(found.is_some());
2087        assert_eq!(found.unwrap().kelvins, 1000);
2088    }
2089
2090    #[test]
2091    fn test_no_frozen_epochs_falls_through_to_baseline() {
2092        let pubkey = Pubkey::new_unique();
2093        let validator = Pubkey::new_unique();
2094
2095        // Create baseline with account, no frozen history
2096        let mut baseline_data = StakeCacheData::default();
2097        baseline_data
2098            .stake_accounts
2099            .insert(pubkey, Some(create_test_stake_account(1000, validator)));
2100        let baseline = StakeCache::with_data(baseline_data);
2101        let handle = StakesHandle::new_shared(
2102            baseline,
2103            StakeCache::default(),
2104            StakeHistory::default(),
2105            Arc::new(|_| false),
2106        );
2107
2108        // Lookup from last frozen should fall through to baseline
2109        let found = handle.get_stake_account_from_last_frozen(&pubkey);
2110        assert!(found.is_some());
2111        assert_eq!(found.unwrap().kelvins, 1000);
2112    }
2113
2114    #[test]
2115    fn test_get_all_stake_accounts_from_frozen_epoch() {
2116        // Test that from_frozen_epoch only includes deltas up to the target epoch
2117        let validator = Pubkey::new_unique();
2118
2119        // Baseline: one account
2120        let baseline_stake = Pubkey::new_unique();
2121        let mut baseline_data = StakeCacheData::default();
2122        baseline_data.stake_accounts.insert(
2123            baseline_stake,
2124            Some(create_test_stake_account(1000, validator)),
2125        );
2126        let baseline = StakeCache::with_data(baseline_data);
2127        let handle = StakesHandle::new_shared(
2128            baseline,
2129            StakeCache::default(),
2130            StakeHistory::default(),
2131            Arc::new(|_| false),
2132        );
2133
2134        // Epoch 5: Add stake_epoch5
2135        let stake_epoch5 = Pubkey::new_unique();
2136        handle.set_pending_epoch(5);
2137        handle.insert_stake_account(stake_epoch5, create_test_stake_account(2000, validator));
2138        handle.freeze_stakes();
2139
2140        // Epoch 6: Add stake_epoch6
2141        let stake_epoch6 = Pubkey::new_unique();
2142        handle.insert_stake_account(stake_epoch6, create_test_stake_account(3000, validator));
2143        handle.freeze_stakes();
2144
2145        // Epoch 7: Add stake_epoch7
2146        let stake_epoch7 = Pubkey::new_unique();
2147        handle.insert_stake_account(stake_epoch7, create_test_stake_account(4000, validator));
2148        handle.freeze_stakes();
2149
2150        // Verify: from_frozen_epoch(5) should include baseline + epoch 5 only
2151        let accounts_epoch5 = handle.get_all_stake_accounts_from_frozen_epoch(5);
2152        assert_eq!(accounts_epoch5.len(), 2); // baseline + epoch5
2153        assert!(accounts_epoch5.iter().any(|(k, _)| *k == baseline_stake));
2154        assert!(accounts_epoch5.iter().any(|(k, _)| *k == stake_epoch5));
2155        assert!(!accounts_epoch5.iter().any(|(k, _)| *k == stake_epoch6));
2156
2157        // Verify: from_frozen_epoch(6) should include baseline + epoch 5 + epoch 6
2158        let accounts_epoch6 = handle.get_all_stake_accounts_from_frozen_epoch(6);
2159        assert_eq!(accounts_epoch6.len(), 3);
2160        assert!(accounts_epoch6.iter().any(|(k, _)| *k == stake_epoch6));
2161        assert!(!accounts_epoch6.iter().any(|(k, _)| *k == stake_epoch7));
2162
2163        // Verify: from_frozen_epoch(7) should include all 4
2164        let accounts_epoch7 = handle.get_all_stake_accounts_from_frozen_epoch(7);
2165        assert_eq!(accounts_epoch7.len(), 4);
2166        assert!(accounts_epoch7.iter().any(|(k, _)| *k == stake_epoch7));
2167    }
2168
2169    #[test]
2170    fn test_get_all_validators_with_no_validators() {
2171        let handle = StakesHandle::default();
2172
2173        // No validators anywhere
2174        let all = handle.get_all_validator_accounts_from_pending();
2175        assert!(all.is_empty());
2176
2177        // Freeze and check again
2178        handle.freeze_stakes();
2179        let all = handle.get_all_validator_accounts_from_last_frozen();
2180        assert!(all.is_empty());
2181    }
2182
2183    // ========== Adoption Tracking Tests ==========
2184
2185    #[test]
2186    fn test_is_previous_epoch_adopted_empty_deque() {
2187        let handle = StakesHandle::default();
2188        // Empty frozen deque → no previous epoch to adopt → returns true
2189        assert!(handle.is_previous_epoch_adopted());
2190    }
2191
2192    #[test]
2193    fn test_is_previous_epoch_adopted_unadopted() {
2194        let handle = StakesHandle::default();
2195        // Push a frozen entry with consensus_adopted_at: None (default)
2196        handle.freeze_stakes();
2197        assert!(
2198            !handle.is_previous_epoch_adopted(),
2199            "Frozen entry with consensus_adopted_at=None should NOT be considered adopted"
2200        );
2201    }
2202
2203    #[test]
2204    fn test_is_previous_epoch_adopted_adopted() {
2205        let handle = StakesHandle::default();
2206        handle.freeze_stakes();
2207        handle.set_consensus_adopted_at(100);
2208        assert!(
2209            handle.is_previous_epoch_adopted(),
2210            "Frozen entry with consensus_adopted_at=Some(100) should be considered adopted"
2211        );
2212    }
2213
2214    #[test]
2215    fn test_is_previous_epoch_adopted_genesis() {
2216        let handle = StakesHandle::default();
2217        // Simulate genesis: set adoption on pending, then freeze
2218        handle.raw_pending().write().consensus_adopted_at = Some(0);
2219        handle.freeze_stakes();
2220        assert!(
2221            handle.is_previous_epoch_adopted(),
2222            "Genesis epoch with consensus_adopted_at=Some(0) should be considered adopted"
2223        );
2224    }
2225
2226    #[test]
2227    fn test_set_consensus_adopted_at() {
2228        let handle = StakesHandle::default();
2229        handle.freeze_stakes();
2230
2231        // Before setting, should be None
2232        assert_eq!(
2233            handle.raw_frozen().back().unwrap().consensus_adopted_at,
2234            None
2235        );
2236
2237        handle.set_consensus_adopted_at(42);
2238
2239        // After setting, should be Some(42)
2240        assert_eq!(
2241            handle.raw_frozen().back().unwrap().consensus_adopted_at,
2242            Some(42)
2243        );
2244    }
2245
2246    #[test]
2247    fn test_signal_and_take_handover() {
2248        let handle = StakesHandle::default();
2249
2250        // Signal a handover timestamp
2251        handle.signal_handover(100);
2252
2253        // First take should return the timestamp
2254        assert_eq!(handle.take_handover(), Some(100));
2255
2256        // Second take should return None (consumed)
2257        assert_eq!(handle.take_handover(), None);
2258    }
2259
2260    #[test]
2261    fn test_take_handover_without_signal() {
2262        let handle = StakesHandle::default();
2263        // No signal was set → take returns None
2264        assert_eq!(handle.take_handover(), None);
2265    }
2266
2267    #[test]
2268    fn test_signal_handover_overwrites() {
2269        let handle = StakesHandle::default();
2270
2271        // Signal twice — last writer wins
2272        handle.signal_handover(100);
2273        handle.signal_handover(200);
2274
2275        assert_eq!(
2276            handle.take_handover(),
2277            Some(200),
2278            "Second signal should overwrite the first"
2279        );
2280        assert_eq!(handle.take_handover(), None);
2281    }
2282}