diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index c0ccad3d1e..2afc2adfae 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -1115,9 +1115,10 @@ mod tests { rent::Rent, signature::{Keypair, Signer}, system_program, sysvar, + sysvar_cache::SysvarCache, transaction::TransactionError, }, - std::{cell::RefCell, fs::File, io::Read, ops::Range, rc::Rc, sync::Arc}, + std::{borrow::Cow, cell::RefCell, fs::File, io::Read, ops::Range, rc::Rc, sync::Arc}, }; struct TestInstructionMeter { @@ -1374,7 +1375,7 @@ mod tests { compute_meter: MockComputeMeter::default(), programs: vec![], accounts: vec![], - sysvars: vec![], + sysvar_cache: Cow::Owned(SysvarCache::default()), disabled_features: vec![].into_iter().collect(), return_data: None, execute_timings: ExecuteDetailsTimings::default(), diff --git a/programs/bpf_loader/src/syscalls.rs b/programs/bpf_loader/src/syscalls.rs index d5caeeadc6..601431ab60 100644 --- a/programs/bpf_loader/src/syscalls.rs +++ b/programs/bpf_loader/src/syscalls.rs @@ -2774,8 +2774,9 @@ mod tests { fee_calculator::FeeCalculator, hash::hashv, process_instruction::{MockComputeMeter, MockInvokeContext, MockLogger}, + sysvar_cache::SysvarCache, }, - std::str::FromStr, + std::{borrow::Cow, str::FromStr}, }; macro_rules! assert_access_violation { @@ -3620,6 +3621,40 @@ mod tests { #[test] fn test_syscall_get_sysvar() { let config = Config::default(); + let src_clock = Clock { + slot: 1, + epoch_start_timestamp: 2, + epoch: 3, + leader_schedule_epoch: 4, + unix_timestamp: 5, + }; + let src_epochschedule = EpochSchedule { + slots_per_epoch: 1, + leader_schedule_slot_offset: 2, + warmup: false, + first_normal_epoch: 3, + first_normal_slot: 4, + }; + let src_fees = Fees { + fee_calculator: FeeCalculator { + lamports_per_signature: 1, + }, + }; + let src_rent = Rent { + lamports_per_byte_year: 1, + exemption_threshold: 2.0, + burn_percent: 3, + }; + + let mut sysvar_cache = SysvarCache::default(); + sysvar_cache.push_entry(sysvar::clock::id(), bincode::serialize(&src_clock).unwrap()); + sysvar_cache.push_entry( + sysvar::epoch_schedule::id(), + bincode::serialize(&src_epochschedule).unwrap(), + ); + sysvar_cache.push_entry(sysvar::fees::id(), bincode::serialize(&src_fees).unwrap()); + sysvar_cache.push_entry(sysvar::rent::id(), bincode::serialize(&src_rent).unwrap()); + // Test clock sysvar { let got_clock = Clock::default(); @@ -3640,17 +3675,8 @@ mod tests { ) .unwrap(); - let src_clock = Clock { - slot: 1, - epoch_start_timestamp: 2, - epoch: 3, - leader_schedule_epoch: 4, - unix_timestamp: 5, - }; let mut invoke_context = MockInvokeContext::new(vec![]); - let mut data = vec![]; - bincode::serialize_into(&mut data, &src_clock).unwrap(); - invoke_context.sysvars = vec![(sysvar::clock::id(), data)]; + invoke_context.sysvar_cache = Cow::Borrowed(&sysvar_cache); let mut syscall = SyscallGetClockSysvar { invoke_context: Rc::new(RefCell::new(&mut invoke_context)), @@ -3683,17 +3709,8 @@ mod tests { ) .unwrap(); - let src_epochschedule = EpochSchedule { - slots_per_epoch: 1, - leader_schedule_slot_offset: 2, - warmup: false, - first_normal_epoch: 3, - first_normal_slot: 4, - }; let mut invoke_context = MockInvokeContext::new(vec![]); - let mut data = vec![]; - bincode::serialize_into(&mut data, &src_epochschedule).unwrap(); - invoke_context.sysvars = vec![(sysvar::epoch_schedule::id(), data)]; + invoke_context.sysvar_cache = Cow::Borrowed(&sysvar_cache); let mut syscall = SyscallGetEpochScheduleSysvar { invoke_context: Rc::new(RefCell::new(&mut invoke_context)), @@ -3734,15 +3751,8 @@ mod tests { ) .unwrap(); - let src_fees = Fees { - fee_calculator: FeeCalculator { - lamports_per_signature: 1, - }, - }; let mut invoke_context = MockInvokeContext::new(vec![]); - let mut data = vec![]; - bincode::serialize_into(&mut data, &src_fees).unwrap(); - invoke_context.sysvars = vec![(sysvar::fees::id(), data)]; + invoke_context.sysvar_cache = Cow::Borrowed(&sysvar_cache); let mut syscall = SyscallGetFeesSysvar { invoke_context: Rc::new(RefCell::new(&mut invoke_context)), @@ -3775,15 +3785,8 @@ mod tests { ) .unwrap(); - let src_rent = Rent { - lamports_per_byte_year: 1, - exemption_threshold: 2.0, - burn_percent: 3, - }; let mut invoke_context = MockInvokeContext::new(vec![]); - let mut data = vec![]; - bincode::serialize_into(&mut data, &src_rent).unwrap(); - invoke_context.sysvars = vec![(sysvar::rent::id(), data)]; + invoke_context.sysvar_cache = Cow::Borrowed(&sysvar_cache); let mut syscall = SyscallGetRentSysvar { invoke_context: Rc::new(RefCell::new(&mut invoke_context)), diff --git a/programs/stake/src/stake_instruction.rs b/programs/stake/src/stake_instruction.rs index 74bd839870..62a7335289 100644 --- a/programs/stake/src/stake_instruction.rs +++ b/programs/stake/src/stake_instruction.rs @@ -288,9 +288,10 @@ mod tests { instruction::{self, LockupArgs}, state::{Authorized, Lockup, StakeAuthorize}, }, - sysvar::{stake_history::StakeHistory, Sysvar}, + sysvar::stake_history::StakeHistory, + sysvar_cache::SysvarCache, }, - std::{cell::RefCell, str::FromStr}, + std::{borrow::Cow, cell::RefCell, str::FromStr}, }; fn create_default_account() -> RefCell { @@ -370,9 +371,12 @@ mod tests { .collect(); let mut invoke_context = MockInvokeContext::new(keyed_accounts); - let mut data = Vec::with_capacity(sysvar::clock::Clock::size_of()); - bincode::serialize_into(&mut data, &sysvar::clock::Clock::default()).unwrap(); - invoke_context.sysvars = vec![(sysvar::clock::id(), data)]; + let mut sysvar_cache = SysvarCache::default(); + sysvar_cache.push_entry( + sysvar::clock::id(), + bincode::serialize(&Clock::default()).unwrap(), + ); + invoke_context.sysvar_cache = Cow::Owned(sysvar_cache); super::process_instruction(&Pubkey::default(), &instruction.data, &mut invoke_context) } } @@ -1035,9 +1039,12 @@ mod tests { ]; let mut invoke_context = MockInvokeContext::new(keyed_accounts); - let mut data = Vec::with_capacity(sysvar::clock::Clock::size_of()); - bincode::serialize_into(&mut data, &sysvar::clock::Clock::default()).unwrap(); - invoke_context.sysvars = vec![(sysvar::clock::id(), data)]; + let mut sysvar_cache = SysvarCache::default(); + sysvar_cache.push_entry( + sysvar::clock::id(), + bincode::serialize(&Clock::default()).unwrap(), + ); + invoke_context.sysvar_cache = Cow::Owned(sysvar_cache); assert_eq!( super::process_instruction( diff --git a/programs/vote/src/vote_instruction.rs b/programs/vote/src/vote_instruction.rs index 3af13d2e19..27261107c5 100644 --- a/programs/vote/src/vote_instruction.rs +++ b/programs/vote/src/vote_instruction.rs @@ -404,9 +404,9 @@ mod tests { account::{self, Account, AccountSharedData}, process_instruction::MockInvokeContext, rent::Rent, - sysvar::Sysvar, + sysvar_cache::SysvarCache, }, - std::{cell::RefCell, str::FromStr}, + std::{borrow::Cow, cell::RefCell, str::FromStr}, }; fn create_default_account() -> RefCell { @@ -463,9 +463,20 @@ mod tests { .map(|(meta, account)| KeyedAccount::new(&meta.pubkey, meta.is_signer, account)) .collect(); let mut invoke_context = MockInvokeContext::new(keyed_accounts); - let mut data = Vec::with_capacity(sysvar::rent::Rent::size_of()); - bincode::serialize_into(&mut data, &sysvar::rent::Rent::default()).unwrap(); - invoke_context.sysvars = vec![(sysvar::rent::id(), data)]; + let mut sysvar_cache = SysvarCache::default(); + sysvar_cache.push_entry( + sysvar::rent::id(), + bincode::serialize(&Rent::default()).unwrap(), + ); + sysvar_cache.push_entry( + sysvar::clock::id(), + bincode::serialize(&Clock::default()).unwrap(), + ); + sysvar_cache.push_entry( + sysvar::slot_hashes::id(), + bincode::serialize(&SlotHashes::default()).unwrap(), + ); + invoke_context.sysvar_cache = Cow::Owned(sysvar_cache); super::process_instruction(&Pubkey::default(), &instruction.data, &mut invoke_context) } } diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index fa26de78d0..b02b0f986a 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -116,6 +116,7 @@ use { slot_history::SlotHistory, system_transaction, sysvar::{self}, + sysvar_cache::SysvarCache, timing::years_as_slots, transaction::{self, Result, Transaction, TransactionError}, }, @@ -144,6 +145,8 @@ use { }, }; +mod sysvar_cache; + pub const SECONDS_PER_YEAR: f64 = 365.25 * 24.0 * 60.0 * 60.0; pub const MAX_LEADER_SCHEDULE_STAKES: Epoch = 5; @@ -1159,7 +1162,7 @@ pub struct Bank { pub cost_tracker: RwLock, - sysvar_cache: RwLock)>>, + sysvar_cache: RwLock, } impl Default for BlockhashQueue { @@ -1433,7 +1436,7 @@ impl Bank { )), freeze_started: AtomicBool::new(false), cost_tracker: RwLock::new(CostTracker::default()), - sysvar_cache: RwLock::new(Vec::new()), + sysvar_cache: RwLock::new(SysvarCache::default()), }; let mut ancestors = Vec::with_capacity(1 + new.parents().len()); @@ -1610,7 +1613,7 @@ impl Bank { freeze_started: AtomicBool::new(fields.hash != Hash::default()), vote_only_bank: false, cost_tracker: RwLock::new(CostTracker::default()), - sysvar_cache: RwLock::new(Vec::new()), + sysvar_cache: RwLock::new(SysvarCache::default()), }; bank.finish_init( genesis_config, @@ -1789,11 +1792,7 @@ impl Bank { // Update the entry in the cache let mut sysvar_cache = self.sysvar_cache.write().unwrap(); - if let Some(position) = sysvar_cache.iter().position(|(id, _data)| id == pubkey) { - sysvar_cache[position].1 = new_account.data().to_vec(); - } else { - sysvar_cache.push((*pubkey, new_account.data().to_vec())); - } + sysvar_cache.update_entry(pubkey, &new_account); } fn inherit_specially_retained_account_fields( @@ -1931,17 +1930,6 @@ impl Bank { }); } - fn fill_sysvar_cache(&mut self) { - let mut sysvar_cache = self.sysvar_cache.write().unwrap(); - for id in sysvar::ALL_IDS.iter() { - if !sysvar_cache.iter().any(|(key, _data)| key == id) { - if let Some(account) = self.get_account_with_fixed_root(id) { - sysvar_cache.push((*id, account.data().to_vec())); - } - } - } - } - pub fn get_slot_history(&self) -> SlotHistory { from_account(&self.get_account(&sysvar::slot_history::id()).unwrap()).unwrap() } diff --git a/runtime/src/bank/sysvar_cache.rs b/runtime/src/bank/sysvar_cache.rs new file mode 100644 index 0000000000..d6e3d42291 --- /dev/null +++ b/runtime/src/bank/sysvar_cache.rs @@ -0,0 +1,17 @@ +use { + super::Bank, + solana_sdk::{account::ReadableAccount, sysvar}, +}; + +impl Bank { + pub(crate) fn fill_sysvar_cache(&mut self) { + let mut sysvar_cache = self.sysvar_cache.write().unwrap(); + for id in sysvar::ALL_IDS.iter() { + if !sysvar_cache.iter().any(|(key, _data)| key == id) { + if let Some(account) = self.get_account_with_fixed_root(id) { + sysvar_cache.push_entry(*id, account.data().to_vec()); + } + } + } + } +} diff --git a/runtime/src/message_processor.rs b/runtime/src/message_processor.rs index 2a77b9de86..bb4a3e2d34 100644 --- a/runtime/src/message_processor.rs +++ b/runtime/src/message_processor.rs @@ -29,9 +29,11 @@ use { rent::Rent, saturating_add_assign, system_program, sysvar::instructions, + sysvar_cache::SysvarCache, transaction::TransactionError, }, std::{ + borrow::Cow, cell::{Ref, RefCell}, collections::HashMap, rc::Rc, @@ -277,7 +279,7 @@ pub struct ThisInvokeContext<'a> { pre_accounts: Vec, accounts: &'a [TransactionAccountRefCell], programs: &'a [(Pubkey, ProcessInstructionWithContext)], - sysvars: &'a [(Pubkey, Vec)], + sysvar_cache: Cow<'a, SysvarCache>, logger: Rc>, bpf_compute_budget: BpfComputeBudget, compute_meter: Rc>, @@ -298,7 +300,7 @@ impl<'a> ThisInvokeContext<'a> { executable_accounts: &'a [TransactionAccountRefCell], accounts: &'a [TransactionAccountRefCell], programs: &'a [(Pubkey, ProcessInstructionWithContext)], - sysvars: &'a [(Pubkey, Vec)], + sysvar_cache: Cow<'a, SysvarCache>, log_collector: Option>, bpf_compute_budget: BpfComputeBudget, compute_meter: Rc>, @@ -327,7 +329,7 @@ impl<'a> ThisInvokeContext<'a> { pre_accounts, accounts, programs, - sysvars, + sysvar_cache, logger: Rc::new(RefCell::new(ThisLogger { log_collector })), bpf_compute_budget, compute_meter, @@ -507,8 +509,8 @@ impl<'a> InvokeContext for ThisInvokeContext<'a> { self.timings.execute_us += execute_us; self.timings.deserialize_us += deserialize_us; } - fn get_sysvars(&self) -> &[(Pubkey, Vec)] { - self.sysvars + fn get_sysvar_cache(&self) -> &SysvarCache { + &self.sysvar_cache } fn set_return_data(&mut self, return_data: Option<(Pubkey, Vec)>) { self.return_data = return_data; @@ -1184,7 +1186,7 @@ impl MessageProcessor { executable_accounts: &[TransactionAccountRefCell], accounts: &[TransactionAccountRefCell], rent_collector: &RentCollector, - sysvars: &[(Pubkey, Vec)], + sysvar_cache: &SysvarCache, log_collector: Option>, executors: Rc>, instruction_recorder: Option, @@ -1234,7 +1236,7 @@ impl MessageProcessor { executable_accounts, accounts, &self.programs, - sysvars, + Cow::Borrowed(sysvar_cache), log_collector, bpf_compute_budget, compute_meter, @@ -1299,7 +1301,7 @@ impl MessageProcessor { bpf_compute_budget: BpfComputeBudget, compute_meter: Rc>, timings: &mut ExecuteTimings, - sysvars: &[(Pubkey, Vec)], + sysvar_cache: &SysvarCache, ) -> Result<(), TransactionError> { for (instruction_index, instruction) in message.instructions.iter().enumerate() { let instruction_recorder = instruction_recorders @@ -1312,7 +1314,7 @@ impl MessageProcessor { &loaders[instruction_index], accounts, rent_collector, - sysvars, + sysvar_cache, log_collector.clone(), executors.clone(), instruction_recorder, @@ -1386,7 +1388,7 @@ mod tests { &[], &accounts, &[], - &[], + Cow::Owned(SysvarCache::default()), None, BpfComputeBudget::default(), Rc::new(RefCell::new(MockComputeMeter::default())), @@ -1990,6 +1992,7 @@ mod tests { Some(&accounts[0].0), ); + let sysvar_cache = SysvarCache::default(); let result = message_processor.process_message( &message, &loaders, @@ -2002,7 +2005,7 @@ mod tests { BpfComputeBudget::new(), Rc::new(RefCell::new(MockComputeMeter::default())), &mut ExecuteTimings::default(), - &[], + &sysvar_cache, ); assert_eq!(result, Ok(())); assert_eq!(accounts[0].1.borrow().lamports(), 100); @@ -2029,7 +2032,7 @@ mod tests { BpfComputeBudget::new(), Rc::new(RefCell::new(MockComputeMeter::default())), &mut ExecuteTimings::default(), - &[], + &sysvar_cache, ); assert_eq!( result, @@ -2060,7 +2063,7 @@ mod tests { BpfComputeBudget::new(), Rc::new(RefCell::new(MockComputeMeter::default())), &mut ExecuteTimings::default(), - &[], + &sysvar_cache, ); assert_eq!( result, @@ -2170,6 +2173,7 @@ mod tests { )], Some(&accounts[0].0), ); + let sysvar_cache = SysvarCache::default(); let result = message_processor.process_message( &message, &loaders, @@ -2182,7 +2186,7 @@ mod tests { BpfComputeBudget::new(), Rc::new(RefCell::new(MockComputeMeter::default())), &mut ExecuteTimings::default(), - &[], + &sysvar_cache, ); assert_eq!( result, @@ -2213,7 +2217,7 @@ mod tests { BpfComputeBudget::new(), Rc::new(RefCell::new(MockComputeMeter::default())), &mut ExecuteTimings::default(), - &[], + &sysvar_cache, ); assert_eq!(result, Ok(())); @@ -2241,7 +2245,7 @@ mod tests { BpfComputeBudget::new(), Rc::new(RefCell::new(MockComputeMeter::default())), &mut ExecuteTimings::default(), - &[], + &sysvar_cache, ); assert_eq!(result, Ok(())); assert_eq!(accounts[0].1.borrow().lamports(), 80); @@ -2342,6 +2346,7 @@ mod tests { let feature_set = FeatureSet::all_enabled(); let demote_program_write_locks = feature_set.is_active(&demote_program_write_locks::id()); + let sysvar_cache = SysvarCache::default(); let mut invoke_context = ThisInvokeContext::new( &caller_program_id, Rent::default(), @@ -2350,7 +2355,7 @@ mod tests { &executable_accounts, &accounts, programs.as_slice(), - &[], + Cow::Borrowed(&sysvar_cache), None, BpfComputeBudget::default(), Rc::new(RefCell::new(MockComputeMeter::default())), @@ -2419,7 +2424,7 @@ mod tests { &executable_accounts, &accounts, programs.as_slice(), - &[], + Cow::Borrowed(&sysvar_cache), None, BpfComputeBudget::default(), Rc::new(RefCell::new(MockComputeMeter::default())), @@ -2562,6 +2567,7 @@ mod tests { ); let message = Message::new(&[callee_instruction.clone()], None); + let sysvar_cache = SysvarCache::default(); let mut invoke_context = ThisInvokeContext::new( &caller_program_id, Rent::default(), @@ -2570,7 +2576,7 @@ mod tests { &executable_accounts, &accounts, programs.as_slice(), - &[], + Cow::Borrowed(&sysvar_cache), None, BpfComputeBudget::default(), Rc::new(RefCell::new(MockComputeMeter::default())), @@ -2635,7 +2641,7 @@ mod tests { &executable_accounts, &accounts, programs.as_slice(), - &[], + Cow::Borrowed(&sysvar_cache), None, BpfComputeBudget::default(), Rc::new(RefCell::new(MockComputeMeter::default())), diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 957658430a..762b7100cc 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -48,6 +48,8 @@ pub mod shred_version; pub mod signature; pub mod signer; pub mod system_transaction; +#[doc(hidden)] +pub mod sysvar_cache; pub mod timing; pub mod transaction; pub mod transport; diff --git a/sdk/src/process_instruction.rs b/sdk/src/process_instruction.rs index b39cfd552b..061fb6933d 100644 --- a/sdk/src/process_instruction.rs +++ b/sdk/src/process_instruction.rs @@ -1,4 +1,5 @@ use { + crate::sysvar_cache::SysvarCache, itertools::Itertools, solana_sdk::{ account::AccountSharedData, @@ -8,7 +9,7 @@ use { pubkey::Pubkey, sysvar::Sysvar, }, - std::{cell::RefCell, collections::HashSet, fmt::Debug, rc::Rc, sync::Arc}, + std::{borrow::Cow, cell::RefCell, collections::HashSet, fmt::Debug, rc::Rc, sync::Arc}, }; /// Prototype of a native loader entry point @@ -104,8 +105,8 @@ pub trait InvokeContext { execute_us: u64, deserialize_us: u64, ); - /// Get sysvars - fn get_sysvars(&self) -> &[(Pubkey, Vec)]; + /// Get sysvar cache + fn get_sysvar_cache(&self) -> &SysvarCache; /// Set the return data fn set_return_data(&mut self, return_data: Option<(Pubkey, Vec)>); /// Get the return data @@ -149,7 +150,7 @@ pub fn get_sysvar( id: &Pubkey, ) -> Result { invoke_context - .get_sysvars() + .get_sysvar_cache() .iter() .find_map(|(key, data)| { if id == key { @@ -394,7 +395,7 @@ pub struct MockInvokeContext<'a> { pub compute_meter: MockComputeMeter, pub programs: Vec<(Pubkey, ProcessInstructionWithContext)>, pub accounts: Vec<(Pubkey, Rc>)>, - pub sysvars: Vec<(Pubkey, Vec)>, + pub sysvar_cache: Cow<'a, SysvarCache>, pub disabled_features: HashSet, pub return_data: Option<(Pubkey, Vec)>, pub execute_timings: ExecuteDetailsTimings, @@ -412,7 +413,7 @@ impl<'a> MockInvokeContext<'a> { }, programs: vec![], accounts: vec![], - sysvars: vec![], + sysvar_cache: Cow::Owned(SysvarCache::default()), disabled_features: HashSet::default(), return_data: None, execute_timings: ExecuteDetailsTimings::default(), @@ -514,8 +515,8 @@ impl<'a> InvokeContext for MockInvokeContext<'a> { _deserialize_us: u64, ) { } - fn get_sysvars(&self) -> &[(Pubkey, Vec)] { - &self.sysvars + fn get_sysvar_cache(&self) -> &SysvarCache { + &self.sysvar_cache } fn set_return_data(&mut self, return_data: Option<(Pubkey, Vec)>) { self.return_data = return_data; diff --git a/sdk/src/sysvar_cache.rs b/sdk/src/sysvar_cache.rs new file mode 100644 index 0000000000..ba9629c992 --- /dev/null +++ b/sdk/src/sysvar_cache.rs @@ -0,0 +1,39 @@ +use { + solana_sdk::{ + account::{AccountSharedData, ReadableAccount}, + pubkey::Pubkey, + }, + std::ops::Deref, +}; + +#[cfg(RUSTC_WITH_SPECIALIZATION)] +impl ::solana_frozen_abi::abi_example::AbiExample for SysvarCache { + fn example() -> Self { + // SysvarCache is not Serialize so just rely on Default. + SysvarCache::default() + } +} + +#[derive(Default, Clone, Debug)] +pub struct SysvarCache(Vec<(Pubkey, Vec)>); + +impl Deref for SysvarCache { + type Target = Vec<(Pubkey, Vec)>; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl SysvarCache { + pub fn push_entry(&mut self, pubkey: Pubkey, data: Vec) { + self.0.push((pubkey, data)); + } + + pub fn update_entry(&mut self, pubkey: &Pubkey, new_account: &AccountSharedData) { + if let Some(position) = self.iter().position(|(id, _data)| id == pubkey) { + self.0[position].1 = new_account.data().to_vec(); + } else { + self.0.push((*pubkey, new_account.data().to_vec())); + } + } +}