Skip to main content

solana_program_runtime/
sysvar_cache.rs

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