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
10use std::{
11    collections::VecDeque,
12    sync::{
13        atomic::{AtomicBool, Ordering},
14        RwLock,
15    },
16};
17
18use im::HashMap as ImHashMap;
19use rialo_s_clock::Epoch;
20use rialo_s_pubkey::Pubkey;
21use rialo_s_type_overrides::sync::Arc;
22use rialo_stake_manager_interface::instruction::StakeInfo;
23use rialo_validator_registry_interface::instruction::ValidatorInfo;
24
25/// A cache of stake and validator accounts.
26///
27/// This wraps `StakeCacheData` in `Arc<RwLock<...>>` to allow thread-safe shared
28/// access during parallel transaction execution. The Arc allows the same data
29/// to be shared between the Bank and StakesHandle, so mutations to pending
30/// stake data by builtin programs are visible to the Bank.
31#[derive(Debug, Clone)]
32pub struct StakeCache(pub Arc<RwLock<StakeCacheData>>);
33
34impl Default for StakeCache {
35    fn default() -> Self {
36        Self(Arc::new(RwLock::new(StakeCacheData::default())))
37    }
38}
39
40impl StakeCache {
41    /// Create a new empty stake cache.
42    pub fn new() -> Self {
43        Self::default()
44    }
45
46    /// Create a stake cache with the given data.
47    pub fn with_data(data: StakeCacheData) -> Self {
48        Self(Arc::new(RwLock::new(data)))
49    }
50
51    /// Create a stake cache from an existing Arc (for sharing references).
52    pub fn from_arc(arc: Arc<RwLock<StakeCacheData>>) -> Self {
53        Self(arc)
54    }
55
56    /// Get a clone of the inner Arc for sharing.
57    pub fn arc_clone(&self) -> Arc<RwLock<StakeCacheData>> {
58        Arc::clone(&self.0)
59    }
60}
61
62/// Data structure holding the cached stake and validator accounts.
63///
64/// Uses `ImHashMap` for O(1) cloning via structural sharing.
65#[derive(Debug, Default, Clone)]
66pub struct StakeCacheData {
67    /// Map of stake accounts by public key.
68    pub stake_accounts: ImHashMap<Pubkey, StakeAccount>,
69    /// Map of validator accounts by public key.
70    pub validator_accounts: ImHashMap<Pubkey, ValidatorAccount>,
71    /// The epoch when this snapshot was taken.
72    pub epoch: Epoch,
73    /// The timestamp (in milliseconds) when this snapshot was taken.
74    pub timestamp: u64,
75}
76
77/// A history of frozen stake cache snapshots across epochs.
78///
79/// This wraps `VecDeque<StakeCacheData>` in `Arc<RwLock<...>>` to allow thread-safe
80/// shared access. The Arc allows the same history to be shared between the Bank
81/// and StakesHandle.
82///
83/// This maintains a queue of stake snapshots, with the oldest at the front
84/// and the most recent at the back. The ValidatorRegistry builtin pushes
85/// new snapshots, and the Bank pops completed epochs after reward distribution.
86#[derive(Debug, Clone)]
87pub struct StakeHistory(pub Arc<RwLock<VecDeque<StakeCacheData>>>);
88
89impl Default for StakeHistory {
90    fn default() -> Self {
91        Self(Arc::new(RwLock::new(VecDeque::new())))
92    }
93}
94
95impl StakeHistory {
96    /// Create a new empty stake history.
97    pub fn new() -> Self {
98        Self::default()
99    }
100
101    /// Create a stake history with an initial entry.
102    pub fn with_entry(data: StakeCacheData) -> Self {
103        let mut deque = VecDeque::new();
104        deque.push_back(data);
105        Self(Arc::new(RwLock::new(deque)))
106    }
107
108    /// Create a stake history from an existing Arc (for sharing references).
109    pub fn from_arc(arc: Arc<RwLock<VecDeque<StakeCacheData>>>) -> Self {
110        Self(arc)
111    }
112
113    /// Get a clone of the inner Arc for sharing.
114    pub fn arc_clone(&self) -> Arc<RwLock<VecDeque<StakeCacheData>>> {
115        Arc::clone(&self.0)
116    }
117
118    /// Push a new snapshot to the back of the history.
119    pub fn push_back(&self, data: StakeCacheData) {
120        self.0
121            .write()
122            .expect("Failed to acquire lock")
123            .push_back(data);
124    }
125
126    /// Pop the oldest snapshot from the front of the history.
127    pub fn pop_front(&self) -> Option<StakeCacheData> {
128        self.0.write().expect("Failed to acquire lock").pop_front()
129    }
130
131    /// Get the number of snapshots in the history.
132    pub fn len(&self) -> usize {
133        self.0.read().expect("Failed to acquire lock").len()
134    }
135
136    /// Check if the history is empty.
137    pub fn is_empty(&self) -> bool {
138        self.0.read().expect("Failed to acquire lock").is_empty()
139    }
140
141    /// Get a clone of the oldest snapshot (front).
142    pub fn front(&self) -> Option<StakeCacheData> {
143        self.0
144            .read()
145            .expect("Failed to acquire lock")
146            .front()
147            .cloned()
148    }
149
150    /// Get a clone of the newest snapshot (back).
151    ///
152    /// This returns the CURRENT epoch's frozen stake data. In normal operation,
153    /// this is never `None` because Bank initialization guarantees at least one
154    /// entry exists after genesis/register_validators.
155    ///
156    /// Use this for lookups that need the current epoch's effective stake state
157    /// (as opposed to `StakesHandle::pending` which is the next epoch being accumulated).
158    pub fn back(&self) -> Option<StakeCacheData> {
159        self.0
160            .read()
161            .expect("Failed to acquire lock")
162            .back()
163            .cloned()
164    }
165
166    /// Iterate over all snapshots from oldest to newest, returning cloned data.
167    ///
168    /// Note: This clones all entries. For large histories, consider accessing
169    /// specific entries via `front()` or `back()` instead.
170    pub fn iter_cloned(&self) -> Vec<StakeCacheData> {
171        self.0
172            .read()
173            .expect("Failed to acquire lock")
174            .iter()
175            .cloned()
176            .collect()
177    }
178}
179
180/// Represents a stake account with its data.
181#[derive(Debug, Clone)]
182pub struct StakeAccount {
183    /// The kelvins balance of the stake account.
184    pub kelvins: u64,
185    /// The deserialized stake info.
186    pub data: StakeInfo,
187}
188
189/// Represents a validator account with its data.
190#[derive(Debug, Clone)]
191pub struct ValidatorAccount {
192    /// The kelvins balance of the validator account.
193    pub kelvins: u64,
194    /// The deserialized validator info.
195    pub data: ValidatorInfo,
196}
197
198/// A lightweight representation of validator data for use by builtin programs.
199///
200/// This is a simplified view of `ValidatorAccount` that contains only the
201/// essential fields needed for builtin program logic, without the full
202/// `AccountSharedData`.
203#[derive(Debug, Clone, Default)]
204pub struct ValidatorAccountView {
205    /// The validator's node identity (their primary pubkey).
206    pub node_identity: Pubkey,
207    /// The validator's protocol key for verifying blocks.
208    pub protocol_key: Pubkey,
209    /// The total stake delegated to this validator.
210    pub stake: u64,
211    /// The validator's commission rate in basis points (e.g., 835 = 8.35%).
212    pub commission_rate: u16,
213}
214
215impl From<&ValidatorAccount> for ValidatorAccountView {
216    fn from(account: &ValidatorAccount) -> Self {
217        Self {
218            node_identity: account.data.node_identity,
219            protocol_key: account.data.protocol_key,
220            stake: account.data.stake,
221            commission_rate: account.data.commission_rate,
222        }
223    }
224}
225
226/// Handle for builtin programs to access stake cache data and freeze stakes.
227///
228/// This handle provides:
229/// - Read/write access to the pending (next epoch) stake cache data
230/// - The ability to freeze the pending stakes into history via `freeze_and_get_validator_stakes()`
231///
232/// # Epoch Semantics
233///
234/// **Important:** The `pending` field contains data for the NEXT epoch (i.e., changes being
235/// accumulated that will take effect after FreezeStakes). To get the CURRENT epoch's frozen
236/// data for lookups, use `history.back()` instead.
237///
238/// The handle is cached at block level for performance. When `freeze_and_get_validator_stakes()`
239/// is called, it sets `needs_refresh` to signal that subsequent transactions should
240/// recreate the handle to see the updated epoch.
241///
242/// # Thread Safety
243///
244/// Both `StakeCache` and `StakeHistory` wrap their data in `Arc<RwLock<...>>` internally,
245/// allowing safe concurrent access from builtin programs during transaction execution.
246/// Mutations to `pending` are immediately visible to the owning Bank since they share
247/// the same Arc.
248#[derive(Debug, Clone)]
249pub struct StakesHandle {
250    /// Stake cache data for the NEXT epoch (pending/accumulating changes).
251    ///
252    /// This is a mutable working copy that accumulates stake and validator account
253    /// modifications throughout the epoch. These changes will become effective after
254    /// the next FreezeStakes call. For current epoch lookups (the frozen effective
255    /// state), use `history.back()` instead.
256    ///
257    /// The `StakeCache` wrapper contains `Arc<RwLock<...>>` internally, allowing
258    /// builtin programs to mutate the pending stake data during transaction execution,
259    /// with changes visible to the Bank.
260    pub pending: StakeCache,
261    /// Target history to push frozen snapshots to.
262    ///
263    /// The `StakeHistory` wrapper contains `Arc<RwLock<...>>` internally.
264    pub history: StakeHistory,
265    /// Flag to signal that the cache needs to be refreshed after FreezeStakes.
266    /// Set to true by `freeze_and_get_validator_stakes()`.
267    pub needs_refresh: Arc<AtomicBool>,
268}
269
270impl Default for StakesHandle {
271    fn default() -> Self {
272        Self {
273            pending: StakeCache::default(),
274            history: StakeHistory::default(),
275            needs_refresh: Arc::new(AtomicBool::new(false)),
276        }
277    }
278}
279
280impl StakesHandle {
281    /// Create a new stakes handle with fresh data.
282    ///
283    /// This creates new `Arc<RwLock<...>>` wrappers for the provided data.
284    /// Note: This creates NEW Arcs, so changes won't be visible to the original data.
285    /// Use `new_shared` instead when you want to share Arcs with the Bank.
286    pub fn new(pending: StakeCacheData, history: StakeHistory) -> Self {
287        Self {
288            pending: StakeCache::with_data(pending),
289            history,
290            needs_refresh: Arc::new(AtomicBool::new(false)),
291        }
292    }
293
294    /// Create a new stakes handle with shared references.
295    ///
296    /// This shares the same `Arc<RwLock<...>>` with the Bank, so mutations
297    /// to `pending` by builtin programs are immediately visible to the Bank.
298    pub fn new_shared(pending: StakeCache, history: StakeHistory) -> Self {
299        Self {
300            pending,
301            history,
302            needs_refresh: Arc::new(AtomicBool::new(false)),
303        }
304    }
305
306    /// Check if this handle needs to be refreshed (was invalidated by FreezeStakes).
307    pub fn needs_refresh(&self) -> bool {
308        self.needs_refresh.load(Ordering::Acquire)
309    }
310
311    /// Freeze the pending stake cache data and return validator stakes.
312    ///
313    /// This performs an O(1) clone of the pending stake cache data (via `ImHashMap`
314    /// structural sharing) and pushes it to the back of the stake history queue.
315    /// This is typically called by the ValidatorRegistry program's FreezeStakes
316    /// instruction to capture the validator set at a specific point.
317    /// It then extracts and returns a vector of `(Pubkey, stake)` pairs for all
318    /// validators in the frozen snapshot.
319    ///
320    /// **Important:** This sets `needs_refresh` to true, signaling that subsequent
321    /// transactions should recreate their `StakesHandle` to see the updated epoch.
322    ///
323    /// Returns the validator stakes list, sorted by pubkey for determinism.
324    pub fn freeze_and_get_validator_stakes(&self) -> Vec<(Pubkey, u64)> {
325        // Clone pending data (O(1) due to ImHashMap structural sharing)
326        let snapshot = self
327            .pending
328            .0
329            .read()
330            .expect("Failed to acquire lock")
331            .clone();
332
333        // Push snapshot to history
334        self.history.push_back(snapshot.clone());
335
336        // Signal that cache needs refresh for subsequent transactions
337        self.needs_refresh.store(true, Ordering::Release);
338
339        // Extract validator stakes from the snapshot
340        let mut validator_stakes: Vec<(Pubkey, u64)> = snapshot
341            .validator_accounts
342            .iter()
343            .map(|(pubkey, validator_account)| (*pubkey, validator_account.data.stake))
344            .collect();
345
346        // Sort by pubkey for deterministic ordering
347        validator_stakes.sort_by_key(|(pubkey, _)| *pubkey);
348        validator_stakes
349    }
350}