diff --git a/program-runtime/src/invoke_context.rs b/program-runtime/src/invoke_context.rs index 69754dcd6d..5350a530c6 100644 --- a/program-runtime/src/invoke_context.rs +++ b/program-runtime/src/invoke_context.rs @@ -26,7 +26,6 @@ use { pubkey::Pubkey, rent::Rent, saturating_add_assign, - sysvar::Sysvar, transaction_context::{InstructionAccount, TransactionAccount, TransactionContext}, }, std::{borrow::Cow, cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc, sync::Arc}, @@ -988,21 +987,9 @@ impl<'a> InvokeContext<'a> { &self.current_compute_budget } - /// Get the value of a sysvar by its id - pub fn get_sysvar(&self, id: &Pubkey) -> Result { - self.sysvar_cache - .iter() - .find_map(|(key, data)| { - if id == key { - bincode::deserialize(data).ok() - } else { - None - } - }) - .ok_or_else(|| { - ic_msg!(self, "Unable to get sysvar {}", id); - InstructionError::UnsupportedSysvar - }) + /// Get cached sysvars + pub fn get_sysvar_cache(&self) -> &SysvarCache { + &self.sysvar_cache } } diff --git a/program-runtime/src/sysvar_cache.rs b/program-runtime/src/sysvar_cache.rs index ba9629c992..6bc744b63b 100644 --- a/program-runtime/src/sysvar_cache.rs +++ b/program-runtime/src/sysvar_cache.rs @@ -1,9 +1,13 @@ +#[allow(deprecated)] +use solana_sdk::sysvar::fees::Fees; use { solana_sdk::{ - account::{AccountSharedData, ReadableAccount}, - pubkey::Pubkey, + instruction::InstructionError, + sysvar::{ + clock::Clock, epoch_schedule::EpochSchedule, rent::Rent, slot_hashes::SlotHashes, + }, }, - std::ops::Deref, + std::sync::Arc, }; #[cfg(RUSTC_WITH_SPECIALIZATION)] @@ -15,25 +19,63 @@ impl ::solana_frozen_abi::abi_example::AbiExample for SysvarCache { } #[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 - } +pub struct SysvarCache { + clock: Option>, + epoch_schedule: Option>, + #[allow(deprecated)] + fees: Option>, + rent: Option>, + slot_hashes: Option>, } impl SysvarCache { - pub fn push_entry(&mut self, pubkey: Pubkey, data: Vec) { - self.0.push((pubkey, data)); + pub fn get_clock(&self) -> Result, InstructionError> { + self.clock + .clone() + .ok_or(InstructionError::UnsupportedSysvar) } - 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())); - } + pub fn set_clock(&mut self, clock: Clock) { + self.clock = Some(Arc::new(clock)); + } + + pub fn get_epoch_schedule(&self) -> Result, InstructionError> { + self.epoch_schedule + .clone() + .ok_or(InstructionError::UnsupportedSysvar) + } + + pub fn set_epoch_schedule(&mut self, epoch_schedule: EpochSchedule) { + self.epoch_schedule = Some(Arc::new(epoch_schedule)); + } + + #[deprecated] + #[allow(deprecated)] + pub fn get_fees(&self) -> Result, InstructionError> { + self.fees.clone().ok_or(InstructionError::UnsupportedSysvar) + } + + #[deprecated] + #[allow(deprecated)] + pub fn set_fees(&mut self, fees: Fees) { + self.fees = Some(Arc::new(fees)); + } + + pub fn get_rent(&self) -> Result, InstructionError> { + self.rent.clone().ok_or(InstructionError::UnsupportedSysvar) + } + + pub fn set_rent(&mut self, rent: Rent) { + self.rent = Some(Arc::new(rent)); + } + + pub fn get_slot_hashes(&self) -> Result, InstructionError> { + self.slot_hashes + .clone() + .ok_or(InstructionError::UnsupportedSysvar) + } + + pub fn set_slot_hashes(&mut self, slot_hashes: SlotHashes) { + self.slot_hashes = Some(Arc::new(slot_hashes)); } } diff --git a/program-test/src/lib.rs b/program-test/src/lib.rs index 07913ce7f7..aa7beb75a6 100644 --- a/program-test/src/lib.rs +++ b/program-test/src/lib.rs @@ -1,8 +1,6 @@ //! The solana-program-test provides a BanksClient-based test framework BPF programs #![allow(clippy::integer_arithmetic)] -#[allow(deprecated)] -use solana_sdk::sysvar::fees::Fees; // Export tokio for test clients pub use tokio; use { @@ -24,10 +22,9 @@ use { solana_sdk::{ account::{Account, AccountSharedData, ReadableAccount, WritableAccount}, account_info::AccountInfo, - clock::{Clock, Slot}, + clock::Slot, compute_budget::ComputeBudget, entrypoint::{ProgramResult, SUCCESS}, - epoch_schedule::EpochSchedule, fee_calculator::{FeeCalculator, FeeRateGovernor}, genesis_config::{ClusterType, GenesisConfig}, hash::Hash, @@ -38,11 +35,7 @@ use { pubkey::Pubkey, rent::Rent, signature::{Keypair, Signer}, - sysvar::{ - clock, epoch_schedule, - fees::{self}, - rent, Sysvar, SysvarId, - }, + sysvar::{Sysvar, SysvarId}, }, solana_vote_program::vote_state::{VoteState, VoteStateVersions}, std::{ @@ -209,8 +202,8 @@ macro_rules! processor { }; } -fn get_sysvar( - id: &Pubkey, +fn get_sysvar( + sysvar: Result, InstructionError>, var_addr: *mut u8, ) -> u64 { let invoke_context = get_invoke_context(); @@ -225,9 +218,9 @@ fn get_sysvar( panic!("Exceeded compute budget"); } - match invoke_context.get_sysvar::(id) { + match sysvar { Ok(sysvar_data) => unsafe { - *(var_addr as *mut _ as *mut T) = sysvar_data; + *(var_addr as *mut _ as *mut T) = T::clone(&sysvar_data); SUCCESS }, Err(_) => UNSUPPORTED_SYSVAR, @@ -341,20 +334,26 @@ impl solana_sdk::program_stubs::SyscallStubs for SyscallStubs { } fn sol_get_clock_sysvar(&self, var_addr: *mut u8) -> u64 { - get_sysvar::(&clock::id(), var_addr) + get_sysvar( + get_invoke_context().get_sysvar_cache().get_clock(), + var_addr, + ) } fn sol_get_epoch_schedule_sysvar(&self, var_addr: *mut u8) -> u64 { - get_sysvar::(&epoch_schedule::id(), var_addr) + get_sysvar( + get_invoke_context().get_sysvar_cache().get_epoch_schedule(), + var_addr, + ) } #[allow(deprecated)] fn sol_get_fees_sysvar(&self, var_addr: *mut u8) -> u64 { - get_sysvar::(&fees::id(), var_addr) + get_sysvar(get_invoke_context().get_sysvar_cache().get_fees(), var_addr) } fn sol_get_rent_sysvar(&self, var_addr: *mut u8) -> u64 { - get_sysvar::(&rent::id(), var_addr) + get_sysvar(get_invoke_context().get_sysvar_cache().get_rent(), var_addr) } } diff --git a/programs/address-lookup-table/src/processor.rs b/programs/address-lookup-table/src/processor.rs index 11c7b57b48..f5e810b876 100644 --- a/programs/address-lookup-table/src/processor.rs +++ b/programs/address-lookup-table/src/processor.rs @@ -15,13 +15,7 @@ use { keyed_account::keyed_account_at_index, program_utils::limited_deserialize, pubkey::{Pubkey, PUBKEY_BYTES}, - slot_hashes::SlotHashes, system_instruction, - sysvar::{ - clock::{self, Clock}, - rent::{self, Rent}, - slot_hashes, - }, }, std::convert::TryFrom, }; @@ -92,7 +86,7 @@ impl Processor { })?; let derivation_slot = { - let slot_hashes: SlotHashes = invoke_context.get_sysvar(&slot_hashes::id())?; + let slot_hashes = invoke_context.get_sysvar_cache().get_slot_hashes()?; if slot_hashes.get(&untrusted_recent_slot).is_some() { Ok(untrusted_recent_slot) } else { @@ -127,7 +121,7 @@ impl Processor { } let table_account_data_len = LOOKUP_TABLE_META_SIZE; - let rent: Rent = invoke_context.get_sysvar(&rent::id())?; + let rent = invoke_context.get_sysvar_cache().get_rent()?; let required_lamports = rent .minimum_balance(table_account_data_len) .max(1) @@ -281,7 +275,7 @@ impl Processor { return Err(InstructionError::InvalidInstructionData); } - let clock: Clock = invoke_context.get_sysvar(&clock::id())?; + let clock = invoke_context.get_sysvar_cache().get_clock()?; if clock.slot != lookup_table.meta.last_extended_slot { lookup_table.meta.last_extended_slot = clock.slot; lookup_table.meta.last_extended_slot_start_index = @@ -313,7 +307,7 @@ impl Processor { } } - let rent: Rent = invoke_context.get_sysvar(&rent::id())?; + let rent = invoke_context.get_sysvar_cache().get_rent()?; let required_lamports = rent .minimum_balance(new_table_data_len) .max(1) @@ -367,7 +361,7 @@ impl Processor { let mut lookup_table_meta = lookup_table.meta; drop(lookup_table_account_ref); - let clock: Clock = invoke_context.get_sysvar(&clock::id())?; + let clock = invoke_context.get_sysvar_cache().get_clock()?; lookup_table_meta.deactivation_slot = clock.slot; AddressLookupTable::overwrite_meta_data( @@ -420,8 +414,9 @@ impl Processor { return Err(InstructionError::IncorrectAuthority); } - let clock: Clock = invoke_context.get_sysvar(&clock::id())?; - let slot_hashes: SlotHashes = invoke_context.get_sysvar(&slot_hashes::id())?; + let sysvar_cache = invoke_context.get_sysvar_cache(); + let clock = sysvar_cache.get_clock()?; + let slot_hashes = sysvar_cache.get_slot_hashes()?; match lookup_table.meta.status(clock.slot, &slot_hashes) { LookupTableStatus::Activated => { diff --git a/programs/bpf_loader/src/syscalls.rs b/programs/bpf_loader/src/syscalls.rs index 9ecaeb375f..679578e583 100644 --- a/programs/bpf_loader/src/syscalls.rs +++ b/programs/bpf_loader/src/syscalls.rs @@ -20,9 +20,7 @@ use { account::{ReadableAccount, WritableAccount}, account_info::AccountInfo, blake3, bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, - clock::Clock, entrypoint::{BPF_ALIGN_OF_U128, MAX_PERMITTED_DATA_INCREASE, SUCCESS}, - epoch_schedule::EpochSchedule, feature_set::{ self, blake3_syscall_enabled, disable_fees_sysvar, do_support_realloc, fixed_memcpy_nonoverlapping_check, libsecp256k1_0_5_upgrade_enabled, @@ -36,11 +34,10 @@ use { program::MAX_RETURN_DATA, program_stubs::is_nonoverlapping, pubkey::{Pubkey, PubkeyError, MAX_SEEDS, MAX_SEED_LEN}, - rent::Rent, secp256k1_recover::{ Secp256k1RecoverError, SECP256K1_PUBLIC_KEY_LENGTH, SECP256K1_SIGNATURE_LENGTH, }, - sysvar::{self, fees::Fees, Sysvar, SysvarId}, + sysvar::{Sysvar, SysvarId}, transaction_context::InstructionAccount, }, std::{ @@ -50,6 +47,7 @@ use { rc::Rc, slice::from_raw_parts_mut, str::{from_utf8, Utf8Error}, + sync::Arc, }, thiserror::Error as ThisError, }; @@ -1070,8 +1068,8 @@ impl<'a, 'b> SyscallObject for SyscallSha256<'a, 'b> { } } -fn get_sysvar( - id: &Pubkey, +fn get_sysvar( + sysvar: Result, InstructionError>, var_addr: u64, loader_id: &Pubkey, memory_mapping: &MemoryMapping, @@ -1082,9 +1080,8 @@ fn get_sysvar( .consume(invoke_context.get_compute_budget().sysvar_base_cost + size_of::() as u64)?; let var = translate_type_mut::(memory_mapping, var_addr, loader_id)?; - *var = invoke_context - .get_sysvar::(id) - .map_err(SyscallError::InstructionError)?; + let sysvar: Arc = sysvar.map_err(SyscallError::InstructionError)?; + *var = T::clone(sysvar.as_ref()); Ok(SUCCESS) } @@ -1117,8 +1114,8 @@ impl<'a, 'b> SyscallObject for SyscallGetClockSysvar<'a, 'b> { .map_err(SyscallError::InstructionError), result ); - *result = get_sysvar::( - &sysvar::clock::id(), + *result = get_sysvar( + invoke_context.get_sysvar_cache().get_clock(), var_addr, &loader_id, memory_mapping, @@ -1154,8 +1151,8 @@ impl<'a, 'b> SyscallObject for SyscallGetEpochScheduleSysvar<'a, 'b> { .map_err(SyscallError::InstructionError), result ); - *result = get_sysvar::( - &sysvar::epoch_schedule::id(), + *result = get_sysvar( + invoke_context.get_sysvar_cache().get_epoch_schedule(), var_addr, &loader_id, memory_mapping, @@ -1192,8 +1189,8 @@ impl<'a, 'b> SyscallObject for SyscallGetFeesSysvar<'a, 'b> { .map_err(SyscallError::InstructionError), result ); - *result = get_sysvar::( - &sysvar::fees::id(), + *result = get_sysvar( + invoke_context.get_sysvar_cache().get_fees(), var_addr, &loader_id, memory_mapping, @@ -1229,8 +1226,8 @@ impl<'a, 'b> SyscallObject for SyscallGetRentSysvar<'a, 'b> { .map_err(SyscallError::InstructionError), result ); - *result = get_sysvar::( - &sysvar::rent::id(), + *result = get_sysvar( + invoke_context.get_sysvar_cache().get_rent(), var_addr, &loader_id, memory_mapping, @@ -2919,6 +2916,8 @@ impl<'a, 'b> SyscallObject for SyscallLogData<'a, 'b> { #[cfg(test)] mod tests { + #[allow(deprecated)] + use solana_sdk::sysvar::fees::Fees; use { super::*, solana_program_runtime::{invoke_context::InvokeContext, sysvar_cache::SysvarCache}, @@ -2926,7 +2925,11 @@ mod tests { ebpf::HOST_ALIGN, memory_region::MemoryRegion, user_error::UserError, vm::Config, }, solana_sdk::{ - account::AccountSharedData, bpf_loader, fee_calculator::FeeCalculator, hash::hashv, + account::AccountSharedData, + bpf_loader, + fee_calculator::FeeCalculator, + hash::hashv, + sysvar::{clock::Clock, epoch_schedule::EpochSchedule, rent::Rent}, transaction_context::TransactionContext, }, std::{borrow::Cow, str::FromStr}, @@ -3811,13 +3814,10 @@ mod tests { }; 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()); + sysvar_cache.set_clock(src_clock.clone()); + sysvar_cache.set_epoch_schedule(src_epochschedule); + sysvar_cache.set_fees(src_fees.clone()); + sysvar_cache.set_rent(src_rent); let program_id = Pubkey::new_unique(); let mut transaction_context = TransactionContext::new( diff --git a/programs/stake/src/stake_instruction.rs b/programs/stake/src/stake_instruction.rs index 81b943ce47..2d96df19a3 100644 --- a/programs/stake/src/stake_instruction.rs +++ b/programs/stake/src/stake_instruction.rs @@ -17,7 +17,7 @@ use { program::id, state::{Authorized, Lockup}, }, - sysvar::{self, clock::Clock, rent::Rent, stake_history::StakeHistory}, + sysvar::{clock::Clock, rent::Rent, stake_history::StakeHistory}, }, }; @@ -204,11 +204,11 @@ pub fn process_instruction( .feature_set .is_active(&feature_set::stake_program_v4::id()) { - Some(invoke_context.get_sysvar::(&sysvar::clock::id())?) + Some(invoke_context.get_sysvar_cache().get_clock()?) } else { None }; - me.set_lockup(&lockup, &signers, clock.as_ref()) + me.set_lockup(&lockup, &signers, clock.as_deref()) } StakeInstruction::InitializeChecked => { if invoke_context @@ -326,8 +326,8 @@ pub fn process_instruction( epoch: lockup_checked.epoch, custodian, }; - let clock = Some(invoke_context.get_sysvar::(&sysvar::clock::id())?); - me.set_lockup(&lockup, &signers, clock.as_ref()) + let clock = Some(invoke_context.get_sysvar_cache().get_clock()?); + me.set_lockup(&lockup, &signers, clock.as_deref()) } else { Err(InstructionError::InvalidInstructionData) } @@ -355,7 +355,7 @@ mod tests { instruction::{self, LockupArgs}, state::{Authorized, Lockup, StakeAuthorize}, }, - sysvar::stake_history::StakeHistory, + sysvar::{self, stake_history::StakeHistory}, }, std::str::FromStr, }; @@ -438,8 +438,7 @@ mod tests { }) .collect(); let mut sysvar_cache = SysvarCache::default(); - let clock = Clock::default(); - sysvar_cache.push_entry(sysvar::clock::id(), bincode::serialize(&clock).unwrap()); + sysvar_cache.set_clock(Clock::default()); mock_process_instruction_with_sysvars( &id(), Vec::new(), @@ -1189,8 +1188,7 @@ mod tests { .unwrap(); let mut sysvar_cache = SysvarCache::default(); - let clock = Clock::default(); - sysvar_cache.push_entry(sysvar::clock::id(), bincode::serialize(&clock).unwrap()); + sysvar_cache.set_clock(Clock::default()); mock_process_instruction_with_sysvars( &id(), Vec::new(), diff --git a/programs/vote/src/vote_instruction.rs b/programs/vote/src/vote_instruction.rs index 70ffbc700d..1d637bfbff 100644 --- a/programs/vote/src/vote_instruction.rs +++ b/programs/vote/src/vote_instruction.rs @@ -23,9 +23,9 @@ use { program_utils::limited_deserialize, pubkey::Pubkey, system_instruction, - sysvar::{self, clock::Clock, rent::Rent, slot_hashes::SlotHashes, Sysvar}, + sysvar::{self, clock::Clock, rent::Rent, slot_hashes::SlotHashes}, }, - std::collections::HashSet, + std::{collections::HashSet, sync::Arc}, thiserror::Error, }; @@ -391,17 +391,37 @@ fn verify_rent_exemption( } } -/// This method facilitates a transition from fetching sysvars from keyed +/// These methods facilitate a transition from fetching sysvars from keyed /// accounts to fetching from the sysvar cache without breaking consensus. In -/// order to keep consistent behavior, it continues to enforce the same checks +/// order to keep consistent behavior, they continue to enforce the same checks /// as `solana_sdk::keyed_account::from_keyed_account` despite dynamically /// loading them instead of deserializing from account data. -fn get_sysvar_with_keyed_account_check( - keyed_account: &KeyedAccount, - invoke_context: &InvokeContext, -) -> Result { - check_sysvar_keyed_account::(keyed_account)?; - invoke_context.get_sysvar(keyed_account.unsigned_key()) +mod get_sysvar_with_keyed_account_check { + use super::*; + + pub fn clock( + keyed_account: &KeyedAccount, + invoke_context: &InvokeContext, + ) -> Result, InstructionError> { + check_sysvar_keyed_account::(keyed_account)?; + invoke_context.get_sysvar_cache().get_clock() + } + + pub fn rent( + keyed_account: &KeyedAccount, + invoke_context: &InvokeContext, + ) -> Result, InstructionError> { + check_sysvar_keyed_account::(keyed_account)?; + invoke_context.get_sysvar_cache().get_rent() + } + + pub fn slot_hashes( + keyed_account: &KeyedAccount, + invoke_context: &InvokeContext, + ) -> Result, InstructionError> { + check_sysvar_keyed_account::(keyed_account)?; + invoke_context.get_sysvar_cache().get_slot_hashes() + } } pub fn process_instruction( @@ -422,19 +442,19 @@ pub fn process_instruction( let signers: HashSet = get_signers(&keyed_accounts[first_instruction_account..]); match limited_deserialize(data)? { VoteInstruction::InitializeAccount(vote_init) => { - let rent: Rent = get_sysvar_with_keyed_account_check( + let rent = get_sysvar_with_keyed_account_check::rent( keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?, invoke_context, )?; verify_rent_exemption(me, &rent)?; - let clock: Clock = get_sysvar_with_keyed_account_check( + let clock = get_sysvar_with_keyed_account_check::clock( keyed_account_at_index(keyed_accounts, first_instruction_account + 2)?, invoke_context, )?; vote_state::initialize_account(me, &vote_init, &signers, &clock) } VoteInstruction::Authorize(voter_pubkey, vote_authorize) => { - let clock: Clock = get_sysvar_with_keyed_account_check( + let clock = get_sysvar_with_keyed_account_check::clock( keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?, invoke_context, )?; @@ -450,11 +470,11 @@ pub fn process_instruction( } VoteInstruction::Vote(vote) | VoteInstruction::VoteSwitch(vote, _) => { inc_new_counter_info!("vote-native", 1); - let slot_hashes: SlotHashes = get_sysvar_with_keyed_account_check( + let slot_hashes = get_sysvar_with_keyed_account_check::slot_hashes( keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?, invoke_context, )?; - let clock: Clock = get_sysvar_with_keyed_account_check( + let clock = get_sysvar_with_keyed_account_check::clock( keyed_account_at_index(keyed_accounts, first_instruction_account + 2)?, invoke_context, )?; @@ -467,12 +487,13 @@ pub fn process_instruction( .is_active(&feature_set::allow_votes_to_directly_update_vote_state::id()) { inc_new_counter_info!("vote-state-native", 1); - let slot_hashes: SlotHashes = - invoke_context.get_sysvar(&sysvar::slot_hashes::id())?; + let sysvar_cache = invoke_context.get_sysvar_cache(); + let slot_hashes = sysvar_cache.get_slot_hashes()?; + let clock = sysvar_cache.get_clock()?; vote_state::process_vote_state_update( me, slot_hashes.slot_hashes(), - &invoke_context.get_sysvar(&sysvar::clock::id())?, + &clock, vote_state_update, &signers, ) @@ -486,11 +507,11 @@ pub fn process_instruction( .feature_set .is_active(&feature_set::reject_non_rent_exempt_vote_withdraws::id()) { - Some(invoke_context.get_sysvar(&sysvar::rent::id())?) + Some(invoke_context.get_sysvar_cache().get_rent()?) } else { None }; - vote_state::withdraw(me, lamports, to, &signers, rent_sysvar) + vote_state::withdraw(me, lamports, to, &signers, rent_sysvar.as_deref()) } VoteInstruction::AuthorizeChecked(vote_authorize) => { if invoke_context @@ -583,15 +604,9 @@ mod tests { }) .collect(); let mut sysvar_cache = SysvarCache::default(); - let rent = Rent::free(); - sysvar_cache.push_entry(sysvar::rent::id(), bincode::serialize(&rent).unwrap()); - let clock = Clock::default(); - sysvar_cache.push_entry(sysvar::clock::id(), bincode::serialize(&clock).unwrap()); - let slot_hashes = SlotHashes::default(); - sysvar_cache.push_entry( - sysvar::slot_hashes::id(), - bincode::serialize(&slot_hashes).unwrap(), - ); + sysvar_cache.set_rent(Rent::free()); + sysvar_cache.set_clock(Clock::default()); + sysvar_cache.set_slot_hashes(SlotHashes::default()); mock_process_instruction_with_sysvars( &id(), Vec::new(), diff --git a/programs/vote/src/vote_state/mod.rs b/programs/vote/src/vote_state/mod.rs index db7c623ac0..728fe17632 100644 --- a/programs/vote/src/vote_state/mod.rs +++ b/programs/vote/src/vote_state/mod.rs @@ -973,7 +973,7 @@ pub fn withdraw( lamports: u64, to_account: &KeyedAccount, signers: &HashSet, - rent_sysvar: Option, + rent_sysvar: Option<&Rent>, ) -> Result<(), InstructionError> { let vote_state: VoteState = State::::state(vote_account)?.convert_to_current(); @@ -2072,7 +2072,7 @@ mod tests { &RefCell::new(AccountSharedData::default()), ), &signers, - Some(rent_sysvar), + Some(&rent_sysvar), ); assert_eq!(res, Err(InstructionError::InsufficientFunds)); } @@ -2095,7 +2095,7 @@ mod tests { withdraw_lamports, &KeyedAccount::new(&solana_sdk::pubkey::new_rand(), false, &to_account), &signers, - Some(rent_sysvar), + Some(&rent_sysvar), ); assert_eq!(res, Ok(())); assert_eq!( @@ -2108,7 +2108,7 @@ mod tests { // full withdraw, before/after activation { let rent_sysvar = Rent::default(); - for rent_sysvar in [None, Some(rent_sysvar)] { + for rent_sysvar in [None, Some(&rent_sysvar)] { let to_account = RefCell::new(AccountSharedData::default()); let (vote_pubkey, vote_account) = create_test_account(); let lamports = vote_account.borrow().lamports(); diff --git a/runtime/benches/bank.rs b/runtime/benches/bank.rs index b4fa9962fc..f680bde5fa 100644 --- a/runtime/benches/bank.rs +++ b/runtime/benches/bank.rs @@ -17,6 +17,10 @@ use { signature::{Keypair, Signer}, transaction::Transaction, }, + solana_vote_program::{ + vote_instruction, + vote_state::{Vote, VoteInit}, + }, std::{sync::Arc, thread::sleep, time::Duration}, test::Bencher, }; @@ -62,6 +66,52 @@ pub fn create_builtin_transactions( .collect() } +pub fn create_vote_transactions( + bank_client: &BankClient, + mint_keypair: &Keypair, +) -> Vec { + let blockhash = bank_client.get_latest_blockhash().unwrap(); + (0..4096) + .map(|_| { + // Seed the signer account + let payer_keypair = Keypair::new(); + bank_client + .transfer_and_confirm(27_000_000, mint_keypair, &payer_keypair.pubkey()) + .unwrap_or_else(|_| panic!("{}:{}", line!(), file!())); + + // Setup vote + let vote_keypair = Keypair::new(); + let instructions = vote_instruction::create_account( + &payer_keypair.pubkey(), + &vote_keypair.pubkey(), + &VoteInit { + node_pubkey: payer_keypair.pubkey(), + authorized_voter: payer_keypair.pubkey(), + authorized_withdrawer: payer_keypair.pubkey(), + commission: 100u8, + }, + 26_858_640, + ); + let message = Message::new(&instructions, Some(&payer_keypair.pubkey())); + bank_client + .send_and_confirm_message(&[&payer_keypair, &vote_keypair], message) + .unwrap(); + + let vote_ix = vote_instruction::vote( + &vote_keypair.pubkey(), + &payer_keypair.pubkey(), + Vote { + slots: vec![0], + hash: blockhash, + timestamp: None, + }, + ); + let message = Message::new(&[vote_ix], Some(&payer_keypair.pubkey())); + Transaction::new(&[&payer_keypair], message, blockhash) + }) + .collect() +} + pub fn create_native_loader_transactions( bank_client: &BankClient, mint_keypair: &Keypair, @@ -123,9 +173,14 @@ fn do_bench_transactions( ) { solana_logger::setup(); let ns_per_s = 1_000_000_000; - let (mut genesis_config, mint_keypair) = create_genesis_config(100_000_000); + let (mut genesis_config, mint_keypair) = create_genesis_config(100_000_000_000_000); genesis_config.ticks_per_slot = 100; - let mut bank = Bank::new_for_benches(&genesis_config); + + let bank = Bank::new_for_benches(&genesis_config); + // freeze bank so that slot hashes is populated + bank.freeze(); + + let mut bank = Bank::new_from_parent(&Arc::new(bank), &Pubkey::default(), 1); bank.add_builtin( "builtin_program", &Pubkey::new(&BUILTIN_PROGRAM_ID), @@ -166,6 +221,12 @@ fn bench_bank_sync_process_native_loader_transactions(bencher: &mut Bencher) { do_bench_transactions(bencher, &sync_bencher, &create_native_loader_transactions); } +#[bench] +#[ignore] +fn bench_bank_sync_process_vote_transactions(bencher: &mut Bencher) { + do_bench_transactions(bencher, &sync_bencher, &create_vote_transactions); +} + #[bench] #[ignore] fn bench_bank_async_process_builtin_transactions(bencher: &mut Bencher) { diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 131c9b4027..90176b9a9c 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -1439,7 +1439,7 @@ impl Bank { bank.update_rent(); bank.update_epoch_schedule(); bank.update_recent_blockhashes(); - bank.fill_sysvar_cache(); + bank.fill_missing_sysvar_cache_entries(); bank } @@ -1626,7 +1626,7 @@ impl Bank { new.update_stake_history(Some(parent_epoch)); new.update_clock(Some(parent_epoch)); new.update_fees(); - new.fill_sysvar_cache(); + new.fill_missing_sysvar_cache_entries(); time.stop(); @@ -1697,7 +1697,7 @@ impl Bank { new.inherit_specially_retained_account_fields(account), ) }); - + new.fill_missing_sysvar_cache_entries(); new.freeze(); new } @@ -1954,10 +1954,6 @@ impl Bank { } self.store_account_and_update_capitalization(pubkey, &new_account); - - // Update the entry in the cache - let mut sysvar_cache = self.sysvar_cache.write().unwrap(); - sysvar_cache.update_entry(pubkey, &new_account); } fn inherit_specially_retained_account_fields( @@ -2077,6 +2073,10 @@ impl Bank { self.inherit_specially_retained_account_fields(account), ) }); + // Simply force fill sysvar cache rather than checking which sysvar was + // actually updated since tests don't need to be optimized for performance. + self.reset_sysvar_cache(); + self.fill_missing_sysvar_cache_entries(); } fn update_slot_history(&self) { @@ -3624,9 +3624,13 @@ impl Bank { return Err(TransactionError::UnsupportedVersion); } - let slot_hashes: SlotHashes = self - .get_cached_sysvar(&sysvar::slot_hashes::id()) - .ok_or(TransactionError::AccountNotFound)?; + let slot_hashes = self + .sysvar_cache + .read() + .unwrap() + .get_slot_hashes() + .map_err(|_| TransactionError::AccountNotFound)?; + Ok(address_table_lookups .iter() .map(|address_table_lookup| { diff --git a/runtime/src/bank/sysvar_cache.rs b/runtime/src/bank/sysvar_cache.rs index c050276144..f4262027c6 100644 --- a/runtime/src/bank/sysvar_cache.rs +++ b/runtime/src/bank/sysvar_cache.rs @@ -1,33 +1,149 @@ use { super::Bank, - solana_sdk::{ - account::ReadableAccount, - pubkey::Pubkey, - sysvar::{self, Sysvar}, - }, + solana_program_runtime::sysvar_cache::SysvarCache, + solana_sdk::{account::ReadableAccount, sysvar::Sysvar}, }; impl Bank { - pub(crate) fn fill_sysvar_cache(&mut self) { + pub(crate) fn fill_missing_sysvar_cache_entries(&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()); - } + if sysvar_cache.get_clock().is_err() { + if let Some(clock) = self.load_sysvar_account() { + sysvar_cache.set_clock(clock); + } + } + if sysvar_cache.get_epoch_schedule().is_err() { + if let Some(epoch_schedule) = self.load_sysvar_account() { + sysvar_cache.set_epoch_schedule(epoch_schedule); + } + } + #[allow(deprecated)] + if sysvar_cache.get_fees().is_err() { + if let Some(fees) = self.load_sysvar_account() { + sysvar_cache.set_fees(fees); + } + } + if sysvar_cache.get_rent().is_err() { + if let Some(rent) = self.load_sysvar_account() { + sysvar_cache.set_rent(rent); + } + } + if sysvar_cache.get_slot_hashes().is_err() { + if let Some(slot_hashes) = self.load_sysvar_account() { + sysvar_cache.set_slot_hashes(slot_hashes); } } } - /// Get the value of a cached sysvar by its id - pub fn get_cached_sysvar(&self, id: &Pubkey) -> Option { - let sysvar_cache = self.sysvar_cache.read().unwrap(); - sysvar_cache.iter().find_map(|(key, data)| { - if id == key { - bincode::deserialize(data).ok() - } else { - None - } - }) + pub(crate) fn reset_sysvar_cache(&self) { + let mut sysvar_cache = self.sysvar_cache.write().unwrap(); + *sysvar_cache = SysvarCache::default(); + } + + fn load_sysvar_account(&self) -> Option { + if let Some(account) = self.get_account_with_fixed_root(&T::id()) { + bincode::deserialize(account.data()).ok() + } else { + None + } + } +} + +#[cfg(test)] +mod tests { + use { + super::*, + solana_sdk::{genesis_config::create_genesis_config, pubkey::Pubkey}, + std::sync::Arc, + }; + + #[test] + #[allow(deprecated)] + fn test_sysvar_cache_initialization() { + let (genesis_config, _mint_keypair) = create_genesis_config(100_000); + let bank0 = Arc::new(Bank::new_for_tests(&genesis_config)); + + let bank0_sysvar_cache = bank0.sysvar_cache.read().unwrap(); + let bank0_cached_clock = bank0_sysvar_cache.get_clock(); + let bank0_cached_epoch_schedule = bank0_sysvar_cache.get_epoch_schedule(); + let bank0_cached_fees = bank0_sysvar_cache.get_fees(); + let bank0_cached_rent = bank0_sysvar_cache.get_rent(); + + assert!(bank0_cached_clock.is_ok()); + assert!(bank0_cached_epoch_schedule.is_ok()); + assert!(bank0_cached_fees.is_ok()); + assert!(bank0_cached_rent.is_ok()); + assert!(bank0 + .sysvar_cache + .read() + .unwrap() + .get_slot_hashes() + .is_err()); + + let bank1 = Bank::new_from_parent(&bank0, &Pubkey::default(), bank0.slot() + 1); + + let bank1_sysvar_cache = bank1.sysvar_cache.read().unwrap(); + let bank1_cached_clock = bank1_sysvar_cache.get_clock(); + let bank1_cached_epoch_schedule = bank0_sysvar_cache.get_epoch_schedule(); + let bank1_cached_fees = bank0_sysvar_cache.get_fees(); + let bank1_cached_rent = bank0_sysvar_cache.get_rent(); + + assert!(bank1_cached_clock.is_ok()); + assert!(bank1_cached_epoch_schedule.is_ok()); + assert!(bank1_cached_fees.is_ok()); + assert!(bank1_cached_rent.is_ok()); + assert!(bank1.sysvar_cache.read().unwrap().get_slot_hashes().is_ok()); + + assert_ne!(bank0_cached_clock, bank1_cached_clock); + assert_eq!(bank0_cached_epoch_schedule, bank1_cached_epoch_schedule); + assert_eq!(bank0_cached_fees, bank1_cached_fees); + assert_eq!(bank0_cached_rent, bank1_cached_rent); + } + + #[test] + #[allow(deprecated)] + fn test_reset_and_fill_sysvar_cache() { + let (genesis_config, _mint_keypair) = create_genesis_config(100_000); + let bank0 = Arc::new(Bank::new_for_tests(&genesis_config)); + let bank1 = Bank::new_from_parent(&bank0, &Pubkey::default(), bank0.slot() + 1); + + let bank1_sysvar_cache = bank1.sysvar_cache.read().unwrap(); + let bank1_cached_clock = bank1_sysvar_cache.get_clock(); + let bank1_cached_epoch_schedule = bank1_sysvar_cache.get_epoch_schedule(); + let bank1_cached_fees = bank1_sysvar_cache.get_fees(); + let bank1_cached_rent = bank1_sysvar_cache.get_rent(); + let bank1_cached_slot_hashes = bank1_sysvar_cache.get_slot_hashes(); + + assert!(bank1_cached_clock.is_ok()); + assert!(bank1_cached_epoch_schedule.is_ok()); + assert!(bank1_cached_fees.is_ok()); + assert!(bank1_cached_rent.is_ok()); + assert!(bank1_cached_slot_hashes.is_ok()); + + drop(bank1_sysvar_cache); + bank1.reset_sysvar_cache(); + + let bank1_sysvar_cache = bank1.sysvar_cache.read().unwrap(); + assert!(bank1_sysvar_cache.get_clock().is_err()); + assert!(bank1_sysvar_cache.get_epoch_schedule().is_err()); + assert!(bank1_sysvar_cache.get_fees().is_err()); + assert!(bank1_sysvar_cache.get_rent().is_err()); + assert!(bank1_sysvar_cache.get_slot_hashes().is_err()); + + drop(bank1_sysvar_cache); + bank1.fill_missing_sysvar_cache_entries(); + + let bank1_sysvar_cache = bank1.sysvar_cache.read().unwrap(); + assert_eq!(bank1_sysvar_cache.get_clock(), bank1_cached_clock); + assert_eq!( + bank1_sysvar_cache.get_epoch_schedule(), + bank1_cached_epoch_schedule + ); + assert_eq!(bank1_sysvar_cache.get_fees(), bank1_cached_fees); + assert_eq!(bank1_sysvar_cache.get_rent(), bank1_cached_rent); + assert_eq!( + bank1_sysvar_cache.get_slot_hashes(), + bank1_cached_slot_hashes + ); } }