Skip to main content

rialo_s_program_runtime/
sysvar_cache.rs

1// Copyright (c) Subzero Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3// This file is either (a) original to Subzero Labs, Inc. or (b) derived from the Anza codebase and modified by Subzero Labs, Inc.
4
5use rialo_s_clock::Clock;
6use rialo_s_epoch_schedule::EpochSchedule;
7use rialo_s_instruction::error::InstructionError;
8use rialo_s_pubkey::Pubkey;
9use rialo_s_rent::Rent;
10#[cfg(any(test, feature = "dev-context-only-utils"))]
11use rialo_s_sdk_ids::sysvar;
12#[allow(deprecated)]
13use rialo_s_sysvar::recent_blockhashes::RecentBlockhashes;
14use rialo_s_sysvar::Sysvar;
15use rialo_s_sysvar_id::SysvarId;
16use rialo_s_transaction_context::{IndexOfAccount, InstructionContext, TransactionContext};
17use rialo_s_type_overrides::sync::Arc;
18use serde::de::DeserializeOwned;
19
20use crate::invoke_context::InvokeContext;
21
22#[cfg(feature = "frozen-abi")]
23impl ::rialo_frozen_abi::abi_example::AbiExample for SysvarCache {
24    fn example() -> Self {
25        // SysvarCache is not Serialize so just rely on Default.
26        SysvarCache::default()
27    }
28}
29
30#[derive(Default, Clone, Debug)]
31pub struct SysvarCache {
32    // full account data as provided by bank, including any trailing zero bytes
33    clock: Option<Vec<u8>>,
34    epoch_schedule: Option<Vec<u8>>,
35    rent: Option<Vec<u8>>,
36
37    #[allow(deprecated)]
38    recent_blockhashes: Option<RecentBlockhashes>,
39}
40
41#[cfg(any(test, feature = "dev-context-only-utils"))]
42const RECENT_BLOCKHASHES_ID: Pubkey =
43    Pubkey::from_str_const("SysvarRecentB1ockHashes11111111111111111111");
44
45impl SysvarCache {
46    /// Overwrite a sysvar. For testing purposes only.
47    #[allow(deprecated)]
48    #[cfg(any(test, feature = "dev-context-only-utils"))]
49    pub fn set_sysvar_for_tests<T: Sysvar + SysvarId>(&mut self, sysvar: &T) {
50        let data = bincode::serialize(sysvar).expect("Failed to serialize sysvar.");
51        let sysvar_id = T::id();
52        match sysvar_id {
53            sysvar::clock::ID => {
54                self.clock = Some(data);
55            }
56            sysvar::epoch_schedule::ID => {
57                self.epoch_schedule = Some(data);
58            }
59            RECENT_BLOCKHASHES_ID => {
60                let recent_blockhashes: RecentBlockhashes = bincode::deserialize(&data)
61                    .expect("Failed to deserialize RecentBlockhashes sysvar.");
62                self.recent_blockhashes = Some(recent_blockhashes);
63            }
64            sysvar::rent::ID => {
65                self.rent = Some(data);
66            }
67            _ => panic!("Unrecognized Sysvar ID: {sysvar_id}"),
68        }
69    }
70
71    // this is exposed for SyscallGetSysvar and should not otherwise be used
72    pub fn sysvar_id_to_buffer(&self, sysvar_id: &Pubkey) -> &Option<Vec<u8>> {
73        if Clock::check_id(sysvar_id) {
74            &self.clock
75        } else if EpochSchedule::check_id(sysvar_id) {
76            &self.epoch_schedule
77        } else if Rent::check_id(sysvar_id) {
78            &self.rent
79        } else {
80            &None
81        }
82    }
83
84    // most if not all of the obj getter functions can be removed once builtins transition to bpf
85    // the Arc<T> wrapper is to preserve the existing public interface
86    fn get_sysvar_obj<T: DeserializeOwned>(
87        &self,
88        sysvar_id: &Pubkey,
89    ) -> Result<Arc<T>, InstructionError> {
90        if let Some(ref sysvar_buf) = self.sysvar_id_to_buffer(sysvar_id) {
91            bincode::deserialize(sysvar_buf)
92                .map(Arc::new)
93                .map_err(|_| InstructionError::UnsupportedSysvar)
94        } else {
95            Err(InstructionError::UnsupportedSysvar)
96        }
97    }
98
99    pub fn get_clock(&self) -> Result<Arc<Clock>, InstructionError> {
100        self.get_sysvar_obj(&Clock::id())
101    }
102
103    pub fn get_epoch_schedule(&self) -> Result<Arc<EpochSchedule>, InstructionError> {
104        self.get_sysvar_obj(&EpochSchedule::id())
105    }
106
107    pub fn get_rent(&self) -> Result<Arc<Rent>, InstructionError> {
108        self.get_sysvar_obj(&Rent::id())
109    }
110
111    #[deprecated]
112    #[allow(deprecated)]
113    pub fn get_recent_blockhashes(&self) -> Result<Arc<RecentBlockhashes>, InstructionError> {
114        self.recent_blockhashes
115            .clone()
116            .ok_or(InstructionError::UnsupportedSysvar)
117            .map(Arc::new)
118    }
119
120    pub fn fill_missing_entries<F: FnMut(&Pubkey, &mut dyn FnMut(&[u8]))>(
121        &mut self,
122        mut get_account_data: F,
123    ) {
124        if self.clock.is_none() {
125            get_account_data(&Clock::id(), &mut |data: &[u8]| {
126                if bincode::deserialize::<Clock>(data).is_ok() {
127                    self.clock = Some(data.to_vec());
128                }
129            });
130        }
131
132        if self.epoch_schedule.is_none() {
133            get_account_data(&EpochSchedule::id(), &mut |data: &[u8]| {
134                if bincode::deserialize::<EpochSchedule>(data).is_ok() {
135                    self.epoch_schedule = Some(data.to_vec());
136                }
137            });
138        }
139
140        if self.rent.is_none() {
141            get_account_data(&Rent::id(), &mut |data: &[u8]| {
142                if bincode::deserialize::<Rent>(data).is_ok() {
143                    self.rent = Some(data.to_vec());
144                }
145            });
146        }
147
148        #[allow(deprecated)]
149        if self.recent_blockhashes.is_none() {
150            get_account_data(&RecentBlockhashes::id(), &mut |data: &[u8]| {
151                if let Ok(recent_blockhashes) = bincode::deserialize(data) {
152                    self.recent_blockhashes = Some(recent_blockhashes);
153                }
154            });
155        }
156    }
157
158    pub fn reset(&mut self) {
159        *self = Self::default();
160    }
161}
162
163/// These methods facilitate a transition from fetching sysvars from keyed
164/// accounts to fetching from the sysvar cache without breaking consensus. In
165/// order to keep consistent behavior, they continue to enforce the same checks
166/// as `rialo_s_sdk::keyed_account::from_keyed_account` despite dynamically
167/// loading them instead of deserializing from account data.
168pub mod get_sysvar_with_account_check {
169    use super::*;
170
171    fn check_sysvar_account<S: Sysvar>(
172        transaction_context: &TransactionContext,
173        instruction_context: &InstructionContext,
174        instruction_account_index: IndexOfAccount,
175    ) -> Result<(), InstructionError> {
176        let index_in_transaction = instruction_context
177            .get_index_of_instruction_account_in_transaction(instruction_account_index)?;
178        if !S::check_id(transaction_context.get_key_of_account_at_index(index_in_transaction)?) {
179            return Err(InstructionError::InvalidArgument);
180        }
181        Ok(())
182    }
183
184    pub fn clock(
185        invoke_context: &InvokeContext<'_>,
186        instruction_context: &InstructionContext,
187        instruction_account_index: IndexOfAccount,
188    ) -> Result<Arc<Clock>, InstructionError> {
189        check_sysvar_account::<Clock>(
190            invoke_context.transaction_context,
191            instruction_context,
192            instruction_account_index,
193        )?;
194        invoke_context.get_sysvar_cache().get_clock()
195    }
196
197    pub fn rent(
198        invoke_context: &InvokeContext<'_>,
199        instruction_context: &InstructionContext,
200        instruction_account_index: IndexOfAccount,
201    ) -> Result<Arc<Rent>, InstructionError> {
202        check_sysvar_account::<Rent>(
203            invoke_context.transaction_context,
204            instruction_context,
205            instruction_account_index,
206        )?;
207        invoke_context.get_sysvar_cache().get_rent()
208    }
209
210    #[allow(deprecated)]
211    pub fn recent_blockhashes(
212        invoke_context: &InvokeContext<'_>,
213        instruction_context: &InstructionContext,
214        instruction_account_index: IndexOfAccount,
215    ) -> Result<Arc<RecentBlockhashes>, InstructionError> {
216        check_sysvar_account::<RecentBlockhashes>(
217            invoke_context.transaction_context,
218            instruction_context,
219            instruction_account_index,
220        )?;
221        invoke_context.get_sysvar_cache().get_recent_blockhashes()
222    }
223}
224
225#[cfg(test)]
226mod tests {
227    use test_case::test_case;
228
229    use super::*;
230
231    // sysvar cache provides the full account data of a sysvar
232    // the setters MUST NOT be changed to serialize an object representation
233    // it is required that the syscall be able to access the full buffer as it exists onchain
234    // this is meant to cover the cases:
235    // * account data is larger than struct sysvar
236    // * vector sysvar has fewer than its maximum entries
237    // if at any point the data is roundtripped through bincode, the vector will shrink
238    #[test_case(Clock::default(); "clock")]
239    #[test_case(EpochSchedule::default(); "epoch_schedule")]
240    #[test_case(Rent::default(); "rent")]
241    fn test_sysvar_cache_preserves_bytes<T: Sysvar>(_: T) {
242        let id = T::id();
243        let size = T::size_of().saturating_mul(2);
244        let in_buf = vec![0; size];
245
246        let mut sysvar_cache = SysvarCache::default();
247        sysvar_cache.fill_missing_entries(|pubkey, callback| {
248            if *pubkey == id {
249                callback(&in_buf)
250            }
251        });
252        let sysvar_cache = sysvar_cache;
253
254        let out_buf = sysvar_cache.sysvar_id_to_buffer(&id).clone().unwrap();
255
256        assert_eq!(out_buf, in_buf);
257    }
258}