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}