solana_program_runtime/
sysvar_cache.rs

1#[allow(deprecated)]
2use solana_sdk::sysvar::{fees::Fees, recent_blockhashes::RecentBlockhashes};
3use {
4    crate::invoke_context::InvokeContext,
5    serde::de::DeserializeOwned,
6    solana_sdk::{
7        instruction::InstructionError,
8        pubkey::Pubkey,
9        sysvar::{
10            self, clock::Clock, epoch_rewards::EpochRewards, epoch_schedule::EpochSchedule,
11            last_restart_slot::LastRestartSlot, rent::Rent, slot_hashes::SlotHashes,
12            stake_history::StakeHistory, Sysvar, SysvarId,
13        },
14        transaction_context::{IndexOfAccount, InstructionContext, TransactionContext},
15    },
16    solana_type_overrides::sync::Arc,
17};
18
19#[cfg(feature = "frozen-abi")]
20impl ::solana_frozen_abi::abi_example::AbiExample for SysvarCache {
21    fn example() -> Self {
22        // SysvarCache is not Serialize so just rely on Default.
23        SysvarCache::default()
24    }
25}
26
27#[derive(Default, Clone, Debug)]
28pub struct SysvarCache {
29    // full account data as provided by bank, including any trailing zero bytes
30    clock: Option<Vec<u8>>,
31    epoch_schedule: Option<Vec<u8>>,
32    epoch_rewards: Option<Vec<u8>>,
33    rent: Option<Vec<u8>>,
34    slot_hashes: Option<Vec<u8>>,
35    stake_history: Option<Vec<u8>>,
36    last_restart_slot: Option<Vec<u8>>,
37
38    // object representations of large sysvars for convenience
39    // these are used by the stake and vote builtin programs
40    // these should be removed once those programs are ported to bpf
41    slot_hashes_obj: Option<Arc<SlotHashes>>,
42    stake_history_obj: Option<Arc<StakeHistory>>,
43
44    // deprecated sysvars, these should be removed once practical
45    #[allow(deprecated)]
46    fees: Option<Fees>,
47    #[allow(deprecated)]
48    recent_blockhashes: Option<RecentBlockhashes>,
49}
50
51// declare_deprecated_sysvar_id doesn't support const.
52// These sysvars are going away anyway.
53const FEES_ID: Pubkey = Pubkey::from_str_const("SysvarFees111111111111111111111111111111111");
54const RECENT_BLOCKHASHES_ID: Pubkey =
55    Pubkey::from_str_const("SysvarRecentB1ockHashes11111111111111111111");
56
57impl SysvarCache {
58    /// Overwrite a sysvar. For testing purposes only.
59    #[allow(deprecated)]
60    pub fn set_sysvar_for_tests<T: Sysvar + SysvarId>(&mut self, sysvar: &T) {
61        let data = bincode::serialize(sysvar).expect("Failed to serialize sysvar.");
62        let sysvar_id = T::id();
63        match sysvar_id {
64            sysvar::clock::ID => {
65                self.clock = Some(data);
66            }
67            sysvar::epoch_rewards::ID => {
68                self.epoch_rewards = Some(data);
69            }
70            sysvar::epoch_schedule::ID => {
71                self.epoch_schedule = Some(data);
72            }
73            FEES_ID => {
74                let fees: Fees =
75                    bincode::deserialize(&data).expect("Failed to deserialize Fees sysvar.");
76                self.fees = Some(fees);
77            }
78            sysvar::last_restart_slot::ID => {
79                self.last_restart_slot = Some(data);
80            }
81            RECENT_BLOCKHASHES_ID => {
82                let recent_blockhashes: RecentBlockhashes = bincode::deserialize(&data)
83                    .expect("Failed to deserialize RecentBlockhashes sysvar.");
84                self.recent_blockhashes = Some(recent_blockhashes);
85            }
86            sysvar::rent::ID => {
87                self.rent = Some(data);
88            }
89            sysvar::slot_hashes::ID => {
90                let slot_hashes: SlotHashes =
91                    bincode::deserialize(&data).expect("Failed to deserialize SlotHashes sysvar.");
92                self.slot_hashes = Some(data);
93                self.slot_hashes_obj = Some(Arc::new(slot_hashes));
94            }
95            sysvar::stake_history::ID => {
96                let stake_history: StakeHistory = bincode::deserialize(&data)
97                    .expect("Failed to deserialize StakeHistory sysvar.");
98                self.stake_history = Some(data);
99                self.stake_history_obj = Some(Arc::new(stake_history));
100            }
101            _ => panic!("Unrecognized Sysvar ID: {sysvar_id}"),
102        }
103    }
104
105    // this is exposed for SyscallGetSysvar and should not otherwise be used
106    pub fn sysvar_id_to_buffer(&self, sysvar_id: &Pubkey) -> &Option<Vec<u8>> {
107        if Clock::check_id(sysvar_id) {
108            &self.clock
109        } else if EpochSchedule::check_id(sysvar_id) {
110            &self.epoch_schedule
111        } else if EpochRewards::check_id(sysvar_id) {
112            &self.epoch_rewards
113        } else if Rent::check_id(sysvar_id) {
114            &self.rent
115        } else if SlotHashes::check_id(sysvar_id) {
116            &self.slot_hashes
117        } else if StakeHistory::check_id(sysvar_id) {
118            &self.stake_history
119        } else if LastRestartSlot::check_id(sysvar_id) {
120            &self.last_restart_slot
121        } else {
122            &None
123        }
124    }
125
126    // most if not all of the obj getter functions can be removed once builtins transition to bpf
127    // the Arc<T> wrapper is to preserve the existing public interface
128    fn get_sysvar_obj<T: DeserializeOwned>(
129        &self,
130        sysvar_id: &Pubkey,
131    ) -> Result<Arc<T>, InstructionError> {
132        if let Some(ref sysvar_buf) = self.sysvar_id_to_buffer(sysvar_id) {
133            bincode::deserialize(sysvar_buf)
134                .map(Arc::new)
135                .map_err(|_| InstructionError::UnsupportedSysvar)
136        } else {
137            Err(InstructionError::UnsupportedSysvar)
138        }
139    }
140
141    pub fn get_clock(&self) -> Result<Arc<Clock>, InstructionError> {
142        self.get_sysvar_obj(&Clock::id())
143    }
144
145    pub fn get_epoch_schedule(&self) -> Result<Arc<EpochSchedule>, InstructionError> {
146        self.get_sysvar_obj(&EpochSchedule::id())
147    }
148
149    pub fn get_epoch_rewards(&self) -> Result<Arc<EpochRewards>, InstructionError> {
150        self.get_sysvar_obj(&EpochRewards::id())
151    }
152
153    pub fn get_rent(&self) -> Result<Arc<Rent>, InstructionError> {
154        self.get_sysvar_obj(&Rent::id())
155    }
156
157    pub fn get_last_restart_slot(&self) -> Result<Arc<LastRestartSlot>, InstructionError> {
158        self.get_sysvar_obj(&LastRestartSlot::id())
159    }
160
161    pub fn get_stake_history(&self) -> Result<Arc<StakeHistory>, InstructionError> {
162        self.stake_history_obj
163            .clone()
164            .ok_or(InstructionError::UnsupportedSysvar)
165    }
166
167    pub fn get_slot_hashes(&self) -> Result<Arc<SlotHashes>, InstructionError> {
168        self.slot_hashes_obj
169            .clone()
170            .ok_or(InstructionError::UnsupportedSysvar)
171    }
172
173    #[deprecated]
174    #[allow(deprecated)]
175    pub fn get_fees(&self) -> Result<Arc<Fees>, InstructionError> {
176        self.fees
177            .clone()
178            .ok_or(InstructionError::UnsupportedSysvar)
179            .map(Arc::new)
180    }
181
182    #[deprecated]
183    #[allow(deprecated)]
184    pub fn get_recent_blockhashes(&self) -> Result<Arc<RecentBlockhashes>, InstructionError> {
185        self.recent_blockhashes
186            .clone()
187            .ok_or(InstructionError::UnsupportedSysvar)
188            .map(Arc::new)
189    }
190
191    pub fn fill_missing_entries<F: FnMut(&Pubkey, &mut dyn FnMut(&[u8]))>(
192        &mut self,
193        mut get_account_data: F,
194    ) {
195        if self.clock.is_none() {
196            get_account_data(&Clock::id(), &mut |data: &[u8]| {
197                if bincode::deserialize::<Clock>(data).is_ok() {
198                    self.clock = Some(data.to_vec());
199                }
200            });
201        }
202
203        if self.epoch_schedule.is_none() {
204            get_account_data(&EpochSchedule::id(), &mut |data: &[u8]| {
205                if bincode::deserialize::<EpochSchedule>(data).is_ok() {
206                    self.epoch_schedule = Some(data.to_vec());
207                }
208            });
209        }
210
211        if self.epoch_rewards.is_none() {
212            get_account_data(&EpochRewards::id(), &mut |data: &[u8]| {
213                if bincode::deserialize::<EpochRewards>(data).is_ok() {
214                    self.epoch_rewards = Some(data.to_vec());
215                }
216            });
217        }
218
219        if self.rent.is_none() {
220            get_account_data(&Rent::id(), &mut |data: &[u8]| {
221                if bincode::deserialize::<Rent>(data).is_ok() {
222                    self.rent = Some(data.to_vec());
223                }
224            });
225        }
226
227        if self.slot_hashes.is_none() {
228            get_account_data(&SlotHashes::id(), &mut |data: &[u8]| {
229                if let Ok(obj) = bincode::deserialize::<SlotHashes>(data) {
230                    self.slot_hashes = Some(data.to_vec());
231                    self.slot_hashes_obj = Some(Arc::new(obj));
232                }
233            });
234        }
235
236        if self.stake_history.is_none() {
237            get_account_data(&StakeHistory::id(), &mut |data: &[u8]| {
238                if let Ok(obj) = bincode::deserialize::<StakeHistory>(data) {
239                    self.stake_history = Some(data.to_vec());
240                    self.stake_history_obj = Some(Arc::new(obj));
241                }
242            });
243        }
244
245        if self.last_restart_slot.is_none() {
246            get_account_data(&LastRestartSlot::id(), &mut |data: &[u8]| {
247                if bincode::deserialize::<LastRestartSlot>(data).is_ok() {
248                    self.last_restart_slot = Some(data.to_vec());
249                }
250            });
251        }
252
253        #[allow(deprecated)]
254        if self.fees.is_none() {
255            get_account_data(&Fees::id(), &mut |data: &[u8]| {
256                if let Ok(fees) = bincode::deserialize(data) {
257                    self.fees = Some(fees);
258                }
259            });
260        }
261
262        #[allow(deprecated)]
263        if self.recent_blockhashes.is_none() {
264            get_account_data(&RecentBlockhashes::id(), &mut |data: &[u8]| {
265                if let Ok(recent_blockhashes) = bincode::deserialize(data) {
266                    self.recent_blockhashes = Some(recent_blockhashes);
267                }
268            });
269        }
270    }
271
272    pub fn reset(&mut self) {
273        *self = Self::default();
274    }
275}
276
277/// These methods facilitate a transition from fetching sysvars from keyed
278/// accounts to fetching from the sysvar cache without breaking consensus. In
279/// order to keep consistent behavior, they continue to enforce the same checks
280/// as `solana_sdk::keyed_account::from_keyed_account` despite dynamically
281/// loading them instead of deserializing from account data.
282pub mod get_sysvar_with_account_check {
283    use super::*;
284
285    fn check_sysvar_account<S: Sysvar>(
286        transaction_context: &TransactionContext,
287        instruction_context: &InstructionContext,
288        instruction_account_index: IndexOfAccount,
289    ) -> Result<(), InstructionError> {
290        let index_in_transaction = instruction_context
291            .get_index_of_instruction_account_in_transaction(instruction_account_index)?;
292        if !S::check_id(transaction_context.get_key_of_account_at_index(index_in_transaction)?) {
293            return Err(InstructionError::InvalidArgument);
294        }
295        Ok(())
296    }
297
298    pub fn clock(
299        invoke_context: &InvokeContext,
300        instruction_context: &InstructionContext,
301        instruction_account_index: IndexOfAccount,
302    ) -> Result<Arc<Clock>, InstructionError> {
303        check_sysvar_account::<Clock>(
304            invoke_context.transaction_context,
305            instruction_context,
306            instruction_account_index,
307        )?;
308        invoke_context.get_sysvar_cache().get_clock()
309    }
310
311    pub fn rent(
312        invoke_context: &InvokeContext,
313        instruction_context: &InstructionContext,
314        instruction_account_index: IndexOfAccount,
315    ) -> Result<Arc<Rent>, InstructionError> {
316        check_sysvar_account::<Rent>(
317            invoke_context.transaction_context,
318            instruction_context,
319            instruction_account_index,
320        )?;
321        invoke_context.get_sysvar_cache().get_rent()
322    }
323
324    pub fn slot_hashes(
325        invoke_context: &InvokeContext,
326        instruction_context: &InstructionContext,
327        instruction_account_index: IndexOfAccount,
328    ) -> Result<Arc<SlotHashes>, InstructionError> {
329        check_sysvar_account::<SlotHashes>(
330            invoke_context.transaction_context,
331            instruction_context,
332            instruction_account_index,
333        )?;
334        invoke_context.get_sysvar_cache().get_slot_hashes()
335    }
336
337    #[allow(deprecated)]
338    pub fn recent_blockhashes(
339        invoke_context: &InvokeContext,
340        instruction_context: &InstructionContext,
341        instruction_account_index: IndexOfAccount,
342    ) -> Result<Arc<RecentBlockhashes>, InstructionError> {
343        check_sysvar_account::<RecentBlockhashes>(
344            invoke_context.transaction_context,
345            instruction_context,
346            instruction_account_index,
347        )?;
348        invoke_context.get_sysvar_cache().get_recent_blockhashes()
349    }
350
351    pub fn stake_history(
352        invoke_context: &InvokeContext,
353        instruction_context: &InstructionContext,
354        instruction_account_index: IndexOfAccount,
355    ) -> Result<Arc<StakeHistory>, InstructionError> {
356        check_sysvar_account::<StakeHistory>(
357            invoke_context.transaction_context,
358            instruction_context,
359            instruction_account_index,
360        )?;
361        invoke_context.get_sysvar_cache().get_stake_history()
362    }
363
364    pub fn last_restart_slot(
365        invoke_context: &InvokeContext,
366        instruction_context: &InstructionContext,
367        instruction_account_index: IndexOfAccount,
368    ) -> Result<Arc<LastRestartSlot>, InstructionError> {
369        check_sysvar_account::<LastRestartSlot>(
370            invoke_context.transaction_context,
371            instruction_context,
372            instruction_account_index,
373        )?;
374        invoke_context.get_sysvar_cache().get_last_restart_slot()
375    }
376}
377
378#[cfg(test)]
379mod tests {
380    use {super::*, test_case::test_case};
381
382    // sysvar cache provides the full account data of a sysvar
383    // the setters MUST NOT be changed to serialize an object representation
384    // it is required that the syscall be able to access the full buffer as it exists onchain
385    // this is meant to cover the cases:
386    // * account data is larger than struct sysvar
387    // * vector sysvar has fewer than its maximum entries
388    // if at any point the data is roundtripped through bincode, the vector will shrink
389    #[test_case(Clock::default(); "clock")]
390    #[test_case(EpochSchedule::default(); "epoch_schedule")]
391    #[test_case(EpochRewards::default(); "epoch_rewards")]
392    #[test_case(Rent::default(); "rent")]
393    #[test_case(SlotHashes::default(); "slot_hashes")]
394    #[test_case(StakeHistory::default(); "stake_history")]
395    #[test_case(LastRestartSlot::default(); "last_restart_slot")]
396    fn test_sysvar_cache_preserves_bytes<T: Sysvar>(_: T) {
397        let id = T::id();
398        let size = T::size_of().saturating_mul(2);
399        let in_buf = vec![0; size];
400
401        let mut sysvar_cache = SysvarCache::default();
402        sysvar_cache.fill_missing_entries(|pubkey, callback| {
403            if *pubkey == id {
404                callback(&in_buf)
405            }
406        });
407        let sysvar_cache = sysvar_cache;
408
409        let out_buf = sysvar_cache.sysvar_id_to_buffer(&id).clone().unwrap();
410
411        assert_eq!(out_buf, in_buf);
412    }
413}